/
phoenix_live_view.ex
207 lines (181 loc) · 7.11 KB
/
phoenix_live_view.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
if Code.ensure_loaded?(Phoenix.LiveView) do
defmodule PromEx.Plugins.PhoenixLiveView do
@moduledoc """
This plugin captures metrics emitted by PhoenixLiveView. Specifically, it captures events related to the
mount, handle_event, and handle_params callbacks for live views and live components.
This plugin supports the following options:
- `metric_prefix`: This option is OPTIONAL and is used to override the default metric prefix of
`[otp_app, :prom_ex, :phoenix_live_view]`. If this changes you will also want to set
`phoenix_live_view_metric_prefix` in your `dashboard_assigns` to the snakecase version of your
prefix, the default `phoenix_live_view_metric_prefix` is `{otp_app}_prom_ex_phoenix_live_view`.
- `duration_unit`: This is an OPTIONAL option and is a `Telemetry.Metrics.time_unit()`. It can be one of:
`:second | :millisecond | :microsecond | :nanosecond`. It is `:millisecond` by default.
This plugin exposes the following metric groups:
- `:phoenix_live_view_event_metrics`
- `:phoenix_live_view_component_event_metrics`
To use plugin in your application, add the following to your PromEx module:
```
defmodule WebApp.PromEx do
use PromEx, otp_app: :web_app
@impl true
def plugins do
[
...
PromEx.Plugins.PhoenixLiveView
]
end
@impl true
def dashboards do
[
...
{:prom_ex, "phoenix_live_view.json"}
]
end
end
```
"""
use PromEx.Plugin
alias Phoenix.LiveView.Socket
alias PromEx.Utils
@live_view_mount_stop [:phoenix, :live_view, :mount, :stop]
@live_view_mount_exception [:phoenix, :live_view, :mount, :exception]
@live_view_handle_event_stop [:phoenix, :live_view, :handle_event, :stop]
@live_view_handle_event_exception [:phoenix, :live_view, :handle_event, :exception]
# Coming soon
# @live_view_handle_params_stop [:phoenix, :live_view, :handle_params, :stop]
# @live_view_handle_params_exception [:phoenix, :live_view, :handle_params, :exception]
# Coming soon
# @live_component_handle_event_stop [:phoenix, :live_component, :handle_event, :stop]
# @live_component_handle_event_exception [:phoenix, :live_component, :handle_event, :exception]
@impl true
def event_metrics(opts) do
otp_app = Keyword.fetch!(opts, :otp_app)
metric_prefix = Keyword.get(opts, :metric_prefix, PromEx.metric_prefix(otp_app, :phoenix_live_view))
duration_unit = Keyword.get(opts, :duration_unit, :millisecond)
# Event metrics definitions
[
live_view_event_metrics(metric_prefix, duration_unit),
live_component_event_metrics(metric_prefix)
]
end
defp live_view_event_metrics(metric_prefix, duration_unit) do
bucket_intervals = [10, 100, 500, 1_000, 2_500, 5_000, 10_000]
duration_unit_plural = String.to_atom("#{duration_unit}s")
Event.build(
:phoenix_live_view_event_metrics,
[
distribution(
metric_prefix ++ [:mount, :duration, duration_unit_plural],
event_name: @live_view_mount_stop,
measurement: :duration,
description: "The time it takes for the live view to complete the mount callback.",
reporter_options: [
buckets: bucket_intervals
],
tag_values: &get_mount_socket_tags/1,
tags: [:action, :module],
unit: {:native, duration_unit}
),
distribution(
metric_prefix ++ [:mount, :exception, :duration, duration_unit_plural],
event_name: @live_view_mount_exception,
measurement: :duration,
description:
"The time it takes for the live view to complete the mount callback that resulted in an exception",
reporter_options: [
buckets: bucket_intervals
],
tag_values: &get_mount_exception_tags/1,
tags: [:action, :module, :kind, :reason],
unit: {:native, duration_unit}
),
distribution(
metric_prefix ++ [:handle_event, :duration, duration_unit_plural],
event_name: @live_view_handle_event_stop,
measurement: :duration,
description: "The time it takes for the live view to complete the handle_event callback.",
reporter_options: [
buckets: bucket_intervals
],
tag_values: &get_handle_event_socket_tags/1,
tags: [:event, :action, :module],
unit: {:native, duration_unit}
),
distribution(
metric_prefix ++ [:handle_event, :exception, :duration, duration_unit_plural],
event_name: @live_view_handle_event_exception,
measurement: :duration,
description:
"The time it takes for the live view to complete the handle_event callback that resulted in an exception.",
reporter_options: [
buckets: bucket_intervals
],
tag_values: &get_handle_event_exception_socket_tags/1,
tags: [:event, :action, :module, :kind, :reason],
unit: {:native, duration_unit}
)
]
)
end
defp live_component_event_metrics(_metric_prefix) do
Event.build(
:phoenix_live_view_component_event_metrics,
[]
)
end
defp get_handle_event_exception_socket_tags(%{socket: socket = %Socket{}} = metadata) do
%{
event: metadata.event,
action: get_live_view_action(socket),
module: get_live_view_module(socket),
kind: metadata.kind,
reason: Utils.normalize_exception(metadata.kind, metadata.reason, metadata.stacktrace)
}
end
defp get_handle_event_socket_tags(%{socket: socket = %Socket{}} = metadata) do
%{
event: metadata.event,
action: get_live_view_action(socket),
module: get_live_view_module(socket)
}
end
defp get_mount_socket_tags(%{socket: socket = %Socket{}}) do
%{
action: get_live_view_action(socket),
module: get_live_view_module(socket)
}
end
defp get_mount_exception_tags(%{socket: socket = %Socket{}} = metadata) do
%{
action: get_live_view_action(socket),
module: get_live_view_module(socket),
kind: metadata.kind,
reason: Utils.normalize_exception(metadata.kind, metadata.reason, metadata.stacktrace)
}
end
defp get_live_view_module(%Socket{} = socket) do
socket
|> Map.get(:view, :unknown)
|> normalize_module_name()
end
defp get_live_view_action(%Socket{} = socket) do
socket.assigns
|> Map.get(:live_action, :unknown)
end
defp normalize_module_name(name) when is_atom(name) do
name
|> Atom.to_string()
|> String.trim_leading("Elixir.")
end
defp normalize_module_name(name), do: name
end
else
defmodule PromEx.Plugins.PhoenixLiveView do
@moduledoc false
use PromEx.Plugin
@impl true
def event_metrics(_opts) do
PromEx.Plugin.no_dep_raise(__MODULE__, "PhoenixLiveView")
end
end
end