Skip to content

Commit 93bdf83

Browse files
committed
feat(webapp): trace compute template creation at deploy
1 parent a90a495 commit 93bdf83

1 file changed

Lines changed: 115 additions & 67 deletions

File tree

apps/webapp/app/v3/services/computeTemplateCreation.server.ts

Lines changed: 115 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,26 @@ import { isOrgMigrated } from "~/runEngine/concerns/computeMigration.server";
1313
import { backingForQueue, workerRegionRegistry } from "~/v3/workerRegions.server";
1414
import { globalFlagsRegistry } from "~/v3/globalFlagsRegistry.server";
1515
import { getEntitlement } from "~/services/platform.v3.server";
16+
import { startActiveSpan, attributesFromAuthenticatedEnv } from "~/v3/tracer.server";
1617

1718
type TemplateCreationMode = "required" | "shadow" | "skip";
1819

20+
// Why the mode was chosen — slices the compute.template.create span by path.
21+
type TemplateModeReason =
22+
| "no-client"
23+
| "no-project"
24+
| "microvm-native"
25+
| "migrated"
26+
| "compute-access"
27+
| "rollout"
28+
| "none";
29+
30+
type ResolvedTemplateMode = {
31+
mode: TemplateCreationMode;
32+
migrated: boolean;
33+
reason: TemplateModeReason;
34+
};
35+
1936
type ResolvedPreset = {
2037
name: MachinePresetName;
2138
cpu: number;
@@ -60,89 +77,116 @@ export class ComputeTemplateCreationService {
6077
prisma: PrismaClientOrTransaction;
6178
writer?: WritableStreamDefaultWriter;
6279
}): Promise<void> {
63-
const mode = await this.resolveMode(options.projectId, options.prisma);
80+
return startActiveSpan("compute.template.create", async (span) => {
81+
const { mode, migrated, reason } = await this.resolveMode(
82+
options.projectId,
83+
options.prisma
84+
);
6485

65-
if (mode === "skip") {
66-
return;
67-
}
86+
span.setAttributes({
87+
...attributesFromAuthenticatedEnv(options.authenticatedEnv),
88+
"compute.template.mode": mode,
89+
"compute.template.migrated": migrated,
90+
"compute.template.reason": reason,
91+
"compute.template.deployment_id": options.deploymentFriendlyId,
92+
"compute.template.presets_total": this.presets.length,
93+
"compute.template.presets_required": this.requiredPresets.size,
94+
});
6895

69-
if (mode === "shadow") {
70-
this.createTemplate(options.imageReference, { background: true })
71-
.then((outcome) => {
72-
if (outcome.error) {
73-
logger.error("Shadow template creation failed", {
96+
if (mode === "skip") {
97+
span.setAttribute("compute.template.result", "skipped");
98+
return;
99+
}
100+
101+
if (mode === "shadow") {
102+
// Shadow is fire-and-forget (background build), so the span only records
103+
// that it was dispatched — the build outcome lands server-side later.
104+
span.setAttribute("compute.template.result", "shadow_dispatched");
105+
this.createTemplate(options.imageReference, { background: true })
106+
.then((outcome) => {
107+
if (outcome.error) {
108+
logger.error("Shadow template creation failed", {
109+
id: options.deploymentFriendlyId,
110+
imageReference: options.imageReference,
111+
error: outcome.error,
112+
});
113+
}
114+
})
115+
.catch((error) => {
116+
logger.error("Shadow template creation threw unexpectedly", {
74117
id: options.deploymentFriendlyId,
75118
imageReference: options.imageReference,
76-
error: outcome.error,
119+
error: error instanceof Error ? error.message : String(error),
77120
});
78-
}
79-
})
80-
.catch((error) => {
81-
logger.error("Shadow template creation threw unexpectedly", {
82-
id: options.deploymentFriendlyId,
83-
imageReference: options.imageReference,
84-
error: error instanceof Error ? error.message : String(error),
85121
});
86-
});
87-
return;
88-
}
89-
90-
// Required mode
91-
if (options.writer) {
92-
try {
93-
await options.writer.write(
94-
`event: log\ndata: ${JSON.stringify({ message: "Building compute template..." })}\n\n`
95-
);
96-
} catch {
97-
// Stream may be closed if client disconnected - continue with template creation
122+
return;
98123
}
99-
}
100-
101-
logger.info("Creating compute template (required mode)", {
102-
id: options.deploymentFriendlyId,
103-
imageReference: options.imageReference,
104-
presets: this.presets.map((p) => p.name),
105-
requiredPresets: [...this.requiredPresets],
106-
});
107124

108-
const outcome = await this.createTemplate(options.imageReference);
109-
const failureMessage = this.failureMessageForRequiredMode(
110-
outcome,
111-
options.deploymentFriendlyId,
112-
options.imageReference
113-
);
125+
// Required mode
126+
if (options.writer) {
127+
try {
128+
await options.writer.write(
129+
`event: log\ndata: ${JSON.stringify({ message: "Building compute template..." })}\n\n`
130+
);
131+
} catch {
132+
// Stream may be closed if client disconnected - continue with template creation
133+
}
134+
}
114135

115-
if (failureMessage) {
116-
logger.error("Compute template creation failed", {
136+
logger.info("Creating compute template (required mode)", {
117137
id: options.deploymentFriendlyId,
118138
imageReference: options.imageReference,
119-
error: failureMessage,
139+
presets: this.presets.map((p) => p.name),
140+
requiredPresets: [...this.requiredPresets],
120141
});
121142

122-
const failService = new FailDeploymentService();
123-
await failService.call(options.authenticatedEnv, options.deploymentFriendlyId, {
124-
error: {
125-
name: "TemplateCreationFailed",
126-
message: `Failed to create compute template: ${failureMessage}`,
127-
},
128-
});
143+
const outcome = await this.createTemplate(options.imageReference);
144+
span.setAttribute("compute.template.presets_built", outcome.results.length);
129145

130-
throw new ServiceValidationError(`Compute template creation failed: ${failureMessage}`);
131-
}
146+
const failureMessage = this.failureMessageForRequiredMode(
147+
outcome,
148+
options.deploymentFriendlyId,
149+
options.imageReference
150+
);
151+
152+
if (failureMessage) {
153+
span.setAttributes({
154+
"compute.template.result": "failed",
155+
"compute.template.failure": failureMessage,
156+
});
132157

133-
logger.info("Compute template created", {
134-
id: options.deploymentFriendlyId,
135-
imageReference: options.imageReference,
136-
results: outcome.results.length,
158+
logger.error("Compute template creation failed", {
159+
id: options.deploymentFriendlyId,
160+
imageReference: options.imageReference,
161+
error: failureMessage,
162+
});
163+
164+
const failService = new FailDeploymentService();
165+
await failService.call(options.authenticatedEnv, options.deploymentFriendlyId, {
166+
error: {
167+
name: "TemplateCreationFailed",
168+
message: `Failed to create compute template: ${failureMessage}`,
169+
},
170+
});
171+
172+
throw new ServiceValidationError(`Compute template creation failed: ${failureMessage}`);
173+
}
174+
175+
span.setAttribute("compute.template.result", "created");
176+
logger.info("Compute template created", {
177+
id: options.deploymentFriendlyId,
178+
imageReference: options.imageReference,
179+
results: outcome.results.length,
180+
});
137181
});
138182
}
139183

140184
async resolveMode(
141185
projectId: string,
142186
prisma: PrismaClientOrTransaction
143-
): Promise<TemplateCreationMode> {
187+
): Promise<ResolvedTemplateMode> {
144188
if (!this.client) {
145-
return "skip";
189+
return { mode: "skip", migrated: false, reason: "no-client" };
146190
}
147191

148192
const project = await prisma.project.findFirst({
@@ -158,11 +202,11 @@ export class ComputeTemplateCreationService {
158202
});
159203

160204
if (!project) {
161-
return "skip";
205+
return { mode: "skip", migrated: false, reason: "no-project" };
162206
}
163207

164208
if (project.defaultWorkerGroup?.workloadType === "MICROVM") {
165-
return "required";
209+
return { mode: "required", migrated: false, reason: "microvm-native" };
166210
}
167211

168212
// Migrated orgs route runs to the compute backing even though their stored
@@ -194,22 +238,26 @@ export class ComputeTemplateCreationService {
194238
}
195239
if (migrated) {
196240
// required => template built at deploy (deploy fails on error); off => shadow.
197-
return decision.flags?.computeMigrationRequireTemplate ? "required" : "shadow";
241+
return {
242+
mode: decision.flags?.computeMigrationRequireTemplate ? "required" : "shadow",
243+
migrated: true,
244+
reason: "migrated",
245+
};
198246
}
199247
}
200248

201249
const hasComputeAccess = await resolveComputeAccess(prisma, project.organization.featureFlags);
202250

203251
if (hasComputeAccess) {
204-
return "shadow";
252+
return { mode: "shadow", migrated: false, reason: "compute-access" };
205253
}
206254

207255
const rolloutPct = Number(env.COMPUTE_TEMPLATE_SHADOW_ROLLOUT_PCT ?? "0");
208256
if (rolloutPct > 0 && Math.random() * 100 < rolloutPct) {
209-
return "shadow";
257+
return { mode: "shadow", migrated: false, reason: "rollout" };
210258
}
211259

212-
return "skip";
260+
return { mode: "skip", migrated: false, reason: "none" };
213261
}
214262

215263
async createTemplate(

0 commit comments

Comments
 (0)