@trigger.dev/sdk 4.5.0-rc.5 → 4.5.0-rc.7

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.
Files changed (213) hide show
  1. package/dist/commonjs/v3/ai.d.ts +178 -5
  2. package/dist/commonjs/v3/ai.js +603 -119
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat-client.js +3 -0
  5. package/dist/commonjs/v3/chat-client.js.map +1 -1
  6. package/dist/commonjs/v3/chat-react.js +10 -7
  7. package/dist/commonjs/v3/chat-react.js.map +1 -1
  8. package/dist/commonjs/v3/chat-server.d.ts +8 -0
  9. package/dist/commonjs/v3/chat-server.js +32 -10
  10. package/dist/commonjs/v3/chat-server.js.map +1 -1
  11. package/dist/commonjs/v3/chat-server.test.js +51 -0
  12. package/dist/commonjs/v3/chat-server.test.js.map +1 -1
  13. package/dist/commonjs/v3/chat.js +34 -6
  14. package/dist/commonjs/v3/chat.js.map +1 -1
  15. package/dist/commonjs/v3/chat.test.js +53 -0
  16. package/dist/commonjs/v3/chat.test.js.map +1 -1
  17. package/dist/commonjs/v3/createStartSessionAction.test.js +30 -0
  18. package/dist/commonjs/v3/createStartSessionAction.test.js.map +1 -1
  19. package/dist/commonjs/v3/sessions.d.ts +11 -6
  20. package/dist/commonjs/v3/sessions.js +10 -5
  21. package/dist/commonjs/v3/sessions.js.map +1 -1
  22. package/dist/commonjs/v3/test/mock-chat-agent.d.ts +6 -0
  23. package/dist/commonjs/v3/test/mock-chat-agent.js +1 -0
  24. package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
  25. package/dist/commonjs/version.js +1 -1
  26. package/dist/esm/v3/ai.d.ts +178 -5
  27. package/dist/esm/v3/ai.js +603 -120
  28. package/dist/esm/v3/ai.js.map +1 -1
  29. package/dist/esm/v3/chat-client.js +3 -0
  30. package/dist/esm/v3/chat-client.js.map +1 -1
  31. package/dist/esm/v3/chat-react.js +10 -7
  32. package/dist/esm/v3/chat-react.js.map +1 -1
  33. package/dist/esm/v3/chat-server.d.ts +8 -0
  34. package/dist/esm/v3/chat-server.js +32 -10
  35. package/dist/esm/v3/chat-server.js.map +1 -1
  36. package/dist/esm/v3/chat-server.test.js +51 -0
  37. package/dist/esm/v3/chat-server.test.js.map +1 -1
  38. package/dist/esm/v3/chat.js +34 -6
  39. package/dist/esm/v3/chat.js.map +1 -1
  40. package/dist/esm/v3/chat.test.js +53 -0
  41. package/dist/esm/v3/chat.test.js.map +1 -1
  42. package/dist/esm/v3/createStartSessionAction.test.js +30 -0
  43. package/dist/esm/v3/createStartSessionAction.test.js.map +1 -1
  44. package/dist/esm/v3/sessions.d.ts +11 -6
  45. package/dist/esm/v3/sessions.js +10 -5
  46. package/dist/esm/v3/sessions.js.map +1 -1
  47. package/dist/esm/v3/test/mock-chat-agent.d.ts +6 -0
  48. package/dist/esm/v3/test/mock-chat-agent.js +1 -0
  49. package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
  50. package/dist/esm/version.js +1 -1
  51. package/docs/ai/prompts.mdx +430 -0
  52. package/docs/ai-chat/actions.mdx +115 -0
  53. package/docs/ai-chat/anatomy.mdx +71 -0
  54. package/docs/ai-chat/backend.mdx +817 -0
  55. package/docs/ai-chat/background-injection.mdx +221 -0
  56. package/docs/ai-chat/changelog.mdx +850 -0
  57. package/docs/ai-chat/chat-local.mdx +174 -0
  58. package/docs/ai-chat/client-protocol.mdx +1081 -0
  59. package/docs/ai-chat/compaction.mdx +411 -0
  60. package/docs/ai-chat/custom-agents.mdx +364 -0
  61. package/docs/ai-chat/error-handling.mdx +415 -0
  62. package/docs/ai-chat/fast-starts.mdx +672 -0
  63. package/docs/ai-chat/frontend.mdx +580 -0
  64. package/docs/ai-chat/how-it-works.mdx +230 -0
  65. package/docs/ai-chat/lifecycle-hooks.mdx +530 -0
  66. package/docs/ai-chat/mcp.mdx +101 -0
  67. package/docs/ai-chat/overview.mdx +90 -0
  68. package/docs/ai-chat/patterns/branching-conversations.mdx +284 -0
  69. package/docs/ai-chat/patterns/code-sandbox.mdx +126 -0
  70. package/docs/ai-chat/patterns/database-persistence.mdx +414 -0
  71. package/docs/ai-chat/patterns/human-in-the-loop.mdx +275 -0
  72. package/docs/ai-chat/patterns/large-payloads.mdx +169 -0
  73. package/docs/ai-chat/patterns/oom-resilience.mdx +120 -0
  74. package/docs/ai-chat/patterns/persistence-and-replay.mdx +211 -0
  75. package/docs/ai-chat/patterns/recovery-boot.mdx +230 -0
  76. package/docs/ai-chat/patterns/skills.mdx +221 -0
  77. package/docs/ai-chat/patterns/sub-agents.mdx +383 -0
  78. package/docs/ai-chat/patterns/tool-result-auditing.mdx +148 -0
  79. package/docs/ai-chat/patterns/trusted-edge-signals.mdx +337 -0
  80. package/docs/ai-chat/patterns/version-upgrades.mdx +172 -0
  81. package/docs/ai-chat/pending-messages.mdx +343 -0
  82. package/docs/ai-chat/prompt-caching.mdx +206 -0
  83. package/docs/ai-chat/quick-start.mdx +161 -0
  84. package/docs/ai-chat/reference.mdx +909 -0
  85. package/docs/ai-chat/server-chat.mdx +263 -0
  86. package/docs/ai-chat/sessions.mdx +333 -0
  87. package/docs/ai-chat/testing.mdx +682 -0
  88. package/docs/ai-chat/tools.mdx +191 -0
  89. package/docs/ai-chat/types.mdx +242 -0
  90. package/docs/ai-chat/upgrade-guide.mdx +515 -0
  91. package/docs/apikeys.mdx +54 -0
  92. package/docs/building-with-ai.mdx +261 -0
  93. package/docs/bulk-actions.mdx +49 -0
  94. package/docs/changelog.mdx +6 -0
  95. package/docs/cli-deploy-commands.mdx +9 -0
  96. package/docs/cli-dev-commands.mdx +9 -0
  97. package/docs/cli-dev.mdx +8 -0
  98. package/docs/cli-init-commands.mdx +58 -0
  99. package/docs/cli-introduction.mdx +25 -0
  100. package/docs/cli-list-profiles-commands.mdx +42 -0
  101. package/docs/cli-login-commands.mdx +33 -0
  102. package/docs/cli-logout-commands.mdx +33 -0
  103. package/docs/cli-preview-archive.mdx +59 -0
  104. package/docs/cli-promote-commands.mdx +9 -0
  105. package/docs/cli-switch.mdx +43 -0
  106. package/docs/cli-update-commands.mdx +42 -0
  107. package/docs/cli-whoami-commands.mdx +33 -0
  108. package/docs/community.mdx +6 -0
  109. package/docs/config/config-file.mdx +602 -0
  110. package/docs/config/extensions/additionalFiles.mdx +38 -0
  111. package/docs/config/extensions/additionalPackages.mdx +40 -0
  112. package/docs/config/extensions/aptGet.mdx +34 -0
  113. package/docs/config/extensions/audioWaveform.mdx +20 -0
  114. package/docs/config/extensions/custom.mdx +380 -0
  115. package/docs/config/extensions/emitDecoratorMetadata.mdx +29 -0
  116. package/docs/config/extensions/esbuildPlugin.mdx +31 -0
  117. package/docs/config/extensions/ffmpeg.mdx +45 -0
  118. package/docs/config/extensions/lightpanda.mdx +56 -0
  119. package/docs/config/extensions/overview.mdx +67 -0
  120. package/docs/config/extensions/playwright.mdx +195 -0
  121. package/docs/config/extensions/prismaExtension.mdx +1014 -0
  122. package/docs/config/extensions/puppeteer.mdx +30 -0
  123. package/docs/config/extensions/pythonExtension.mdx +182 -0
  124. package/docs/config/extensions/syncEnvVars.mdx +291 -0
  125. package/docs/context.mdx +235 -0
  126. package/docs/database-connections.mdx +213 -0
  127. package/docs/deploy-environment-variables.mdx +435 -0
  128. package/docs/deployment/atomic-deployment.mdx +172 -0
  129. package/docs/deployment/overview.mdx +257 -0
  130. package/docs/deployment/preview-branches.mdx +224 -0
  131. package/docs/errors-retrying.mdx +379 -0
  132. package/docs/github-actions.mdx +222 -0
  133. package/docs/github-integration.mdx +136 -0
  134. package/docs/github-repo.mdx +8 -0
  135. package/docs/help-email.mdx +6 -0
  136. package/docs/help-slack.mdx +11 -0
  137. package/docs/hidden-tasks.mdx +56 -0
  138. package/docs/how-it-works.mdx +454 -0
  139. package/docs/how-to-reduce-your-spend.mdx +217 -0
  140. package/docs/idempotency.mdx +504 -0
  141. package/docs/introduction.mdx +223 -0
  142. package/docs/limits.mdx +241 -0
  143. package/docs/logging.mdx +195 -0
  144. package/docs/machines.mdx +952 -0
  145. package/docs/manual-setup.mdx +632 -0
  146. package/docs/mcp-agent-rules.mdx +41 -0
  147. package/docs/mcp-introduction.mdx +385 -0
  148. package/docs/mcp-tools.mdx +273 -0
  149. package/docs/migrating-from-v3.mdx +334 -0
  150. package/docs/observability/dashboards.mdx +102 -0
  151. package/docs/observability/query.mdx +585 -0
  152. package/docs/open-source-contributing.mdx +16 -0
  153. package/docs/open-source-self-hosting.mdx +541 -0
  154. package/docs/private-networking/aws-console-setup.mdx +304 -0
  155. package/docs/private-networking/overview.mdx +144 -0
  156. package/docs/private-networking/troubleshooting.mdx +78 -0
  157. package/docs/queue-concurrency.mdx +354 -0
  158. package/docs/quick-start.mdx +97 -0
  159. package/docs/realtime/auth.mdx +208 -0
  160. package/docs/realtime/backend/overview.mdx +45 -0
  161. package/docs/realtime/backend/streams.mdx +418 -0
  162. package/docs/realtime/backend/subscribe.mdx +225 -0
  163. package/docs/realtime/how-it-works.mdx +94 -0
  164. package/docs/realtime/overview.mdx +63 -0
  165. package/docs/realtime/react-hooks/overview.mdx +73 -0
  166. package/docs/realtime/react-hooks/streams.mdx +449 -0
  167. package/docs/realtime/react-hooks/subscribe.mdx +674 -0
  168. package/docs/realtime/react-hooks/swr.mdx +87 -0
  169. package/docs/realtime/react-hooks/triggering.mdx +194 -0
  170. package/docs/realtime/react-hooks/use-wait-token.mdx +34 -0
  171. package/docs/realtime/run-object.mdx +174 -0
  172. package/docs/replaying.mdx +72 -0
  173. package/docs/request-feature.mdx +6 -0
  174. package/docs/roadmap.mdx +6 -0
  175. package/docs/run-tests.mdx +20 -0
  176. package/docs/run-usage.mdx +113 -0
  177. package/docs/runs/heartbeats.mdx +38 -0
  178. package/docs/runs/max-duration.mdx +139 -0
  179. package/docs/runs/metadata.mdx +734 -0
  180. package/docs/runs/priority.mdx +31 -0
  181. package/docs/runs.mdx +396 -0
  182. package/docs/self-hosting/docker.mdx +458 -0
  183. package/docs/self-hosting/env/supervisor.mdx +74 -0
  184. package/docs/self-hosting/env/webapp.mdx +276 -0
  185. package/docs/self-hosting/kubernetes.mdx +601 -0
  186. package/docs/self-hosting/overview.mdx +108 -0
  187. package/docs/skills.mdx +85 -0
  188. package/docs/tags.mdx +120 -0
  189. package/docs/tasks/overview.mdx +697 -0
  190. package/docs/tasks/scheduled.mdx +382 -0
  191. package/docs/tasks/schemaTask.mdx +413 -0
  192. package/docs/tasks/streams.mdx +884 -0
  193. package/docs/triggering.mdx +1320 -0
  194. package/docs/troubleshooting-alerts.mdx +385 -0
  195. package/docs/troubleshooting-debugging-in-vscode.mdx +8 -0
  196. package/docs/troubleshooting-github-issues.mdx +6 -0
  197. package/docs/troubleshooting-uptime-status.mdx +6 -0
  198. package/docs/troubleshooting.mdx +398 -0
  199. package/docs/upgrading-packages.mdx +80 -0
  200. package/docs/vercel-integration.mdx +207 -0
  201. package/docs/versioning.mdx +56 -0
  202. package/docs/video-walkthrough.mdx +23 -0
  203. package/docs/wait-for-token.mdx +540 -0
  204. package/docs/wait-for.mdx +42 -0
  205. package/docs/wait-until.mdx +53 -0
  206. package/docs/wait.mdx +18 -0
  207. package/docs/writing-tasks-introduction.mdx +33 -0
  208. package/package.json +10 -6
  209. package/skills/trigger-authoring-chat-agent/SKILL.md +296 -0
  210. package/skills/trigger-authoring-tasks/SKILL.md +254 -0
  211. package/skills/trigger-chat-agent-advanced/SKILL.md +368 -0
  212. package/skills/trigger-cost-savings/SKILL.md +116 -0
  213. package/skills/trigger-realtime-and-frontend/SKILL.md +276 -0
