Packages

Dragonfly in-memory store monitor, with its dashboard panel bundled in the same package as a separate module (Integrations.Dragonfly.Display) — one install, both halves; a release without raven_web simply runs the monitor headless.

Current section

Files

Jump to
raven_integration_dragonfly lib integrations dragonfly display.ex
Raw

lib/integrations/dragonfly/display.ex

defmodule Integrations.Dragonfly.Display do
@moduledoc """
Dashboard panel for `Integrations.Dragonfly` (same package).
Nested under the monitor's own namespace deliberately — the package's
top-level module name never changes, and this module's name is
guaranteed distinct from any separately-published standalone display
package. `Display.BundledDefault` auto-hooks this whenever
`Integrations.Dragonfly` starts, the same as if they were still one module.
"""
use CodeNameRaven.Display, category: :infrastructure, size_hint: :medium
@default_port 6379
@impl true
def display_name, do: "Dragonfly"
@impl true
def compatible_monitors, do: [Integrations.Dragonfly]
@impl true
def render(assigns) do
result = assigns[:result]
assigns = Map.merge(assigns, %{result: result})
~H"""
<div class="space-y-3">
<div class="flex items-start justify-between gap-2">
<div class="min-w-0">
<h2 class="text-sm font-semibold text-base-content truncate">
<%= @config.name || instance_title(@config.params) %>
</h2>
<p class="text-xs font-mono text-base-content/40 truncate">
<%= instance_title(@config.params) %>
<%= if @result && @result[:version], do: " · v#{@result.version}" %>
</p>
</div>
<CodeNameRaven.MonitorComponents.status_chip status={@status} />
</div>
<div :if={@result} class="flex gap-2 flex-wrap">
<CodeNameRaven.MonitorComponents.stat
label="Latency"
value={"#{@result.latency_ms} ms"} />
<CodeNameRaven.MonitorComponents.stat
:if={@result[:connected_clients]}
label="Clients"
value={"#{@result.connected_clients}"} />
<CodeNameRaven.MonitorComponents.stat
:if={@result[:ops_per_sec]}
label="Ops/sec"
value={"#{@result.ops_per_sec}"} />
<CodeNameRaven.MonitorComponents.stat
:if={@result[:hit_rate_pct]}
label="Hit Rate"
value={"#{@result.hit_rate_pct}%"} />
<CodeNameRaven.MonitorComponents.stat
:if={@result[:used_memory_bytes]}
label="Memory"
value={format_memory(@result)} />
</div>
<div :if={@result && (@result[:evicted_keys] || @result[:expired_keys])}
class="text-xs text-base-content/50 flex gap-4">
<span :if={@result[:evicted_keys]}>Evicted: <%= @result.evicted_keys %></span>
<span :if={@result[:expired_keys]}>Expired: <%= @result.expired_keys %></span>
</div>
<div :if={@result && @result[:replication_lag]} class="text-xs text-base-content/50">
Replication lag: <%= @result.replication_lag %> bytes
</div>
<CodeNameRaven.MonitorComponents.error_message message={@last_error} />
<CodeNameRaven.MonitorComponents.check_time checked_at={@last_checked_at} />
</div>
"""
end
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
defp instance_title(params) do
host = params[:host] || params["host"] || "?"
port = parse_int(params[:port] || params["port"], @default_port)
"#{host}:#{port}"
end
defp format_memory(%{used_memory_bytes: used, maxmemory_bytes: max})
when is_integer(max) and max > 0 do
pct = round(used / max * 100)
"#{format_bytes(used)} / #{format_bytes(max)} (#{pct}%)"
end
defp format_memory(%{used_memory_bytes: used}), do: format_bytes(used)
defp format_memory(_), do: "?"
defp format_bytes(bytes) when is_integer(bytes) do
cond do
bytes >= 1_073_741_824 -> "#{Float.round(bytes / 1_073_741_824, 1)} GB"
bytes >= 1_048_576 -> "#{Float.round(bytes / 1_048_576, 1)} MB"
bytes >= 1024 -> "#{Float.round(bytes / 1024, 1)} KB"
true -> "#{bytes} B"
end
end
defp format_bytes(_), do: "?"
defp parse_int(nil, default), do: default
defp parse_int(v, _) when is_integer(v), do: v
defp parse_int(v, default) when is_binary(v) do
case Integer.parse(v) do
{n, _} -> n
:error -> default
end
end
defp parse_int(_, default), do: default
end