Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filtered process list #411

Open
wants to merge 49 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
ff627d2
Enable filtering of process PIDs
bokner Feb 17, 2023
63bc1bf
Fix process count
bokner Feb 22, 2023
3e92e97
Use the process naming if it's created by process filter
bokner Feb 27, 2023
9f0c928
Customize process_info page
bokner Mar 1, 2023
a8fb339
Let the callback decide what to do with the filter name
bokner Mar 2, 2023
6c97999
Behaviour for process filter
bokner Mar 2, 2023
e628820
Merge branch 'main' into filtered_process_list
bokner Mar 2, 2023
94265e3
Fixes
bokner Mar 2, 2023
a13b1a9
Refactoring
bokner Mar 3, 2023
ed5d42f
process_filter -> page_filter
bokner Mar 3, 2023
d1e4834
Refactoring, fixes
bokner Mar 6, 2023
9db7de8
The content returned by the info_content/2 can be either of Phoenix.L…
bokner Mar 6, 2023
1be224a
Update lib/phoenix/live_dashboard/page_filter.ex
bokner Mar 7, 2023
fe0f424
Update lib/phoenix/live_dashboard/components/table_component.ex
bokner Mar 7, 2023
2c3a7be
Update lib/phoenix/live_dashboard/pages/processes_page.ex
bokner Mar 7, 2023
2b1f7c4
Update lib/phoenix/live_dashboard/components/table_component.ex
bokner Mar 7, 2023
2f12ee8
Fix formatting and indentation
bokner Mar 7, 2023
58dc67f
Add description for Phoenix.LiveDashboard.PageFilter
bokner Mar 8, 2023
3ac44c1
Get rid of rendering the modal info page (might go to another PR)
bokner Mar 9, 2023
547ca85
Retrieve filter list from the remote node
bokner Mar 9, 2023
71798ec
Put it all together (wip, cleanup to follow)
bokner Mar 11, 2023
e4a5222
Various fixes
bokner Mar 12, 2023
8e7c2cd
Update lib/phoenix/live_dashboard/components/table_component.ex
bokner Mar 24, 2023
df782d6
Various changes to address the review
bokner Mar 24, 2023
7eec63d
Have row_fetcher return 5-tuple
bokner Mar 25, 2023
7242996
Add documentation for the case row_fetcher returns page filter data
bokner Mar 25, 2023
bbdbb62
Unit tests for process filter
bokner Mar 25, 2023
97b5838
Unit tests: add filter to process paths for compability with table co…
bokner Mar 25, 2023
5f0c700
Formatting
bokner Mar 27, 2023
d146580
Fix process filter module in test helper
bokner Mar 27, 2023
30d5ee6
Use :if for conditional filter form
bokner Apr 16, 2023
6c9a3e1
Fix active_filter form
bokner Apr 16, 2023
a74954b
Rename process_filter to filter
bokner Apr 16, 2023
fe86d97
Fix misplaced @impl
bokner Apr 16, 2023
e6d9ec1
Have process filter tests for SystemInfo to run with async: false
bokner Apr 26, 2023
235a6d5
Unit tests for the filter in the process page
bokner Apr 26, 2023
e056334
do not use active_filter; instead, update filter in fetch_rows
bokner Apr 27, 2023
408a28b
Refactoring, fixes
bokner Apr 29, 2023
a3b5137
Simplify the base implementation of default_filter/0
bokner Apr 29, 2023
2076105
More refactoring
bokner Apr 29, 2023
d6e566b
Merge branch 'filtered_process_list' of github.com:bokner/phoenix_liv…
bokner Apr 29, 2023
ce63526
Use default filter if the filter is not listed
bokner Apr 29, 2023
93d1fb5
Minor
bokner Apr 29, 2023
e988f05
Do not use hardcoded filter values in test cases
bokner Apr 29, 2023
a0ce9f8
Update docs for row_fetcher
bokner Apr 29, 2023
f826536
Fix the invalid filter value handling
bokner Apr 29, 2023
fa3f109
Move filter param down the list of params
bokner Apr 29, 2023
7fe72ca
Adjust filter_list to nil, if it's empty
bokner Apr 30, 2023
e5c4b6f
Merge branch 'phoenixframework:main' into filtered_process_list
bokner May 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 45 additions & 5 deletions lib/phoenix/live_dashboard/components/table_component.ex
Expand Up @@ -10,7 +10,8 @@ defmodule Phoenix.LiveDashboard.TableComponent do
sort_by: :atom,
sort_dir: :desc | :asc,
search: binary(),
hint: binary() | nil
hint: binary() | nil,
filter: binary() | nil
bokner marked this conversation as resolved.
Show resolved Hide resolved
}

