Skip to content

Commit

Permalink
Phoenix LiveView instrumentation
Browse files Browse the repository at this point in the history
  • Loading branch information
binaryseed committed Feb 12, 2025
1 parent b1c8d48 commit ebe4134
Show file tree
Hide file tree
Showing 20 changed files with 308 additions and 50 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Some common Elixir packages are auto-instrumented via [`telemetry`](https://gith

* [`Plug`](https://github.com/elixir-plug/plug): See [NewRelic.Telemetry.Plug](https://hexdocs.pm/new_relic_agent/NewRelic.Telemetry.Plug.html) for details.
* [`Phoenix`](https://github.com/phoenixframework/phoenix): See [NewRelic.Telemetry.Phoenix](https://hexdocs.pm/new_relic_agent/NewRelic.Telemetry.Phoenix.html) for details.
* [`Phoenix LiveView`](https://github.com/phoenixframework/phoenix_live_view): See [NewRelic.Telemetry.PhoenixLiveView](https://hexdocs.pm/new_relic_agent/NewRelic.Telemetry.PhoenixLiveView.html) for details.
* [`Ecto`](https://github.com/elixir-ecto/ecto): See [NewRelic.Telemetry.Ecto](https://hexdocs.pm/new_relic_agent/NewRelic.Telemetry.Ecto.html) for details.
* [`Redix`](https://github.com/whatyouhide/redix): See [NewRelic.Telemetry.Redix](https://hexdocs.pm/new_relic_agent/NewRelic.Telemetry.Redix.html) for details.
* [`Finch`](https://github.com/sneako/finch): See [NewRelic.Telemetry.Finch](https://hexdocs.pm/new_relic_agent/NewRelic.Telemetry.Finch.html) for details.
Expand Down
8 changes: 6 additions & 2 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import Config

if Mix.env() == :test, do: import_config("test.exs")
if File.exists?("config/secret.exs"), do: import_config("secret.exs")
if Mix.env() == :test do
import_config("test.exs")
else
if File.exists?("config/secret.exs"),
do: import_config("secret.exs")
end
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ defmodule AbsintheExampleTest do

operation =
Enum.find(spans, fn %{attributes: attr} ->
attr[:name] == "query:TestQuery"
attr[:name] == "Absinthe/Operation/query:TestQuery"
end)

one_resolver =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ defmodule PhxExampleWeb.BanditEndpoint do
same_site: "Lax"
]

socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
socket "/live",
Phoenix.LiveView.Socket,
websocket: [connect_info: [session: @session_options]],
longpoll: [connect_info: [session: @session_options]]

plug Plug.Static,
at: "/",
Expand Down
5 changes: 4 additions & 1 deletion examples/apps/phx_example/lib/phx_example_web/endpoint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ defmodule PhxExampleWeb.Endpoint do
same_site: "Lax"
]

socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
socket "/live",
Phoenix.LiveView.Socket,
websocket: [connect_info: [session: @session_options]],
longpoll: [connect_info: [session: @session_options]]

plug Plug.Static,
at: "/",
Expand Down
28 changes: 27 additions & 1 deletion examples/apps/phx_example/lib/phx_example_web/live/home_live.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
defmodule PhxExampleWeb.HomeLive do
use PhxExampleWeb, :live_view

@impl true
def mount(_params, _session, socket) do
socket =
socket
|> assign(:content, "stuff")

{:ok, socket}
end

def handle_event("click", _params, socket) do
socket =
socket
|> assign(:content, "clicked")

Process.send_after(self(), :after, 1_000)
{:noreply, socket}
end

def handle_info(:after, socket) do
socket =
socket
|> assign(:content, "after")

{:noreply, socket}
end

def render(assigns) do
~H"""
<div>
<h1>Home</h1>
<p>Some content</p>
<p><%= @content %></p>
<div phx-click="click">Click me</div>
</div>
"""
end
Expand Down
1 change: 1 addition & 0 deletions examples/apps/phx_example/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ defmodule PhxExample.MixProject do
{:phoenix_html, "~> 3.3"},
{:phoenix_view, "~> 2.0"},
{:phoenix_live_view, "~> 0.20"},
{:floki, ">= 0.30.0", only: :test},
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:bandit, "~> 1.0"}
Expand Down
57 changes: 40 additions & 17 deletions examples/apps/phx_example/test/phx_example_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ defmodule PhxExampleTest do
TestHelper.simulate_agent_enabled()
end

setup do
TestHelper.restart_harvest_cycle(Collector.Metric.HarvestCycle)
TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)
TestHelper.restart_harvest_cycle(Collector.SpanEvent.HarvestCycle)
TestHelper.restart_harvest_cycle(Collector.ErrorTrace.HarvestCycle)
:ok
end

for server <- [:cowboy, :bandit] do
describe "Testing #{server}:" do
test "Phoenix metrics generated" do
TestHelper.restart_harvest_cycle(Collector.Metric.HarvestCycle)
TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)

{:ok, %{body: body}} = request("/phx/bar", unquote(server))
assert body =~ "Welcome to Phoenix"

Expand All @@ -37,9 +42,6 @@ defmodule PhxExampleTest do
end

test "Phoenix metrics generated for LiveView" do
TestHelper.restart_harvest_cycle(Collector.Metric.HarvestCycle)
TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)

{:ok, %{body: body}} = request("/phx/home", unquote(server))
assert body =~ "Some content"

Expand All @@ -60,23 +62,51 @@ defmodule PhxExampleTest do
end

test "Phoenix spans generated" do
TestHelper.restart_harvest_cycle(Collector.SpanEvent.HarvestCycle)
{:ok, %{body: body}} = request("/phx/home", unquote(server))
assert body =~ "Some content"

span_events = TestHelper.gather_harvest(Collector.SpanEvent.Harvester)

tx_span = TestHelper.find_span(span_events, "/Phoenix/PhxExampleWeb.HomeLive/index")
process_span = TestHelper.find_span(span_events, "Transaction Root Process")
mount_span = TestHelper.find_span(span_events, "PhxExampleWeb.HomeLive:index.mount")

assert process_span[:parentId] == tx_span[:guid]
assert mount_span[:parentId] == process_span[:guid]
end

@endpoint PhxExampleWeb.Endpoint
test "Live View transaction and spans generated" do
import Phoenix.ConnTest
import Phoenix.LiveViewTest

conn =
Phoenix.ConnTest.build_conn()
|> Plug.Test.init_test_session([])

conn = get(conn, "/phx/home")
assert html_response(conn, 200) =~ "<p>Some content</p>"

{:ok, _view, _html} = live(conn)

span_events = TestHelper.gather_harvest(Collector.SpanEvent.Harvester)

tx_span =
TestHelper.find_span(span_events, "Phoenix.LiveView/Live/PhxExampleWeb.HomeLive/index")

process_span = TestHelper.find_span(span_events, "Transaction Root Process")
mount_span = TestHelper.find_span(span_events, "PhxExampleWeb.HomeLive:index.mount")
render_span = TestHelper.find_span(span_events, "PhxExampleWeb.HomeLive:index.render")

assert tx_span[:"live_view.endpoint"] == "PhxExampleWeb.Endpoint"

assert process_span[:parentId] == tx_span[:guid]
assert mount_span[:parentId] == process_span[:guid]
assert render_span[:parentId] == process_span[:guid]
end

@tag :capture_log
test "Phoenix error" do
TestHelper.restart_harvest_cycle(Collector.Metric.HarvestCycle)
TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)

