Skip to content

Commit 5218d3d

Browse files
committed
feat(supervisor): instrument outbound worker api client requests
1 parent fec93a0 commit 5218d3d

4 files changed

Lines changed: 71 additions & 14 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/core": patch
3+
---
4+
5+
Add an optional `onHttpRequestComplete` callback to the supervisor worker API client so hosts can record metrics for its outbound requests (method, response status, and outcome).

apps/supervisor/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ class ManagedSupervisor {
329329
runNotificationsEnabled: env.TRIGGER_WORKLOAD_API_ENABLED,
330330
heartbeatIntervalSeconds: env.TRIGGER_WORKER_HEARTBEAT_INTERVAL_SECONDS,
331331
sendRunDebugLogs: env.SEND_RUN_DEBUG_LOGS,
332+
onHttpRequestComplete: (metric) => outboundRequestsTotal.inc(metric),
332333
preDequeue: async () => {
333334
// Synchronous, hot-path-safe cached read; false when no monitors are active.
334335
const skipForBackpressure = this.backpressureMonitors.some((m) => m.shouldSkipDequeue());

packages/core/src/v3/runEngineWorker/supervisor/http.ts

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import {
2121
WorkerApiSuspendRunResponseBody,
2222
WorkerApiRunSnapshotsSinceResponseBody,
2323
} from "./schemas.js";
24-
import type { SupervisorClientCommonOptions } from "./types.js";
24+
import type { SupervisorClientCommonOptions, SupervisorHttpRequestMetric } from "./types.js";
2525
import { getDefaultWorkerHeaders } from "./util.js";
26-
import { wrapZodFetch } from "../../zodfetch.js";
26+
import { wrapZodFetch, type ApiResult, type ZodFetchOptions } from "../../zodfetch.js";
2727
import { createHeaders } from "../util.js";
2828
import { WORKER_HEADERS } from "../consts.js";
2929
import { SimpleStructuredLogger } from "../../utils/structuredLogger.js";
@@ -36,6 +36,7 @@ export class SupervisorHttpClient {
3636
private readonly instanceName: string;
3737
private readonly defaultHeaders: Record<string, string>;
3838
private readonly sendRunDebugLogs: boolean;
39+
private readonly onHttpRequestComplete?: (metric: SupervisorHttpRequestMetric) => void;
3940

4041
private readonly logger = new SimpleStructuredLogger("supervisor-http-client");
4142

@@ -45,6 +46,7 @@ export class SupervisorHttpClient {
4546
this.instanceName = opts.instanceName;
4647
this.defaultHeaders = getDefaultWorkerHeaders(opts);
4748
this.sendRunDebugLogs = opts.sendRunDebugLogs ?? false;
49+
this.onHttpRequestComplete = opts.onHttpRequestComplete;
4850

4951
if (!this.apiUrl) {
5052
throw new Error("apiURL is required and needs to be a non-empty string");
@@ -59,8 +61,38 @@ export class SupervisorHttpClient {
5961
}
6062
}
6163

64+
private async request<T extends z.ZodTypeAny>(
65+
name: string,
66+
schema: T,
67+
url: string,
68+
requestInit?: RequestInit,
69+
options?: ZodFetchOptions<z.output<T>>
70+
): Promise<ApiResult<z.infer<T>>> {
71+
const result = await wrapZodFetch(schema, url, requestInit, options);
72+
73+
if (this.onHttpRequestComplete) {
74+
const method = requestInit?.method ?? "GET";
75+
76+
if (result.success) {
77+
this.onHttpRequestComplete({ name, method, status: "2xx", outcome: "ok" });
78+
} else if (typeof result.statusCode === "number") {
79+
this.onHttpRequestComplete({
80+
name,
81+
method,
82+
status: String(result.statusCode),
83+
outcome: "http_error",
84+
});
85+
} else {
86+
this.onHttpRequestComplete({ name, method, status: "none", outcome: "network_error" });
87+
}
88+
}
89+
90+
return result;
91+
}
92+
6293
async connect(body: WorkerApiConnectRequestBody) {
63-
return wrapZodFetch(
94+
return this.request(
95+
"connect",
6496
WorkerApiConnectResponseBody,
6597
`${this.apiUrl}/engine/v1/worker-actions/connect`,
6698
{
@@ -75,7 +107,8 @@ export class SupervisorHttpClient {
75107
}
76108

77109
async dequeue(body: WorkerApiDequeueRequestBody) {
78-
return wrapZodFetch(
110+
return this.request(
111+
"dequeue",
79112
WorkerApiDequeueResponseBody,
80113
`${this.apiUrl}/engine/v1/worker-actions/dequeue`,
81114
{
@@ -91,7 +124,8 @@ export class SupervisorHttpClient {
91124

92125
/** @deprecated Not currently used */
93126
async dequeueFromVersion(deploymentId: string, maxRunCount = 1, runnerId?: string) {
94-
return wrapZodFetch(
127+
return this.request(
128+
"dequeue_from_version",
95129
WorkerApiDequeueResponseBody,
96130
`${this.apiUrl}/engine/v1/worker-actions/deployments/${deploymentId}/dequeue?maxRunCount=${maxRunCount}`,
97131
{
@@ -104,7 +138,8 @@ export class SupervisorHttpClient {
104138
}
105139

106140
async heartbeatWorker(body: WorkerApiHeartbeatRequestBody) {
107-
return wrapZodFetch(
141+
return this.request(
142+
"heartbeat_worker",
108143
WorkerApiHeartbeatResponseBody,
109144
`${this.apiUrl}/engine/v1/worker-actions/heartbeat`,
110145
{
@@ -124,7 +159,8 @@ export class SupervisorHttpClient {
124159
body: WorkerApiRunHeartbeatRequestBody,
125160
runnerId?: string
126161
) {
127-
return wrapZodFetch(
162+
return this.request(
163+
"heartbeat_run",
128164
WorkerApiRunHeartbeatResponseBody,
129165
`${this.apiUrl}/engine/v1/worker-actions/runs/${runId}/snapshots/${snapshotId}/heartbeat`,
130166
{
@@ -146,7 +182,8 @@ export class SupervisorHttpClient {
146182
runnerId?: string,
147183
environmentId?: string
148184
) {
149-
return wrapZodFetch(
185+
return this.request(
186+
"start_run_attempt",
150187
WorkerApiRunAttemptStartResponseBody,
151188
`${this.apiUrl}/engine/v1/worker-actions/runs/${runId}/snapshots/${snapshotId}/attempts/start`,
152189
{
@@ -168,7 +205,8 @@ export class SupervisorHttpClient {
168205
runnerId?: string,
169206
environmentId?: string
170207
) {
171-
return wrapZodFetch(
208+
return this.request(
209+
"complete_run_attempt",
172210
WorkerApiRunAttemptCompleteResponseBody,
173211
`${this.apiUrl}/engine/v1/worker-actions/runs/${runId}/snapshots/${snapshotId}/attempts/complete`,
174212
{
@@ -184,7 +222,8 @@ export class SupervisorHttpClient {
184222
}
185223

186224
async getLatestSnapshot(runId: string, runnerId?: string, environmentId?: string) {
187-
return wrapZodFetch(
225+
return this.request(
226+
"get_latest_snapshot",
188227
WorkerApiRunLatestSnapshotResponseBody,
189228
`${this.apiUrl}/engine/v1/worker-actions/runs/${runId}/snapshots/latest`,
190229
{
@@ -204,7 +243,8 @@ export class SupervisorHttpClient {
204243
runnerId?: string,
205244
environmentId?: string
206245
) {
207-
return wrapZodFetch(
246+
return this.request(
247+
"get_snapshots_since",
208248
WorkerApiRunSnapshotsSinceResponseBody,
209249
`${this.apiUrl}/engine/v1/worker-actions/runs/${runId}/snapshots/since/${snapshotId}`,
210250
{
@@ -224,7 +264,8 @@ export class SupervisorHttpClient {
224264
}
225265

226266
try {
227-
const res = await wrapZodFetch(
267+
const res = await this.request(
268+
"send_debug_log",
228269
z.unknown(),
229270
`${this.apiUrl}/engine/v1/worker-actions/runs/${runId}/logs/debug`,
230271
{
@@ -252,7 +293,8 @@ export class SupervisorHttpClient {
252293
runnerId?: string,
253294
environmentId?: string
254295
) {
255-
return wrapZodFetch(
296+
return this.request(
297+
"continue_run_execution",
256298
WorkerApiContinueRunExecutionRequestBody,
257299
`${this.apiUrl}/engine/v1/worker-actions/runs/${runId}/snapshots/${snapshotId}/continue`,
258300
{
@@ -292,7 +334,8 @@ export class SupervisorHttpClient {
292334
runnerId?: string;
293335
body: WorkerApiSuspendRunRequestBody;
294336
}) {
295-
return wrapZodFetch(
337+
return this.request(
338+
"submit_suspend_completion",
296339
WorkerApiSuspendRunResponseBody,
297340
`${this.apiUrl}/engine/v1/worker-actions/runs/${runId}/snapshots/${snapshotId}/suspend`,
298341
{

packages/core/src/v3/runEngineWorker/supervisor/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import type { MachineResources } from "../../schemas/runEngine.js";
22

3+
export type SupervisorHttpRequestMetric = {
4+
name: string;
5+
method: string;
6+
status: string;
7+
outcome: "ok" | "http_error" | "network_error";
8+
};
9+
310
export type SupervisorClientCommonOptions = {
411
apiUrl: string;
512
workerToken: string;
613
instanceName: string;
714
deploymentId?: string;
815
managedWorkerSecret?: string;
916
sendRunDebugLogs?: boolean;
17+
onHttpRequestComplete?: (metric: SupervisorHttpRequestMetric) => void;
1018
};
1119

1220
export type PreDequeueFn = () => Promise<{

0 commit comments

Comments
 (0)