|
1 | 1 | import { ChildProcess, spawn } from "child_process"; |
2 | 2 | import path from "path"; |
| 3 | +import fs from "fs"; |
3 | 4 | import type { ConsumerConfig, ProfilingConfig } from "./config"; |
4 | 5 |
|
5 | 6 | interface ConsumerMetrics { |
@@ -37,6 +38,7 @@ export class ConsumerProcessManager { |
37 | 38 | const isProfiling = this.profiling.enabled && this.profiling.tool !== "none"; |
38 | 39 |
|
39 | 40 | this.process = spawn(args[0], args.slice(1), { |
| 41 | + cwd: path.join(__dirname, "../.."), // Run from webapp directory |
40 | 42 | env: { |
41 | 43 | ...process.env, |
42 | 44 | CONSUMER_CONFIG: JSON.stringify(this.config), |
@@ -99,18 +101,45 @@ export class ConsumerProcessManager { |
99 | 101 | } |
100 | 102 |
|
101 | 103 | console.log("Stopping consumer process"); |
102 | | - this.process.send({ type: "shutdown" }); |
| 104 | + |
| 105 | + const isProfiling = this.profiling.enabled && this.profiling.tool !== "none"; |
| 106 | + |
| 107 | + if (isProfiling) { |
| 108 | + // When profiling, IPC doesn't work - use a shutdown signal file instead |
| 109 | + const outputDir = this.config.outputDir || "/tmp"; |
| 110 | + const shutdownFilePath = path.join(outputDir, ".shutdown-signal"); |
| 111 | + console.log("Creating shutdown signal file for consumer (profiling mode)"); |
| 112 | + |
| 113 | + // Ensure directory exists |
| 114 | + if (!fs.existsSync(outputDir)) { |
| 115 | + fs.mkdirSync(outputDir, { recursive: true }); |
| 116 | + } |
| 117 | + |
| 118 | + fs.writeFileSync(shutdownFilePath, "shutdown"); |
| 119 | + } else { |
| 120 | + // For non-profiling runs, use IPC message |
| 121 | + try { |
| 122 | + this.process.send({ type: "shutdown" }); |
| 123 | + } catch (error) { |
| 124 | + console.warn("Could not send shutdown message, process may have already exited"); |
| 125 | + } |
| 126 | + } |
103 | 127 |
|
104 | 128 | // Wait for process to exit |
105 | 129 | await new Promise<void>((resolve) => { |
| 130 | + // With shutdown signal file, consumer-runner should exit within a few seconds |
| 131 | + // With --collect-only, Clinic.js then quickly packages the data and exits |
| 132 | + const timeoutMs = isProfiling ? 15000 : 30000; |
| 133 | + |
106 | 134 | const timeout = setTimeout(() => { |
107 | | - console.warn("Consumer process did not exit gracefully, killing"); |
| 135 | + console.warn(`Consumer process did not exit after ${timeoutMs}ms, killing`); |
108 | 136 | this.process?.kill("SIGKILL"); |
109 | 137 | resolve(); |
110 | | - }, 30000); // 30 second timeout |
| 138 | + }, timeoutMs); |
111 | 139 |
|
112 | | - this.process?.on("exit", () => { |
| 140 | + this.process?.on("exit", (code, signal) => { |
113 | 141 | clearTimeout(timeout); |
| 142 | + console.log(`Consumer process exited with code ${code}, signal ${signal}`); |
114 | 143 | resolve(); |
115 | 144 | }); |
116 | 145 | }); |
@@ -144,21 +173,30 @@ export class ConsumerProcessManager { |
144 | 173 | const tool = this.profiling.tool === "both" ? "doctor" : this.profiling.tool; |
145 | 174 | const runnerPath = path.join(__dirname, "consumer-runner.ts"); |
146 | 175 |
|
| 176 | + // Use clinic from node_modules/.bin directly (more reliable than pnpm exec) |
| 177 | + const clinicPath = path.join(__dirname, "../../node_modules/.bin/clinic"); |
| 178 | + |
| 179 | + // Point --dest to the output directory itself |
| 180 | + // Clinic.js will create PID.clinic-flame inside this directory |
| 181 | + const destPath = path.resolve(this.profiling.outputDir); |
| 182 | + |
147 | 183 | // Clinic.js requires node, so use node with tsx/register loader |
148 | 184 | const args = [ |
149 | | - "pnpm", |
150 | | - "exec", |
151 | | - "clinic", |
| 185 | + clinicPath, |
152 | 186 | tool, |
| 187 | + "--collect-only", // Only collect data, don't generate visualization immediately |
| 188 | + "--open=false", // Don't try to open in browser |
153 | 189 | "--dest", |
154 | | - this.profiling.outputDir, |
| 190 | + destPath, |
155 | 191 | "--", |
156 | 192 | "node", |
157 | 193 | "--import", |
158 | 194 | "tsx", |
159 | 195 | runnerPath, |
160 | 196 | ]; |
161 | 197 |
|
| 198 | + console.log(`Clinic.js will save profiling data to: ${destPath}`); |
| 199 | + |
162 | 200 | return args; |
163 | 201 | } |
164 | 202 |
|
|
0 commit comments