Skip to content

Instantly share code, notes, and snippets.

@dkuku
Last active January 11, 2021 22:09
Show Gist options
  • Save dkuku/b4c4320c8d245022f72e8fc2bc77cea4 to your computer and use it in GitHub Desktop.
Save dkuku/b4c4320c8d245022f72e8fc2bc77cea4 to your computer and use it in GitHub Desktop.
live dashboard os processes
defmodule Phoenix.LiveDashboard.OsProcesses do
@moduledoc false
use Phoenix.LiveDashboard.PageBuilder
# module requires:
# {:table_parser, "~> 0.1.1"},
# {:number, "~> 1.0.3"},
@ps_cmd "ps"
@stat_map %{
"D" => "uninterruptible sleep (IO)",
"I" => "Idle kernel thread",
"R" => "running or runnable",
"S" => "interruptible sleep (waiting)",
"T" => "stopped, by a job control signal",
"t" => "stopped by debugger",
"W" => "paging",
"X" => "dead",
"Z" => "zombie process",
"<" => "high-priority",
"N" => "low-priority",
"L" => "has pages locked into memory",
"s" => "is a session leader",
"l" => "is multi-threaded",
"+" => "foreground process group."
}
@impl true
def menu_link(_, _) do
{:ok, "OS Processes"}
end
@impl true
def render_page(assigns) do
table(
columns: columns(),
id: :os_processes,
row_attrs: &row_attrs/1,
row_fetcher: &fetch_processes/2,
rows_name: "processes",
title: "OS processes",
sort_by: :"%cpu"
)
end
def processes_callback(ps_params, limit, search, sort_by, sort_dir) do
@ps_cmd
|> System.cmd(ps_params)
|> elem(0)
|> TableParser.parse_table()
|> Enum.sort_by(&sort_value(&1, sort_by), sort_dir)
|> filter_rows(search)
|> Enum.take(limit)
end
def fetch_processes(params, node) do
%{limit: limit, search: search, sort_by: sort_by, sort_dir: sort_dir} = params
ps_params =
columns()
|> Enum.map(&Map.get(&1, :ps, Map.get(&1, :field)))
|> Enum.join(",")
data =
:rpc.call(node, __MODULE__, :processes_callback, [["-eo", ps_params], limit, search, sort_by, sort_dir])
{data, length(data)}
end
defp filter_rows(rows, nil), do: rows
defp filter_rows(rows, search) do
Enum.filter(rows, fn row -> include_row?(row, search) end)
end
defp include_row?(nil, search), do: false
defp include_row?("", search), do: false
defp include_row?(row, search) do
row |> Map.values() |> Enum.join() =~ search
end
defp sort_value(map, key) do
map[key] |> Float.parse() |> elem(0)
end
def decode_stat(stat) do
{:safe,
String.graphemes(stat)
|> Enum.map(fn char -> Map.get(@stat_map, char) end)
|> Enum.join("<br>")}
end
def columns() do
[
%{
field: :pid,
sortable: :asc
},
%{
field: :user
},
%{
field: :"%cpu",
ps: :pcpu,
sortable: :desc,
format: &Kernel.<>(&1, "%")
},
%{
field: :"%mem",
ps: :pmem,
sortable: :desc,
format: &Kernel.<>(&1, "%")
},
%{
field: :vsz,
sortable: :asc,
format: &humanize_kilobyte_number/1
},
%{
field: :rss,
sortable: :asc,
format: &humanize_kilobyte_number/1
},
%{
field: :stat,
format: &decode_stat/1
},
%{
field: :command,
ps: :comm
}
]
end
defp row_attrs(table) do
[
{"phx-page-loading", true}
]
end
defp humanize_number(num_str) do
num_str
|> String.to_integer()
|> Number.SI.number_to_si( unit: "B")
end
defp humanize_kilobyte_number(num_str) do
num_str
|> String.to_integer()
|> Kernel.*(1024)
|> Number.SI.number_to_si( unit: "B")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment