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_dashboard
This 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.2"},
{:timeless_metrics_dashboard, "~> 0.4"}
]
end
1. 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"
end
The router macro sets up LiveDashboard with the metrics history callback, the Timeless page, and the backup download plug.
The history callback reads from TimelessMetrics by default. To back the
history charts with a different owner-compatible store (for example the
release Stack adapter for Rust/libSQL historical reads), call
TimelessMetricsDashboard.metrics_history/3 yourself with :query_module
set to the adapter module; it must implement query_aggregate_multi/4, and no
fallback occurs if it returns an error.
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()}
]
The page, component, and download modules are compiled only when their Phoenix or Plug dependencies are available. A reporter-only application does not need to add those optional dependencies.
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",
webhook_format: "ntfy"
)
The alert form supports generic JSON webhooks and ntfy topic URLs and shows the configured format in the rule list.
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 |
:max_buffer_size |
100_000 |
Maximum queued points before new points are dropped and counted |
:batch_size |
5_000 |
Maximum points written in each store batch |
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 |
:metric_page_size |
200 |
Maximum metric buttons rendered per page |
:max_chart_series |
20 |
Maximum series rendered on one chart |
:download_path |
nil |
Path to DownloadPlug (enables download links) |
The router macro also accepts :live_session_name for multiple dashboard
mounts and :download_auth_token to require a bearer token for backup
downloads. Archive downloads are serialized per store by default and use the
web server's fixed-size HTTP chunks instead of loading the archive into BEAM
memory.
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 as existing atoms (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:
- Prepared metric config -- Output names are built once when the reporter starts.
- Bounded buffer ETS (
write_concurrency: true) -- Accepts concurrent points up to:max_buffer_size; excess points are dropped without blocking emitters and exposed byReporter.stats/1. - Periodic flush -- The GenServer writes chunks with
TimelessMetrics.write_batch/2and deletes only successfully accepted entries. Concurrent inserts remain queued, and failed writes are retried.
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.exs
License
MIT