<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/logo-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="docs/logo-light.svg">
<img src="docs/logo-light.svg" width="300" alt="Timeless">
</picture>
</p>
<h3 align="center">LiveDashboard Plugin for Timeless Metrics</h3>
<p align="center">
<a href="https://hex.pm/packages/timeless_metrics_dashboard"><img src="https://img.shields.io/hexpm/v/timeless_metrics_dashboard.svg" alt="Hex.pm"></a>
<a href="https://hexdocs.pm/timeless_metrics_dashboard"><img src="https://img.shields.io/badge/docs-hexdocs-blue.svg" alt="Docs"></a>
<a href="LICENSE"><img src="https://img.shields.io/hexpm/l/timeless_metrics_dashboard.svg" alt="License"></a>
</p>
---
> "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](https://github.com/phoenixframework/phoenix_live_dashboard) page plugin for [TimelessMetrics](https://github.com/awksedgreep/timeless_metrics).
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)
```bash
mix igniter.install timeless_metrics_dashboard
```
This automatically:
1. Adds `{TimelessMetricsDashboard, data_dir: "priv/timeless_metrics"}` to your supervision tree
2. Adds `import TimelessMetricsDashboard.Router` to your router
3. Adds `timeless_metrics_dashboard "/dashboard"` to your browser scope
4. Updates your `.formatter.exs`
### Manual Setup
Add to your `mix.exs`:
```elixir
def deps do
[
{:timeless_metrics, "~> 6.0"},
{:timeless_metrics_dashboard, "~> 0.4"}
]
end
```
#### 1. Supervision Tree
Add to your application's supervision tree. This starts TimelessMetrics and the telemetry reporter:
```elixir
# application.ex
children = [
{TimelessMetricsDashboard, data_dir: "priv/timeless_metrics"}
]
```
Or for more control over which metrics are captured:
```elixir
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
```elixir
# 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.
#### 3. Reporter Only (no Phoenix)
The reporter works without Phoenix. Any application that uses `:telemetry` can use it:
```elixir
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:
```elixir
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}` to `series_id`. First miss calls `TimelessMetrics.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:
```bash
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:
```bash
TIMELESS_DATA_DIR=~/.timeless_demo mix run examples/demo.exs
```
## License
MIT