@@ -0,0 +1,952 @@
1
+ ---
2
+ title: "Machines"
3
+ description: "Configure the number of vCPUs and GBs of RAM you want the task to use."
4
+ ---
5
+
6
+ The `machine` configuration is optional. Using higher spec machines will increase the cost of running the task but can also improve the performance of the task if it is CPU or memory bound.
7
+
8
+ ```ts /trigger/heavy-task.ts
9
+ import { task } from "@trigger.dev/sdk";
10
+
11
+ export const heavyTask = task({
12
+ id: "heavy-task",
13
+ machine: "large-1x",
14
+ run: async ({ payload, ctx }) => {
15
+ //...
16
+ },
17
+ });
18
+ ```
19
+
20
+ The default machine is `small-1x` which has 0.5 vCPU and 0.5 GB of RAM. You can change the default machine in your `trigger.config.ts` file:
21
+
22
+ ```ts trigger.config.ts
23
+ import type { TriggerConfig } from "@trigger.dev/sdk";
24
+
25
+ export const config: TriggerConfig = {
26
+ machine: "small-2x",
27
+ // ... other config
28
+ };
29
+ ```
30
+
31
+ ## Machine configurations
32
+
33
+ | Preset | vCPU | Memory | Disk space |
34
+ | :----------------- | :--- | :----- | :--------- |
35
+ | micro | 0.25 | 0.25 | 10GB |
36
+ | small-1x (default) | 0.5 | 0.5 | 10GB |
37
+ | small-2x | 1 | 1 | 10GB |
38
+ | medium-1x | 1 | 2 | 10GB |
39
+ | medium-2x | 2 | 4 | 10GB |
40
+ | large-1x | 4 | 8 | 10GB |
41
+ | large-2x | 8 | 16 | 10GB |
42
+
43
+ You can view the Trigger.dev cloud pricing for these machines [here](https://trigger.dev/pricing#computePricing).
44
+
45
+ ## Overriding the machine when triggering
46
+
47
+ You can also override the task machine when you [trigger](/triggering) it:
48
+
49
+ ```ts
50
+ await tasks.trigger<typeof heavyTask>(
51
+ "heavy-task",
52
+ { message: "hello world" },
53
+ { machine: "large-2x" }
54
+ );
55
+ ```
56
+
57
+ This is useful when you know that a certain payload will require more memory than the default machine. For example, you know it's a larger file or a customer that has a lot of data.
58
+
59
+ ## Out Of Memory (OOM) errors
60
+
61
+ Sometimes you might see one of your runs fail with an "Out Of Memory" error.
62
+
63
+ > TASK_PROCESS_OOM_KILLED. Your run was terminated due to exceeding the machine's memory limit. Try increasing the machine preset in your task options or replay using a larger machine.
64
+
65
+ We automatically detect common Out Of Memory errors:
66
+
67
+ - When using Node.js, if the V8 heap limit is exceeded (this can happen when creating large long-lived objects)
68
+ - When the entire process exceeds the memory limit of the machine the run is executing on.
69
+ - When a child process, such as ffmpeg, causes the memory limit of the machine to be exceeded, and exits with a non-zero code.
70
+
71
+ ### Memory/Resource monitoring
72
+
73
+ To better understand why an OOM error occurred, we've published a helper class that will log memory debug information at regular intervals.
74
+
75
+ First, add this `ResourceMonitor` class to your project:
76
+
77
+ <Accordion title="View ResourceMonitor class">
78
+
79
+ ```ts /src/resourceMonitor.ts
80
+ import { promisify } from "node:util";
81
+ import { exec } from "node:child_process";
82
+ import os from "node:os";
83
+ import { promises as fs } from "node:fs";
84
+ import { type Context, logger } from "@trigger.dev/sdk";
85
+ import { getHeapStatistics } from "node:v8";
86
+ import { PerformanceObserver, constants } from "node:perf_hooks";
87
+
88
+ const execAsync = promisify(exec);
89
+
90
+ export type DiskMetrics = {
91
+ total: number;
92
+ used: number;
93
+ free: number;
94
+ percentUsed: number;
95
+ warning?: string;
96
+ };
97
+
98
+ export type MemoryMetrics = {
99
+ total: number;
100
+ free: number;
101
+ used: number;
102
+ percentUsed: number;
103
+ };
104
+
105
+ export type NodeProcessMetrics = {
106
+ memoryUsage: number;
107
+ memoryUsagePercent: number;
108
+ heapUsed: number;
109
+ heapSizeLimit: number;
110
+ heapUsagePercent: number;
111
+ availableHeap: number;
112
+ isNearHeapLimit: boolean;
113
+ };
114
+
115
+ export type TargetProcessMetrics = {
116
+ method: string;
117
+ processName: string;
118
+ count: number;
119
+ processes: ProcessInfo[];
120
+ averages: {
121
+ cpu: number;
122
+ memory: number;
123
+ rss: number;
124
+ vsz: number;
125
+ } | null;
126
+ totals: {
127
+ cpu: number;
128
+ memory: number;
129
+ rss: number;
130
+ vsz: number;
131
+ } | null;
132
+ };
133
+
134
+ export type ProcessMetrics = {
135
+ node: NodeProcessMetrics;
136
+ targetProcess: TargetProcessMetrics | null;
137
+ };
138
+
139
+ type GCSummary = {
140
+ count: number;
141
+ totalDuration: number; // ms
142
+ avgDuration: number; // ms
143
+ maxDuration: number; // ms
144
+ kinds: Record<
145
+ string,
146
+ {
147
+ // breakdown by kind
148
+ count: number;
149
+ totalDuration: number;
150
+ avgDuration: number;
151
+ maxDuration: number;
152
+ }
153
+ >;
154
+ };
155
+
156
+ type ProcessInfo = {
157
+ user: string;
158
+ pid: number;
159
+ cpu: number;
160
+ mem: number;
161
+ vsz: number;
162
+ rss: number;
163
+ command: string;
164
+ };
165
+
166
+ export type SystemMetrics = {
167
+ disk: DiskMetrics;
168
+ memory: MemoryMetrics;
169
+ };
170
+
171
+ export type ResourceMonitorConfig = {
172
+ dirName?: string;
173
+ processName?: string;
174
+ ctx: Context;
175
+ compactLogging?: boolean;
176
+ };
177
+
178
+ // Constants
179
+ const DISK_LIMIT_GB = 10;
180
+ const DISK_LIMIT_BYTES = DISK_LIMIT_GB * 1024 * 1024 * 1024; // 10Gi in bytes
181
+
182
+ export class ResourceMonitor {
183
+ private logInterval: NodeJS.Timeout | null = null;
184
+ private logger: typeof logger;
185
+ private dirName: string;
186
+ private processName: string | undefined;
187
+ private ctx: Context;
188
+ private verbose: boolean;
189
+ private compactLogging: boolean;
190
+ private gcObserver: PerformanceObserver | null = null;
191
+ private bufferedGcEntries: PerformanceEntry[] = [];
192
+
193
+ constructor(config: ResourceMonitorConfig) {
194
+ this.logger = logger;
195
+ this.dirName = config.dirName ?? "/tmp";
196
+ this.processName = config.processName;
197
+ this.ctx = config.ctx;
198
+ this.verbose = true;
199
+ this.compactLogging = config.compactLogging ?? false;
200
+ }
201
+
202
+ /**
203
+ * Start periodic resource monitoring
204
+ * @param intervalMs Monitoring interval in milliseconds
205
+ */
206
+ startMonitoring(intervalMs = 10000): void {
207
+ if (intervalMs < 1000) {
208
+ intervalMs = 1000;
209
+ this.logger.warn("ResourceMonitor: intervalMs is less than 1000, setting to 1000");
210
+ }
211
+
212
+ if (this.logInterval) {
213
+ clearInterval(this.logInterval);
214
+ }
215
+
216
+ this.logInterval = setInterval(this.logResources.bind(this), intervalMs);
217
+
218
+ this.gcObserver = new PerformanceObserver((list) => {
219
+ this.bufferedGcEntries.push(...list.getEntries());
220
+ });
221
+
222
+ this.gcObserver.observe({ entryTypes: ["gc"], buffered: true });
223
+ }
224
+
225
+ /**
226
+ * Stop resource monitoring
227
+ */
228
+ stopMonitoring(): void {
229
+ if (this.logInterval) {
230
+ clearInterval(this.logInterval);
231
+ this.logInterval = null;
232
+ }
233
+
234
+ if (this.gcObserver) {
235
+ this.gcObserver.disconnect();
236
+ this.gcObserver = null;
237
+ }
238
+ }
239
+
240
+ private async logResources() {
241
+ try {
242
+ await this.logResourceSnapshot("ResourceMonitor");
243
+ } catch (error) {
244
+ this.logger.error(
245
+ `Resource monitoring error: ${error instanceof Error ? error.message : String(error)}`
246
+ );
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Get combined system metrics (disk and memory)
252
+ */
253
+ private async getSystemMetrics(): Promise<SystemMetrics> {
254
+ const [disk, memory] = await Promise.all([this.getDiskMetrics(), this.getMemoryMetrics()]);
255
+ return { disk, memory };
256
+ }
257
+
258
+ /**
259
+ * Get disk space information
260
+ */
261
+ private async getDiskMetrics(): Promise<DiskMetrics> {
262
+ try {
263
+ // Even with permission errors, du will output a total
264
+ const { stdout, stderr } = await execAsync(`du -sb ${this.dirName} || true`);
265
+
266
+ // Get the last line of stdout which contains the total
267
+ const lastLine = stdout.split("\n").filter(Boolean).pop() || "";
268
+ const usedBytes = parseInt(lastLine.split("\t")[0], 10);
269
+
270
+ const effectiveTotal = DISK_LIMIT_BYTES;
271
+ const effectiveUsed = Math.min(usedBytes, DISK_LIMIT_BYTES);
272
+ const effectiveFree = effectiveTotal - effectiveUsed;
273
+ const percentUsed = (effectiveUsed / effectiveTotal) * 100;
274
+
275
+ const metrics: DiskMetrics = {
276
+ total: effectiveTotal,
277
+ used: effectiveUsed,
278
+ free: effectiveFree,
279
+ percentUsed,
280
+ };
281
+
282
+ // If we had permission errors, add a warning
283
+ if (stderr.includes("Permission denied") || stderr.includes("cannot access")) {
284
+ metrics.warning = "Some directories were not accessible";
285
+ } else if (stderr.includes("No such file or directory")) {
286
+ metrics.warning = "The directory does not exist";
287
+ }
288
+
289
+ return metrics;
290
+ } catch (error) {
291
+ this.logger.error(
292
+ `Error getting disk metrics: ${error instanceof Error ? error.message : String(error)}`
293
+ );
294
+ return {
295
+ free: DISK_LIMIT_BYTES,
296
+ total: DISK_LIMIT_BYTES,
297
+ used: 0,
298
+ percentUsed: 0,
299
+ warning: "Failed to measure disk usage",
300
+ };
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Get memory metrics
306
+ */
307
+ private getMemoryMetrics(): MemoryMetrics {
308
+ const total = os.totalmem();
309
+ const free = os.freemem();
310
+ const used = total - free;
311
+ const percentUsed = (used / total) * 100;
312
+
313
+ return { total, free, used, percentUsed };
314
+ }
315
+
316
+ /**
317
+ * Get process-specific metrics using /proc filesystem
318
+ */
319
+ private async getProcMetrics(pids: number[]): Promise<ProcessInfo[]> {
320
+ return Promise.all(
321
+ pids.map(async (pid) => {
322
+ try {
323
+ // Read process status
324
+ const status = await fs.readFile(`/proc/${pid}/status`, "utf8");
325
+ const cmdline = await fs.readFile(`/proc/${pid}/cmdline`, "utf8");
326
+ const stat = await fs.readFile(`/proc/${pid}/stat`, "utf8");
327
+
328
+ // Parse VmRSS (resident set size) from status
329
+ const rss = parseInt(status.match(/VmRSS:\s+(\d+)/)?.[1] ?? "0", 10);
330
+ // Parse VmSize (virtual memory size) from status
331
+ const vsz = parseInt(status.match(/VmSize:\s+(\d+)/)?.[1] ?? "0", 10);
332
+ // Get process owner
333
+ const user = (await fs.stat(`/proc/${pid}`)).uid.toString();
334
+
335
+ // Parse CPU stats from /proc/[pid]/stat
336
+ const stats = stat.split(" ");
337
+ const utime = parseInt(stats[13], 10);
338
+ const stime = parseInt(stats[14], 10);
339
+ const starttime = parseInt(stats[21], 10);
340
+
341
+ // Calculate CPU percentage
342
+ const totalTime = utime + stime;
343
+ const uptime = os.uptime();
344
+ const hertz = 100; // Usually 100 on Linux
345
+ const elapsedTime = uptime - starttime / hertz;
346
+ const cpuUsage = 100 * (totalTime / hertz / elapsedTime);
347
+
348
+ // Calculate memory percentage against total system memory
349
+ const totalMem = os.totalmem();
350
+ const memoryPercent = (rss * 1024 * 100) / totalMem;
351
+
352
+ return {
353
+ user,
354
+ pid,
355
+ cpu: cpuUsage,
356
+ mem: memoryPercent,
357
+ vsz,
358
+ rss,
359
+ command: cmdline.replace(/\0/g, " ").trim(),
360
+ };
361
+ } catch (error) {
362
+ return null;
363
+ }
364
+ })
365
+ ).then((results) => results.filter((r): r is ProcessInfo => r !== null));
366
+ }
367
+
368
+ /**
369
+ * Find PIDs for a process name using /proc filesystem
370
+ */
371
+ private async findPidsByName(processName?: string): Promise<number[]> {
372
+ if (!processName) {
373
+ return [];
374
+ }
375
+
376
+ try {
377
+ const pids: number[] = [];
378
+ const procDirs = await fs.readdir("/proc");
379
+
380
+ for (const dir of procDirs) {
381
+ if (!/^\d+$/.test(dir)) continue;
382
+
383
+ const processPid = parseInt(dir, 10);
384
+
385
+ // Ignore processes that have a lower PID than our own PID
386
+ if (processPid <= process.pid) {
387
+ continue;
388
+ }
389
+
390
+ try {
391
+ const cmdline = await fs.readFile(`/proc/${dir}/cmdline`, "utf8");
392
+ if (cmdline.includes(processName)) {
393
+ pids.push(parseInt(dir, 10));
394
+ }
395
+ } catch {
396
+ // Ignore errors reading individual process info
397
+ continue;
398
+ }
399
+ }
400
+
401
+ return pids;
402
+ } catch {
403
+ return [];
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Get process-specific metrics
409
+ */
410
+ private async getProcessMetrics(): Promise<ProcessMetrics> {
411
+ // Get Node.js process metrics
412
+ const totalMemory = os.totalmem();
413
+ // Convert GB to bytes (machine.memory is in GB)
414
+ const machineMemoryBytes = this.ctx.machine
415
+ ? this.ctx.machine.memory * 1024 * 1024 * 1024
416
+ : totalMemory;
417
+ const nodeMemoryUsage = process.memoryUsage();
418
+
419
+ // Node process percentage is based on machine memory if available, otherwise system memory
420
+ const nodeMemoryPercent = (nodeMemoryUsage.rss / machineMemoryBytes) * 100;
421
+ const heapStats = getHeapStatistics();
422
+
423
+ const nodeMetrics: NodeProcessMetrics = {
424
+ memoryUsage: nodeMemoryUsage.rss,
425
+ memoryUsagePercent: nodeMemoryPercent,
426
+ heapUsed: nodeMemoryUsage.heapUsed,
427
+ heapSizeLimit: heapStats.heap_size_limit,
428
+ heapUsagePercent: (heapStats.used_heap_size / heapStats.heap_size_limit) * 100,
429
+ availableHeap: heapStats.total_available_size,
430
+ isNearHeapLimit: heapStats.used_heap_size / heapStats.heap_size_limit > 0.8,
431
+ };
432
+
433
+ let method = "ps";
434
+
435
+ try {
436
+ let processes: ProcessInfo[] = [];
437
+
438
+ // Try ps first, fall back to /proc if it fails
439
+ try {
440
+ const { stdout: psOutput } = await execAsync(
441
+ `ps aux | grep ${this.processName} | grep -v grep`
442
+ );
443
+
444
+ if (psOutput.trim()) {
445
+ processes = psOutput
446
+ .trim()
447
+ .split("\n")
448
+ .filter((line) => {
449
+ const parts = line.trim().split(/\s+/);
450
+ const pid = parseInt(parts[1], 10);
451
+
452
+ // Ignore processes that have a lower PID than our own PID
453
+ return pid > process.pid;
454
+ })
455
+ .map((line) => {
456
+ const parts = line.trim().split(/\s+/);
457
+ return {
458
+ user: parts[0],
459
+ pid: parseInt(parts[1], 10),
460
+ cpu: parseFloat(parts[2]),
461
+ mem: parseFloat(parts[3]),
462
+ vsz: parseInt(parts[4], 10),
463
+ rss: parseInt(parts[5], 10),
464
+ command: parts.slice(10).join(" "),
465
+ };
466
+ });
467
+ }
468
+ } catch {
469
+ // ps failed, try /proc instead
470
+ method = "proc";
471
+ const pids = await this.findPidsByName(this.processName);
472
+ processes = await this.getProcMetrics(pids);
473
+ }
474
+
475
+ if (processes.length === 0) {
476
+ return {
477
+ node: nodeMetrics,
478
+ targetProcess: this.processName
479
+ ? {
480
+ method,
481
+ processName: this.processName,
482
+ count: 0,
483
+ processes: [],
484
+ averages: null,
485
+ totals: null,
486
+ }
487
+ : null,
488
+ };
489
+ }
490
+
491
+ // For CPU:
492
+ // - ps shows CPU percentage per core (e.g., 100% = 1 core)
493
+ // - machine.cpu is in cores (e.g., 0.5 = half a core)
494
+ // - we want to show percentage of allocated CPU (e.g., 100% = using all allocated CPU)
495
+ const availableCpu = this.ctx.machine?.cpu ?? os.cpus().length;
496
+ const cpuNormalizer = availableCpu * 100; // Convert to basis points for better precision with fractional CPUs
497
+
498
+ // For Memory:
499
+ // - ps 'mem' is already a percentage of system memory
500
+ // - we need to convert it to a percentage of machine memory
501
+ // - if machine memory is 0.5GB and system has 16GB, we multiply the percentage by 32
502
+ const memoryScaleFactor = this.ctx.machine ? totalMemory / machineMemoryBytes : 1;
503
+
504
+ const totals = processes.reduce(
505
+ (acc, proc) => ({
506
+ cpu: acc.cpu + proc.cpu,
507
+ // Scale memory percentage to machine memory
508
+ // TODO: test this
509
+ memory: acc.memory + proc.mem * memoryScaleFactor,
510
+ rss: acc.rss + proc.rss,
511
+ vsz: acc.vsz + proc.vsz,
512
+ }),
513
+ { cpu: 0, memory: 0, rss: 0, vsz: 0 }
514
+ );
515
+
516
+ const count = processes.length;
517
+
518
+ const averages = {
519
+ cpu: totals.cpu / (count * cpuNormalizer),
520
+ memory: totals.memory / count,
521
+ rss: totals.rss / count,
522
+ vsz: totals.vsz / count,
523
+ };
524
+
525
+ return {
526
+ node: nodeMetrics,
527
+ targetProcess: this.processName
528
+ ? {
529
+ method,
530
+ processName: this.processName,
531
+ count,
532
+ processes,
533
+ averages,
534
+ totals: {
535
+ cpu: totals.cpu / cpuNormalizer,
536
+ memory: totals.memory,
537
+ rss: totals.rss,
538
+ vsz: totals.vsz,
539
+ },
540
+ }
541
+ : null,
542
+ };
543
+ } catch (error) {
544
+ return {
545
+ node: nodeMetrics,
546
+ targetProcess: this.processName
547
+ ? {
548
+ method,
549
+ processName: this.processName,
550
+ count: 0,
551
+ processes: [],
552
+ averages: null,
553
+ totals: null,
554
+ }
555
+ : null,
556
+ };
557
+ }
558
+ }
559
+
560
+ /**
561
+ * Log a snapshot of current resource usage
562
+ */
563
+ async logResourceSnapshot(label = "Resource Snapshot"): Promise<void> {
564
+ try {
565
+ const payload = await this.getResourceSnapshotPayload();
566
+ const enhancedLabel = this.compactLogging
567
+ ? this.createCompactLabel(payload, label)
568
+ : this.createEnhancedLabel(payload, label);
569
+
570
+ if (payload.process.node.isNearHeapLimit) {
571
+ this.logger.warn(`${enhancedLabel}: Node is near heap limit`, payload);
572
+ } else {
573
+ this.logger.info(enhancedLabel, payload);
574
+ }
575
+ } catch (error) {
576
+ this.logger.error(
577
+ `Error logging resource snapshot: ${error instanceof Error ? error.message : String(error)}`
578
+ );
579
+ }
580
+ }
581
+
582
+ /**
583
+ * Create an enhanced log label with key metrics for quick scanning
584
+ */
585
+ private createEnhancedLabel(payload: any, baseLabel: string): string {
586
+ const parts: string[] = [baseLabel];
587
+
588
+ // System resources with text indicators
589
+ const diskPercent = parseFloat(payload.system.disk.percentUsed);
590
+ const memoryPercent = parseFloat(payload.system.memory.percentUsed);
591
+ const diskIndicator = this.getTextIndicator(diskPercent, 80, 90);
592
+ const memIndicator = this.getTextIndicator(memoryPercent, 80, 90);
593
+ parts.push(`Disk:${diskPercent.toFixed(1).padStart(5)}%${diskIndicator}`);
594
+ parts.push(`Mem:${memoryPercent.toFixed(1).padStart(5)}%${memIndicator}`);
595
+
596
+ // Node process metrics with text indicators
597
+ const nodeMemPercent = parseFloat(payload.process.node.memoryUsagePercent);
598
+ const heapPercent = parseFloat(payload.process.node.heapUsagePercent);
599
+ const nodeIndicator = this.getTextIndicator(nodeMemPercent, 70, 85);
600
+ const heapIndicator = this.getTextIndicator(heapPercent, 70, 85);
601
+ parts.push(`Node:${nodeMemPercent.toFixed(1).padStart(4)}%${nodeIndicator}`);
602
+ parts.push(`Heap:${heapPercent.toFixed(1).padStart(4)}%${heapIndicator}`);
603
+
604
+ // Target process metrics (if available)
605
+ if (payload.process.targetProcess && payload.process.targetProcess.count > 0) {
606
+ const targetCpu = payload.process.targetProcess.totals?.cpuPercent || "0";
607
+ const targetMem = payload.process.targetProcess.totals?.memoryPercent || "0";
608
+ const targetCpuNum = parseFloat(targetCpu);
609
+ const targetMemNum = parseFloat(targetMem);
610
+ const cpuIndicator = this.getTextIndicator(targetCpuNum, 80, 90);
611
+ const memIndicator = this.getTextIndicator(targetMemNum, 80, 90);
612
+ parts.push(
613
+ `${payload.process.targetProcess.processName}:${targetCpu.padStart(
614
+ 4
615
+ )}%${cpuIndicator}/${targetMem.padStart(4)}%${memIndicator}`
616
+ );
617
+ }
618
+
619
+ // GC activity with performance indicators
620
+ if (payload.gc && payload.gc.count > 0) {
621
+ const avgDuration = payload.gc.avgDuration;
622
+ const gcIndicator = this.getTextIndicator(avgDuration, 5, 10, true);
623
+ parts.push(
624
+ `GC:${payload.gc.count.toString().padStart(2)}(${avgDuration
625
+ .toFixed(1)
626
+ .padStart(4)}ms)${gcIndicator}`
627
+ );
628
+ }
629
+
630
+ // Machine constraints
631
+ if (payload.constraints) {
632
+ parts.push(`[${payload.constraints.cpu}CPU/${payload.constraints.memoryGB}GB]`);
633
+ }
634
+
635
+ // Warning indicators (only show critical ones in the main label)
636
+ const criticalWarnings: string[] = [];
637
+ if (payload.process.node.isNearHeapLimit) criticalWarnings.push("HEAP_LIMIT");
638
+ if (diskPercent > 90) criticalWarnings.push("DISK_CRITICAL");
639
+ if (memoryPercent > 95) criticalWarnings.push("MEM_CRITICAL");
640
+ if (payload.system.disk.warning) criticalWarnings.push("DISK_WARN");
641
+
642
+ if (criticalWarnings.length > 0) {
643
+ parts.push(`[${criticalWarnings.join(",")}]`);
644
+ }
645
+
646
+ return parts.join(" | ");
647
+ }
648
+
649
+ /**
650
+ * Get text-based indicator for percentage values
651
+ */
652
+ private getTextIndicator(
653
+ value: number,
654
+ warningThreshold: number,
655
+ criticalThreshold: number,
656
+ isDuration = false
657
+ ): string {
658
+ if (isDuration) {
659
+ // For duration values, higher is worse
660
+ if (value >= criticalThreshold) return " [CRIT]";
661
+ if (value >= warningThreshold) return " [WARN]";
662
+ return " [OK]";
663
+ } else {
664
+ // For percentage values, higher is worse
665
+ if (value >= criticalThreshold) return " [CRIT]";
666
+ if (value >= warningThreshold) return " [WARN]";
667
+ return " [OK]";
668
+ }
669
+ }
670
+
671
+ /**
672
+ * Create a compact version of the enhanced label for high-frequency logging
673
+ */
674
+ private createCompactLabel(payload: any, baseLabel: string): string {
675
+ const parts: string[] = [baseLabel];
676
+
677
+ // Only show critical metrics in compact mode
678
+ const diskPercent = parseFloat(payload.system.disk.percentUsed);
679
+ const memoryPercent = parseFloat(payload.system.memory.percentUsed);
680
+ const heapPercent = parseFloat(payload.process.node.heapUsagePercent);
681
+
682
+ // Use single character indicators for compactness
683
+ const diskIndicator = diskPercent > 90 ? "!" : diskPercent > 80 ? "?" : ".";
684
+ const memIndicator = memoryPercent > 95 ? "!" : memoryPercent > 80 ? "?" : ".";
685
+ const heapIndicator = heapPercent > 85 ? "!" : heapPercent > 70 ? "?" : ".";
686
+
687
+ parts.push(`D:${diskPercent.toFixed(0).padStart(2)}%${diskIndicator}`);
688
+ parts.push(`M:${memoryPercent.toFixed(0).padStart(2)}%${memIndicator}`);
689
+ parts.push(`H:${heapPercent.toFixed(0).padStart(2)}%${heapIndicator}`);
690
+
691
+ // GC activity (only if significant)
692
+ if (payload.gc && payload.gc.count > 0 && payload.gc.avgDuration > 2) {
693
+ const gcIndicator =
694
+ payload.gc.avgDuration > 10 ? "!" : payload.gc.avgDuration > 5 ? "?" : ".";
695
+ parts.push(`GC:${payload.gc.count}${gcIndicator}`);
696
+ }
697
+
698
+ return parts.join(" ");
699
+ }
700
+
701
+ async getResourceSnapshotPayload() {
702
+ const [systemMetrics, processMetrics] = await Promise.all([
703
+ this.getSystemMetrics(),
704
+ this.getProcessMetrics(),
705
+ ]);
706
+
707
+ const gcSummary = summarizeGCEntries(this.bufferedGcEntries);
708
+ this.bufferedGcEntries = [];
709
+
710
+ const formatBytes = (bytes: number) => (bytes / (1024 * 1024)).toFixed(2);
711
+ const formatPercent = (value: number) => value.toFixed(1);
712
+
713
+ return {
714
+ system: {
715
+ disk: {
716
+ limitGiB: DISK_LIMIT_GB,
717
+ dirName: this.dirName,
718
+ usedGiB: (systemMetrics.disk.used / (1024 * 1024 * 1024)).toFixed(2),
719
+ freeGiB: (systemMetrics.disk.free / (1024 * 1024 * 1024)).toFixed(2),
720
+ percentUsed: formatPercent(systemMetrics.disk.percentUsed),
721
+ warning: systemMetrics.disk.warning,
722
+ },
723
+ memory: {
724
+ freeGB: (systemMetrics.memory.free / (1024 * 1024 * 1024)).toFixed(2),
725
+ percentUsed: formatPercent(systemMetrics.memory.percentUsed),
726
+ },
727
+ },
728
+ gc: gcSummary,
729
+ constraints: this.ctx.machine
730
+ ? {
731
+ cpu: this.ctx.machine.cpu,
732
+ memoryGB: this.ctx.machine.memory,
733
+ diskGB: DISK_LIMIT_BYTES / (1024 * 1024 * 1024),
734
+ }
735
+ : {
736
+ cpu: os.cpus().length,
737
+ memoryGB: Math.floor(os.totalmem() / (1024 * 1024 * 1024)),
738
+ note: "Using system resources (no machine constraints specified)",
739
+ },
740
+ process: {
741
+ node: {
742
+ memoryUsageMB: formatBytes(processMetrics.node.memoryUsage),
743
+ memoryUsagePercent: formatPercent(processMetrics.node.memoryUsagePercent),
744
+ heapUsedMB: formatBytes(processMetrics.node.heapUsed),
745
+ heapSizeLimitMB: formatBytes(processMetrics.node.heapSizeLimit),
746
+ heapUsagePercent: formatPercent(processMetrics.node.heapUsagePercent),
747
+ availableHeapMB: formatBytes(processMetrics.node.availableHeap),
748
+ isNearHeapLimit: processMetrics.node.isNearHeapLimit,
749
+ ...(this.verbose
750
+ ? {
751
+ heapStats: getHeapStatistics(),
752
+ }
753
+ : {}),
754
+ },
755
+ targetProcess: processMetrics.targetProcess
756
+ ? {
757
+ method: processMetrics.targetProcess.method,
758
+ processName: processMetrics.targetProcess.processName,
759
+ count: processMetrics.targetProcess.count,
760
+ averages: processMetrics.targetProcess.averages
761
+ ? {
762
+ cpuPercent: formatPercent(processMetrics.targetProcess.averages.cpu * 100),
763
+ memoryPercent: formatPercent(processMetrics.targetProcess.averages.memory),
764
+ rssMB: formatBytes(processMetrics.targetProcess.averages.rss * 1024),
765
+ vszMB: formatBytes(processMetrics.targetProcess.averages.vsz * 1024),
766
+ }
767
+ : null,
768
+ totals: processMetrics.targetProcess.totals
769
+ ? {
770
+ cpuPercent: formatPercent(processMetrics.targetProcess.totals.cpu * 100),
771
+ memoryPercent: formatPercent(processMetrics.targetProcess.totals.memory),
772
+ rssMB: formatBytes(processMetrics.targetProcess.totals.rss * 1024),
773
+ vszMB: formatBytes(processMetrics.targetProcess.totals.vsz * 1024),
774
+ }
775
+ : null,
776
+ }
777
+ : null,
778
+ },
779
+ timestamp: new Date().toISOString(),
780
+ };
781
+ }
782
+ }
783
+
784
+ function summarizeGCEntries(entries: PerformanceEntry[]): GCSummary {
785
+ if (entries.length === 0) {
786
+ return {
787
+ count: 0,
788
+ totalDuration: 0,
789
+ avgDuration: 0,
790
+ maxDuration: 0,
791
+ kinds: {},
792
+ };
793
+ }
794
+
795
+ let totalDuration = 0;
796
+ let maxDuration = 0;
797
+ const kinds: Record<string, { count: number; totalDuration: number; maxDuration: number }> = {};
798
+
799
+ for (const e of entries) {
800
+ const duration = e.duration;
801
+ totalDuration += duration;
802
+ if (duration > maxDuration) maxDuration = duration;
803
+
804
+ const kind = kindName((e as any)?.detail?.kind ?? "unknown");
805
+ if (!kinds[kind]) {
806
+ kinds[kind] = { count: 0, totalDuration: 0, maxDuration: 0 };
807
+ }
808
+ kinds[kind].count += 1;
809
+ kinds[kind].totalDuration += duration;
810
+ if (duration > kinds[kind].maxDuration) kinds[kind].maxDuration = duration;
811
+ }
812
+
813
+ // finalize averages
814
+ const avgDuration = totalDuration / entries.length;
815
+ const kindsWithAvg: GCSummary["kinds"] = {};
816
+ for (const [kind, stats] of Object.entries(kinds)) {
817
+ kindsWithAvg[kind] = {
818
+ count: stats.count,
819
+ totalDuration: stats.totalDuration,
820
+ avgDuration: stats.totalDuration / stats.count,
821
+ maxDuration: stats.maxDuration,
822
+ };
823
+ }
824
+
825
+ return {
826
+ count: entries.length,
827
+ totalDuration,
828
+ avgDuration,
829
+ maxDuration,
830
+ kinds: kindsWithAvg,
831
+ };
832
+ }
833
+
834
+ const kindName = (k: number | string) => {
835
+ if (typeof k === "number") {
836
+ return (
837
+ {
838
+ [constants.NODE_PERFORMANCE_GC_MAJOR]: "major",
839
+ [constants.NODE_PERFORMANCE_GC_MINOR]: "minor",
840
+ [constants.NODE_PERFORMANCE_GC_INCREMENTAL]: "incremental",
841
+ [constants.NODE_PERFORMANCE_GC_WEAKCB]: "weak-cb",
842
+ }[k] ?? `kind:${k}`
843
+ );
844
+ }
845
+ return k;
846
+ };
847
+ ```
848
+
849
+ </Accordion>
850
+
851
+ Then, in your task, you can create an instance of the `ResourceMonitor` class and start monitoring memory, disk, and CPU usage:
852
+
853
+ ```ts /src/trigger/example.ts
854
+ import { task, logger, wait } from "@trigger.dev/sdk";
855
+ import { ResourceMonitor } from "../resourceMonitor.js";
856
+
857
+ // Middleware to enable the resource monitor
858
+ tasks.middleware("resource-monitor", async ({ ctx, next }) => {
859
+ const resourceMonitor = new ResourceMonitor({
860
+ ctx,
861
+ });
862
+
863
+ // Only enable the resource monitor if the environment variable is set
864
+ if (process.env.RESOURCE_MONITOR_ENABLED === "1") {
865
+ resourceMonitor.startMonitoring(1_000);
866
+ }
867
+
868
+ await next();
869
+
870
+ resourceMonitor.stopMonitoring();
871
+ });
872
+
873
+ export const resourceMonitorTest = task({
874
+ id: "resource-monitor-test",
875
+ run: async (payload: any, { ctx }) => {
876
+ const interval = createMemoryPressure();
877
+
878
+ await setTimeout(180_000);
879
+
880
+ clearInterval(interval);
881
+
882
+ return {
883
+ message: "Hello, resources!",
884
+ };
885
+ },
886
+ });
887
+ ```
888
+
889
+ This will produce logs that look like this:
890
+
891
+ ![Resource monitor logs](/images/machines-resource-monitor-logs.png)
892
+
893
+ If you are spawning a child process and you want to monitor its memory usage, you can pass the `processName` option to the `ResourceMonitor` class:
894
+
895
+ ```ts /src/trigger/example.ts
896
+ const resourceMonitor = new ResourceMonitor({
897
+ ctx,
898
+ processName: "ffmpeg",
899
+ });
900
+ ```
901
+
902
+ This will produce logs that includes the memory and CPU usage of the `ffmpeg` process:
903
+
904
+ ![Resource monitor logs](/images/machines-resource-monitor-ffmpeg.png)
905
+
906
+ ### Explicit OOM errors
907
+
908
+ You can explicitly throw an Out Of Memory error in your task. This can be useful if you use a native package that detects it's going to run out of memory and then stops before it runs out. If you can detect this, you can then throw this error.
909
+
910
+ ```ts /trigger/heavy-task.ts
911
+ import { task } from "@trigger.dev/sdk";
912
+ import { OutOfMemoryError } from "@trigger.dev/sdk";
913
+
914
+ export const yourTask = task({
915
+ id: "your-task",
916
+ machine: "medium-1x",
917
+ run: async (payload: any, { ctx }) => {
918
+ //...
919
+
920
+ throw new OutOfMemoryError();
921
+ },
922
+ });
923
+ ```
924
+
925
+ If OOM errors happen regularly you need to either optimize the memory-efficiency of your code, or increase the machine.
926
+
927
+ ### Retrying with a larger machine
928
+
929
+ If you are seeing rare OOM errors, it might make sense to add a setting to your task to retry with a large machine when an OOM happens:
930
+
931
+ ```ts /trigger/heavy-task.ts
932
+ import { task } from "@trigger.dev/sdk";
933
+
934
+ export const yourTask = task({
935
+ id: "your-task",
936
+ machine: "medium-1x",
937
+ retry: {
938
+ outOfMemory: {
939
+ machine: "large-1x",
940
+ },
941
+ },
942
+ run: async (payload: any, { ctx }) => {
943
+ //...
944
+ },
945
+ });
946
+ ```
947
+
948
+ <Note>
949
+ This will only retry the task if you get an OOM error. It won't permanently change the machine
950
+ that a new run starts on, so if you consistently see OOM errors you should change the machine in
951
+ the `machine` property.
952
+ </Note>