Berserk Docs
Tabular OperatorsAggregate Operators

make-series

Creates series of aggregated values along a specified axis, typically time-based.

Each output column is a dynamic array with one element per bin: the axis column plus one array per aggregate. Bins with no rows are filled from default= (0 when omitted), so the arrays are dense and all the same length.

An aggregate must return a value a series can carry — a numeric (int/long/real), bool, datetime or timespan. Those all have a numeric representation, which is what lets a bin be gap-filled and the series_* functions do arithmetic over the array. An aggregate returning string, guid or dynamic is rejected; use summarize for those.

The check is on the aggregate's result, not its name, so the same function can be valid or not depending on its argument: max(duration) and max(timestamp) are series, max(service_name) is not. Multi-column aggregates (percentiles(x, 50, 95), arg_max) return dynamic and are likewise rejected — take one value per call (percentile(x, 95)) instead. ADX applies the same restriction.

A dynamic argument is not rejected by this: OTel data reaches aggregates as $raw.x, and it is cast to the aggregate's expected type first, so make-series max($raw.duration) builds a numeric series rather than failing.

The exception, for max, min and take_any. Those three accept every scalar type, so "the expected type" is ambiguous and the cast resolves to numeric. That is right when the dynamic holds a number — which is how OTel carries durations and counts — but a dynamic holding a datetime or timespan extracts as null, and the series comes back empty rather than erroring. Cast explicitly when that is the shape:

| make-series last_seen = max(asdatetime($raw.event_time)) on timestamp step 1h | make-series slowest = max(astimespan($raw.took)) on timestamp step 1h

This applies to those three only. Every other aggregate either declares dynamic as its input type and needs no cast at all (otel_rate($raw) and the rest of the OTel family, which is what the metrics charts use), or is numeric to begin with (sum, avg, stdev, percentile, …) so the numeric cast is the correct one. There is no need to cast make-series arguments in general.

Syntax

make-series aggregation on column [from start] [to end] step interval [by group]

Create time series

Parameters

NameDescription
aggregationAggregation function to apply
columnColumn defining the series axis (usually datetime)
startStart of the axis range. When omitted, inferred from data min. (optional)
endEnd of the axis range. When omitted, inferred from data max. (optional)
intervalBin size for the axis
groupGrouping expression (optional)

Examples

Example 1

datatable(ts:datetime, silver:long, region:string)[
  datetime(2024-01-01), 100, "England",
  datetime(2024-01-02), 250, "England",
  datetime(2024-01-03), 180, "England",
  datetime(2024-01-01), 50, "Francia",
  datetime(2024-01-02), 300, "Francia",
  datetime(2024-01-03), 120, "Francia"
]
| make-series total_loot = sum(silver) on ts
  from datetime(2024-01-01) to datetime(2024-01-04) step 1d
  by region
region (string)total_loot (dynamic)ts (dynamic)
England[100,250,180][2024-01-01T00:00:00Z,2024-01-02T00:00:00Z,2024-01-03T00:00:00Z]
Francia[50,300,120][2024-01-01T00:00:00Z,2024-01-02T00:00:00Z,2024-01-03T00:00:00Z]

Example 2

datatable(ts:datetime, raids:long)[
  datetime(2024-01-01), 3,
  datetime(2024-01-02), 5,
  datetime(2024-01-03), 2,
  datetime(2024-01-04), 7
]
| make-series total = sum(raids) on ts step 1d
total (dynamic)ts (dynamic)
[3,5,2,7][2024-01-01T00:00:00Z,2024-01-02T00:00:00Z,2024-01-03T00:00:00Z,2024-01-04T00:00:00Z]

On this page