pond-ts 0.1.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/TimeSeries.d.ts +132 -2
- package/dist/TimeSeries.d.ts.map +1 -1
- package/dist/TimeSeries.js +1044 -239
- package/dist/TimeSeries.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/reducers/avg.d.ts +3 -0
- package/dist/reducers/avg.d.ts.map +1 -0
- package/dist/reducers/avg.js +45 -0
- package/dist/reducers/avg.js.map +1 -0
- package/dist/reducers/count.d.ts +3 -0
- package/dist/reducers/count.d.ts.map +1 -0
- package/dist/reducers/count.js +35 -0
- package/dist/reducers/count.js.map +1 -0
- package/dist/reducers/difference.d.ts +3 -0
- package/dist/reducers/difference.d.ts.map +1 -0
- package/dist/reducers/difference.js +48 -0
- package/dist/reducers/difference.js.map +1 -0
- package/dist/reducers/first.d.ts +3 -0
- package/dist/reducers/first.d.ts.map +1 -0
- package/dist/reducers/first.js +23 -0
- package/dist/reducers/first.js.map +1 -0
- package/dist/reducers/index.d.ts +4 -0
- package/dist/reducers/index.d.ts.map +1 -0
- package/dist/reducers/index.js +35 -0
- package/dist/reducers/index.js.map +1 -0
- package/dist/reducers/keep.d.ts +3 -0
- package/dist/reducers/keep.d.ts.map +1 -0
- package/dist/reducers/keep.js +56 -0
- package/dist/reducers/keep.js.map +1 -0
- package/dist/reducers/last.d.ts +3 -0
- package/dist/reducers/last.d.ts.map +1 -0
- package/dist/reducers/last.js +23 -0
- package/dist/reducers/last.js.map +1 -0
- package/dist/reducers/max.d.ts +3 -0
- package/dist/reducers/max.d.ts.map +1 -0
- package/dist/reducers/max.js +25 -0
- package/dist/reducers/max.js.map +1 -0
- package/dist/reducers/median.d.ts +3 -0
- package/dist/reducers/median.d.ts.map +1 -0
- package/dist/reducers/median.js +39 -0
- package/dist/reducers/median.js.map +1 -0
- package/dist/reducers/min.d.ts +3 -0
- package/dist/reducers/min.d.ts.map +1 -0
- package/dist/reducers/min.js +25 -0
- package/dist/reducers/min.js.map +1 -0
- package/dist/reducers/percentile.d.ts +5 -0
- package/dist/reducers/percentile.d.ts.map +1 -0
- package/dist/reducers/percentile.js +56 -0
- package/dist/reducers/percentile.js.map +1 -0
- package/dist/reducers/rolling.d.ts +15 -0
- package/dist/reducers/rolling.d.ts.map +1 -0
- package/dist/reducers/rolling.js +84 -0
- package/dist/reducers/rolling.js.map +1 -0
- package/dist/reducers/stdev.d.ts +3 -0
- package/dist/reducers/stdev.d.ts.map +1 -0
- package/dist/reducers/stdev.js +58 -0
- package/dist/reducers/stdev.js.map +1 -0
- package/dist/reducers/sum.d.ts +3 -0
- package/dist/reducers/sum.d.ts.map +1 -0
- package/dist/reducers/sum.js +35 -0
- package/dist/reducers/sum.js.map +1 -0
- package/dist/reducers/types.d.ts +58 -0
- package/dist/reducers/types.d.ts.map +1 -0
- package/dist/reducers/types.js +2 -0
- package/dist/reducers/types.js.map +1 -0
- package/dist/types.d.ts +28 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/TimeSeries.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
var _a;
|
|
1
2
|
import { BoundedSequence } from './BoundedSequence.js';
|
|
2
3
|
import { parseTimestampString } from './calendar.js';
|
|
3
4
|
import { Interval } from './Interval.js';
|
|
4
5
|
import { Time } from './Time.js';
|
|
5
6
|
import { TimeRange } from './TimeRange.js';
|
|
7
|
+
import { Event } from './Event.js';
|
|
6
8
|
import { Sequence } from './Sequence.js';
|
|
7
9
|
import { validateAndNormalize } from './validate.js';
|
|
10
|
+
import { resolveReducer, } from './reducers/index.js';
|
|
8
11
|
function isObjectRow(value) {
|
|
9
12
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
10
13
|
}
|
|
@@ -89,6 +92,23 @@ function parseJsonRows(schema, rows, options = {}) {
|
|
|
89
92
|
}));
|
|
90
93
|
});
|
|
91
94
|
}
|
|
95
|
+
function serializeJsonKey(kind, key, rowFormat) {
|
|
96
|
+
if (kind === 'time') {
|
|
97
|
+
return key.begin();
|
|
98
|
+
}
|
|
99
|
+
if (kind === 'timeRange') {
|
|
100
|
+
return rowFormat === 'object'
|
|
101
|
+
? { start: key.begin(), end: key.end() }
|
|
102
|
+
: [key.begin(), key.end()];
|
|
103
|
+
}
|
|
104
|
+
const interval = key;
|
|
105
|
+
return rowFormat === 'object'
|
|
106
|
+
? { value: interval.value, start: interval.begin(), end: interval.end() }
|
|
107
|
+
: [interval.value, interval.begin(), interval.end()];
|
|
108
|
+
}
|
|
109
|
+
function serializeJsonValue(value) {
|
|
110
|
+
return value === undefined ? null : value;
|
|
111
|
+
}
|
|
92
112
|
function toRows(schema, events) {
|
|
93
113
|
return events.map((event) => {
|
|
94
114
|
const data = event.data();
|
|
@@ -100,6 +120,20 @@ function toRows(schema, events) {
|
|
|
100
120
|
]);
|
|
101
121
|
});
|
|
102
122
|
}
|
|
123
|
+
function toObjects(schema, events) {
|
|
124
|
+
const keyColumn = schema[0];
|
|
125
|
+
const dataColumns = schema.slice(1);
|
|
126
|
+
return events.map((event) => {
|
|
127
|
+
const row = {
|
|
128
|
+
[keyColumn.name]: event.key(),
|
|
129
|
+
};
|
|
130
|
+
const data = event.data();
|
|
131
|
+
for (const column of dataColumns) {
|
|
132
|
+
row[column.name] = data[column.name];
|
|
133
|
+
}
|
|
134
|
+
return Object.freeze(row);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
103
137
|
function isEventKey(value) {
|
|
104
138
|
return (typeof value === 'object' &&
|
|
105
139
|
value !== null &&
|
|
@@ -180,26 +214,38 @@ function eventAnchorTime(key) {
|
|
|
180
214
|
return key instanceof Time ? key.begin() : key.timeRange().midpoint();
|
|
181
215
|
}
|
|
182
216
|
function loessAt(x, anchors, values, span) {
|
|
183
|
-
|
|
184
|
-
const value = values[index];
|
|
185
|
-
return value === undefined ? [] : [{ x: anchor, y: value }];
|
|
186
|
-
});
|
|
187
|
-
if (points.length === 0) {
|
|
217
|
+
if (anchors.length === 0) {
|
|
188
218
|
return undefined;
|
|
189
219
|
}
|
|
190
|
-
if (
|
|
191
|
-
return
|
|
220
|
+
if (anchors.length === 1) {
|
|
221
|
+
return values[0];
|
|
192
222
|
}
|
|
193
|
-
const neighborCount = Math.max(2, Math.min(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
223
|
+
const neighborCount = Math.max(2, Math.min(anchors.length, Math.ceil(span * anchors.length)));
|
|
224
|
+
let start = 0;
|
|
225
|
+
if (neighborCount < anchors.length) {
|
|
226
|
+
let low = 0;
|
|
227
|
+
let high = anchors.length - neighborCount;
|
|
228
|
+
while (low < high) {
|
|
229
|
+
const mid = Math.floor((low + high) / 2);
|
|
230
|
+
if (x - anchors[mid] > anchors[mid + neighborCount] - x) {
|
|
231
|
+
low = mid + 1;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
high = mid;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
start = low;
|
|
238
|
+
}
|
|
239
|
+
const end = start + neighborCount;
|
|
240
|
+
const bandwidth = Math.max(Math.abs(x - anchors[start]), Math.abs(anchors[end - 1] - x));
|
|
198
241
|
if (bandwidth === 0) {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
242
|
+
const coincidentStart = lowerBound(anchors, x);
|
|
243
|
+
const coincidentEnd = upperBound(anchors, x);
|
|
244
|
+
let coincidentSum = 0;
|
|
245
|
+
for (let index = coincidentStart; index < coincidentEnd; index++) {
|
|
246
|
+
coincidentSum += values[index];
|
|
247
|
+
}
|
|
248
|
+
return coincidentSum / (coincidentEnd - coincidentStart);
|
|
203
249
|
}
|
|
204
250
|
let weightedCount = 0;
|
|
205
251
|
let sumW = 0;
|
|
@@ -207,7 +253,10 @@ function loessAt(x, anchors, values, span) {
|
|
|
207
253
|
let sumWY = 0;
|
|
208
254
|
let sumWXX = 0;
|
|
209
255
|
let sumWXY = 0;
|
|
210
|
-
for (
|
|
256
|
+
for (let index = start; index < end; index++) {
|
|
257
|
+
const pointX = anchors[index];
|
|
258
|
+
const pointY = values[index];
|
|
259
|
+
const distance = Math.abs(pointX - x);
|
|
211
260
|
const ratio = distance / bandwidth;
|
|
212
261
|
const weight = ratio >= 1 ? 0 : (1 - ratio ** 3) ** 3;
|
|
213
262
|
if (weight === 0) {
|
|
@@ -215,10 +264,10 @@ function loessAt(x, anchors, values, span) {
|
|
|
215
264
|
}
|
|
216
265
|
weightedCount += 1;
|
|
217
266
|
sumW += weight;
|
|
218
|
-
sumWX += weight *
|
|
219
|
-
sumWY += weight *
|
|
220
|
-
sumWXX += weight *
|
|
221
|
-
sumWXY += weight *
|
|
267
|
+
sumWX += weight * pointX;
|
|
268
|
+
sumWY += weight * pointY;
|
|
269
|
+
sumWXX += weight * pointX * pointX;
|
|
270
|
+
sumWXY += weight * pointX * pointY;
|
|
222
271
|
}
|
|
223
272
|
if (weightedCount === 0 || sumW === 0) {
|
|
224
273
|
return undefined;
|
|
@@ -231,6 +280,34 @@ function loessAt(x, anchors, values, span) {
|
|
|
231
280
|
const slope = (sumW * sumWXY - sumWX * sumWY) / denominator;
|
|
232
281
|
return intercept + slope * x;
|
|
233
282
|
}
|
|
283
|
+
function lowerBound(values, target) {
|
|
284
|
+
let low = 0;
|
|
285
|
+
let high = values.length;
|
|
286
|
+
while (low < high) {
|
|
287
|
+
const mid = Math.floor((low + high) / 2);
|
|
288
|
+
if (values[mid] < target) {
|
|
289
|
+
low = mid + 1;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
high = mid;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return low;
|
|
296
|
+
}
|
|
297
|
+
function upperBound(values, target) {
|
|
298
|
+
let low = 0;
|
|
299
|
+
let high = values.length;
|
|
300
|
+
while (low < high) {
|
|
301
|
+
const mid = Math.floor((low + high) / 2);
|
|
302
|
+
if (values[mid] <= target) {
|
|
303
|
+
low = mid + 1;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
high = mid;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return low;
|
|
310
|
+
}
|
|
234
311
|
function makeSmoothSchema(schema, target, output) {
|
|
235
312
|
if (output === undefined || output === target) {
|
|
236
313
|
return Object.freeze([
|
|
@@ -273,28 +350,60 @@ function bucketOverlapsHalfOpen(bucket, event) {
|
|
|
273
350
|
function aggregateValues(operation, values) {
|
|
274
351
|
const defined = values.filter((value) => value !== undefined);
|
|
275
352
|
const numeric = defined.filter((value) => typeof value === 'number');
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
353
|
+
return resolveReducer(operation).reduce(defined, numeric);
|
|
354
|
+
}
|
|
355
|
+
function isBuiltInAggregateReducer(reducer) {
|
|
356
|
+
return typeof reducer === 'string';
|
|
357
|
+
}
|
|
358
|
+
function applyAggregateReducer(reducer, values) {
|
|
359
|
+
return isBuiltInAggregateReducer(reducer)
|
|
360
|
+
? aggregateValues(reducer, values)
|
|
361
|
+
: reducer(values);
|
|
362
|
+
}
|
|
363
|
+
function isAggregateOutputSpec(value) {
|
|
364
|
+
return (typeof value === 'object' &&
|
|
365
|
+
value !== null &&
|
|
366
|
+
'from' in value &&
|
|
367
|
+
'using' in value);
|
|
368
|
+
}
|
|
369
|
+
function normalizeAggregateColumns(schema, mapping) {
|
|
370
|
+
const columnsByName = new Map(schema.slice(1).map((column) => [column.name, column]));
|
|
371
|
+
const normalized = [];
|
|
372
|
+
for (const [outputName, raw] of Object.entries(mapping)) {
|
|
373
|
+
const sourceName = isAggregateOutputSpec(raw) ? raw.from : outputName;
|
|
374
|
+
const sourceColumn = columnsByName.get(sourceName);
|
|
375
|
+
if (!sourceColumn) {
|
|
376
|
+
throw new TypeError(`aggregate mapping references unknown source column '${sourceName}'`);
|
|
377
|
+
}
|
|
378
|
+
if (sourceColumn.kind !== 'number' &&
|
|
379
|
+
sourceColumn.kind !== 'string' &&
|
|
380
|
+
sourceColumn.kind !== 'boolean') {
|
|
381
|
+
throw new TypeError(`aggregate source column '${sourceName}' must be a scalar value column`);
|
|
382
|
+
}
|
|
383
|
+
const reducer = isAggregateOutputSpec(raw) ? raw.using : raw;
|
|
384
|
+
if (typeof reducer !== 'string' && typeof reducer !== 'function') {
|
|
385
|
+
throw new TypeError(`aggregate reducer for '${outputName}' must be a built-in name or function`);
|
|
386
|
+
}
|
|
387
|
+
const explicitKind = isAggregateOutputSpec(raw) ? raw.kind : undefined;
|
|
388
|
+
const resolvedKind = explicitKind ??
|
|
389
|
+
(typeof reducer === 'string' &&
|
|
390
|
+
resolveReducer(reducer).outputKind === 'number'
|
|
391
|
+
? 'number'
|
|
392
|
+
: sourceColumn.kind);
|
|
393
|
+
normalized.push({
|
|
394
|
+
output: outputName,
|
|
395
|
+
source: sourceName,
|
|
396
|
+
reducer,
|
|
397
|
+
kind: resolvedKind,
|
|
398
|
+
});
|
|
297
399
|
}
|
|
400
|
+
return normalized;
|
|
401
|
+
}
|
|
402
|
+
function createAggregateBucketState(operation) {
|
|
403
|
+
return resolveReducer(operation).bucketState();
|
|
404
|
+
}
|
|
405
|
+
function createRollingReducerState(operation) {
|
|
406
|
+
return resolveReducer(operation).rollingState();
|
|
298
407
|
}
|
|
299
408
|
function parseDurationInput(value) {
|
|
300
409
|
if (typeof value === 'number') {
|
|
@@ -428,7 +537,7 @@ export class TimeSeries {
|
|
|
428
537
|
* the supplied `parse.timeZone`, which defaults to `UTC`.
|
|
429
538
|
*/
|
|
430
539
|
static fromJSON(input) {
|
|
431
|
-
return new
|
|
540
|
+
return new _a({
|
|
432
541
|
name: input.name,
|
|
433
542
|
schema: input.schema,
|
|
434
543
|
rows: parseJsonRows(input.schema, input.rows, input.parse),
|
|
@@ -441,6 +550,61 @@ export class TimeSeries {
|
|
|
441
550
|
this.events = validateAndNormalize(input);
|
|
442
551
|
Object.freeze(this);
|
|
443
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* Example: `series.toJSON({ rowFormat: "object" })`.
|
|
555
|
+
* Serializes the series into the JSON-friendly shape accepted by `TimeSeries.fromJSON(...)`.
|
|
556
|
+
*
|
|
557
|
+
* Timestamps are emitted as numbers to avoid time zone ambiguity. Missing payload values are
|
|
558
|
+
* emitted as `null`. By default rows are emitted as arrays; use `rowFormat: "object"` for rows
|
|
559
|
+
* keyed by schema column names.
|
|
560
|
+
*/
|
|
561
|
+
toJSON(options = {}) {
|
|
562
|
+
const rowFormat = options.rowFormat ?? 'array';
|
|
563
|
+
const dataColumns = this.schema.slice(1);
|
|
564
|
+
if (rowFormat === 'object') {
|
|
565
|
+
const keyColumn = this.schema[0];
|
|
566
|
+
const rows = this.events.map((event) => {
|
|
567
|
+
const row = {
|
|
568
|
+
[keyColumn.name]: serializeJsonKey(keyColumn.kind, event.key(), rowFormat),
|
|
569
|
+
};
|
|
570
|
+
const data = event.data();
|
|
571
|
+
for (const column of dataColumns) {
|
|
572
|
+
row[column.name] = serializeJsonValue(data[column.name]);
|
|
573
|
+
}
|
|
574
|
+
return Object.freeze(row);
|
|
575
|
+
});
|
|
576
|
+
return {
|
|
577
|
+
name: this.name,
|
|
578
|
+
schema: this.schema,
|
|
579
|
+
rows,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const rows = this.events.map((event) => {
|
|
583
|
+
const data = event.data();
|
|
584
|
+
return Object.freeze([
|
|
585
|
+
serializeJsonKey(this.schema[0].kind, event.key(), rowFormat),
|
|
586
|
+
...dataColumns.map((column) => serializeJsonValue(data[column.name])),
|
|
587
|
+
]);
|
|
588
|
+
});
|
|
589
|
+
return {
|
|
590
|
+
name: this.name,
|
|
591
|
+
schema: this.schema,
|
|
592
|
+
rows,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Builds a series from event data that has already been validated and ordered by the caller.
|
|
597
|
+
*
|
|
598
|
+
* This is intentionally private and only used by transforms that preserve the existing event
|
|
599
|
+
* order and normalized key invariants.
|
|
600
|
+
*/
|
|
601
|
+
static #fromTrustedEvents(name, schema, events) {
|
|
602
|
+
const series = Object.create(_a.prototype);
|
|
603
|
+
series.name = name;
|
|
604
|
+
series.schema = Object.freeze(schema.slice());
|
|
605
|
+
series.events = Object.freeze(events.slice());
|
|
606
|
+
return Object.freeze(series);
|
|
607
|
+
}
|
|
444
608
|
/** Example: `series.firstColumnKind`. Returns the first-column kind from the series schema. */
|
|
445
609
|
get firstColumnKind() {
|
|
446
610
|
return this.schema[0].kind;
|
|
@@ -449,6 +613,14 @@ export class TimeSeries {
|
|
|
449
613
|
get rows() {
|
|
450
614
|
return toRows(this.schema, this.events);
|
|
451
615
|
}
|
|
616
|
+
/** Example: `series.toRows()`. Returns normalized row arrays using `Time`/`TimeRange`/`Interval` keys and `undefined` for missing payload values. */
|
|
617
|
+
toRows() {
|
|
618
|
+
return this.rows;
|
|
619
|
+
}
|
|
620
|
+
/** Example: `series.toObjects()`. Returns normalized schema-keyed object rows using temporal key objects and `undefined` for missing payload values. */
|
|
621
|
+
toObjects() {
|
|
622
|
+
return toObjects(this.schema, this.events);
|
|
623
|
+
}
|
|
452
624
|
/** Example: `series.at(0)`. Returns the event at the supplied zero-based position, if present. */
|
|
453
625
|
at(index) {
|
|
454
626
|
return this.events[index];
|
|
@@ -466,7 +638,7 @@ export class TimeSeries {
|
|
|
466
638
|
/** Example: `series.map(nextSchema, event => event)`. Maps each event into a new typed schema and returns a new series. */
|
|
467
639
|
map(schema, mapper) {
|
|
468
640
|
const mappedEvents = this.events.map((event, index) => mapper(event, index));
|
|
469
|
-
return new
|
|
641
|
+
return new _a({
|
|
470
642
|
name: this.name,
|
|
471
643
|
schema,
|
|
472
644
|
rows: toRows(schema, mappedEvents),
|
|
@@ -478,10 +650,14 @@ export class TimeSeries {
|
|
|
478
650
|
{ name: 'time', kind: 'time' },
|
|
479
651
|
...this.schema.slice(1),
|
|
480
652
|
]);
|
|
481
|
-
|
|
653
|
+
const resultEvents = this.events.map((event) => event.asTime(options));
|
|
654
|
+
if ((options.at ?? 'begin') === 'begin') {
|
|
655
|
+
return _a.#fromTrustedEvents(this.name, schema, resultEvents);
|
|
656
|
+
}
|
|
657
|
+
return new _a({
|
|
482
658
|
name: this.name,
|
|
483
659
|
schema,
|
|
484
|
-
rows: toRows(schema,
|
|
660
|
+
rows: toRows(schema, resultEvents),
|
|
485
661
|
});
|
|
486
662
|
}
|
|
487
663
|
/** Example: `series.asTimeRange()`. Converts the series key type to `"timeRange"` while preserving each event extent. */
|
|
@@ -490,11 +666,8 @@ export class TimeSeries {
|
|
|
490
666
|
{ name: 'timeRange', kind: 'timeRange' },
|
|
491
667
|
...this.schema.slice(1),
|
|
492
668
|
]);
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
schema,
|
|
496
|
-
rows: toRows(schema, this.events.map((event) => event.asTimeRange())),
|
|
497
|
-
});
|
|
669
|
+
const resultEvents = this.events.map((event) => event.asTimeRange());
|
|
670
|
+
return _a.#fromTrustedEvents(this.name, schema, resultEvents);
|
|
498
671
|
}
|
|
499
672
|
asInterval(value) {
|
|
500
673
|
const schema = Object.freeze([
|
|
@@ -506,14 +679,13 @@ export class TimeSeries {
|
|
|
506
679
|
? event.asInterval(() => value(event, index))
|
|
507
680
|
: event.asInterval(value);
|
|
508
681
|
});
|
|
509
|
-
return
|
|
510
|
-
name: this.name,
|
|
511
|
-
schema,
|
|
512
|
-
rows: toRows(schema, nextEvents),
|
|
513
|
-
});
|
|
682
|
+
return _a.#fromTrustedEvents(this.name, schema, nextEvents);
|
|
514
683
|
}
|
|
515
684
|
join(other, options = {}) {
|
|
516
|
-
const [left, right] = prepareSeriesForJoin([
|
|
685
|
+
const [left, right] = prepareSeriesForJoin([
|
|
686
|
+
this,
|
|
687
|
+
other,
|
|
688
|
+
], options);
|
|
517
689
|
const joinType = options.type ?? 'outer';
|
|
518
690
|
if (left.firstColumnKind !== right.firstColumnKind) {
|
|
519
691
|
throw new TypeError('cannot join series with different key kinds');
|
|
@@ -566,11 +738,7 @@ export class TimeSeries {
|
|
|
566
738
|
rightIndex += 1;
|
|
567
739
|
}
|
|
568
740
|
}
|
|
569
|
-
return
|
|
570
|
-
name: left.name,
|
|
571
|
-
schema: resultSchema,
|
|
572
|
-
rows: toRows(resultSchema, joinedEvents),
|
|
573
|
-
});
|
|
741
|
+
return _a.#fromTrustedEvents(left.name, resultSchema, joinedEvents);
|
|
574
742
|
}
|
|
575
743
|
/**
|
|
576
744
|
* Example: `series.align(Sequence.every("1m"))`.
|
|
@@ -602,7 +770,7 @@ export class TimeSeries {
|
|
|
602
770
|
const range = options.range ?? this.timeRange();
|
|
603
771
|
const resultSchema = makeAlignedSchema(this.schema);
|
|
604
772
|
if (!range) {
|
|
605
|
-
return new
|
|
773
|
+
return new _a({
|
|
606
774
|
name: this.name,
|
|
607
775
|
schema: resultSchema,
|
|
608
776
|
rows: [],
|
|
@@ -613,103 +781,481 @@ export class TimeSeries {
|
|
|
613
781
|
}
|
|
614
782
|
const intervals = toBoundedSequence(sequence, range, sample).intervals();
|
|
615
783
|
const valueColumns = this.schema.slice(1);
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
.
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
784
|
+
const resultColumns = resultSchema.slice(1);
|
|
785
|
+
const alignedRows = method === 'linear'
|
|
786
|
+
? (() => {
|
|
787
|
+
const cursor = { index: 0 };
|
|
788
|
+
const rows = new Array(intervals.length);
|
|
789
|
+
for (let i = 0; i < intervals.length; i += 1) {
|
|
790
|
+
const interval = intervals[i];
|
|
791
|
+
const t = sampleTime(interval, sample);
|
|
792
|
+
const data = alignLinearAt(this, t, valueColumns, cursor);
|
|
793
|
+
const row = new Array(resultColumns.length + 1);
|
|
794
|
+
row[0] = interval;
|
|
795
|
+
for (let j = 0; j < resultColumns.length; j += 1) {
|
|
796
|
+
const column = resultColumns[j];
|
|
797
|
+
row[j + 1] = data[column.name];
|
|
798
|
+
}
|
|
799
|
+
rows[i] = Object.freeze(row);
|
|
800
|
+
}
|
|
801
|
+
return rows;
|
|
802
|
+
})()
|
|
803
|
+
: intervals.map((interval) => {
|
|
804
|
+
const t = sampleTime(interval, sample);
|
|
805
|
+
const data = alignHoldAt(this, t);
|
|
806
|
+
return Object.freeze([
|
|
807
|
+
interval,
|
|
808
|
+
...resultSchema
|
|
809
|
+
.slice(1)
|
|
810
|
+
.map((column) => data[column.name]),
|
|
811
|
+
]);
|
|
812
|
+
});
|
|
813
|
+
return new _a({
|
|
629
814
|
name: this.name,
|
|
630
815
|
schema: resultSchema,
|
|
631
816
|
rows: alignedRows,
|
|
632
817
|
});
|
|
633
818
|
}
|
|
819
|
+
aggregate(sequence, mapping, options = {}) {
|
|
820
|
+
return aggregateInternal(this, sequence, mapping, options);
|
|
821
|
+
}
|
|
822
|
+
reduce(columnOrMapping, reducer) {
|
|
823
|
+
if (typeof columnOrMapping === 'string') {
|
|
824
|
+
const values = this.events.map((event) => {
|
|
825
|
+
const data = event.data();
|
|
826
|
+
return data[columnOrMapping];
|
|
827
|
+
});
|
|
828
|
+
return applyAggregateReducer(reducer, values);
|
|
829
|
+
}
|
|
830
|
+
const columns = normalizeAggregateColumns(this.schema, columnOrMapping);
|
|
831
|
+
const result = {};
|
|
832
|
+
for (const col of columns) {
|
|
833
|
+
const values = this.events.map((event) => {
|
|
834
|
+
const data = event.data();
|
|
835
|
+
return data[col.source];
|
|
836
|
+
});
|
|
837
|
+
result[col.output] = applyAggregateReducer(col.reducer, values);
|
|
838
|
+
}
|
|
839
|
+
return result;
|
|
840
|
+
}
|
|
841
|
+
groupBy(column, transform) {
|
|
842
|
+
const buckets = new Map();
|
|
843
|
+
for (const event of this.events) {
|
|
844
|
+
const raw = event.data()[column];
|
|
845
|
+
const key = raw === undefined ? 'undefined' : String(raw);
|
|
846
|
+
let bucket = buckets.get(key);
|
|
847
|
+
if (!bucket) {
|
|
848
|
+
bucket = [];
|
|
849
|
+
buckets.set(key, bucket);
|
|
850
|
+
}
|
|
851
|
+
bucket.push(event);
|
|
852
|
+
}
|
|
853
|
+
const buildGroup = (events) => new _a({
|
|
854
|
+
name: this.name,
|
|
855
|
+
schema: this.schema,
|
|
856
|
+
rows: toRows(this.schema, events),
|
|
857
|
+
});
|
|
858
|
+
if (transform) {
|
|
859
|
+
const result = new Map();
|
|
860
|
+
for (const [key, events] of buckets) {
|
|
861
|
+
result.set(key, transform(buildGroup(events), key));
|
|
862
|
+
}
|
|
863
|
+
return result;
|
|
864
|
+
}
|
|
865
|
+
const result = new Map();
|
|
866
|
+
for (const [key, events] of buckets) {
|
|
867
|
+
result.set(key, buildGroup(events));
|
|
868
|
+
}
|
|
869
|
+
return result;
|
|
870
|
+
}
|
|
634
871
|
/**
|
|
635
|
-
* Example: `series.
|
|
636
|
-
*
|
|
872
|
+
* Example: `series.diff("requests")`.
|
|
873
|
+
* Computes per-event differences for the specified numeric columns.
|
|
874
|
+
* Non-specified columns pass through unchanged. The first event gets
|
|
875
|
+
* `undefined` in affected columns unless `{ drop: true }` is passed,
|
|
876
|
+
* which removes the first event entirely.
|
|
637
877
|
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
* overlap under half-open overlap rules.
|
|
878
|
+
* Example: `series.diff(["requests", "cpu"])`.
|
|
879
|
+
* Multiple columns can be diffed in a single call.
|
|
641
880
|
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
881
|
+
* Example: `series.diff("requests", { drop: true })`.
|
|
882
|
+
* Drops the first event instead of keeping it with undefined values.
|
|
883
|
+
*/
|
|
884
|
+
diff(columns, options) {
|
|
885
|
+
return this.#diffOrRate('diff', columns, options);
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Example: `series.rate("requests")`.
|
|
889
|
+
* Computes the per-second rate of change for the specified numeric columns.
|
|
890
|
+
* Non-specified columns pass through unchanged. The first event gets
|
|
891
|
+
* `undefined` in affected columns unless `{ drop: true }` is passed,
|
|
892
|
+
* which removes the first event entirely.
|
|
644
893
|
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
* `
|
|
894
|
+
* Example: `series.rate(["requests", "cpu"])`.
|
|
895
|
+
* Multiple columns can be rated in a single call.
|
|
896
|
+
*
|
|
897
|
+
* Example: `series.rate("requests", { drop: true })`.
|
|
898
|
+
* Drops the first event instead of keeping it with undefined values.
|
|
899
|
+
*/
|
|
900
|
+
rate(columns, options) {
|
|
901
|
+
return this.#diffOrRate('rate', columns, options);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Example: `series.pctChange("requests")`.
|
|
905
|
+
* Computes the percentage change `(curr - prev) / prev` for the specified
|
|
906
|
+
* numeric columns. Non-specified columns pass through unchanged. The first
|
|
907
|
+
* event gets `undefined` in affected columns unless `{ drop: true }` is
|
|
908
|
+
* passed.
|
|
909
|
+
*/
|
|
910
|
+
pctChange(columns, options) {
|
|
911
|
+
return this.#diffOrRate('pctChange', columns, options);
|
|
912
|
+
}
|
|
913
|
+
#diffOrRate(mode, columns, options) {
|
|
914
|
+
const cols = typeof columns === 'string' ? [columns] : columns;
|
|
915
|
+
const drop = options?.drop === true;
|
|
916
|
+
if (cols.length === 0) {
|
|
917
|
+
throw new Error(`${mode}() requires at least one column name`);
|
|
918
|
+
}
|
|
919
|
+
const targetSet = new Set(cols);
|
|
920
|
+
const outSchema = Object.freeze(this.schema.map((col, i) => {
|
|
921
|
+
if (i === 0)
|
|
922
|
+
return col;
|
|
923
|
+
if (targetSet.has(col.name)) {
|
|
924
|
+
return { ...col, kind: 'number', required: false };
|
|
925
|
+
}
|
|
926
|
+
return col;
|
|
927
|
+
}));
|
|
928
|
+
const events = this.events;
|
|
929
|
+
if (events.length === 0) {
|
|
930
|
+
return _a.#fromTrustedEvents(this.name, outSchema, []);
|
|
931
|
+
}
|
|
932
|
+
const resultEvents = [];
|
|
933
|
+
if (!drop) {
|
|
934
|
+
const firstData = { ...events[0].data() };
|
|
935
|
+
for (const col of cols) {
|
|
936
|
+
firstData[col] = undefined;
|
|
937
|
+
}
|
|
938
|
+
resultEvents.push(new Event(events[0].key(), firstData));
|
|
939
|
+
}
|
|
940
|
+
for (let i = 1; i < events.length; i++) {
|
|
941
|
+
const prev = events[i - 1];
|
|
942
|
+
const curr = events[i];
|
|
943
|
+
const data = { ...curr.data() };
|
|
944
|
+
const dt = mode === 'rate' ? (curr.begin() - prev.begin()) / 1000 : undefined;
|
|
945
|
+
for (const col of cols) {
|
|
946
|
+
const prevVal = prev.data()[col];
|
|
947
|
+
const currVal = data[col];
|
|
948
|
+
if (typeof currVal === 'number' && typeof prevVal === 'number') {
|
|
949
|
+
const delta = currVal - prevVal;
|
|
950
|
+
if (mode === 'pctChange') {
|
|
951
|
+
data[col] = prevVal !== 0 ? delta / prevVal : undefined;
|
|
952
|
+
}
|
|
953
|
+
else if (mode === 'rate') {
|
|
954
|
+
data[col] = dt !== 0 ? delta / dt : undefined;
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
data[col] = delta;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
data[col] = undefined;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
resultEvents.push(new Event(curr.key(), data));
|
|
965
|
+
}
|
|
966
|
+
return _a.#fromTrustedEvents(this.name, outSchema, resultEvents);
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Example: `series.cumulative({ requests: "sum" })`.
|
|
970
|
+
* Computes running accumulations for the specified numeric columns.
|
|
971
|
+
* Non-accumulated columns pass through unchanged.
|
|
649
972
|
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
973
|
+
* Built-in accumulators: `"sum"`, `"max"`, `"min"`, `"count"`.
|
|
974
|
+
* Custom accumulators: `(acc: number, value: number) => number`.
|
|
975
|
+
*/
|
|
976
|
+
cumulative(spec) {
|
|
977
|
+
const entries = Object.entries(spec);
|
|
978
|
+
if (entries.length === 0) {
|
|
979
|
+
throw new Error('cumulative() requires at least one column');
|
|
980
|
+
}
|
|
981
|
+
const targetSet = new Set(entries.map(([name]) => name));
|
|
982
|
+
const outSchema = Object.freeze(this.schema.map((col, i) => {
|
|
983
|
+
if (i === 0)
|
|
984
|
+
return col;
|
|
985
|
+
if (targetSet.has(col.name)) {
|
|
986
|
+
return { ...col, kind: 'number', required: false };
|
|
987
|
+
}
|
|
988
|
+
return col;
|
|
989
|
+
}));
|
|
990
|
+
const events = this.events;
|
|
991
|
+
if (events.length === 0) {
|
|
992
|
+
return _a.#fromTrustedEvents(this.name, outSchema, []);
|
|
993
|
+
}
|
|
994
|
+
const state = new Map();
|
|
995
|
+
for (const [name, reducer] of entries) {
|
|
996
|
+
if (typeof reducer === 'function') {
|
|
997
|
+
const fn = reducer;
|
|
998
|
+
state.set(name, {
|
|
999
|
+
acc: undefined,
|
|
1000
|
+
apply: (acc, v) => (acc === undefined ? v : fn(acc, v)),
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
switch (reducer) {
|
|
1005
|
+
case 'sum':
|
|
1006
|
+
state.set(name, {
|
|
1007
|
+
acc: undefined,
|
|
1008
|
+
apply: (acc, v) => (acc ?? 0) + v,
|
|
1009
|
+
});
|
|
1010
|
+
break;
|
|
1011
|
+
case 'count':
|
|
1012
|
+
state.set(name, { acc: undefined, apply: (acc) => (acc ?? 0) + 1 });
|
|
1013
|
+
break;
|
|
1014
|
+
case 'max':
|
|
1015
|
+
state.set(name, {
|
|
1016
|
+
acc: undefined,
|
|
1017
|
+
apply: (acc, v) => (acc === undefined || v > acc ? v : acc),
|
|
1018
|
+
});
|
|
1019
|
+
break;
|
|
1020
|
+
case 'min':
|
|
1021
|
+
state.set(name, {
|
|
1022
|
+
acc: undefined,
|
|
1023
|
+
apply: (acc, v) => (acc === undefined || v < acc ? v : acc),
|
|
1024
|
+
});
|
|
1025
|
+
break;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
const resultEvents = [];
|
|
1030
|
+
for (const event of events) {
|
|
1031
|
+
const data = { ...event.data() };
|
|
1032
|
+
for (const [name, s] of state) {
|
|
1033
|
+
const raw = data[name];
|
|
1034
|
+
if (typeof raw === 'number') {
|
|
1035
|
+
s.acc = s.apply(s.acc, raw);
|
|
1036
|
+
data[name] = s.acc;
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
data[name] = s.acc;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
resultEvents.push(new Event(event.key(), data));
|
|
1043
|
+
}
|
|
1044
|
+
return _a.#fromTrustedEvents(this.name, outSchema, resultEvents);
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Example: `series.shift("value", 1)`.
|
|
1048
|
+
* Lags column values by N events (positive N) or leads them (negative N).
|
|
1049
|
+
* Vacated positions get `undefined`.
|
|
1050
|
+
*/
|
|
1051
|
+
shift(columns, n) {
|
|
1052
|
+
const cols = typeof columns === 'string' ? [columns] : columns;
|
|
1053
|
+
if (cols.length === 0) {
|
|
1054
|
+
throw new Error('shift() requires at least one column name');
|
|
1055
|
+
}
|
|
1056
|
+
if (!Number.isInteger(n)) {
|
|
1057
|
+
throw new Error('shift() requires an integer offset');
|
|
1058
|
+
}
|
|
1059
|
+
const targetSet = new Set(cols);
|
|
1060
|
+
const outSchema = Object.freeze(this.schema.map((col, i) => {
|
|
1061
|
+
if (i === 0)
|
|
1062
|
+
return col;
|
|
1063
|
+
if (targetSet.has(col.name)) {
|
|
1064
|
+
return { ...col, kind: 'number', required: false };
|
|
1065
|
+
}
|
|
1066
|
+
return col;
|
|
1067
|
+
}));
|
|
1068
|
+
const events = this.events;
|
|
1069
|
+
if (events.length === 0) {
|
|
1070
|
+
return _a.#fromTrustedEvents(this.name, outSchema, []);
|
|
1071
|
+
}
|
|
1072
|
+
const resultEvents = [];
|
|
1073
|
+
for (let i = 0; i < events.length; i++) {
|
|
1074
|
+
const data = { ...events[i].data() };
|
|
1075
|
+
const srcIdx = i - n;
|
|
1076
|
+
for (const col of cols) {
|
|
1077
|
+
if (srcIdx >= 0 && srcIdx < events.length) {
|
|
1078
|
+
data[col] = events[srcIdx].data()[col];
|
|
1079
|
+
}
|
|
1080
|
+
else {
|
|
1081
|
+
data[col] = undefined;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
resultEvents.push(new Event(events[i].key(), data));
|
|
1085
|
+
}
|
|
1086
|
+
return _a.#fromTrustedEvents(this.name, outSchema, resultEvents);
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Example: `series.fill("hold")`.
|
|
1090
|
+
* Fills `undefined` values using the given strategy for all payload columns.
|
|
652
1091
|
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
1092
|
+
* Example: `series.fill({ cpu: "linear", host: "hold" })`.
|
|
1093
|
+
* Per-column fill strategies. Unmentioned columns are left as-is.
|
|
1094
|
+
* Strategy names: `"hold"` (forward fill), `"linear"` (time-interpolated),
|
|
1095
|
+
* `"zero"` (fill with 0). A non-string value is used as a literal fill value.
|
|
655
1096
|
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
* throw new Error("empty series");
|
|
660
|
-
* }
|
|
1097
|
+
* Example: `series.fill("hold", { limit: 3 })`.
|
|
1098
|
+
* Caps consecutive fills per column. After `limit` consecutive fills, further
|
|
1099
|
+
* `undefined` values are left as-is until a real value resets the counter.
|
|
661
1100
|
*
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
* { value: "avg" },
|
|
665
|
-
* );
|
|
666
|
-
* ```
|
|
1101
|
+
* `"linear"` requires known values on both sides of a gap to interpolate.
|
|
1102
|
+
* Leading and trailing `undefined` runs are left unfilled.
|
|
667
1103
|
*/
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
{ name: 'interval', kind: 'interval' },
|
|
672
|
-
...this.schema
|
|
673
|
-
.slice(1)
|
|
674
|
-
.filter((column) => column.name in mapping)
|
|
675
|
-
.map((column) => {
|
|
676
|
-
const operation = mapping[column.name];
|
|
677
|
-
return {
|
|
678
|
-
name: column.name,
|
|
679
|
-
kind: operation === 'sum' ||
|
|
680
|
-
operation === 'avg' ||
|
|
681
|
-
operation === 'count'
|
|
682
|
-
? 'number'
|
|
683
|
-
: column.kind,
|
|
684
|
-
required: false,
|
|
685
|
-
};
|
|
686
|
-
}),
|
|
687
|
-
]);
|
|
688
|
-
if (!range) {
|
|
689
|
-
return new TimeSeries({
|
|
690
|
-
name: this.name,
|
|
691
|
-
schema: resultSchema,
|
|
692
|
-
rows: [],
|
|
693
|
-
});
|
|
1104
|
+
fill(strategy, options) {
|
|
1105
|
+
if (this.events.length === 0) {
|
|
1106
|
+
return this;
|
|
694
1107
|
}
|
|
695
|
-
const
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
name
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1108
|
+
const colNames = this.schema.slice(1).map((c) => c.name);
|
|
1109
|
+
const specs = new Map();
|
|
1110
|
+
if (typeof strategy === 'string') {
|
|
1111
|
+
for (const name of colNames) {
|
|
1112
|
+
specs.set(name, { mode: strategy });
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
else {
|
|
1116
|
+
const strategies = new Set([
|
|
1117
|
+
'hold',
|
|
1118
|
+
'bfill',
|
|
1119
|
+
'linear',
|
|
1120
|
+
'zero',
|
|
1121
|
+
]);
|
|
1122
|
+
for (const [name, spec] of Object.entries(strategy)) {
|
|
1123
|
+
if (typeof spec === 'string' && strategies.has(spec)) {
|
|
1124
|
+
specs.set(name, { mode: spec });
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
specs.set(name, { mode: 'literal', value: spec });
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const limit = options?.limit;
|
|
1132
|
+
const n = this.events.length;
|
|
1133
|
+
const columns = {};
|
|
1134
|
+
for (const name of colNames) {
|
|
1135
|
+
columns[name] = new Array(n);
|
|
1136
|
+
}
|
|
1137
|
+
for (let i = 0; i < n; i++) {
|
|
1138
|
+
const data = this.events[i].data();
|
|
1139
|
+
for (const name of colNames) {
|
|
1140
|
+
columns[name][i] = data[name];
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
const times = new Array(n);
|
|
1144
|
+
for (let i = 0; i < n; i++) {
|
|
1145
|
+
times[i] = this.events[i].begin();
|
|
1146
|
+
}
|
|
1147
|
+
for (const [name, spec] of specs) {
|
|
1148
|
+
const col = columns[name];
|
|
1149
|
+
if (!col)
|
|
1150
|
+
continue;
|
|
1151
|
+
switch (spec.mode) {
|
|
1152
|
+
case 'hold': {
|
|
1153
|
+
let last;
|
|
1154
|
+
let consecutive = 0;
|
|
1155
|
+
for (let i = 0; i < n; i++) {
|
|
1156
|
+
if (col[i] !== undefined) {
|
|
1157
|
+
last = col[i];
|
|
1158
|
+
consecutive = 0;
|
|
1159
|
+
}
|
|
1160
|
+
else if (last !== undefined) {
|
|
1161
|
+
consecutive++;
|
|
1162
|
+
if (limit === undefined || consecutive <= limit) {
|
|
1163
|
+
col[i] = last;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
case 'bfill': {
|
|
1170
|
+
let next;
|
|
1171
|
+
let consecutive = 0;
|
|
1172
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
1173
|
+
if (col[i] !== undefined) {
|
|
1174
|
+
next = col[i];
|
|
1175
|
+
consecutive = 0;
|
|
1176
|
+
}
|
|
1177
|
+
else if (next !== undefined) {
|
|
1178
|
+
consecutive++;
|
|
1179
|
+
if (limit === undefined || consecutive <= limit) {
|
|
1180
|
+
col[i] = next;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
break;
|
|
1185
|
+
}
|
|
1186
|
+
case 'zero': {
|
|
1187
|
+
let consecutive = 0;
|
|
1188
|
+
for (let i = 0; i < n; i++) {
|
|
1189
|
+
if (col[i] !== undefined) {
|
|
1190
|
+
consecutive = 0;
|
|
1191
|
+
}
|
|
1192
|
+
else {
|
|
1193
|
+
consecutive++;
|
|
1194
|
+
if (limit === undefined || consecutive <= limit) {
|
|
1195
|
+
col[i] = 0;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
case 'literal': {
|
|
1202
|
+
let consecutive = 0;
|
|
1203
|
+
for (let i = 0; i < n; i++) {
|
|
1204
|
+
if (col[i] !== undefined) {
|
|
1205
|
+
consecutive = 0;
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
consecutive++;
|
|
1209
|
+
if (limit === undefined || consecutive <= limit) {
|
|
1210
|
+
col[i] = spec.value;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
case 'linear': {
|
|
1217
|
+
let gapStart = -1;
|
|
1218
|
+
for (let i = 0; i < n; i++) {
|
|
1219
|
+
if (col[i] !== undefined) {
|
|
1220
|
+
if (gapStart >= 0 && gapStart > 0) {
|
|
1221
|
+
const before = col[gapStart - 1];
|
|
1222
|
+
const after = col[i];
|
|
1223
|
+
const t0 = times[gapStart - 1];
|
|
1224
|
+
const t1 = times[i];
|
|
1225
|
+
const span = t1 - t0;
|
|
1226
|
+
const gapLen = i - gapStart;
|
|
1227
|
+
for (let j = gapStart; j < i; j++) {
|
|
1228
|
+
const fillIndex = j - gapStart + 1;
|
|
1229
|
+
if (limit !== undefined && fillIndex > limit)
|
|
1230
|
+
break;
|
|
1231
|
+
if (span === 0) {
|
|
1232
|
+
col[j] = before;
|
|
1233
|
+
}
|
|
1234
|
+
else {
|
|
1235
|
+
const ratio = (times[j] - t0) / span;
|
|
1236
|
+
col[j] = before + (after - before) * ratio;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
gapStart = -1;
|
|
1241
|
+
}
|
|
1242
|
+
else if (gapStart < 0) {
|
|
1243
|
+
gapStart = i;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
break;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const resultEvents = [];
|
|
1251
|
+
for (let i = 0; i < n; i++) {
|
|
1252
|
+
const data = {};
|
|
1253
|
+
for (const name of colNames) {
|
|
1254
|
+
data[name] = columns[name][i];
|
|
1255
|
+
}
|
|
1256
|
+
resultEvents.push(new Event(this.events[i].key(), data));
|
|
1257
|
+
}
|
|
1258
|
+
return _a.#fromTrustedEvents(this.name, this.schema, resultEvents);
|
|
713
1259
|
}
|
|
714
1260
|
rolling(sequenceOrWindow, windowOrMapping, mappingOrOptions, maybeOptions = {}) {
|
|
715
1261
|
const buildResultColumns = () => this.schema
|
|
@@ -765,7 +1311,7 @@ export class TimeSeries {
|
|
|
765
1311
|
...buildResultColumns(),
|
|
766
1312
|
]);
|
|
767
1313
|
if (!range) {
|
|
768
|
-
return new
|
|
1314
|
+
return new _a({
|
|
769
1315
|
name: this.name,
|
|
770
1316
|
schema: resultSchema,
|
|
771
1317
|
rows: [],
|
|
@@ -776,39 +1322,162 @@ export class TimeSeries {
|
|
|
776
1322
|
const anchor = sampleTime(bucket, sample);
|
|
777
1323
|
const contributors = this.events.filter((candidate) => anchorInWindow(candidate.begin(), anchor));
|
|
778
1324
|
const aggregated = resultSchema.slice(1).map((column) => {
|
|
779
|
-
const
|
|
1325
|
+
const reducer = mapping[column.name];
|
|
780
1326
|
const values = contributors.map((candidate) => {
|
|
781
1327
|
const data = candidate.data();
|
|
782
1328
|
return data[column.name];
|
|
783
1329
|
});
|
|
784
|
-
return
|
|
1330
|
+
return applyAggregateReducer(reducer, values);
|
|
785
1331
|
});
|
|
786
1332
|
return Object.freeze([bucket, ...aggregated]);
|
|
787
1333
|
});
|
|
788
|
-
return new
|
|
1334
|
+
return new _a({
|
|
789
1335
|
name: this.name,
|
|
790
1336
|
schema: resultSchema,
|
|
791
1337
|
rows: resultRows,
|
|
792
1338
|
});
|
|
793
1339
|
}
|
|
1340
|
+
const resultColumns = buildResultColumns();
|
|
794
1341
|
const resultSchema = Object.freeze([
|
|
795
1342
|
this.schema[0],
|
|
796
|
-
...
|
|
1343
|
+
...resultColumns,
|
|
797
1344
|
]);
|
|
798
|
-
const
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1345
|
+
const reducerStates = resultColumns.map((column) => {
|
|
1346
|
+
const reducer = mapping[column.name];
|
|
1347
|
+
return isBuiltInAggregateReducer(reducer)
|
|
1348
|
+
? createRollingReducerState(reducer)
|
|
1349
|
+
: null;
|
|
1350
|
+
});
|
|
1351
|
+
const beginTimes = this.events.map((event) => event.begin());
|
|
1352
|
+
const resultRows = new Array(this.events.length);
|
|
1353
|
+
let windowStart = 0;
|
|
1354
|
+
let windowEnd = 0;
|
|
1355
|
+
const addEvent = (index) => {
|
|
1356
|
+
const event = this.events[index];
|
|
1357
|
+
const data = event.data();
|
|
1358
|
+
for (let i = 0; i < reducerStates.length; i++) {
|
|
1359
|
+
const state = reducerStates[i];
|
|
1360
|
+
if (state) {
|
|
1361
|
+
const column = resultColumns[i];
|
|
1362
|
+
state.add(index, data[column.name]);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
const removeEvent = (index) => {
|
|
1367
|
+
const event = this.events[index];
|
|
1368
|
+
const data = event.data();
|
|
1369
|
+
for (let i = 0; i < reducerStates.length; i++) {
|
|
1370
|
+
const state = reducerStates[i];
|
|
1371
|
+
if (state) {
|
|
1372
|
+
const column = resultColumns[i];
|
|
1373
|
+
state.remove(index, data[column.name]);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
const snapshotWindow = () => resultColumns.map((column, i) => {
|
|
1378
|
+
const state = reducerStates[i];
|
|
1379
|
+
if (state)
|
|
1380
|
+
return state.snapshot();
|
|
1381
|
+
const reducer = mapping[column.name];
|
|
1382
|
+
const values = this.events
|
|
1383
|
+
.slice(windowStart, windowEnd)
|
|
1384
|
+
.map((event) => {
|
|
1385
|
+
const data = event.data();
|
|
1386
|
+
return data[column.name];
|
|
808
1387
|
});
|
|
809
|
-
return
|
|
1388
|
+
return applyAggregateReducer(reducer, values);
|
|
810
1389
|
});
|
|
811
|
-
|
|
1390
|
+
if (alignment === 'trailing') {
|
|
1391
|
+
for (let groupStart = 0; groupStart < this.events.length;) {
|
|
1392
|
+
const anchor = beginTimes[groupStart];
|
|
1393
|
+
let groupEnd = groupStart + 1;
|
|
1394
|
+
while (groupEnd < this.events.length &&
|
|
1395
|
+
beginTimes[groupEnd] === anchor) {
|
|
1396
|
+
groupEnd += 1;
|
|
1397
|
+
}
|
|
1398
|
+
while (windowEnd < this.events.length &&
|
|
1399
|
+
beginTimes[windowEnd] <= anchor) {
|
|
1400
|
+
addEvent(windowEnd);
|
|
1401
|
+
windowEnd += 1;
|
|
1402
|
+
}
|
|
1403
|
+
const lowerBound = anchor - windowMs;
|
|
1404
|
+
while (windowStart < windowEnd &&
|
|
1405
|
+
beginTimes[windowStart] <= lowerBound) {
|
|
1406
|
+
removeEvent(windowStart);
|
|
1407
|
+
windowStart += 1;
|
|
1408
|
+
}
|
|
1409
|
+
const aggregated = snapshotWindow();
|
|
1410
|
+
for (let index = groupStart; index < groupEnd; index++) {
|
|
1411
|
+
resultRows[index] = Object.freeze([
|
|
1412
|
+
this.events[index].key(),
|
|
1413
|
+
...aggregated,
|
|
1414
|
+
]);
|
|
1415
|
+
}
|
|
1416
|
+
groupStart = groupEnd;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
else if (alignment === 'leading') {
|
|
1420
|
+
for (let groupStart = 0; groupStart < this.events.length;) {
|
|
1421
|
+
const anchor = beginTimes[groupStart];
|
|
1422
|
+
let groupEnd = groupStart + 1;
|
|
1423
|
+
while (groupEnd < this.events.length &&
|
|
1424
|
+
beginTimes[groupEnd] === anchor) {
|
|
1425
|
+
groupEnd += 1;
|
|
1426
|
+
}
|
|
1427
|
+
const lowerBound = anchor;
|
|
1428
|
+
while (windowStart < windowEnd &&
|
|
1429
|
+
beginTimes[windowStart] < lowerBound) {
|
|
1430
|
+
removeEvent(windowStart);
|
|
1431
|
+
windowStart += 1;
|
|
1432
|
+
}
|
|
1433
|
+
const upperBound = anchor + windowMs;
|
|
1434
|
+
while (windowEnd < this.events.length &&
|
|
1435
|
+
beginTimes[windowEnd] < upperBound) {
|
|
1436
|
+
addEvent(windowEnd);
|
|
1437
|
+
windowEnd += 1;
|
|
1438
|
+
}
|
|
1439
|
+
const aggregated = snapshotWindow();
|
|
1440
|
+
for (let index = groupStart; index < groupEnd; index++) {
|
|
1441
|
+
resultRows[index] = Object.freeze([
|
|
1442
|
+
this.events[index].key(),
|
|
1443
|
+
...aggregated,
|
|
1444
|
+
]);
|
|
1445
|
+
}
|
|
1446
|
+
groupStart = groupEnd;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
else {
|
|
1450
|
+
const halfWindow = windowMs / 2;
|
|
1451
|
+
for (let groupStart = 0; groupStart < this.events.length;) {
|
|
1452
|
+
const anchor = beginTimes[groupStart];
|
|
1453
|
+
let groupEnd = groupStart + 1;
|
|
1454
|
+
while (groupEnd < this.events.length &&
|
|
1455
|
+
beginTimes[groupEnd] === anchor) {
|
|
1456
|
+
groupEnd += 1;
|
|
1457
|
+
}
|
|
1458
|
+
const lowerBound = anchor - halfWindow;
|
|
1459
|
+
while (windowStart < windowEnd &&
|
|
1460
|
+
beginTimes[windowStart] < lowerBound) {
|
|
1461
|
+
removeEvent(windowStart);
|
|
1462
|
+
windowStart += 1;
|
|
1463
|
+
}
|
|
1464
|
+
const upperBound = anchor + halfWindow;
|
|
1465
|
+
while (windowEnd < this.events.length &&
|
|
1466
|
+
beginTimes[windowEnd] < upperBound) {
|
|
1467
|
+
addEvent(windowEnd);
|
|
1468
|
+
windowEnd += 1;
|
|
1469
|
+
}
|
|
1470
|
+
const aggregated = snapshotWindow();
|
|
1471
|
+
for (let index = groupStart; index < groupEnd; index++) {
|
|
1472
|
+
resultRows[index] = Object.freeze([
|
|
1473
|
+
this.events[index].key(),
|
|
1474
|
+
...aggregated,
|
|
1475
|
+
]);
|
|
1476
|
+
}
|
|
1477
|
+
groupStart = groupEnd;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return new _a({
|
|
812
1481
|
name: this.name,
|
|
813
1482
|
schema: resultSchema,
|
|
814
1483
|
rows: resultRows,
|
|
@@ -873,7 +1542,7 @@ export class TimeSeries {
|
|
|
873
1542
|
.map((nextColumn) => nextEvent.data()[nextColumn.name]),
|
|
874
1543
|
]);
|
|
875
1544
|
});
|
|
876
|
-
return new
|
|
1545
|
+
return new _a({
|
|
877
1546
|
name: this.name,
|
|
878
1547
|
schema: resultSchema,
|
|
879
1548
|
rows: resultRows,
|
|
@@ -890,8 +1559,17 @@ export class TimeSeries {
|
|
|
890
1559
|
span > 1) {
|
|
891
1560
|
throw new TypeError('loess smoothing requires span to be a finite number in the range (0, 1]');
|
|
892
1561
|
}
|
|
1562
|
+
const loessAnchors = [];
|
|
1563
|
+
const loessValues = [];
|
|
1564
|
+
for (let index = 0; index < anchors.length; index++) {
|
|
1565
|
+
const value = sourceValues[index];
|
|
1566
|
+
if (typeof value === 'number') {
|
|
1567
|
+
loessAnchors.push(anchors[index]);
|
|
1568
|
+
loessValues.push(value);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
893
1571
|
const resultRows = this.events.map((event, index) => {
|
|
894
|
-
const smoothed = loessAt(anchors[index],
|
|
1572
|
+
const smoothed = loessAt(anchors[index], loessAnchors, loessValues, span);
|
|
895
1573
|
const nextEvent = output === undefined
|
|
896
1574
|
? event.set(column, smoothed)
|
|
897
1575
|
: event.merge({ [output]: smoothed });
|
|
@@ -902,7 +1580,7 @@ export class TimeSeries {
|
|
|
902
1580
|
.map((nextColumn) => nextEvent.data()[nextColumn.name]),
|
|
903
1581
|
]);
|
|
904
1582
|
});
|
|
905
|
-
return new
|
|
1583
|
+
return new _a({
|
|
906
1584
|
name: this.name,
|
|
907
1585
|
schema: resultSchema,
|
|
908
1586
|
rows: resultRows,
|
|
@@ -914,24 +1592,78 @@ export class TimeSeries {
|
|
|
914
1592
|
const window = options.window;
|
|
915
1593
|
const windowMs = parseDurationInput(window);
|
|
916
1594
|
const alignment = options.alignment ?? 'trailing';
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
|
|
1595
|
+
const resultValues = new Array(this.events.length);
|
|
1596
|
+
let windowStart = 0;
|
|
1597
|
+
let windowEnd = 0;
|
|
1598
|
+
let numericSum = 0;
|
|
1599
|
+
let numericCount = 0;
|
|
1600
|
+
const addEvent = (index) => {
|
|
1601
|
+
const value = sourceValues[index];
|
|
1602
|
+
if (typeof value === 'number') {
|
|
1603
|
+
numericSum += value;
|
|
1604
|
+
numericCount += 1;
|
|
920
1605
|
}
|
|
921
|
-
|
|
922
|
-
|
|
1606
|
+
};
|
|
1607
|
+
const removeEvent = (index) => {
|
|
1608
|
+
const value = sourceValues[index];
|
|
1609
|
+
if (typeof value === 'number') {
|
|
1610
|
+
numericSum -= value;
|
|
1611
|
+
numericCount -= 1;
|
|
923
1612
|
}
|
|
924
|
-
const halfWindow = windowMs / 2;
|
|
925
|
-
return (candidate >= anchor - halfWindow && candidate < anchor + halfWindow);
|
|
926
1613
|
};
|
|
1614
|
+
const snapshot = () => numericCount === 0 ? undefined : numericSum / numericCount;
|
|
1615
|
+
for (let groupStart = 0; groupStart < this.events.length;) {
|
|
1616
|
+
const anchor = anchors[groupStart];
|
|
1617
|
+
let groupEnd = groupStart + 1;
|
|
1618
|
+
while (groupEnd < this.events.length && anchors[groupEnd] === anchor) {
|
|
1619
|
+
groupEnd += 1;
|
|
1620
|
+
}
|
|
1621
|
+
if (alignment === 'trailing') {
|
|
1622
|
+
while (windowEnd < this.events.length &&
|
|
1623
|
+
anchors[windowEnd] <= anchor) {
|
|
1624
|
+
addEvent(windowEnd);
|
|
1625
|
+
windowEnd += 1;
|
|
1626
|
+
}
|
|
1627
|
+
const lowerBound = anchor - windowMs;
|
|
1628
|
+
while (windowStart < windowEnd && anchors[windowStart] <= lowerBound) {
|
|
1629
|
+
removeEvent(windowStart);
|
|
1630
|
+
windowStart += 1;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
else if (alignment === 'leading') {
|
|
1634
|
+
while (windowStart < windowEnd && anchors[windowStart] < anchor) {
|
|
1635
|
+
removeEvent(windowStart);
|
|
1636
|
+
windowStart += 1;
|
|
1637
|
+
}
|
|
1638
|
+
const upperBound = anchor + windowMs;
|
|
1639
|
+
while (windowEnd < this.events.length &&
|
|
1640
|
+
anchors[windowEnd] < upperBound) {
|
|
1641
|
+
addEvent(windowEnd);
|
|
1642
|
+
windowEnd += 1;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
else {
|
|
1646
|
+
const halfWindow = windowMs / 2;
|
|
1647
|
+
while (windowStart < windowEnd &&
|
|
1648
|
+
anchors[windowStart] < anchor - halfWindow) {
|
|
1649
|
+
removeEvent(windowStart);
|
|
1650
|
+
windowStart += 1;
|
|
1651
|
+
}
|
|
1652
|
+
const upperBound = anchor + halfWindow;
|
|
1653
|
+
while (windowEnd < this.events.length &&
|
|
1654
|
+
anchors[windowEnd] < upperBound) {
|
|
1655
|
+
addEvent(windowEnd);
|
|
1656
|
+
windowEnd += 1;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
const smoothed = snapshot();
|
|
1660
|
+
for (let index = groupStart; index < groupEnd; index++) {
|
|
1661
|
+
resultValues[index] = smoothed;
|
|
1662
|
+
}
|
|
1663
|
+
groupStart = groupEnd;
|
|
1664
|
+
}
|
|
927
1665
|
const resultRows = this.events.map((event, index) => {
|
|
928
|
-
const
|
|
929
|
-
const values = sourceValues
|
|
930
|
-
.filter((_, candidateIndex) => anchorInWindow(anchors[candidateIndex], anchor))
|
|
931
|
-
.flatMap((value) => (value === undefined ? [] : [value]));
|
|
932
|
-
const smoothed = values.length === 0
|
|
933
|
-
? undefined
|
|
934
|
-
: values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
1666
|
+
const smoothed = resultValues[index];
|
|
935
1667
|
const nextEvent = output === undefined
|
|
936
1668
|
? event.set(column, smoothed)
|
|
937
1669
|
: event.merge({ [output]: smoothed });
|
|
@@ -942,7 +1674,7 @@ export class TimeSeries {
|
|
|
942
1674
|
.map((nextColumn) => nextEvent.data()[nextColumn.name]),
|
|
943
1675
|
]);
|
|
944
1676
|
});
|
|
945
|
-
return new
|
|
1677
|
+
return new _a({
|
|
946
1678
|
name: this.name,
|
|
947
1679
|
schema: resultSchema,
|
|
948
1680
|
rows: resultRows,
|
|
@@ -950,19 +1682,11 @@ export class TimeSeries {
|
|
|
950
1682
|
}
|
|
951
1683
|
/** Example: `series.slice(0, 10)`. Returns a positional half-open slice of the series. */
|
|
952
1684
|
slice(beginIndex, endIndex) {
|
|
953
|
-
return
|
|
954
|
-
name: this.name,
|
|
955
|
-
schema: this.schema,
|
|
956
|
-
rows: toRows(this.schema, this.events.slice(beginIndex, endIndex)),
|
|
957
|
-
});
|
|
1685
|
+
return _a.#fromTrustedEvents(this.name, this.schema, this.events.slice(beginIndex, endIndex));
|
|
958
1686
|
}
|
|
959
1687
|
/** Example: `series.filter(event => event.get("active"))`. Returns a new series containing only events that match the predicate. */
|
|
960
1688
|
filter(predicate) {
|
|
961
|
-
return
|
|
962
|
-
name: this.name,
|
|
963
|
-
schema: this.schema,
|
|
964
|
-
rows: toRows(this.schema, this.events.filter((event, index) => predicate(event, index))),
|
|
965
|
-
});
|
|
1689
|
+
return _a.#fromTrustedEvents(this.name, this.schema, this.events.filter((event, index) => predicate(event, index)));
|
|
966
1690
|
}
|
|
967
1691
|
/** Example: `series.find(event => event.get("value") > 0)`. Returns the first event that matches the predicate, if any. */
|
|
968
1692
|
find(predicate) {
|
|
@@ -979,7 +1703,9 @@ export class TimeSeries {
|
|
|
979
1703
|
/** Example: `series.includesKey(new Time(Date.now()))`. Returns `true` when the series contains an event with an exactly matching key. */
|
|
980
1704
|
includesKey(key) {
|
|
981
1705
|
const normalizedKey = toKey(key);
|
|
982
|
-
|
|
1706
|
+
const index = this.bisect(normalizedKey);
|
|
1707
|
+
return (index < this.events.length &&
|
|
1708
|
+
this.events[index].key().equals(normalizedKey));
|
|
983
1709
|
}
|
|
984
1710
|
/** Example: `series.bisect(new Time(Date.now()))`. Returns the insertion index for the supplied key in the ordered event sequence. */
|
|
985
1711
|
bisect(key) {
|
|
@@ -1081,11 +1807,7 @@ export class TimeSeries {
|
|
|
1081
1807
|
const trimmedEvents = this.events
|
|
1082
1808
|
.map((event) => event.trim(range))
|
|
1083
1809
|
.filter((event) => event !== undefined);
|
|
1084
|
-
return
|
|
1085
|
-
name: this.name,
|
|
1086
|
-
schema: this.schema,
|
|
1087
|
-
rows: toRows(this.schema, trimmedEvents),
|
|
1088
|
-
});
|
|
1810
|
+
return _a.#fromTrustedEvents(this.name, this.schema, trimmedEvents);
|
|
1089
1811
|
}
|
|
1090
1812
|
/** Example: `series.before(Date.now())`. Returns the events ending strictly before the supplied temporal boundary. */
|
|
1091
1813
|
before(boundary) {
|
|
@@ -1117,11 +1839,7 @@ export class TimeSeries {
|
|
|
1117
1839
|
const selectedEvent = event.select(...keys);
|
|
1118
1840
|
return selectedEvent;
|
|
1119
1841
|
});
|
|
1120
|
-
return
|
|
1121
|
-
name: this.name,
|
|
1122
|
-
schema: resultSchema,
|
|
1123
|
-
rows: toRows(resultSchema, resultEvents),
|
|
1124
|
-
});
|
|
1842
|
+
return _a.#fromTrustedEvents(this.name, resultSchema, resultEvents);
|
|
1125
1843
|
}
|
|
1126
1844
|
/** Example: `series.rename({ cpu: "usage" })`. Returns a new series with payload field names renamed according to the supplied mapping. */
|
|
1127
1845
|
rename(mapping) {
|
|
@@ -1139,11 +1857,7 @@ export class TimeSeries {
|
|
|
1139
1857
|
const renamedEvent = event.rename(mapping);
|
|
1140
1858
|
return renamedEvent;
|
|
1141
1859
|
});
|
|
1142
|
-
return
|
|
1143
|
-
name: this.name,
|
|
1144
|
-
schema: resultSchema,
|
|
1145
|
-
rows: toRows(resultSchema, resultEvents),
|
|
1146
|
-
});
|
|
1860
|
+
return _a.#fromTrustedEvents(this.name, resultSchema, resultEvents);
|
|
1147
1861
|
}
|
|
1148
1862
|
collapse(keys, output, reducer, options) {
|
|
1149
1863
|
const nextEvents = this.events.map((event) => {
|
|
@@ -1171,47 +1885,138 @@ export class TimeSeries {
|
|
|
1171
1885
|
: 'string',
|
|
1172
1886
|
},
|
|
1173
1887
|
]);
|
|
1174
|
-
return
|
|
1175
|
-
name: this.name,
|
|
1176
|
-
schema: resultSchema,
|
|
1177
|
-
rows: toRows(resultSchema, nextEvents),
|
|
1178
|
-
});
|
|
1888
|
+
return _a.#fromTrustedEvents(this.name, resultSchema, nextEvents);
|
|
1179
1889
|
}
|
|
1180
1890
|
/** Example: `series.length`. Returns the number of events in the series. */
|
|
1181
1891
|
get length() {
|
|
1182
1892
|
return this.events.length;
|
|
1183
1893
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1894
|
+
}
|
|
1895
|
+
_a = TimeSeries;
|
|
1896
|
+
function aggregateInternal(series, sequence, mapping, options = {}) {
|
|
1897
|
+
const range = options.range ?? series.timeRange();
|
|
1898
|
+
const aggregateColumns = normalizeAggregateColumns(series.schema, mapping);
|
|
1899
|
+
const resultSchema = Object.freeze([
|
|
1900
|
+
{ name: 'interval', kind: 'interval' },
|
|
1901
|
+
...aggregateColumns.map((column) => ({
|
|
1902
|
+
name: column.output,
|
|
1903
|
+
kind: column.kind,
|
|
1904
|
+
required: false,
|
|
1905
|
+
})),
|
|
1906
|
+
]);
|
|
1907
|
+
if (!range) {
|
|
1908
|
+
return new TimeSeries({
|
|
1909
|
+
name: series.name,
|
|
1910
|
+
schema: resultSchema,
|
|
1911
|
+
rows: [],
|
|
1912
|
+
});
|
|
1187
1913
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1914
|
+
const buckets = toBoundedSequence(sequence, range, 'begin').intervals();
|
|
1915
|
+
const columns = aggregateColumns;
|
|
1916
|
+
if (isTimeKeyed(series)) {
|
|
1917
|
+
const builtInOnly = columns.every((column) => isBuiltInAggregateReducer(column.reducer));
|
|
1918
|
+
let eventIndex = 0;
|
|
1919
|
+
const resultRows = buckets.map((bucket) => {
|
|
1920
|
+
const states = builtInOnly
|
|
1921
|
+
? columns.map((column) => createAggregateBucketState(column.reducer))
|
|
1922
|
+
: undefined;
|
|
1923
|
+
while (eventIndex < series.events.length &&
|
|
1924
|
+
series.events[eventIndex].begin() < bucket.begin()) {
|
|
1925
|
+
eventIndex += 1;
|
|
1926
|
+
}
|
|
1927
|
+
const bucketStart = eventIndex;
|
|
1928
|
+
let scanIndex = bucketStart;
|
|
1929
|
+
while (scanIndex < series.events.length &&
|
|
1930
|
+
series.events[scanIndex].begin() < bucket.end()) {
|
|
1931
|
+
if (states) {
|
|
1932
|
+
const data = series.events[scanIndex].data();
|
|
1933
|
+
for (let index = 0; index < columns.length; index += 1) {
|
|
1934
|
+
const column = columns[index];
|
|
1935
|
+
states[index].add(data[column.source]);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
scanIndex += 1;
|
|
1939
|
+
}
|
|
1940
|
+
eventIndex = scanIndex;
|
|
1941
|
+
if (states) {
|
|
1942
|
+
return Object.freeze([
|
|
1943
|
+
bucket,
|
|
1944
|
+
...states.map((state) => state.snapshot()),
|
|
1945
|
+
]);
|
|
1211
1946
|
}
|
|
1212
|
-
|
|
1947
|
+
const contributors = series.events.slice(bucketStart, scanIndex);
|
|
1948
|
+
const aggregated = columns.map((column) => {
|
|
1949
|
+
const values = contributors.map((event) => {
|
|
1950
|
+
const data = event.data();
|
|
1951
|
+
return data[column.source];
|
|
1952
|
+
});
|
|
1953
|
+
return applyAggregateReducer(column.reducer, values);
|
|
1954
|
+
});
|
|
1955
|
+
return Object.freeze([bucket, ...aggregated]);
|
|
1956
|
+
});
|
|
1957
|
+
return new TimeSeries({
|
|
1958
|
+
name: series.name,
|
|
1959
|
+
schema: resultSchema,
|
|
1960
|
+
rows: resultRows,
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
const resultRows = buckets.map((bucket) => {
|
|
1964
|
+
const contributors = series.events.filter((event) => bucketOverlapsHalfOpen(bucket, event.key()));
|
|
1965
|
+
const aggregated = columns.map((column) => {
|
|
1966
|
+
const values = contributors.map((event) => {
|
|
1967
|
+
const data = event.data();
|
|
1968
|
+
return data[column.source];
|
|
1969
|
+
});
|
|
1970
|
+
return applyAggregateReducer(column.reducer, values);
|
|
1971
|
+
});
|
|
1972
|
+
return Object.freeze([bucket, ...aggregated]);
|
|
1973
|
+
});
|
|
1974
|
+
return new TimeSeries({
|
|
1975
|
+
name: series.name,
|
|
1976
|
+
schema: resultSchema,
|
|
1977
|
+
rows: resultRows,
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
function alignHoldAt(series, t) {
|
|
1981
|
+
const event = series.atOrBefore(new Time(t));
|
|
1982
|
+
return (event?.data() ?? {});
|
|
1983
|
+
}
|
|
1984
|
+
function alignLinearAt(series, t, valueColumns, cursor) {
|
|
1985
|
+
const events = series.events;
|
|
1986
|
+
const hasCursor = cursor !== undefined;
|
|
1987
|
+
let index = hasCursor ? cursor.index : series.bisect(t);
|
|
1988
|
+
if (hasCursor) {
|
|
1989
|
+
while (index < events.length && events[index].begin() < t) {
|
|
1990
|
+
index += 1;
|
|
1213
1991
|
}
|
|
1214
|
-
|
|
1992
|
+
cursor.index = index;
|
|
1993
|
+
}
|
|
1994
|
+
if (index < events.length && events[index].begin() === t) {
|
|
1995
|
+
return events[index].data();
|
|
1996
|
+
}
|
|
1997
|
+
if (index === 0) {
|
|
1998
|
+
return {};
|
|
1999
|
+
}
|
|
2000
|
+
const previous = events[index - 1];
|
|
2001
|
+
const next = events[index];
|
|
2002
|
+
if (!next || previous.begin() === next.begin()) {
|
|
2003
|
+
return previous.data();
|
|
2004
|
+
}
|
|
2005
|
+
const ratio = (t - previous.begin()) / (next.begin() - previous.begin());
|
|
2006
|
+
const result = {};
|
|
2007
|
+
const previousData = previous.data();
|
|
2008
|
+
const nextData = next.data();
|
|
2009
|
+
for (const column of valueColumns) {
|
|
2010
|
+
const previousValue = previousData[column.name];
|
|
2011
|
+
const nextValue = nextData[column.name];
|
|
2012
|
+
if (column.kind === 'number' &&
|
|
2013
|
+
typeof previousValue === 'number' &&
|
|
2014
|
+
typeof nextValue === 'number') {
|
|
2015
|
+
result[column.name] = previousValue + (nextValue - previousValue) * ratio;
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
result[column.name] = previousValue;
|
|
1215
2019
|
}
|
|
2020
|
+
return result;
|
|
1216
2021
|
}
|
|
1217
2022
|
//# sourceMappingURL=TimeSeries.js.map
|