Packages
new_relic_agent
1.23.0-rc.1
1.40.3
1.40.2
1.40.1
1.40.0
1.39.0
1.38.0
1.37.0
1.36.0
1.35.0
1.34.0
1.33.0
1.32.0
1.31.1
1.31.0
1.30.0
1.29.0
1.28.0
1.27.8
1.27.7
1.27.6
1.27.5
1.27.4
1.27.3
1.27.2
1.27.1
1.27.0
1.26.0
1.25.3
1.25.2
1.25.1
1.25.0
1.24.5
1.24.4
1.24.3
1.24.2
1.24.1
1.24.0
1.24.0-rc.10
1.24.0-rc.9
1.24.0-rc.8
1.24.0-rc.7
1.23.6
1.23.5
1.23.4
1.23.3
1.23.2
1.23.1
1.23.0
1.23.0-rc.6
1.23.0-rc.5
1.23.0-rc.4
1.23.0-rc.3
1.23.0-rc.2
1.23.0-rc.1
1.22.6
1.22.5
1.22.4
1.22.3
1.22.2
1.22.1
1.22.0
1.21.2
1.21.1
1.21.0
1.21.0-rc.3
1.21.0-rc.1
1.20.0
1.20.0-rc.1
1.19.7
1.19.6
1.19.5
1.19.4
1.19.3
1.19.2
1.19.1
1.19.0
1.18.5
1.18.4
1.18.3
1.18.2
1.18.1
1.18.0
1.18.0-rc.5
1.18.0-rc.4
1.18.0-rc.2
1.18.0-rc.1
1.17.1
1.17.0
1.17.0-rc.4
1.17.0-rc.3
1.17.0-rc.2
1.17.0-rc.1
1.17.0-rc.0
1.16.7
1.16.6
1.16.5
1.16.4
1.16.3
1.16.2
1.16.1
1.16.0
1.16.0-rc.2
1.16.0-rc.1
1.16.0-rc.0
1.15.0
1.15.0-rc.3
1.15.0-rc.2
1.15.0-rc.1
1.14.0
1.13.1
1.13.0
1.12.0
1.11.0
1.10.2
1.10.1
1.10.0
1.9.12
1.9.11
1.9.10
1.9.9
1.9.8
1.9.7
1.9.6
1.9.5
1.9.4
1.9.3
1.9.2
1.9.1
1.9.0
1.8.0
1.7.0
1.6.2
1.6.1
1.6.0
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.0
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
New Relic's Open-Source Elixir Agent
Current section
Files
Jump to
Current section
Files
lib/new_relic/transaction/sidecar.ex
defmodule NewRelic.Transaction.Sidecar do
@moduledoc false
use GenServer, restart: :temporary
def setup_stores do
:ets.new(__MODULE__.ContextStore, [:named_table, :set, :public, read_concurrency: true])
:ets.new(__MODULE__.LookupStore, [:named_table, :set, :public, read_concurrency: true])
:persistent_term.put({__MODULE__, :counter}, :counters.new(1, []))
end
def track(type) do
{:ok, sidecar} = GenServer.start(__MODULE__, {self(), type})
store_sidecar(self(), sidecar)
set_sidecar(sidecar)
receive do
:sidecar_ready -> :ok
end
end
def init({parent, type}) do
Process.monitor(parent)
send(parent, :sidecar_ready)
counter(:add)
{:ok,
%{
start_time: System.system_time(:millisecond),
type: type,
parent: parent,
exclusions: [],
offspring: MapSet.new(),
attributes: []
}}
end
def connect_parent() do
store_sidecar(self(), get_sidecar())
cast({:add_offspring, self()})
end
def tracking?() do
is_pid(get_sidecar())
end
def track_spawn(parent, child, timestamp) do
parent_sidecar = lookup_sidecar(parent)
store_sidecar(child, parent_sidecar)
cast(parent_sidecar, {:spawn, parent, child, timestamp})
end
def add(attrs) do
cast({:add_attributes, attrs})
end
def incr(attrs) do
attrs
|> wrap(:counter)
|> add()
end
def append(attrs) do
attrs
|> wrap(:list)
|> add()
end
def trace_context(context) do
:ets.insert(__MODULE__.ContextStore, {{:context, get_sidecar()}, context})
end
def trace_context() do
case :ets.lookup(__MODULE__.ContextStore, {:context, get_sidecar()}) do
[{_, value}] -> value
[] -> nil
end
end
def ignore() do
cast(:ignore)
set_sidecar(:no_track)
end
def exclude() do
cast({:exclude, self()})
set_sidecar(:no_track)
end
def complete() do
with sidecar when is_pid(sidecar) <- get_sidecar() do
cleanup(context: sidecar)
cleanup(lookup: self())
clear_sidecar()
cast(sidecar, :complete)
end
end
defp cast(message) do
GenServer.cast(get_sidecar(), message)
end
defp cast(sidecar, message) do
GenServer.cast(sidecar, message)
end
def handle_cast({:add_attributes, attrs}, state) do
{:noreply, %{state | attributes: attrs ++ state.attributes}}
end
def handle_cast({:spawn, _parent, _child, timestamp}, %{start_time: start_time} = state)
when timestamp < start_time do
{:noreply, state}
end
def handle_cast({:spawn, parent, child, timestamp}, state) do
Process.monitor(child)
spawn_attrs = [
process_spawns: {:list, {child, timestamp, parent, NewRelic.Util.process_name(child)}}
]
{:noreply,
%{
state
| attributes: spawn_attrs ++ state.attributes,
offspring: MapSet.put(state.offspring, child)
}}
end
def handle_cast({:exclude, pid}, state) do
{:noreply, %{state | exclusions: [pid | state.exclusions]}}
end
def handle_cast({:add_offspring, pid}, state) do
{:noreply, %{state | offspring: MapSet.put(state.offspring, pid)}}
end
def handle_cast(:ignore, state) do
cleanup(context: self())
cleanup(lookup: state.parent)
{:stop, :normal, state}
end
def handle_cast(:complete, state) do
{:noreply, state, {:continue, :complete}}
end
def handle_info(
{:DOWN, _, _, parent, down_reason},
%{type: :other, parent: parent} = state
) do
attributes = state.attributes
attributes =
with {reason, stack} when reason != :shutdown <- down_reason do
error_attrs = [
error: true,
error_kind: :exit,
error_reason: inspect(reason),
error_stack: inspect(stack)
]
error_attrs ++ attributes
else
_ -> attributes
end
attributes = Keyword.put_new(attributes, :end_time_mono, System.monotonic_time())
{:noreply, %{state | attributes: attributes}, {:continue, :complete}}
end
def handle_info({:DOWN, _, _, child, _}, state) do
exit_attrs = [process_exits: {:list, {child, System.system_time(:millisecond)}}]
{:noreply, %{state | attributes: exit_attrs ++ state.attributes}}
end
def handle_info(_msg, state) do
{:noreply, state}
end
def handle_continue(:complete, state) do
cleanup(context: self())
Enum.each(state.offspring, &cleanup(lookup: &1))
run_complete(state)
counter(:sub)
{:stop, :normal, :completed}
end
defp clear_sidecar() do
Process.delete(:nr_tx_sidecar)
end
defp set_sidecar(nil) do
nil
end
defp set_sidecar(pid) do
Process.put(:nr_tx_sidecar, pid)
pid
end
defp get_sidecar() do
case Process.get(:nr_tx_sidecar) do
nil ->
sidecar =
lookup_sidecar_in(process_callers()) ||
lookup_sidecar_in(process_ancestors())
set_sidecar(sidecar)
:no_track ->
nil
pid ->
pid
end
end
defp lookup_sidecar_in(processes) do
Enum.find_value(processes, &lookup_sidecar/1)
end
defp store_sidecar(_, nil), do: :no_sidecar
defp store_sidecar(pid, sidecar) do
:ets.insert(__MODULE__.LookupStore, {pid, sidecar})
end
defp lookup_sidecar(pid) when is_pid(pid) do
case :ets.lookup(__MODULE__.LookupStore, pid) do
[{_, sidecar}] -> sidecar
[] -> nil
end
end
defp lookup_sidecar(_named_process), do: nil
defp process_callers() do
Process.get(:"$callers", []) |> Enum.reverse()
end
defp process_ancestors() do
Process.get(:"$ancestors", [])
end
defp cleanup(context: sidecar) do
:ets.delete(__MODULE__.ContextStore, {:context, sidecar})
end
defp cleanup(lookup: root) do
:ets.delete(__MODULE__.LookupStore, root)
end
def counter() do
:counters.get(:persistent_term.get({__MODULE__, :counter}), 1)
end
defp counter(:add) do
:counters.add(:persistent_term.get({__MODULE__, :counter}), 1, 1)
end
defp counter(:sub) do
:counters.sub(:persistent_term.get({__MODULE__, :counter}), 1, 1)
end
defp run_complete(%{attributes: attributes} = state) do
attributes
|> Enum.reverse()
|> Enum.reject(&exclude_attrs(&1, state.exclusions))
|> Enum.reduce(%{}, &collect_attr/2)
|> NewRelic.Transaction.Complete.run(state.parent)
end
defp wrap(attrs, tag) do
Enum.map(attrs, fn {key, value} -> {key, {tag, value}} end)
end
defp exclude_attrs({:process_spawns, {:list, {pid, _, _, _}}}, exclusions),
do: pid in exclusions
defp exclude_attrs(_, _), do: false
defp collect_attr({k, {:list, item}}, acc), do: Map.update(acc, k, [item], &[item | &1])
defp collect_attr({k, {:counter, n}}, acc), do: Map.update(acc, k, n, &(&1 + n))
defp collect_attr({k, v}, acc), do: Map.put(acc, k, v)
end