Time-series and event analytics for Elixir, stored in SQLite.
One file. No server. Full SQL.
Summary
Functions
Closes an open database, terminating its writer and read pool.
Creates a continuous aggregate over a metric (spec §8).
Synchronously commits any buffered samples. Rarely needed by callers (batching is automatic); useful in tests and before shutdown.
Opens (or creates) a Barograph database at path.
Queries a metric, optionally bucketed and aggregated (spec §9.2 level 1).
Forces an immediate refresh of all continuous aggregates. Normally
handled by the refresher on each aggregate's :refresh_every
interval; useful in tests and after bulk imports.
Runs raw SQL against the database (spec §9.2 level 3).
Returns the database's time unit (:second, :millisecond, or :microsecond).
Writes a single sample, timestamped now in the database's time unit.
Writes a single sample with an explicit timestamp (integer epoch in the database's time unit).
Writes a batch of samples. Each entry is {metric, labels, value}
or {metric, labels, value, ts}.
Types
@type db() :: {:via, Registry, {Barograph.Registry, {:database, String.t()}}}
A handle to an open Barograph database.
A :via tuple registered in Barograph.Registry, usable anywhere a
GenServer name is accepted.
Functions
@spec close(db()) :: :ok
Closes an open database, terminating its writer and read pool.
The file itself is untouched.
Creates a continuous aggregate over a metric (spec §8).
Barograph.create_continuous_aggregate(db, "cpu_1h",
from: "cpu_usage",
bucket: {1, :hour},
refresh_lag: {5, :minute},
refresh_every: {1, :minute}
)Creates the rollup table bg_agg_<name> storing partial aggregate
state (count/sum, never avg) and registers it for periodic
watermark-bounded refresh. Query it with plain SQL via sql/3.
@spec flush(db()) :: :ok
Synchronously commits any buffered samples. Rarely needed by callers (batching is automatic); useful in tests and before shutdown.
Opens (or creates) a Barograph database at path.
If the file does not exist, it is created and initialised with the Barograph schema. If it exists and was written by Barograph, its metadata is checked and the database is opened. Anything else is rejected.
Options
:time_unit-:second(default),:millisecond, or:microsecond. Fixed at creation; must match when reopening an existing database.
Examples
{:ok, db} = Barograph.open("/var/data/metrics.bg")
Queries a metric, optionally bucketed and aggregated (spec §9.2 level 1).
Barograph.query(db, "engine_temp",
labels: %{forklift: "FL-07"},
from: ~U[2026-07-01 00:00:00Z],
to: ~U[2026-07-19 00:00:00Z],
bucket: {1, :hour},
agg: :avg
)With :bucket (and :agg, default :avg), returns
{:ok, [%{bucket:, value:}, ...]}. Without :bucket, returns raw
samples as %{ts, value} maps. See Barograph.Query.run/3 for all
options.
@spec refresh_aggregates(db()) :: :ok
Forces an immediate refresh of all continuous aggregates. Normally
handled by the refresher on each aggregate's :refresh_every
interval; useful in tests and after bulk imports.
@spec sql(db(), String.t(), [term()]) :: {:ok, [%{required(String.t()) => term()}]} | {:error, term()}
Runs raw SQL against the database (spec §9.2 level 3).
Always available, never second-class: every design decision in
Barograph keeps the file queryable with plain SQL. Positional
parameters (?1, ?2, …) are supported. Returns rows as maps of
column name to value.
{:ok, rows} = Barograph.sql(db, "SELECT * FROM bg_series")
Returns the database's time unit (:second, :millisecond, or :microsecond).
Writes a single sample, timestamped now in the database's time unit.
Barograph.write(db, "engine_temp", %{forklift: "FL-07"}, 94.2)Returns {:error, :overloaded} if the writer's buffer is full
(spec §7.2); the caller decides whether to drop or retry.
Writes a single sample with an explicit timestamp (integer epoch in the database's time unit).
Writes are idempotent: a duplicate (series, timestamp) pair
replaces the previous value (spec §7.5).
Writes a batch of samples. Each entry is {metric, labels, value}
or {metric, labels, value, ts}.