LiveDashboard Plugin for Timeless Metrics
"I found it ironic that the first thing you do to time series data is squash the timestamp. That's how the name Timeless was born." --Mark Cotner
Telemetry reporter and LiveDashboard page plugin for TimelessMetrics.
Phoenix LiveDashboard ships real-time metrics that reset on every page load. TimelessMetricsDashboard bridges the gap: a telemetry reporter captures events into TimelessMetrics, and the dashboard page gives you persistent historical charts, alert visibility, backup controls, and compression stats.
Drop it in and your LiveDashboard gets real trending for free.
Installation
Quick Start (Igniter)
mix igniter.install timeless_metrics_dashboardThis automatically:
-
Adds
{TimelessMetricsDashboard, data_dir: "priv/timeless_metrics"}to your supervision tree -
Adds
import TimelessMetricsDashboard.Routerto your router -
Adds
timeless_metrics_dashboard "/dashboard"to your browser scope -
Updates your
.formatter.exs
Manual Setup
Add to your mix.exs:
def deps do
[
{:timeless_metrics, "~> 6.0"},
{:timeless_metrics_dashboard, "~> 0.4"}
]
end1. Supervision Tree
Add to your application's supervision tree. This starts TimelessMetrics and the telemetry reporter:
# application.ex
children = [
{TimelessMetricsDashboard, data_dir: "priv/timeless_metrics"}
]Or for more control over which metrics are captured:
children = [
{TimelessMetricsDashboard,
data_dir: "priv/timeless_metrics",
metrics:
TimelessMetricsDashboard.DefaultMetrics.vm_metrics() ++
TimelessMetricsDashboard.DefaultMetrics.phoenix_metrics() ++
TimelessMetricsDashboard.DefaultMetrics.ecto_metrics("my_app.repo") ++
TimelessMetricsDashboard.DefaultMetrics.live_view_metrics()}
]2. Router
# router.ex
import TimelessMetricsDashboard.Router
scope "/" do
pipe_through :browser
timeless_metrics_dashboard "/dashboard"
endThe router macro sets up LiveDashboard with the metrics history callback, the Timeless page, and the backup download plug.
3. Reporter Only (no Phoenix)
The reporter works without Phoenix. Any application that uses :telemetry can use it:
children = [
{TimelessMetrics, name: :metrics, data_dir: "/var/lib/metrics"},
{TimelessMetricsDashboard.Reporter,
store: :metrics,
metrics: TimelessMetricsDashboard.DefaultMetrics.vm_metrics()}
]Dashboard Tabs
Overview
Store statistics at a glance: series count, total points, compression ratio, storage size, and rollup tier breakdown.
Metrics
Browse all metrics in the store (both telemetry-captured and directly written), select a time range, and view SVG charts with automatic bucketing. Metric metadata (type, unit, description) is displayed when available.
All metrics written to the TimelessMetrics store appear here, whether they came through the reporter or were written directly via TimelessMetrics.write/4.
Alerts
Lists all configured alert rules with their current state (ok/pending/firing). Includes inline documentation with examples for creating alerts via the TimelessMetrics API:
TimelessMetrics.create_alert(:timeless_metrics,
name: "high_memory",
metric: "telemetry.vm.memory.total",
condition: :above,
threshold: 512_000_000,
duration: 60
)
# With webhook notification (ntfy.sh, Slack, etc.)
TimelessMetrics.create_alert(:timeless_metrics,
name: "high_latency",
metric: "telemetry.phoenix.endpoint.stop.duration",
condition: :above,
threshold: 500,
duration: 120,
aggregate: :avg,
webhook_url: "https://ntfy.sh/my-alerts"
)Storage
Database path, size, and retention settings. Create and download backups, flush buffered data to disk.
Child Spec Options
| Option | Default | Description |
|---|---|---|
:name | :timeless_metrics | TimelessMetrics store name |
:data_dir | "priv/timeless_metrics" | Data directory |
:metrics | DefaultMetrics.metrics() |
List of Telemetry.Metrics structs |
:reporter | [] |
Extra opts forwarded to Reporter (:flush_interval, :prefix) |
Reporter Options
| Option | Default | Description |
|---|---|---|
:store | required | TimelessMetrics store name (atom) |
:metrics | [] |
List of Telemetry.Metrics structs |
:flush_interval | 10_000 | Milliseconds between batch flushes |
:prefix | "telemetry" | Metric name prefix |
:name | TimelessMetricsDashboard.Reporter | GenServer name |
Page Options
| Option | Default | Description |
|---|---|---|
:store | required | TimelessMetrics store name (atom) |
:chart_width | 700 | SVG chart width in pixels |
:chart_height | 250 | SVG chart height in pixels |
:download_path | nil | Path to DownloadPlug (enables download links) |
Default Metrics
Pre-built metric definitions for common events:
TimelessMetricsDashboard.DefaultMetrics.vm_metrics/0-- Memory, run queues, system counts. Requires:telemetry_poller.TimelessMetricsDashboard.DefaultMetrics.phoenix_metrics/0-- Endpoint and router dispatch duration/count, tagged by method/route/status.TimelessMetricsDashboard.DefaultMetrics.ecto_metrics/1-- Query total_time and queue_time, tagged by source table. Pass the repo event prefix (e.g.,"my_app.repo").TimelessMetricsDashboard.DefaultMetrics.live_view_metrics/0-- Mount and handle_event duration, tagged by view/event.TimelessMetricsDashboard.DefaultMetrics.metrics/0-- All non-repo-specific metrics combined.
Mix and match with your own custom Telemetry.Metrics definitions.
Architecture
The reporter handler runs in the caller's process, not the GenServer. All hot-path operations are lock-free:
- Cache ETS (
read_concurrency: true) -- Maps{metric_name, labels}toseries_id. First miss callsTimelessMetrics.resolve_series/3, then all subsequent lookups are O(1). - Buffer ETS (
write_concurrency: true) -- Accumulates{series_id, timestamp, value}from concurrent handlers. - Periodic flush -- GenServer drains the buffer and calls
TimelessMetrics.write_batch_resolved/2.
Demo
Run the included demo to see everything in action:
cd timeless_metrics_dashboard
mix run examples/demo.exs
# Open http://localhost:4000/dashboard/timeless
VM metrics will start populating immediately via :telemetry_poller. Use TIMELESS_DATA_DIR to persist data across restarts:
TIMELESS_DATA_DIR=~/.timeless_demo mix run examples/demo.exsLicense
MIT