Current section
Files
Jump to
Current section
Files
lib/tracer/collector.ex
defmodule Tracer.Collector do
alias Tracer.Formatter
import Tracer.Utils
def ensure_started(node) do
case rpc(node, :erlang, :whereis, [__MODULE__]) do
pid when is_pid(pid) -> pid
:undefined -> start(node)
end
end
def start(node \\ node) do
pid = :erlang.spawn(node, __MODULE__, :init, [self])
{rpc(node, :erlang, :register, [__MODULE__, pid]), node}
end
def enable(node, group_leader, limit, processes, trace_options, formatter) do
call({__MODULE__, node}, {:enable, group_leader, limit, processes, trace_options, formatter})
end
def trace_pattern(node, pattern) do
call({__MODULE__, node}, {:set, pattern})
end
def stop(node), do: call({__MODULE__, node}, :stop)
def init(parent) do
:erlang.monitor(:process, parent)
loop(%{parent: parent, formatter: nil, group_leader: nil,
limit: %{time: nil, rate: nil, overall: nil}, window: :os.timestamp(), count: 0, all_count: 0})
end
def loop(state = %{parent: parent}) do
receive do
{{pid, ref} = _from, msg} ->
{action, answer, new_state} = handle_call(msg, state)
send(pid, {ref, answer})
case action do
:stop -> stop
:reply -> loop(new_state)
end
{:DOWN, _, _, ^parent, _} ->
stop
msg ->
handle_trace(msg, state) |> loop()
end
end
def handle_call({:enable, group_leader, new_limit, processes, trace_options, formatter_opts}, state = %{formatter: formatter, limit: limit}) do
:erlang.trace(processes, true, [{:tracer, self} | trace_options])
{ :reply, :ok, %{state | formatter: start_formatter(formatter, group_leader, formatter_opts),
group_leader: group_leader,
limit: :maps.merge(limit, new_limit)} }
end
def handle_call({:set, pattern}, state) do
{{module, _, _} = pattern, match_options, global_options} = pattern
module.module_info()
result = :erlang.trace_pattern(pattern, match_options, global_options)
{:reply, result, state}
end
def handle_call(:stop, state) do
{:stop, :ok, state}
end
def stop() do
:erlang.trace(:all, false, [:all])
:erlang.trace_pattern({:_,:_,:_}, false, [:local, :meta, :call_count, :call_time])
:erlang.trace_pattern({:_,:_,:_}, false, [])
exit({:shutdown, :stop})
end
def handle_trace(trace, %{limit: limit, formatter: formatter, window: window, count: count, all_count: all} = state) when (elem(trace, 0) == :trace) do
send(formatter, trace)
%{time: time, rate: rate, overall: overall} = limit
now = :os.timestamp()
delay = :timer.now_diff(now, window) |> div(1000)
cond do
all > overall -> stop()
delay > time -> %{state | window: now, count: 0, all_count: all + 1}
rate <= count -> stop()
rate > count -> %{state | count: count + 1, all_count: all + 1}
end
end
defp start_formatter(nil, group_leader, options) do
if options[:formatter_local] do
node = :erlang.node(group_leader)
:erlang.spawn_link(node, Formatter, :init, [group_leader, options[:formatter]])
else
Formatter.start_link(group_leader, options[:formatter])
end
end
defp start_formatter(formatter, _group_leader, options) do
if new_formatter = options[:formatter] do
send(formatter, {:formatter, new_formatter})
end
formatter
end
end