dev_profile.erl - Performance Profiling Tools
Overview
Purpose: Integrate profiling tools into HyperBEAM execution flows
Module: dev_profile
Pattern: Function/Resolution → Profiling Engine → Report/Visualization
Device: Profile@1.0
This device wraps various profiling tools (eflame, eprof, event counters) for analyzing HyperBEAM performance. Supports both Erlang function profiling and AO-Core path resolution profiling, with configurable output formats and access controls.
Supported Engines
- eflame - Flame graph visualization (requires ENABLE_EFLAME feature)
- eprof - Erlang profiler with function call analysis
- event - HyperBEAM event counter profiling
Dependencies
- HyperBEAM:
hb_ao,hb_opts,hb_maps,hb_util,hb_message,hb_features,hb_event,hb_format,hb_escape,hb_json - Erlang:
eprof,eflame(optional) - Includes:
include/hb.hrl
Configuration
Node Options
#{
% Enable profiling
profiling => true, % Enable for all
profiling => false, % Disable completely
profiling => [Address1, Address2], % Whitelist signers
% Mode-based defaults
mode => debug, % Allow open mode
mode => prod, % Restrict to message mode
% eflame options
profiler_allow_open => true, % Allow opening flame graphs
profiler_open_cmd => "open", % Command to open files
profiler_open_delay => 500 % Delay before cleanup (ms)
}Access Control
Production:- Profiling disabled by default
- Requires explicit signer whitelist
- Profiling enabled by default
- Allows local file opening
- Profiling enabled automatically
Public Functions Overview
%% Device Interface
-spec info(Msg) -> DeviceInfo.
%% Profiling API
-spec eval(Fun) -> Result.
-spec eval(Fun, Opts) -> Result.
-spec eval(Fun, Req, Opts) -> Result | {ok, Message}.
-spec eval(PathKey, Base, Req, Opts) -> Result | {ok, Message}.Public Functions
1. info/1
-spec info(Msg) -> #{
excludes => [binary()],
default => fun()
}
when
Msg :: map().Description: Return device metadata. Defaults to eval function, excludes keys/set operations.
-module(dev_profile_info_test).
-include_lib("eunit/include/eunit.hrl").
info_test() ->
Info = dev_profile:info(#{}),
?assert(maps:is_key(default, Info)),
?assert(maps:is_key(excludes, Info)),
?assert(lists:member(<<"keys">>, maps:get(excludes, Info))).2. eval/1, eval/2, eval/3, eval/4
-spec eval(Fun) -> {EngineOut, Result}
when
Fun :: function().
-spec eval(Fun, Opts) -> {EngineOut, Result}
when
Fun :: function(),
Opts :: map().
-spec eval(Fun, Req, Opts) -> {EngineOut, Result} | {ok, EngineMessage}
when
Fun :: function(),
Req :: map(),
Opts :: map().
-spec eval(PathKey, Base, Req, Opts) -> {ok, EngineMessage} | {error, Reason}
when
PathKey :: binary(),
Base :: map(),
Req :: map(),
Opts :: map().Description: Profile a function or AO-Core resolution path. Returns profiling data in requested format (console or message).
Return Modes:open- Local console output (debug mode only)message- Structured message response (default)
-module(dev_profile_eval_test).
-include_lib("eunit/include/eunit.hrl").
eval_function_eprof_test() ->
Result = dev_profile:eval(
fun() -> lists:seq(1, 1000) end,
#{
<<"engine">> => <<"eprof">>,
<<"return-mode">> => <<"message">>
},
#{}
),
?assertMatch(
{ok, #{
<<"content-type">> := <<"text/plain">>,
<<"body">> := Body
}} when byte_size(Body) > 0,
Result
).
eval_resolution_test() ->
Result = hb_ao:resolve(
#{
<<"path">> => <<"/~profile@1.0/run?run=/~meta@1.0/build">>,
<<"engine">> => <<"eprof">>,
<<"return-mode">> => <<"message">>
},
#{}
),
?assertMatch({ok, #{ <<"body">> := _ }}, Result).
eval_with_engine_selection_test() ->
% Test that engine function works with eprof (event requires prometheus)
Result = dev_profile:eval(
fun() -> lists:seq(1, 10) end,
#{
<<"engine">> => <<"eprof">>,
<<"return-mode">> => <<"message">>
},
#{}
),
?assertMatch({ok, _}, Result).Profiling Engines
1. eflame - Flame Graph Profiler
-spec eflame_profile(Fun, Req, Opts) -> {ok, SVGMessage} | {SVG, Result}
when
Fun :: function(),
Req :: map(),
Opts :: map().Description: Generate flame graph visualizations using eflame. Requires ENABLE_EFLAME compile flag.
- Stack trace flame graphs
- Time-based flame charts
- Automatic file handling
- Optional local opening
merge(default) - Merge stacks by functiontime- Flame chart with time ordering
openmode - Opens SVG locally, returns function resultmessagemode - Returns SVG as structured message
% No tests - eflame_profile is an internal function
% Requires ENABLE_EFLAME compile flag2. eprof - Function Call Profiler
-spec eprof_profile(Fun, Req, Opts) -> {ok, LogMessage} | Result
when
Fun :: function(),
Req :: map(),
Opts :: map().Description: Profile function execution times using Erlang's built-in eprof tool.
Features:- Per-function call counts
- Total execution time
- Time per call statistics
- Text-based output
openmode - Prints to console, returns function resultmessagemode - Returns log as text message
% No tests - eprof_profile is an internal function
% Tested via public eval/3 interface3. event - Event Counter Profiler
-spec event_profile(Fun, Req, Opts) -> {ok, CounterDiff} | Result
when
Fun :: function(),
Req :: map(),
Opts :: map().Description: Profile using HyperBEAM's event counting system. Tracks internal operations and metrics.
Features:- Operation counters
- Event type tracking
- Lightweight overhead
- Structured diff output
openmode - Prints formatted counters, returns function resultmessagemode - Returns counter diff map
% No tests - event_profile is an internal function
% Event engine requires prometheus tables not available in test environmentValidation System
Profiling Enabled Check
validate_enabled(Opts) ->
case find_profiling_config(Opts) of
false -> {validation_error, disabled};
_ -> true
end.- Explicit
profilingoption modesetting (prod=disabled, debug/test=enabled)- TEST build flag
Return Mode Validation
validate_return_mode(Req, Opts) ->
case return_mode(Req, Opts) of
<<"open">> ->
% Only allowed in debug mode
hb_opts:get(mode, prod, Opts) == debug;
_ -> true
end.Signer Authorization
validate_signer(Req, Opts) ->
case find_profiling_config(Opts) of
ValidSigners when is_list(ValidSigners) ->
% Check if request signer in whitelist
lists:any(
fun(Signer) ->
lists:member(Signer, ValidSigners)
end,
hb_message:signers(Req, Opts)
);
EnableProfiling -> EnableProfiling
end.Common Patterns
%% Profile a specific function
Result = dev_profile:eval(
fun() -> expensive_computation() end,
#{},
#{}
).
%% Profile with specific engine
{ok, FlameGraph} = dev_profile:eval(
fun() -> my_function() end,
#{
<<"engine">> => <<"eflame">>,
<<"return-mode">> => <<"message">>
},
#{}
).
%% Profile AO-Core resolution
{ok, ProfileData} = hb_ao:resolve(
#{
<<"path">> => <<"/~profile@1.0/run">>,
<<"eval">> => <<"/~meta@1.0/build">>,
<<"engine">> => <<"eprof">>,
<<"return-mode">> => <<"message">>
},
#{}
).
%% Profile with event counters
{ok, Counters} = dev_profile:eval(
fun() -> hb_ao:resolve(Process, Request, #{}) end,
#{
<<"engine">> => <<"event">>,
<<"return-mode">> => <<"message">>
},
#{}
).
%% Local development profiling (debug mode)
% Opens flame graph automatically
dev_profile:eval(
fun() -> slow_function() end,
#{
<<"engine">> => <<"eflame">>,
<<"return-mode">> => <<"open">>
},
#{ mode => debug, profiler_allow_open => true }
).
%% Production profiling with whitelist
Node = hb_http_server:start_node(#{
profiling => [
<<"authorized_user_1">>,
<<"authorized_user_2">>
],
mode => prod
}),
% Only authorized signers can profile
SignedReq = hb_message:commit(
#{
<<"path">> => <<"/~profile@1.0/run">>,
<<"eval">> => <<"/expensive-operation">>,
<<"engine">> => <<"eprof">>
},
AuthorizedWallet
),
{ok, ProfileResult} = hb_http:post(Node, SignedReq, #{}).HTTP Integration
%% Via HTTP query parameters
GET /~profile@1.0/run?run=/~meta@1.0/build&engine=eprof
%% Via POST with body
POST /~profile@1.0/eval
{
"eval": "/path/to/profile",
"engine": "eflame",
"return-mode": "message"
}Engine Selection
% Default engine (eflame if available, else eprof)
engine(default) -> {ok, default()}.
default() ->
case hb_features:eflame() of
true -> fun eflame_profile/3;
false -> fun eprof_profile/3
end.
% Explicit engines
engine(<<"eflame">>) -> {ok, fun eflame_profile/3};
engine(<<"eprof">>) -> {ok, fun eprof_profile/3};
engine(<<"event">>) -> {ok, fun event_profile/3};
engine(Unknown) -> {unknown_engine, Unknown}.Flame Graph Options
Stack Merging Modes
Merge (default):#{ <<"mode">> => <<"merge">> }
% Combines identical stack traces
% Shows aggregate function time#{ <<"mode">> => <<"time">> }
% Preserves temporal ordering
% Shows execution timelineFile Handling
% Temporary file generation
temp_file() ->
<<"profile-",
(integer_to_binary(os:system_time(microsecond)))/binary,
".out">>.
temp_file(Ext) ->
<<"profile-",
(integer_to_binary(os:system_time(microsecond)))/binary,
".",
Ext/binary>>.
% Automatic cleanup
file:write_file(TempFile, Data),
Result = process_file(TempFile),
file:delete(TempFile).Error Handling
% Validation errors
{error, <<"Profiling is disabled.">>}
{error, <<"Unsupported engine `unknown`">>}
{error, <<"Invalid request.">>}
{error, <<"Path key `X` not found in request.">>}
% Engine errors
{error, <<"eflame is not enabled.">>}
{error, <<"Execution of function to profile failed.">>}
% Authorization errors (silent failure)
% Returns false from validate_signer/2Security Considerations
Production Restrictions
- Profiling disabled by default
- Requires explicit whitelist
openmode forbidden- Only authorized signers
Debug Mode Allowances
- Profiling enabled by default
- Local file opening allowed
- Console output permitted
- No signer restrictions
Performance Impact
eflame
- Overhead: Moderate (stack sampling)
- Output: Large SVG files
- Best For: Overall performance analysis
eprof
- Overhead: Low-Moderate (function tracing)
- Output: Text statistics
- Best For: Function-level analysis
event
- Overhead: Very Low (counter increments)
- Output: Structured counters
- Best For: Operation counting
Output Formats
eflame SVG
<svg ...>
<!-- Flame graph visualization -->
<rect ... fill="rgb(...)" />
<text>function_name</text>
...
</svg>eprof Text
FUNCTION CALLS % TIME [uS / CALLS]
module:function/arity 1234 15 5678 [ 4.6]
...event Map
#{
<<"cache_read">> => 42,
<<"cache_write">> => 13,
<<"resolve">> => 5,
...
}References
- eflame - Flame graph generation library
- eprof - Erlang profiler
- Event System -
hb_event.erl - Features -
hb_features.erl - AO Core -
hb_ao.erl
Notes
- Compile Flag: eflame requires
ENABLE_EFLAMEat compile time - Mode Sensitive: Behavior changes based on debug/prod mode
- Access Control: Three-tier system (disabled, whitelist, all)
- Return Modes: Console vs. message output
- Engine Selection: Automatic fallback if eflame unavailable
- File Cleanup: Temporary files automatically deleted
- Error Recovery: Profiler failures don't crash function
- Signer Whitelist: Per-node configuration
- HTTP Support: Full integration with HTTP interface
- Stack Naming: Attempts to use meaningful path names
- Flamegraph.pl: External Perl script for SVG generation
- Default Engine: Prefers eflame, falls back to eprof
- Event Counters: Lightweight, always available
- Path Resolution: Can profile any AO-Core path
- Production Safety: Strong defaults prevent unauthorized profiling