- LiveDashboard library allows us to visualize our application metrics, performance and behavior in real-time. In this post, we’ll add LiveDashboard to our Phoenix app, examine the out-of-the-box features and take a look under the hood to understand how LiveDashboard hooks into Telemetry events in order to visualize them.">The recent release of the LiveDashboard library allows us to visualize our application metrics, performance and behavior in real-time. In this post, we’ll add LiveDashboard to our Phoenix app, examine the out-of-the-box features and take a look under the hood to understand how LiveDashboard hooks into Telemetry events in order to visualize them.
- 使用 Telemetry 和 LiveDashboard 对 Phoenix 进行仪表化
author: Sophie DeBenedetto author_link: https://github.com/sophiedebenedetto categories: general date: 2020-04-24 layout: post title: “Instrumenting Phoenix with Telemetry and LiveDashboard” excerpt: >
The recent release of the LiveDashboard library allows us to visualize our application metrics, performance and behavior in real-time. In this post, we’ll add LiveDashboard to our Phoenix app, examine the out-of-the-box features and take a look under the hood to understand how LiveDashboard hooks into Telemetry events in order to visualize them.
使用 Telemetry 和 LiveDashboard 对 Phoenix 进行仪表化
最近发布的 LiveDashboard 库允许我们实时地可视化应用程序的指标、性能和行为。在这篇文章中,我们将把 LiveDashboard 添加到我们的 Phoenix 应用中,检查开箱即用的功能,并在了解 LiveDashboard 背后是如何钩入 Telemetry 事件以实现可视化。
应用
我们将使用我们为 Telemetry 系列博客文章建立的 Phoenix 应用,Quantum。Quantum 应用并没有做太多事情—它的存在其实只是为了被测量。在这篇文章中,我们将设置一个 Telemetry supervisor,为 Telemetry 事件实现一套度量定义。我们将定义的指标与 Phoenix 和 Ecto 发出的一些开箱即用的 Telemetry 事件相匹配。要深入了解 Telemetry 事件,请查看我们的系列文章 Instrumenting Phoenix with Telemetry。本系列即将发布的文章将仔细研究 Phoenix 和 Ecto 提供的开箱即用的 Telemetry 事件,演练如何添加我们自己的 Telemetry 事件等。
代码
本演示的最终代码可以在这里找到。
添加 LiveDashboard
首先,我们将把 LiveDashboard 依赖添加到我们的 Phoenix 应用中。
# mix.exsdef deps do[{:phoenix_live_dashboard, "~> 0.1"}]end
接下来,我们将确认 LiveView 已经配置了:
# config/config.exsconfig :quantum, QuantumWeb.Endpoint,live_view: [signing_salt: "SECRET_SALT"]
然后呢,我们确认我们应用的 Endpoint 已经声明了 LiveView 的 socket
# lib/quantum_web/endpoint.exsocket "/live", Phoenix.LiveView.Socket
最后,我们将设置从 /dashboard 端点到路由器 LiveDashboard 的请求转发。
use MyAppWeb, :routerimport Phoenix.LiveDashboard.Router...if Mix.env() == :dev doscope "/" dopipe_through :browserlive_dashboard "/dashboard"endend
就是这样! 如果我们运行 mix deps.get,然后运行 mix phx.server,我们将看到我们的 LiveDashboard 和它开箱即用的监控可视化。
LiveDashboard 开箱即用
首页

LiveView 为我们显示了一些开箱即用的监控功能。
在 Home 页面,我们可以看到系统信息,比如我们的 Erlang/OTP 版本,Phoenix 版本和 Elixir 版本。我们还可以看到我们的应用所负责的端口、进程和原子的数量等一些信息。
进程

Processes 页面允许我们对应用程序中运行的进程进行检查。我们可以看到一些有用的信息,比如每个进程占了多少内存,甚至某个进程当前正在执行的功能。检查一个给定的进程,我们可以看到更多的信息,包括它的状态,初始函数和堆栈跟踪。
端口

在 Ports 页面,我们可以看到我们的应用程序所暴露的端口(负责应用程序的 I/O)。
检查一个给定的端口,我们可以看到哪个进程负责暴露该端口并管理该端口的输入/输出。

