Packages
new_relic_agent
1.19.2
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/telemetry/redix.ex
defmodule NewRelic.Telemetry.Redix do
use GenServer
@moduledoc """
`NewRelic.Telemetry.Redix` provides `Redix` instrumentation via `telemetry`.
Redix connections are auto-discovered and instrumented.
We automatically gather:
* Datastore metrics
* Transaction Trace segments
* Transaction datastore attributes
* Distributed Trace span events
You can opt-out of this instrumentation as a whole and specifically of
query collection via configuration. See `NewRelic.Config` for details.
"""
def start_link(_) do
enabled = NewRelic.Config.feature?(:redix_instrumentation)
GenServer.start_link(__MODULE__, [enabled: enabled], name: __MODULE__)
end
@redix_connection [:redix, :connection]
@redix_pipeline_stop [:redix, :pipeline, :stop]
@init_events [@redix_connection]
@connected_events [@redix_connection, @redix_pipeline_stop]
def init(enabled: false), do: :ignore
def init(enabled: true) do
config = %{
handler_id: {:new_relic, :redix},
connections: %{},
collect_db_query?: NewRelic.Config.feature?(:db_query_collection)
}
:telemetry.attach_many(
config.handler_id,
@init_events,
&__MODULE__.handle_event/4,
config
)
Process.flag(:trap_exit, true)
{:ok, config}
end
def handle_call({@redix_connection, meta}, _from, config) do
[host, port] = meta.address |> String.split(":")
config =
update_in(
config.connections,
&Map.put(
&1,
meta[:connection_name] || meta[:connection],
%{address: meta.address, host: host, port: port}
)
)
:telemetry.detach(config.handler_id)
:telemetry.attach_many(
config.handler_id,
@connected_events,
&__MODULE__.handle_event/4,
config
)
{:reply, :ok, config}
end
def terminate(_reason, %{handler_id: handler_id}) do
:telemetry.detach(handler_id)
end
def handle_event(@redix_connection, _, meta, _config) do
GenServer.call(__MODULE__, {@redix_connection, meta})
end
def handle_event(
@redix_pipeline_stop,
%{duration: duration},
%{commands: commands} = meta,
config
) do
end_time_ms = System.system_time(:millisecond)
duration_ms = System.convert_time_unit(duration, :native, :millisecond)
duration_s = duration_ms / 1000
start_time_ms = end_time_ms - duration_ms
datastore = "Redis"
{operation, query} = parse_command(commands, collect: config.collect_db_query?)
connection = meta[:connection_name] || meta[:connection]
pid = inspect(self())
id = {:redix, make_ref()}
parent_id = Process.get(:nr_current_span) || :root
instance = config.connections[connection] || %{}
address = instance[:address] || "unknown"
hostname = instance[:host] || "unknown"
port = instance[:port] || "unknown"
metric_name = "Datastore/operation/#{datastore}/#{operation}"
secondary_name = "#{inspect(connection)} #{address}"
NewRelic.Transaction.Reporter.add_trace_segment(%{
primary_name: metric_name,
secondary_name: secondary_name,
attributes: %{
sql: query,
operation: operation,
host: hostname,
port_path_or_id: port
},
pid: pid,
id: id,
parent_id: parent_id,
start_time: start_time_ms,
end_time: end_time_ms
})
NewRelic.report_span(
timestamp_ms: start_time_ms,
duration_s: duration_s,
name: metric_name,
edge: [span: id, parent: parent_id],
category: "datastore",
attributes:
%{
component: datastore,
"span.kind": :client,
"db.statement": query,
"peer.address": address,
"peer.hostname": hostname,
"db.operation": operation
}
|> maybe_add(:"redix.error", meta[:reason])
|> maybe_add(:"redix.connection", meta[:connection])
|> maybe_add(:"redix.connection_name", meta[:connection_name])
)
NewRelic.report_metric({:datastore, datastore, operation}, duration_s: duration_s)
NewRelic.Transaction.Reporter.track_metric({
{:datastore, datastore, operation},
duration_s: duration_s
})
NewRelic.incr_attributes(
databaseCallCount: 1,
databaseDuration: duration_s,
datastore_call_count: 1,
datastore_duration_ms: duration_ms
)
end
def handle_event(_event, _measurements, _meta, _config) do
:ignore
end
@not_collected "[NOT_COLLECTED]"
def parse_command([[operation | _args] = command], collect: true) do
query = Enum.join(command, " ")
{operation, query}
end
def parse_command([[operation | _args]], collect: false) do
{operation, @not_collected}
end
def parse_command(pipeline, collect: true) do
query = pipeline |> Enum.map(&Enum.join(&1, " ")) |> Enum.join("; ")
{"PIPELINE", query}
end
def parse_command(_pipeline, collect: false) do
{"PIPELINE", @not_collected}
end
defp maybe_add(map, _, nil), do: map
defp maybe_add(map, key, %{__exception__: true} = value),
do: Map.put(map, key, Exception.message(value))
defp maybe_add(map, key, value), do: Map.put(map, key, inspect(value))
end