defmodule PrymnWeb.SystemInfo do use PrymnWeb, :live_component require Logger alias Phoenix.LiveView.AsyncResult @impl true def update(assigns, socket) do {:ok, socket |> assign(:ip, assigns.ip) |> assign(:sys_info, AsyncResult.loading()) |> start_async(:get_sys_info, fn -> Logger.debug("getting initial system info for #{assigns.ip}...") Prymn.Agents.get_sys_info(assigns.ip) end)} end @impl true def render(assigns) do ~H"""
<.async_result :let={sys_info} assign={@sys_info}> <:loading>
<.spinner size="sm" />

Loading system statistics...

<%= sys_info.uptime || "" %>s

Uptime

<%= Enum.count(sys_info.cpus || []) %>

CPUs

<%= calculate_cpu_usage(sys_info.cpus) %>

CPU%

<%= bytes_to_gigabytes(sys_info.mem_total_bytes - sys_info.mem_avail_bytes) %> / <%= bytes_to_gigabytes(sys_info.mem_total_bytes) %> GiB

Memory

<%= calculate_disk_used_percent(sys_info.disks) %> %

Used Disk

""" end def handle_async(:get_sys_info, {:ok, {:ok, sys_info}}, socket) do %{sys_info: sys_info_result, ip: host_address} = socket.assigns {:noreply, socket |> assign(:sys_info, AsyncResult.ok(sys_info_result, sys_info)) |> start_async(:get_sys_info, fn -> Logger.debug("getting more system info for #{host_address}...") # 10 seconds is >5 which is gun's timeout duration (which might have a race # condition if they are equal) Process.sleep(:timer.seconds(10)) Prymn.Agents.get_sys_info(host_address) end)} end def handle_async(:get_sys_info, {:ok, {:error, grpc_error}}, socket) do %{sys_info: sys_info_result} = socket.assigns {:noreply, socket |> assign(:sys_info, AsyncResult.failed(sys_info_result, grpc_error))} end def handle_async(:get_sys_info, {:exit, _reason}, socket) do {:noreply, socket} end defp calculate_cpu_usage(cpus) do (Enum.reduce(cpus, 0, fn x, acc -> x.usage + acc end) / Enum.count(cpus)) |> Float.round(2) end defp bytes_to_gigabytes(bytes) do Float.round(bytes / Integer.pow(1024, 3), 2) end defp calculate_disk_used_percent(disks) do alias PrymnProto.Prymn.SysInfoResponse.Disk {used, total} = Enum.reduce(disks, {0, 0}, fn %Disk{} = disk, {used, total} -> {used + disk.total_bytes - disk.avail_bytes, total + disk.total_bytes} end) Float.round(100 * used / total, 2) end end