Skip to content

Commit d791acd

Browse files
committed
Expose usage info to the client
1 parent 7588fee commit d791acd

File tree

7 files changed

+119
-41
lines changed

7 files changed

+119
-41
lines changed

apps/webapp/app/env.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ const EnvironmentSchema = z.object({
199199
CENTS_PER_HOUR_MEDIUM_1X: z.coerce.number().positive().default(0),
200200
CENTS_PER_HOUR_MEDIUM_2X: z.coerce.number().positive().default(0),
201201
CENTS_PER_HOUR_LARGE_1X: z.coerce.number().positive().default(0),
202+
BASE_RUN_COST_IN_CENTS: z.coerce.number().positive().default(0),
202203

203204
USAGE_OPEN_METER_API_KEY: z.string().optional(),
204205
USAGE_OPEN_METER_BASE_URL: z.string().optional(),

apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { tracer } from "../tracer.server";
4242
import { generateJWTTokenForEnvironment } from "~/services/apiAuth.server";
4343
import { EnvironmentVariable } from "../environmentVariables/repository";
4444
import { machinePresetFromConfig } from "../machinePresets.server";
45+
import { env } from "~/env.server";
4546

4647
const WithTraceContext = z.object({
4748
traceparent: z.string().optional(),
@@ -411,6 +412,7 @@ export class SharedQueueConsumer {
411412
lockedById: backgroundTask.id,
412413
lockedToVersionId: deployment.worker.id,
413414
startedAt: existingTaskRun.startedAt ?? new Date(),
415+
baseCostInCents: env.BASE_RUN_COST_IN_CENTS,
414416
},
415417
include: {
416418
runtimeEnvironment: true,
@@ -1032,6 +1034,7 @@ class SharedQueueTasks {
10321034
idempotencyKey: taskRun.idempotencyKey ?? undefined,
10331035
durationMs: taskRun.usageDurationMs,
10341036
costInCents: taskRun.costInCents,
1037+
baseCostInCents: taskRun.baseCostInCents,
10351038
},
10361039
queue: {
10371040
id: queue.friendlyId,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "TaskRun" ADD COLUMN "baseCostInCents" DOUBLE PRECISION NOT NULL DEFAULT 0;

packages/database/prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,6 +1648,7 @@ model TaskRun {
16481648
16491649
usageDurationMs Int @default(0)
16501650
costInCents Float @default(0)
1651+
baseCostInCents Float @default(0)
16511652
16521653
lockedAt DateTime?
16531654
lockedBy BackgroundWorkerTask? @relation(fields: [lockedById], references: [id])

packages/trigger-sdk/src/v3/usage.ts

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ export type CurrentUsage = {
1212
total: ComputeUsage;
1313
};
1414
baseCostInCents: number;
15-
totalInCents: number;
15+
totalCostInCents: number;
1616
};
1717

1818
export const usage = {
1919
/**
20-
* Get the current usage of this task run attempt.
21-
*
22-
* @returns The current usage of this task run attempt.
20+
* Get the current running usage of this task run.
2321
*
2422
* @example
2523
*
@@ -33,35 +31,104 @@ export const usage = {
3331
*
3432
* const currentUsage = usage.getCurrent();
3533
*
36-
* console.log("Current cost and duration", {
37-
* cost: currentUsage.costInCents,
38-
* duration: currentUsage.durationMs,
34+
* // You have access to the current compute cost and duration up to this point
35+
* console.log("Current attempt compute cost and duration", {
36+
* cost: currentUsage.compute.attempt.costInCents,
37+
* duration: currentUsage.compute.attempt.durationMs,
38+
* });
39+
*
40+
* // You also can see the total compute cost and duration up to this point in the run, across all attempts
41+
* console.log("Current total compute cost and duration", {
42+
* cost: currentUsage.compute.total.costInCents,
43+
* duration: currentUsage.compute.total.durationMs,
3944
* });
4045
*
41-
* // Use ctx to access the total run cost and duration
42-
* console.log("Total cost and duration", {
43-
* cost: ctx.run.costInCents + currentUsage.costInCents,
44-
* duration: ctx.run.durationMs + currentUsage.durationMs,
46+
* // You can see the base cost of the run, which is the cost of the run before any compute costs
47+
* console.log("Total cost", {
48+
* cost: currentUsage.totalCostInCents,
49+
* baseCost: currentUsage.baseCostInCents,
4550
* });
4651
* },
4752
* });
4853
* ```
4954
*/
5055
getCurrent: (): CurrentUsage => {
5156
const sample = usageApi.sample();
57+
const machine = taskContext.ctx?.machine;
58+
const run = taskContext.ctx?.run;
5259

5360
if (!sample) {
5461
return {
55-
costInCents: 0,
56-
durationMs: 0,
62+
compute: {
63+
attempt: {
64+
costInCents: 0,
65+
durationMs: 0,
66+
},
67+
total: {
68+
costInCents: run?.costInCents ?? 0,
69+
durationMs: run?.durationMs ?? 0,
70+
},
71+
},
72+
baseCostInCents: run?.baseCostInCents ?? 0,
73+
totalCostInCents: (run?.costInCents ?? 0) + (run?.baseCostInCents ?? 0),
5774
};
5875
}
5976

77+
const currentCostInCents = machine?.centsPerMs ? sample.cpuTime * machine.centsPerMs : 0;
78+
79+
return {
80+
compute: {
81+
attempt: {
82+
costInCents: currentCostInCents,
83+
durationMs: sample.cpuTime,
84+
},
85+
total: {
86+
costInCents: (run?.costInCents ?? 0) + currentCostInCents,
87+
durationMs: (run?.durationMs ?? 0) + sample.cpuTime,
88+
},
89+
},
90+
baseCostInCents: run?.baseCostInCents ?? 0,
91+
totalCostInCents: (run?.costInCents ?? 0) + currentCostInCents + (run?.baseCostInCents ?? 0),
92+
};
93+
},
94+
/**
95+
* Measure the cost and duration of a function.
96+
*
97+
* @example
98+
*
99+
* ```typescript
100+
* import { usage } from "@trigger.dev/sdk/v3";
101+
*
102+
* export const myTask = task({
103+
* id: "my-task",
104+
* run: async (payload, { ctx }) => {
105+
* const { result, usage } = await usage.measure(async () => {
106+
* // Do some work
107+
* return "result";
108+
* });
109+
*
110+
* console.log("Result", result);
111+
* console.log("Cost and duration", { cost: usage.costInCents, duration: usage.durationMs });
112+
* },
113+
* });
114+
* ```
115+
*/
116+
measure: async <T>(cb: () => Promise<T>): Promise<{ result: T; usage: ComputeUsage }> => {
117+
const measurement = usageApi.start();
118+
119+
const result = await cb();
120+
121+
const sample = usageApi.stop(measurement);
60122
const machine = taskContext.ctx?.machine;
61123

124+
const costInCents = machine?.centsPerMs ? sample.cpuTime * machine.centsPerMs : 0;
125+
62126
return {
63-
costInCents: machine?.centsPerMs ? sample.cpuTime * machine.centsPerMs : 0,
64-
durationMs: sample.cpuTime,
127+
result,
128+
usage: {
129+
costInCents,
130+
durationMs: sample.cpuTime,
131+
},
65132
};
66133
},
67134
};

references/v3-catalog/src/trigger/longRunning.ts

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,14 @@
1-
import { logger, task, wait, usage } from "@trigger.dev/sdk/v3";
1+
import { logger, task, wait } from "@trigger.dev/sdk/v3";
22

33
export const longRunning = task({
44
id: "long-running",
5-
machine: {
6-
preset: "medium-2x",
7-
},
85
run: async (payload: { message: string }, { ctx }) => {
96
logger.info("Long running payloadddd", { payload });
107

11-
if (ctx.machine) {
12-
logger.info("Machine preset", { preset: ctx.machine });
13-
}
14-
15-
logger.info("Cost and duration", { cost: ctx.run.costInCents, duration: ctx.run.durationMs });
16-
178
// Wait for 3 minutes
189
await new Promise((resolve) => setTimeout(resolve, 5000));
1910

20-
let currentUsage = usage.getCurrent();
21-
22-
logger.info("Current Cost and duration (before wait)", {
23-
cost: currentUsage.costInCents,
24-
duration: currentUsage.durationMs,
25-
});
26-
2711
await wait.for({ seconds: 5 });
28-
29-
currentUsage = usage.getCurrent();
30-
31-
logger.info("Current Cost and duration (after wait)", {
32-
cost: currentUsage.costInCents,
33-
duration: currentUsage.durationMs,
34-
});
35-
36-
throw new Error(`This is an error at ${new Date().toISOString()}`);
3712
},
3813
});
3914

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { logger, task, wait, usage } from "@trigger.dev/sdk/v3";
2+
3+
export const usagePlayground = task({
4+
id: "usage-playground",
5+
machine: {
6+
preset: "medium-2x",
7+
},
8+
run: async (payload: { duration: number }, { ctx }) => {
9+
if (ctx.machine) {
10+
logger.info("Machine preset", { preset: ctx.machine });
11+
}
12+
13+
logger.info("Cost and duration", { cost: ctx.run.costInCents, duration: ctx.run.durationMs });
14+
15+
await new Promise((resolve) => setTimeout(resolve, payload.duration));
16+
17+
let currentUsage = usage.getCurrent();
18+
19+
logger.info("Current Cost and duration (before wait)", { currentUsage });
20+
21+
await wait.for({ seconds: 5 });
22+
23+
currentUsage = usage.getCurrent();
24+
25+
logger.info("Current Cost and duration (after wait)", { currentUsage });
26+
27+
throw new Error(`This is an error at ${new Date().toISOString()}`);
28+
},
29+
});

0 commit comments

Comments
 (0)