Current section
Files
Jump to
Current section
Files
lib/qtrace/tracer.ex
defmodule Qtrace.Tracer do
@moduledoc false
use GenServer
require Logger
alias DogSketch.SimpleDog
@inactivity_timeout 120_000
def start_link(opts) do
GenServer.start_link(__MODULE__, opts)
end
def get_quantile(pid, quantile, module, function, arity) do
GenServer.call(pid, {:get_quantile, quantile, module, function, arity})
end
def get_sketch(pid, module, function, arity) do
GenServer.call(pid, {:get_sketch, module, function, arity})
end
def list_functions(pid) do
GenServer.call(pid, {:list_functions})
end
def start_supervised(opts \\ []) do
DynamicSupervisor.start_child(
Qtrace.TracerSupervisor,
Supervisor.child_spec({__MODULE__, opts}, restart: :temporary)
)
end
def stop(pid) when is_pid(pid) do
DynamicSupervisor.terminate_child(Qtrace.TracerSupervisor, pid)
end
def init(_opts) do
{:ok, %{ongoing: %{}, completed: %{}}, @inactivity_timeout}
end
def handle_info({:trace_ts, pid, :call, {m, f, a}, ts}, state) when pid != self() do
state = %{
state
| ongoing:
Map.put(
state.ongoing,
{pid, m, f, length(a)},
:erlang.convert_time_unit(ts, :nanosecond, :microsecond)
)
}
{:noreply, state}
end
def handle_info({:trace_ts, pid, :return_from, {m, f, a}, _val, ts}, state)
when pid != self() do
{started_at, ongoing} = Map.pop(state.ongoing, {pid, m, f, a})
if is_nil(started_at) do
{:noreply, state}
else
sketch =
state.completed
|> Map.get_lazy({m, f, a}, fn -> DogSketch.SimpleDog.new(error: 0.04) end)
|> DogSketch.SimpleDog.insert(
max(1, :erlang.convert_time_unit(ts, :nanosecond, :microsecond) - started_at)
)
{:noreply,
%{state | ongoing: ongoing, completed: Map.put(state.completed, {m, f, a}, sketch)},
@inactivity_timeout}
end
end
def handle_info(:timeout, state) do
Logger.debug("Tracer #{inspect(self())} terminating due to inactivity")
{:stop, :normal, state}
end
def handle_info(_msg, state) do
{:noreply, state, @inactivity_timeout}
end
def handle_call({:get_quantile, quantile, m, f, a}, _from, state) do
case Map.get(state.completed, {m, f, a}) do
nil ->
{:reply, nil, state, @inactivity_timeout}
sketch ->
{:reply, SimpleDog.quantile(sketch, quantile), state, @inactivity_timeout}
end
end
def handle_call({:get_sketch, m, f, a}, _from, state) do
case Map.get(state.completed, {m, f, a}) do
nil ->
{:reply, nil, state, @inactivity_timeout}
sketch ->
{:reply, sketch, state, @inactivity_timeout}
end
end
def handle_call({:list_functions}, _from, state) do
{:reply, Map.keys(state.completed), state, @inactivity_timeout}
end
end