# `TimelessMetrics`
[🔗](https://github.com/awksedgreep/timeless_metrics/blob/main/lib/timeless_metrics.ex#L1)

Embedded time series storage for Elixir.

TimelessMetrics runs as an embedded store inside your application and defaults to
a Rust-backed engine for raw point ingestion and queries. Administrative data such
as annotations, alerts, scrape targets, and rollup metadata stays on the Elixir side.

## Quick Start

    # Add to your supervision tree
    children = [
      {TimelessMetrics, name: :metrics, data_dir: "/tmp/metrics"}
    ]

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

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

# `api_result`

```elixir
@type api_result(result) :: {:ok, result} | {:error, term()}
```

# `command_result`

```elixir
@type command_result() :: :ok | :noop | {:error, term()}
```

# `labels`

```elixir
@type labels() :: %{optional(String.t()) =&gt; String.t()}
```

# `metric_name`

```elixir
@type metric_name() :: String.t()
```

# `point`

```elixir
@type point() :: {timestamp(), value()}
```

# `store`

```elixir
@type store() :: atom()
```

# `timestamp`

```elixir
@type timestamp() :: integer()
```

# `value`

```elixir
@type value() :: number() | String.t()
```

# `write_entry`

```elixir
@type write_entry() ::
  {metric_name(), labels(), value()}
  | {metric_name(), labels(), value(), timestamp()}
```

# `acknowledge_alert`

```elixir
@spec acknowledge_alert(store(), integer()) :: command_result()
```

Acknowledge an alert history entry by ID.

# `alert_history`

```elixir
@spec alert_history(store(), keyword()) :: api_result([map()])
```

List recent alert history entries.

Options: `:limit`, `:rule_id`, `:acknowledged` (true/false/nil).

# `annotate`

```elixir
@spec annotate(store(), timestamp(), String.t(), keyword()) :: api_result(integer())
```

Create an annotation (event marker).

Returns `{:ok, id}`.

# `annotations`

```elixir
@spec annotations(store(), timestamp(), timestamp(), keyword()) :: api_result([map()])
```

Query annotations within a time range.

Returns `{:ok, [%{id: n, timestamp: ts, title: "...", description: "...", tags: [...]}]}`.

# `backup`

```elixir
@spec backup(store(), Path.t()) :: api_result(map())
```

Create a consistent online backup.

# `child_spec`

```elixir
@spec child_spec(keyword()) :: Supervisor.child_spec()
```

Start a TimelessMetrics instance as part of a supervision tree.

# `clear_alert_history`

```elixir
@spec clear_alert_history(store(), keyword()) :: command_result()
```

Clear alert history entries.

Options: `:acknowledged_only` (default true), `:before` (timestamp cutoff).

# `create_alert`

```elixir
@spec create_alert(store(), keyword()) :: api_result(integer())
```

Create an alert rule.

Returns `{:ok, rule_id}`.

# `delete_alert`

```elixir
@spec delete_alert(store(), integer()) :: command_result()
```

Delete an alert rule.

# `delete_annotation`

```elixir
@spec delete_annotation(store(), integer()) :: command_result()
```

Delete an annotation by ID.

# `detect_anomalies`

```elixir
@spec detect_anomalies(store(), metric_name(), labels(), keyword()) ::
  api_result([map()])
```

Detect anomalies in matching series.

Returns `{:ok, [%{labels: map, analysis: [%{timestamp, value, expected, score, anomaly}]}, ...]}`.

# `enforce_retention`

```elixir
@spec enforce_retention(store()) :: command_result()
```

Force retention enforcement now.

# `evaluate_alerts`

```elixir
@spec evaluate_alerts(store()) :: command_result()
```

Evaluate all alert rules against current data.

# `flush`

```elixir
@spec flush(store()) :: command_result()
```

Force flush all buffered data to disk.

# `forecast`

```elixir
@spec forecast(store(), metric_name(), labels(), keyword()) :: api_result([map()])
```

Forecast future values for matching series.

Returns `{:ok, [%{labels: map, data: [{ts, val}], forecast: [{ts, val}]}, ...]}`.

# `get_metadata`

```elixir
@spec get_metadata(store(), metric_name()) :: api_result(map() | nil)
```

Get metadata for a metric.

Returns `{:ok, %{type: :gauge, unit: "%", description: "..."}}` or `{:ok, nil}`.

# `info`

```elixir
@spec info(store()) :: map() | {:error, term()}
```

Get store info and statistics.

# `label_values`

```elixir
@spec label_values(store(), metric_name(), String.t()) :: api_result([String.t()])
```

List distinct values for a specific label key across all series of a metric.

Returns `{:ok, ["web-1", "web-2", ...]}`.

# `latest`

```elixir
@spec latest(store(), metric_name(), labels()) :: api_result(point() | nil)
```

Get the latest value for a series.

Returns `{:ok, {timestamp, value}}` or `{:ok, nil}`.

# `latest_multi`

```elixir
@spec latest_multi(store(), metric_name(), labels()) :: api_result([map()])
```

Get the latest value for ALL series matching a metric name and label filter.

Returns `{:ok, [%{labels: %{...}, timestamp: ts, value: val}, ...]}`.

# `latest_text`

```elixir
@spec latest_text(store(), metric_name(), labels()) :: api_result(point() | nil)
```

Get the latest text value for a series.

# `list_alerts`

```elixir
@spec list_alerts(store()) :: api_result([map()])
```

List all alert rules with current state.

# `list_metrics`

```elixir
@spec list_metrics(store()) :: api_result([metric_name()])
```

List all distinct metric names in the store.

Returns `{:ok, ["cpu_usage", "mem_usage", ...]}`.

# `list_series`

```elixir
@spec list_series(store(), metric_name()) :: api_result([map()])
```

List all series for a given metric name.

Returns `{:ok, [%{labels: %{"host" => "web-1"}, ...}, ...]}`.

# `merge_now`

```elixir
@spec merge_now(store()) :: :noop
```

No-op for sharded engine (no per-series blocks to merge).

# `query`

```elixir
@spec query(store(), metric_name(), labels(), keyword()) :: api_result([point()])
```

Query raw time series points for a single series (exact label match).

## Options

  * `:from` - Start timestamp (unix seconds, default: 0)
  * `:to` - End timestamp (unix seconds, default: now)

Returns `{:ok, [{timestamp, value}, ...]}`.

# `query_aggregate`

```elixir
@spec query_aggregate(store(), metric_name(), labels(), keyword()) ::
  api_result([point()])
```

Query with time-bucket aggregation for a single series (exact label match).

## Options

  * `:from` - Start timestamp
  * `:to` - End timestamp
  * `:bucket` - Bucket size: `:minute`, `:hour`, `:day`, or `{n, :seconds}`
  * `:aggregate` - Aggregate function: `:avg`, `:min`, `:max`, `:sum`, `:count`, `:last`, `:first`

Returns `{:ok, [{bucket_timestamp, aggregate_value}, ...]}`.

# `query_aggregate_grouped`

```elixir
@spec query_aggregate_grouped(store(), metric_name(), labels(), keyword()) ::
  api_result([map()])
```

Query with cross-series aggregation, grouping results by a label key.

Returns `{:ok, [%{group: %{"hostname" => "host_0"}, data: [{ts, val}]}, ...]}`.

# `query_aggregate_grouped_metrics`

```elixir
@spec query_aggregate_grouped_metrics(store(), [metric_name()], labels(), keyword()) ::
  api_result([map()])
```

Query with cross-series aggregation across multiple metrics, with group-by.

Returns `{:ok, [%{group: %{...}, data: [{ts, val}]}, ...]}`.

# `query_aggregate_multi`

```elixir
@spec query_aggregate_multi(store(), metric_name(), labels(), keyword()) ::
  api_result([map()])
```

Query with aggregation across multiple series matching a label filter.

Returns `{:ok, [%{labels: %{...}, data: [{bucket_ts, agg_value}, ...]}, ...]}`.

# `query_aggregate_multi_filtered`

```elixir
@spec query_aggregate_multi_filtered(store(), metric_name(), labels(), keyword()) ::
  api_result([map()])
```

Query with aggregation and threshold filtering.

Returns `{:ok, [%{labels: %{...}, data: [{ts, val}]}, ...]}`.

# `query_aggregate_multi_metrics`

```elixir
@spec query_aggregate_multi_metrics(store(), [metric_name()], labels(), keyword()) ::
  api_result([map()])
```

Query with aggregation across multiple metric names matching a label filter.

Returns `{:ok, [%{metric: name, labels: %{...}, data: [{ts, val}, ...]}, ...]}`.

# `query_daily`

```elixir
@spec query_daily(store(), metric_name(), labels(), timestamp(), timestamp()) ::
  api_result([map()])
```

Query pre-computed daily rollup data.

Returns `{:ok, [%{bucket: ts, avg: v, min: v, max: v, count: n, sum: v, last: v}, ...]}`.

# `query_multi`

```elixir
@spec query_multi(store(), metric_name(), labels(), keyword()) :: api_result([map()])
```

Query raw points across multiple series matching a label filter.

Returns `{:ok, [%{labels: %{...}, points: [{ts, val}, ...]}, ...]}`.
Point order is stable within each series. As with the legacy/Rust parallel
query paths, the outer series list is unordered; sort it in the caller when
presentation order matters.

# `query_multi_metrics`

```elixir
@spec query_multi_metrics(store(), [metric_name()], labels(), keyword()) ::
  api_result([map()])
```

Query raw points for several metric names in one engine operation.

Returns `{:ok, [%{metric: name, labels: labels, points: points}, ...]}`.

# `query_text`

```elixir
@spec query_text(store(), metric_name(), labels(), keyword()) :: api_result([point()])
```

Query text time series points for a single series.

# `query_text_multi`

```elixir
@spec query_text_multi(store(), metric_name(), labels(), keyword()) ::
  api_result([map()])
```

Query text points across multiple series matching a label filter.

# `register_metric`

```elixir
@spec register_metric(store(), metric_name(), atom(), keyword()) ::
  TimelessMetrics.DB.query_result()
```

Register metadata for a metric (type, unit, description).

# `resolve_series`

```elixir
@spec resolve_series(store(), metric_name(), labels()) :: api_result(integer())
```

Resolve a series to an integer ID for use with `write_resolved/4`.

Cache the result for repeated writes to the same series.

# `rollup`

```elixir
@spec rollup(store()) :: command_result()
```

Force a daily rollup run.

# `top_n`

```elixir
@spec top_n([item], non_neg_integer(), (item -&gt; term())) :: [item] when item: term()
```

Sort results by a value function and take top N.

# `update_alert`

```elixir
@spec update_alert(store(), integer(), keyword()) :: command_result()
```

Update an alert rule (partial update). Returns `:ok`.

# `write`

```elixir
@spec write(store(), metric_name(), labels(), value(), keyword()) :: command_result()
```

Write a single metric point.

## Parameters

  * `store` - The store name (atom)
  * `metric_name` - String metric name (e.g., "cpu_usage")
  * `labels` - Map of string labels (e.g., %{"host" => "web-1"})
  * `value` - Numeric value (float or integer)
  * `opts` - Optional keyword list:
    * `:timestamp` - Unix timestamp in seconds (default: now)

# `write_batch`

```elixir
@spec write_batch(store(), [write_entry()]) :: command_result()
```

Write a batch of metric points.

Each entry is a tuple of `{metric_name, labels, value}` or
`{metric_name, labels, value, timestamp}`.

# `write_each`

```elixir
@spec write_each(store(), [write_entry()]) :: command_result()
```

Write entries directly, one per unique series. Same as write_batch/2
for the sharded engine.

# `write_resolved`

```elixir
@spec write_resolved(store(), integer(), value(), keyword()) :: command_result()
```

Write directly using a pre-resolved series ID. Zero lookup cost.

    {:ok, sid} = TimelessMetrics.resolve_series(:metrics, "cpu_usage", %{"host" => "web-1"})
    TimelessMetrics.write_resolved(:metrics, sid, 73.2, timestamp: ts)

# `write_text`

```elixir
@spec write_text(store(), metric_name(), labels(), String.t(), keyword()) ::
  command_result()
```

Write a single text metric point.

# `write_text_batch`

```elixir
@spec write_text_batch(store(), [write_entry()]) :: command_result()
```

Write a batch of text metric points.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
