observability-toolkit 1.8.2 → 1.8.5
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 +60 -0
- package/dist/backends/index.d.ts +43 -0
- package/dist/backends/index.d.ts.map +1 -1
- package/dist/backends/index.js +41 -0
- package/dist/backends/index.js.map +1 -1
- package/dist/backends/index.test.d.ts +5 -0
- package/dist/backends/index.test.d.ts.map +1 -0
- package/dist/backends/index.test.js +156 -0
- package/dist/backends/index.test.js.map +1 -0
- package/dist/backends/local-jsonl-boolean-search.test.js +15 -12
- package/dist/backends/local-jsonl-boolean-search.test.js.map +1 -1
- package/dist/backends/local-jsonl-cache.test.d.ts +2 -0
- package/dist/backends/local-jsonl-cache.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-cache.test.js +295 -0
- package/dist/backends/local-jsonl-cache.test.js.map +1 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.d.ts +2 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.js +180 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.js.map +1 -0
- package/dist/backends/local-jsonl-export.test.d.ts +2 -0
- package/dist/backends/local-jsonl-export.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-export.test.js +704 -0
- package/dist/backends/local-jsonl-export.test.js.map +1 -0
- package/dist/backends/local-jsonl-index.test.d.ts +2 -0
- package/dist/backends/local-jsonl-index.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-index.test.js +554 -0
- package/dist/backends/local-jsonl-index.test.js.map +1 -0
- package/dist/backends/local-jsonl-logs.test.d.ts +2 -0
- package/dist/backends/local-jsonl-logs.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-logs.test.js +612 -0
- package/dist/backends/local-jsonl-logs.test.js.map +1 -0
- package/dist/backends/local-jsonl-metrics.test.d.ts +2 -0
- package/dist/backends/local-jsonl-metrics.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-metrics.test.js +876 -0
- package/dist/backends/local-jsonl-metrics.test.js.map +1 -0
- package/dist/backends/local-jsonl-traces.test.d.ts +2 -0
- package/dist/backends/local-jsonl-traces.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-traces.test.js +1729 -0
- package/dist/backends/local-jsonl-traces.test.js.map +1 -0
- package/dist/backends/local-jsonl.d.ts +9 -0
- package/dist/backends/local-jsonl.d.ts.map +1 -1
- package/dist/backends/local-jsonl.js +348 -227
- package/dist/backends/local-jsonl.js.map +1 -1
- package/dist/backends/local-jsonl.test.js +290 -21
- package/dist/backends/local-jsonl.test.js.map +1 -1
- package/dist/backends/signoz-api-circuit-breaker.test.d.ts +6 -0
- package/dist/backends/signoz-api-circuit-breaker.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-circuit-breaker.test.js +548 -0
- package/dist/backends/signoz-api-circuit-breaker.test.js.map +1 -0
- package/dist/backends/signoz-api-rate-limiter.test.d.ts +6 -0
- package/dist/backends/signoz-api-rate-limiter.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-rate-limiter.test.js +389 -0
- package/dist/backends/signoz-api-rate-limiter.test.js.map +1 -0
- package/dist/backends/signoz-api-ssrf.test.d.ts +6 -0
- package/dist/backends/signoz-api-ssrf.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-ssrf.test.js +216 -0
- package/dist/backends/signoz-api-ssrf.test.js.map +1 -0
- package/dist/backends/signoz-api-test-helpers.d.ts +80 -0
- package/dist/backends/signoz-api-test-helpers.d.ts.map +1 -0
- package/dist/backends/signoz-api-test-helpers.js +79 -0
- package/dist/backends/signoz-api-test-helpers.js.map +1 -0
- package/dist/backends/signoz-api.d.ts +16 -0
- package/dist/backends/signoz-api.d.ts.map +1 -1
- package/dist/backends/signoz-api.js +71 -9
- package/dist/backends/signoz-api.js.map +1 -1
- package/dist/backends/signoz-api.test.d.ts +9 -0
- package/dist/backends/signoz-api.test.d.ts.map +1 -1
- package/dist/backends/signoz-api.test.js +14 -1027
- package/dist/backends/signoz-api.test.js.map +1 -1
- package/dist/lib/cache.d.ts +47 -1
- package/dist/lib/cache.d.ts.map +1 -1
- package/dist/lib/cache.js +40 -3
- package/dist/lib/cache.js.map +1 -1
- package/dist/lib/circuit-breaker.d.ts +83 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.js +125 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/circuit-breaker.test.d.ts +2 -0
- package/dist/lib/circuit-breaker.test.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.test.js +263 -0
- package/dist/lib/circuit-breaker.test.js.map +1 -0
- package/dist/lib/constants-symlink.test.d.ts +12 -0
- package/dist/lib/constants-symlink.test.d.ts.map +1 -0
- package/dist/lib/constants-symlink.test.js +357 -0
- package/dist/lib/constants-symlink.test.js.map +1 -0
- package/dist/lib/constants.d.ts +43 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +154 -24
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/constants.test.js +156 -7
- package/dist/lib/constants.test.js.map +1 -1
- package/dist/lib/edge-cases.test.d.ts +11 -0
- package/dist/lib/edge-cases.test.d.ts.map +1 -0
- package/dist/lib/edge-cases.test.js +634 -0
- package/dist/lib/edge-cases.test.js.map +1 -0
- package/dist/lib/error-sanitizer.d.ts.map +1 -1
- package/dist/lib/error-sanitizer.js +62 -26
- package/dist/lib/error-sanitizer.js.map +1 -1
- package/dist/lib/error-sanitizer.test.js +186 -0
- package/dist/lib/error-sanitizer.test.js.map +1 -1
- package/dist/lib/error-types.d.ts +54 -0
- package/dist/lib/error-types.d.ts.map +1 -0
- package/dist/lib/error-types.js +154 -0
- package/dist/lib/error-types.js.map +1 -0
- package/dist/lib/error-types.test.d.ts +2 -0
- package/dist/lib/error-types.test.d.ts.map +1 -0
- package/dist/lib/error-types.test.js +196 -0
- package/dist/lib/error-types.test.js.map +1 -0
- package/dist/lib/file-utils.test.js +3 -3
- package/dist/lib/file-utils.test.js.map +1 -1
- package/dist/lib/indexer.test.js +157 -24
- package/dist/lib/indexer.test.js.map +1 -1
- package/dist/lib/input-validator.d.ts +17 -0
- package/dist/lib/input-validator.d.ts.map +1 -1
- package/dist/lib/input-validator.fuzz.test.d.ts +12 -0
- package/dist/lib/input-validator.fuzz.test.d.ts.map +1 -0
- package/dist/lib/input-validator.fuzz.test.js +290 -0
- package/dist/lib/input-validator.fuzz.test.js.map +1 -0
- package/dist/lib/input-validator.js +62 -3
- package/dist/lib/input-validator.js.map +1 -1
- package/dist/lib/input-validator.test.js +129 -1
- package/dist/lib/input-validator.test.js.map +1 -1
- package/dist/lib/logger.d.ts +46 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +81 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/logger.test.d.ts +2 -0
- package/dist/lib/logger.test.d.ts.map +1 -0
- package/dist/lib/logger.test.js +122 -0
- package/dist/lib/logger.test.js.map +1 -0
- package/dist/lib/query-sanitizer.d.ts +51 -3
- package/dist/lib/query-sanitizer.d.ts.map +1 -1
- package/dist/lib/query-sanitizer.js +105 -31
- package/dist/lib/query-sanitizer.js.map +1 -1
- package/dist/lib/query-sanitizer.test.js +102 -1
- package/dist/lib/query-sanitizer.test.js.map +1 -1
- package/dist/lib/server-utils.d.ts +88 -0
- package/dist/lib/server-utils.d.ts.map +1 -0
- package/dist/lib/server-utils.js +173 -0
- package/dist/lib/server-utils.js.map +1 -0
- package/dist/lib/shared-schemas.d.ts +81 -0
- package/dist/lib/shared-schemas.d.ts.map +1 -0
- package/dist/lib/shared-schemas.js +80 -0
- package/dist/lib/shared-schemas.js.map +1 -0
- package/dist/lib/shared-schemas.test.d.ts +5 -0
- package/dist/lib/shared-schemas.test.d.ts.map +1 -0
- package/dist/lib/shared-schemas.test.js +106 -0
- package/dist/lib/shared-schemas.test.js.map +1 -0
- package/dist/lib/toon-encoder.d.ts +26 -0
- package/dist/lib/toon-encoder.d.ts.map +1 -0
- package/dist/lib/toon-encoder.js +61 -0
- package/dist/lib/toon-encoder.js.map +1 -0
- package/dist/lib/toon-encoder.test.d.ts +5 -0
- package/dist/lib/toon-encoder.test.d.ts.map +1 -0
- package/dist/lib/toon-encoder.test.js +85 -0
- package/dist/lib/toon-encoder.test.js.map +1 -0
- package/dist/server.d.ts +1 -49
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +154 -162
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +198 -7
- package/dist/server.test.js.map +1 -1
- package/dist/test-helpers/env-utils.d.ts +87 -0
- package/dist/test-helpers/env-utils.d.ts.map +1 -0
- package/dist/test-helpers/env-utils.js +132 -0
- package/dist/test-helpers/env-utils.js.map +1 -0
- package/dist/test-helpers/file-utils.d.ts +67 -0
- package/dist/test-helpers/file-utils.d.ts.map +1 -1
- package/dist/test-helpers/file-utils.js +165 -2
- package/dist/test-helpers/file-utils.js.map +1 -1
- package/dist/test-helpers/fuzz-generators.d.ts +58 -0
- package/dist/test-helpers/fuzz-generators.d.ts.map +1 -0
- package/dist/test-helpers/fuzz-generators.js +216 -0
- package/dist/test-helpers/fuzz-generators.js.map +1 -0
- package/dist/test-helpers/index.d.ts +11 -0
- package/dist/test-helpers/index.d.ts.map +1 -0
- package/dist/test-helpers/index.js +30 -0
- package/dist/test-helpers/index.js.map +1 -0
- package/dist/test-helpers/memfs-utils.d.ts +181 -0
- package/dist/test-helpers/memfs-utils.d.ts.map +1 -0
- package/dist/test-helpers/memfs-utils.js +292 -0
- package/dist/test-helpers/memfs-utils.js.map +1 -0
- package/dist/test-helpers/memfs-utils.test.d.ts +5 -0
- package/dist/test-helpers/memfs-utils.test.d.ts.map +1 -0
- package/dist/test-helpers/memfs-utils.test.js +338 -0
- package/dist/test-helpers/memfs-utils.test.js.map +1 -0
- package/dist/test-helpers/mock-backends.d.ts +113 -2
- package/dist/test-helpers/mock-backends.d.ts.map +1 -1
- package/dist/test-helpers/mock-backends.js +199 -3
- package/dist/test-helpers/mock-backends.js.map +1 -1
- package/dist/test-helpers/mock-backends.test.d.ts +5 -0
- package/dist/test-helpers/mock-backends.test.d.ts.map +1 -0
- package/dist/test-helpers/mock-backends.test.js +368 -0
- package/dist/test-helpers/mock-backends.test.js.map +1 -0
- package/dist/test-helpers/race-condition-helpers.d.ts +85 -0
- package/dist/test-helpers/race-condition-helpers.d.ts.map +1 -0
- package/dist/test-helpers/race-condition-helpers.js +279 -0
- package/dist/test-helpers/race-condition-helpers.js.map +1 -0
- package/dist/test-helpers/schema-validators.d.ts +32 -0
- package/dist/test-helpers/schema-validators.d.ts.map +1 -0
- package/dist/test-helpers/schema-validators.js +125 -0
- package/dist/test-helpers/schema-validators.js.map +1 -0
- package/dist/test-helpers/test-data-builders.d.ts +260 -0
- package/dist/test-helpers/test-data-builders.d.ts.map +1 -0
- package/dist/test-helpers/test-data-builders.js +337 -0
- package/dist/test-helpers/test-data-builders.js.map +1 -0
- package/dist/test-helpers/test-data-builders.test.d.ts +2 -0
- package/dist/test-helpers/test-data-builders.test.d.ts.map +1 -0
- package/dist/test-helpers/test-data-builders.test.js +306 -0
- package/dist/test-helpers/test-data-builders.test.js.map +1 -0
- package/dist/test-helpers/tool-validators.d.ts +28 -0
- package/dist/test-helpers/tool-validators.d.ts.map +1 -0
- package/dist/test-helpers/tool-validators.js +71 -0
- package/dist/test-helpers/tool-validators.js.map +1 -0
- package/dist/tools/context-stats.d.ts +1 -0
- package/dist/tools/context-stats.d.ts.map +1 -1
- package/dist/tools/context-stats.js +9 -5
- package/dist/tools/context-stats.js.map +1 -1
- package/dist/tools/context-stats.test.js +24 -10
- package/dist/tools/context-stats.test.js.map +1 -1
- package/dist/tools/get-trace-url.js +2 -2
- package/dist/tools/get-trace-url.js.map +1 -1
- package/dist/tools/health-check.js +2 -2
- package/dist/tools/health-check.js.map +1 -1
- package/dist/tools/query-evaluations.d.ts +21 -18
- package/dist/tools/query-evaluations.d.ts.map +1 -1
- package/dist/tools/query-evaluations.js +33 -19
- package/dist/tools/query-evaluations.js.map +1 -1
- package/dist/tools/query-evaluations.test.js +60 -63
- package/dist/tools/query-evaluations.test.js.map +1 -1
- package/dist/tools/query-llm-events.d.ts +19 -15
- package/dist/tools/query-llm-events.d.ts.map +1 -1
- package/dist/tools/query-llm-events.js +31 -15
- package/dist/tools/query-llm-events.js.map +1 -1
- package/dist/tools/query-llm-events.test.js +277 -12
- package/dist/tools/query-llm-events.test.js.map +1 -1
- package/dist/tools/query-logs.d.ts +22 -22
- package/dist/tools/query-logs.d.ts.map +1 -1
- package/dist/tools/query-logs.js +9 -9
- package/dist/tools/query-logs.js.map +1 -1
- package/dist/tools/query-logs.test.js +19 -72
- package/dist/tools/query-logs.test.js.map +1 -1
- package/dist/tools/query-metrics.d.ts +14 -14
- package/dist/tools/query-metrics.d.ts.map +1 -1
- package/dist/tools/query-metrics.js +9 -9
- package/dist/tools/query-metrics.js.map +1 -1
- package/dist/tools/query-metrics.test.js +12 -25
- package/dist/tools/query-metrics.test.js.map +1 -1
- package/dist/tools/query-traces.d.ts +28 -28
- package/dist/tools/query-traces.d.ts.map +1 -1
- package/dist/tools/query-traces.js +18 -18
- package/dist/tools/query-traces.js.map +1 -1
- package/dist/tools/query-traces.test.js +58 -54
- package/dist/tools/query-traces.test.js.map +1 -1
- package/dist/tools/setup-claudeignore.js +7 -7
- package/dist/tools/setup-claudeignore.js.map +1 -1
- package/dist/tools/setup-claudeignore.test.js +4 -25
- package/dist/tools/setup-claudeignore.test.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
2
|
+
import * as assert from 'node:assert';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { LocalJsonlBackend } from './local-jsonl.js';
|
|
5
|
+
import { getSharedTempDir, clearTempDir, removeSharedTempDir, writeJsonlFileAsync, getTestDate } from '../test-helpers/file-utils.js';
|
|
6
|
+
import { createMockMetric, createMockMetrics, resetBuilderCounters, } from '../test-helpers/test-data-builders.js';
|
|
7
|
+
describe('LocalJsonlBackend', () => {
|
|
8
|
+
let tempDir;
|
|
9
|
+
let backend;
|
|
10
|
+
before(() => {
|
|
11
|
+
tempDir = getSharedTempDir('LocalJsonlBackend-Metrics');
|
|
12
|
+
});
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
clearTempDir(tempDir);
|
|
15
|
+
backend = new LocalJsonlBackend(tempDir);
|
|
16
|
+
});
|
|
17
|
+
after(() => {
|
|
18
|
+
removeSharedTempDir('LocalJsonlBackend-Metrics');
|
|
19
|
+
});
|
|
20
|
+
describe('queryMetrics', () => {
|
|
21
|
+
it('should read and normalize metric data points from JSONL files', async () => {
|
|
22
|
+
const today = getTestDate();
|
|
23
|
+
resetBuilderCounters();
|
|
24
|
+
const mockMetrics = [
|
|
25
|
+
createMockMetric({
|
|
26
|
+
name: 'http.requests.total',
|
|
27
|
+
value: 100,
|
|
28
|
+
type: 'counter',
|
|
29
|
+
unit: 'requests',
|
|
30
|
+
resource: { serviceName: 'api-gateway' },
|
|
31
|
+
attributes: { 'http.method': 'GET', 'http.status_code': 200 },
|
|
32
|
+
}),
|
|
33
|
+
];
|
|
34
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
35
|
+
const results = await backend.queryMetrics({});
|
|
36
|
+
assert.strictEqual(results.length, 1);
|
|
37
|
+
assert.strictEqual(results[0].name, 'http.requests.total');
|
|
38
|
+
assert.strictEqual(results[0].value, 100);
|
|
39
|
+
assert.strictEqual(results[0].unit, 'requests');
|
|
40
|
+
assert.strictEqual(results[0].attributes?.['service.name'], 'api-gateway');
|
|
41
|
+
assert.strictEqual(results[0].attributes?.['http.method'], 'GET');
|
|
42
|
+
});
|
|
43
|
+
it('should filter metrics by name substring', async () => {
|
|
44
|
+
const today = getTestDate();
|
|
45
|
+
resetBuilderCounters();
|
|
46
|
+
const mockMetrics = [
|
|
47
|
+
createMockMetric({ name: 'http.requests.total', value: 100, type: 'counter' }),
|
|
48
|
+
createMockMetric({ name: 'http.request.duration', value: 150, type: 'histogram' }),
|
|
49
|
+
createMockMetric({ name: 'memory.usage', value: 512, type: 'gauge' }),
|
|
50
|
+
];
|
|
51
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
52
|
+
const results = await backend.queryMetrics({ metricName: 'http' });
|
|
53
|
+
assert.strictEqual(results.length, 2);
|
|
54
|
+
assert.ok(results.every(m => m.name.includes('http')));
|
|
55
|
+
});
|
|
56
|
+
it('should apply limit and offset to metric results', async () => {
|
|
57
|
+
const today = getTestDate();
|
|
58
|
+
resetBuilderCounters();
|
|
59
|
+
const mockMetrics = createMockMetrics(150, (i) => ({
|
|
60
|
+
timestamp: new Date(Date.now() + i * 1000).toISOString(),
|
|
61
|
+
name: `metric.${i}`,
|
|
62
|
+
value: i * 10,
|
|
63
|
+
type: 'gauge',
|
|
64
|
+
}));
|
|
65
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
66
|
+
const results = await backend.queryMetrics({ limit: 50, offset: 30 });
|
|
67
|
+
assert.strictEqual(results.length, 50);
|
|
68
|
+
assert.strictEqual(results[0].name, 'metric.30');
|
|
69
|
+
});
|
|
70
|
+
it('should aggregate metrics with sum function', async () => {
|
|
71
|
+
const today = getTestDate();
|
|
72
|
+
resetBuilderCounters();
|
|
73
|
+
const mockMetrics = [
|
|
74
|
+
createMockMetric({ name: 'requests', value: 100, type: 'counter' }),
|
|
75
|
+
createMockMetric({ name: 'requests', value: 150, type: 'counter' }),
|
|
76
|
+
createMockMetric({ name: 'requests', value: 200, type: 'counter' }),
|
|
77
|
+
];
|
|
78
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
79
|
+
const results = await backend.queryMetrics({ aggregation: 'sum' });
|
|
80
|
+
assert.strictEqual(results.length, 1);
|
|
81
|
+
assert.strictEqual(results[0].value, 450);
|
|
82
|
+
});
|
|
83
|
+
it('should aggregate metrics with avg function', async () => {
|
|
84
|
+
const today = getTestDate();
|
|
85
|
+
resetBuilderCounters();
|
|
86
|
+
const mockMetrics = [
|
|
87
|
+
createMockMetric({ name: 'latency', value: 100, type: 'histogram' }),
|
|
88
|
+
createMockMetric({ name: 'latency', value: 200, type: 'histogram' }),
|
|
89
|
+
createMockMetric({ name: 'latency', value: 300, type: 'histogram' }),
|
|
90
|
+
];
|
|
91
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
92
|
+
const results = await backend.queryMetrics({ aggregation: 'avg' });
|
|
93
|
+
assert.strictEqual(results.length, 1);
|
|
94
|
+
assert.strictEqual(results[0].value, 200);
|
|
95
|
+
});
|
|
96
|
+
it('should aggregate metrics with min function', async () => {
|
|
97
|
+
const today = getTestDate();
|
|
98
|
+
resetBuilderCounters();
|
|
99
|
+
const mockMetrics = [
|
|
100
|
+
createMockMetric({ name: 'response_time', value: 150, type: 'gauge' }),
|
|
101
|
+
createMockMetric({ name: 'response_time', value: 50, type: 'gauge' }),
|
|
102
|
+
createMockMetric({ name: 'response_time', value: 200, type: 'gauge' }),
|
|
103
|
+
];
|
|
104
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
105
|
+
const results = await backend.queryMetrics({ aggregation: 'min' });
|
|
106
|
+
assert.strictEqual(results.length, 1);
|
|
107
|
+
assert.strictEqual(results[0].value, 50);
|
|
108
|
+
});
|
|
109
|
+
it('should aggregate metrics with max function', async () => {
|
|
110
|
+
const today = getTestDate();
|
|
111
|
+
resetBuilderCounters();
|
|
112
|
+
const mockMetrics = [
|
|
113
|
+
createMockMetric({ name: 'memory', value: 512, type: 'gauge' }),
|
|
114
|
+
createMockMetric({ name: 'memory', value: 256, type: 'gauge' }),
|
|
115
|
+
createMockMetric({ name: 'memory', value: 1024, type: 'gauge' }),
|
|
116
|
+
];
|
|
117
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
118
|
+
const results = await backend.queryMetrics({ aggregation: 'max' });
|
|
119
|
+
assert.strictEqual(results.length, 1);
|
|
120
|
+
assert.strictEqual(results[0].value, 1024);
|
|
121
|
+
});
|
|
122
|
+
it('should aggregate metrics with count function', async () => {
|
|
123
|
+
const today = getTestDate();
|
|
124
|
+
resetBuilderCounters();
|
|
125
|
+
const mockMetrics = [
|
|
126
|
+
createMockMetric({ name: 'events', value: 10, type: 'counter' }),
|
|
127
|
+
createMockMetric({ name: 'events', value: 20, type: 'counter' }),
|
|
128
|
+
createMockMetric({ name: 'events', value: 30, type: 'counter' }),
|
|
129
|
+
];
|
|
130
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
131
|
+
const results = await backend.queryMetrics({ aggregation: 'count' });
|
|
132
|
+
assert.strictEqual(results.length, 1);
|
|
133
|
+
assert.strictEqual(results[0].value, 3);
|
|
134
|
+
});
|
|
135
|
+
it('should aggregate metrics with p50 (median) function', async () => {
|
|
136
|
+
const today = getTestDate();
|
|
137
|
+
resetBuilderCounters();
|
|
138
|
+
const mockMetrics = [
|
|
139
|
+
createMockMetric({ name: 'latency', value: 10, type: 'histogram' }),
|
|
140
|
+
createMockMetric({ name: 'latency', value: 20, type: 'histogram' }),
|
|
141
|
+
createMockMetric({ name: 'latency', value: 30, type: 'histogram' }),
|
|
142
|
+
createMockMetric({ name: 'latency', value: 40, type: 'histogram' }),
|
|
143
|
+
createMockMetric({ name: 'latency', value: 50, type: 'histogram' }),
|
|
144
|
+
];
|
|
145
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
146
|
+
const results = await backend.queryMetrics({ aggregation: 'p50' });
|
|
147
|
+
assert.strictEqual(results.length, 1);
|
|
148
|
+
assert.strictEqual(results[0].value, 30); // median of [10, 20, 30, 40, 50]
|
|
149
|
+
});
|
|
150
|
+
it('should aggregate metrics with p95 function', async () => {
|
|
151
|
+
const today = getTestDate();
|
|
152
|
+
resetBuilderCounters();
|
|
153
|
+
// Create 100 data points for a more realistic p95 calculation
|
|
154
|
+
const mockMetrics = createMockMetrics(100, (i) => ({
|
|
155
|
+
timestamp: `2026-01-28T10:${String(Math.floor(i / 60)).padStart(2, '0')}:${String(i % 60).padStart(2, '0')}Z`,
|
|
156
|
+
name: 'response_time',
|
|
157
|
+
value: i + 1, // values 1-100
|
|
158
|
+
type: 'histogram',
|
|
159
|
+
}));
|
|
160
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
161
|
+
const results = await backend.queryMetrics({ aggregation: 'p95' });
|
|
162
|
+
assert.strictEqual(results.length, 1);
|
|
163
|
+
assert.strictEqual(results[0].value, 95); // 95th percentile of 1-100
|
|
164
|
+
});
|
|
165
|
+
it('should aggregate metrics with p99 function', async () => {
|
|
166
|
+
const today = getTestDate();
|
|
167
|
+
resetBuilderCounters();
|
|
168
|
+
// Create 100 data points for a more realistic p99 calculation
|
|
169
|
+
const mockMetrics = createMockMetrics(100, (i) => ({
|
|
170
|
+
timestamp: `2026-01-28T10:${String(Math.floor(i / 60)).padStart(2, '0')}:${String(i % 60).padStart(2, '0')}Z`,
|
|
171
|
+
name: 'response_time',
|
|
172
|
+
value: i + 1, // values 1-100
|
|
173
|
+
type: 'histogram',
|
|
174
|
+
}));
|
|
175
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
176
|
+
const results = await backend.queryMetrics({ aggregation: 'p99' });
|
|
177
|
+
assert.strictEqual(results.length, 1);
|
|
178
|
+
assert.strictEqual(results[0].value, 99); // 99th percentile of 1-100
|
|
179
|
+
});
|
|
180
|
+
it('should handle p50 with even number of values', async () => {
|
|
181
|
+
const today = getTestDate();
|
|
182
|
+
resetBuilderCounters();
|
|
183
|
+
const mockMetrics = [
|
|
184
|
+
createMockMetric({ name: 'latency', value: 10, type: 'histogram' }),
|
|
185
|
+
createMockMetric({ name: 'latency', value: 20, type: 'histogram' }),
|
|
186
|
+
createMockMetric({ name: 'latency', value: 30, type: 'histogram' }),
|
|
187
|
+
createMockMetric({ name: 'latency', value: 40, type: 'histogram' }),
|
|
188
|
+
];
|
|
189
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
190
|
+
const results = await backend.queryMetrics({ aggregation: 'p50' });
|
|
191
|
+
assert.strictEqual(results.length, 1);
|
|
192
|
+
assert.strictEqual(results[0].value, 20); // ceil(0.5 * 4) - 1 = 1, sorted[1] = 20
|
|
193
|
+
});
|
|
194
|
+
it('should handle percentile with single value', async () => {
|
|
195
|
+
const today = getTestDate();
|
|
196
|
+
resetBuilderCounters();
|
|
197
|
+
const mockMetrics = [
|
|
198
|
+
createMockMetric({ name: 'latency', value: 42, type: 'histogram' }),
|
|
199
|
+
];
|
|
200
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
201
|
+
const results = await backend.queryMetrics({ aggregation: 'p95' });
|
|
202
|
+
assert.strictEqual(results.length, 1);
|
|
203
|
+
assert.strictEqual(results[0].value, 42); // single value is the only percentile
|
|
204
|
+
});
|
|
205
|
+
it('should calculate percentiles with groupBy', async () => {
|
|
206
|
+
const today = getTestDate();
|
|
207
|
+
resetBuilderCounters();
|
|
208
|
+
const mockMetrics = [
|
|
209
|
+
createMockMetric({ name: 'latency', value: 10, type: 'histogram', attributes: { endpoint: '/api/users' } }),
|
|
210
|
+
createMockMetric({ name: 'latency', value: 20, type: 'histogram', attributes: { endpoint: '/api/users' } }),
|
|
211
|
+
createMockMetric({ name: 'latency', value: 30, type: 'histogram', attributes: { endpoint: '/api/users' } }),
|
|
212
|
+
createMockMetric({ name: 'latency', value: 100, type: 'histogram', attributes: { endpoint: '/api/orders' } }),
|
|
213
|
+
createMockMetric({ name: 'latency', value: 200, type: 'histogram', attributes: { endpoint: '/api/orders' } }),
|
|
214
|
+
createMockMetric({ name: 'latency', value: 300, type: 'histogram', attributes: { endpoint: '/api/orders' } }),
|
|
215
|
+
];
|
|
216
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
217
|
+
const results = await backend.queryMetrics({ aggregation: 'p50', groupBy: ['endpoint'] });
|
|
218
|
+
assert.strictEqual(results.length, 2);
|
|
219
|
+
const usersMetric = results.find(m => m.attributes?.endpoint === '/api/users');
|
|
220
|
+
const ordersMetric = results.find(m => m.attributes?.endpoint === '/api/orders');
|
|
221
|
+
assert.strictEqual(usersMetric?.value, 20); // median of [10, 20, 30]
|
|
222
|
+
assert.strictEqual(ordersMetric?.value, 200); // median of [100, 200, 300]
|
|
223
|
+
});
|
|
224
|
+
it('should aggregate metrics grouped by attributes', async () => {
|
|
225
|
+
const today = getTestDate();
|
|
226
|
+
resetBuilderCounters();
|
|
227
|
+
const mockMetrics = [
|
|
228
|
+
createMockMetric({ name: 'http.requests', value: 100, type: 'counter', attributes: { method: 'GET' } }),
|
|
229
|
+
createMockMetric({ name: 'http.requests', value: 50, type: 'counter', attributes: { method: 'POST' } }),
|
|
230
|
+
createMockMetric({ name: 'http.requests', value: 200, type: 'counter', attributes: { method: 'GET' } }),
|
|
231
|
+
];
|
|
232
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
233
|
+
const results = await backend.queryMetrics({ aggregation: 'sum', groupBy: ['method'] });
|
|
234
|
+
assert.strictEqual(results.length, 2);
|
|
235
|
+
const getMetric = results.find(m => m.attributes?.method === 'GET');
|
|
236
|
+
const postMetric = results.find(m => m.attributes?.method === 'POST');
|
|
237
|
+
assert.strictEqual(getMetric?.value, 300);
|
|
238
|
+
assert.strictEqual(postMetric?.value, 50);
|
|
239
|
+
});
|
|
240
|
+
it('should return empty array when no metrics found', async () => {
|
|
241
|
+
// No files created
|
|
242
|
+
const results = await backend.queryMetrics({});
|
|
243
|
+
assert.strictEqual(results.length, 0);
|
|
244
|
+
});
|
|
245
|
+
it('should aggregate metrics by time bucket with 1m buckets', async () => {
|
|
246
|
+
const today = getTestDate();
|
|
247
|
+
const mockMetrics = [
|
|
248
|
+
// First minute bucket: 10:00:00 - 10:00:59
|
|
249
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 10, type: 'counter' },
|
|
250
|
+
{ timestamp: `${today}T10:00:30Z`, name: 'requests', value: 20, type: 'counter' },
|
|
251
|
+
{ timestamp: `${today}T10:00:45Z`, name: 'requests', value: 30, type: 'counter' },
|
|
252
|
+
// Second minute bucket: 10:01:00 - 10:01:59
|
|
253
|
+
{ timestamp: `${today}T10:01:00Z`, name: 'requests', value: 100, type: 'counter' },
|
|
254
|
+
{ timestamp: `${today}T10:01:30Z`, name: 'requests', value: 200, type: 'counter' },
|
|
255
|
+
// Third minute bucket: 10:02:00 - 10:02:59
|
|
256
|
+
{ timestamp: `${today}T10:02:15Z`, name: 'requests', value: 50, type: 'counter' },
|
|
257
|
+
];
|
|
258
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
259
|
+
const results = await backend.queryMetrics({ aggregation: 'sum', timeBucket: '1m' });
|
|
260
|
+
assert.strictEqual(results.length, 3);
|
|
261
|
+
// Results should be sorted by timestamp
|
|
262
|
+
assert.strictEqual(results[0].value, 60); // 10 + 20 + 30
|
|
263
|
+
assert.strictEqual(results[1].value, 300); // 100 + 200
|
|
264
|
+
assert.strictEqual(results[2].value, 50); // 50
|
|
265
|
+
// Timestamps should be floored to bucket boundaries
|
|
266
|
+
assert.strictEqual(results[0].timestamp, `${today}T10:00:00.000Z`);
|
|
267
|
+
assert.strictEqual(results[1].timestamp, `${today}T10:01:00.000Z`);
|
|
268
|
+
assert.strictEqual(results[2].timestamp, `${today}T10:02:00.000Z`);
|
|
269
|
+
});
|
|
270
|
+
it('should aggregate metrics by time bucket with 5m buckets', async () => {
|
|
271
|
+
const today = getTestDate();
|
|
272
|
+
const mockMetrics = [
|
|
273
|
+
// First 5-minute bucket: 10:00:00 - 10:04:59
|
|
274
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 10, type: 'counter' },
|
|
275
|
+
{ timestamp: `${today}T10:02:00Z`, name: 'requests', value: 20, type: 'counter' },
|
|
276
|
+
{ timestamp: `${today}T10:04:00Z`, name: 'requests', value: 30, type: 'counter' },
|
|
277
|
+
// Second 5-minute bucket: 10:05:00 - 10:09:59
|
|
278
|
+
{ timestamp: `${today}T10:05:00Z`, name: 'requests', value: 100, type: 'counter' },
|
|
279
|
+
{ timestamp: `${today}T10:08:00Z`, name: 'requests', value: 200, type: 'counter' },
|
|
280
|
+
];
|
|
281
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
282
|
+
const results = await backend.queryMetrics({ aggregation: 'sum', timeBucket: '5m' });
|
|
283
|
+
assert.strictEqual(results.length, 2);
|
|
284
|
+
assert.strictEqual(results[0].value, 60); // 10 + 20 + 30
|
|
285
|
+
assert.strictEqual(results[1].value, 300); // 100 + 200
|
|
286
|
+
});
|
|
287
|
+
it('should aggregate metrics by time bucket with 1h buckets', async () => {
|
|
288
|
+
const today = getTestDate();
|
|
289
|
+
const mockMetrics = [
|
|
290
|
+
// First hour bucket: 10:00:00 - 10:59:59
|
|
291
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 10, type: 'counter' },
|
|
292
|
+
{ timestamp: `${today}T10:30:00Z`, name: 'requests', value: 20, type: 'counter' },
|
|
293
|
+
// Second hour bucket: 11:00:00 - 11:59:59
|
|
294
|
+
{ timestamp: `${today}T11:00:00Z`, name: 'requests', value: 100, type: 'counter' },
|
|
295
|
+
{ timestamp: `${today}T11:45:00Z`, name: 'requests', value: 200, type: 'counter' },
|
|
296
|
+
];
|
|
297
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
298
|
+
const results = await backend.queryMetrics({ aggregation: 'sum', timeBucket: '1h' });
|
|
299
|
+
assert.strictEqual(results.length, 2);
|
|
300
|
+
assert.strictEqual(results[0].value, 30); // 10 + 20
|
|
301
|
+
assert.strictEqual(results[1].value, 300); // 100 + 200
|
|
302
|
+
});
|
|
303
|
+
it('should aggregate metrics by time bucket with 1d buckets', async () => {
|
|
304
|
+
// Create metrics files for two days
|
|
305
|
+
await writeJsonlFileAsync(path.join(tempDir, 'metrics-2026-01-28.jsonl'), [
|
|
306
|
+
{ timestamp: '2026-01-28T10:00:00Z', name: 'requests', value: 100, type: 'counter' },
|
|
307
|
+
{ timestamp: '2026-01-28T20:00:00Z', name: 'requests', value: 200, type: 'counter' },
|
|
308
|
+
]);
|
|
309
|
+
await writeJsonlFileAsync(path.join(tempDir, 'metrics-2026-01-29.jsonl'), [
|
|
310
|
+
{ timestamp: '2026-01-29T08:00:00Z', name: 'requests', value: 300, type: 'counter' },
|
|
311
|
+
{ timestamp: '2026-01-29T16:00:00Z', name: 'requests', value: 400, type: 'counter' },
|
|
312
|
+
]);
|
|
313
|
+
const results = await backend.queryMetrics({
|
|
314
|
+
aggregation: 'sum',
|
|
315
|
+
timeBucket: '1d',
|
|
316
|
+
startDate: '2026-01-28',
|
|
317
|
+
endDate: '2026-01-29',
|
|
318
|
+
});
|
|
319
|
+
assert.strictEqual(results.length, 2);
|
|
320
|
+
assert.strictEqual(results[0].value, 300); // 100 + 200
|
|
321
|
+
assert.strictEqual(results[1].value, 700); // 300 + 400
|
|
322
|
+
});
|
|
323
|
+
it('should combine time bucket with groupBy', async () => {
|
|
324
|
+
const today = getTestDate();
|
|
325
|
+
const mockMetrics = [
|
|
326
|
+
// First minute, method=GET
|
|
327
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 10, type: 'counter', attributes: { method: 'GET' } },
|
|
328
|
+
{ timestamp: `${today}T10:00:30Z`, name: 'requests', value: 20, type: 'counter', attributes: { method: 'GET' } },
|
|
329
|
+
// First minute, method=POST
|
|
330
|
+
{ timestamp: `${today}T10:00:15Z`, name: 'requests', value: 5, type: 'counter', attributes: { method: 'POST' } },
|
|
331
|
+
// Second minute, method=GET
|
|
332
|
+
{ timestamp: `${today}T10:01:00Z`, name: 'requests', value: 100, type: 'counter', attributes: { method: 'GET' } },
|
|
333
|
+
// Second minute, method=POST
|
|
334
|
+
{ timestamp: `${today}T10:01:30Z`, name: 'requests', value: 50, type: 'counter', attributes: { method: 'POST' } },
|
|
335
|
+
];
|
|
336
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
337
|
+
const results = await backend.queryMetrics({
|
|
338
|
+
aggregation: 'sum',
|
|
339
|
+
timeBucket: '1m',
|
|
340
|
+
groupBy: ['method'],
|
|
341
|
+
});
|
|
342
|
+
assert.strictEqual(results.length, 4);
|
|
343
|
+
// First bucket GET
|
|
344
|
+
const bucket1GET = results.find(m => m.timestamp === `${today}T10:00:00.000Z` && m.attributes?.method === 'GET');
|
|
345
|
+
assert.strictEqual(bucket1GET?.value, 30); // 10 + 20
|
|
346
|
+
// First bucket POST
|
|
347
|
+
const bucket1POST = results.find(m => m.timestamp === `${today}T10:00:00.000Z` && m.attributes?.method === 'POST');
|
|
348
|
+
assert.strictEqual(bucket1POST?.value, 5);
|
|
349
|
+
// Second bucket GET
|
|
350
|
+
const bucket2GET = results.find(m => m.timestamp === `${today}T10:01:00.000Z` && m.attributes?.method === 'GET');
|
|
351
|
+
assert.strictEqual(bucket2GET?.value, 100);
|
|
352
|
+
// Second bucket POST
|
|
353
|
+
const bucket2POST = results.find(m => m.timestamp === `${today}T10:01:00.000Z` && m.attributes?.method === 'POST');
|
|
354
|
+
assert.strictEqual(bucket2POST?.value, 50);
|
|
355
|
+
});
|
|
356
|
+
it('should use avg aggregation with time buckets', async () => {
|
|
357
|
+
const today = getTestDate();
|
|
358
|
+
const mockMetrics = [
|
|
359
|
+
// First minute bucket
|
|
360
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'latency', value: 100, type: 'histogram' },
|
|
361
|
+
{ timestamp: `${today}T10:00:30Z`, name: 'latency', value: 200, type: 'histogram' },
|
|
362
|
+
{ timestamp: `${today}T10:00:45Z`, name: 'latency', value: 300, type: 'histogram' },
|
|
363
|
+
// Second minute bucket
|
|
364
|
+
{ timestamp: `${today}T10:01:00Z`, name: 'latency', value: 500, type: 'histogram' },
|
|
365
|
+
{ timestamp: `${today}T10:01:30Z`, name: 'latency', value: 700, type: 'histogram' },
|
|
366
|
+
];
|
|
367
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
368
|
+
const results = await backend.queryMetrics({ aggregation: 'avg', timeBucket: '1m' });
|
|
369
|
+
assert.strictEqual(results.length, 2);
|
|
370
|
+
assert.strictEqual(results[0].value, 200); // (100 + 200 + 300) / 3
|
|
371
|
+
assert.strictEqual(results[1].value, 600); // (500 + 700) / 2
|
|
372
|
+
});
|
|
373
|
+
it('should ignore invalid time bucket format', async () => {
|
|
374
|
+
const today = getTestDate();
|
|
375
|
+
const mockMetrics = [
|
|
376
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 10, type: 'counter' },
|
|
377
|
+
{ timestamp: `${today}T10:00:30Z`, name: 'requests', value: 20, type: 'counter' },
|
|
378
|
+
{ timestamp: `${today}T10:01:00Z`, name: 'requests', value: 30, type: 'counter' },
|
|
379
|
+
];
|
|
380
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
381
|
+
// Invalid format - should be ignored and aggregate all together
|
|
382
|
+
const results = await backend.queryMetrics({ aggregation: 'sum', timeBucket: 'invalid' });
|
|
383
|
+
assert.strictEqual(results.length, 1);
|
|
384
|
+
assert.strictEqual(results[0].value, 60); // All grouped together
|
|
385
|
+
});
|
|
386
|
+
it('should calculate rate of change per second', async () => {
|
|
387
|
+
const today = getTestDate();
|
|
388
|
+
const mockMetrics = [
|
|
389
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 100, type: 'counter' },
|
|
390
|
+
{ timestamp: `${today}T10:00:30Z`, name: 'requests', value: 200, type: 'counter' },
|
|
391
|
+
{ timestamp: `${today}T10:01:00Z`, name: 'requests', value: 400, type: 'counter' },
|
|
392
|
+
];
|
|
393
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
394
|
+
const results = await backend.queryMetrics({ aggregation: 'rate' });
|
|
395
|
+
assert.strictEqual(results.length, 1);
|
|
396
|
+
// Rate = (400 - 100) / 60 seconds = 5 per second
|
|
397
|
+
assert.strictEqual(results[0].value, 5);
|
|
398
|
+
});
|
|
399
|
+
it('should calculate rate with timeBucket', async () => {
|
|
400
|
+
const today = getTestDate();
|
|
401
|
+
const mockMetrics = [
|
|
402
|
+
// First minute bucket: 10:00:00 - 10:00:59
|
|
403
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 0, type: 'counter' },
|
|
404
|
+
{ timestamp: `${today}T10:00:30Z`, name: 'requests', value: 60, type: 'counter' },
|
|
405
|
+
// Second minute bucket: 10:01:00 - 10:01:59
|
|
406
|
+
{ timestamp: `${today}T10:01:00Z`, name: 'requests', value: 100, type: 'counter' },
|
|
407
|
+
{ timestamp: `${today}T10:01:30Z`, name: 'requests', value: 250, type: 'counter' },
|
|
408
|
+
];
|
|
409
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
410
|
+
const results = await backend.queryMetrics({ aggregation: 'rate', timeBucket: '1m' });
|
|
411
|
+
assert.strictEqual(results.length, 2);
|
|
412
|
+
// First bucket: (60 - 0) / 30 seconds = 2 per second
|
|
413
|
+
assert.strictEqual(results[0].value, 2);
|
|
414
|
+
// Second bucket: (250 - 100) / 30 seconds = 5 per second
|
|
415
|
+
assert.strictEqual(results[1].value, 5);
|
|
416
|
+
});
|
|
417
|
+
it('should return rate of 0 for single data point', async () => {
|
|
418
|
+
const today = getTestDate();
|
|
419
|
+
const mockMetrics = [
|
|
420
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 100, type: 'counter' },
|
|
421
|
+
];
|
|
422
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
423
|
+
const results = await backend.queryMetrics({ aggregation: 'rate' });
|
|
424
|
+
assert.strictEqual(results.length, 1);
|
|
425
|
+
assert.strictEqual(results[0].value, 0);
|
|
426
|
+
});
|
|
427
|
+
it('should return rate of 0 when all timestamps are the same', async () => {
|
|
428
|
+
const today = getTestDate();
|
|
429
|
+
const mockMetrics = [
|
|
430
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 100, type: 'counter' },
|
|
431
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 200, type: 'counter' },
|
|
432
|
+
{ timestamp: `${today}T10:00:00Z`, name: 'requests', value: 300, type: 'counter' },
|
|
433
|
+
];
|
|
434
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
435
|
+
const results = await backend.queryMetrics({ aggregation: 'rate' });
|
|
436
|
+
assert.strictEqual(results.length, 1);
|
|
437
|
+
// Avoid division by zero - return 0 when duration is 0
|
|
438
|
+
assert.strictEqual(results[0].value, 0);
|
|
439
|
+
});
|
|
440
|
+
it('should read and normalize histogram metrics with bucket distribution', async () => {
|
|
441
|
+
const today = getTestDate();
|
|
442
|
+
const mockMetrics = [
|
|
443
|
+
{
|
|
444
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
445
|
+
name: 'http.request.duration',
|
|
446
|
+
value: 150, // typically the sum/count average or similar aggregate
|
|
447
|
+
type: 'histogram',
|
|
448
|
+
unit: 'ms',
|
|
449
|
+
resource: { serviceName: 'api-gateway' },
|
|
450
|
+
attributes: { 'http.method': 'GET' },
|
|
451
|
+
histogram: {
|
|
452
|
+
buckets: [
|
|
453
|
+
{ le: 50, count: 10 },
|
|
454
|
+
{ le: 100, count: 25 },
|
|
455
|
+
{ le: 250, count: 45 },
|
|
456
|
+
{ le: 500, count: 48 },
|
|
457
|
+
{ le: Infinity, count: 50 },
|
|
458
|
+
],
|
|
459
|
+
sum: 7500,
|
|
460
|
+
count: 50,
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
];
|
|
464
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
465
|
+
const results = await backend.queryMetrics({});
|
|
466
|
+
assert.strictEqual(results.length, 1);
|
|
467
|
+
assert.strictEqual(results[0].name, 'http.request.duration');
|
|
468
|
+
assert.strictEqual(results[0].value, 150);
|
|
469
|
+
assert.strictEqual(results[0].unit, 'ms');
|
|
470
|
+
assert.strictEqual(results[0].attributes?.['service.name'], 'api-gateway');
|
|
471
|
+
assert.strictEqual(results[0].attributes?.['http.method'], 'GET');
|
|
472
|
+
// Verify histogram data is present
|
|
473
|
+
assert.ok(results[0].histogram, 'Histogram data should be present');
|
|
474
|
+
assert.strictEqual(results[0].histogram?.sum, 7500);
|
|
475
|
+
assert.strictEqual(results[0].histogram?.count, 50);
|
|
476
|
+
assert.strictEqual(results[0].histogram?.buckets.length, 5);
|
|
477
|
+
// Verify bucket boundaries and cumulative counts
|
|
478
|
+
assert.strictEqual(results[0].histogram?.buckets[0].le, 50);
|
|
479
|
+
assert.strictEqual(results[0].histogram?.buckets[0].count, 10);
|
|
480
|
+
assert.strictEqual(results[0].histogram?.buckets[1].le, 100);
|
|
481
|
+
assert.strictEqual(results[0].histogram?.buckets[1].count, 25);
|
|
482
|
+
assert.strictEqual(results[0].histogram?.buckets[2].le, 250);
|
|
483
|
+
assert.strictEqual(results[0].histogram?.buckets[2].count, 45);
|
|
484
|
+
});
|
|
485
|
+
it('should handle histogram metrics without histogram data (non-histogram type)', async () => {
|
|
486
|
+
const today = getTestDate();
|
|
487
|
+
const mockMetrics = [
|
|
488
|
+
{
|
|
489
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
490
|
+
name: 'http.requests.total',
|
|
491
|
+
value: 100,
|
|
492
|
+
type: 'counter',
|
|
493
|
+
unit: 'requests',
|
|
494
|
+
},
|
|
495
|
+
];
|
|
496
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
497
|
+
const results = await backend.queryMetrics({});
|
|
498
|
+
assert.strictEqual(results.length, 1);
|
|
499
|
+
assert.strictEqual(results[0].name, 'http.requests.total');
|
|
500
|
+
assert.strictEqual(results[0].value, 100);
|
|
501
|
+
assert.strictEqual(results[0].histogram, undefined);
|
|
502
|
+
});
|
|
503
|
+
it('should handle mixed metric types including histograms', async () => {
|
|
504
|
+
const today = getTestDate();
|
|
505
|
+
const mockMetrics = [
|
|
506
|
+
{
|
|
507
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
508
|
+
name: 'http.requests.total',
|
|
509
|
+
value: 100,
|
|
510
|
+
type: 'counter',
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
514
|
+
name: 'http.request.duration',
|
|
515
|
+
value: 150,
|
|
516
|
+
type: 'histogram',
|
|
517
|
+
histogram: {
|
|
518
|
+
buckets: [
|
|
519
|
+
{ le: 100, count: 20 },
|
|
520
|
+
{ le: 500, count: 80 },
|
|
521
|
+
{ le: Infinity, count: 100 },
|
|
522
|
+
],
|
|
523
|
+
sum: 15000,
|
|
524
|
+
count: 100,
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
529
|
+
name: 'memory.usage',
|
|
530
|
+
value: 512,
|
|
531
|
+
type: 'gauge',
|
|
532
|
+
},
|
|
533
|
+
];
|
|
534
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
535
|
+
const results = await backend.queryMetrics({});
|
|
536
|
+
assert.strictEqual(results.length, 3);
|
|
537
|
+
const counter = results.find(m => m.name === 'http.requests.total');
|
|
538
|
+
const histogram = results.find(m => m.name === 'http.request.duration');
|
|
539
|
+
const gauge = results.find(m => m.name === 'memory.usage');
|
|
540
|
+
assert.ok(counter, 'Counter metric should be present');
|
|
541
|
+
assert.strictEqual(counter?.histogram, undefined);
|
|
542
|
+
assert.ok(histogram, 'Histogram metric should be present');
|
|
543
|
+
assert.ok(histogram?.histogram, 'Histogram should have histogram data');
|
|
544
|
+
assert.strictEqual(histogram?.histogram?.count, 100);
|
|
545
|
+
assert.ok(gauge, 'Gauge metric should be present');
|
|
546
|
+
assert.strictEqual(gauge?.histogram, undefined);
|
|
547
|
+
});
|
|
548
|
+
it('should preserve histogram data when filtering by metric name', async () => {
|
|
549
|
+
const today = getTestDate();
|
|
550
|
+
const mockMetrics = [
|
|
551
|
+
{
|
|
552
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
553
|
+
name: 'api.latency',
|
|
554
|
+
value: 200,
|
|
555
|
+
type: 'histogram',
|
|
556
|
+
histogram: {
|
|
557
|
+
buckets: [
|
|
558
|
+
{ le: 100, count: 5 },
|
|
559
|
+
{ le: 500, count: 15 },
|
|
560
|
+
{ le: 1000, count: 20 },
|
|
561
|
+
],
|
|
562
|
+
sum: 4000,
|
|
563
|
+
count: 20,
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
timestamp: '2026-01-28T10:01:00Z',
|
|
568
|
+
name: 'db.query.duration',
|
|
569
|
+
value: 50,
|
|
570
|
+
type: 'histogram',
|
|
571
|
+
histogram: {
|
|
572
|
+
buckets: [
|
|
573
|
+
{ le: 10, count: 30 },
|
|
574
|
+
{ le: 50, count: 80 },
|
|
575
|
+
{ le: 100, count: 100 },
|
|
576
|
+
],
|
|
577
|
+
sum: 3500,
|
|
578
|
+
count: 100,
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
];
|
|
582
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
583
|
+
const results = await backend.queryMetrics({ metricName: 'api.latency' });
|
|
584
|
+
assert.strictEqual(results.length, 1);
|
|
585
|
+
assert.strictEqual(results[0].name, 'api.latency');
|
|
586
|
+
assert.ok(results[0].histogram);
|
|
587
|
+
assert.strictEqual(results[0].histogram?.sum, 4000);
|
|
588
|
+
assert.strictEqual(results[0].histogram?.count, 20);
|
|
589
|
+
assert.strictEqual(results[0].histogram?.buckets.length, 3);
|
|
590
|
+
});
|
|
591
|
+
it('should ignore histogram field when metric type is not histogram', async () => {
|
|
592
|
+
const today = getTestDate();
|
|
593
|
+
// Edge case: a metric that has histogram field but type is not 'histogram'
|
|
594
|
+
const mockMetrics = [
|
|
595
|
+
{
|
|
596
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
597
|
+
name: 'malformed.metric',
|
|
598
|
+
value: 100,
|
|
599
|
+
type: 'gauge', // Not histogram type
|
|
600
|
+
histogram: {
|
|
601
|
+
// This should be ignored since type != 'histogram'
|
|
602
|
+
buckets: [{ le: 100, count: 10 }],
|
|
603
|
+
sum: 500,
|
|
604
|
+
count: 10,
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
];
|
|
608
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
609
|
+
const results = await backend.queryMetrics({});
|
|
610
|
+
assert.strictEqual(results.length, 1);
|
|
611
|
+
assert.strictEqual(results[0].name, 'malformed.metric');
|
|
612
|
+
assert.strictEqual(results[0].value, 100);
|
|
613
|
+
// Histogram data should NOT be included since type is 'gauge', not 'histogram'
|
|
614
|
+
assert.strictEqual(results[0].histogram, undefined);
|
|
615
|
+
});
|
|
616
|
+
it('should normalize aggregationTemporality from numeric OTel values', async () => {
|
|
617
|
+
const today = getTestDate();
|
|
618
|
+
const mockMetrics = [
|
|
619
|
+
{
|
|
620
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
621
|
+
name: 'http.requests.delta',
|
|
622
|
+
value: 100,
|
|
623
|
+
type: 'counter',
|
|
624
|
+
aggregationTemporality: 1, // DELTA
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
timestamp: '2026-01-28T10:01:00Z',
|
|
628
|
+
name: 'http.requests.cumulative',
|
|
629
|
+
value: 500,
|
|
630
|
+
type: 'counter',
|
|
631
|
+
aggregationTemporality: 2, // CUMULATIVE
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
timestamp: '2026-01-28T10:02:00Z',
|
|
635
|
+
name: 'http.requests.unspecified',
|
|
636
|
+
value: 50,
|
|
637
|
+
type: 'counter',
|
|
638
|
+
aggregationTemporality: 0, // UNSPECIFIED
|
|
639
|
+
},
|
|
640
|
+
];
|
|
641
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
642
|
+
const results = await backend.queryMetrics({});
|
|
643
|
+
assert.strictEqual(results.length, 3);
|
|
644
|
+
const deltaMetric = results.find(m => m.name === 'http.requests.delta');
|
|
645
|
+
const cumulativeMetric = results.find(m => m.name === 'http.requests.cumulative');
|
|
646
|
+
const unspecifiedMetric = results.find(m => m.name === 'http.requests.unspecified');
|
|
647
|
+
assert.strictEqual(deltaMetric?.aggregationTemporality, 'DELTA');
|
|
648
|
+
assert.strictEqual(cumulativeMetric?.aggregationTemporality, 'CUMULATIVE');
|
|
649
|
+
assert.strictEqual(unspecifiedMetric?.aggregationTemporality, 'UNSPECIFIED');
|
|
650
|
+
});
|
|
651
|
+
it('should normalize aggregationTemporality from string values', async () => {
|
|
652
|
+
const today = getTestDate();
|
|
653
|
+
const mockMetrics = [
|
|
654
|
+
{
|
|
655
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
656
|
+
name: 'requests.delta',
|
|
657
|
+
value: 100,
|
|
658
|
+
type: 'counter',
|
|
659
|
+
aggregationTemporality: 'delta', // lowercase
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
timestamp: '2026-01-28T10:01:00Z',
|
|
663
|
+
name: 'requests.cumulative',
|
|
664
|
+
value: 500,
|
|
665
|
+
type: 'counter',
|
|
666
|
+
aggregationTemporality: 'CUMULATIVE', // uppercase
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
timestamp: '2026-01-28T10:02:00Z',
|
|
670
|
+
name: 'requests.unspecified',
|
|
671
|
+
value: 50,
|
|
672
|
+
type: 'counter',
|
|
673
|
+
aggregationTemporality: 'Unspecified', // mixed case
|
|
674
|
+
},
|
|
675
|
+
];
|
|
676
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
677
|
+
const results = await backend.queryMetrics({});
|
|
678
|
+
assert.strictEqual(results.length, 3);
|
|
679
|
+
const deltaMetric = results.find(m => m.name === 'requests.delta');
|
|
680
|
+
const cumulativeMetric = results.find(m => m.name === 'requests.cumulative');
|
|
681
|
+
const unspecifiedMetric = results.find(m => m.name === 'requests.unspecified');
|
|
682
|
+
assert.strictEqual(deltaMetric?.aggregationTemporality, 'DELTA');
|
|
683
|
+
assert.strictEqual(cumulativeMetric?.aggregationTemporality, 'CUMULATIVE');
|
|
684
|
+
assert.strictEqual(unspecifiedMetric?.aggregationTemporality, 'UNSPECIFIED');
|
|
685
|
+
});
|
|
686
|
+
it('should return undefined aggregationTemporality when not provided', async () => {
|
|
687
|
+
const today = getTestDate();
|
|
688
|
+
const mockMetrics = [
|
|
689
|
+
{
|
|
690
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
691
|
+
name: 'gauge.metric',
|
|
692
|
+
value: 42,
|
|
693
|
+
type: 'gauge',
|
|
694
|
+
// No aggregationTemporality field
|
|
695
|
+
},
|
|
696
|
+
];
|
|
697
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
698
|
+
const results = await backend.queryMetrics({});
|
|
699
|
+
assert.strictEqual(results.length, 1);
|
|
700
|
+
assert.strictEqual(results[0].aggregationTemporality, undefined);
|
|
701
|
+
});
|
|
702
|
+
it('should handle invalid aggregationTemporality string values', async () => {
|
|
703
|
+
const today = getTestDate();
|
|
704
|
+
const mockMetrics = [
|
|
705
|
+
{
|
|
706
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
707
|
+
name: 'metric.invalid',
|
|
708
|
+
value: 100,
|
|
709
|
+
type: 'counter',
|
|
710
|
+
aggregationTemporality: 'invalid_value',
|
|
711
|
+
},
|
|
712
|
+
];
|
|
713
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
714
|
+
const results = await backend.queryMetrics({});
|
|
715
|
+
assert.strictEqual(results.length, 1);
|
|
716
|
+
// Invalid values should normalize to UNSPECIFIED
|
|
717
|
+
assert.strictEqual(results[0].aggregationTemporality, 'UNSPECIFIED');
|
|
718
|
+
});
|
|
719
|
+
it('should handle unknown numeric aggregationTemporality values', async () => {
|
|
720
|
+
const today = getTestDate();
|
|
721
|
+
const mockMetrics = [
|
|
722
|
+
{
|
|
723
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
724
|
+
name: 'metric.unknown',
|
|
725
|
+
value: 100,
|
|
726
|
+
type: 'counter',
|
|
727
|
+
aggregationTemporality: 99, // Unknown numeric value
|
|
728
|
+
},
|
|
729
|
+
];
|
|
730
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
731
|
+
const results = await backend.queryMetrics({});
|
|
732
|
+
assert.strictEqual(results.length, 1);
|
|
733
|
+
// Unknown numeric values should normalize to UNSPECIFIED
|
|
734
|
+
assert.strictEqual(results[0].aggregationTemporality, 'UNSPECIFIED');
|
|
735
|
+
});
|
|
736
|
+
it('should read and normalize metrics with exemplars', async () => {
|
|
737
|
+
const today = getTestDate();
|
|
738
|
+
const mockMetrics = [
|
|
739
|
+
{
|
|
740
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
741
|
+
name: 'http.request.duration',
|
|
742
|
+
value: 150,
|
|
743
|
+
type: 'histogram',
|
|
744
|
+
unit: 'ms',
|
|
745
|
+
histogram: {
|
|
746
|
+
buckets: [
|
|
747
|
+
{ le: 100, count: 10 },
|
|
748
|
+
{ le: 500, count: 45 },
|
|
749
|
+
{ le: Infinity, count: 50 },
|
|
750
|
+
],
|
|
751
|
+
sum: 7500,
|
|
752
|
+
count: 50,
|
|
753
|
+
},
|
|
754
|
+
exemplars: [
|
|
755
|
+
{
|
|
756
|
+
timestamp: '2026-01-28T10:00:00.123Z',
|
|
757
|
+
value: 450,
|
|
758
|
+
traceId: 'abc123def456',
|
|
759
|
+
spanId: 'span789',
|
|
760
|
+
attributes: { 'http.status_code': 500 },
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
timestamp: '2026-01-28T10:00:00.456Z',
|
|
764
|
+
value: 95,
|
|
765
|
+
traceId: 'xyz789abc123',
|
|
766
|
+
spanId: 'span456',
|
|
767
|
+
},
|
|
768
|
+
],
|
|
769
|
+
},
|
|
770
|
+
];
|
|
771
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
772
|
+
const results = await backend.queryMetrics({});
|
|
773
|
+
assert.strictEqual(results.length, 1);
|
|
774
|
+
assert.strictEqual(results[0].name, 'http.request.duration');
|
|
775
|
+
assert.ok(results[0].exemplars, 'Exemplars should be present');
|
|
776
|
+
assert.strictEqual(results[0].exemplars?.length, 2);
|
|
777
|
+
// Verify first exemplar (high latency with error)
|
|
778
|
+
const highLatencyExemplar = results[0].exemplars?.[0];
|
|
779
|
+
assert.strictEqual(highLatencyExemplar?.value, 450);
|
|
780
|
+
assert.strictEqual(highLatencyExemplar?.traceId, 'abc123def456');
|
|
781
|
+
assert.strictEqual(highLatencyExemplar?.spanId, 'span789');
|
|
782
|
+
assert.strictEqual(highLatencyExemplar?.attributes?.['http.status_code'], 500);
|
|
783
|
+
// Verify second exemplar
|
|
784
|
+
const normalExemplar = results[0].exemplars?.[1];
|
|
785
|
+
assert.strictEqual(normalExemplar?.value, 95);
|
|
786
|
+
assert.strictEqual(normalExemplar?.traceId, 'xyz789abc123');
|
|
787
|
+
});
|
|
788
|
+
it('should normalize exemplar timestamps from [seconds, nanoseconds] format', async () => {
|
|
789
|
+
const today = getTestDate();
|
|
790
|
+
const mockMetrics = [
|
|
791
|
+
{
|
|
792
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
793
|
+
name: 'api.latency',
|
|
794
|
+
value: 200,
|
|
795
|
+
type: 'histogram',
|
|
796
|
+
exemplars: [
|
|
797
|
+
{
|
|
798
|
+
timestamp: [1738062000, 123000000], // [seconds, nanoseconds]
|
|
799
|
+
value: 350,
|
|
800
|
+
traceId: 'trace123',
|
|
801
|
+
spanId: 'span456',
|
|
802
|
+
},
|
|
803
|
+
],
|
|
804
|
+
},
|
|
805
|
+
];
|
|
806
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
807
|
+
const results = await backend.queryMetrics({});
|
|
808
|
+
assert.strictEqual(results.length, 1);
|
|
809
|
+
assert.ok(results[0].exemplars);
|
|
810
|
+
assert.strictEqual(results[0].exemplars?.length, 1);
|
|
811
|
+
// Timestamp should be converted to ISO string
|
|
812
|
+
assert.ok(results[0].exemplars?.[0].timestamp.includes('T'));
|
|
813
|
+
assert.strictEqual(results[0].exemplars?.[0].value, 350);
|
|
814
|
+
});
|
|
815
|
+
it('should handle exemplars without optional fields', async () => {
|
|
816
|
+
const today = getTestDate();
|
|
817
|
+
const mockMetrics = [
|
|
818
|
+
{
|
|
819
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
820
|
+
name: 'counter.metric',
|
|
821
|
+
value: 100,
|
|
822
|
+
type: 'counter',
|
|
823
|
+
exemplars: [
|
|
824
|
+
{
|
|
825
|
+
value: 1, // Only required field
|
|
826
|
+
},
|
|
827
|
+
],
|
|
828
|
+
},
|
|
829
|
+
];
|
|
830
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
831
|
+
const results = await backend.queryMetrics({});
|
|
832
|
+
assert.strictEqual(results.length, 1);
|
|
833
|
+
assert.ok(results[0].exemplars);
|
|
834
|
+
assert.strictEqual(results[0].exemplars?.length, 1);
|
|
835
|
+
assert.strictEqual(results[0].exemplars?.[0].value, 1);
|
|
836
|
+
// Timestamp should default to metric timestamp
|
|
837
|
+
assert.strictEqual(results[0].exemplars?.[0].timestamp, '2026-01-28T10:00:00Z');
|
|
838
|
+
assert.strictEqual(results[0].exemplars?.[0].traceId, undefined);
|
|
839
|
+
assert.strictEqual(results[0].exemplars?.[0].spanId, undefined);
|
|
840
|
+
});
|
|
841
|
+
it('should handle metrics without exemplars', async () => {
|
|
842
|
+
const today = getTestDate();
|
|
843
|
+
const mockMetrics = [
|
|
844
|
+
{
|
|
845
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
846
|
+
name: 'simple.counter',
|
|
847
|
+
value: 42,
|
|
848
|
+
type: 'counter',
|
|
849
|
+
// No exemplars field
|
|
850
|
+
},
|
|
851
|
+
];
|
|
852
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
853
|
+
const results = await backend.queryMetrics({});
|
|
854
|
+
assert.strictEqual(results.length, 1);
|
|
855
|
+
assert.strictEqual(results[0].exemplars, undefined);
|
|
856
|
+
});
|
|
857
|
+
it('should handle empty exemplars array', async () => {
|
|
858
|
+
const today = getTestDate();
|
|
859
|
+
const mockMetrics = [
|
|
860
|
+
{
|
|
861
|
+
timestamp: '2026-01-28T10:00:00Z',
|
|
862
|
+
name: 'empty.exemplars',
|
|
863
|
+
value: 100,
|
|
864
|
+
type: 'counter',
|
|
865
|
+
exemplars: [], // Empty array
|
|
866
|
+
},
|
|
867
|
+
];
|
|
868
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
869
|
+
const results = await backend.queryMetrics({});
|
|
870
|
+
assert.strictEqual(results.length, 1);
|
|
871
|
+
// Empty exemplars array should result in undefined
|
|
872
|
+
assert.strictEqual(results[0].exemplars, undefined);
|
|
873
|
+
});
|
|
874
|
+
});
|
|
875
|
+
});
|
|
876
|
+
//# sourceMappingURL=local-jsonl-metrics.test.js.map
|