Current section
Files
Jump to
Current section
Files
lib/tracer_server.ex
defmodule Tracer.Server do
@moduledoc """
Orchestrates the tracing session
"""
use GenServer
alias __MODULE__
alias Tracer.{AgentCmds, ToolServer, ProbeList, Tool}
@server_name __MODULE__
defstruct tool_server_pid: nil,
tracing: false,
forward_pid: nil,
probes: [],
node: nil,
agent_pids: [],
tool: nil
defmacro ensure_server_up(do: clauses) do
quote do
case Process.whereis(@server_name) do
nil ->
start()
unquote(clauses)
pid ->
unquote(clauses)
end
end
end
def start_link(params) do
GenServer.start_link(__MODULE__, params, [name: @server_name])
end
def start do
Tracer.Supervisor.start_server()
end
def stop do
case Process.whereis(@server_name) do
nil -> {:error, :not_running}
pid ->
GenServer.call(@server_name, :stop_tool)
Tracer.Supervisor.stop_server(pid)
end
end
[:stop_tool]
|> Enum.each(fn cmd ->
def unquote(cmd)() do
ensure_server_up do
GenServer.call(@server_name, unquote(cmd))
end
end
end)
def set_tool(%{"__tool__": _} = tool) do
with :ok <- Tool.valid?(tool) do
ensure_server_up do
GenServer.call(@server_name, {:set_tool, tool})
end
end
end
def set_tool(_) do
raise ArgumentError, message: "Argument is not a tool"
end
def start_tool(%{"__tool__": _} = tool) do
with :ok <- Tool.valid?(tool) do
ensure_server_up do
GenServer.call(@server_name, {:start_tool, tool})
end
end
end
def init(_params) do
Process.flag(:trap_exit, true)
{:ok, %Server{}}
end
def handle_call({:set_tool, tool}, _from, %Server{} = state) do
{:reply, :ok, put_in(state.tool, tool)}
end
def handle_call({:start_tool, tool}, _from, %Server{} = state) do
with %Server{} = state <- stop_if_tracing(state),
%Server{} = state <- get_running_config(state, tool),
:ok <- ProbeList.valid?(state.probes),
ret when is_pid(ret) <- ToolServer.start(tool) do
state = state
|> Map.put(:tool_server_pid, ret)
agent_opts = Tool.get_agent_opts(tool)
node = Keyword.get(agent_opts, :node, state.node)
{ret, new_state} = case AgentCmds.start(node,
state.probes,
[forward_pid: state.tool_server_pid]
++ agent_opts) do
{:error, error} ->
{{:error, error}, state}
agent_pids ->
new_state = state
|> Map.put(:agent_pids, agent_pids)
|> Map.put(:node, node)
|> Map.put(:tracing, true)
# TODO get a notification from agents
:timer.sleep(5)
report_message(state,
:started_tracing,
"started tracing")
{:ok, new_state}
end
{:reply, ret, new_state}
else
error ->
{:reply, error, state}
end
end
def handle_call(:stop_tool, _from, %Server{} = state) do
{ret, state} = handle_stop_trace(state)
{:reply, ret, state}
end
def handle_info({:EXIT, pid, :done_reporting},
%Server{} = state) do
if state.tool_server_pid == pid do
{:noreply, put_in(state.tool_server_pid, nil)}
else
{:noreply, state}
end
end
def handle_info({:EXIT, pid, {:done_tracing, exit_status}},
%Server{} = state) do
state = handle_agent_exit(state, pid)
report_message(state,
{:done_tracing, exit_status},
"done tracing: #{inspect exit_status}")
{:noreply, state}
end
def handle_info({:EXIT, pid, {:done_tracing, key, val}},
%Server{} = state) do
state = handle_agent_exit(state, pid)
report_message(state,
{:done_tracing, key, val},
"done tracing: #{to_string(key)} #{val}")
{:noreply, state}
end
def handle_info({:EXIT, pid, exit_code},
%Server{} = state) do
state = handle_agent_exit(state, pid)
report_message(state,
{:done_tracing, exit_code},
"done tracing: #{inspect exit_code}")
{:noreply, state}
end
defp get_running_config(state, tool) do
tool
|> Tool.get_probes()
|> case do
nil -> state
probes when is_list(probes) ->
put_in(state.probes, probes)
probe ->
put_in(state.probes, [probe])
end
|> Map.put(:forward_pid, Tool.get_forward_to(tool))
end
defp report_message(state, event, message) do
if is_pid(state.forward_pid) do
send state.forward_pid, event
else
IO.puts(message)
end
end
defp handle_stop_trace(state) do
{ret, state} = case AgentCmds.stop(state.agent_pids) do
{:error, error} ->
{{:error, error}, state}
_ ->
new_state = state
|> Map.put(:agent_pids, [])
|> Map.put(:node, nil)
|> Map.put(:tracing, false)
{:ok, new_state}
end
ToolServer.stop(state.tool_server_pid)
state = put_in(state.tool_server_pid, nil)
{ret, state}
end
defp handle_agent_exit(state, pid) do
agent_pids = state.agent_pids -- [pid]
state = put_in(state.agent_pids, agent_pids)
if Enum.empty?(agent_pids) do
{_ret, state} = handle_stop_trace(state)
state
else
state
end
end
defp stop_if_tracing(%Server{tracing: false} = state), do: state
defp stop_if_tracing(state), do: elem(handle_stop_trace(state), 1)
end