Current section
Files
Jump to
Current section
Files
lib/observer_web/tracer.ex
defmodule ObserverWeb.Tracer do
@moduledoc """
This module provides Tracer context
"""
alias ObserverWeb.Rpc
alias ObserverWeb.Tracer.Server, as: TServer
@default_session_timeout_ms 30_000
@default_max_msg 5
@type t :: %__MODULE__{
status: :idle | :running,
session_id: binary() | nil,
max_messages: non_neg_integer(),
session_timeout_ms: non_neg_integer(),
functions_by_node: map(),
request_pid: pid() | nil,
tool: ObserverWeb.Tracer.Tool.t(),
tool_opts: map(),
tool_state: term()
}
defstruct status: :idle,
session_id: nil,
max_messages: @default_max_msg,
session_timeout_ms: @default_session_timeout_ms,
functions_by_node: %{},
request_pid: nil,
tool: :display,
tool_opts: %{},
tool_state: nil
### ==========================================================================
### Public APIs
### ==========================================================================
@doc """
This function retrieves all modules within the passed node
"""
@spec get_modules(node :: atom()) :: list()
def get_modules(node \\ Node.self()) do
Rpc.call(node, :code, :all_loaded, [], :infinity)
|> Enum.map(fn
{module, _} ->
module
# coveralls-ignore-start
module ->
module
# coveralls-ignore-stop
end)
end
@doc """
This function retrieves all functions within the passed node and module
"""
@spec get_module_functions_info(node :: atom(), module :: atom()) :: %{
functions: map(),
module: atom(),
node: atom()
}
def get_module_functions_info(node \\ Node.self(), module) do
functions = Rpc.call(node, module, :module_info, [:functions], :infinity)
externals = Rpc.call(node, module, :module_info, [:exports], :infinity)
all_functions =
(functions ++ externals)
|> Enum.reduce(%{}, fn {name, arity}, acc ->
full_name = "#{name}/#{arity}"
if :erl_internal.guard_bif(name, arity) == false and
regular_functions?(full_name) == false do
Map.put(acc, full_name, %{name: name, arity: arity})
else
acc
end
end)
%{node: node, module: module, functions: all_functions}
end
@doc """
This function retrieves all match specs available in the Tracing page's match-spec picker.
NOTE: `ObserverWeb.Tracer.Server` concatenates the patterns of every selected key into separate
match-spec clauses (not a single clause with combined actions), and Erlang match specs only run
the first clause whose head matches - since every clause here matches on `_`, selecting more than
one key at once means only the first selected key's actions actually run.
"""
@spec get_default_functions_matchspecs :: map()
def get_default_functions_matchspecs do
%{
return_trace: %{
pattern: [{:_, [], [{:return_trace}]}],
name: "Return Trace",
fun: "fun(_) -> return_trace() end"
},
exception_trace: %{
pattern: [{:_, [], [{:exception_trace}]}],
name: "Exception Trace",
fun: "fun(_) -> exception_trace() end"
},
caller: %{
pattern: [{:_, [], [{:message, {:caller}}]}],
name: "Message Caller",
fun: "fun(_) -> message(caller()) end"
},
process_dump: %{
pattern: [{:_, [], [{:message, {:process_dump}}]}],
name: "Message Dump",
fun: "fun(_) -> message(process_dump()) end"
}
}
end
@doc """
Match specs forced in by the Profiling tools (see
`ObserverWeb.Tracer.Tool.forced_match_spec_keys/1`), kept separate from
`get_default_functions_matchspecs/0` so they don't show up as user-facing options in the Tracing
page's match-spec picker - `capture_args` is redundant there (the display tool already traces
full argument values) and `call_seq`'s captured arguments would be mislabeled as the caller by
the display decoding.
`call_seq` combines `return_trace()` and argument capture in a single clause because of the
first-matching-clause limitation described in `get_default_functions_matchspecs/0` - forcing
`return_trace` + `capture_args` as two clauses would only ever run the first one.
"""
@spec get_tool_functions_matchspecs :: map()
def get_tool_functions_matchspecs do
%{
capture_args: %{
pattern: [{:_, [], [{:message, :"$_"}]}],
name: "Capture Arguments",
fun: "fun(_) -> message($_) end"
},
call_seq: %{
pattern: [{:_, [], [{:return_trace}, {:message, :"$_"}]}],
name: "Call Sequence (return + arguments)",
fun: "fun(_) -> return_trace(), message($_) end"
}
}
end
@doc """
This function starts the trace for the passed module/functions
Accepts an optional `:tool` attr (any `ObserverWeb.Tracer.Tool.t()`: `:display` - the default -
`:count`, `:duration`, `:call_seq` or `:flame_graph`) selecting how trace events are reported
back: `:display` streams each event as a `{:new_trace_message, ...}` message like today, other
tools instead accumulate events and report a single `{:tool_report, session_id, report}` message
when the session ends. `:tool_opts` passes per-tool options, e.g. `%{aggregation: :avg}` for
`:duration`.
"""
@spec start_trace(functions :: list(), attrs :: map()) ::
{:ok, t()} | {:error, :already_started}
def start_trace(functions, attrs \\ %{}) do
TServer.start_trace(functions, attrs)
end
@doc """
This function stops the trace for the passed session ID
"""
@spec stop_trace(binary()) :: :ok
def stop_trace(session_id) do
TServer.stop_trace(session_id)
end
@doc """
This function returns the current trace server state
"""
@spec state :: map()
def state do
TServer.state()
end
### ==========================================================================
### Private functions
### ==========================================================================
# coveralls-ignore-start
defp regular_functions?(function) do
String.contains?(function, "-anonymous-") or
String.contains?(function, "-fun-") or
String.contains?(function, "-inlined-") or
String.contains?(function, "-lists^") or
String.contains?(function, "-lc") or
String.contains?(function, "-lbc")
end
# coveralls-ignore-stop
end