metrics

package
v0.2.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package metrics decorates any r3.CRUD[T, ID] with domain-level analytics, recording configurable metrics for every CRUD operation. Metric records are themselves r3 entities, persisted via any r3.CRUD[MetricRecord, string] - the same or a different backend than the wrapped entity uses.

Ten built-in collectors cover common CRUD analytics (action counts, latency, popularity, error rates, filter analysis, field changes, lifecycle, patch size); the Collector[T, ID] interface adds custom ones. Labels come in three layers - core (auto), context, entity - and records are time-bucketed (minutely to monthly) for cheap aggregation. Async mode keeps the hot path latency-free; r3.Actor is picked up from context for attribution.

Aggregation

The Aggregator aggregates in-memory, or delegates to server-side aggregation when the store also implements AggregationPusher.

Retention and Rollup

RetentionEnforcer deletes old records by age (MaxAge) or count (MaxRecords). RollupExecutor compacts old fine-grained records into coarser time-bucket summaries, preserving label dimensions and tagging summaries with _rollup metadata labels.

Both delete one record at a time via r3.CRUD Delete (slow at scale; a future BatchDelete could optimize this). RollupExecutor compacts all old records for a type regardless of source granularity; RollupPolicy.SourceBucket is provenance metadata only - filter at the query level to target one granularity. The ticker in RetentionEnforcer.Start (and any rollup loop) first fires after one interval elapses, not immediately.

Usage

metricsStore := r3pgx.NewPgxCRUD[metrics.MetricRecord, string](db)

repo := metrics.WithMetrics[Order, int64](
    innerRepo,
    metricsStore,
    metrics.WithIDFunc[Order, int64](func(o Order) int64 { return o.ID }),
    metrics.WithCollectors[Order, int64](
        metrics.CRUDActionCollector[Order, int64](),
        metrics.PopularityCollector[Order, int64](),
        metrics.LatencyCollector[Order, int64](),
    ),
    metrics.WithLabelers[Order, int64](
        func(o Order) metrics.Labels { return metrics.Labels{"order_type": o.Type} },
    ),
    metrics.WithAsync[Order, int64](),
)

// Query metrics:
agg := metrics.NewAggregator(metricsStore)
count, _ := agg.Count(ctx, "orders", "crud.action", metrics.LastMonth(),
    metrics.WithLabel("operation", "create"))
top, _ := agg.TopN(ctx, "orders", "entity.popularity", metrics.LastWeek(), 10)

Index

Constants

View Source
const (
	// MetricEntityFieldChange tracks which fields change most often on updates.
	MetricEntityFieldChange = "entity.field_change"

	// MetricEntityLifecycle tracks the duration from creation to deletion.
	MetricEntityLifecycle = "entity.lifecycle"

	// MetricEntityPatchSize tracks how many fields are patched at once.
	MetricEntityPatchSize = "entity.patch_size"
)

Metric names for entity-level collectors.

View Source
const (
	// MetricListFilter tracks which fields users filter by in List queries.
	MetricListFilter = "crud.list.filter"

	// MetricListResultSize tracks how many results each List query returns.
	MetricListResultSize = "crud.list.result_size"

	// MetricListTotalCount tracks the total matching count from List queries (before pagination).
	MetricListTotalCount = "crud.list.total_count"
)

Metric names for list-related collectors.

View Source
const MetricCRUDAction = "crud.action"

MetricCRUDAction is the metric name for the universal CRUD action counter, incremented by 1 on every operation. The "operation" core label distinguishes create/get/list/update/patch/delete.

View Source
const MetricCRUDError = "crud.error"

MetricCRUDError is the metric name for failed operation tracking.

View Source
const MetricCRUDLatency = "crud.action.latency"

MetricCRUDLatency is the metric name for operation duration; value is milliseconds.

View Source
const MetricEntityPopularity = "entity.popularity"

MetricEntityPopularity is the metric name for entity access scoring. Incremented when an entity is accessed via Get or appears in List results.

Variables

View Source
var (
	// ErrNoMetrics is returned when querying metrics for an entity with no recorded data.
	ErrNoMetrics = errors.New("r3metrics: no metrics found")

	// ErrIDFuncRequired is returned when a collector requires IDFunc but it's not set.
	ErrIDFuncRequired = errors.New("r3metrics: IDFunc is required for entity-level metrics; use WithIDFunc option")
)

