defmodule Phoenix.LiveDashboard.Helpers do @moduledoc false alias Phoenix.LiveDashboard.{PageBuilder, SystemInfo} use Phoenix.Component alias SystemInfo.{ProcessDetails, PortDetails} @format_limit 100 @doc """ Formats any value. """ def format_value(%ProcessDetails{} = info, live_dashboard_path) do %{pid: pid, name_or_initial_call: name_or_initial_call} = info text = if name_or_initial_call do "#{inspect(pid)} (#{name_or_initial_call})" else inspect(pid) end assigns = %{ to: live_dashboard_path.(node(pid), info: PageBuilder.encode_pid(pid)), text: text } ~H|<.link patch={@to}><%= @text %>| end def format_value(%PortDetails{} = info, live_dashboard_path) do %{port: port, description: description} = info assigns = %{ to: live_dashboard_path.(node(port), info: PageBuilder.encode_port(port)), text: "#{inspect(port)} (#{description})" } ~H|<.link patch={@to}><%= @text %>| end # Not used in `phoenix_live_dashboard` code, but available for custom pages def format_value(port, live_dashboard_path) when is_port(port) do assigns = %{ to: live_dashboard_path.(node(port), info: PageBuilder.encode_port(port)), text: inspect(port) } ~H|<.link patch={@to}><%= @text %>| end # Not used in `phoenix_live_dashboard` code, but available for custom pages def format_value(pid, live_dashboard_path) when is_pid(pid) do assigns = %{ to: live_dashboard_path.(node(pid), info: PageBuilder.encode_pid(pid)), text: inspect(pid) } ~H|<.link patch={@to}><%= @text %>| end def format_value([_ | _] = list, live_dashboard_path) do {entries, left_over} = Enum.split(list, @format_limit) entries |> Enum.map(&format_value(&1, live_dashboard_path)) |> Kernel.++(if left_over == [], do: [], else: ["..."]) |> Enum.intersperse({:safe, "
"}) end def format_value(other, _socket), do: inspect(other, pretty: true, limit: @format_limit) @doc """ Formats initial calls. Same as `format_call` but takes into account Supervisor's special format. """ def format_initial_call({:supervisor, mod, arity}), do: format_call({mod, :init, arity}) def format_initial_call(call), do: format_call(call) @doc """ Formats MFAs. """ def format_call(:undefined), do: "undefined" def format_call({m, f, a}), do: Exception.format_mfa(m, f, a) @doc """ Formats the stacktrace. """ def format_stacktrace(stacktrace) do stacktrace |> Exception.format_stacktrace() |> String.split("\n") |> Enum.map(&String.replace_prefix(&1, " ", "")) |> Enum.join("\n") end @format_path_regex ~r/^(?((.+?\/){3})).*(?(\/.*){3})$/ @doc """ Formats large paths by removing intermediate parts. """ def format_path(path) do path_string = path |> to_string() |> String.replace_prefix("\"", "") |> String.replace_suffix("\"", "") case Regex.named_captures(@format_path_regex, path_string) do %{"beginning" => beginning, "ending" => ending} -> "#{beginning}...#{ending}" _ -> path_string end end @doc """ Formats uptime. """ def format_uptime(uptime) do {d, {h, m, _s}} = :calendar.seconds_to_daystime(div(uptime, 1000)) cond do d > 0 -> "#{d}d#{h}h#{m}m" h > 0 -> "#{h}h#{m}m" true -> "#{m}m" end end @doc """ Formats percent. """ def format_percent(percent) when is_float(percent), do: "#{Float.round(percent, 1)}%" def format_percent(nil), do: "0%" def format_percent(percent), do: "#{percent}%" @doc """ Formats words as bytes. """ def format_words(words) when is_integer(words) do format_bytes(words * :erlang.system_info(:wordsize)) end @doc """ Formats bytes. """ def format_bytes(bytes) when is_integer(bytes) do cond do bytes >= memory_unit(:TB) -> format_bytes(bytes, :TB) bytes >= memory_unit(:GB) -> format_bytes(bytes, :GB) bytes >= memory_unit(:MB) -> format_bytes(bytes, :MB) bytes >= memory_unit(:KB) -> format_bytes(bytes, :KB) true -> format_bytes(bytes, :B) end end defp format_bytes(bytes, :B) when is_integer(bytes), do: "#{bytes} B" defp format_bytes(bytes, unit) when is_integer(bytes) do value = bytes / memory_unit(unit) "#{:erlang.float_to_binary(value, decimals: 1)} #{unit}" end defp memory_unit(:TB), do: 1024 * 1024 * 1024 * 1024 defp memory_unit(:GB), do: 1024 * 1024 * 1024 defp memory_unit(:MB), do: 1024 * 1024 defp memory_unit(:KB), do: 1024 @doc """ Computes the percentage between `value` and `total`. """ def percentage(value, total, rounds \\ 1) def percentage(_value, 0, _rounds), do: 0 def percentage(nil, _total, _rounds), do: 0 def percentage(value, total, rounds), do: Float.round(value / total * 100, rounds) @doc """ All connected nodes (including the current node). """ def nodes(), do: [node()] ++ Node.list(:connected) end