Current section

Files

Jump to
eflambe src eflambe.erl
Raw

src/eflambe.erl

%%%-------------------------------------------------------------------
%%% @doc
%%% This module provides the public API for eflambe. These public functions are
%%% intended to be invoked by the end user to perform profiling of their
%%% application.
%%% @end
%%%-------------------------------------------------------------------
-module(eflambe).
%% Application callbacks
-export([capture/1, capture/2, capture/3,
apply/1, apply/2]).
-type mfa_fun() :: {atom(), atom(), list()} | fun().
-type program() :: hotspot | speedscope.
-type option() :: {output_directory, binary()} | {output_format, binary()} | {open, program()}.
-type options() :: [option()].
-define(FLAGS, [call, return_to, running, procs, garbage_collection, arity,
timestamp, set_on_spawn]).
%%--------------------------------------------------------------------
%% @doc
%% Starts capturing of function call data for any invocation of the specified
%% MFA and of a flamegraph for
%% the current process.
%%
%% @end
%%--------------------------------------------------------------------
-spec capture(MFA :: mfa()) -> ok.
capture(MFA) ->
capture(MFA, 1).
-spec capture(MFA :: mfa(), NumCalls :: integer()) -> ok.
capture(MFA, NumCalls) ->
capture(MFA, NumCalls, []).
-spec capture(MFA :: mfa(), NumCalls :: integer(), Options :: options()) -> ok.
capture({Module, Function, Arity}, NumCalls, Options) ->
ok = meck:new(Module, [unstick, passthrough]),
TraceId = setup_for_trace(),
ShimmedFunction = fun(Args) ->
Trace = start_trace(TraceId, NumCalls, [{meck, Module}|Options]),
% Invoke the original function
Results = meck:passthrough(Args),
stop_trace(Trace),
Results
end,
MockFun = mock_fun(Arity, ShimmedFunction),
% Replace the original function with our new function that wraps the old
% function in profiling code.
meck:expect(Module, Function, MockFun).
%%--------------------------------------------------------------------
%% @doc
%% Traces the execution of the function passed in for generation of a for a
%% flamegraph of the function call.
%%
%% @end
%%--------------------------------------------------------------------
-spec apply(Function :: mfa_fun()) -> any().
apply(Function) ->
?MODULE:apply(Function, []).
-spec apply(Function :: mfa_fun(), Options :: options()) -> any().
apply({Module, Function, Args}, Options) ->
TraceId = setup_for_trace(),
Trace = start_trace(TraceId, 1, Options),
% Invoke the original function
Results = erlang:apply(Module, Function, Args),
stop_trace(Trace),
Results;
apply({Function, Args}, Options) ->
TraceId = setup_for_trace(),
Trace = start_trace(TraceId, 1, Options),
% Invoke the original function
Results = erlang:apply(Function, Args),
stop_trace(Trace),
Results.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec start_trace(TraceId :: any(), NumCalls :: integer(), Options :: list()) -> reference().
start_trace(TraceId, NumCalls, Options) ->
case eflambe_server:start_trace(TraceId, NumCalls, Options) of
{ok, TraceId, true, Tracer} ->
MatchSpec = [{'_', [], [{message, {{cp, {caller}}}}]}],
erlang:trace_pattern(on_load, MatchSpec, [local]),
erlang:trace_pattern({'_', '_', '_'}, MatchSpec, [local]),
erlang:trace(self(), true, [{tracer, Tracer} | ?FLAGS]);
{ok, TraceId, false, _Tracer} ->
% Trace is already running or has already finished. Or this could
% be a recursive function call. We do not need to do anything.
ok
end,
TraceId.
-spec stop_trace(any()) -> ok.
stop_trace(Trace) ->
erlang:trace(self(), false, [all]),
{ok, _} = eflambe_server:stop_trace(Trace),
ok.
setup_for_trace() ->
application:ensure_all_started(eflambe),
eflambe_sup:get_or_start_server(),
% All traces must have a unique ref so we can keep track of them
make_ref().
% Total hack
% TODO: Is there a way to programmatically generate a function of a given arity?
% I asked here:
% https://stackoverflow.com/questions/69244814/erlang-generate-anonymous-function-of-an-arbitary-arity
mock_fun(1, Function) ->
fun(A) -> Function([A]) end;
mock_fun(2, Function) ->
fun(A, B) -> Function([A, B]) end;
mock_fun(3, Function) ->
fun(A, B, C) -> Function([A, B, C]) end;
mock_fun(4, Function) ->
fun(A, B, C, D) -> Function([A, B, C, D]) end;
mock_fun(5, Function) ->
fun(A, B, C, D, E) -> Function([A, B, C, D, E]) end;
mock_fun(6, Function) ->
fun(A, B, C, D, E, F) -> Function([A, B, C, D, E, F]) end;
mock_fun(7, Function) ->
fun(A, B, C, D, E, F, G) -> Function([A, B, C, D, E, F, G]) end;
mock_fun(8, Function) ->
fun(A, B, C, D, E, F, G, H) -> Function([A, B, C, D, E, F, G, H]) end;
mock_fun(9, Function) ->
fun(A, B, C, D, E, F, G, H, I) -> Function([A, B, C, D, E, F, G, H, I]) end;
mock_fun(10, Function) ->
fun(A, B, C, D, E, F, G, H, I, J) -> Function([A, B, C, D, E, F, G, H, I, J]) end.