Sentinel errors for metrics operations.

Functions

func ComputeBucket

func ComputeBucket(t time.Time, size BucketSize) string

ComputeBucket truncates t to the bucket boundary and returns it as RFC3339. Truncation is delegated to the years library so boundary semantics stay consistent across the codebase.

func QueryByBucket

func QueryByBucket(recordType, metricName, bucket string) r3.Query

QueryByBucket retrieves metrics for a specific time bucket.

func QueryByEntity

func QueryByEntity(recordType, recordID string, tr TimeRange) r3.Query

QueryByEntity retrieves all metrics for a specific entity instance.

func QueryByEntityMetric

func QueryByEntityMetric(recordType, recordID, metricName string, tr TimeRange) r3.Query

QueryByEntityMetric builds a Query for a specific metric on a specific entity.

func QueryByMetric

func QueryByMetric(recordType, metricName string, tr TimeRange) r3.Query

QueryByMetric retrieves a specific metric across an entity type.

func QueryByType

func QueryByType(recordType string, tr TimeRange) r3.Query

QueryByType retrieves all metrics for an entity type in a time range, newest first.

Types

type AggregationPusher

type AggregationPusher interface {
	// PushCount returns the count of matching records. A non-nil labels filters
	// to records carrying those labels.
	PushCount(ctx context.Context, recordType, metricName string, tr TimeRange, labels Labels) (int64, error)

	// PushSum returns the sum of metric values matching the given criteria.
	PushSum(ctx context.Context, recordType, metricName string, tr TimeRange, labels Labels) (float64, error)

	// PushAvg returns the average of metric values matching the given criteria.
	PushAvg(ctx context.Context, recordType, metricName string, tr TimeRange, labels Labels) (float64, error)

	// PushTopN returns the top N entities by summed metric value.
	PushTopN(
		ctx context.Context,
		recordType, metricName string,
		tr TimeRange,
		n int,
		labels Labels,
	) ([]RankedEntity, error)

	// PushTimeSeries returns metric values grouped by time bucket.
	PushTimeSeries(
		ctx context.Context,
		recordType, metricName string,
		tr TimeRange,
		bucket BucketSize,
		labels Labels,
	) ([]BucketValue, error)

	// PushGroupBy returns metric values grouped by a label key.
	PushGroupBy(
		ctx context.Context,
		recordType, metricName string,
		tr TimeRange,
		labelKey string,
		labels Labels,
	) (map[string]float64, error)
}

AggregationPusher is an optional interface a metric store implements to push aggregation server-side instead of fetching all records and computing in Go. If a store implements it, Aggregator delegates to these methods; otherwise it falls back to in-memory aggregation, so this is pure opt-in.

type Aggregator

type Aggregator struct {
	// contains filtered or unexported fields
}

Aggregator computes metric aggregations over any r3.Querier for MetricRecord (read access only). It aggregates in-memory unless the store implements AggregationPusher, in which case it delegates server-side.

func NewAggregator

func NewAggregator(store r3.Querier[MetricRecord, string]) *Aggregator

NewAggregator creates an Aggregator over the given metric store, enabling server-side aggregation automatically if the store implements AggregationPusher.

func (*Aggregator) Avg

func (a *Aggregator) Avg(
	ctx context.Context,
	recordType, metricName string,
	tr TimeRange,
	opts ...QueryOption,
) (float64, error)

Avg returns the average metric value matching the query.

agg.Avg(ctx, "users", "crud.action.latency", LastWeek(), WithLabel("operation", "create"))

func (*Aggregator) Count

func (a *Aggregator) Count(
	ctx context.Context,
	recordType, metricName string,
	tr TimeRange,
	opts ...QueryOption,
) (int64, error)

Count returns the number of metric records matching the query. For counter metrics (value=1) this equals the sum.

agg.Count(ctx, "users", "crud.action", LastMonth(), WithLabel("operation", "create"))

func (*Aggregator) GroupBy

func (a *Aggregator) GroupBy(
	ctx context.Context,
	recordType, metricName string,
	tr TimeRange,
	labelKey string,
	opts ...QueryOption,
) (map[string]float64, error)

GroupBy returns metric values grouped by a label key.

agg.GroupBy(ctx, "orders", "crud.action", LastMonth(), "operation")
// -> {"create": 1247, "get": 8391, "list": 2100, ...}

