defmodule Integrations.Postgres.Display do
@moduledoc """
Dashboard panel for `Integrations.Postgres` (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.Postgres` starts, the same as if they were still one module.
"""
use CodeNameRaven.Display, category: :database, size_hint: :full
@default_port 5432
@default_database "postgres"
@impl true
def display_name, do: "Postgres"
@impl true
def compatible_monitors, do: [Integrations.Postgres]
@impl true
def render(assigns) do
window_seconds = assigns.config.display_window_seconds
cutoff = DateTime.add(DateTime.utc_now(), -window_seconds, :second)
samples = CodeNameRaven.Runtime.recent_samples(assigns.config.id, limit: 120)
samples = Enum.filter(samples, &(DateTime.compare(&1.collected_at, cutoff) != :lt))
local_id = CodeNameRaven.Runtime.instance_id()
result = assigns[:result]
role = result && result.role_metrics
db = result && result.db_metrics
bgw = result && result.bgwriter_metrics
assigns =
Map.merge(assigns, %{
samples: samples,
local_id: local_id,
result: result,
role: role,
db: db,
bgw: bgw
})
~H"""
<%!-- Header --%>
<%= @config.name || @config.params[:host] || "Postgres" %>
<%= subtitle(@config.params) %>
<.status_chip status={@status} />
<.role_badge :if={@result} result={@result} />
<%!-- Row 1: Vital signs --%>
<.stat label="Latency" value={"#{@result && @result.latency_ms || "–"} ms"} />
<.stat label="Connections" value={conn_label(@role)} />
<.stat label="Idle in Tx" value={"#{(@role && @role.idle_in_transaction) || "–"}"} />
<.stat label="Lock Waiters" value={"#{(@role && @role.waiting_on_lock) || "–"}"} />
<%!-- Error --%>
<%= @last_error %>
<%!-- Row 2: Performance charts --%>
Tuple Throughput (fetched / sec)
<%!-- Row 3: Operational stats --%>
<.stat label="DB Size" value={format_bytes(@db && @db.db_size_bytes)} />
<.stat label="XID Age" value={format_xid_age(@db && @db.xid_wraparound_age)} />
<.stat label="Checkpoint Req/s" value={format_rate(@bgw && @bgw.checkpoints_req_rate)} />
<.stat label="Buffers Backend/s" value={format_rate(@bgw && @bgw.buffers_backend_rate)} />
<%!-- Row 4: Legacy latency chart --%>
<%!-- Footer --%>
Checked <%= CodeNameRaven.Timezone.format_datetime(@last_checked_at, __MODULE__) %>
"""
end
# ---------------------------------------------------------------------------
# Private components
# ---------------------------------------------------------------------------
defp status_chip(%{status: :up} = assigns) do
~H"""
Up
"""
end
defp status_chip(%{status: :degraded} = assigns) do
~H"""
Degraded
"""
end
defp status_chip(%{status: :down} = assigns) do
~H"""
Down
"""
end
defp status_chip(assigns) do
~H"""
Unknown
"""
end
defp role_badge(%{result: %{is_replica: false}} = assigns) do
~H"""
PRIMARY
"""
end
defp role_badge(%{result: %{is_replica: true}} = assigns) do
~H"""
REPLICA
"""
end
defp role_badge(assigns), do: ~H""
defp stat(assigns) do
~H"""
<%= @label %>
<%= @value %>
"""
end
# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------
defp subtitle(params) do
host = params[:host] || params["host"] || "unknown"
port = params[:port] || params["port"] || @default_port
db = params[:database] || params["database"] || @default_database
"#{host}:#{port}/#{db}"
end
defp conn_label(nil), do: "–"
defp conn_label(%{active_connections: a, max_connections: m}), do: "#{a}/#{m}"
defp conn_label(_), do: "–"
defp format_bytes(nil), do: "–"
defp format_bytes(b) when b >= 1_073_741_824, do: "#{Float.round(b / 1_073_741_824, 1)} GB"
defp format_bytes(b) when b >= 1_048_576, do: "#{Float.round(b / 1_048_576, 1)} MB"
defp format_bytes(b) when b >= 1_024, do: "#{Float.round(b / 1_024, 1)} KB"
defp format_bytes(b), do: "#{b} B"
defp format_xid_age(nil), do: "–"
defp format_xid_age(age) when age >= 1_000_000_000, do: "#{Float.round(age / 1.0e9, 2)}B"
defp format_xid_age(age) when age >= 1_000_000, do: "#{Float.round(age / 1.0e6, 1)}M"
defp format_xid_age(age), do: "#{age}"
defp format_rate(nil), do: "–"
defp format_rate(r) when is_float(r), do: "#{Float.round(r, 2)}/s"
defp format_rate(r), do: "#{r}/s"
end