@impl true
Expand All @@ -30,6 +31,7 @@ defmodule Phoenix.LiveDashboard.TableComponent do
|> validate_required_one_sortable_column()
|> Map.put_new(:search, true)
|> Map.put_new(:limit, @limit)
|> Map.put_new(:filter, nil)
bokner marked this conversation as resolved.
Show resolved Hide resolved
|> Map.put_new(:row_attrs, [])
|> Map.put_new(:hint, nil)
|> Map.put_new(:dom_id, nil)
Expand Down Expand Up @@ -91,14 +93,23 @@ defmodule Phoenix.LiveDashboard.TableComponent do
defp fetch_rows(row_fetcher, table_params, page_node, socket)
when is_function(row_fetcher, 2) do
{rows, total} = row_fetcher.(table_params, page_node)
{rows, total, socket}
{rows, total, assign(socket, :active_filter, nil)}
bokner marked this conversation as resolved.
Show resolved Hide resolved
end

defp fetch_rows({row_fetcher, initial_state}, table_params, page_node, socket)
when is_function(row_fetcher, 3) do
state = Map.get(socket.assigns, :row_fetcher_state, initial_state)
{rows, total, state} = row_fetcher.(table_params, page_node, state)
{rows, total, assign(socket, :row_fetcher_state, state)}

{active_filter, available_filters, rows, total, state} =
with {rows, total, state} <- row_fetcher.(table_params, page_node, state) do
{nil, nil, rows, total, state}
end
bokner marked this conversation as resolved.
Show resolved Hide resolved

{rows, total,
socket
|> assign(:row_fetcher_state, state)
|> assign(:filter_list, available_filters)
|> assign(:active_filter, active_filter)}
bokner marked this conversation as resolved.
Show resolved Hide resolved
end

defp normalize_table_params(assigns) do
Expand All @@ -125,7 +136,15 @@ defmodule Phoenix.LiveDashboard.TableComponent do
search = all_params["search"]
search = if search == "", do: nil, else: search

table_params = %{sort_by: sort_by, sort_dir: sort_dir, limit: limit, search: search}
filter = all_params["filter"]

table_params = %{
sort_by: sort_by,
sort_dir: sort_dir,
limit: limit,
search: search,
filter: filter
}

assigns
|> Map.put(:table_params, table_params)
Expand Down Expand Up @@ -186,6 +205,21 @@ defmodule Phoenix.LiveDashboard.TableComponent do
</div>
</form>

<%= if @active_filter do %>
<form phx-change="select_filter" phx-target={@myself} class="form-inline">
bokner marked this conversation as resolved.
Show resolved Hide resolved
<div class="form-row align-items-center">
<div class="col-auto">Filter</div>
<div class="col-auto">
<div class="input-group input-group-sm">
<select name="filter" class="custom-select" id="filter-select">
<%= options_for_select(@filter_list, @active_filter) %>
bokner marked this conversation as resolved.
Show resolved Hide resolved
</select>
</div>
</div>
</div>
</form>
<% end %>

<div class="card tabular-card mb-4 mt-4">
<div class="card-body p-0">
<div class="dash-table-wrapper">
Expand Down Expand Up @@ -249,6 +283,12 @@ defmodule Phoenix.LiveDashboard.TableComponent do
{:noreply, push_patch(socket, to: to)}
end

def handle_event("select_filter", %{"filter" => filter}, socket) do
table_params = %{socket.assigns.table_params | filter: filter}
bokner marked this conversation as resolved.
Show resolved Hide resolved
to = PageBuilder.live_dashboard_path(socket, socket.assigns.page, table_params)
{:noreply, push_patch(socket, to: to)}
end

defp sort_link(assigns) do
if assigns.table_params.sort_by == assigns.column.field do
~H"""
Expand Down
9 changes: 8 additions & 1 deletion lib/phoenix/live_dashboard/page_builder.ex
Expand Up @@ -96,7 +96,7 @@ defmodule Phoenix.LiveDashboard.PageBuilder do

We currently support `card/1`, `fields_card/1`, `row/1`,
`shared_usage_card/1`, and `usage_card/1`;
and the live components `live_layered_graph/1`, `live_nav_bar/1`,
and the live components `live_layered_graph/1`, `live_nav_bar/1`,
and `live_table/1`.

## Helpers
Expand Down Expand Up @@ -256,6 +256,11 @@ defmodule Phoenix.LiveDashboard.PageBuilder do
In this case, the function will receive the state as third argument and must return
a tuple with the rows, the total number, and the new state for the following call:
`{(params(), node(), term() -> {list(), integer() | binary(), term()}), term()}`
Optionally for latter case: if the page decides to implement custom page filter
(please see `Phoenix.LiveDashboard.PageFilter` for details), the function must return
a tuple with the active filter, list of available filters, the rows, the total number,
and the new state for the following call:
`(params(), node(), term() -> {binary() | nil, list() | nil, list(), integer() | binary(), term()})`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should change the order of the tuple, as I said in a previous comment.

"""