func (*Aggregator) Sum

func (a *Aggregator) Sum(
	ctx context.Context,
	recordType, metricName string,
	tr TimeRange,
	opts ...QueryOption,
) (float64, error)

Sum returns the sum of metric values matching the query.

agg.Sum(ctx, "users", "crud.action.latency", LastWeek(), WithLabel("operation", "list"))

func (*Aggregator) TimeSeries

func (a *Aggregator) TimeSeries(
	ctx context.Context,
	recordType, metricName string,
	tr TimeRange,
	bucket BucketSize,
	opts ...QueryOption,
) ([]BucketValue, error)

TimeSeries returns metric values grouped by time bucket.

agg.TimeSeries(ctx, "users", "crud.action", Last30Days(), BucketDaily, WithLabel("operation", "create"))

func (*Aggregator) TopN

func (a *Aggregator) TopN(
	ctx context.Context,
	recordType, metricName string,
	tr TimeRange,
	n int,
	opts ...QueryOption,
) ([]RankedEntity, error)

TopN returns the N entities with the highest summed metric value.

agg.TopN(ctx, "users", "entity.popularity", LastMonth(), 10)

type BucketSize

type BucketSize string

BucketSize defines the granularity of time-based metric bucketing.

const (
	BucketMinutely BucketSize = "minutely"
	BucketHourly   BucketSize = "hourly"
	BucketDaily    BucketSize = "daily"
	BucketWeekly   BucketSize = "weekly"
	BucketMonthly  BucketSize = "monthly"
)

type BucketValue

type BucketValue struct {
	Bucket string
	Value  float64
}

BucketValue is a time bucket with its aggregated metric value.

type CRUD

type CRUD[T any, ID comparable] struct {
	// contains filtered or unexported fields
}

CRUD records metrics for every operation via configurable collectors, storing them in any r3.CRUD[MetricRecord, string]. See the package doc for details.

func WithMetrics

func WithMetrics[T any, ID comparable](
	inner r3.CRUD[T, ID],
	store r3.CRUD[MetricRecord, string],
	optFns ...Option[T, ID],
) *CRUD[T, ID]

WithMetrics wraps inner with metrics collection persisted to store.

func (*CRUD[T, ID]) Aggregate added in v0.0.3

func (m *CRUD[T, ID]) Aggregate(ctx context.Context, qarg ...r3.Query) ([]r3.AggregateRow, error)

Aggregate computes grouped aggregates and records metrics (OpAggregate, with TotalCount = number of grouped rows returned). An inner repo without aggregation support yields r3.ErrAggregateNotSupported.

func (*CRUD[T, ID]) AggregateThroughRelation added in v0.0.5

func (m *CRUD[T, ID]) AggregateThroughRelation(
	ctx context.Context, relation string, qarg ...r3.Query,
) ([]r3.AggregateRow, error)

AggregateThroughRelation aggregates related rows and records metrics (OpAggregate, with TotalCount = number of grouped rows returned). An inner repo without relation-aggregation support yields r3.ErrRelationAggregateNotSupported.

func (*CRUD[T, ID]) Count

func (m *CRUD[T, ID]) Count(ctx context.Context, qarg ...r3.Query) (int64, error)

Count returns the number of matching entities and records metrics.

func (*CRUD[T, ID]) Create

func (m *CRUD[T, ID]) Create(ctx context.Context, entity T) (T, error)

Create inserts a new entity and records metrics.

func (*CRUD[T, ID]) Delete

func (m *CRUD[T, ID]) Delete(ctx context.Context, id ID) error

Delete removes an entity and records metrics.

func (*CRUD[T, ID]) Get

func (m *CRUD[T, ID]) Get(ctx context.Context, id ID, qarg ...r3.Query) (T, error)

Get retrieves an entity by ID and records metrics.

func (*CRUD[T, ID]) Inner

func (m *CRUD[T, ID]) Inner() r3.CRUD[T, ID]

Inner returns the underlying CRUD, bypassing metrics for a specific operation.

func (*CRUD[T, ID]) List

func (m *CRUD[T, ID]) List(ctx context.Context, qarg ...r3.Query) ([]T, int64, error)

List retrieves entities and records metrics.

func (*CRUD[T, ID]) Metrics

func (m *CRUD[T, ID]) Metrics() r3.CRUD[MetricRecord, string]

