Trend stores float64 samples and unsigned counters behind a small byte-store interface. It is meant for embedded or service-local time-series data where simple writes, mergeable state, iterator reads, and compact storage are enough.
It keeps recent raw points, can compact older points into buckets, and serializes state with a compact binary format compressed with S2.
- Samples: Last-write-wins float64 values.
- Counters: Grow-only unsigned counter deltas.
- Reads:
ValuesandRangereturn Go iterators. - Storage: Pluggable stores registered by URI scheme.
Use When
- You need simple local time-series storage inside a Go service.
- You want separate APIs for samples and counters.
- You want compact serialized state without running a metrics database.
Not For
- High-cardinality metrics ingestion at Prometheus/TSDB scale.
- Distributed query engines, retention policies, or alerting.
- Interoperable on-disk formats.
package main
import (
"context"
"fmt"
"time"
"github.com/kelindar/trend"
_ "github.com/kelindar/trend/storage/buntdb"
)
func main() {
ctx := context.Background()
db, err := trend.Open("buntdb:///:memory:")
if err != nil {
panic(err)
}
defer db.Close()
now := time.Now()
_ = db.Samples("cpu").Set(ctx, now, 0.42)
_ = db.Counters("requests").Add(ctx, now, 1)
values, _ := db.Samples("cpu").Values(ctx, now.Add(-time.Minute), now)
for at, value := range values {
fmt.Println(at, value)
}
}Open(uri string, opts ...Option): Open a registered storage adapter.New(store Store, opts ...Option): Use an existing store implementation.Samples(key).Set(ctx, at, value): Store a float64 sample.Samples(key).Values(ctx, from, to): Iterate raw sample values.Samples(key).Range(ctx, from, to, span, agg): Iterate bucketed sample aggregates.Counters(key).Add(ctx, at, delta): Store an unsigned counter delta.Counters(key).Values(ctx, from, to): Iterate counter values.Counters(key).Range(ctx, from, to, span, agg): Iterate bucketed counter aggregates.Compact(ctx): Compact old points using the configured window.
Stores implement a byte-oriented update interface:
type Store interface {
Load(context.Context, string) ([]byte, error)
Update(context.Context, string, func([]byte) ([]byte, error)) error
Delete(context.Context, string) error
Close() error
}Adapters register by URI scheme. Import the adapter package for registration:
import (
_ "github.com/kelindar/trend/storage/buntdb"
_ "github.com/kelindar/trend/storage/redis"
_ "github.com/kelindar/trend/storage/sqlite"
)name time/op ops/s allocs/op
samples/append 327.9 us 3.0K 10
samples/range 177.9 us 5.6K 4
samples/values 95.0 us 10.5K 4
counters/append 286.4 us 3.5K 9
counters/range 100.7 us 9.9K 4
counters/values 78.6 us 12.7K 4
Numbers are from local 10K element DB-backed benchmarks on an Intel i7-13700K.
Trend is MIT licensed and maintained by @kelindar.
PRs and issues welcome.
