Prom Poller
Deps
Mix.install([
{:vega_lite, "~> 0.1.3"},
{:kino, "~> 0.5.0"},
{:tesla, "~> 1.4"},
{:jason, "~> 1.3"},
# {:prometheus_parser, "~> 0.1.6"},
{:prometheus_parser,
git: "https://github.com/Logflare/turnio-prometheus-parser", branch: "master"}
])
Input
project_ref = Kino.Input.text("Supabase Project Ref")
bearer = Kino.Input.text("Supabase Project Service Role Key")
Define Prometheus Endpoint Request
defmodule Prom do
def do_request(project_ref, bearer) when is_binary(bearer) and is_binary(project_ref) do
middleware = [
{Tesla.Middleware.BaseUrl, "https://#{project_ref}.supabase.co"},
{Tesla.Middleware.Headers, [{"authorization", "Bearer " <> bearer}, {"apiKey", bearer}]}
]
{:ok, response} =
middleware
|> Tesla.client()
|> Tesla.get("/admin/v1/privileged/project-metrics")
String.split(response.body, "\n")
|> Enum.map(&PrometheusParser.parse(&1))
|> Enum.reject(fn
{:error, _y} -> true
{:ok, _y} -> false
end)
|> Enum.map(fn {_x, y} -> y end)
end
end
Poll the Prometheus Endpoint
defmodule Poller do
use GenServer
def start_link(args) when is_list(args) do
GenServer.start_link(__MODULE__, args)
end
def init(args) do
defaults = %{project_ref: "", bearer: ""}
opts = Enum.into(args, defaults)
stack = %{data: []} |> Map.merge(opts)
if opts.project_ref == "" || opts.bearer == "",
do: raise(ArgumentError, message: "`project_ref` and `bearer` required")
poll(0)
{:ok, stack}
end
def get_all_metrics(pid) when is_pid(pid) do
GenServer.call(pid, :get_all_metrics)
end
def get_random_metric(pid) when is_pid(pid) do
GenServer.call(pid, :get_rand_metric)
end
def get_metric(pid, label) when is_pid(pid) and is_binary(label) do
GenServer.call(pid, {:get_metric, label})
end
def get_metric(pid, label, {"", ""}) when is_pid(pid) and is_binary(label) do
GenServer.call(pid, {:get_metric, label})
end
def get_metric(pid, label, tag) when is_pid(pid) and is_binary(label) and is_tuple(tag) do
GenServer.call(pid, {:get_metric, label, tag})
end
def handle_call(:get_all_metrics, _from, %{data: data} = stack) do
{:reply, {:ok, data}, stack}
end
def handle_call({:get_metric, label}, _from, %{data: data} = stack) do
metric =
data
|> Enum.filter(&(&1.line_type == "ENTRY"))
|> Enum.find(%{}, fn %PrometheusParser.Line{label: l} ->
l == label
end)
response =
case metric do
%PrometheusParser.Line{} -> {:ok, metric}
%{} -> {:error, :metric_not_found}
end
{:reply, response, stack}
end
def handle_call({:get_metric, label, tag}, _from, %{data: data} = stack) do
metric =
Enum.find(data, %{}, fn %PrometheusParser.Line{label: l, pairs: tags} ->
label_match = l == label
tag_match = Enum.any?(tags, fn x -> x == tag end)
if label_match and tag_match, do: true, else: false
end)
response =
case metric do
%PrometheusParser.Line{} -> {:ok, metric}
%{} -> {:error, :metric_not_found}
end
{:reply, response, stack}
end
def handle_call(:get_rand_metric, _from, %{data: data} = stack) do
metric = Enum.random(data)
{:reply, {:ok, metric}, stack}
end
def handle_info(:poll, %{project_ref: ref, bearer: bearer} = stack) do
metrics = Prom.do_request(ref, bearer)
stack = stack |> Map.put(:data, metrics)
poll()
{:noreply, stack}
end
defp poll(interval \\ 5_000) do
Process.send_after(self(), :poll, interval)
end
end
{:ok, pid} =
Poller.start_link(
project_ref: Kino.Input.read(project_ref),
bearer: Kino.Input.read(bearer)
)
Browse Metrics
filter = Kino.Input.text("Filter Metrics by Label")
{:ok, data} = Poller.get_all_metrics(pid)
data =
case Kino.Input.read(filter) do
"" -> data
filter -> Enum.filter(data, &String.contains?(inspect(&1.label), filter))
end
Kino.DataTable.new(data)
Graph a Metric
Interesting Metrics
-
node_scrape_collector_duration_seconds
-
replication_realtime_lag_bytes
-
node_cpu_seconds_total
-
go_memstats_frees_total
-
gotrue_up
-
promhttp_metric_handler_requests_in_flight
-
auth_users_user_count
-
supabase_usage_metrics_user_queries_total
metric_label = Kino.Input.text("Metric Label")
tag_key = Kino.Input.text("Metric Tag Key")
tag_value = Kino.Input.text("Metric Tag Value")
label = Kino.Input.read(metric_label)
tag_key = Kino.Input.read(tag_key)
tag_value = Kino.Input.read(tag_value)
Poller.get_metric(pid, label, {tag_key, tag_value})
alias VegaLite, as: Vl
label = Kino.Input.read(metric_label)
widget =
Vl.new(width: 750, height: 400)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "x",
time_unit: :monthdatehoursminutesseconds,
type: :temporal,
title: "Time"
)
|> Vl.encode_field(:y, "y", type: :quantitative, title: label <> " #{tag_key}:#{tag_value}")
|> Kino.VegaLite.new()
|> Kino.render()
Kino.animate(1_000, 0, fn i ->
{:ok, metric} = Poller.get_metric(pid, label)
now = DateTime.utc_now() |> DateTime.to_unix(:millisecond)
point = %{x: now, y: metric.value}
chart = Kino.VegaLite.push(widget, point, window: 1_000)
{:cont, chart, i + 1}
end)