Skip to content

Commit 13d0b76

Browse files
authored
feat(monitor): enhance execution monitoring with detailed node metric… (#125)
…s and reporting
1 parent f529ac9 commit 13d0b76

File tree

4 files changed

+143
-60
lines changed

4 files changed

+143
-60
lines changed

backend/src/build-system/__tests__/fullstack-gen.spec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import { FrontendCodeHandler } from '../handlers/frontend-code-generate';
9393
name: 'Frontend Code Generator Node',
9494
},
9595
],
96+
packages: [],
9697
};
9798
const context = new BuilderContext(sequence, 'fullstack-code-gen');
9899
await context.execute();

backend/src/build-system/context.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@ export class BuilderContext {
282282
// Mark the node as pending execution
283283
this.executionState.pending.add(handlerName);
284284

285+
// Start monitoring node execution
286+
this.monitor.startNodeExecution(handlerName, this.sequence.id);
287+
285288
// Execute the node handler and update the execution state accordingly
286289
const executionPromise = this.invokeNodeHandler<ExtractHandlerType<T>>(node)
287290
.then((result) => {
@@ -290,13 +293,22 @@ export class BuilderContext {
290293
this.executionState.pending.delete(handlerName);
291294
// Store the result of the node execution
292295
this.setNodeData(node.handler, result.data);
296+
// End monitoring for successful execution
297+
this.monitor.endNodeExecution(handlerName, this.sequence.id, true);
293298
return result;
294299
})
295300
.catch((error) => {
296301
// Mark the node as failed in case of an error
297302
this.executionState.failed.add(handlerName);
298303
this.executionState.pending.delete(handlerName);
299304
this.logger.error(`[Node Failed] ${handlerName}:`, error);
305+
// End monitoring for failed execution
306+
this.monitor.endNodeExecution(
307+
handlerName,
308+
this.sequence.id,
309+
false,
310+
error,
311+
);
300312
throw error;
301313
});
302314

@@ -370,7 +382,16 @@ export class BuilderContext {
370382
await Promise.all(Array.from(runningPromises));
371383
await new Promise((resolve) => setTimeout(resolve, this.POLL_INTERVAL));
372384
}
373-
return this.getGlobalContext('projectUUID');
385+
386+
// Write monitor report at the end of sequence execution
387+
const projectUUID = this.getGlobalContext('projectUUID');
388+
await this.monitor.endSequenceExecution(this.sequence.id, projectUUID);
389+
390+
this.writeLog(
391+
'summery-matrix.json',
392+
this.monitor.generateTextReport(this.sequence.id),
393+
);
394+
return projectUUID;
374395
} catch (error) {
375396
this.writeLog('execution-error.json', {
376397
error: error.message,

backend/src/build-system/monitor.ts

+112-57
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ export class BuildMonitor {
6767
private static instance: BuildMonitor;
6868
private logger: Logger;
6969
private sequenceMetrics: Map<string, SequenceMetrics> = new Map();
70-
private static timeRecorders: Map<string, any[]> = new Map();
71-
private static model = OpenAIModelProvider.getInstance();
70+
private timeRecorders: Map<string, any[]> = new Map();
71+
private model = OpenAIModelProvider.getInstance();
7272

7373
private constructor() {
7474
this.logger = new Logger('BuildMonitor');
@@ -81,7 +81,7 @@ export class BuildMonitor {
8181
return BuildMonitor.instance;
8282
}
8383

84-
public static async timeRecorder(
84+
public async timeRecorder(
8585
generateDuration: number,
8686
name: string,
8787
step: string,
@@ -103,13 +103,45 @@ export class BuildMonitor {
103103

104104
// Node-level monitoring
105105
startNodeExecution(nodeId: string, sequenceId: string): void {
106-
const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId);
107-
metrics.startTime = Date.now();
108-
metrics.status = 'pending';
106+
let sequenceMetrics = this.sequenceMetrics.get(sequenceId);
107+
108+
// Create sequence metrics if not exists
109+
if (!sequenceMetrics) {
110+
sequenceMetrics = {
111+
sequenceId,
112+
startTime: Date.now(),
113+
endTime: 0,
114+
duration: 0,
115+
nodeMetrics: new Map(),
116+
nodesOrder: [],
117+
totalNodes: 0, // Will be updated when we know the total
118+
completedNodes: 0,
119+
failedNodes: 0,
120+
successRate: 0,
121+
};
122+
this.sequenceMetrics.set(sequenceId, sequenceMetrics);
123+
}
124+
125+
// Create or get node metrics
126+
if (!sequenceMetrics.nodeMetrics.has(nodeId)) {
127+
sequenceMetrics.nodeMetrics.set(nodeId, {
128+
nodeId,
129+
startTime: Date.now(),
130+
endTime: 0,
131+
duration: 0,
132+
status: 'pending',
133+
retryCount: 0,
134+
});
135+
// Update total nodes count
136+
sequenceMetrics.totalNodes++;
137+
}
138+
139+
const nodeMetrics = sequenceMetrics.nodeMetrics.get(nodeId)!;
140+
nodeMetrics.startTime = Date.now();
141+
nodeMetrics.status = 'pending';
109142

110143
// Add node to execution order if not already present
111-
const sequenceMetrics = this.sequenceMetrics.get(sequenceId);
112-
if (sequenceMetrics && !sequenceMetrics.nodesOrder.includes(nodeId)) {
144+
if (!sequenceMetrics.nodesOrder.includes(nodeId)) {
113145
sequenceMetrics.nodesOrder.push(nodeId);
114146
}
115147
}
@@ -120,21 +152,60 @@ export class BuildMonitor {
120152
success: boolean,
121153
error?: Error,
122154
): void {
123-
const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId);
124-
metrics.endTime = Date.now();
125-
metrics.duration = metrics.endTime - metrics.startTime;
126-
metrics.status = success ? 'completed' : 'failed';
127-
if (error) {
128-
metrics.error = error;
155+
let sequenceMetrics = this.sequenceMetrics.get(sequenceId);
156+
157+
// Create sequence metrics if not exists
158+
if (!sequenceMetrics) {
159+
this.startNodeExecution(nodeId, sequenceId);
160+
sequenceMetrics = this.sequenceMetrics.get(sequenceId)!;
161+
}
162+
163+
const nodeMetrics = sequenceMetrics.nodeMetrics.get(nodeId);
164+
if (!nodeMetrics) {
165+
// Create node metrics if not exists
166+
sequenceMetrics.nodeMetrics.set(nodeId, {
167+
nodeId,
168+
startTime: Date.now() - 1, // Set a minimal duration
169+
endTime: Date.now(),
170+
duration: 1,
171+
status: success ? 'completed' : 'failed',
172+
retryCount: 0,
173+
error: error,
174+
});
175+
sequenceMetrics.totalNodes++;
176+
if (!sequenceMetrics.nodesOrder.includes(nodeId)) {
177+
sequenceMetrics.nodesOrder.push(nodeId);
178+
}
179+
} else {
180+
nodeMetrics.endTime = Date.now();
181+
nodeMetrics.duration = nodeMetrics.endTime - nodeMetrics.startTime;
182+
nodeMetrics.status = success ? 'completed' : 'failed';
183+
if (error) {
184+
nodeMetrics.error = error;
185+
}
129186
}
130187

131188
// Update sequence metrics
132189
this.updateSequenceMetrics(sequenceId);
133190
}
134191

135192
incrementNodeRetry(nodeId: string, sequenceId: string): void {
136-
const metrics = this.getOrCreateNodeMetrics(nodeId, sequenceId);
137-
metrics.retryCount++;
193+
let sequenceMetrics = this.sequenceMetrics.get(sequenceId);
194+
195+
// Create sequence metrics if not exists
196+
if (!sequenceMetrics) {
197+
this.startNodeExecution(nodeId, sequenceId);
198+
sequenceMetrics = this.sequenceMetrics.get(sequenceId)!;
199+
}
200+
201+
let nodeMetrics = sequenceMetrics.nodeMetrics.get(nodeId);
202+
if (!nodeMetrics) {
203+
// Create node metrics if not exists
204+
this.startNodeExecution(nodeId, sequenceId);
205+
nodeMetrics = sequenceMetrics.nodeMetrics.get(nodeId)!;
206+
}
207+
208+
nodeMetrics.retryCount++;
138209
}
139210

140211
// Sequence-level monitoring
@@ -159,24 +230,30 @@ export class BuildMonitor {
159230
projectUUID: string,
160231
): Promise<void> {
161232
const metrics = this.sequenceMetrics.get(sequenceId);
162-
if (metrics) {
163-
metrics.endTime = Date.now();
164-
metrics.duration = metrics.endTime - metrics.startTime;
233+
if (!metrics) {
234+
throw new Error(`No metrics found for sequence ${sequenceId}`);
235+
}
165236

166-
this.updateSequenceMetrics(sequenceId);
237+
metrics.endTime = Date.now();
238+
metrics.duration = metrics.endTime - metrics.startTime;
167239

168-
const report = await this.generateStructuredReport(
169-
sequenceId,
170-
projectUUID,
171-
);
172-
await ProjectEventLogger.getInstance().logEvent({
173-
timestamp: report.metadata.timestamp,
174-
projectId: report.metadata.projectId,
175-
eventId: `build-${report.metadata.sequenceId}`,
176-
type: 'BUILD_METRICS',
177-
data: report,
178-
});
179-
}
240+
this.updateSequenceMetrics(sequenceId);
241+
242+
// Generate and log the report
243+
const report = await this.generateStructuredReport(sequenceId, projectUUID);
244+
await ProjectEventLogger.getInstance().logEvent({
245+
timestamp: report.metadata.timestamp,
246+
projectId: report.metadata.projectId,
247+
eventId: `build-${report.metadata.sequenceId}`,
248+
type: 'BUILD_METRICS',
249+
data: report,
250+
});
251+
252+
// Clean up sequence metrics and time recorders after logging
253+
this.sequenceMetrics.delete(sequenceId);
254+
metrics.nodesOrder.forEach((nodeId) => {
255+
this.timeRecorders.delete(nodeId);
256+
});
180257
}
181258

182259
private updateSequenceMetrics(sequenceId: string): void {
@@ -192,28 +269,6 @@ export class BuildMonitor {
192269
}
193270
}
194271

195-
private getOrCreateNodeMetrics(
196-
nodeId: string,
197-
sequenceId: string,
198-
): NodeMetrics {
199-
const sequenceMetrics = this.sequenceMetrics.get(sequenceId);
200-
if (!sequenceMetrics) {
201-
throw new Error(`No metrics found for sequence ${sequenceId}`);
202-
}
203-
204-
if (!sequenceMetrics.nodeMetrics.has(nodeId)) {
205-
sequenceMetrics.nodeMetrics.set(nodeId, {
206-
nodeId,
207-
startTime: 0,
208-
endTime: 0,
209-
duration: 0,
210-
status: 'pending',
211-
retryCount: 0,
212-
});
213-
}
214-
return sequenceMetrics.nodeMetrics.get(nodeId)!;
215-
}
216-
217272
async generateStructuredReport(
218273
sequenceId: string,
219274
projectUUID: string,
@@ -228,7 +283,7 @@ export class BuildMonitor {
228283
const nodeMetric = metrics.nodeMetrics.get(nodeId);
229284
if (!nodeMetric) return null;
230285

231-
const values = BuildMonitor.timeRecorders.get(nodeId);
286+
const values = this.timeRecorders.get(nodeId);
232287
return {
233288
id: nodeId,
234289
name: nodeId,
@@ -254,7 +309,7 @@ export class BuildMonitor {
254309
duration: metrics.duration,
255310
},
256311
summary: {
257-
spendTime: Array.from(BuildMonitor.timeRecorders.entries()).map(
312+
spendTime: Array.from(this.timeRecorders.entries()).map(
258313
([id, time]) => `Node ${id} duration is ${time} ms`,
259314
),
260315
totalNodes: metrics.totalNodes,
@@ -288,7 +343,7 @@ export class BuildMonitor {
288343
report += ` Duration: ${nodeMetric.duration}ms\n`;
289344
report += ` Retries: ${nodeMetric.retryCount}\n`;
290345

291-
const values = BuildMonitor.timeRecorders.get(nodeId);
346+
const values = this.timeRecorders.get(nodeId);
292347
if (values) {
293348
report += ` Clock:\n`;
294349
values.forEach((value) => {

backend/src/build-system/utils/handler-helper.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ export async function chatSyncWithClocker(
1414
const duration = endTime.getTime() - startTime.getTime();
1515

1616
const inputContent = input.messages.map((m) => m.content).join('');
17-
BuildMonitor.timeRecorder(duration, name, step, inputContent, modelResponse);
17+
BuildMonitor.getInstance().timeRecorder(
18+
duration,
19+
name,
20+
step,
21+
inputContent,
22+
modelResponse,
23+
);
1824
return modelResponse;
1925
}
2026

@@ -32,7 +38,7 @@ export async function batchChatSyncWithClock(
3238
const inputContent = inputs
3339
.map((input) => input.messages.map((m) => m.content).join(''))
3440
.join('');
35-
BuildMonitor.timeRecorder(
41+
BuildMonitor.getInstance().timeRecorder(
3642
duration,
3743
id,
3844
step,

0 commit comments

Comments
 (0)