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
|
Deployed at
|
{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"""
"""
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)}%
"""
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