Sockets
Sockets 页面公开了当前由应用程序管理的所有 socket 的信息。Phoenix 应用中的 socket 负责所有 UDP/TCP 流量。在这里,我们甚至可以看到负责监听端口 :4000 的 socket 连接。

ETS
LiveDashboard 为我们提供的最后一个开箱即用的页面是 ETS 页面。ETS(Erlang Term Storage)是我们的内存存储。我们甚至可以看到 Telemetry handler 表的条目。

LiveDashboard 指标
建立指标
有两个 LiveDashboard 功能,我们要做一点工作才能启用。我们将从 LiveDashboard Metrics 开始,它利用了 telemetry_metrics 库。
首先,我们将把 telemetry_metrics 添加到我们应用程序的依赖中:
# mix.exs{:telemetry_metrics, "~> 0.4"},
然后运行 mix deps.get
Telemetry.Metrics 库提供了一个接口,用于将 Telemetry 事件转换为度量指标。我们将在稍后的一篇博文中更深入地了解这个库,现在只关注一个高级别的理解。
现在我们已经安装了这个库,我们准备好定义 Telemetry 监督器了。监督器将实现一个 metrics/0 函数,声明我们要处理的 Telemetry 事件集,并指定要为这些事件构建哪些度量指标。
# lib/quantum/telemetry.exdefmodule Quantum.Telemetry douse Supervisorimport Telemetry.Metricsdef start_link(arg) doSupervisor.start_link(__MODULE__, arg, name: __MODULE__)enddef init(_arg) doSupervisor.init([], strategy: :one_for_one)enddef metrics do[# Erlang VM Metrics - Formats `gauge` metric typelast_value("vm.memory.total", unit: :byte),last_value("vm.total_run_queue_lengths.total"),last_value("vm.total_run_queue_lengths.cpu"),last_value("vm.system_counts.process_count"),# Database Time Metrics - Formats `timing` metric typesummary("quantum.repo.query.total_time",unit: {:native, :millisecond},tags: [:source, :command]),# Database Count Metrics - Formats `count` metric typecounter("quantum.repo.query.count",tags: [:source, :command]),# Phoenix Time Metrics - Formats `timing` metric typesummary("phoenix.router_dispatch.stop.duration",unit: {:native, :millisecond}),# Phoenix Count Metrics - Formats `count` metric typecounter("phoenix.router_dispatch.stop.count"),counter("phoenix.error_rendered.count")]endend
在这里,我们使用 Telemetry.Metrics 的度量函数(counter、summary 和 last_value)来指定哪些 Telemetry 事件要被视为哪种度量指标。这些函数中的每一个都以 Telemetry 事件为参数,例如,quantum.repo.query.tquery.total_time。我们的 metrics/0 函数中列出的事件都是由 Phoenix 或 Ecto 源码为我们免费执行的。
metrics/0 函数之后会传递给 LiveDashboard LiveView。LiveDashboard 使用这个 Telemetry 事件- metrics 列表,为每个事件附加适当的处理程序。关于如何在 ETS 的帮助下执行和处理 Telemetry 事件,请查看我们的 Telemetry 介绍博客文章。
我们稍后将重新审视它是如何工作的。首先,让我们将新的监督器添加到我们的应用程序的监督树中。
# lib/quantum/application.exchildren = [Quantum.Repo,Quantum.Telemetry,Quantum.Endpoint,...]
现在,我们已经准备好用我们新定义的指标来配置 LiveDashboard。
配置 LiveDashboard 指标
我们将在 live_dashboard 路由调用中添加以下选项。
# lib/quantum_web/router.exlive_dashboard "/dashboard", metrics: Quantum.Telemetry
现在,如果我们访问浏览器中的 /dashboard,并点击 Metrics 标签,我们将看到我们的指标:

