Packages

A Fair Source multi-agent runtime with deterministic agent scoring and replayable run history.

Current section

Files

Jump to
syntropy lib syntropy_web telemetry.ex
Raw

lib/syntropy_web/telemetry.ex

defmodule SyntropyWeb.Telemetry do
@moduledoc """
Telemetry supervisor: periodic VM measurements plus the in-process
Prometheus reporter backing `GET /metrics`.
Metrics cover the BEAM (memory, run queues), Phoenix endpoint latency,
Ecto query timing (only emitted when persistence is enabled), and the
custom runtime events defined in `Syntropy.Telemetry` — task runs, LLM
requests and token usage, webhook deliveries, and event recorder batch
flushes.
"""
use Supervisor
import Telemetry.Metrics
@reporter :syntropy_prometheus_metrics
@duration_buckets_ms [
10,
50,
100,
250,
500,
1_000,
2_500,
5_000,
10_000,
30_000,
60_000,
120_000
]
@spec start_link(keyword()) :: Supervisor.on_start()
def start_link(opts) do
Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl true
def init(_opts) do
children = [
# telemetry_poller also emits its default VM measurements
# ([:vm, :memory], [:vm, :total_run_queue_lengths], ...) on this period.
{:telemetry_poller, measurements: [], period: 10_000, name: :syntropy_telemetry_poller},
{TelemetryMetricsPrometheus.Core, metrics: metrics(), name: @reporter}
]
Supervisor.init(children, strategy: :one_for_one)
end
@doc """
Renders the current metric values in Prometheus text exposition format.
"""
@spec scrape() :: String.t()
def scrape do
TelemetryMetricsPrometheus.Core.scrape(@reporter)
end
@spec metrics() :: [Telemetry.Metrics.t()]
def metrics do
[
# BEAM
last_value("vm.memory.total", unit: :byte),
last_value("vm.total_run_queue_lengths.total"),
last_value("vm.total_run_queue_lengths.cpu"),
last_value("vm.total_run_queue_lengths.io"),
# Phoenix
distribution("phoenix.endpoint.stop.duration",
unit: {:native, :millisecond},
reporter_options: [buckets: @duration_buckets_ms]
),
# Ecto (no-op unless persistence is enabled)
distribution("syntropy.repo.query.total_time",
unit: {:native, :millisecond},
reporter_options: [buckets: @duration_buckets_ms]
),
# Task runs. The histogram carries run count (by status/strategy),
# total duration, and latency buckets.
distribution("syntropy.task_run.stop.duration_ms",
tags: [:status, :strategy],
reporter_options: [buckets: @duration_buckets_ms],
description: "Terminal run duration by status and strategy"
),
sum("syntropy.task_run.stop.agent_count",
tags: [:status],
description: "Total agents dispatched across terminal runs"
),
# LLM requests
distribution("syntropy.llm_request.stop.duration_ms",
tags: [:provider, :status],
reporter_options: [buckets: @duration_buckets_ms],
description: "LLM completion call latency by provider and outcome"
),
sum("syntropy.llm_request.tokens.input_tokens", tags: [:provider]),
sum("syntropy.llm_request.tokens.output_tokens", tags: [:provider]),
sum("syntropy.llm_request.tokens.total_tokens", tags: [:provider]),
# Webhook deliveries
counter("syntropy.webhook_delivery.stop.count",
tags: [:status],
description: "Webhook delivery count by final status"
),
sum("syntropy.webhook_delivery.stop.attempts",
tags: [:status],
description: "Total delivery attempts (including retries)"
),
# Event recorder. The histogram carries flush count and total flush time.
sum("syntropy.event_recorder.flush.batch_size",
description: "Total events flushed to Postgres"
),
distribution("syntropy.event_recorder.flush.duration_ms",
reporter_options: [buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1_000]]
)
]
end
end