README.md

<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">Embedded Time Series Database for Elixir</h3>

<p align="center">
  <a href="https://hex.pm/packages/timeless_metrics"><img src="https://img.shields.io/hexpm/v/timeless_metrics.svg" alt="Hex.pm"></a>
  <a href="https://hexdocs.pm/timeless_metrics"><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.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

TimelessMetrics is an embedded time-series database for Elixir with a Rust-native hot path, built-in HTTP ingest/query APIs, retention, rollups, alerting, scraping, charts, and Prometheus/VictoriaMetrics compatibility.

It runs:
- as a library inside your Elixir application
- as a local service with the included HTTP API
- in memory-only mode for tests and constrained environments

## Current Architecture

The default engine is the Rust engine.

Hot-path responsibilities live in the Rust NIF:
- labeled writes and batched writes
- raw and aggregate range queries
- series index and label filtering
- chunk persistence and recovery

Elixir still owns the surrounding product surface:
- HTTP API
- background ingest workers
- alerts, annotations, metadata, and scrape target management
- rollups, retention orchestration, charts, dashboard, and PromQL handling

If you need the detailed design, start with [docs/architecture.md](docs/architecture.md).

## Documentation

- [Getting Started](docs/getting_started.md)
- [Configuration Reference](docs/configuration.md)
- [Architecture](docs/architecture.md)
- [API Reference](docs/API.md)
- [Alerting](docs/alerting.md)
- [Scraping](docs/scraping.md)
- [Annotations](docs/annotations.md)
- [Forecasting & Anomaly Detection](docs/forecasting.md)
- [Charts & Embedding](docs/charts.md)
- [Grafana Integration](docs/grafana.md)
- [Operations](docs/operations.md)
- [Capacity Planning](docs/capacity_planning.md)
- [Scaling](docs/scaling_options.md)
- [Benchmarks](bench/README.md)

## Highlights

- Rust-native storage engine enabled by default
- Embedded Elixir API plus optional HTTP API
- Prometheus text ingest, VictoriaMetrics JSON-line ingest, and Influx line protocol ingest
- Prometheus-compatible query endpoints for Grafana
- Fast batched writes and compact on-disk chunks
- Built-in dashboard, SVG charts, annotations, forecasting, anomaly detection, and alerts
- Scraping subsystem for pulling Prometheus targets into the local store
- Memory-only mode for ephemeral deployments and tests

## Quick Start

Add to `mix.exs`:

```elixir
{:timeless_metrics, "~> 6.0"}
```

Add to your supervision tree:

```elixir
children = [
  {TimelessMetrics, name: :metrics, data_dir: "/var/lib/metrics"},
  {TimelessMetrics.HTTP, store: :metrics, port: 8428}
]
```

Write and query:

```elixir
TimelessMetrics.write(:metrics, "cpu_usage", %{"host" => "web-1"}, 73.2)

{:ok, points} =
  TimelessMetrics.query(:metrics, "cpu_usage", %{"host" => "web-1"},
    from: System.os_time(:second) - 3600,
    to: System.os_time(:second)
  )
```

Memory-only mode:

```elixir
children = [
  {TimelessMetrics, name: :metrics, mode: :memory},
  {TimelessMetrics.HTTP, store: :metrics, port: 8428}
]
```

## Elixir API

Writes:

```elixir
TimelessMetrics.write(:metrics, "cpu_usage", %{"host" => "web-1"}, 73.2)

TimelessMetrics.write_batch(:metrics, [
  {"cpu_usage", %{"host" => "web-1"}, 73.2},
  {"mem_usage", %{"host" => "web-1"}, 4096.0}
])
```

Queries:

```elixir
{:ok, points} =
  TimelessMetrics.query(:metrics, "cpu_usage", %{"host" => "web-1"},
    from: System.os_time(:second) - 3600
  )

{:ok, series} =
  TimelessMetrics.query_multi(:metrics, "cpu_usage", %{"host" => "web-1"},
    from: System.os_time(:second) - 3600
  )

{:ok, buckets} =
  TimelessMetrics.query_aggregate(:metrics, "cpu_usage", %{"host" => "web-1"},
    from: System.os_time(:second) - 3600,
    bucket: {60, :seconds},
    aggregate: :avg
  )
```

Discovery and operations:

```elixir
TimelessMetrics.list_metrics(:metrics)
TimelessMetrics.list_series(:metrics, "cpu_usage")
TimelessMetrics.label_values(:metrics, "cpu_usage", "host")
TimelessMetrics.info(:metrics)
TimelessMetrics.flush(:metrics)
TimelessMetrics.backup(:metrics, "/tmp/metrics-backup")
```

## HTTP API

Core endpoints:

| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/api/v1/import` | VictoriaMetrics JSON-line ingest |
| `POST` | `/api/v1/import/prometheus` | Prometheus text ingest |
| `POST` | `/write` | Influx line protocol ingest |
| `GET` | `/api/v1/query` | Latest-value query |
| `GET` | `/api/v1/query_range` | Native range query |
| `GET` | `/api/v1/export` | Multi-series export |
| `GET` | `/prometheus/api/v1/query` | Prometheus instant query |
| `GET` | `/prometheus/api/v1/query_range` | Prometheus range query |
| `GET` | `/prometheus/api/v1/labels` | Prometheus label names |
| `GET` | `/prometheus/api/v1/series` | Prometheus series listing |
| `GET` | `/chart` | SVG chart |
| `GET` | `/health` | Lightweight health/status |
| `GET` | `/health/detailed` | More expensive store diagnostics |

Example ingest:

```bash
curl -X POST http://localhost:8428/api/v1/import/prometheus \
  -H "Content-Type: text/plain" \
  --data-binary '
cpu_usage{host="web-1"} 73.2
cpu_usage{host="web-2"} 61.8
'
```

Example range query:

```bash
curl 'http://localhost:8428/api/v1/query_range?metric=cpu_usage&host=web-1&from=1700000000&to=1700003600&step=60'
```

Example Prometheus-compatible query:

```bash
curl 'http://localhost:8428/prometheus/api/v1/query_range?query=cpu_usage{host="web-1"}&start=1700000000&end=1700003600&step=60'
```

## Benchmarks

The maintained benchmark set lives under [bench/](bench/README.md):
- embedded API throughput: [bench/write_bench.exs](bench/write_bench.exs)
- HTTP concurrency: [bench/http_concurrency.exs](bench/http_concurrency.exs)
- realistic workload ramp: [bench/realistic_workload.exs](bench/realistic_workload.exs)
- TSBS harness: [bench/tsbs_bench.exs](bench/tsbs_bench.exs)
- VictoriaMetrics comparison: [bench/vs_victoriametrics.exs](bench/vs_victoriametrics.exs)

## Notes

- The legacy Elixir engine still exists behind explicit `engine:` selection for compatibility and migration work, but the default path is Rust.
- The rust build may emit the upstream `rustler::resource!` `non_local_definitions` warning. That warning is currently expected.