Packages

MariaDB / MySQL server monitor, with its dashboard panel bundled in the same package as a separate module (Integrations.MariaDb.Display, #299) — one install, both halves; a release without raven_web simply runs the monitor headless.

Current section

Files

Jump to
raven_integration_maria_db lib integrations maria_db display.ex
Raw

lib/integrations/maria_db/display.ex

defmodule Integrations.MariaDb.Display do
@moduledoc """
Dashboard panel for `Integrations.MariaDb` (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.MariaDb` starts, the same as if they were still one module.
"""
use CodeNameRaven.Display, category: :infrastructure, size_hint: :large
@default_port 3306
@impl true
def display_name, do: "MariaDB"
@impl true
def compatible_monitors, do: [Integrations.MariaDb]
@impl true
def render(assigns) do
samples = CodeNameRaven.Runtime.recent_samples(assigns.config.id, limit: 120)
local_id = CodeNameRaven.Runtime.instance_id()
result = assigns[:result]
role = result && result[:role_metrics]
db = result && result[:db_metrics]
repl = result && result[:replication]
assigns = Map.merge(assigns, %{
samples: samples,
local_id: local_id,
result: result,
role: role,
db: db,
repl: repl,
default_conn_util_warn: 0.8
})
~H"""
<div class="space-y-4">
<%!-- Header --%>
<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 text-base-content/40 font-mono truncate">
<%= instance_title(@config.params) %>
<%= if @result && @result[:version], do: " · #{@result.version}" %>
</p>
</div>
<div class="flex flex-col items-end gap-1 shrink-0">
<.status_chip status={@status} />
<span :if={@result && @result[:is_replica]} class="badge badge-info badge-sm">replica</span>
</div>
</div>
<%!-- Vital stats --%>
<div class="flex gap-2 flex-wrap">
<.stat label="Latency" value={"#{(@result && @result.latency_ms) || "–"} ms"} />
<.stat label="Connections" value={conn_label(@role)} />
<.stat label="Queries/s" value={format_rate(@db && @db[:queries_rate])} />
<.stat label="Slow/s" value={format_rate(@db && @db[:slow_queries_rate])} />
</div>
<%!-- Connection utilisation bar --%>
<div :if={@role && @role[:connection_utilization]}>
<div class="flex justify-between text-xs text-base-content/50 mb-1">
<span>Connection utilisation</span>
<span><%= round(@role.connection_utilization * 100) %>%</span>
</div>
<div class="w-full bg-base-300 rounded-full h-1.5">
<div
class={["h-1.5 rounded-full",
if(@role.connection_utilization >= @default_conn_util_warn,
do: "bg-error", else: "bg-success")]}
style={"width: #{min(100, round(@role.connection_utilization * 100))}%"}>
</div>
</div>
</div>
<%!-- Charts --%>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<p class="text-xs text-base-content/40 mb-1">Queries / sec</p>
<CodeNameRaven.MonitorComponents.metric_chart
id={"chart-#{@config.id}-qps"}
samples={@samples}
local_id={@local_id}
metric_name="queries_rate" />
</div>
<div>
<p class="text-xs text-base-content/40 mb-1">Buffer Pool Hit Ratio %</p>
<CodeNameRaven.MonitorComponents.metric_chart
id={"chart-#{@config.id}-bph"}
samples={@samples}
local_id={@local_id}
metric_name="buffer_pool_hit_ratio"
y_max={100.0} />
</div>
<div>
<p class="text-xs text-base-content/40 mb-1">Latency</p>
<CodeNameRaven.MonitorComponents.line_chart
id={"chart-#{@config.id}-latency"}
samples={@samples}
local_id={@local_id} />
</div>
<div :if={@repl && @repl[:lag_seconds]}>
<p class="text-xs text-base-content/40 mb-1">Replication lag (s)</p>
<CodeNameRaven.MonitorComponents.metric_chart
id={"chart-#{@config.id}-repl"}
samples={@samples}
local_id={@local_id}
metric_name="replication_lag_seconds" />
</div>
</div>
<CodeNameRaven.MonitorComponents.error_message message={@last_error} />
<p class="text-xs text-base-content/30">
Checked <%= CodeNameRaven.Timezone.format_datetime(@last_checked_at, __MODULE__) %>
</p>
</div>
"""
end
# ---------------------------------------------------------------------------
# Private components
# ---------------------------------------------------------------------------
defp status_chip(%{status: :up} = assigns) do
~H"""
<span class="badge badge-success gap-1 px-3 py-3 text-xs font-semibold">
<span class="w-1.5 h-1.5 rounded-full bg-current"></span> Up
</span>
"""
end
defp status_chip(%{status: :degraded} = assigns) do
~H"""
<span class="badge badge-warning gap-1 px-3 py-3 text-xs font-semibold">
<span class="w-1.5 h-1.5 rounded-full bg-current"></span> Degraded
</span>
"""
end
defp status_chip(%{status: :down} = assigns) do
~H"""
<span class="badge badge-error gap-1 px-3 py-3 text-xs font-semibold">
<span class="w-1.5 h-1.5 rounded-full bg-current"></span> Down
</span>
"""
end
defp status_chip(assigns) do
~H"""
<span class="badge badge-neutral gap-1 px-3 py-3 text-xs font-semibold">
<span class="w-1.5 h-1.5 rounded-full bg-current"></span> Unknown
</span>
"""
end
defp stat(assigns) do
~H"""
<div class="bg-base-200 rounded px-3 py-2 text-center min-w-[70px]">
<p class="text-xs text-base-content/50"><%= @label %></p>
<p class="text-sm font-semibold text-base-content"><%= @value %></p>
</div>
"""
end
# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------
defp instance_title(params) do
host = params[:host] || params["host"] || "?"
port = parse_int(params[:port] || params["port"], @default_port)
db = params[:database] || params["database"]
if db && db != "", do: "#{host}:#{port}/#{db}", else: "#{host}:#{port}"
end
defp conn_label(%{threads_connected: c, max_connections: m}), do: "#{c}/#{m}"
defp conn_label(nil), do: "–"
defp conn_label(_), do: "–"
defp format_rate(nil), do: "–"
defp format_rate(r), do: "#{r}"
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