{:ok, %{body: body, status_code: 500}} = request("/phx/error", unquote(server))

assert body =~ "Oops, Internal Server Error"
Expand All @@ -100,9 +130,6 @@ defmodule PhxExampleTest do

@tag :capture_log
test "Phoenix LiveView error" do
TestHelper.restart_harvest_cycle(Collector.Metric.HarvestCycle)
TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)

{:ok, %{body: body, status_code: 500}} = request("/phx/live_error", unquote(server))

assert body =~ "Oops, Internal Server Error"
Expand All @@ -125,10 +152,6 @@ defmodule PhxExampleTest do
end

test "Phoenix route not found" do
TestHelper.restart_harvest_cycle(Collector.Metric.HarvestCycle)
TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)
TestHelper.restart_harvest_cycle(Collector.ErrorTrace.HarvestCycle)

{:ok, %{body: body, status_code: 404}} = request("/not_found", unquote(server))
assert body =~ "Not Found"

Expand Down
2 changes: 1 addition & 1 deletion examples/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ for config <- "../apps/*/config/config.exs" |> Path.expand(__DIR__) |> Path.wild
import_config config
end

if File.exists?("config/secret.exs"), do: import_config("secret.exs")
if File.exists?(Path.expand("./secret.exs", __DIR__)), do: import_config("secret.exs")
1 change: 1 addition & 0 deletions examples/mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"ecto_sqlite3": {:hex, :ecto_sqlite3, "0.14.0", "8710aa6de137a9c428fc86306b574777e9d545534b0259030a325948d43fc740", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.11", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.9", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "4f69e5df5bdf8b98d80797b1a88b1d24b13ad9f187a553241c2aa6cb7447a672"},
"elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"},
"exqlite": {:hex, :exqlite, "0.17.0", "865ab503debde7913ffa02b58838ab92885165978f4c88d8169ee8688c655d1e", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "719fa7986fed242839629a907d60f774000c1d2dc03ba6ba05fcd30579f2ab45"},
"floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"},
"hackney": {:hex, :hackney, "1.17.0", "717ea195fd2f898d9fe9f1ce0afcc2621a41ecfe137fae57e7fe6e9484b9aa99", [:rebar3], [{:certifi, "~>2.5", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "64c22225f1ea8855f584720c0e5b3cd14095703af1c9fbc845ba042811dc671c"},
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"},
Expand Down
2 changes: 1 addition & 1 deletion lib/new_relic/aggregate/aggregate.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defmodule NewRelic.Aggregate do

defp averages(%{call_count: call_count} = values) do
values
|> Stream.reject(fn {key, _value} -> key == :call_count end)
|> Enum.reject(fn {key, _value} -> key == :call_count end)
|> Map.new(fn {key, value} -> {:"avg_#{key}", value / call_count} end)
end

Expand Down
5 changes: 5 additions & 0 deletions lib/new_relic/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ defmodule NewRelic.Config do
* `:plug_instrumentation_enabled`
* `:phoenix_instrumentation_enabled`
* `:phoenix_live_view_instrumentation_enabled`
* `:ecto_instrumentation_enabled`
* `:redix_instrumentation_enabled`
* `:oban_instrumentation_enabled`
Expand Down Expand Up @@ -184,6 +185,10 @@ defmodule NewRelic.Config do
get(:features, :phoenix_instrumentation)
end

def feature?(:phoenix_live_view_instrumentation) do
get(:features, :phoenix_live_view_instrumentation)
end

def feature?(:ecto_instrumentation) do
get(:features, :ecto_instrumentation)
end
Expand Down
5 changes: 5 additions & 0 deletions lib/new_relic/init.ex
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ defmodule NewRelic.Init do
"NEW_RELIC_PHOENIX_INSTRUMENTATION_ENABLED",
:phoenix_instrumentation_enabled
),
phoenix_live_view_instrumentation:
determine_feature(
"NEW_RELIC_PHOENIX_LIVE_VIEW_INSTRUMENTATION_ENABLED",
:phoenix_live_view_instrumentation_enabled
),
oban_instrumentation:
determine_feature(
"NEW_RELIC_OBAN_INSTRUMENTATION_ENABLED",
Expand Down
10 changes: 10 additions & 0 deletions lib/new_relic/metric/metric_data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,16 @@ defmodule NewRelic.Metric.MetricData do
max_call_time: duration_s
}

def transform({:function, function_name}, duration_s: duration_s),
do: %Metric{
name: join(["Function", function_name]),
call_count: 1,
total_call_time: duration_s,
total_exclusive_time: duration_s,
min_call_time: duration_s,
max_call_time: duration_s
}

def transform({:function, function_name},
duration_s: duration_s,
exclusive_time_s: exclusive_time_s
Expand Down
10 changes: 5 additions & 5 deletions lib/new_relic/telemetry/absinthe.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ defmodule NewRelic.Telemetry.Absinthe do

NewRelic.Tracer.Direct.start_span(
meta.id,
"Operation",
start_time: meas.system_time,
"Absinthe/Operation",
system_time: meas.system_time,
attributes: [
"absinthe.schema": inspect(meta.options[:schema]),
"absinthe.query": query
Expand Down Expand Up @@ -116,7 +116,7 @@ defmodule NewRelic.Telemetry.Absinthe do
NewRelic.Tracer.Direct.start_span(
meta.id,
{resolver_name, "#{inspect(resolution.schema)}.#{path}"},
start_time: meas.system_time,
system_time: meas.system_time,
attributes: [
"absinthe.field.path": path,
"absinthe.field.type": type,
Expand Down Expand Up @@ -160,11 +160,11 @@ defmodule NewRelic.Telemetry.Absinthe do
end

defp operation_span_name(%{type: type, name: name}) when is_binary(name) do
"#{to_string(type)}:#{name}"
"Absinthe/Operation/#{to_string(type)}:#{name}"
end

defp operation_span_name(%{type: type}) do
"#{to_string(type)}"
"Absinthe/Operation/#{to_string(type)}"
end

defp transaction_name(schema, operation) do
Expand Down
Loading

0 comments on commit ebe4134

Please sign in to comment.