让我们来一探究竟,以便更好地了解这个配置是如何工作的。
LiveDashboard Metrics 的背后
live_dashboard 宏包含下面这一行,它将实时 /metrics 请求路由到 Phoenix.LiveDashboard.MetricsLive LiveView,会话 payload 包含我们传递的 metrics.Quantum.Telemetry 选项:
# live_dashboard/router.exlive "/:node/metrics", Phoenix.LiveDashboard.MetricsLive, :metrics, opts
当 Phoenix.LiveDashboard.MetricsLive LiveView 启动时,它会使用我们定义在 Quantum.Telemetry 中指标相关的参数调用 Phoenix.LiveDashboard.TelemetryListener.listen/2 函数。
Phoenix.LiveDashboard.TelemetryListener 模块通过在 ETS 中存储事件/处理程序组合,负责将一组处理程序附加到 Telemetry 事件。Phoenix.LiveDashboard.TelemetryListener 的 init/1 函数对我们在Quantum.Telemetry 中定义的指标进行迭代,并在 ETS 中存储每个度量的 Telemetry 事件名称及其自己的 handle_metrics/4 函数的处理程序。
# live_dashboard/telemetry_listener.exdef init({parent, metrics}) dometrics = Enum.with_index(metrics, 0)metrics_per_event = Enum.group_by(metrics, fn {metric, _} -> metric.event_name end)for {event_name, metrics} <- metrics_per_event doid = {__MODULE__, event_name, self()}:telemetry.attach(id, event_name, &handle_metrics/4, {parent, metrics})end{:ok, %{ref: ref, events: Map.keys(metrics_per_event)}}end
回顾我们之前关于 Telemetry 的文章,:telemetry.attach/4 函数在 ETS 中存储了将 Telemetry 事件映射到处理程序的条目。之后,当给定的 Telemetry 事件被执行时(例如,当 Ecto 源代码执行 "quantum.repo.query.tquery.total_time" 事件时),Telemetry 将在 ETS 中查找这个名称的事件,并调用存储的处理函数,在本例中 Phoenix.LiveDashboard.TelemetryListener.handle_metrics/4。
看看 Phoenix.LiveDashboard.TelemetryListener.handle_metrics/4 函数,我们可以看到它做了一些格式化事件度量的工作,然后发送消息到它的 父亲 — Phoenix.LiveDashboard.MetricsLive LiveView。
# live_dashboard/telemetry_listener.exdef handle_metrics(_event_name, measurements, metadata, {parent, metrics}) dotime = System.system_time(:second)entries =for {metric, index} <- metrics doif measurement = extract_measurement(metric, measurements) dolabel = tags_to_label(metric, metadata){index, label, measurement, time}endendsend(parent, {:telemetry, entries})end
Phoenix.LiveDashboard.MetricsLive LiveView 为这个 {:telemetry, entries} 事件实现了一个 handle_info/2 方法,通过更新 ChartComponent 作为响应,导致 UI 更新。
# live_dashboard/live/metrics_live.exdef handle_info({:telemetry, entries}, socket) dofor {id, label, measurement, time} <- entries dodata = [{label, measurement, time}]send_update(ChartComponent, id: id, data: data)end{:noreply, socket}end
放在一起
让我们来回顾一下所有这些活动部件是如何连接的。
- 我们定义了一个 Telemetry supervisor,
Quantum.Telemetry,它实现了一个metrics/0函数。这个函数负责将一组 Telemetry 事件映射到各种类型的度量。 - 我们将 Telemetry 监督器
Quantum.Telemetry作为一个选项传递给路由器的 LiveDashboard。 - LiveDashboard 启动一个 LiveView,
Phoenix.LiveDashboard.MetricsLive,里面有我们在Quantum.Telemetry中定义的指标。 Phoenix.LiveDashboard.MetricsLive调用Phoenix.LiveDashboard.TelemetryListener,它用自己的handle_metrics/4处理函数将我们在Quantum.Telemetry中定义的 Telemetry 事件作为指标存储在 ETS 中。- 之后,当这些 Telemetry 事件之一被执行时,Telemetry 调用存储的处理函数
Phoenix.LiveDashboard.TelemetryListener.handle_metrics/4。 handle_metrics/4函数将事件格式化为指定的度量,并向Phoenix.LiveDashboard.MetricsLiveLiveView 发送一条消息,然后更新 UI!
这个过程如果很酷,但这里有很多需要解读的地方。如果想深入了解 ETS 中如何存储 Telemetry 事件、执行和处理 Telemetry 事件,请查看本帖。
LiveDashboard 请求记录
最后一个我们要设置的 LiveDashboard 功能是 RequestLogger。这个 LiveDashboard 功能可以让我们记录 LiveDashboard 中所有传入的请求。
添加 RequestLogger
我们需要做的就是将以下内容添加到我们的应用程序的 Endpoint 模块中,就在 Plug.RequestId 之前:
# lib/quantum_web/endpoint.explug Phoenix.LiveDashboard.RequestLogger,param_key: "request_logger",cookie_key: "request_logger"
现在我们可以访问浏览器中的 /dashboard,点击 Request Logger 标签。我们将点击 “enable cookie” 来启用请求日志流的 cookie。现在我们应该看到我们的请求日志流:

