From daabdffcbd3bdb510754a06793e2a0b211003fc8 Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Tue, 26 Jul 2016 14:28:29 +0200 Subject: [PATCH 01/12] Limit tracing time --- src/eflame.erl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/eflame.erl b/src/eflame.erl index c19c730..707f966 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -26,11 +26,14 @@ apply1(Mode, OutputFile, {Fun, Args}) -> Tracer = spawn_tracer(), start_trace(Tracer, self(), Mode), - Return = (catch apply_fun(Fun, Args)), + + spawn_link(fun() -> apply_fun(Fun, Args) end), + + timer:sleep(10000), + {ok, Bytes} = stop_trace(Tracer, self()), - ok = file:write_file(OutputFile, Bytes), - Return. + ok = file:write_file(OutputFile, Bytes). apply_fun({M, F}, A) -> erlang:apply(M, F, A); @@ -169,4 +172,3 @@ intercalate(Sep, Xs) -> lists:concat(intersperse(Sep, Xs)). intersperse(_, []) -> []; intersperse(_, [X]) -> [X]; intersperse(Sep, [X | Xs]) -> [X, Sep | intersperse(Sep, Xs)]. - From c274e2b3e1351c0a5782953e929e32741de5160c Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 17:51:46 +0200 Subject: [PATCH 02/12] Replace usage of dict for maps --- .gitignore | 2 + src/eflame.erl | 258 ++++++++++++++++++++++++++----------------------- 2 files changed, 140 insertions(+), 120 deletions(-) diff --git a/.gitignore b/.gitignore index 1b522f7..7ad6acd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ ebin/* *.svg *.out erl_crash.dump +_build/ +rebar.lock diff --git a/src/eflame.erl b/src/eflame.erl index 707f966..8d45dae 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -1,171 +1,189 @@ -module(eflame). + -export([apply/2, apply/3, apply/4, apply/5]). -define(RESOLUTION, 1000). %% us --record(dump, {stack=[], us=0, acc=[]}). % per-process state +-record(dump, {stack = [], us = 0, acc = []}). % per-process state -define(DEFAULT_MODE, normal_with_children). -define(DEFAULT_OUTPUT_FILE, "stacks.out"). apply(F, A) -> - apply1(?DEFAULT_MODE, ?DEFAULT_OUTPUT_FILE, {F, A}). + apply1(?DEFAULT_MODE, ?DEFAULT_OUTPUT_FILE, {F, A}). apply(M, F, A) -> - apply1(?DEFAULT_MODE, ?DEFAULT_OUTPUT_FILE, {{M, F}, A}). + apply1(?DEFAULT_MODE, ?DEFAULT_OUTPUT_FILE, {{M, F}, A}). apply(Mode, OutputFile, Fun, Args) -> - apply1(Mode, OutputFile, {Fun, Args}). + apply1(Mode, OutputFile, {Fun, Args}). apply(Mode, OutputFile, M, F, A) -> - apply1(Mode, OutputFile, {{M, F}, A}). + apply1(Mode, OutputFile, {{M, F}, A}). apply1(Mode, OutputFile, {Fun, Args}) -> - Tracer = spawn_tracer(), - - start_trace(Tracer, self(), Mode), - - spawn_link(fun() -> apply_fun(Fun, Args) end), + Tracer = spawn_tracer(), - timer:sleep(10000), + start_trace(Tracer, self(), Mode), + Return = (catch apply_fun(Fun, Args)), + {ok, Bytes} = stop_trace(Tracer, self()), - {ok, Bytes} = stop_trace(Tracer, self()), - - ok = file:write_file(OutputFile, Bytes). + ok = file:write_file(OutputFile, Bytes), + Return. apply_fun({M, F}, A) -> - erlang:apply(M, F, A); + erlang:apply(M, F, A); apply_fun(F, A) -> - erlang:apply(F, A). + erlang:apply(F, A). start_trace(Tracer, Target, Mode) -> - MatchSpec = [{'_', [], [{message, {{cp, {caller}}}}]}], - erlang:trace_pattern(on_load, MatchSpec, [local]), - erlang:trace_pattern({'_', '_', '_'}, MatchSpec, [local]), - erlang:trace(Target, true, [{tracer, Tracer} | trace_flags(Mode)]), - ok. + MatchSpec = [{'_', [], [{message, {{cp, {caller}}}}]}], + erlang:trace_pattern(on_load, MatchSpec, [local]), + erlang:trace_pattern({'_', '_', '_'}, MatchSpec, [local]), + erlang:trace(Target, true, [{tracer, Tracer} | trace_flags(Mode)]), + ok. stop_trace(Tracer, Target) -> - erlang:trace(Target, false, [all]), - Tracer ! {dump_bytes, self()}, + erlang:trace(Target, false, [all]), + Tracer ! {dump_bytes, self()}, - Ret = receive {bytes, B} -> {ok, B} - after 5000 -> {error, timeout} - end, + Ret = receive {bytes, B} -> {ok, B} + after 5000 -> {error, timeout} + end, - exit(Tracer, normal), - Ret. + exit(Tracer, normal), + Ret. -spawn_tracer() -> spawn(fun() -> trace_listener(dict:new()) end). +spawn_tracer() -> spawn(fun() -> trace_listener(#{}) end). trace_flags(normal) -> - [call, arity, return_to, timestamp, running]; + [call, arity, return_to, timestamp, running]; trace_flags(normal_with_children) -> - [call, arity, return_to, timestamp, running, set_on_spawn]; -trace_flags(like_fprof) -> % fprof does this as 'normal', will not work! - [call, return_to, running, procs, garbage_collection, arity, timestamp, set_on_spawn]. - -trace_listener(State) -> - receive - {dump, Pid} -> - Pid ! {stacks, dict:to_list(State)}; - {dump_bytes, Pid} -> - Bytes = iolist_to_binary([dump_to_iolist(TPid, Dump) || {TPid, [Dump]} <- dict:to_list(State)]), - Pid ! {bytes, Bytes}; - Term -> - trace_ts = element(1, Term), - PidS = element(2, Term), - - PidState = case dict:find(PidS, State) of - {ok, [Ps]} -> Ps; - error -> #dump{} - end, - - NewPidState = trace_proc_stream(Term, PidState), - - D1 = dict:erase(PidS, State), - D2 = dict:append(PidS, NewPidState, D1), - trace_listener(D2) - end. + [call, arity, return_to, timestamp, running, set_on_spawn]; +trace_flags(like_fprof) -> + %% fprof does this as 'normal', will not work! + [ call, return_to, running + , procs, garbage_collection + , arity, timestamp, set_on_spawn + ]. + +-spec trace_listener(map()) -> + {stacks, map()} | {bytes, binary()}. +trace_listener(State0) -> + receive + {dump, Pid} -> + Pid ! {stacks, State0}; + {dump_bytes, Pid} -> + IOList = [ dump_to_iolist(TPid, Dump) + || {TPid, [Dump]} <- maps:to_list(State0) + ], + Bytes = iolist_to_binary(IOList), + Pid ! {bytes, Bytes}; + Term -> + trace_ts = element(1, Term), + Pid = element(2, Term), + + PidState0 = maps:get(Pid, State0, #dump{}), + PidState1 = trace_proc_stream(Term, PidState0), + + State1 = maps:put(Pid, State0, PidState1), + trace_listener(State1) + end. us({Mega, Secs, Micro}) -> - Mega*1000*1000*1000*1000 + Secs*1000*1000 + Micro. + Mega * 1000 * 1000 * 1000 * 1000 + Secs * 1000 * 1000 + Micro. new_state(#dump{us=Us, acc=Acc} = State, Stack, Ts) -> - %io:format("new state: ~p ~p ~p~n", [Us, length(Stack), Ts]), - UsTs = us(Ts), - case Us of - 0 -> State#dump{us=UsTs, stack=Stack}; - _ when Us > 0 -> - Diff = us(Ts) - Us, - NOverlaps = Diff div ?RESOLUTION, - Overlapped = NOverlaps * ?RESOLUTION, - %Rem = Diff - Overlapped, - case NOverlaps of - X when X >= 1 -> - StackRev = lists:reverse(Stack), - Stacks = [StackRev || _ <- lists:seq(1, NOverlaps)], - State#dump{us=Us+Overlapped, acc=lists:append(Stacks, Acc), stack=Stack}; - _ -> - State#dump{stack=Stack} - end - end. - -trace_proc_stream({trace_ts, _Ps, call, MFA, {cp, {_,_,_} = CallerMFA}, Ts}, #dump{stack=[]} = State) -> - new_state(State, [MFA, CallerMFA], Ts); - -trace_proc_stream({trace_ts, _Ps, call, MFA, {cp, undefined}, Ts}, #dump{stack=[]} = State) -> - new_state(State, [MFA], Ts); - -trace_proc_stream({trace_ts, _Ps, call, MFA, {cp, undefined}, Ts}, #dump{stack=[MFA|_] = Stack} = State) -> - new_state(State, Stack, Ts); - -trace_proc_stream({trace_ts, _Ps, call, MFA, {cp, undefined}, Ts}, #dump{stack=Stack} = State) -> - new_state(State, [MFA | Stack], Ts); - -trace_proc_stream({trace_ts, _Ps, call, MFA, {cp, MFA}, Ts}, #dump{stack=[MFA|Stack]} = State) -> - new_state(State, [MFA|Stack], Ts); % collapse tail recursion - -trace_proc_stream({trace_ts, _Ps, call, MFA, {cp, CpMFA}, Ts}, #dump{stack=[CpMFA|Stack]} = State) -> - new_state(State, [MFA, CpMFA|Stack], Ts); - -trace_proc_stream({trace_ts, _Ps, call, _MFA, {cp, _}, _Ts} = TraceTs, #dump{stack=[_|StackRest]} = State) -> - trace_proc_stream(TraceTs, State#dump{stack=StackRest}); - -trace_proc_stream({trace_ts, _Ps, return_to, MFA, Ts}, #dump{stack=[_Current, MFA|Stack]} = State) -> - new_state(State, [MFA|Stack], Ts); % do not try to traverse stack down because we've already collapsed it - + %% io:format("new state: ~p ~p ~p~n", [Us, length(Stack), Ts]), + UsTs = us(Ts), + case Us of + 0 -> State#dump{us=UsTs, stack=Stack}; + _ when Us > 0 -> + Diff = us(Ts) - Us, + NOverlaps = Diff div ?RESOLUTION, + Overlapped = NOverlaps * ?RESOLUTION, + %% Rem = Diff - Overlapped, + case NOverlaps of + X when X >= 1 -> + StackRev = lists:reverse(Stack), + Stacks = [StackRev || _ <- lists:seq(1, NOverlaps)], + State#dump{us=Us+Overlapped, acc=lists:append(Stacks, Acc), stack=Stack}; + _ -> + State#dump{stack=Stack} + end + end. + +trace_proc_stream( {trace_ts, _Ps, call, MFA, {cp, {_,_,_} = CallerMFA}, Ts} + , #dump{stack=[]} = State + ) -> + new_state(State, [MFA, CallerMFA], Ts); +trace_proc_stream( {trace_ts, _Ps, call, MFA, {cp, undefined}, Ts} + , #dump{stack=[]} = State + ) -> + new_state(State, [MFA], Ts); +trace_proc_stream( {trace_ts, _Ps, call, MFA, {cp, undefined}, Ts} + , #dump{stack=[MFA|_] = Stack} = State + ) -> + new_state(State, Stack, Ts); +trace_proc_stream( {trace_ts, _Ps, call, MFA, {cp, undefined}, Ts} + , #dump{stack=Stack} = State + ) -> + new_state(State, [MFA | Stack], Ts); +trace_proc_stream( {trace_ts, _Ps, call, MFA, {cp, MFA}, Ts} + , #dump{stack=[MFA|Stack]} = State + ) -> + new_state(State, [MFA|Stack], Ts); % collapse tail recursion +trace_proc_stream( {trace_ts, _Ps, call, MFA, {cp, CpMFA}, Ts} + , #dump{stack=[CpMFA|Stack]} = State + ) -> + new_state(State, [MFA, CpMFA|Stack], Ts); +trace_proc_stream( {trace_ts, _Ps, call, _MFA, {cp, _}, _Ts} = TraceTs + , #dump{stack=[_|StackRest]} = State + ) -> + trace_proc_stream(TraceTs, State#dump{stack=StackRest}); +trace_proc_stream( {trace_ts, _Ps, return_to, MFA, Ts} + , #dump{stack=[_Current, MFA|Stack]} = State + ) -> + %% do not try to traverse stack down because we've already collapsed it + new_state(State, [MFA|Stack], Ts); trace_proc_stream({trace_ts, _Ps, return_to, undefined, _Ts}, State) -> - State; - + State; trace_proc_stream({trace_ts, _Ps, return_to, _, _Ts}, State) -> - State; - -trace_proc_stream({trace_ts, _Ps, in, _MFA, Ts}, #dump{stack=[sleep|Stack]} = State) -> - new_state(new_state(State, [sleep|Stack], Ts), Stack, Ts); - -trace_proc_stream({trace_ts, _Ps, in, _MFA, Ts}, #dump{stack=Stack} = State) -> - new_state(State, Stack, Ts); - -trace_proc_stream({trace_ts, _Ps, out, _MFA, Ts}, #dump{stack=Stack} = State) -> - new_state(State, [sleep|Stack], Ts); - + State; +trace_proc_stream( {trace_ts, _Ps, in, _MFA, Ts} + , #dump{stack=[sleep|Stack]} = State + ) -> + new_state(new_state(State, [sleep|Stack], Ts), Stack, Ts); +trace_proc_stream( {trace_ts, _Ps, in, _MFA, Ts} + , #dump{stack=Stack} = State + ) -> + new_state(State, Stack, Ts); +trace_proc_stream( {trace_ts, _Ps, out, _MFA, Ts} + , #dump{stack=Stack} = State + ) -> + new_state(State, [sleep|Stack], Ts); trace_proc_stream(TraceTs, State) -> - io:format("trace_proc_stream: unknown trace: ~p~n", [TraceTs]), - State. - + io:format("trace_proc_stream: unknown trace: ~p~n", [TraceTs]), + State. stack_collapse(Stack) -> - intercalate(";", [entry_to_iolist(S) || S <- Stack]). + intercalate(";", [entry_to_iolist(S) || S <- Stack]). entry_to_iolist({M, F, A}) -> - [atom_to_binary(M, utf8), <<":">>, atom_to_binary(F, utf8), <<"/">>, integer_to_list(A)]; + [ atom_to_binary(M, utf8), <<":">> + , atom_to_binary(F, utf8), <<"/">> + , integer_to_list(A) + ]; entry_to_iolist(A) when is_atom(A) -> - [atom_to_binary(A, utf8)]. + [atom_to_binary(A, utf8)]. dump_to_iolist(Pid, #dump{acc=Acc}) -> - [[pid_to_list(Pid), <<";">>, stack_collapse(S), <<"\n">>] || S <- lists:reverse(Acc)]. + [ [ pid_to_list(Pid), <<";">> + , stack_collapse(S), <<"\n">> + ] + || S <- lists:reverse(Acc) + ]. intercalate(Sep, Xs) -> lists:concat(intersperse(Sep, Xs)). From 4e1bcd3113b26295c3b4672648e9ca5fd5fb0e20 Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 18:07:57 +0200 Subject: [PATCH 03/12] Add logging --- src/eflame.erl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/eflame.erl b/src/eflame.erl index 8d45dae..a4ec83d 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -11,6 +11,9 @@ -define(DEFAULT_MODE, normal_with_children). -define(DEFAULT_OUTPUT_FILE, "stacks.out"). +-define(LOG(Msg, Args), io:format(Msg, Args)). +-define(LOG(Msg), ?LOG(Msg, [])). + apply(F, A) -> apply1(?DEFAULT_MODE, ?DEFAULT_OUTPUT_FILE, {F, A}). @@ -76,12 +79,14 @@ trace_listener(State0) -> {dump, Pid} -> Pid ! {stacks, State0}; {dump_bytes, Pid} -> + ?LOG("Dumping bytes..."), IOList = [ dump_to_iolist(TPid, Dump) || {TPid, [Dump]} <- maps:to_list(State0) ], Bytes = iolist_to_binary(IOList), Pid ! {bytes, Bytes}; Term -> + ?LOG("Term: ~p", [Term]), trace_ts = element(1, Term), Pid = element(2, Term), From eb97111dbd8f14c9ee098fa1121edc58ac0b80d8 Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 18:38:26 +0200 Subject: [PATCH 04/12] Avoid using timer:sleep/1 --- src/eflame.erl | 64 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/src/eflame.erl b/src/eflame.erl index a4ec83d..3cd7a20 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -10,36 +10,67 @@ -define(DEFAULT_MODE, normal_with_children). -define(DEFAULT_OUTPUT_FILE, "stacks.out"). +-define(DEFAULT_TIMEOUT, 10000). -define(LOG(Msg, Args), io:format(Msg, Args)). -define(LOG(Msg), ?LOG(Msg, [])). +-spec apply(function(), [any()]) -> any(). apply(F, A) -> - apply1(?DEFAULT_MODE, ?DEFAULT_OUTPUT_FILE, {F, A}). + do_apply(?DEFAULT_MODE, ?DEFAULT_OUTPUT_FILE, {F, A}). +-spec apply(module(), atom(), [any()]) -> any(). apply(M, F, A) -> - apply1(?DEFAULT_MODE, ?DEFAULT_OUTPUT_FILE, {{M, F}, A}). + do_apply(?DEFAULT_MODE, ?DEFAULT_OUTPUT_FILE, {{M, F}, A}). +-spec apply(atom(), string(), function(), [any()]) -> any(). apply(Mode, OutputFile, Fun, Args) -> - apply1(Mode, OutputFile, {Fun, Args}). + do_apply(Mode, OutputFile, {Fun, Args}). +-spec apply(atom(), string(), module(), atom(), [any()]) -> any(). apply(Mode, OutputFile, M, F, A) -> - apply1(Mode, OutputFile, {{M, F}, A}). + do_apply(Mode, OutputFile, {{M, F}, A}). -apply1(Mode, OutputFile, {Fun, Args}) -> +%% ============================================================================= +%% Internal functions +%% ============================================================================= + +-spec do_apply(atom(), string(), {{module(), atom()} | function(), [any()]}) -> + {ok, any()} | timeout. +do_apply(Mode, OutputFile, {Fun, Args}) -> Tracer = spawn_tracer(), start_trace(Tracer, self(), Mode), - Return = (catch apply_fun(Fun, Args)), + + F = build_fun(Fun, Args), + Ref = apply_fun(F, self()), + Result = wait_result(Ref, ?DEFAULT_TIMEOUT), {ok, Bytes} = stop_trace(Tracer, self()), ok = file:write_file(OutputFile, Bytes), - Return. + Result. -apply_fun({M, F}, A) -> - erlang:apply(M, F, A); -apply_fun(F, A) -> - erlang:apply(F, A). +-spec wait_result(reference(), timeout()) -> {ok, any()} | timeout. +wait_result(Ref, Timeout) -> + receive {Ref, Result} -> {ok, Result} + after Timeout -> timeout + end. + +-spec build_fun({module(), atom()} | function(), [any()]) -> function(). +build_fun({M, F}, A) -> + fun() -> erlang:apply(M, F, A) end; +build_fun(F, A) -> + fun() -> erlang:apply(F, A) end. + +-spec apply_fun(function(), pid()) -> function(). +apply_fun(InnerFun, Pid) -> + Ref = make_ref(), + Fun = fun() -> + Result = InnerFun(), + Pid ! {Ref, Result} + end, + spawn_link(Fun), + Ref. start_trace(Tracer, Target, Mode) -> MatchSpec = [{'_', [], [{message, {{cp, {caller}}}}]}], @@ -79,21 +110,23 @@ trace_listener(State0) -> {dump, Pid} -> Pid ! {stacks, State0}; {dump_bytes, Pid} -> - ?LOG("Dumping bytes..."), + ?LOG("Dumping bytes...~n"), IOList = [ dump_to_iolist(TPid, Dump) - || {TPid, [Dump]} <- maps:to_list(State0) + || {TPid, Dump} <- maps:to_list(State0) ], + %% ?LOG("~p", [IOList]), + Bytes = iolist_to_binary(IOList), Pid ! {bytes, Bytes}; Term -> - ?LOG("Term: ~p", [Term]), + %% ?LOG("~p~n", [Term]), trace_ts = element(1, Term), Pid = element(2, Term), PidState0 = maps:get(Pid, State0, #dump{}), PidState1 = trace_proc_stream(Term, PidState0), - State1 = maps:put(Pid, State0, PidState1), + State1 = maps:put(Pid, PidState1, State0), trace_listener(State1) end. @@ -172,6 +205,7 @@ trace_proc_stream( {trace_ts, _Ps, out, _MFA, Ts} trace_proc_stream(TraceTs, State) -> io:format("trace_proc_stream: unknown trace: ~p~n", [TraceTs]), State. + stack_collapse(Stack) -> intercalate(";", [entry_to_iolist(S) || S <- Stack]). From 604db8ecebe8c84685a788241aa5ef8074bb6f0d Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 21:33:19 +0200 Subject: [PATCH 05/12] Avoid calling lists:reverse/1 and then going over the list again --- src/eflame.erl | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/eflame.erl b/src/eflame.erl index 3cd7a20..ab8c6bd 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -111,7 +111,7 @@ trace_listener(State0) -> Pid ! {stacks, State0}; {dump_bytes, Pid} -> ?LOG("Dumping bytes...~n"), - IOList = [ dump_to_iolist(TPid, Dump) + IOList = [ dump_to_iolist(TPid, Dump#dump.acc) || {TPid, Dump} <- maps:to_list(State0) ], %% ?LOG("~p", [IOList]), @@ -217,12 +217,15 @@ entry_to_iolist({M, F, A}) -> entry_to_iolist(A) when is_atom(A) -> [atom_to_binary(A, utf8)]. -dump_to_iolist(Pid, #dump{acc=Acc}) -> - [ [ pid_to_list(Pid), <<";">> - , stack_collapse(S), <<"\n">> - ] - || S <- lists:reverse(Acc) - ]. +dump_to_iolist(Pid, Stacks) -> + PidList = pid_to_list(Pid), + dump_to_iolist(PidList, Stacks, []). + +dump_to_iolist(_PidList, [], Result) -> + Result; +dump_to_iolist(PidList, [X | Rest], Result) -> + Item = [ PidList, <<";">>, stack_collapse(X), <<"\n">>], + dump_to_iolist(PidList, Rest, [Item | Result]). intercalate(Sep, Xs) -> lists:concat(intersperse(Sep, Xs)). From b537c8bb3969b4ae1cf6709d6efbb1119c8c7462 Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 21:42:27 +0200 Subject: [PATCH 06/12] Remove unnecesary call to lists:concat/1 and build iolist() in intersperse --- src/eflame.erl | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/eflame.erl b/src/eflame.erl index ab8c6bd..595823e 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -206,8 +206,10 @@ trace_proc_stream(TraceTs, State) -> io:format("trace_proc_stream: unknown trace: ~p~n", [TraceTs]), State. +%% Conversion to iolist() + stack_collapse(Stack) -> - intercalate(";", [entry_to_iolist(S) || S <- Stack]). + intersperse(";", Stack, []). entry_to_iolist({M, F, A}) -> [ atom_to_binary(M, utf8), <<":">> @@ -217,6 +219,13 @@ entry_to_iolist({M, F, A}) -> entry_to_iolist(A) when is_atom(A) -> [atom_to_binary(A, utf8)]. +intersperse(_, [], Result) -> + Result; +intersperse(Sep, [X | Xs], []) -> + intersperse(Sep, Xs, [entry_to_iolist(X)]); +intersperse(Sep, [X | Xs], Result) -> + intersperse(Sep, Xs, [Result, Sep, entry_to_iolist(X)]). + dump_to_iolist(Pid, Stacks) -> PidList = pid_to_list(Pid), dump_to_iolist(PidList, Stacks, []). @@ -226,9 +235,3 @@ dump_to_iolist(_PidList, [], Result) -> dump_to_iolist(PidList, [X | Rest], Result) -> Item = [ PidList, <<";">>, stack_collapse(X), <<"\n">>], dump_to_iolist(PidList, Rest, [Item | Result]). - -intercalate(Sep, Xs) -> lists:concat(intersperse(Sep, Xs)). - -intersperse(_, []) -> []; -intersperse(_, [X]) -> [X]; -intersperse(Sep, [X | Xs]) -> [X, Sep | intersperse(Sep, Xs)]. From b0627dd599cd0503bc4d8ef346a8e2f466d61a23 Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 21:53:24 +0200 Subject: [PATCH 07/12] Simplify new_state/3 --- src/eflame.erl | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/eflame.erl b/src/eflame.erl index 595823e..21e5e39 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -133,24 +133,25 @@ trace_listener(State0) -> us({Mega, Secs, Micro}) -> Mega * 1000 * 1000 * 1000 * 1000 + Secs * 1000 * 1000 + Micro. -new_state(#dump{us=Us, acc=Acc} = State, Stack, Ts) -> - %% io:format("new state: ~p ~p ~p~n", [Us, length(Stack), Ts]), +new_state(#dump{us = 0} = State, Stack, Ts) -> UsTs = us(Ts), - case Us of - 0 -> State#dump{us=UsTs, stack=Stack}; - _ when Us > 0 -> - Diff = us(Ts) - Us, - NOverlaps = Diff div ?RESOLUTION, - Overlapped = NOverlaps * ?RESOLUTION, - %% Rem = Diff - Overlapped, - case NOverlaps of - X when X >= 1 -> - StackRev = lists:reverse(Stack), - Stacks = [StackRev || _ <- lists:seq(1, NOverlaps)], - State#dump{us=Us+Overlapped, acc=lists:append(Stacks, Acc), stack=Stack}; - _ -> - State#dump{stack=Stack} - end + State#dump{us = UsTs, stack = Stack}; +new_state(#dump{us = Us, acc = Acc} = State, Stack, Ts) -> + %% io:format("new state: ~p ~p ~p~n", [Us, length(Stack), Ts]), + Diff = us(Ts) - Us, + NOverlaps = Diff div ?RESOLUTION, + Overlapped = NOverlaps * ?RESOLUTION, + %% Rem = Diff - Overlapped, + case NOverlaps >= 1 of + true -> + StackRev = lists:reverse(Stack), + Stacks = lists:duplicate(NOverlaps, StackRev), + State#dump{ us = Us + Overlapped + , acc = lists:append(Stacks, Acc) + , stack = Stack + }; + false -> + State#dump{stack = Stack} end. trace_proc_stream( {trace_ts, _Ps, call, MFA, {cp, {_,_,_} = CallerMFA}, Ts} From 6c3ccd85377dff845eea418cab4721191afe9967 Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 21:56:33 +0200 Subject: [PATCH 08/12] Reduce 3 multiplications into a constant --- src/eflame.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eflame.erl b/src/eflame.erl index 21e5e39..92ba9d6 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -131,7 +131,7 @@ trace_listener(State0) -> end. us({Mega, Secs, Micro}) -> - Mega * 1000 * 1000 * 1000 * 1000 + Secs * 1000 * 1000 + Micro. + Mega * 1000000000000 + Secs * 1000000 + Micro. new_state(#dump{us = 0} = State, Stack, Ts) -> UsTs = us(Ts), From fe1789812a9bd7dccb007f0e17cb8df96b4b4433 Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 22:22:14 +0200 Subject: [PATCH 09/12] Don't do list processing when getting trace messages --- src/eflame.erl | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/eflame.erl b/src/eflame.erl index 92ba9d6..a9fc029 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -144,10 +144,9 @@ new_state(#dump{us = Us, acc = Acc} = State, Stack, Ts) -> %% Rem = Diff - Overlapped, case NOverlaps >= 1 of true -> - StackRev = lists:reverse(Stack), - Stacks = lists:duplicate(NOverlaps, StackRev), + %% ?LOG("Overlaps ~p~n", [NOverlaps]), State#dump{ us = Us + Overlapped - , acc = lists:append(Stacks, Acc) + , acc = [{NOverlaps, Stack} | Acc] , stack = Stack }; false -> @@ -225,7 +224,7 @@ intersperse(_, [], Result) -> intersperse(Sep, [X | Xs], []) -> intersperse(Sep, Xs, [entry_to_iolist(X)]); intersperse(Sep, [X | Xs], Result) -> - intersperse(Sep, Xs, [Result, Sep, entry_to_iolist(X)]). + intersperse(Sep, Xs, [entry_to_iolist(X), Sep | Result]). dump_to_iolist(Pid, Stacks) -> PidList = pid_to_list(Pid), @@ -233,6 +232,14 @@ dump_to_iolist(Pid, Stacks) -> dump_to_iolist(_PidList, [], Result) -> Result; -dump_to_iolist(PidList, [X | Rest], Result) -> - Item = [ PidList, <<";">>, stack_collapse(X), <<"\n">>], +dump_to_iolist(PidList, [{N, Stack} | Rest], Result) -> + Item = stack_to_iolist(PidList, Stack), + Items = lists:duplicate(N, Item), + dump_to_iolist(PidList, Rest, [Items | Result]); +dump_to_iolist(PidList, [Stack | Rest], Result) -> + Item = stack_to_iolist(PidList, Stack), dump_to_iolist(PidList, Rest, [Item | Result]). + +-spec stack_to_iolist(string(), list()) -> iolist(). +stack_to_iolist(PidList, Stack) -> + [ PidList, <<";">>, stack_collapse(Stack), <<"\n">>]. From 24df91390dfd5859c7f358b4bc8d53ee55e7af73 Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 22:24:09 +0200 Subject: [PATCH 10/12] Add rebar.config --- rebar.config | 1 + src/eflame.erl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 rebar.config diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..0f5d40e --- /dev/null +++ b/rebar.config @@ -0,0 +1 @@ +{erl_opts, [debug_info]}. diff --git a/src/eflame.erl b/src/eflame.erl index a9fc029..8ab716d 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -242,4 +242,4 @@ dump_to_iolist(PidList, [Stack | Rest], Result) -> -spec stack_to_iolist(string(), list()) -> iolist(). stack_to_iolist(PidList, Stack) -> - [ PidList, <<";">>, stack_collapse(Stack), <<"\n">>]. + [PidList, <<";">>, stack_collapse(Stack), <<"\n">>]. From a018f10950bb834cf8ded024a4b438fdc38975fa Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 22:36:12 +0200 Subject: [PATCH 11/12] Remove commented out log messages --- src/eflame.erl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/eflame.erl b/src/eflame.erl index 8ab716d..c86933e 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -42,8 +42,8 @@ do_apply(Mode, OutputFile, {Fun, Args}) -> start_trace(Tracer, self(), Mode), - F = build_fun(Fun, Args), - Ref = apply_fun(F, self()), + F = build_fun(Fun, Args), + Ref = apply_fun(F, self()), Result = wait_result(Ref, ?DEFAULT_TIMEOUT), {ok, Bytes} = stop_trace(Tracer, self()), @@ -114,12 +114,10 @@ trace_listener(State0) -> IOList = [ dump_to_iolist(TPid, Dump#dump.acc) || {TPid, Dump} <- maps:to_list(State0) ], - %% ?LOG("~p", [IOList]), Bytes = iolist_to_binary(IOList), Pid ! {bytes, Bytes}; Term -> - %% ?LOG("~p~n", [Term]), trace_ts = element(1, Term), Pid = element(2, Term), From b579a6c4043e063083a4de28defdea264e39f1c5 Mon Sep 17 00:00:00 2001 From: Juan Facorro Date: Sun, 1 Jul 2018 23:18:12 +0200 Subject: [PATCH 12/12] Avoid having to post-process file by generating the correct output --- src/eflame.erl | 16 +++++++--------- stack_to_flame.sh | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/eflame.erl b/src/eflame.erl index c86933e..7311379 100644 --- a/src/eflame.erl +++ b/src/eflame.erl @@ -212,7 +212,7 @@ stack_collapse(Stack) -> entry_to_iolist({M, F, A}) -> [ atom_to_binary(M, utf8), <<":">> , atom_to_binary(F, utf8), <<"/">> - , integer_to_list(A) + , integer_to_binary(A) ]; entry_to_iolist(A) when is_atom(A) -> [atom_to_binary(A, utf8)]. @@ -231,13 +231,11 @@ dump_to_iolist(Pid, Stacks) -> dump_to_iolist(_PidList, [], Result) -> Result; dump_to_iolist(PidList, [{N, Stack} | Rest], Result) -> - Item = stack_to_iolist(PidList, Stack), - Items = lists:duplicate(N, Item), - dump_to_iolist(PidList, Rest, [Items | Result]); -dump_to_iolist(PidList, [Stack | Rest], Result) -> - Item = stack_to_iolist(PidList, Stack), + Item = stack_to_iolist(PidList, N, Stack), dump_to_iolist(PidList, Rest, [Item | Result]). --spec stack_to_iolist(string(), list()) -> iolist(). -stack_to_iolist(PidList, Stack) -> - [PidList, <<";">>, stack_collapse(Stack), <<"\n">>]. +-spec stack_to_iolist(string(), integer(), list()) -> iolist(). +stack_to_iolist(PidList, N, Stack) -> + [ PidList, <<";">>, stack_collapse(Stack) + , <<" ">>, integer_to_binary(N) + , <<"\n">>]. diff --git a/stack_to_flame.sh b/stack_to_flame.sh index 8ca2dfc..da3df18 100755 --- a/stack_to_flame.sh +++ b/stack_to_flame.sh @@ -2,4 +2,4 @@ me="$(dirname $0)" -uniq -c | awk '{print $2, " ", $1}' | $me/flamegraph.pl +$me/flamegraph.pl $1