@vzn/vx-otel 0.0.518 → 0.0.520
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 +20 -1
- package/package.json +2 -2
- package/src/otlp.ts +161 -1
- package/src/sink.ts +100 -9
package/README.md
CHANGED
|
@@ -105,7 +105,17 @@ otel({
|
|
|
105
105
|
a sandboxed one its violation count (`vx.task.sandbox_violations`), on a
|
|
106
106
|
persistent one that never became ready why (`vx.task.not_ready`). A
|
|
107
107
|
failed task sets span status
|
|
108
|
-
`ERROR`.
|
|
108
|
+
`ERROR`. A task span links to the spans of the tasks it waited on (a
|
|
109
|
+
group seen through to the tasks behind it), and carries an event per
|
|
110
|
+
attempt that failed and was run again (`vx.task.retry`, with
|
|
111
|
+
`vx.task.attempt` and `vx.task.exit_code`) and one when vx's own
|
|
112
|
+
timeout killed it (`vx.task.timeout`);
|
|
113
|
+
- a child span per stage of the run, named by the stage (`startup`,
|
|
114
|
+
`load configs`, `git enumeration`, `classify + probe`, `run graph`,
|
|
115
|
+
`record history`, …, the stages `VX_TIMING` prints), with
|
|
116
|
+
`vx.stage.name`: where the time before the first task and after the
|
|
117
|
+
last went. The stages ahead of the run lock end before the `vx.run`
|
|
118
|
+
span starts.
|
|
109
119
|
|
|
110
120
|
**Metrics per run**: `vx.tasks.total`, `vx.tasks.failed`,
|
|
111
121
|
`vx.tasks.cache_hits{source=local|remote}`, what those hits did to the disk as
|
|
@@ -116,6 +126,15 @@ and the `vx.run.duration_ms` gauge. The `vx.run` span carries the same counts
|
|
|
116
126
|
The counts are DELTA sums over the run's own interval (start to end), so a
|
|
117
127
|
backend adds runs rather than reading each as the series' new total.
|
|
118
128
|
|
|
129
|
+
**Metrics per task**, as gauges keyed by `cicd.pipeline.task.name`,
|
|
130
|
+
`vx.task.project` and `vx.task.task`: at each task's end
|
|
131
|
+
`vx.task.duration` (ms, with `vx.cache.source`), and for a task the runner
|
|
132
|
+
measured `vx.task.cpu_time` (ms) and `vx.task.peak_memory` (bytes); a
|
|
133
|
+
skipped task sends none. While a task runs, its process tree is sampled
|
|
134
|
+
each second: `vx.task.cpu_usage` (cores busy since the last sample, 1 =
|
|
135
|
+
one core) and `vx.task.memory` (resident bytes). Sampling runs only while
|
|
136
|
+
metrics export; a remote task, or a task under 1 s, has no samples.
|
|
137
|
+
|
|
119
138
|
**Logs per run** (on by default): the captured output tail of each executed
|
|
120
139
|
task, as one log record linked to its task span (unlinked when traces are
|
|
121
140
|
off: the span is never exported). Build output can hold
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx-otel",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.520",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "OpenTelemetry exporter for vx — a telemetry plugin mapping runs to OTLP traces + metrics (CI/CD + VCS semantic conventions).",
|
|
6
6
|
"keywords": [
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@vzn/vx": "^0.0.
|
|
37
|
+
"@vzn/vx": "^0.0.520"
|
|
38
38
|
},
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|
package/src/otlp.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
// of SDK-version drift. Maps a vx run to:
|
|
7
7
|
// - a TRACE: one root `vx.run` span + one child `vx.task` span per task,
|
|
8
8
|
// with CI/CD + VCS semantic-convention attributes;
|
|
9
|
-
// - METRICS: task/run counters + a run-duration gauge;
|
|
9
|
+
// - METRICS: task/run counters + a run-duration gauge; per task its
|
|
10
|
+
// duration, CPU time and peak memory, and while it runs its process
|
|
11
|
+
// tree's CPU usage and memory, sampled each second;
|
|
10
12
|
// - LOGS: one record per executed task carrying its captured output tail.
|
|
11
13
|
//
|
|
12
14
|
// References: OpenTelemetry CI/CD + VCS semantic conventions; OTLP/JSON
|
|
@@ -124,6 +126,8 @@ const VX_ATTR = {
|
|
|
124
126
|
taskOutputs: 'vx.task.outputs',
|
|
125
127
|
peakRssBytes: 'vx.peak_rss_bytes',
|
|
126
128
|
taskAttempts: 'vx.task.attempts',
|
|
129
|
+
taskAttempt: 'vx.task.attempt',
|
|
130
|
+
stageName: 'vx.stage.name',
|
|
127
131
|
wallclockStartNs: 'vx.task.wallclock_start_ns',
|
|
128
132
|
wallclockEndNs: 'vx.task.wallclock_end_ns',
|
|
129
133
|
// task log records (the OTel Logs signal)
|
|
@@ -149,6 +153,76 @@ export interface OtlpSpan {
|
|
|
149
153
|
endTimeUnixNano: string
|
|
150
154
|
attributes: KeyValue[]
|
|
151
155
|
status: { code: number }
|
|
156
|
+
events?: OtlpSpanEvent[]
|
|
157
|
+
links?: OtlpSpanLink[]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface OtlpSpanEvent {
|
|
161
|
+
timeUnixNano: string
|
|
162
|
+
name: string
|
|
163
|
+
attributes: KeyValue[]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface OtlpSpanLink {
|
|
167
|
+
traceId: string
|
|
168
|
+
spanId: string
|
|
169
|
+
attributes: KeyValue[]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* A task span's events: each attempt that failed and was run again
|
|
174
|
+
* (`vx.task.retry`, at its end), and vx's own deadline killing the task
|
|
175
|
+
* (`vx.task.timeout`, at the span's end).
|
|
176
|
+
*/
|
|
177
|
+
export function taskSpanEvents(t: TaskTelemetry, endUnixNano: string): OtlpSpanEvent[] {
|
|
178
|
+
const events: OtlpSpanEvent[] = (t.failedAttempts ?? []).map((a, i) => ({
|
|
179
|
+
timeUnixNano: String(Math.trunc(a.endedAt) * 1_000_000),
|
|
180
|
+
name: 'vx.task.retry',
|
|
181
|
+
attributes: [
|
|
182
|
+
intAttr(VX_ATTR.taskAttempt, i + 1),
|
|
183
|
+
intAttr(VX_ATTR.taskExitCode, a.exitCode),
|
|
184
|
+
...(a.timedOut === true ? [boolAttr(VX_ATTR.taskTimedOut, true)] : []),
|
|
185
|
+
],
|
|
186
|
+
}))
|
|
187
|
+
if (t.timedOut === true) {
|
|
188
|
+
events.push({ timeUnixNano: endUnixNano, name: 'vx.task.timeout', attributes: [] })
|
|
189
|
+
}
|
|
190
|
+
return events
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* A child span of the run for one of its stages (`VX_TIMING`'s marks):
|
|
195
|
+
* where the time before the first task and after the last one went. Named
|
|
196
|
+
* by the stage, a fixed set. The stages ahead of the run lock end before
|
|
197
|
+
* the run span starts.
|
|
198
|
+
*/
|
|
199
|
+
export function stageSpan(
|
|
200
|
+
traceId: string,
|
|
201
|
+
parentSpanId: string,
|
|
202
|
+
stage: { name: string; startedAt: number; endedAt: number },
|
|
203
|
+
spanId: string,
|
|
204
|
+
): OtlpSpan {
|
|
205
|
+
return {
|
|
206
|
+
traceId,
|
|
207
|
+
spanId,
|
|
208
|
+
parentSpanId,
|
|
209
|
+
name: stage.name,
|
|
210
|
+
kind: SPAN_KIND_INTERNAL,
|
|
211
|
+
startTimeUnixNano: exactNanos(stage.startedAt),
|
|
212
|
+
endTimeUnixNano: exactNanos(stage.endedAt),
|
|
213
|
+
attributes: [strAttr(VX_ATTR.stageName, stage.name)],
|
|
214
|
+
status: { code: STATUS_UNSET },
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Epoch ms with a fraction, as OTLP's int64 nanoseconds: a stage can be under 1 ms. */
|
|
219
|
+
function exactNanos(ms: number): string {
|
|
220
|
+
return String(BigInt(Math.round(ms * 1000)) * 1000n)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** A link from a task span to the span of a task it waited on. */
|
|
224
|
+
export function dependencyLink(traceId: string, spanId: string, taskId: string): OtlpSpanLink {
|
|
225
|
+
return { traceId, spanId, attributes: [strAttr(SEMCONV.taskName, taskId)] }
|
|
152
226
|
}
|
|
153
227
|
|
|
154
228
|
// --- attribute mapping (pure) ------------------------------------------
|
|
@@ -367,6 +441,7 @@ export function buildMetricsRequest(
|
|
|
367
441
|
nowUnixNano: string,
|
|
368
442
|
startUnixNano: string,
|
|
369
443
|
resource?: Readonly<Record<string, string>>,
|
|
444
|
+
points: readonly TaskMetricPoint[] = [],
|
|
370
445
|
): unknown {
|
|
371
446
|
const point = (value: number, attrs: KeyValue[] = []) => ({
|
|
372
447
|
asInt: String(value),
|
|
@@ -408,6 +483,7 @@ export function buildMetricsRequest(
|
|
|
408
483
|
]),
|
|
409
484
|
sum('vx.tasks.cache_up_to_date', [point(summary.upToDateCount)]),
|
|
410
485
|
gauge('vx.run.duration_ms', summary.totalDurationMs),
|
|
486
|
+
...taskMetrics(points),
|
|
411
487
|
],
|
|
412
488
|
},
|
|
413
489
|
],
|
|
@@ -416,6 +492,90 @@ export function buildMetricsRequest(
|
|
|
416
492
|
}
|
|
417
493
|
}
|
|
418
494
|
|
|
495
|
+
/**
|
|
496
|
+
* A per-task metric point: a task's totals at its end, or one live sample
|
|
497
|
+
* of its process tree while it runs.
|
|
498
|
+
*/
|
|
499
|
+
export type TaskMetricPoint =
|
|
500
|
+
| { kind: 'end'; task: TaskTelemetry; startUnixNano: string; endUnixNano: string }
|
|
501
|
+
| {
|
|
502
|
+
kind: 'sample'
|
|
503
|
+
taskId: string
|
|
504
|
+
project: string
|
|
505
|
+
task: string
|
|
506
|
+
timeUnixNano: string
|
|
507
|
+
/** CPU cores busy since the previous sample (1 = one core). */
|
|
508
|
+
cpuUsage: number
|
|
509
|
+
rssBytes: number
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* An ExportMetricsServiceRequest of per-task gauges alone: the points past
|
|
514
|
+
* what fit beside the run's own metrics (`buildMetricsRequest`).
|
|
515
|
+
*/
|
|
516
|
+
export function buildTaskMetricsRequest(
|
|
517
|
+
serviceName: string,
|
|
518
|
+
vxVersion: string,
|
|
519
|
+
points: readonly TaskMetricPoint[],
|
|
520
|
+
resource?: Readonly<Record<string, string>>,
|
|
521
|
+
): unknown {
|
|
522
|
+
return {
|
|
523
|
+
resourceMetrics: [
|
|
524
|
+
{
|
|
525
|
+
resource: { attributes: resourceAttributes(serviceName, vxVersion, resource) },
|
|
526
|
+
scopeMetrics: [{ scope: { name: 'vx', version: vxVersion }, metrics: taskMetrics(points) }],
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Per-task gauges, keyed by task. At each task's end: its duration, CPU time and peak memory (CPU and memory as
|
|
534
|
+
* the runner measured them at exit; a task it did not measure, a cache hit or
|
|
535
|
+
* a remote run, sends no point for them). While a task runs: its process
|
|
536
|
+
* tree's CPU usage and memory, once per sample. A task's numbers otherwise
|
|
537
|
+
* live only on its span, which no metrics backend charts.
|
|
538
|
+
*/
|
|
539
|
+
function taskMetrics(points: readonly TaskMetricPoint[]): unknown[] {
|
|
540
|
+
const series = new Map<string, { unit: string; dataPoints: unknown[] }>()
|
|
541
|
+
const add = (name: string, unit: string, point: unknown): void => {
|
|
542
|
+
const s = series.get(name)
|
|
543
|
+
if (s === undefined) series.set(name, { unit, dataPoints: [point] })
|
|
544
|
+
else s.dataPoints.push(point)
|
|
545
|
+
}
|
|
546
|
+
const taskAttrs = (taskId: string, project: string, task: string): KeyValue[] => [
|
|
547
|
+
strAttr(SEMCONV.taskName, taskId),
|
|
548
|
+
strAttr(VX_ATTR.taskProject, project),
|
|
549
|
+
strAttr(VX_ATTR.taskTask, task),
|
|
550
|
+
]
|
|
551
|
+
for (const p of points) {
|
|
552
|
+
if (p.kind === 'sample') {
|
|
553
|
+
const at = (value: number) => ({
|
|
554
|
+
asDouble: value,
|
|
555
|
+
timeUnixNano: p.timeUnixNano,
|
|
556
|
+
attributes: taskAttrs(p.taskId, p.project, p.task),
|
|
557
|
+
})
|
|
558
|
+
add('vx.task.cpu_usage', '1', at(p.cpuUsage))
|
|
559
|
+
add('vx.task.memory', 'By', at(p.rssBytes))
|
|
560
|
+
continue
|
|
561
|
+
}
|
|
562
|
+
const t = p.task
|
|
563
|
+
const at = (value: number) => ({
|
|
564
|
+
asDouble: value,
|
|
565
|
+
startTimeUnixNano: p.startUnixNano,
|
|
566
|
+
timeUnixNano: p.endUnixNano,
|
|
567
|
+
attributes: [
|
|
568
|
+
...taskAttrs(t.taskId, t.project, t.task),
|
|
569
|
+
strAttr(VX_ATTR.cacheSource, t.cacheSource),
|
|
570
|
+
],
|
|
571
|
+
})
|
|
572
|
+
add('vx.task.duration', 'ms', at(t.durationMs))
|
|
573
|
+
if (t.cpuMs !== undefined) add('vx.task.cpu_time', 'ms', at(t.cpuMs))
|
|
574
|
+
if (t.peakRssBytes !== undefined) add('vx.task.peak_memory', 'By', at(t.peakRssBytes))
|
|
575
|
+
}
|
|
576
|
+
return [...series].map(([name, { unit, dataPoints }]) => ({ name, unit, gauge: { dataPoints } }))
|
|
577
|
+
}
|
|
578
|
+
|
|
419
579
|
// --- logs ---------------------------------------------------------------
|
|
420
580
|
|
|
421
581
|
// OTLP severity numbers: 9 INFO, 17 ERROR.
|
package/src/sink.ts
CHANGED
|
@@ -16,10 +16,15 @@ import type {
|
|
|
16
16
|
import {
|
|
17
17
|
buildLogsRequest,
|
|
18
18
|
buildMetricsRequest,
|
|
19
|
+
buildTaskMetricsRequest,
|
|
19
20
|
buildTraceRequest,
|
|
20
21
|
type OtlpSpan,
|
|
22
|
+
type TaskMetricPoint,
|
|
21
23
|
runSpanAttributes,
|
|
22
24
|
SPAN_KIND_INTERNAL,
|
|
25
|
+
dependencyLink,
|
|
26
|
+
stageSpan,
|
|
27
|
+
taskSpanEvents,
|
|
23
28
|
runStatusCode,
|
|
24
29
|
taskSpanAttributes,
|
|
25
30
|
taskStatusCode,
|
|
@@ -269,8 +274,12 @@ export class OtelSink implements TelemetrySink {
|
|
|
269
274
|
private rootStartNano = '0'
|
|
270
275
|
private rootEndNano = '0'
|
|
271
276
|
private readonly spans: OtlpSpan[] = []
|
|
277
|
+
private readonly taskPoints: TaskMetricPoint[] = []
|
|
278
|
+
private readonly taskNames = new Map<string, { project: string; task: string; startMs: number }>()
|
|
279
|
+
private readonly lastSample = new Map<string, { ts: number; cpuMs: number }>()
|
|
272
280
|
private readonly taskSpanId = new Map<string, string>()
|
|
273
281
|
private readonly taskStartNano = new Map<string, string>()
|
|
282
|
+
private readonly taskDeps = new Map<string, readonly string[]>()
|
|
274
283
|
private summary: RunSummaryRecord | undefined
|
|
275
284
|
private uploaded = false
|
|
276
285
|
// Core's own bounded capture buffer, so which task's output survives a
|
|
@@ -301,9 +310,14 @@ export class OtelSink implements TelemetrySink {
|
|
|
301
310
|
post: config.post ?? defaultPost(config.timeoutMs),
|
|
302
311
|
...(config.warn ? { warn: config.warn } : {}),
|
|
303
312
|
}
|
|
304
|
-
this.wants =
|
|
305
|
-
|
|
306
|
-
|
|
313
|
+
this.wants = [
|
|
314
|
+
'run.start',
|
|
315
|
+
'task.start',
|
|
316
|
+
...(config.logsEnabled ? (['task.log'] as const) : []),
|
|
317
|
+
...(config.metricsEnabled ? (['task.sample'] as const) : []),
|
|
318
|
+
'task.end',
|
|
319
|
+
'run.end',
|
|
320
|
+
]
|
|
307
321
|
}
|
|
308
322
|
|
|
309
323
|
onRecord(record: TelemetryRecord): void {
|
|
@@ -321,12 +335,50 @@ export class OtelSink implements TelemetrySink {
|
|
|
321
335
|
case 'task.start':
|
|
322
336
|
this.taskSpanId.set(record.taskId, genId(8))
|
|
323
337
|
this.taskStartNano.set(record.taskId, nanos(record.ts))
|
|
338
|
+
if (record.dependsOn !== undefined) this.taskDeps.set(record.taskId, record.dependsOn)
|
|
339
|
+
this.taskNames.set(record.taskId, {
|
|
340
|
+
project: record.project,
|
|
341
|
+
task: record.task,
|
|
342
|
+
startMs: record.ts,
|
|
343
|
+
})
|
|
344
|
+
return
|
|
345
|
+
case 'task.sample': {
|
|
346
|
+
// Usage is the CPU the tree spent since the last look, over the
|
|
347
|
+
// wall time between: the first look counts from the task's start.
|
|
348
|
+
const names = this.taskNames.get(record.taskId)
|
|
349
|
+
if (names === undefined) return
|
|
350
|
+
const prev = this.lastSample.get(record.taskId) ?? { ts: names.startMs, cpuMs: 0 }
|
|
351
|
+
const wallMs = record.ts - prev.ts
|
|
352
|
+
// A descendant that exited takes its CPU out of the sum: no negative usage.
|
|
353
|
+
const cpuUsage = wallMs > 0 ? Math.max(0, record.cpuMs - prev.cpuMs) / wallMs : 0
|
|
354
|
+
this.lastSample.set(record.taskId, { ts: record.ts, cpuMs: record.cpuMs })
|
|
355
|
+
this.taskPoints.push({
|
|
356
|
+
kind: 'sample',
|
|
357
|
+
taskId: record.taskId,
|
|
358
|
+
project: names.project,
|
|
359
|
+
task: names.task,
|
|
360
|
+
timeUnixNano: nanos(record.ts),
|
|
361
|
+
cpuUsage,
|
|
362
|
+
rssBytes: record.rssBytes,
|
|
363
|
+
})
|
|
324
364
|
return
|
|
365
|
+
}
|
|
325
366
|
case 'task.log':
|
|
326
367
|
this.logs.append(record.taskId, record.chunk)
|
|
327
368
|
return
|
|
328
369
|
case 'task.end': {
|
|
329
|
-
|
|
370
|
+
// A task that never started (a skip) still gets a span, and a later
|
|
371
|
+
// task that waited on it links to it.
|
|
372
|
+
let spanId = this.taskSpanId.get(record.taskId)
|
|
373
|
+
if (spanId === undefined) {
|
|
374
|
+
spanId = genId(8)
|
|
375
|
+
this.taskSpanId.set(record.taskId, spanId)
|
|
376
|
+
}
|
|
377
|
+
const links = (this.taskDeps.get(record.taskId) ?? []).flatMap((dep) => {
|
|
378
|
+
const depSpan = this.taskSpanId.get(dep)
|
|
379
|
+
return depSpan === undefined ? [] : [dependencyLink(this.traceId, depSpan, dep)]
|
|
380
|
+
})
|
|
381
|
+
const events = taskSpanEvents(record, nanos(record.ts))
|
|
330
382
|
const startNano =
|
|
331
383
|
this.taskStartNano.get(record.taskId) ?? nanos(record.ts - record.durationMs)
|
|
332
384
|
const t: TaskTelemetry = record
|
|
@@ -344,7 +396,19 @@ export class OtelSink implements TelemetrySink {
|
|
|
344
396
|
startedAt: this.runStartedAt,
|
|
345
397
|
}),
|
|
346
398
|
status: { code: taskStatusCode(t) },
|
|
399
|
+
...(events.length > 0 ? { events } : {}),
|
|
400
|
+
...(links.length > 0 ? { links } : {}),
|
|
347
401
|
})
|
|
402
|
+
// A skipped task never ran: a zero duration would read as a fast one.
|
|
403
|
+
if (record.status !== 'skipped') {
|
|
404
|
+
this.taskPoints.push({
|
|
405
|
+
kind: 'end',
|
|
406
|
+
task: t,
|
|
407
|
+
startUnixNano: startNano,
|
|
408
|
+
endUnixNano: nanos(record.ts),
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
this.lastSample.delete(record.taskId)
|
|
348
412
|
// Decides retention: a cache hit's bytes belong to the run that
|
|
349
413
|
// executed them, so only an executed success/failure keeps a tail.
|
|
350
414
|
if (this.cfg.logsEnabled) {
|
|
@@ -391,6 +455,10 @@ export class OtelSink implements TelemetrySink {
|
|
|
391
455
|
attributes: runSpanAttributes(this.run, this.summary),
|
|
392
456
|
status: { code: runStatusCode(this.summary) },
|
|
393
457
|
})
|
|
458
|
+
const stages = (this.summary?.stages ?? []).map((stage) =>
|
|
459
|
+
stageSpan(this.traceId, this.rootSpanId, stage, genId(8)),
|
|
460
|
+
)
|
|
461
|
+
this.spans.splice(1, 0, ...stages)
|
|
394
462
|
}
|
|
395
463
|
const vxVersion = this.run?.vxVersion ?? '0.0.0'
|
|
396
464
|
await Promise.all([this.shipTraces(vxVersion), this.shipMetrics(), this.shipLogs(vxVersion)])
|
|
@@ -406,16 +474,39 @@ export class OtelSink implements TelemetrySink {
|
|
|
406
474
|
|
|
407
475
|
private async shipMetrics(): Promise<void> {
|
|
408
476
|
if (!this.cfg.metricsEnabled || this.summary === undefined) return
|
|
409
|
-
const
|
|
477
|
+
const summary = this.summary
|
|
478
|
+
const vxVersion = this.run?.vxVersion ?? '0.0.0'
|
|
479
|
+
const first = this.taskPoints[0]
|
|
480
|
+
// The run's own metrics ride with the first batch of per-task points.
|
|
481
|
+
const bodies =
|
|
482
|
+
first === undefined
|
|
483
|
+
? [this.runMetricsBody(summary, [])]
|
|
484
|
+
: requestBodies(this.taskPoints, (points) =>
|
|
485
|
+
points[0] === first
|
|
486
|
+
? this.runMetricsBody(summary, points)
|
|
487
|
+
: JSON.stringify(
|
|
488
|
+
buildTaskMetricsRequest(
|
|
489
|
+
this.cfg.serviceName,
|
|
490
|
+
vxVersion,
|
|
491
|
+
points,
|
|
492
|
+
this.cfg.resource,
|
|
493
|
+
),
|
|
494
|
+
),
|
|
495
|
+
)
|
|
496
|
+
await this.send('metrics', this.cfg.metricsUrl, bodies)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private runMetricsBody(summary: RunSummaryRecord, points: TaskMetricPoint[]): string {
|
|
500
|
+
return JSON.stringify(
|
|
410
501
|
buildMetricsRequest(
|
|
411
502
|
this.cfg.serviceName,
|
|
412
|
-
|
|
413
|
-
nanos(
|
|
414
|
-
nanos(
|
|
503
|
+
summary,
|
|
504
|
+
nanos(summary.endedAt),
|
|
505
|
+
nanos(summary.startedAt),
|
|
415
506
|
this.cfg.resource,
|
|
507
|
+
points,
|
|
416
508
|
),
|
|
417
509
|
)
|
|
418
|
-
await this.send('metrics', this.cfg.metricsUrl, [body])
|
|
419
510
|
}
|
|
420
511
|
|
|
421
512
|
private async shipLogs(vxVersion: string): Promise<void> {
|