Metrics returns the metric record CRUD for querying metrics directly, e.g. via the query builders or the Aggregator.

func (*CRUD[T, ID]) Patch

func (m *CRUD[T, ID]) Patch(ctx context.Context, entity T, fields r3.Fields) (T, error)

Patch performs a partial update and records metrics.

func (*CRUD[T, ID]) PatchWhere added in v0.0.4

func (m *CRUD[T, ID]) PatchWhere(
	ctx context.Context, filters r3.Filters, entity T, fields r3.Fields,
) (int64, error)

PatchWhere runs a bulk conditional update via the inner CRUD and records metrics (OpPatchWhere, with TotalCount = affected-row count). Returns r3.ErrBulkPatchNotSupported when the inner repo has no bulk-patch capability.

func (*CRUD[T, ID]) Rewrap

func (m *CRUD[T, ID]) Rewrap(inner r3.CRUD[T, ID]) r3.CRUD[T, ID]

Rewrap rebuilds this decorator around a different inner CRUD, sharing the store and options (used to re-apply metrics over a transaction-bound CRUD).

func (*CRUD[T, ID]) Unwrap

func (m *CRUD[T, ID]) Unwrap() r3.CRUD[T, ID]

Unwrap returns the wrapped CRUD so the decorator chain can be walked.

func (*CRUD[T, ID]) Update

func (m *CRUD[T, ID]) Update(ctx context.Context, entity T) (T, error)

Update modifies an existing entity and records metrics.

func (*CRUD[T, ID]) Upsert added in v0.0.4

func (m *CRUD[T, ID]) Upsert(ctx context.Context, entity T, opts ...r3.UpsertOption) (T, error)

Upsert inserts-or-updates via the inner CRUD and records metrics (OpUpsert). The pre-state is fetched (when IDFunc is set) so diff-based collectors see the old entity, mirroring Update. Returns r3.ErrUpsertNotSupported when the inner repo has no upsert capability.

type Collector

type Collector[T any, ID comparable] interface {
	Collect(ctx context.Context, opCtx OperationContext[T, ID]) []MetricEntry
}

Collector emits zero or more MetricEntry values for a completed CRUD operation. It is called after every operation; nil means record nothing. Collectors must be pure - no I/O or side effects, just compute from the OperationContext.

func CRUDActionCollector

func CRUDActionCollector[T any, ID comparable]() Collector[T, ID]

CRUDActionCollector emits one "crud.action" entry (value 1) per successful operation. The operation type rides the decorator's "operation" core label, so this collector does not add it. RecordID is set for entity-level ops and empty for type-level ones (list/count/aggregate).

func ErrorCollector

func ErrorCollector[T any, ID comparable]() Collector[T, ID]

ErrorCollector emits one "crud.error" entry (value 1) per failed operation. Unlike other collectors, it fires ONLY on error.

func FieldChangeCollector

func FieldChangeCollector[T any, ID comparable]() Collector[T, ID]

FieldChangeCollector emits one "entity.field_change" entry per changed field, labeled {"field": "<name>"}. Patch uses the Fields list; Update/Upsert detect changes via a shallow reflect comparison, so it needs the old entity (HasOld).

func LatencyCollector

func LatencyCollector[T any, ID comparable]() Collector[T, ID]

LatencyCollector emits one "crud.action.latency" entry per operation, valued as wall-clock milliseconds. Fires on both success and failure.

func LifecycleCollector

func LifecycleCollector[T any, ID comparable](createdAtFunc func(T) time.Time) Collector[T, ID]

LifecycleCollector emits one "entity.lifecycle" entry on Delete, valued as hours since creation. createdAtFunc extracts the creation timestamp; pass nil (or return a zero time) and the collector is a no-op.

func ListFilterCollector

func ListFilterCollector[T any, ID comparable]() Collector[T, ID]

ListFilterCollector emits one "crud.list.filter" entry per leaf filter, labeled {"field": "<name>", "operator": "<op>"} - answering "what do users filter by?".

func ListResultSizeCollector

func ListResultSizeCollector[T any, ID comparable]() Collector[T, ID]

ListResultSizeCollector emits one "crud.list.result_size" entry per List, valued as the number of results returned.

func ListTotalCountCollector

func ListTotalCountCollector[T any, ID comparable]() Collector[T, ID]

ListTotalCountCollector emits one "crud.list.total_count" entry per List, valued as the total matching count (before pagination) - tracks data growth.