让我们简单了解一下 LiveDashboard 背后 RequestLogger 的工作原理。
LiveDashboard RequestLogger 一探究竟
LiveDashboard 路由挂载一个 LiveView, Phoenix.LiveDashboard.RequestLoggerLive:
# live_dashboard/router.exlive "/:node/request_logger",Phoenix.LiveDashboard.RequestLoggerLive,:request_logger,opts
RequestLoggerLive LiveView 从端点抓取主应用程序的 PubSub 服务器并订阅 “请求记录器” 主题:
# live_dashboard/live/request_logger_love.exdef mount(%{"stream" => stream} = params, session, socket) do%{"request_logger" => {param_key, cookie_key}} = sessionif connected?(socket) doendpoint = socket.endpointpubsub_server = endpoint.config(:pubsub_server) || endpoint.__pubsub_server__()Phoenix.PubSub.subscribe(pubsub_server, Phoenix.LiveDashboard.RequestLogger.topic(stream))endsocket =socket|> assign_defaults(params, session)|> assign(stream: stream,param_key: param_key,cookie_key: cookie_key,cookie_enabled: false,autoscroll_enabled: true,messages_present: false){:ok, socket, temporary_assigns: [messages: []]}end
这意味着 RequestLoggerLive LiveView 进程订阅了通过 “请求记录器” 主题广播的任何事件。它为 {:logger, level, message} 事件实现了一个 handle_info/2 函数,并通过更新 socket 和 UI 做出响应:
# live_dashboard/live/request_logger_live.exdef handle_info({:logger, level, message}, socket) do{:noreply, assign(socket, messages: [{message, level}], messages_present: true)}end
什么时候 {:logger, level, message} 事件会通过 PubSub 广播到这个主题?
当 LiveDashboard 启动时,会给应用程序添加一个 新 的日志后台。
# live_dashboard/application.exdefmodule Phoenix.LiveDashboard.Application do@moduledoc falseuse Applicationdef start(_, _) doLogger.add_backend(Phoenix.LiveDashboard.LoggerPubSubBackend) # HERE!children = [{DynamicSupervisor, name: Phoenix.LiveDashboard.DynamicSupervisor, strategy: :one_for_one}]Supervisor.start_link(children, strategy: :one_for_one)endend
Phoenix.LiveDashboard.LoggerPubSubBackend 广播了 log 信息到 RequestLogger PubSub topic 无论何时它收到一条 log 事件:
# live_dashboard/logger_pubsub_backend.exdef handle_event({level, gl, {Logger, msg, ts, metadata}}, {format, keys} = state)when node(gl) == node() dowith {pubsub, topic} <- metadata[:logger_pubsub_backend] dometadata = take_metadata(metadata, keys)formatted = Logger.Formatter.format(format, level, msg, ts, metadata)Phoenix.PubSub.broadcast(pubsub, topic, {:logger, level, formatted}) # HERE!end{:ok, state}end
就是这样!
结语
LiveDashboard 为 Phoenix 开发者的工具箱增加了一个强大的工具。现在,我们可以轻松地可视化我们应用程序,并在开发过程中实时监控其性能和行为。LiveDashboard 代表了 “让开发者幸福” 工具这个不断增长的神殿中的又一个入口,它使 Phoenix 和 Elixir 越来越成为 Web 开发的一个引人注目的选择。
我希望这次功能之旅能让你对 LiveDashboard 感到兴奋,同时我们对它的窥探再次说明了 ETS 和消息传递等 Elixir 语言功能是如何让我们有可能构建强大的系统,并且仍然保持简单而优雅。
