%% BEAM calls and returned rows expose bounded work that E2E cannot measure.
-module(albedo_retrieval_test_support).
-export([measure/2]).
measure(Owner, Run) ->
%% EUnit's inherited inparallel mode can overlap tests from this module.
%% Legacy trace patterns are VM-wide, so measurements must not disable
%% one another's patterns while a different test is still running.
global:trans({?MODULE, self()}, fun() -> measure_locked(Owner, Run) end, [node()]).
measure_locked(Owner, Run) ->
Processes = lists:usort([self(), Owner]),
Patterns = [
{'albedo@harness@search', pages, 3},
{'albedo@daemon@conversation', read_input, 2},
{'albedo@daemon@conversation', sourced_entry, 3}
],
Rows = {'albedo@daemon@store', rows, 4},
lists:foreach(fun({Module, _, _}) -> {module, Module} = code:ensure_loaded(Module) end, [Rows | Patterns]),
Tracer = spawn(fun() -> collect({counts, 0, 0, 0, 0, 0, #{}}, none) end),
lists:foreach(fun(Pattern) -> erlang:trace_pattern(Pattern, true, [local]) end, Patterns),
erlang:trace_pattern(Rows, [{'_', [], [{return_trace}]}], [local]),
lists:foreach(fun(Pid) -> erlang:trace(Pid, true, [call, {tracer, Tracer}]) end, Processes),
try
Value = Run(),
Tracer ! {result, self(), Processes},
receive {measurement, Counts} -> {Value, Counts} end
after
lists:foreach(fun(Pid) -> erlang:trace(Pid, false, [call]) end, Processes),
lists:foreach(fun(Pattern) -> erlang:trace_pattern(Pattern, false, [local]) end, [Rows | Patterns]),
exit(Tracer, kill)
end.
%% Consume delivery markers in the same mailbox as trace events before
%% reporting counts, while call tracing remains enabled.
collect(State, Finish) ->
receive
{result, Reply, Processes} ->
Refs = [erlang:trace_delivered(Pid) || Pid <- Processes],
collect(State, {Reply, Refs});
{trace_delivered, _, Ref} ->
{Reply, Refs} = Finish,
case lists:delete(Ref, Refs) of
[] ->
{counts, Pages, Decoded, Sources, Nodes, Kept, _} = State,
Reply ! {measurement, {counts, Pages, Decoded, Sources, Nodes, Kept}},
collect(State, none);
Pending -> collect(State, {Reply, Pending})
end;
Event -> collect(event(Event, State), Finish)
end.
event({trace, _, call, {'albedo@harness@search', pages, [_, _, Acc]}},
{counts, Pages, Decoded, Sources, Nodes, Kept, Queries}) ->
Size = case Acc of {page, _, Matches} -> length(Matches); _ -> 0 end,
{counts, Pages + 1, Decoded, Sources, Nodes, max(Kept, Size), Queries};
event({trace, _, call, {'albedo@daemon@conversation', read_input, _}},
{counts, Pages, Decoded, Sources, Nodes, Kept, Queries}) ->
{counts, Pages, Decoded + 1, Sources, Nodes, Kept, Queries};
event({trace, _, call, {'albedo@daemon@conversation', sourced_entry, _}},
{counts, Pages, Decoded, Sources, Nodes, Kept, Queries}) ->
{counts, Pages, Decoded, Sources + 1, Nodes, Kept, Queries};
event({trace, Pid, call, {'albedo@daemon@store', rows, [_, Sql, _, _]}},
{counts, Pages, Decoded, Sources, Nodes, Kept, Queries}) ->
IsNodes = case Sql of
<<"SELECT id,depth", _/binary>> -> true;
<<"SELECT n.id,n.depth", _/binary>> -> true;
_ -> false
end,
{counts, Pages, Decoded, Sources, Nodes, Kept, Queries#{Pid => IsNodes}};
event({trace, Pid, return_from, {'albedo@daemon@store', rows, 4}, Result},
{counts, Pages, Decoded, Sources, Nodes, Kept, Queries}) ->
Count = case {maps:get(Pid, Queries, false), Result} of
{true, {ok, Values}} when is_list(Values) -> length(Values);
_ -> 0
end,
{counts, Pages, Decoded, Sources, Nodes + Count, Kept, maps:remove(Pid, Queries)};
event(_, State) -> State.