Current section

Files

Jump to
new_relic_agent lib new_relic distributed_trace.ex
Raw

lib/new_relic/distributed_trace.ex

defmodule NewRelic.DistributedTrace do
@dt_header "newrelic"
@moduledoc false
alias NewRelic.DistributedTrace.{Context, Tracker}
alias NewRelic.Harvest.Collector.AgentRun
alias NewRelic.Transaction
def accept_distributed_trace_payload(:http, conn) do
case Plug.Conn.get_req_header(conn, @dt_header) do
[trace_payload | _] ->
trace_payload
|> Context.decode()
[] ->
:no_payload
end
end
def create_distributed_trace_payload(:http) do
case get_tracing_context() do
nil -> []
context -> [{@dt_header, Context.encode(context, get_current_span_guid())}]
end
end
def generate_new_context() do
{priority, sampled} = generate_sampling()
%Context{
account_id: AgentRun.account_id(),
app_id: AgentRun.primary_application_id(),
trust_key: AgentRun.trusted_account_key(),
priority: priority,
sampled: sampled
}
end
def track_transaction(context, transport_type: type) do
context
|> assign_transaction_guid()
|> report_attributes(transport_type: type)
|> convert_to_outbound()
|> set_tracing_context()
end
def report_attributes(
%Context{parent_id: nil} = context,
transport_type: _type
) do
[
guid: context.guid,
traceId: context.guid,
priority: context.priority,
sampled: context.sampled
]
|> NewRelic.add_attributes()
context
end
def report_attributes(context, transport_type: type) do
[
"parent.type": context.type,
"parent.app": context.app_id,
"parent.account": context.account_id,
"parent.transportType": type,
"parent.transportDuration": transport_duration(context.timestamp),
parentId: context.parent_id,
parentSpanId: context.span_guid,
guid: context.guid,
traceId: context.trace_id,
priority: context.priority,
sampled: context.sampled
]
|> NewRelic.add_attributes()
context
end
def convert_to_outbound(%Context{parent_id: nil} = context) do
%Context{
account_id: AgentRun.account_id(),
app_id: AgentRun.primary_application_id(),
parent_id: nil,
trust_key: context.trust_key,
guid: context.guid,
trace_id: context.guid,
priority: context.priority,
sampled: context.sampled
}
end
def convert_to_outbound(%Context{} = context) do
%Context{
account_id: AgentRun.account_id(),
app_id: AgentRun.primary_application_id(),
parent_id: context.guid,
trust_key: context.trust_key,
guid: context.guid,
trace_id: context.trace_id,
priority: context.priority,
sampled: context.sampled
}
end
def set_tracing_context(context) do
Tracker.store(self(), context: context)
end
def cleanup_context() do
Tracker.cleanup(self())
end
def get_tracing_context() do
if Transaction.Reporter.tracking?(self()) do
self()
|> Transaction.Reporter.root()
|> Tracker.fetch()
end
end
def set_span(:generic, attrs) do
Process.put(:nr_current_span_attrs, attrs)
end
def set_span(:http, url: url, method: method, component: component) do
Process.put(:nr_current_span_attrs, %{url: url, method: method, component: component})
end
def set_span(
:datastore,
statement: statement,
instance: instance,
address: address,
hostname: hostname,
component: component
) do
Process.put(:nr_current_span_attrs, %{
statement: statement,
instance: instance,
address: address,
hostname: hostname,
component: component
})
end
def get_span_attrs() do
Process.get(:nr_current_span_attrs) || %{}
end
def set_current_span(label: label, ref: ref) do
current = {label, ref}
previous = Process.get(:nr_current_span)
Process.put(:nr_current_span, current)
{current, previous}
end
def get_current_span_guid() do
case Process.get(:nr_current_span) do
nil -> generate_guid(pid: self())
{label, ref} -> generate_guid(pid: self(), label: label, ref: ref)
end
end
def reset_span(previous: previous) do
Process.put(:nr_current_span, previous)
end
defp generate_sampling() do
case {generate_sample?(), generate_priority()} do
{true, priority} -> {priority + 1, true}
{false, priority} -> {priority, false}
end
end
defp generate_sample?() do
NewRelic.DistributedTrace.BackoffSampler.sample?()
end
defp generate_priority, do: :rand.uniform() |> Float.round(6)
def assign_transaction_guid(context) do
Map.put(context, :guid, generate_guid())
end
def generate_guid(), do: :crypto.strong_rand_bytes(8) |> Base.encode16() |> String.downcase()
def generate_guid(pid: pid), do: encode_guid([pid, node()])
def generate_guid(pid: pid, label: label, ref: ref), do: encode_guid([label, ref, pid, node()])
def encode_guid(segments) when is_list(segments) do
segments
|> Enum.map(&encode_guid/1)
|> Enum.join("")
|> String.pad_trailing(16, "0")
end
def encode_guid(term) do
term
|> :erlang.phash2()
|> Integer.to_charlist(16)
|> to_string()
|> String.slice(0..4)
|> String.downcase()
end
defp transport_duration(context_start_time) do
(System.system_time(:millisecond) - context_start_time) / 1_000
end
end