From 71f54f5e3cafb1c2c7bde3574fa4df49b8ce9c5e Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Thu, 13 Oct 2022 21:22:20 -0400 Subject: [PATCH 1/7] Add details to readme on new `return` option --- README.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.adoc b/README.adoc index 6142791..0d26151 100644 --- a/README.adoc +++ b/README.adoc @@ -80,6 +80,10 @@ ok === Options +* `{return, value | flamegraph | filename}` - What return to the calling code. Defaults to `value` for `apply/2` function and `filename` for `capture/3` function. + * `value` - return the return value of the function being profiled. Implies the flamegraph data will be written to a file. + * `flamegraph` - return the flamegraph data in the format specified instead of writing it to a file. + * `filename` - return the filename of the file the flamegraph data was written to. Implies the flamegraph data will be written to a file. * `{output_directory, Dir}` - Specify the output directory to write trace data to. Default is project root, and relative paths are relative to the project root. * `{output_format, Format}` - Specify the output format. Unfortunately I was not able to implement all the formatters I would have liked to during Spawnfest. Currently only the `brendan_gregg` formatter is available. ++++++`Format` must be one of `perf`, `brendan_gregg`, or `svg`. Default is `brendan_gregg`.++++++ * `{open, Program}` - Specify the program to load the trace output in after output is generated. `Program` must be one of `speedscope` or `hotspot`. By default this option is not set. `hotspot` is not useful right now as it cannot load files generated by the `brendan_gregg` formatter. From 06311b0d9ceb8d39a93228773b98fd0e374f1ec3 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Thu, 13 Oct 2022 21:23:42 -0400 Subject: [PATCH 2/7] Move option logic from eflambe_server to eflambe --- src/eflambe.erl | 32 ++++++++++++++++++++++++++------ src/eflambe_server.erl | 17 ++--------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/eflambe.erl b/src/eflambe.erl index d5c659b..094a650 100644 --- a/src/eflambe.erl +++ b/src/eflambe.erl @@ -14,9 +14,16 @@ -type mfa_fun() :: {atom(), atom(), list()} | {fun(), list()}. -type program() :: hotspot | speedscope. --type option() :: {output_directory, binary()} | {output_format, brendan_gregg} | {open, program()}. +-type option() :: {return, value | flamegraph | filename} + | {output_directory, binary()} + | {output_format, brendan_gregg} + | {open, program()}. -type options() :: [option()]. +-define(DEFAULT_OPTIONS, [{output_format, brendan_gregg}]). +-define(DEFAULT_APPLY_OPTIONS, [{return, value}|?DEFAULT_OPTIONS]). +-define(DEFAULT_CAPTURE_OPTIONS, [{return, filename}|?DEFAULT_OPTIONS]). + %%-------------------------------------------------------------------- %% @doc %% Starts capturing of function call data for any invocation of the specified @@ -37,13 +44,13 @@ capture(MFA, NumCalls) -> capture(MFA, NumCalls, []). -spec capture(MFA :: mfa(), NumCalls :: pos_integer(), Options :: options()) -> - ok | {error, already_mecked}. + {ok, binary() | [any()]} | {error, already_mecked}. capture({Module, Function, Arity}, NumCalls, Options) when is_atom(Module), is_atom(Function), is_integer(Arity) -> - setup_for_trace(), + CompleteOptions = merge([{max_calls, NumCalls}|Options], ?DEFAULT_CAPTURE_OPTIONS), - CompleteOptions = [{max_calls, NumCalls}|Options], + setup_for_trace(), case eflambe_sup:start_trace({Module, Function, Arity}, CompleteOptions) of {ok, _Pid} -> ok; @@ -66,8 +73,10 @@ apply(Function) -> -spec apply(Function :: mfa_fun(), Options :: options()) -> any(). apply({Module, Function, Args}, Options) when is_atom(Module), is_atom(Function), is_list(Args) -> + CompleteOptions = merge(Options, ?DEFAULT_APPLY_OPTIONS), + setup_for_trace(), - {ok, TracePid} = eflambe_server:start_trace(Options), + {ok, TracePid} = eflambe_server:start_trace(CompleteOptions), % Invoke the original function Results = erlang:apply(Module, Function, Args), @@ -76,8 +85,10 @@ apply({Module, Function, Args}, Options) when is_atom(Module), is_atom(Function) Results; apply({Function, Args}, Options) when is_function(Function), is_list(Args) -> + CompleteOptions = merge(Options, ?DEFAULT_APPLY_OPTIONS), + setup_for_trace(), - {ok, TracePid} = eflambe_server:start_trace(Options), + {ok, TracePid} = eflambe_server:start_trace(CompleteOptions), % Invoke the original function Results = erlang:apply(Function, Args), @@ -91,3 +102,12 @@ apply({Function, Args}, Options) when is_function(Function), is_list(Args) -> setup_for_trace() -> application:ensure_all_started(eflambe). + +% https://stackoverflow.com/questions/21873644/combine-merge-two-erlang-lists +merge(In1, In2) -> + Combined = In1 ++ In2, + Fun = fun(Key) -> + [FinalValue|_] = proplists:get_all_values(Key, Combined), + {Key, FinalValue} + end, + lists:map(Fun, proplists:get_keys(Combined)). diff --git a/src/eflambe_server.erl b/src/eflambe_server.erl index 8669c5d..99c2dcc 100644 --- a/src/eflambe_server.erl +++ b/src/eflambe_server.erl @@ -20,7 +20,6 @@ -include_lib("kernel/include/logger.hrl"). --define(DEFAULT_OPTIONS, [{output_format, brendan_gregg}]). -define(FLAGS, [call, return_to, running, procs, garbage_collection, arity, timestamp, set_on_spawn]). @@ -132,9 +131,6 @@ stop_trace(TracerPid) -> -spec init(Args :: list(tracer_options())) -> {ok, state()}. init([{Module, Function, Arity}, Options]) -> - % Generate complete list of options by falling back to default list - FinalOptions = merge(Options, ?DEFAULT_OPTIONS), - % If necessary, set up meck to wrap original function in tracing code that % calls out to this process. We'll need to inject the pid of this tracer % (self()) into the wrapper function so we can track invocation and return @@ -159,8 +155,8 @@ init([{Module, Function, Arity}, Options]) -> {error, already_mecked} -> {stop, already_mecked}; ok -> - MaxCalls = proplists:get_value(max_calls, FinalOptions), - {ok, #state{module = Module, max_calls = MaxCalls, options = FinalOptions}} + MaxCalls = proplists:get_value(max_calls, Options), + {ok, #state{module = Module, max_calls = MaxCalls, options = Options}} end. -spec handle_call(Request :: any(), from(), state()) -> @@ -264,15 +260,6 @@ lookup_fun(Pid) -> (_) -> false end. -% https://stackoverflow.com/questions/21873644/combine-merge-two-erlang-lists -merge(In1, In2) -> - Combined = In1 ++ In2, - Fun = fun(Key) -> - [FinalValue|_] = proplists:get_all_values(Key, Combined), - {Key, FinalValue} - end, - lists:map(Fun, proplists:get_keys(Combined)). - -spec start_erlang_trace(PidToTrace :: pid(), TracerPid :: pid()) -> integer(). start_erlang_trace(PidToTrace, TracerPid) -> From 01cca7d4de1c34012d93fa1a6570dd86291dae73 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Fri, 14 Oct 2022 20:16:07 -0400 Subject: [PATCH 3/7] Wait for capture trace to finish before returning from eflambe:capture/3 --- src/eflambe_server.erl | 10 +++++++--- src/eflambe_sup.erl | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/eflambe_server.erl b/src/eflambe_server.erl index 99c2dcc..3c5002a 100644 --- a/src/eflambe_server.erl +++ b/src/eflambe_server.erl @@ -24,11 +24,13 @@ timestamp, set_on_spawn]). -record(state, { + callback :: {pid(), reference()}, module :: atom(), max_calls :: integer(), calls = 0 :: integer(), options = [] :: list(), - pid_traces = [] :: list(pid_trace()) + pid_traces = [] :: list(pid_trace()), + results = [] :: list() }). % For capture traces we could end up tracing invocations in multiple processes. @@ -156,7 +158,8 @@ init([{Module, Function, Arity}, Options]) -> {stop, already_mecked}; ok -> MaxCalls = proplists:get_value(max_calls, Options), - {ok, #state{module = Module, max_calls = MaxCalls, options = Options}} + Callback = proplists:get_value(callback, Options), + {ok, #state{callback = Callback, module = Module, max_calls = MaxCalls, options = Options}} end. -spec handle_call(Request :: any(), from(), state()) -> @@ -226,7 +229,8 @@ handle_call(stop_trace, {FromPid, _}, #state{max_calls = MaxCalls, calls = Calls handle_cast(_Msg, State) -> {noreply, State}. -handle_continue(finish, State) -> +handle_continue(finish, #state{callback = PidRef, results = Results} = State) -> + ok = gen_server:reply(PidRef, Results), {stop, normal, State}. -spec handle_info(Info :: any(), state()) -> {noreply, state()} | diff --git a/src/eflambe_sup.erl b/src/eflambe_sup.erl index 8a80034..ff658b3 100644 --- a/src/eflambe_sup.erl +++ b/src/eflambe_sup.erl @@ -23,7 +23,24 @@ start_link() -> -spec start_trace(MFA :: mfa(), Options :: list()) -> {ok, pid()} | {error, already_mecked}. start_trace(MFA, Options) -> - supervisor:start_child(?SERVER, [MFA, Options]). + % This feels hacky but we MAY need the gen_server to send us all the results + % right before it shuts down. Is there a gen_server or proc_lib function + % that provides a better abstraction for one-off messages FROM a gen_server + % like this? + % + % It's as the initial start_child start_link call should be treated as a + % regular gen_server call and we need to wait on a response. + Ref = make_ref(), + case supervisor:start_child(?SERVER, [MFA, [{callback, {self(), Ref}}|Options]]) of + {ok, _Child} -> + receive + {Ref, Msg} -> + io:format("Msg: ~p~n", [Msg]), + {ok, Msg} + end; + {error, _} = Error -> Error + end. + %%%=================================================================== %%% supervisor callbacks From c9e4d7530b2ad34e07ca20796d2d262a3dbed913 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Fri, 14 Oct 2022 21:39:28 -0400 Subject: [PATCH 4/7] Add logic to return filename or the profiled function's return value --- src/eflambe.erl | 17 ++++---- src/eflambe_server.erl | 76 ++++++++++++++++++++++------------- src/eflambe_sup.erl | 1 - src/eflambe_tracer.erl | 27 +++++++++++-- test/eflambe_server_SUITE.erl | 28 ++++++++----- 5 files changed, 96 insertions(+), 53 deletions(-) diff --git a/src/eflambe.erl b/src/eflambe.erl index 094a650..b69059d 100644 --- a/src/eflambe.erl +++ b/src/eflambe.erl @@ -20,6 +20,8 @@ | {open, program()}. -type options() :: [option()]. +-type capture_return() :: {ok, [any()]} | {error, already_mecked}. + -define(DEFAULT_OPTIONS, [{output_format, brendan_gregg}]). -define(DEFAULT_APPLY_OPTIONS, [{return, value}|?DEFAULT_OPTIONS]). -define(DEFAULT_CAPTURE_OPTIONS, [{return, filename}|?DEFAULT_OPTIONS]). @@ -33,18 +35,18 @@ %% @end %%-------------------------------------------------------------------- --spec capture(MFA :: mfa()) -> ok. +-spec capture(MFA :: mfa()) -> capture_return(). capture(MFA) -> capture(MFA, 1). --spec capture(MFA :: mfa(), NumCalls :: pos_integer()) -> ok. +-spec capture(MFA :: mfa(), NumCalls :: pos_integer()) -> capture_return(). capture(MFA, NumCalls) -> capture(MFA, NumCalls, []). -spec capture(MFA :: mfa(), NumCalls :: pos_integer(), Options :: options()) -> - {ok, binary() | [any()]} | {error, already_mecked}. + capture_return(). capture({Module, Function, Arity}, NumCalls, Options) when is_atom(Module), is_atom(Function), is_integer(Arity) -> @@ -52,10 +54,7 @@ capture({Module, Function, Arity}, NumCalls, Options) setup_for_trace(), - case eflambe_sup:start_trace({Module, Function, Arity}, CompleteOptions) of - {ok, _Pid} -> ok; - {error, _} = Error -> Error - end. + eflambe_sup:start_trace({Module, Function, Arity}, CompleteOptions). %%-------------------------------------------------------------------- %% @doc @@ -81,7 +80,7 @@ apply({Module, Function, Args}, Options) when is_atom(Module), is_atom(Function) % Invoke the original function Results = erlang:apply(Module, Function, Args), - ok = eflambe_server:stop_trace(TracePid), + {ok, _} = eflambe_server:stop_trace(TracePid), Results; apply({Function, Args}, Options) when is_function(Function), is_list(Args) -> @@ -93,7 +92,7 @@ apply({Function, Args}, Options) when is_function(Function), is_list(Args) -> % Invoke the original function Results = erlang:apply(Function, Args), - ok = eflambe_server:stop_trace(TracePid), + {ok, _} = eflambe_server:stop_trace(TracePid), Results. %%%=================================================================== diff --git a/src/eflambe_server.erl b/src/eflambe_server.erl index 3c5002a..72d6f6f 100644 --- a/src/eflambe_server.erl +++ b/src/eflambe_server.erl @@ -9,7 +9,7 @@ -behaviour(gen_server). %% API --export([start_link/2, start_capture_trace/1, stop_capture_trace/1, start_trace/1, stop_trace/1]). +-export([start_link/2, start_capture_trace/1, stop_capture_trace/2, start_trace/1, stop_trace/1]). %% gen_server callbacks -export([init/1, @@ -30,7 +30,8 @@ calls = 0 :: integer(), options = [] :: list(), pid_traces = [] :: list(pid_trace()), - results = [] :: list() + results = [] :: list(), + return :: atom() }). % For capture traces we could end up tracing invocations in multiple processes. @@ -92,8 +93,11 @@ start_capture_trace(ServerPid) -> %% %% @end %%-------------------------------------------------------------------- -stop_capture_trace(ServerPid) -> - gen_server:call(ServerPid, stop_trace). + +-spec stop_capture_trace(pid(), any()) -> ok. + +stop_capture_trace(ServerPid, Return) -> + gen_server:call(ServerPid, {stop_trace, Return}). %%-------------------------------------------------------------------- %% @doc @@ -146,7 +150,7 @@ init([{Module, Function, Arity}, Options]) -> case StartedNew of true -> - eflambe_server:stop_capture_trace(ServerPid); + eflambe_server:stop_capture_trace(ServerPid, Results); false -> ok end, @@ -159,7 +163,15 @@ init([{Module, Function, Arity}, Options]) -> ok -> MaxCalls = proplists:get_value(max_calls, Options), Callback = proplists:get_value(callback, Options), - {ok, #state{callback = Callback, module = Module, max_calls = MaxCalls, options = Options}} + Return = proplists:get_value(return, Options), + InitialState = #state{ + callback = Callback, + module = Module, + max_calls = MaxCalls, + options = Options, + return = Return + }, + {ok, InitialState} end. -spec handle_call(Request :: any(), from(), state()) -> @@ -190,38 +202,46 @@ handle_call(start_trace, {FromPid, _}, #state{max_calls = MaxCalls, calls = Call {reply, {ok, false}, State} end; -handle_call(stop_trace, {FromPid, _}, #state{max_calls = MaxCalls, calls = Calls, +handle_call({stop_trace, Return}, {FromPid, _}, #state{max_calls = MaxCalls, calls = Calls, module = ModuleName, - pid_traces = PidTraces} = State) -> + results = Results, + return = ReturnOption} = State) -> % Stop tracing immediately! stop_erlang_trace(FromPid), - NewTraces = case get_pid_trace(State, FromPid) of + case get_pid_trace(State, FromPid) of #pid_trace{tracer_pid = TracerPid} -> - eflambe_tracer:finish(TracerPid), + {ok, Result} = eflambe_tracer:finish(TracerPid), + + FinalResult = case ReturnOption of + value -> Return; + _ -> Result + end, % Generate new list of traces - remove_pid_trace(State, FromPid); - undefined -> - PidTraces - end, + NewTraces = remove_pid_trace(State, FromPid), - NoTracesRemaining = (MaxCalls =:= Calls), + NoTracesRemaining = (MaxCalls =:= Calls), - case {NoTracesRemaining, NewTraces} of - {true, []} -> - % We are completely finished - ok = eflambe_meck:unload(ModuleName), + NewState = State#state{pid_traces = NewTraces, results = [FinalResult|Results]}, - io:format("Successful finished trace~n"), + case {NoTracesRemaining, NewTraces} of + {true, []} -> + % We are completely finished + ok = eflambe_meck:unload(ModuleName), - % The only reason we don't stop here is because this is a call and - % the linked call would crash as well. This feels kind of wrong so - % I may revisit this - {reply, ok, State, {continue, finish}}; - {_, _} -> - % Still some stuff in flight - {reply, ok, State#state{pid_traces = NewTraces}} + io:format("Successful finished trace~n"), + + % The only reason we don't stop here is because this is a call and + % the linked call would crash as well. This feels kind of wrong so + % I may revisit this + {reply, ok, NewState, {continue, finish}}; + {_, _} -> + % Still some stuff in flight + {reply, ok, NewState} + end; + undefined -> + {reply, ok, State} end. -spec handle_cast(any(), state()) -> {noreply, state()}. @@ -230,7 +250,7 @@ handle_cast(_Msg, State) -> {noreply, State}. handle_continue(finish, #state{callback = PidRef, results = Results} = State) -> - ok = gen_server:reply(PidRef, Results), + ok = gen_server:reply(PidRef, lists:reverse(Results)), {stop, normal, State}. -spec handle_info(Info :: any(), state()) -> {noreply, state()} | diff --git a/src/eflambe_sup.erl b/src/eflambe_sup.erl index ff658b3..4d8ab86 100644 --- a/src/eflambe_sup.erl +++ b/src/eflambe_sup.erl @@ -35,7 +35,6 @@ start_trace(MFA, Options) -> {ok, _Child} -> receive {Ref, Msg} -> - io:format("Msg: ~p~n", [Msg]), {ok, Msg} end; {error, _} = Error -> Error diff --git a/src/eflambe_tracer.erl b/src/eflambe_tracer.erl index 423d046..e308149 100644 --- a/src/eflambe_tracer.erl +++ b/src/eflambe_tracer.erl @@ -31,7 +31,8 @@ impl :: atom(), impl_state :: any(), options :: eflambe:options(), - filename :: file:filename_all() + filename :: file:filename_all(), + return :: atom() }). -type state() :: #state{}. @@ -81,16 +82,25 @@ init([Options]) -> Filename = generate_filename(Ext), FullFilename = filename:join([output_directory(Options), Filename]), + Return = proplists:get_value(return, FinalOptions), + % Initialize implementation state {ok, State} = erlang:apply(Impl, init, [FullFilename, Options]), - {ok, #state{impl = Impl, impl_state = State, options = FinalOptions, filename = FullFilename}}. + InitialState = #state{ + impl = Impl, + impl_state = State, + options = FinalOptions, + filename = FullFilename, + return = Return + }, + {ok, InitialState}. -spec handle_call(Request :: any(), from(), state()) -> {reply, Reply :: any(), state()} | {reply, Reply :: any(), state(), {continue, finish}}. handle_call(finish, _From, #state{impl = Impl, impl_state = ImplState, options = Options, - filename = Filename} = State) -> + filename = Filename, return = Return} = State) -> % Format the trace data and write to file {ok, _FinalImplState} = erlang:apply(Impl, finalize, [Options, ImplState]), @@ -98,10 +108,19 @@ handle_call(finish, _From, #state{impl = Impl, impl_state = ImplState, options = maybe_open_in_program(Options, Filename), io:format("Output filename: ~s~n", [Filename]), + Results = case Return of + value -> + % Values are only readily available in the tracer so we + % let the eflambe_server populate this type of return value + undefined; + filename -> Filename; + flamegraph -> ok % TODO + end, + % The only reason we don't stop here is because this is a call and the % linked call would crash as well. This feels kind of wrong so I may revisit % this - {reply, ok, State, {continue, finish}}. + {reply, {ok, Results}, State, {continue, finish}}. -spec handle_cast(any(), state()) -> {noreply, state()}. diff --git a/test/eflambe_server_SUITE.erl b/test/eflambe_server_SUITE.erl index d745708..196a55a 100644 --- a/test/eflambe_server_SUITE.erl +++ b/test/eflambe_server_SUITE.erl @@ -80,32 +80,36 @@ start_link(_Config) -> {ok, Pid} = eflambe_server:start_link({arithmetic, multiply, 2}, [{max_calls, 1}]), true = is_pid(Pid), - eflambe_server:stop_capture_trace(Pid). + eflambe_server:stop_capture_trace(Pid, return_value). start_capture_trace(_Config) -> - Options = [{max_calls, 1}, {output_format, plain}], Self = self(), + Ref = make_ref(), + Options = [{callback, {Self, Ref}}, {max_calls, 1}, {output_format, plain}, {return, filename}], % Returns an ok tuple when eflambe_server is running and arguments are valid {ok, Pid} = eflambe_server:start_link({arithmetic, multiply, 2}, Options), - {state,arithmetic,1,0, FinalOptions, []} = get_server_state(Pid), + {state, {Self, Ref}, arithmetic, 1, 0, FinalOptions, [], [], filename} = get_server_state(Pid), {ok, true} = eflambe_server:start_capture_trace(Pid), % Stores trace state - {state, arithmetic, 1, 1, FinalOptions, [{pid_trace, Self, _Tracer}]} = State = - get_server_state(Pid), + State = get_server_state(Pid), + {state, {Self, Ref}, arithmetic, 1, 1, FinalOptions, Traces, [], filename} = State, + [{pid_trace, Self, _Tracer}] = Traces, % Returns the same trace data when called twice {ok, false} = eflambe_server:start_capture_trace(Pid), State = get_server_state(Pid), % Returns false when trace is stopped but number of calls has already been reached - ok = eflambe_server:stop_capture_trace(Pid). + ok = eflambe_server:stop_capture_trace(Pid, return_value). stop_capture_trace(_Config) -> - Options = [{max_calls, 2}, {output_format, plain}], Self = self(), + Ref = make_ref(), + Reply = {Self, Ref}, + Options = [{callback, Reply}, {max_calls, 2}, {output_format, plain}, {return, filename}], {ok, Pid} = eflambe_server:start_link({arithmetic, multiply, 2}, Options), @@ -113,16 +117,18 @@ stop_capture_trace(_Config) -> {ok, true} = eflambe_server:start_capture_trace(Pid), % Stores trace state - {state, arithmetic, 2, 1, FinalOptions, [{pid_trace, Self, _}]} = get_server_state(Pid), + {state, Reply, arithmetic, 2, 1, FinalOptions, Traces, [], filename} = get_server_state(Pid), + [{pid_trace, Self, _}] = Traces, % When called removes trace from server state - ok = eflambe_server:stop_capture_trace(Pid), - {state, arithmetic, 2, 1, FinalOptions, []} = get_server_state(Pid), + ok = eflambe_server:stop_capture_trace(Pid, return_value), + State = get_server_state(Pid), + {state, Reply, arithmetic, 2, 1, FinalOptions, [], [_Filename], filename} = State, % After being called start_capture_trace invoked in the same process returns % true to kick off a new trace {ok, true} = eflambe_server:start_capture_trace(Pid), - ok = eflambe_server:stop_capture_trace(Pid). + ok = eflambe_server:stop_capture_trace(Pid, return_value). get_server_state(Pid) -> {status, _, _, State} = sys:get_status(Pid), From a58b4020f176b2747155ce0e575a9dfd85bf93af Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Fri, 14 Oct 2022 23:26:01 -0400 Subject: [PATCH 5/7] fixup! Add logic to return filename or the profiled function's return value --- test/eflambe_SUITE.erl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/eflambe_SUITE.erl b/test/eflambe_SUITE.erl index cecaad7..15580c1 100644 --- a/test/eflambe_SUITE.erl +++ b/test/eflambe_SUITE.erl @@ -159,9 +159,13 @@ multiple_captures(_Config) -> NumFiles = length(Files), % Capturing multiple calls should result in multiple output files - ok = eflambe:capture({arithmetic, multiply, 2}, 2, Options), - 12 = arithmetic:multiply(4, 3), - 20 = arithmetic:multiply(5, 4), + spawn_link(fun() -> + timer:sleep(100), + 12 = arithmetic:multiply(4, 3), + 20 = arithmetic:multiply(5, 4) + end), + + {ok, [_Filename1, _Filename2]} = eflambe:capture({arithmetic, multiply, 2}, 2, Options), % Both write separate trace files {ok, UpdatedFiles} = file:list_dir("."), From e5b2b13a983110da1e3d5def6e21af5f682ba6f7 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Sat, 15 Oct 2022 09:13:13 -0400 Subject: [PATCH 6/7] fixup! fixup! Add logic to return filename or the profiled function's return value --- test/eflambe_SUITE.erl | 53 ++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/test/eflambe_SUITE.erl b/test/eflambe_SUITE.erl index 15580c1..7b91ff4 100644 --- a/test/eflambe_SUITE.erl +++ b/test/eflambe_SUITE.erl @@ -86,17 +86,24 @@ end_per_testcase(_TestCase, _Config) -> %%%=================================================================== capture(_Config) -> - Options = [{output_format, plain}], + Options = [{output_format, plain}, {return, filename}], + MFA = {arithmetic, multiply, 2}, + + spawn(fun() -> + timer:sleep(100), + 12 = arithmetic:multiply(4, 3) + end), % Shouldn't crash when invoked - ok = eflambe:capture({arithmetic, multiply, 2}, 1, Options), + {ok, [_Filename]} = eflambe:capture(MFA, 1, Options), - 12 = arithmetic:multiply(4, 3), + spawn(fun() -> + timer:sleep(100), + 12 = arithmetic:multiply(4, 3) + end), % Should behave the same when run a second time - ok = eflambe:capture({arithmetic, multiply, 2}, 1, Options), - - 12 = arithmetic:multiply(4, 3), + {ok, [_Filename2]} = eflambe:capture(MFA, 1, Options), ok = application:stop(eflambe). @@ -136,8 +143,12 @@ capture_and_apply_brendan_gregg(_Config) -> % Both calls should work with the brendan gregg formatter eflambe:apply({arithmetic, multiply, [2, 3]}, Options), - ok = eflambe:capture({arithmetic, multiply, 2}, 1, Options), - 12 = arithmetic:multiply(4, 3), + spawn_link(fun() -> + timer:sleep(100), + 12 = arithmetic:multiply(4, 3) + end), + + {ok, _} = eflambe:capture({arithmetic, multiply, 2}, 1, Options), % Both write separate trace files {ok, UpdatedFiles} = file:list_dir("."), @@ -182,17 +193,26 @@ multiple_captures(_Config) -> capture_same_module(_Config) -> Options = [{output_format, brendan_gregg}], + MFA = {arithmetic, multiply, 2}, + % Count files in dir {ok, Files} = file:list_dir("."), NumFiles = length(Files), - % First call should succeed - ok = eflambe:capture({arithmetic, multiply, 2}, 1, Options), + spawn_link(fun() -> + timer:sleep(100), + 12 = arithmetic:multiply(4, 3) + end), % This second call should fail and return immediately - {error, already_mecked} = eflambe:capture({arithmetic, multiply, 2}, 1, Options), + spawn_link(fun() -> + timer:sleep(100), + {error, already_mecked} = eflambe:capture(MFA, 1, Options) + end), + + % First call should succeed + {ok, [_Filename]} = eflambe:capture(MFA, 1, Options), - 12 = arithmetic:multiply(4, 3), % First call should succeed and write trace file {ok, UpdatedFiles} = file:list_dir("."), @@ -206,9 +226,12 @@ capture_same_module(_Config) -> end, NewFiles), - % Should behave the same when run a second time - ok = eflambe:capture({arithmetic, multiply, 2}, 1, Options), + spawn_link(fun() -> + timer:sleep(100), + 12 = arithmetic:multiply(4, 3) + end), - 12 = arithmetic:multiply(4, 3), + % Should behave the same when run a second time + {ok, [_Filename2]} = eflambe:capture({arithmetic, multiply, 2}, 1, Options), ok = application:stop(eflambe). From 2bbd773f70a0272db07f6b648b862db8b557523b Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Sat, 15 Oct 2022 09:13:59 -0400 Subject: [PATCH 7/7] Correct return type in eflambe_sup --- src/eflambe_sup.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eflambe_sup.erl b/src/eflambe_sup.erl index 4d8ab86..48f8467 100644 --- a/src/eflambe_sup.erl +++ b/src/eflambe_sup.erl @@ -20,7 +20,7 @@ start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). --spec start_trace(MFA :: mfa(), Options :: list()) -> {ok, pid()} | {error, already_mecked}. +-spec start_trace(MFA :: mfa(), Options :: list()) -> {ok, [any()]} | {error, already_mecked}. start_trace(MFA, Options) -> % This feels hacky but we MAY need the gen_server to send us all the results