defmodule Replbug.Utils do
def mfas(calls) do
Map.keys(calls)
end
def call_stats(calls, stats_fun) when is_map(calls) do
Map.new(calls, fn {mfa, mfa_calls} ->
{mfa, stats_fun.(mfa_calls)}
end)
end
def counts(calls) when is_map(calls) do
call_stats(calls, &length/1)
end
def total_durations(calls) when is_map(calls) do
call_stats(calls, &total_duration/1)
end
def max_duration_calls(calls) when is_map(calls) do
call_stats(calls, &max_duration_call/1)
end
def min_duration_calls(calls) when is_map(calls) do
call_stats(calls, &min_duration_call/1)
end
def average_durations(calls) when is_map(calls) do
call_stats(calls, &average_duration/1)
end
def max_args(calls) when is_map(calls) do
call_stats(calls, fn mfa_calls ->
Enum.max_by(
mfa_calls,
fn call ->
Enum.max(arg_sizes(call))
end
)
end)
end
def max_returns(calls) when is_map(calls) do
call_stats(calls, fn mfa_calls ->
Enum.max_by(mfa_calls, &return_size/1)
end)
end
def summary(calls) when is_map(calls) do
call_stats(calls, &mfa_summary/1)
end
defp total_duration(calls) when is_list(calls) do
Enum.reduce(calls, 0, fn call, acc -> acc + call.duration end)
end
defp average_duration(calls) when is_list(calls) do
total_duration(calls) / length(calls)
end
defp min_duration_call(calls) when is_list(calls) do
Enum.min_by(calls, & &1.duration)
end
defp max_duration_call(calls) when is_list(calls) do
Enum.max_by(calls, & &1.duration)
end
defp mfa_summary(calls) when is_list(calls) do
{min_d, max_d, total_d} =
Enum.reduce(calls, {:infinity, 0, 0}, fn %{duration: d} = _call,
{min_acc, max_acc, total_acc} ->
{min(d, min_acc), max(d, max_acc), d + total_acc}
end)
%{
count: length(calls),
min_duration: min_d,
max_duration: max_d,
total_duration: total_d,
average_duration: total_d / length(calls)
}
end
defp arg_sizes(call) do
Enum.map(call.args, fn arg -> :erlang_term.byte_size(arg) end)
end
defp return_size(call) do
:erlang_term.byte_size(call.return)
end
@spec collect_traces(:receive | :send | binary | maybe_improper_list, integer, keyword) :: %{
optional(pid) => list
}
@doc """
Collect the traces over the `time_interval` duration.
Note: it's a blocking call, use with care
"""
def collect_traces(call_pattern, time_interval, opts \\ []) do
case Replbug.start(call_pattern, Keyword.put(opts, :time, time_interval)) do
{:ok, _pid} ->
Process.sleep(time_interval + 100)
Replbug.stop()
error ->
error
end
end
end