@shipfox/api-runners 12.4.0 → 12.4.1
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +14 -0
- package/dist/core/errors.d.ts +5 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/runner-control-sessions.d.ts.map +1 -1
- package/dist/core/runner-control-sessions.js +62 -12
- package/dist/core/runner-control-sessions.js.map +1 -1
- package/dist/db/job-executions.d.ts.map +1 -1
- package/dist/db/job-executions.js +35 -4
- package/dist/db/job-executions.js.map +1 -1
- package/dist/db/runner-assignments.d.ts +8 -1
- package/dist/db/runner-assignments.d.ts.map +1 -1
- package/dist/db/runner-assignments.js +57 -10
- package/dist/db/runner-assignments.js.map +1 -1
- package/dist/db/runner-instances.d.ts +9 -0
- package/dist/db/runner-instances.d.ts.map +1 -1
- package/dist/db/runner-instances.js +43 -0
- package/dist/db/runner-instances.js.map +1 -1
- package/dist/db/runner-sessions.d.ts.map +1 -1
- package/dist/db/runner-sessions.js +14 -1
- package/dist/db/runner-sessions.js.map +1 -1
- package/dist/metrics/index.d.ts +2 -2
- package/dist/metrics/index.d.ts.map +1 -1
- package/dist/metrics/index.js +1 -1
- package/dist/metrics/index.js.map +1 -1
- package/dist/metrics/instance.d.ts +48 -0
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +138 -1
- package/dist/metrics/instance.js.map +1 -1
- package/dist/metrics/service.d.ts.map +1 -1
- package/dist/metrics/service.js +25 -4
- package/dist/metrics/service.js.map +1 -1
- package/dist/presentation/routes/assign-runner-instances.d.ts.map +1 -1
- package/dist/presentation/routes/assign-runner-instances.js +7 -1
- package/dist/presentation/routes/assign-runner-instances.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/core/errors.ts +30 -1
- package/src/core/runner-control-sessions.test.ts +71 -0
- package/src/core/runner-control-sessions.ts +79 -10
- package/src/db/job-executions.test.ts +98 -12
- package/src/db/job-executions.ts +52 -4
- package/src/db/runner-assignments.ts +67 -16
- package/src/db/runner-instances.test.ts +92 -0
- package/src/db/runner-instances.ts +74 -0
- package/src/db/runner-sessions.ts +19 -1
- package/src/metrics/index.ts +14 -0
- package/src/metrics/instance.test.ts +118 -0
- package/src/metrics/instance.ts +188 -1
- package/src/metrics/service.test.ts +38 -0
- package/src/metrics/service.ts +55 -8
- package/src/presentation/routes/assign-runner-instances.ts +8 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/metrics/instance.ts
CHANGED
|
@@ -1,7 +1,116 @@
|
|
|
1
|
-
import {instanceMetrics} from '@shipfox/node-opentelemetry';
|
|
1
|
+
import {instanceMetrics, logger} from '@shipfox/node-opentelemetry';
|
|
2
|
+
import type {RunnerAssignmentRejectionReason} from '#core/errors.js';
|
|
2
3
|
|
|
3
4
|
const meter = instanceMetrics.getMeter('runners');
|
|
4
5
|
|
|
6
|
+
export type RunnerLaunchKind = 'demand' | 'warm' | 'manual';
|
|
7
|
+
export type RunnerAssignmentSurface = 'provisioner' | 'enrollment';
|
|
8
|
+
|
|
9
|
+
type ProviderRunnerLifecycleLabels = {
|
|
10
|
+
provider: string;
|
|
11
|
+
launch_kind: RunnerLaunchKind;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type ProviderRunnerAssignmentLifecycleLabels = ProviderRunnerLifecycleLabels & {
|
|
15
|
+
surface: RunnerAssignmentSurface;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type JobExecutionQueueTimeLabels = {
|
|
19
|
+
provider: string;
|
|
20
|
+
launch_kind: RunnerLaunchKind | 'unknown';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export interface JobExecutionQueueTimeObservation {
|
|
24
|
+
durationMilliseconds: number;
|
|
25
|
+
provider: string | null;
|
|
26
|
+
launchKind: RunnerLaunchKind | 'unknown';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ProviderRunnerLifecycleObservation {
|
|
30
|
+
durationMilliseconds: number;
|
|
31
|
+
provider: string | null;
|
|
32
|
+
launchKind: RunnerLaunchKind;
|
|
33
|
+
runnerInstanceId?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ProviderRunnerAssignmentObservation extends ProviderRunnerLifecycleObservation {
|
|
37
|
+
surface: RunnerAssignmentSurface;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const UNKNOWN_PROVIDER_KIND = 'unknown';
|
|
41
|
+
|
|
42
|
+
const lifecycleDurationBuckets = {
|
|
43
|
+
long: [
|
|
44
|
+
100, 500, 1_000, 5_000, 10_000, 15_000, 20_000, 30_000, 45_000, 60_000, 90_000, 120_000,
|
|
45
|
+
300_000, 600_000,
|
|
46
|
+
],
|
|
47
|
+
short: [10, 25, 50, 100, 250, 500, 1_000, 5_000, 10_000],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const queueTimeBucketsMilliseconds = [
|
|
51
|
+
100, 500, 1_000, 5_000, 10_000, 15_000, 20_000, 30_000, 45_000, 60_000, 90_000, 120_000, 300_000,
|
|
52
|
+
600_000, 900_000, 1_800_000, 3_600_000, 7_200_000, 14_400_000,
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
// Keep the ordered lifecycle phases as separate histograms so long boot times do not flatten
|
|
56
|
+
// short handoffs. Assignment is the only phase with a surface dimension.
|
|
57
|
+
export const providerRunnerCreatedToControlSessionDuration =
|
|
58
|
+
meter.createHistogram<ProviderRunnerLifecycleLabels>(
|
|
59
|
+
'runners_provider_runner_created_to_control_session',
|
|
60
|
+
{
|
|
61
|
+
description: 'Provider runner row creation to control-session creation duration',
|
|
62
|
+
unit: 'ms',
|
|
63
|
+
advice: {explicitBucketBoundaries: lifecycleDurationBuckets.long},
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
export const providerRunnerControlSessionToAssignmentDuration =
|
|
68
|
+
meter.createHistogram<ProviderRunnerAssignmentLifecycleLabels>(
|
|
69
|
+
'runners_provider_runner_control_session_to_assignment',
|
|
70
|
+
{
|
|
71
|
+
description: 'Provider runner control-session creation to reservation assignment duration',
|
|
72
|
+
unit: 'ms',
|
|
73
|
+
advice: {explicitBucketBoundaries: lifecycleDurationBuckets.long},
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
export const providerRunnerAssignmentToActivationDuration =
|
|
78
|
+
meter.createHistogram<ProviderRunnerLifecycleLabels>(
|
|
79
|
+
'runners_provider_runner_assignment_to_activation',
|
|
80
|
+
{
|
|
81
|
+
description:
|
|
82
|
+
'Provider runner reservation assignment to workspace runner-session creation duration',
|
|
83
|
+
unit: 'ms',
|
|
84
|
+
advice: {explicitBucketBoundaries: lifecycleDurationBuckets.short},
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
export const providerRunnerActivationToFirstClaimDuration =
|
|
89
|
+
meter.createHistogram<ProviderRunnerLifecycleLabels>(
|
|
90
|
+
'runners_provider_runner_activation_to_first_claim',
|
|
91
|
+
{
|
|
92
|
+
description: 'Provider runner workspace runner-session creation to first job claim duration',
|
|
93
|
+
unit: 'ms',
|
|
94
|
+
advice: {explicitBucketBoundaries: lifecycleDurationBuckets.short},
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
export const jobExecutionQueueTimeDuration = meter.createHistogram<JobExecutionQueueTimeLabels>(
|
|
99
|
+
'runners_job_execution_queue_time',
|
|
100
|
+
{
|
|
101
|
+
description: 'Job execution pending queue duration from enqueue to runner claim',
|
|
102
|
+
unit: 'ms',
|
|
103
|
+
advice: {explicitBucketBoundaries: queueTimeBucketsMilliseconds},
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
export const providerRunnerAssignmentRejectedCount = meter.createCounter<{
|
|
108
|
+
reason: RunnerAssignmentRejectionReason;
|
|
109
|
+
surface: RunnerAssignmentSurface;
|
|
110
|
+
}>('runners_provider_runner_assignment_rejected', {
|
|
111
|
+
description: 'Provider runner assignment operations rejected by bounded reason and surface',
|
|
112
|
+
});
|
|
113
|
+
|
|
5
114
|
export const jobExecutionEnqueuedCount = meter.createCounter<Record<string, never>>(
|
|
6
115
|
'runners_job_execution_enqueued',
|
|
7
116
|
{
|
|
@@ -152,6 +261,84 @@ function recordMetric(record: () => void): void {
|
|
|
152
261
|
}
|
|
153
262
|
}
|
|
154
263
|
|
|
264
|
+
function resolveProviderRunnerMetricProvider(params: {
|
|
265
|
+
provider: string | null;
|
|
266
|
+
runnerInstanceId?: string;
|
|
267
|
+
}): string {
|
|
268
|
+
if (params.provider) return params.provider;
|
|
269
|
+
logger().debug(
|
|
270
|
+
{runnerInstanceId: params.runnerInstanceId},
|
|
271
|
+
'Provider runner metric missing provider kind',
|
|
272
|
+
);
|
|
273
|
+
return UNKNOWN_PROVIDER_KIND;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function recordProviderRunnerCreatedToControlSession(
|
|
277
|
+
params: ProviderRunnerLifecycleObservation,
|
|
278
|
+
): void {
|
|
279
|
+
if (params.durationMilliseconds < 0) return;
|
|
280
|
+
recordMetric(() =>
|
|
281
|
+
providerRunnerCreatedToControlSessionDuration.record(params.durationMilliseconds, {
|
|
282
|
+
provider: resolveProviderRunnerMetricProvider(params),
|
|
283
|
+
launch_kind: params.launchKind,
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function recordProviderRunnerControlSessionToAssignment(
|
|
289
|
+
params: ProviderRunnerAssignmentObservation,
|
|
290
|
+
): void {
|
|
291
|
+
if (params.durationMilliseconds < 0) return;
|
|
292
|
+
recordMetric(() =>
|
|
293
|
+
providerRunnerControlSessionToAssignmentDuration.record(params.durationMilliseconds, {
|
|
294
|
+
provider: resolveProviderRunnerMetricProvider(params),
|
|
295
|
+
launch_kind: params.launchKind,
|
|
296
|
+
surface: params.surface,
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function recordProviderRunnerAssignmentToActivation(
|
|
302
|
+
params: ProviderRunnerLifecycleObservation,
|
|
303
|
+
): void {
|
|
304
|
+
if (params.durationMilliseconds < 0) return;
|
|
305
|
+
recordMetric(() =>
|
|
306
|
+
providerRunnerAssignmentToActivationDuration.record(params.durationMilliseconds, {
|
|
307
|
+
provider: resolveProviderRunnerMetricProvider(params),
|
|
308
|
+
launch_kind: params.launchKind,
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function recordProviderRunnerActivationToFirstClaim(
|
|
314
|
+
params: ProviderRunnerLifecycleObservation,
|
|
315
|
+
): void {
|
|
316
|
+
if (params.durationMilliseconds < 0) return;
|
|
317
|
+
recordMetric(() =>
|
|
318
|
+
providerRunnerActivationToFirstClaimDuration.record(params.durationMilliseconds, {
|
|
319
|
+
provider: resolveProviderRunnerMetricProvider(params),
|
|
320
|
+
launch_kind: params.launchKind,
|
|
321
|
+
}),
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function recordJobExecutionQueueTime(params: JobExecutionQueueTimeObservation): void {
|
|
326
|
+
if (params.durationMilliseconds < 0) return;
|
|
327
|
+
recordMetric(() =>
|
|
328
|
+
jobExecutionQueueTimeDuration.record(params.durationMilliseconds, {
|
|
329
|
+
provider: params.provider ?? UNKNOWN_PROVIDER_KIND,
|
|
330
|
+
launch_kind: params.launchKind,
|
|
331
|
+
}),
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function recordProviderRunnerAssignmentRejected(params: {
|
|
336
|
+
reason: RunnerAssignmentRejectionReason;
|
|
337
|
+
surface: RunnerAssignmentSurface;
|
|
338
|
+
}): void {
|
|
339
|
+
recordMetric(() => providerRunnerAssignmentRejectedCount.add(1, params));
|
|
340
|
+
}
|
|
341
|
+
|
|
155
342
|
export function recordRunnerReservationPromotionFailure(
|
|
156
343
|
reason: RunnerReservationPromotionFailureReason,
|
|
157
344
|
): void {
|
|
@@ -2,11 +2,15 @@ const mocks = vi.hoisted(() => {
|
|
|
2
2
|
const gauges = {
|
|
3
3
|
enrolledRunnersWithoutRecentReport: {},
|
|
4
4
|
pendingJobExecutions: {},
|
|
5
|
+
providerRunnersByPhase: {},
|
|
6
|
+
providerRunnersByPhaseOldestAge: {},
|
|
5
7
|
runningJobExecutions: {},
|
|
6
8
|
};
|
|
7
9
|
const gaugeByName = {
|
|
8
10
|
runners_enrolled_without_recent_report: gauges.enrolledRunnersWithoutRecentReport,
|
|
9
11
|
runners_pending_job_executions: gauges.pendingJobExecutions,
|
|
12
|
+
runners_provider_runner_by_phase: gauges.providerRunnersByPhase,
|
|
13
|
+
runners_provider_runner_by_phase_oldest_age: gauges.providerRunnersByPhaseOldestAge,
|
|
10
14
|
runners_running_job_executions: gauges.runningJobExecutions,
|
|
11
15
|
};
|
|
12
16
|
return {
|
|
@@ -16,6 +20,7 @@ const mocks = vi.hoisted(() => {
|
|
|
16
20
|
gauges,
|
|
17
21
|
getMeter: vi.fn(),
|
|
18
22
|
getJobExecutionQueueDepth: vi.fn(),
|
|
23
|
+
listProviderRunnerByPhaseMetrics: vi.fn(),
|
|
19
24
|
getServiceMetricsProvider: vi.fn(),
|
|
20
25
|
};
|
|
21
26
|
});
|
|
@@ -31,6 +36,7 @@ vi.mock('#db/job-executions.js', () => ({
|
|
|
31
36
|
}));
|
|
32
37
|
vi.mock('#db/runner-instances.js', () => ({
|
|
33
38
|
countStaleEnrolledRunnerInstances: mocks.countStaleEnrolledRunnerInstances,
|
|
39
|
+
listProviderRunnerByPhaseMetrics: mocks.listProviderRunnerByPhaseMetrics,
|
|
34
40
|
}));
|
|
35
41
|
|
|
36
42
|
let registerRunnersServiceMetrics: typeof import('./service.js').registerRunnersServiceMetrics;
|
|
@@ -48,12 +54,14 @@ describe('registerRunnersServiceMetrics', () => {
|
|
|
48
54
|
mocks.countStaleEnrolledRunnerInstances.mockReset();
|
|
49
55
|
mocks.createObservableGauge.mockClear();
|
|
50
56
|
mocks.getJobExecutionQueueDepth.mockReset();
|
|
57
|
+
mocks.listProviderRunnerByPhaseMetrics.mockReset();
|
|
51
58
|
mocks.getMeter.mockReset();
|
|
52
59
|
mocks.getServiceMetricsProvider.mockReset();
|
|
53
60
|
mocks.getJobExecutionQueueDepth.mockResolvedValue({
|
|
54
61
|
pendingJobExecutions: 0,
|
|
55
62
|
runningJobExecutions: 0,
|
|
56
63
|
});
|
|
64
|
+
mocks.listProviderRunnerByPhaseMetrics.mockResolvedValue([]);
|
|
57
65
|
mocks.getMeter.mockReturnValue({
|
|
58
66
|
createObservableGauge: mocks.createObservableGauge,
|
|
59
67
|
addBatchObservableCallback: mocks.addBatchObservableCallback,
|
|
@@ -103,4 +111,34 @@ describe('registerRunnersServiceMetrics', () => {
|
|
|
103
111
|
expect(observer.observe).toHaveBeenCalledWith(mocks.gauges.runningJobExecutions, 4);
|
|
104
112
|
expect(observer.observe).toHaveBeenCalledTimes(2);
|
|
105
113
|
});
|
|
114
|
+
|
|
115
|
+
it('observes provider runners by lifecycle phase', async () => {
|
|
116
|
+
mocks.listProviderRunnerByPhaseMetrics.mockResolvedValue([
|
|
117
|
+
{
|
|
118
|
+
phase: 'assignment',
|
|
119
|
+
provider: 'ec2',
|
|
120
|
+
launchKind: 'demand',
|
|
121
|
+
count: 3,
|
|
122
|
+
oldestAgeMilliseconds: 42,
|
|
123
|
+
},
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
registerRunnersServiceMetrics();
|
|
127
|
+
const callback = mocks.addBatchObservableCallback.mock.calls[0]?.[0];
|
|
128
|
+
if (typeof callback !== 'function') throw new Error('Expected metrics callback');
|
|
129
|
+
const observer = {observe: vi.fn()};
|
|
130
|
+
|
|
131
|
+
await callback(observer);
|
|
132
|
+
|
|
133
|
+
expect(observer.observe).toHaveBeenCalledWith(mocks.gauges.providerRunnersByPhase, 3, {
|
|
134
|
+
phase: 'assignment',
|
|
135
|
+
provider: 'ec2',
|
|
136
|
+
launch_kind: 'demand',
|
|
137
|
+
});
|
|
138
|
+
expect(observer.observe).toHaveBeenCalledWith(
|
|
139
|
+
mocks.gauges.providerRunnersByPhaseOldestAge,
|
|
140
|
+
42,
|
|
141
|
+
{phase: 'assignment', provider: 'ec2', launch_kind: 'demand'},
|
|
142
|
+
);
|
|
143
|
+
});
|
|
106
144
|
});
|
package/src/metrics/service.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import {getServiceMetricsProvider} from '@shipfox/node-opentelemetry';
|
|
2
2
|
import {config} from '#config.js';
|
|
3
3
|
import {getJobExecutionQueueDepth} from '#db/job-executions.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
countStaleEnrolledRunnerInstances,
|
|
6
|
+
listProviderRunnerByPhaseMetrics,
|
|
7
|
+
type ProviderRunnerPhaseMetric,
|
|
8
|
+
} from '#db/runner-instances.js';
|
|
9
|
+
|
|
10
|
+
type ProviderRunnerPhaseLabels = {
|
|
11
|
+
phase: ProviderRunnerPhaseMetric['phase'];
|
|
12
|
+
provider: string;
|
|
13
|
+
launch_kind: ProviderRunnerPhaseMetric['launchKind'];
|
|
14
|
+
};
|
|
5
15
|
|
|
6
16
|
export function registerRunnersServiceMetrics(): void {
|
|
7
17
|
const meter = getServiceMetricsProvider().getMeter('runners');
|
|
@@ -19,15 +29,31 @@ export function registerRunnersServiceMetrics(): void {
|
|
|
19
29
|
'Running enrolled runners with a live control session, no workspace or runner session, and no recent provisioner report after the stale-runner grace window',
|
|
20
30
|
},
|
|
21
31
|
);
|
|
32
|
+
const providerRunnersByPhase = meter.createObservableGauge<ProviderRunnerPhaseLabels>(
|
|
33
|
+
'runners_provider_runner_by_phase',
|
|
34
|
+
{
|
|
35
|
+
description:
|
|
36
|
+
'Provider runners by lifecycle phase, including idle runners without an active assignment',
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
const providerRunnersByPhaseOldestAge = meter.createObservableGauge<ProviderRunnerPhaseLabels>(
|
|
40
|
+
'runners_provider_runner_by_phase_oldest_age',
|
|
41
|
+
{
|
|
42
|
+
description: 'Oldest provider runner age by lifecycle phase',
|
|
43
|
+
unit: 'ms',
|
|
44
|
+
},
|
|
45
|
+
);
|
|
22
46
|
|
|
23
47
|
meter.addBatchObservableCallback(
|
|
24
48
|
async (observer) => {
|
|
25
|
-
const [depthResult, staleEnrolledRunnerCountResult] =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
const [depthResult, staleEnrolledRunnerCountResult, providerRunnersByPhaseResult] =
|
|
50
|
+
await Promise.allSettled([
|
|
51
|
+
getJobExecutionQueueDepth(),
|
|
52
|
+
countStaleEnrolledRunnerInstances({
|
|
53
|
+
graceSeconds: config.RUNNER_STALE_PROVISIONED_RUNNER_THRESHOLD_SECONDS,
|
|
54
|
+
}),
|
|
55
|
+
listProviderRunnerByPhaseMetrics(),
|
|
56
|
+
]);
|
|
31
57
|
|
|
32
58
|
if (depthResult.status === 'fulfilled') {
|
|
33
59
|
observer.observe(pendingJobExecutions, depthResult.value.pendingJobExecutions);
|
|
@@ -36,7 +62,28 @@ export function registerRunnersServiceMetrics(): void {
|
|
|
36
62
|
if (staleEnrolledRunnerCountResult.status === 'fulfilled') {
|
|
37
63
|
observer.observe(enrolledRunnersWithoutRecentReport, staleEnrolledRunnerCountResult.value);
|
|
38
64
|
}
|
|
65
|
+
if (providerRunnersByPhaseResult.status === 'fulfilled') {
|
|
66
|
+
for (const metric of providerRunnersByPhaseResult.value) {
|
|
67
|
+
const attributes: ProviderRunnerPhaseLabels = {
|
|
68
|
+
phase: metric.phase,
|
|
69
|
+
provider: metric.provider,
|
|
70
|
+
launch_kind: metric.launchKind,
|
|
71
|
+
};
|
|
72
|
+
observer.observe(providerRunnersByPhase, metric.count, attributes);
|
|
73
|
+
observer.observe(
|
|
74
|
+
providerRunnersByPhaseOldestAge,
|
|
75
|
+
metric.oldestAgeMilliseconds,
|
|
76
|
+
attributes,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
39
80
|
},
|
|
40
|
-
[
|
|
81
|
+
[
|
|
82
|
+
pendingJobExecutions,
|
|
83
|
+
runningJobExecutions,
|
|
84
|
+
enrolledRunnersWithoutRecentReport,
|
|
85
|
+
providerRunnersByPhase,
|
|
86
|
+
providerRunnersByPhaseOldestAge,
|
|
87
|
+
],
|
|
41
88
|
);
|
|
42
89
|
}
|
|
@@ -7,12 +7,14 @@ import {
|
|
|
7
7
|
} from '@shipfox/api-runners-dto';
|
|
8
8
|
import {ClientError, defineRoute} from '@shipfox/node-fastify';
|
|
9
9
|
import {
|
|
10
|
+
getRunnerAssignmentRejectionReason,
|
|
10
11
|
ReservationExpiredError,
|
|
11
12
|
ReservationNotFoundError,
|
|
12
13
|
RunnerInstanceAlreadyAssignedError,
|
|
13
14
|
RunnerInstanceNotAssignableError,
|
|
14
15
|
} from '#core/errors.js';
|
|
15
16
|
import {assignRunnerInstances} from '#core/runner-assignments.js';
|
|
17
|
+
import {recordProviderRunnerAssignmentRejected} from '#metrics/instance.js';
|
|
16
18
|
|
|
17
19
|
export const assignRunnerInstancesRoute = defineRoute({
|
|
18
20
|
method: 'POST',
|
|
@@ -23,6 +25,12 @@ export const assignRunnerInstancesRoute = defineRoute({
|
|
|
23
25
|
response: {200: assignRunnerInstancesResponseSchema},
|
|
24
26
|
},
|
|
25
27
|
errorHandler: (error) => {
|
|
28
|
+
const rejectionReason = getRunnerAssignmentRejectionReason(error);
|
|
29
|
+
if (rejectionReason)
|
|
30
|
+
recordProviderRunnerAssignmentRejected({
|
|
31
|
+
reason: rejectionReason,
|
|
32
|
+
surface: 'provisioner',
|
|
33
|
+
});
|
|
26
34
|
if (error instanceof ReservationNotFoundError)
|
|
27
35
|
throw new ClientError('Reservation not found', 'reservation-not-found', {status: 404});
|
|
28
36
|
if (error instanceof ReservationExpiredError)
|