defmodule VikWeb.DashboardLive do @moduledoc false use VikWeb, :live_view alias Vik.Repo alias Vik.Store alias Vik.Shard alias Vik.PubSub @memory_usage_sections [ {:atom, "Atoms", "bg-green-500/85"}, {:binary, "Binary", "bg-blue-500/85"}, {:code, "Code", "bg-purple-500/85"}, {:ets, "ETS", "bg-yellow-500/85"}, {:process, "Processes", "bg-orange-500/85"}, {:other, "Other", "bg-gray-700/85"} ] import Ecto.Query on_mount {VikWeb.SystemHandler, :realtime} @impl true def mount(_params, _session, socket) do PubSub.subscribe("vik:dashboard") {:ok, assign(socket, :shards, load_shards())} end @impl true def handle_info({:status, _}, socket) do {:noreply, assign(socket, :shards, load_shards())} end @impl true def handle_info({:new, _}, socket) do {:noreply, assign(socket, :shards, load_shards())} end @impl true def handle_info(_, socket) do {:noreply, socket} end defp load_shards do query = from s in Shard, order_by: [desc: s.updated_at] for %Shard{} = shard <- Repo.all(query), into: %{} do PubSub.subscribe(shard.slug) {shard.slug, {shard, Store.status(shard)}} end end @impl true def render(assigns) do ~H"""

System information

<.card title="Erlang" value={@versions.erlang} class="bg-red-600/5 text-red-600" /> <.card title="Elixir" value={@versions.elixir} class="bg-purple-600/5 text-purple-600" /> <.card title="Phoenix" value={@versions.phoenix} class="bg-brand/5 text-brand" /> <.card title="Uptime" value={format_uptime(@usage.uptime)} /> <.card title="Network in" value={@usage.io |> elem(0) |> format_bytes()} /> <.card title="Network out" value={@usage.io |> elem(1) |> format_bytes()} /> <.card title="Memory" value={format_bytes(@usage.memory.total)} />

{extract_flags(@info.banner)} [{@info.architecture}]

Shards

Title Status

{shard.title} ({shard.slug})

System limits

<.usage :for={type <- [:atoms, :ports, :processes]} title={Phoenix.Naming.humanize(type)} current={@usage[type]} limit={@limits[type]} />

Memory

{name} {format_bytes(value)}

""" end attr :title, :string, required: true attr :value, :string, required: true attr :rest, :global defp card(assigns) do ~H"""

{@title}

{@value}

""" end attr :title, :integer, required: true attr :current, :integer, required: true attr :limit, :integer, required: true defp usage(assigns) do ~H"""

{@title}

({@current} / {@limit})
{format_percentage(@current, @limit)}%
{format_percentage(@current, @limit)}%
""" end defp calculate_memory_usage(memory_usage) do for {key, name, color} <- @memory_usage_sections, value = memory_usage[key] do {name, value, color} end end defp format_percentage(value, total) do Float.round(value / total * 100, 1) end def extract_flags(banner) do ["Erlang/OTP", _version, flags] = banner |> to_string() |> String.split(" ", parts: 3) flags end 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 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 def dot_color(:stale), do: "bg-amber-400/10 text-amber-400" def dot_color(:up), do: "bg-green-400/10 text-green-400" def dot_color(:down), do: "bg-red-400/10 text-red-400" end