attr :rows_name, :string,
Expand Down Expand Up @@ -287,6 +292,8 @@ defmodule Phoenix.LiveDashboard.PageBuilder do

attr :hint, :string, default: nil, doc: "A textual hint to show close to the title."
attr :dom_id, :string, default: nil, doc: "id attribute for the HTML the main tag."
attr :filter, :string, default: nil, doc: "To filter table rows"
bokner marked this conversation as resolved.
Show resolved Hide resolved

@spec live_table(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t()
def live_table(assigns) do
~H"""
Expand Down
73 changes: 73 additions & 0 deletions lib/phoenix/live_dashboard/page_filter.ex
@@ -0,0 +1,73 @@
defmodule Phoenix.LiveDashboard.PageFilter do
@moduledoc """
Page filter allows to customize the list of items for table components.
This could be useful for cases where there are too many items and/or
the customized list of items is desired.
For instance, you may have millions of processes per node. Then, using the standard Processes page
may present a performance problem. Besides, you may want to look at the application-specific groups of processes,
rather then going through the whole list.

Example of custom page filter implementation:

defmodule ProcessFilter.Demo do
@behaviour Phoenix.LiveDashboard.PageFilter

@impl true
def list() do
["All", "Registered", "Phoenix"]
end

@impl true
def default_filter() do
"Phoenix"
end

@impl true
def filter("Registered") do
Process.registered() |> Enum.map(fn name -> Process.whereis(name) end)
end

def filter("All") do
Process.list()
end

def filter("Phoenix") do
Process.registered() |> Enum.flat_map(fn name -> String.contains?(to_string(name), "Phoenix") &&
[Process.whereis(name)] || []
end)
end
end


To enable the filter for Processes page, add this to your config.exs:

config :phoenix_live_dashboard, :process_filter, ProcessFilter.Demo

What will happen:

- The Processes page will have Filter dropdown defined in list() function;
- The groups of processes will be displayed according to selected filter

"""

@callback list() :: [String.t()]

@callback filter(filter_name :: String.t()) :: [any()]

@callback default_filter() :: String.t()

defmacro __using__(_) do
quote do
@behaviour Phoenix.LiveDashboard.PageFilter
def default_filter() do
case list() do
nil -> nil
[] -> nil
[first | _rest] -> first
end
bokner marked this conversation as resolved.
Show resolved Hide resolved
end

defoverridable default_filter: 0
alexcastano marked this conversation as resolved.
Show resolved Hide resolved
end
end
end
8 changes: 2 additions & 6 deletions lib/phoenix/live_dashboard/pages/processes_page.ex
bokner marked this conversation as resolved.
Show resolved Hide resolved
Expand Up @@ -35,12 +35,8 @@ defmodule Phoenix.LiveDashboard.ProcessesPage do
end

defp fetch_processes(params, node, state) do
%{search: search, sort_by: sort_by, sort_dir: sort_dir, limit: limit} = params

{processes, count, state} =
SystemInfo.fetch_processes(node, search, sort_by, sort_dir, limit, state)

{processes, count, state}
%{search: search, sort_by: sort_by, sort_dir: sort_dir, limit: limit, filter: filter} = params
SystemInfo.fetch_processes(node, filter, search, sort_by, sort_dir, limit, state)
bokner marked this conversation as resolved.
Show resolved Hide resolved
end

defp row_attrs(process) do
Expand Down
37 changes: 32 additions & 5 deletions lib/phoenix/live_dashboard/system_info.ex
Expand Up @@ -44,10 +44,19 @@ defmodule Phoenix.LiveDashboard.SystemInfo do

## Fetchers

def fetch_processes(node, search, sort_by, sort_dir, limit, prev_reductions \\ nil) do
def fetch_processes(
node,
process_filter,
bokner marked this conversation as resolved.
Show resolved Hide resolved
search,
sort_by,
sort_dir,
limit,
prev_reductions \\ nil
) do
search = search && String.downcase(search)

:rpc.call(node, __MODULE__, :processes_callback, [
process_filter,
search,
sort_by,
sort_dir,
Expand Down Expand Up @@ -205,11 +214,12 @@ defmodule Phoenix.LiveDashboard.SystemInfo do
]

@doc false
def processes_callback(search, sort_by, sort_dir, limit, prev_reductions) do
def processes_callback(process_filter, search, sort_by, sort_dir, limit, prev_reductions) do
bokner marked this conversation as resolved.
Show resolved Hide resolved
multiplier = sort_dir_multipler(sort_dir)
{active_filter, available_filters, process_list} = get_process_filter_data(process_filter)

processes =
for pid <- Process.list(),
for pid <- process_list,
info = process_info(pid, prev_reductions[pid]),
show_process?(info, search) do
sorter = info[sort_by] * multiplier
Expand All @@ -218,10 +228,23 @@ defmodule Phoenix.LiveDashboard.SystemInfo do

next_state = for {_sorter, info} <- processes, into: %{}, do: {info[:pid], info[:reductions]}

count = if search, do: length(processes), else: :erlang.system_info(:process_count)
count = length(processes)
processes = processes |> Enum.sort() |> Enum.take(limit) |> Enum.map(&elem(&1, 1))

{processes, count, next_state}
{active_filter, available_filters, processes, count, next_state}
end

def get_process_filter_data(filter) do
case Application.get_env(:phoenix_live_dashboard, :process_filter) do
bokner marked this conversation as resolved.
Show resolved Hide resolved
nil ->
{nil, nil, Process.list()}
bokner marked this conversation as resolved.
Show resolved Hide resolved

filter_mod ->
available_filters = filter_mod.list()
active_filter = (filter in available_filters && filter) || filter_mod.default_filter()
alexcastano marked this conversation as resolved.
Show resolved Hide resolved
process_list = filter_mod.filter(active_filter)
{active_filter, available_filters, process_list}
end
end

defp process_info(pid, prev_reductions) do
Expand Down Expand Up @@ -722,6 +745,10 @@ defmodule Phoenix.LiveDashboard.SystemInfo do
defp pid_or_port_details(port) when is_port(port), do: to_port_details(port)
defp pid_or_port_details(reference) when is_reference(reference), do: reference

def to_process_details(detail_map) when is_map(detail_map) do
struct(ProcessDetails, detail_map)
end

def to_process_details(pid) when is_pid(pid) and node(pid) == node() do
{name, initial_call} =
case Process.info(pid, [:initial_call, :dictionary, :registered_name]) do
Expand Down
Expand Up @@ -75,6 +75,6 @@ defmodule Phoenix.LiveDashboard.ApplicationsPageTest do

defp applications_path(limit, search, sort_by, sort_dir) do
"/dashboard/applications?" <>
"limit=#{limit}&search=#{search}&sort_by=#{sort_by}&sort_dir=#{sort_dir}"
"filter=&limit=#{limit}&search=#{search}&sort_by=#{sort_by}&sort_dir=#{sort_dir}"
alexcastano marked this conversation as resolved.
Show resolved Hide resolved
end
end
2 changes: 1 addition & 1 deletion test/phoenix/live_dashboard/pages/ets_page_test.exs
Expand Up @@ -93,6 +93,6 @@ defmodule Phoenix.LiveDashboard.EtsPageTest do

defp ets_path(limit, search, sort_by, sort_dir) do
"/dashboard/ets?" <>
"limit=#{limit}&search=#{search}&sort_by=#{sort_by}&sort_dir=#{sort_dir}"
"filter=&limit=#{limit}&search=#{search}&sort_by=#{sort_by}&sort_dir=#{sort_dir}"
end
end
2 changes: 1 addition & 1 deletion test/phoenix/live_dashboard/pages/ports_page_test.exs
Expand Up @@ -98,6 +98,6 @@ defmodule Phoenix.LiveDashboard.PortsPageTest do

defp ports_path(limit, search, sort_by, sort_dir) do
"/dashboard/ports?" <>
"limit=#{limit}&search=#{search}&sort_by=#{sort_by}&sort_dir=#{sort_dir}"
"filter=&limit=#{limit}&search=#{search}&sort_by=#{sort_by}&sort_dir=#{sort_dir}"
end
end
2 changes: 1 addition & 1 deletion test/phoenix/live_dashboard/pages/processes_live_test.exs
bokner marked this conversation as resolved.
Show resolved Hide resolved
Expand Up @@ -149,6 +149,6 @@ defmodule Phoenix.LiveDashboard.ProcessesLiveTest do

defp processes_path(prefix \\ "dashboard", limit, search, sort_by, sort_dir) do
"/#{prefix}/processes?" <>
"limit=#{limit}&search=#{search}&sort_by=#{sort_by}&sort_dir=#{sort_dir}"
"filter=&limit=#{limit}&search=#{search}&sort_by=#{sort_by}&sort_dir=#{sort_dir}"
end
end
2 changes: 1 addition & 1 deletion test/phoenix/live_dashboard/pages/sockets_page_test.exs
Expand Up @@ -98,7 +98,7 @@ defmodule Phoenix.LiveDashboard.SocketsPageTest do

defp sockets_path(limit, search, sort_by, sort_dir) do
"/dashboard/sockets?" <>
"limit=#{limit}&search=#{search}&sort_by=#{sort_by}&sort_dir=#{sort_dir}"
"filter=&limit=#{limit}&search=#{search}&sort_by=#{sort_by}&sort_dir=#{sort_dir}"
end

defp open_socket() do
Expand Down