From 26ec887ff47f7051f6081878bc97e7e5a4f8ca71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Casta=C3=B1o?= Date: Sat, 19 Sep 2020 09:09:27 +0200 Subject: [PATCH 1/2] TabBar links clean params --- lib/phoenix/live_dashboard/components/tab_bar_component.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/phoenix/live_dashboard/components/tab_bar_component.ex b/lib/phoenix/live_dashboard/components/tab_bar_component.ex index 295a696a..d47d73d1 100644 --- a/lib/phoenix/live_dashboard/components/tab_bar_component.ex +++ b/lib/phoenix/live_dashboard/components/tab_bar_component.ex @@ -127,7 +127,8 @@ defmodule Phoenix.LiveDashboard.TabBarComponent do end defp render_tab_link(socket, page, tab, current, id) do - path = live_dashboard_path(socket, page, tab: id) + params = [tab: id] |> maybe_put(:info, page.params.info) + path = live_dashboard_path(socket, page.route, page.node, params) class = "nav-link#{if current == id, do: " active"}" case tab[:method] do @@ -136,6 +137,9 @@ defmodule Phoenix.LiveDashboard.TabBarComponent do end end + defp maybe_put(keyword, _key, nil), do: keyword + defp maybe_put(keyword, key, value), do: [{key, value} | keyword] + defp render_content(socket, page, tabs, current) do case tabs[current][:render] do {component, component_assigns} -> From cbdd2cb26f4ed96f1c52d6d2bb32d7c383e44e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Casta=C3=B1o?= Date: Sun, 27 Sep 2020 12:02:35 +0200 Subject: [PATCH 2/2] It works --- dev.exs | 22 +- .../components/tab_bar_component.ex | 6 +- lib/phoenix/live_dashboard/page_live.ex | 2 +- .../pages/ecto_psql_extras_page.ex | 263 ++++++++++++++++++ lib/phoenix/live_dashboard/router.ex | 11 +- mix.exs | 3 + mix.lock | 8 + 7 files changed, 305 insertions(+), 10 deletions(-) create mode 100644 lib/phoenix/live_dashboard/pages/ecto_psql_extras_page.ex diff --git a/dev.exs b/dev.exs index c10666c6..723885e7 100644 --- a/dev.exs +++ b/dev.exs @@ -174,7 +174,11 @@ defmodule DemoWeb.Router do live_dashboard("/dashboard", metrics: DemoWeb.Telemetry, env_keys: ["USER", "ROOTDIR"], - metrics_history: {DemoWeb.History, :data, []} + metrics_history: {DemoWeb.History, :data, []}, + additional_pages: [ + {"ecto_psql_extras", + {Phoenix.LiveDashboard.Pages.EctoPsqlExtrasPage, %{repo: DemoWeb.Repo}}} + ] ) end end @@ -202,13 +206,29 @@ defmodule DemoWeb.Endpoint do plug DemoWeb.Router end +# Configures the endpoint +Application.put_env(:phoenix_live_dashboard, DemoWeb.Repo, + database: "PUT_YOUR_OWN_DATABASE", + username: "postgres", + password: "postgres", + hostname: "localhost" +) + +defmodule DemoWeb.Repo do + use Ecto.Repo, otp_app: :phoenix_live_dashboard, adapter: Ecto.Adapters.Postgres +end + Application.ensure_all_started(:os_mon) +Application.ensure_all_started(:postgrex) +Application.ensure_all_started(:ecto_sql) +Application.ensure_all_started(:ecto) Application.put_env(:phoenix, :serve_endpoints, true) Task.start(fn -> children = [ {Phoenix.PubSub, [name: Demo.PubSub, adapter: Phoenix.PubSub.PG2]}, {DemoWeb.History, DemoWeb.Telemetry.metrics()}, + DemoWeb.Repo, DemoWeb.Endpoint ] diff --git a/lib/phoenix/live_dashboard/components/tab_bar_component.ex b/lib/phoenix/live_dashboard/components/tab_bar_component.ex index d47d73d1..05ae3b29 100644 --- a/lib/phoenix/live_dashboard/components/tab_bar_component.ex +++ b/lib/phoenix/live_dashboard/components/tab_bar_component.ex @@ -68,7 +68,7 @@ defmodule Phoenix.LiveDashboard.TabBarComponent do {:ok, render} when is_function(render, 0) -> tab - {:ok, {component, args}} when is_atom(component) and is_list(args) -> + {:ok, {component, _args}} when is_atom(component) -> tab {:ok, _invalid} -> @@ -127,7 +127,7 @@ defmodule Phoenix.LiveDashboard.TabBarComponent do end defp render_tab_link(socket, page, tab, current, id) do - params = [tab: id] |> maybe_put(:info, page.params.info) + params = [tab: id] |> maybe_put(:info, page.params[:info]) path = live_dashboard_path(socket, page.route, page.node, params) class = "nav-link#{if current == id, do: " active"}" @@ -143,7 +143,7 @@ defmodule Phoenix.LiveDashboard.TabBarComponent do defp render_content(socket, page, tabs, current) do case tabs[current][:render] do {component, component_assigns} -> - live_component(socket, component, [page: page] ++ component_assigns) + live_component(socket, component, Map.put(component_assigns, :page, page)) # Needed for the metrics page, should be removed soon fun when is_function(fun, 0) -> diff --git a/lib/phoenix/live_dashboard/page_live.ex b/lib/phoenix/live_dashboard/page_live.ex index d7176a75..1e526de7 100644 --- a/lib/phoenix/live_dashboard/page_live.ex +++ b/lib/phoenix/live_dashboard/page_live.ex @@ -114,7 +114,7 @@ defmodule Phoenix.LiveDashboard.PageLive do {:skip, socket} {false, {:disabled, anchor}} -> - {{:disabled, anchor, nil}, socket} + {{:disabled, anchor}, socket} {false, {:disabled, anchor, more_info_url}} -> {{:disabled, anchor, more_info_url}, socket} diff --git a/lib/phoenix/live_dashboard/pages/ecto_psql_extras_page.ex b/lib/phoenix/live_dashboard/pages/ecto_psql_extras_page.ex new file mode 100644 index 00000000..2967b22a --- /dev/null +++ b/lib/phoenix/live_dashboard/pages/ecto_psql_extras_page.ex @@ -0,0 +1,263 @@ +if match?({:module, _}, Code.ensure_compiled(EctoPSQLExtras)) do + {:module, _} = Code.ensure_compiled(Postgrex) + + defmodule Phoenix.LiveDashboard.Pages.EctoPsqlExtrasPage do + @moduledoc false + use Phoenix.LiveDashboard.PageBuilder + + @menu_text "PsqlExtras" + + @impl true + def init(%{repo: repo}) do + {:ok, %{repo: repo}, process: repo} + end + + @impl true + def mount(_params, %{repo: repo}, socket) do + {:ok, assign(socket, repo: repo)} + end + + @impl true + def menu_link(%{repo: repo}, capabilities) do + if repo in capabilities.processes do + {:ok, @menu_text} + else + {:disabled, @menu_text} + end + end + + @impl true + def render_page(assigns) do + tab_bar(tabs: tabs(assigns) |> IO.inspect()) + end + + @tables [ + :cache_hit, + :index_cache_hit, + :table_cache_hit, + :index_usage, + :locks, + :all_locks, + :outliers, + :calls, + :blocking, + :total_index_size, + :index_size, + :table_size, + :table_indexes_size, + :total_table_size, + :unused_indexes, + :seq_scans, + :long_running_queries, + :records_rank, + :bloat, + :vacuum_stats + ] + defp tabs(%{repo: repo}) do + for table_name <- @tables do + {table_name, + name: Phoenix.Naming.humanize(table_name), render: render_table(table_name, repo)} + end + end + + defp render_table(table, repo) do + table( + columns: table_columns(table), + id: :table_id, + # row_attrs: table_row_attrs(table), + row_fetcher: row_fetcher(table, repo), + title: Phoenix.Naming.humanize(table) + ) + end + + defp table_columns(:cache_hit) do + [%{field: :name, sortable: true}, %{field: :ratio, sortable: true}] + end + + defp table_columns(:index_cache_hit) do + [ + %{field: :name, sortable: true}, + %{field: :buffer_hits, sortable: true}, + %{field: :block_reads, sortable: true}, + %{field: :total_read, sortable: true}, + %{field: :ratio, sortable: true} + ] + end + + defp table_columns(:table_cache_hit) do + [ + %{field: :name, sortable: true}, + %{field: :buffer_hits, sortable: true}, + %{field: :block_reads, sortable: true}, + %{field: :total_read, sortable: true}, + %{field: :ratio, sortable: true} + ] + end + + defp table_columns(:index_usage) do + [ + %{field: :relname, sortable: true}, + %{field: :percent_of_times_index_used, sortable: true}, + %{field: :rows_in_table, sortable: true} + ] + end + + defp table_columns(:locks) do + [ + %{field: :procpid, sortable: true}, + %{field: :relname, sortable: true}, + %{field: :transactionid, sortable: true}, + %{field: :granted, sortable: true}, + %{field: :query_snippet, sortable: true}, + %{field: :mode, sortable: true}, + %{field: :age, sortable: true} + ] + end + + defp table_columns(:all_locks) do + [ + %{field: :pid, sortable: true}, + %{field: :relname, sortable: true}, + %{field: :transactionid, sortable: true}, + %{field: :granted, sortable: true}, + %{field: :query_snippet, sortable: true}, + %{field: :mode, sortable: true}, + %{field: :age, sortable: true} + ] + end + + defp table_columns(table) when table in [:outliers, :calls] do + [ + %{field: :qry, sortable: true}, + %{field: :exec_time, sortable: true}, + %{field: :prop_exec_time, sortable: true}, + %{field: :ncalls, sortable: true}, + %{field: :sync_io_time, sortable: true} + ] + end + + defp table_columns(:blocking) do + [ + %{field: :blocked_pid, sortable: true}, + %{field: :blocking_statement, sortable: true}, + %{field: :blocking_duration, sortable: true}, + %{field: :blocking_pid, sortable: true}, + %{field: :blocked_statement, sortable: true}, + %{field: :blocked_duration, sortable: true} + ] + end + + defp table_columns(:total_index_size) do + [ + %{field: :size, sortable: true} + ] + end + + defp table_columns(table) + when table in [ + :index_size, + :index_size, + :total_indexes_size, + :table_size, + :total_table_size + ] do + [ + %{field: :name, sortable: true}, + %{field: :size, sortable: true} + ] + end + + defp table_columns(:table_indexes_size) do + [ + %{field: :table, sortable: true}, + %{field: :size, sortable: true} + ] + end + + defp table_columns(:unused_indexes) do + [ + %{field: :table, sortable: true}, + %{field: :index, sortable: true}, + %{field: :index_size, sortable: true}, + %{field: :index_scans, sortable: true} + ] + end + + defp table_columns(:seq_scans) do + [ + %{field: :name, sortable: true}, + %{field: :count, sortable: true} + ] + end + + defp table_columns(:long_running_queries) do + [ + %{field: :pid, sortable: true}, + %{field: :duration, sortable: true}, + %{field: :query, sortable: true} + ] + end + + defp table_columns(:records_rank) do + [ + %{field: :name, sortable: true}, + %{field: :estimated_count, sortable: true} + ] + end + + defp table_columns(:bloat) do + [ + %{field: :type, sortable: true}, + %{field: :schemaname, sortable: true}, + %{field: :object_name, sortable: true}, + %{field: :bloat, sortable: true}, + %{field: :waste, sortable: true} + ] + end + + defp table_columns(:vacuum_stats) do + [ + %{field: :schema, sortable: true}, + %{field: :table, sortable: true}, + %{field: :last_vacuum, sortable: true}, + %{field: :last_autovacuum, sortable: true}, + %{field: :rowcount, sortable: true}, + %{field: :dead_rowcount, sortable: true}, + %{field: :autovacuum_threshold, sortable: true}, + %{field: :expect_autovacuum, sortable: true} + ] + end + + defp row_fetcher(name, repo) do + fn params, node -> + :rpc.call(node, EctoPSQLExtras, :query, [name, repo, :raw]) + |> calc_rows(params) + end + end + + defp calc_rows(%Postgrex.Result{} = result, params) do + %{search: _search, sort_by: sort_by, sort_dir: sort_dir, limit: limit} = params + sorter = if sort_dir == :asc, do: &<=/2, else: &>=/2 + %{columns: columns, rows: rows} = result |> IO.inspect() + + rows = + rows + |> Enum.map(&Enum.zip(columns, &1)) + |> Enum.map(fn row -> + Map.new(row, fn {key, value} -> {String.to_atom(key), convert_value(value)} end) + end) + + count = length(rows) + rows = rows |> Enum.sort_by(&Map.fetch!(&1, sort_by), sorter) |> Enum.take(limit) + {rows, count} + end + + defp convert_value(%Decimal{} = decimal) do + Decimal.to_float(decimal) + end + + defp convert_value(value) do + value + end + end +end diff --git a/lib/phoenix/live_dashboard/router.ex b/lib/phoenix/live_dashboard/router.ex index 715094f4..7a58406a 100644 --- a/lib/phoenix/live_dashboard/router.ex +++ b/lib/phoenix/live_dashboard/router.ex @@ -135,15 +135,16 @@ defmodule Phoenix.LiveDashboard.Router do defp normalize_additional_pages(pages) do Enum.map(pages, fn - module when is_atom(module) -> - {module, []} + {path, module} when is_binary(path) and is_atom(module) -> + {path, {module, %{}}} - {module, args} when is_atom(module) and is_list(args) -> - {module, args} + {path, {module, args}} when is_binary(path) and is_atom(module) -> + {path, {module, args}} other -> raise ArgumentError, - "invalid :additional_page, must be a tuple {module, args}, got: " <> inspect(other) + "invalid :additional_page, must be a tuple {path, {module, args}}, got: " <> + inspect(other) end) end diff --git a/mix.exs b/mix.exs index dc4ebea5..97b10351 100644 --- a/mix.exs +++ b/mix.exs @@ -45,6 +45,9 @@ defmodule Phoenix.LiveDashboard.MixProject do {:phoenix_live_view, "~> 0.14.3", phoenix_live_view_opts()}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0"}, {:phoenix_html, "~> 2.14.1 or ~> 2.15"}, + {:ecto_psql_extras, "~> 0.1.4", optional: true}, + {:ecto_sql, "~> 3.4", optional: true}, + {:postgrex, "~> 0.0", optional: true}, {:circular_buffer, "~> 0.2", only: :dev}, {:telemetry_poller, "~> 0.4", only: :dev}, {:phoenix_live_reload, "~> 1.2", only: :dev}, diff --git a/mix.lock b/mix.lock index e63eb927..6928ae73 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,15 @@ %{ "circular_buffer": {:hex, :circular_buffer, "0.2.0", "c42be0740855831d04e22e17318a4e186acc3b9ebe9aad8db90b0a08ac0ff589", [:mix], [], "hexpm", "b2a05705485c7576373eeff99b2f76b0048731d9c80c7fa42d6c7b71eb69521e"}, + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, + "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, + "decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"}, "earmark": {:hex, :earmark, "1.4.9", "837e4c1c5302b3135e9955f2bbf52c6c52e950c383983942b68b03909356c0d9", [:mix], [{:earmark_parser, ">= 1.4.9", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "0d72df7d13a3dc8422882bed5263fdec5a773f56f7baeb02379361cb9e5b0d8e"}, "earmark_parser": {:hex, :earmark_parser, "1.4.9", "819bda2049e6ee1365424e4ced1ba65806eacf0d2867415f19f3f80047f8037b", [:mix], [], "hexpm", "8bf54fddabf2d7e137a0c22660e71b49d5a0a82d1fb05b5af62f2761cd6485c4"}, + "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"}, + "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.1.4", "625019c97aced5364d8327fe2c85dc9ae8436a818a34711730e71ed810f1572d", [:mix], [{:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "4dabc82738483d5b117eefc4097ec280ea404579f45384d0e48292fcdc567bac"}, + "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"}, "ex_doc": {:hex, :ex_doc, "0.22.1", "9bb6d51508778193a4ea90fa16eac47f8b67934f33f8271d5e1edec2dc0eee4c", [:mix], [{:earmark, "~> 1.4.0", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "d957de1b75cb9f78d3ee17820733dc4460114d8b1e11f7ee4fd6546e69b1db60"}, "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, "floki": {:hex, :floki, "0.27.0", "6b29a14283f1e2e8fad824bc930eaa9477c462022075df6bea8f0ad811c13599", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "583b8c13697c37179f1f82443bcc7ad2f76fbc0bf4c186606eebd658f7f2631b"}, @@ -21,10 +27,12 @@ "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"}, "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, + "postgrex": {:hex, :postgrex, "0.15.6", "a464c72010a56e3214fe2b99c1a76faab4c2bb0255cabdef30dea763a3569aa2", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "f99268325ac8f66ffd6c4964faab9e70fbf721234ab2ad238c00f9530b8cdd55"}, "propcheck": {:hex, :propcheck, "1.2.0", "e2b84f2f1a4c46b6b2aa22a0f6ddf97696f99d4a5c8f71d45f6519741e727eca", [:mix], [{:proper, "~> 1.3", [hex: :proper, repo: "hexpm", optional: false]}], "hexpm", "0f4fb2393fa5321ba7f23a8feabc861ac825e62d9e1db4aa35e39cace4c5c08d"}, "proper": {:hex, :proper, "1.3.0", "c1acd51c51da17a2fe91d7a6fc6a0c25a6a9849d8dc77093533109d1218d8457", [:make, :mix, :rebar3], [], "hexpm", "4aa192fccddd03fdbe50fef620be9d4d2f92635b54f55fb83aec185994403cbc"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, + "table_rex": {:hex, :table_rex, "3.0.0", "5189b71b3b92ed461358f40f7b7b630dc37716bf6c8ab3e934b2bc63a99028bd", [:mix], [], "hexpm", "582776d24cbe6a4d30a39a7f02035b1bc979b6cd64923d7234dd2f0ad21a18c7"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"}, "telemetry_poller": {:hex, :telemetry_poller, "0.4.1", "50d03d976a3b8ab4898d9e873852e688840df47685a13af90af40e1ba43a758b", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c5bacbbcd62c1fe4e4517485bd64312622e9b83683273dcf2627ff224d7d485b"},