func PatchSizeCollector

func PatchSizeCollector[T any, ID comparable]() Collector[T, ID]

PatchSizeCollector emits one "entity.patch_size" entry on Patch, valued as the number of fields patched at once.

func PopularityCollector

func PopularityCollector[T any, ID comparable]() Collector[T, ID]

PopularityCollector emits "entity.popularity" (value 1) per entity accessed: on Get one entry labeled {"source": "get"}, on List one per result labeled {"source": "list"}. Needs IDFunc set so entity IDs are available; on List, entity labelers apply per-entity, overriding the decorator's single-entity merge.

type CollectorFunc

type CollectorFunc[T any, ID comparable] func(ctx context.Context, opCtx OperationContext[T, ID]) []MetricEntry

CollectorFunc adapts a function to Collector for inline collectors:

metrics.CollectorFunc[Order, int64](func(ctx context.Context, opCtx metrics.OperationContext[Order, int64]) []metrics.MetricEntry {
    // custom logic
})

func (CollectorFunc[T, ID]) Collect

func (f CollectorFunc[T, ID]) Collect(ctx context.Context, opCtx OperationContext[T, ID]) []MetricEntry

Collect implements the Collector interface.

type ContextLabeler

type ContextLabeler func(ctx context.Context) Labels

ContextLabeler extracts dimension labels from request context, for labels that come from the HTTP layer rather than the entity.

type IDFunc

type IDFunc[T any, ID comparable] func(entity T) ID

IDFunc extracts an entity's primary key. The decorator uses it to populate RecordID and to fetch old entity state before updates/deletes.

type Labeler

type Labeler[T any] func(entity T) Labels

Labeler extracts dimension labels from an entity; composed labelers' outputs are merged into every metric record.

orderTypeLabeler := func(o Order) metrics.Labels {
    return metrics.Labels{"order_type": o.Type}
}

type Labels

type Labels map[string]string

Labels attaches arbitrary key-value dimensions to a metric record for slicing and filtering (e.g. {"order_type": "manual", "actor_id": "42"}).

func (Labels) Clone

func (l Labels) Clone() Labels

Clone returns a shallow copy of the labels map.

func (Labels) Merge

func (l Labels) Merge(other Labels) Labels

Merge combines two label sets; other wins on key conflicts.

type MetricEntry

type MetricEntry struct {
	// MetricName identifies the metric (e.g. "crud.action").
	MetricName string

	// Value is the numeric measurement.
	Value float64

	// Labels are collector-specific, merged with global/context/core labels by
	// the decorator.
	Labels Labels

	// RecordID is the stringified entity key; empty for type-level metrics.
	RecordID string
}

MetricEntry is what a Collector emits; the decorator enriches it with ID, bucket, timestamps, and merged labels before storing as a MetricRecord.

type MetricRecord

type MetricRecord struct {
	// ID is the unique identifier for this record.
	ID string `json:"id" db:"id,pk" bson:"_id"`

	// RecordType is the entity type name (e.g. "users"), derived from the struct
	// type or set via options.
	RecordType string `json:"record_type" db:"record_type" bson:"record_type"`

	// RecordID is the stringified entity key; empty for type-level metrics.
	RecordID string `json:"record_id" db:"record_id" bson:"record_id"`

	// MetricName identifies the metric (e.g. "crud.action").
	MetricName string `json:"metric_name" db:"metric_name" bson:"metric_name"`

	// Value is the numeric measurement (1 for counters, ms for latency, etc.).
	Value float64 `json:"value" db:"value" bson:"value"`

	// Labels carries arbitrary dimensions, stored as a JSON blob in SQL.
	Labels r3.JSONColumn[Labels] `json:"labels" db:"labels" bson:"labels"`

	// Bucket is the pre-computed time bucket for efficient aggregation: RFC3339
	// truncated to the boundary (e.g. "2026-02-16T00:00:00Z" for daily).
	Bucket string `json:"bucket" db:"bucket" bson:"bucket"`

	// CreatedAt is when this metric was recorded.
	CreatedAt time.Time `json:"created_at" db:"created_at" bson:"created_at"`
}

MetricRecord is a single recorded metric data point, itself a first-class r3 entity storable via any r3.CRUD[MetricRecord, string] backend.

type Operation

type Operation string

Operation identifies which CRUD method was invoked.

