From 1f535f6941fa3990b99bc0846dafa879f35e52bd Mon Sep 17 00:00:00 2001 From: SisMaker <1713699517@qq.com> Date: Sun, 20 Jun 2021 00:02:43 +0800 Subject: [PATCH] =?UTF-8?q?ft:=20=E9=83=A8=E5=88=86=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 35 ++++++--- src/callgrind/tpCallGrind.erl | 4 +- src/eTpf.erl | 9 +++ src/eTpf_app.erl | 1 - src/flame/tpFlame.erl | 4 +- src/gcColl/tpGcColl.erl | 4 +- src/messages/tpMsgRS.erl | 4 +- src/messages/tpMsgSD.erl | 4 +- src/tracer/tpTracerFile.erl | 4 +- src/tracer/tpTracerShell.erl | 1 + src/tracer/tpTracerSocket.erl | 6 +- src/utils/tpSocketCli.erl | 10 +-- src/utils/tpTermCut.erl | 130 +++++++++++++++++----------------- 13 files changed, 121 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 62869b2..6ac3069 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ Useage oooo +相关软件 +----- +[qcachegrind](http://47.108.26.175:53000/SisMaker/eTpfSoftware) +[graphviz](http://47.108.26.175:53000/SisMaker/eTpfSoftware) +[FlameGraph](https://github.com/brendangregg/FlameGraph) +[seqdiag](http://blockdiag.com/en/seqdiag/) + + ## 介绍 eTpf是Erlang/OTP的跟踪器和分析器。 旨在提供一种非常有效的工具, 可在开发和生产环境中使用, 并且即使在繁忙的系统上也能够长时间运行 需要使用cachegrind工具从中读取输出lg_callgrind。 @@ -255,7 +263,7 @@ eTpf的主要目的是对Erlang应用程序进行性能分析。这是通过首 快速开始 假设您使用概要文件模式和运行标志生成了跟踪文件(如Tracer一章中所述),则可以使用以下命令生成callgrind.out文件: ``` -1> tpCallgrind:profile_many("traces.lz4.*", "callgrind.out", #{running => true}). +1> tpCallgrind:pfm("traces.lz4.*", "callgrind.out", #{running => true}). ``` 这将为您生成的所有跟踪文件创建一个callgrind.out文件。例如,如果您具有“ traces.lz4.1”和“ traces.lz4.2”,则现在还应该具有“ callgrind.out.1”和“ callgrind.out.2”。 @@ -267,22 +275,22 @@ $ qcachegrind callgrind.out 它将自动检测并打开所有与该callgrind.out.*模式匹配的文件。 -Profiling one file 您可以通过调用函数来分析一个文件 `tpCallgrind:utils/2,3`。它包含跟踪文件名,输出文件名和一个可选的选项映射: +Profiling one file 您可以通过调用函数来分析一个文件 `tpCallgrind:pfs/2,3`。它包含跟踪文件名,输出文件名和一个可选的选项映射: ``` -1> tpCallgrind:utils("traces.lz4.1", "callgrind.out.1"). +1> tpCallgrind:pfs("traces.lz4.1", "callgrind.out.1"). ``` 它还接受以下选项: ``` -1> tpCallgrind:utils("traces.lz4.1", "callgrind.out.1", #{running => true}). +1> tpCallgrind:pfs("traces.lz4.1", "callgrind.out.1", #{running => true}). ``` Profiling many files 便利功能可用于一次分析许多文件:`tpCallgrind:profile_many/2,3`, 它以通配符模式作为第一个参数,并以文件名前缀作为第二个参数: ``` -1> tpCallgrind:profile_many("traces.lz4.*", "callgrind.out"). +1> tpCallgrind:pfm("traces.lz4.*", "callgrind.out"). ``` 如果有两个跟踪文件,这将导致两个'callgrind.out'文件:'callgrind.out.1'和'callgrind.out.2'。 @@ -290,13 +298,13 @@ Profiling many files 便利功能可用于一次分析许多文件:`tpCallgrin 它还接受以下选项: ``` -1> tpCallgrind:profile_many("traces.lz4.*", "callgrind.out", #{running => true}). +1> tpCallgrind:pfm("traces.lz4.*", "callgrind.out", #{running => true}). ``` Running information 当跟踪文件包含运行信息时,这意味着它们是在running启用了标记的情况下创建的,您还需要将running标记传递给事件探查器,以使该信息在'callgrind.out'文件中可用: ``` -1> tpCallgrind:profile_many("traces.lz4.*", "callgrind.out", #{running => true}). +1> tpCallgrind:pfm("traces.lz4.*", "callgrind.out", #{running => true}). ``` Scope 默认情况下,跟踪事件的范围是全局的。这意味着cachegrind工具会将所有事件组合在一起,无论它们发生在何处。这对于查看哪些功能总体上占用最多资源很有用。 @@ -304,7 +312,7 @@ Scope 默认情况下,跟踪事件的范围是全局的。这意味着cachegri 其他时间,您可能想查看哪些进程占用最多的资源。为此,您需要指示eTpf在生成“ callgrind.out”文件时保留过程信息。使用以下scope选项完成此操作: ``` -1> lg_callgrind:profile_many("traces.lz4.*", "callgrind.out", #{scope => per_process}). +1> lg_callgrind:pfm("traces.lz4.*", "callgrind.out", #{scope => per_process}). ``` 使用cachegrind工具 当将cachegrind工具与Looking Glass生成的输出一起使用时,需要注意一些陷阱。 @@ -502,6 +510,17 @@ activation = none; 序列 图1.示例输出 +执行 seqdiag 命令 +$ seqdiag simple.diag +$ ls simple.png +simple.png +如果您想要 SVG 图像,请使用 -T 选项 + +$ seqdiag -Tsvg simple.diag +$ ls simple.svg +simple.svg + + 识别过程 Looking Glass将显示每个过程的pid和一个示例消息,但识别哪个过程并不总是理想的。 为了解决这个问题,Looking Glass提供了一个简单的解决方案:lg在运行跟踪程序时将消息发送到指定的进程。 Looking diff --git a/src/callgrind/tpCallGrind.erl b/src/callgrind/tpCallGrind.erl index 9e925aa..af689a5 100644 --- a/src/callgrind/tpCallGrind.erl +++ b/src/callgrind/tpCallGrind.erl @@ -7,8 +7,8 @@ -export([pfm/4]). -type opts() :: #{ -scope => global | per_process, %% Whether we filter the output per process. -running => boolean() %% Whether we compute and save wait times. + scope => global | per_process, %% Whether we filter the output per process. + running => boolean() %% Whether we compute and save wait times. }. -record(call, { diff --git a/src/eTpf.erl b/src/eTpf.erl index a50a64b..2bafac0 100644 --- a/src/eTpf.erl +++ b/src/eTpf.erl @@ -29,6 +29,9 @@ , cgPfm/3 , cgPfm/4 + , gcPfs/1 + , gcPfm/2 + %% trace函数 , trace/1 , trace/2 @@ -87,6 +90,12 @@ cgPfm(Wildcard, Cwd, Prefix) -> cgPfm(Wildcard, Cwd, Prefix, Opts) -> tpCallGrind:pfm(Wildcard, Cwd, Prefix, Opts). +gcPfs(InputFile) -> + tpGcColl:pfs(InputFile). + +gcPfm(InputFiles, Cwd) -> + tpGcColl:pfm(InputFiles, Cwd). + -spec trace(userInput()) -> ok. trace(Input) -> trace(Input, tpTracerShell). diff --git a/src/eTpf_app.erl b/src/eTpf_app.erl index 1322a0e..bfd7d50 100644 --- a/src/eTpf_app.erl +++ b/src/eTpf_app.erl @@ -1,5 +1,4 @@ -module(eTpf_app). - -behaviour(application). -export([start/2, stop/1]). diff --git a/src/flame/tpFlame.erl b/src/flame/tpFlame.erl index 92902c9..3d5ffcf 100644 --- a/src/flame/tpFlame.erl +++ b/src/flame/tpFlame.erl @@ -1,8 +1,8 @@ -module(tpFlame). %% 火焰图分析 -export([ - pfs/2 %% 分析单个文件 - , pfm/3 %% 分析多个文件 + pfs/2 + , pfm/3 ]). -record(state, { diff --git a/src/gcColl/tpGcColl.erl b/src/gcColl/tpGcColl.erl index 1e9841c..ab08be2 100644 --- a/src/gcColl/tpGcColl.erl +++ b/src/gcColl/tpGcColl.erl @@ -3,8 +3,8 @@ -include("eTpf.hrl"). -export([ - pfs/1 %% 分析单个文件 - , pfm/2 %% 分析多个文件 + pfs/1 + , pfm/2 ]). -record(state, { diff --git a/src/messages/tpMsgRS.erl b/src/messages/tpMsgRS.erl index 17f555c..880d005 100644 --- a/src/messages/tpMsgRS.erl +++ b/src/messages/tpMsgRS.erl @@ -3,8 +3,8 @@ -include("eTpf.hrl"). -export([ - pfs/1 %% 分析单个文件 - , pfm/2 %% 分析多个文件 + pfs/1 + , pfm/2 ]). -record(state, { diff --git a/src/messages/tpMsgSD.erl b/src/messages/tpMsgSD.erl index c554162..7bd584a 100644 --- a/src/messages/tpMsgSD.erl +++ b/src/messages/tpMsgSD.erl @@ -3,8 +3,8 @@ -include("eTpf.hrl"). -export([ - pfs/2 %% 分析单个文件 - , pfm/3 %% 分析多个文件 + pfs/2 + , pfm/3 ]). -record(state, { diff --git a/src/tracer/tpTracerFile.erl b/src/tracer/tpTracerFile.erl index 9bb66ce..f421385 100644 --- a/src/tracer/tpTracerFile.erl +++ b/src/tracer/tpTracerFile.erl @@ -16,8 +16,8 @@ -record(state, { parent :: pid() - , fDir :: file:filename_all() %% file base name - , fBaseName :: file:filename_all() %% file base name + , fDir :: file:filename_all() + , fBaseName :: file:filename_all() , size = 0 :: non_neg_integer() , fMaxSize :: infinity | non_neg_integer() , ioDevice :: file:io_device() diff --git a/src/tracer/tpTracerShell.erl b/src/tracer/tpTracerShell.erl index 766c621..698ed30 100644 --- a/src/tracer/tpTracerShell.erl +++ b/src/tracer/tpTracerShell.erl @@ -18,6 +18,7 @@ start_link(_TracerOpts) -> init(Parent) -> process_flag(message_queue_data, off_heap), + process_flag(trap_exit, true), ?MODULE:loop(Parent). loop(Parent) -> diff --git a/src/tracer/tpTracerSocket.erl b/src/tracer/tpTracerSocket.erl index 6bcd321..9513b15 100644 --- a/src/tracer/tpTracerSocket.erl +++ b/src/tracer/tpTracerSocket.erl @@ -84,12 +84,12 @@ trace_loop(State = #state{parent = Parent, timerRef = TRef}, CSocket) -> ?MODULE:trace_loop(State, CSocket); Msg -> Bin = term_to_binary(Msg), - _ = byte_size(Bin), + %% _ = byte_size(Bin), case erlang:port_command(CSocket, <>, [nosuspend]) of true -> ?MODULE:trace_loop(State, CSocket); %% The send buffer is full. - false -> + _ -> close(State, CSocket) end end. @@ -98,7 +98,7 @@ close(State, CSocket) -> _ = gen_tcp:close(CSocket), accept(cancel_timeout(State)). -system_continue(_, _, {LoopTag = accept_loop, State, LoopArgs}) -> +system_continue(_, _, {LoopTag, State, LoopArgs}) -> case LoopTag of accept_loop -> ?MODULE:accept_loop(State, LoopArgs); diff --git a/src/utils/tpSocketCli.erl b/src/utils/tpSocketCli.erl index 37d392a..bdec144 100644 --- a/src/utils/tpSocketCli.erl +++ b/src/utils/tpSocketCli.erl @@ -15,7 +15,7 @@ -record(state, { port :: inet:port_number(), - baseFileName :: file:filename_all(), + fBaseName :: file:filename_all(), nth = 0 :: non_neg_integer(), socket :: inet:socket() | undefined, ioDevice :: file:io_device() | undefined, @@ -30,10 +30,10 @@ start_link(Port, BaseFilename) -> stop(Pid) -> gen_srv:stop(Pid). -init([Port, BaseFilename]) -> +init([Port, FBaseName]) -> process_flag(message_queue_data, off_heap), process_flag(trap_exit, true), - {ok, #state{port = Port, baseFileName = BaseFilename}, {nTimeout, connect, 0, doConnect}}. + {ok, #state{port = Port, fBaseName = FBaseName}, {nTimeout, connect, 0, doConnect}}. handleCall(_Msg, _State, _FROM) -> {reply, ignored}. @@ -56,10 +56,10 @@ handleInfo({tcp_closed, _Socket}, State) -> {noreply, clearSet(State), {nTimeout, connect, 0, doConnect}}; handleInfo({tcp_error, _Socket, _}, State) -> {noreply, clearSet(State), {nTimeout, connect, 0, doConnect}}; -handleInfo(doConnect, #state{port = Port, baseFileName = Filename0, nth = Nth} = State) -> +handleInfo(doConnect, #state{port = Port, fBaseName = FBaseName, nth = Nth} = State) -> case gen_tcp:connect("localhost", Port, [binary, {packet, 2}, {active, true}]) of {ok, Socket} -> - Filename = filename:flatten([Filename0, ".", integer_to_list(Nth)]), + Filename = filename:flatten([FBaseName, ".", integer_to_list(Nth)]), {ok, IoDevice} = file:open(Filename, [write, raw]), {noreply, State#state{socket = Socket, nth = Nth + 1, ioDevice = IoDevice}}; {error, _} -> diff --git a/src/utils/tpTermCut.erl b/src/utils/tpTermCut.erl index dd4a961..103ba67 100644 --- a/src/utils/tpTermCut.erl +++ b/src/utils/tpTermCut.erl @@ -1,92 +1,90 @@ -module(tpTermCut). -include("eTpf.hrl"). -%% 现在要使用硬编码的值。我们不能花时间在记录或地图中查找。 - -export([cut/1]). -export([cut/2]). getTcmValue(Key) -> - case get(Key) of - undefined -> - maps:get(Key, ?defTcmMap); - Value -> - Value - end. + case get(Key) of + undefined -> + maps:get(Key, ?defTcmMap); + Value -> + Value + end. cut(Term) -> - cut(Term, 1). + cut(Term, 1). cut(Term, Depth) -> - TcmDepth = getTcmValue(tcmDepth), - case Depth > TcmDepth of - true -> - '$truncated'; - _ -> - if - is_bitstring(Term) -> - case bit_size(Term) > getTcmValue(tcmBitSize) of - true -> - TcmBinSize = getTcmValue(tcmBinSize), - <> = Term, - <>; - _ -> - Term - end; - is_list(Term) -> - cutList(Term, Depth, 0, getTcmValue(tcmListSize), getTcmValue(tcmNestStruct), 0, []); - is_map(Term), Depth =:= TcmDepth -> - #{'$truncated' => '$truncated'}; - is_map(Term) -> - maps:from_list(cutMap(maps_to_list(Term, getTcmValue(tcmMapSize)), Depth, getTcmValue(tcmNestStruct), 0)); - is_tuple(Term), Depth =:= TcmDepth -> - {'$truncated'}; - is_tuple(Term) -> - list_to_tuple(cutList(tuple_to_list(Term), Depth, 0, getTcmValue(tcmTupleSize), getTcmValue(tcmNestStruct), 0, [])); - true -> - Term - end - end. + TcmDepth = getTcmValue(tcmDepth), + case Depth > TcmDepth of + true -> + '$truncated'; + _ -> + if + is_bitstring(Term) -> + case bit_size(Term) > getTcmValue(tcmBitSize) of + true -> + TcmBinSize = getTcmValue(tcmBinSize), + <> = Term, + <>; + _ -> + Term + end; + is_list(Term) -> + cutList(Term, Depth, 0, getTcmValue(tcmListSize), getTcmValue(tcmNestStruct), 0, []); + is_map(Term), Depth =:= TcmDepth -> + #{'$truncated' => '$truncated'}; + is_map(Term) -> + maps:from_list(cutMap(mapsToList(Term, getTcmValue(tcmMapSize)), Depth, getTcmValue(tcmNestStruct), 0)); + is_tuple(Term), Depth =:= TcmDepth -> + {'$truncated'}; + is_tuple(Term) -> + list_to_tuple(cutList(tuple_to_list(Term), Depth, 0, getTcmValue(tcmTupleSize), getTcmValue(tcmNestStruct), 0, [])); + true -> + Term + end + end. cutList([], _, _, _, _, _, Acc) -> - lists:reverse(Acc); + lists:reverse(Acc); cutList([Term | Tail], Depth, Len, TcmTupleSize, TcmNestStruct, NumStructs, Acc) -> - case Len >= TcmTupleSize orelse NumStructs >= TcmNestStruct of - true -> - lists:reverse(['$truncated' | Acc]); - _ -> - %% if List was a cons, Tail can be anything - cutList(Tail, Depth, Len + 1, TcmTupleSize, TcmNestStruct, NumStructs + isStruct(Term), [cut(Term, Depth + 1) | Acc]) - end; + case Len >= TcmTupleSize orelse NumStructs >= TcmNestStruct of + true -> + lists:reverse(['$truncated' | Acc]); + _ -> + %% if List was a cons, Tail can be anything + cutList(Tail, Depth, Len + 1, TcmTupleSize, TcmNestStruct, NumStructs + isStruct(Term), [cut(Term, Depth + 1) | Acc]) + end; cutList(Term, Depth, _, _, _, _, Acc) -> %% if List was a cons [[[a,a,a|b],1,2,3]|bbbb] - lists:reverse(Acc) ++ cut(Term, Depth + 1). + lists:reverse(Acc) ++ cut(Term, Depth + 1). cutMap([], _, _, _) -> - []; + []; cutMap(_, _, TcmNestStruct, NumStructs) when NumStructs > TcmNestStruct -> - [{'$truncated', '$truncated'}]; + [{'$truncated', '$truncated'}]; cutMap([{Key, Value} | Tail], Depth, TcmNestStruct, NumStructs) -> - AddStruct = isStruct(Key) + isStruct(Value), - [{cut(Key, Depth + 1), cut(Value, Depth + 1)} | cutMap(Tail, Depth, TcmNestStruct, NumStructs + AddStruct)]. + AddStruct = isStruct(Key) + isStruct(Value), + [{cut(Key, Depth + 1), cut(Value, Depth + 1)} | cutMap(Tail, Depth, TcmNestStruct, NumStructs + AddStruct)]. isStruct(Term) -> - case is_list(Term) orelse is_map(Term) orelse is_tuple(Term) of - true -> - 1; - _ -> - 0 - end. + case is_list(Term) orelse is_map(Term) orelse is_tuple(Term) of + true -> + 1; + _ -> + 0 + end. -maps_to_list(Map, MaxSize) -> - I = maps:iterator(Map), - maps_to_list(maps:next(I), MaxSize, []). +mapsToList(Map, MaxSize) -> + I = maps:iterator(Map), + mapsToList(maps:next(I), MaxSize, []). %% Returns elements in arbitrary order. We reverse when we truncate %% so that the truncated elements come at the end to avoid having %% two truncated elements in the final output. -maps_to_list(none, _, Acc) -> - Acc; -maps_to_list(_, 0, Acc) -> - lists:reverse([{'$truncated', '$truncated'} | Acc]); -maps_to_list({K, V, I}, N, Acc) -> - maps_to_list(maps:next(I), N - 1, [{K, V} | Acc]). +mapsToList(none, _, Acc) -> + Acc; +mapsToList(_, 0, Acc) -> + lists:reverse([{'$truncated', '$truncated'} | Acc]); +mapsToList({K, V, I}, N, Acc) -> + mapsToList(maps:next(I), N - 1, [{K, V} | Acc]).