const (
	OpCreate     Operation = "create"
	OpGet        Operation = "get"
	OpList       Operation = "list"
	OpCount      Operation = "count"
	OpAggregate  Operation = "aggregate"
	OpUpdate     Operation = "update"
	OpPatch      Operation = "patch"
	OpDelete     Operation = "delete"
	OpUpsert     Operation = "upsert"
	OpPatchWhere Operation = "patch_where"
)

type OperationContext

type OperationContext[T any, ID comparable] struct {
	// Operation is the CRUD method that was invoked.
	Operation Operation

	// Duration is the wall-clock time the inner operation took.
	Duration time.Duration

	// Entity is the result: post-mutation for create/update/patch, pre-deletion
	// for delete, fetched for get. Zero for list - use Entities.
	Entity T

	// Entities is the result set for List. Nil otherwise.
	Entities []T

	// OldEntity is the state before mutation (update/patch/delete).
	OldEntity T

	// HasOld reports whether OldEntity is valid.
	HasOld bool

	// EntityID is the primary key (get/delete).
	EntityID ID

	// TotalCount is List's total match count before pagination.
	TotalCount int64

	// Query is the r3.Query passed to Get or List.
	Query r3.Query

	// Fields is the field list passed to Patch.
	Fields r3.Fields

	// Err is the inner operation's error, nil on success. Most collectors fire
	// only on success; ErrorCollector uses this.
	Err error
}

OperationContext carries everything a collector needs about a completed CRUD operation. Fields not relevant to a given Operation hold their zero value.

type Option

type Option[T any, ID comparable] func(*Options[T, ID])

Option is a functional option for configuring the metrics CRUD decorator.

func WithAsync

func WithAsync[T any, ID comparable]() Option[T, ID]

WithAsync enables asynchronous metric recording.

func WithBucketSize

func WithBucketSize[T any, ID comparable](size BucketSize) Option[T, ID]

WithBucketSize sets the time bucketing granularity.

func WithCollectors

func WithCollectors[T any, ID comparable](collectors ...Collector[T, ID]) Option[T, ID]

WithCollectors sets the metric collectors.

func WithContextLabelers

func WithContextLabelers[T any, ID comparable](labelers ...ContextLabeler) Option[T, ID]

WithContextLabelers sets context-based label extractors.

func WithErrorHandler

func WithErrorHandler[T any, ID comparable](fn func(error)) Option[T, ID]

WithErrorHandler sets a custom error handler for metric persistence failures.

func WithIDFunc

func WithIDFunc[T any, ID comparable](fn IDFunc[T, ID]) Option[T, ID]

WithIDFunc sets the function that extracts the primary key from an entity.

func WithLabelers

func WithLabelers[T any, ID comparable](labelers ...Labeler[T]) Option[T, ID]

WithLabelers sets entity-based label extractors.

func WithRecordType

func WithRecordType[T any, ID comparable](name string) Option[T, ID]

WithRecordType sets an explicit record type name for metric records.

type Options

type Options[T any, ID comparable] struct {
	// RecordType names this entity type in metric records. If empty, derived from
	// T as snake_case plural (e.g. Order -> "orders").
	RecordType string

	// IDFunc extracts the primary key; required for entity-level metrics.
	IDFunc IDFunc[T, ID]

	// Collectors are the metrics emitted per operation; all run on every op.
	Collectors []Collector[T, ID]

	// Labelers extract labels from entities, applied wherever an entity is available.
	Labelers []Labeler[T]

	// ContextLabelers extract labels from request context, applied to every record.
	ContextLabelers []ContextLabeler

	// BucketSize is the time bucketing granularity. Default: BucketDaily.
	BucketSize BucketSize

	// Async records metrics in a background goroutine: the CRUD op returns
	// immediately and recording errors are logged, not surfaced. Default: false.
	Async bool

	// ErrorHandler is called on persistence failure; if nil, errors go to slog.
	ErrorHandler func(error)
}

Options configures a metrics CRUD decorator.

type QueryOption

type QueryOption func(*queryConfig)

QueryOption configures additional filtering for aggregator methods.

func WithLabel

func WithLabel(key, value string) QueryOption

WithLabel restricts an aggregator query to records carrying this exact label key-value pair.

type RankedEntity

type RankedEntity struct {
	RecordID string
	Value    float64
}

RankedEntity is an entity ranked by an aggregated metric value.

type RetentionEnforcer

type RetentionEnforcer struct {
	// contains filtered or unexported fields
}

RetentionEnforcer cleans up old metric records in any r3.CRUD[MetricRecord, string] per its policy.

Usage:

enforcer := metrics.NewRetentionEnforcer(metricsStore, metrics.RetentionPolicy{
    MaxAge: 90 * 24 * time.Hour,   // delete records older than 90 days
    MaxRecords: 1_000_000,          // keep at most 1M records per type
})

// Run once (e.g. from a cron job):
enforcer.Enforce(ctx, "orders")

// Run periodically in the background:
stop := enforcer.Start(ctx, "orders", 1*time.Hour)
defer stop()

func NewRetentionEnforcer

func NewRetentionEnforcer(store r3.CRUD[MetricRecord, string], policy RetentionPolicy) *RetentionEnforcer

NewRetentionEnforcer creates an enforcer for store with the given policy.

func (*RetentionEnforcer) Enforce

func (e *RetentionEnforcer) Enforce(ctx context.Context, recordType string) int64

Enforce runs a single retention pass and returns the number of records deleted. Individual deletion errors are logged but do not halt the pass.

func (*RetentionEnforcer) Start

func (e *RetentionEnforcer) Start(ctx context.Context, recordType string, interval time.Duration) func()

Start enforces retention periodically in a goroutine, returning a stop function that cancels the loop.

type RetentionPolicy

type RetentionPolicy struct {
	// MaxAge deletes records older than this. Zero disables age-based retention.
	MaxAge time.Duration

	// MaxRecords caps records kept per entity type, deleting the oldest first.
	// Zero disables the count-based limit.
	MaxRecords int64
}

RetentionPolicy defines cleanup rules for old metric records; both fields apply together, whichever deletes more.

type RollupExecutor

type RollupExecutor struct {
	// contains filtered or unexported fields
}

RollupExecutor compacts fine-grained metric records into coarser summaries by querying old records, grouping by (record_type, metric_name, target_bucket, label set), summing each group into one summary, then deleting the originals.

Usage:

executor := metrics.NewRollupExecutor(metricsStore, metrics.RollupPolicy{
    SourceBucket: metrics.BucketMinutely,
    TargetBucket: metrics.BucketHourly,
    MinAge:       7 * 24 * time.Hour,
})
executor.Execute(ctx, "orders")

func NewRollupExecutor

func NewRollupExecutor(store r3.CRUD[MetricRecord, string], policy RollupPolicy) *RollupExecutor

NewRollupExecutor creates a rollup executor for store with the given policy.

func (*RollupExecutor) Execute

func (rx *RollupExecutor) Execute(ctx context.Context, recordType string) (int64, int64)

Execute runs a single rollup pass, returning the count of records deleted (compacted) and summary records created.

type RollupPolicy

type RollupPolicy struct {
	// SourceBucket is recorded in the summary's _rollup_source label for
	// provenance only; it does not filter which records are compacted.
	SourceBucket BucketSize

	// TargetBucket is the coarser bucket size to roll up into.
	TargetBucket BucketSize

	// MinAge compacts only records older than this. Zero means all records.
	MinAge time.Duration
}

RollupPolicy defines how old fine-grained records are compacted into coarser summaries - e.g. minutely records older than 7 days into hourly.

type TimeRange

type TimeRange struct {
	From time.Time
	To   time.Time
}

TimeRange defines a time window for querying metrics.

func Last24Hours

func Last24Hours() TimeRange

Last24Hours returns a TimeRange covering the last 24 hours.

func Last30Days

func Last30Days() TimeRange

Last30Days returns a TimeRange covering the last 30 days.

func LastDays

func LastDays(n int) TimeRange

LastDays returns a TimeRange covering the last n days.

func LastHours

func LastHours(n int) TimeRange

LastHours returns a TimeRange covering the last n hours.

func LastMinutes

func LastMinutes(n int) TimeRange

LastMinutes returns a TimeRange covering the last n minutes.

func LastMonth

func LastMonth() TimeRange

LastMonth returns a TimeRange covering the last calendar month.

func LastWeek

func LastWeek() TimeRange

LastWeek returns a TimeRange covering the last 7 days.

func LastYear

func LastYear() TimeRange

LastYear returns a TimeRange covering the last 365 days.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL