mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 00:21:21 +00:00
Add Sentry breadcrumbs and memory monitoring for crash diagnostics
- Add addBreadcrumb() utility to track user actions before crashes - Add startMemoryMonitoring() with configurable threshold and interval - Record breadcrumbs at agent spawn/kill operations - Auto-start memory monitoring when Sentry initializes (500MB threshold, 1min interval) This provides context in crash reports to help identify patterns leading to renderer crashes (MAESTRO-5A/4Y). Relates to MAESTRO-5A, MAESTRO-4Y
This commit is contained in:
@@ -191,6 +191,12 @@ if (crashReportingEnabled && !isDevelopment) {
|
||||
});
|
||||
// Add installation ID to Sentry for error correlation across installations
|
||||
setTag('installationId', installationId);
|
||||
|
||||
// Start memory monitoring for crash diagnostics (MAESTRO-5A/4Y)
|
||||
// Records breadcrumbs with memory state every minute, warns above 500MB heap
|
||||
import('./utils/sentry').then(({ startMemoryMonitoring }) => {
|
||||
startMemoryMonitoring(500, 60000);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.warn('Failed to initialize Sentry', 'Startup', { error: String(err) });
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as os from 'os';
|
||||
import { ProcessManager } from '../../process-manager';
|
||||
import { AgentDetector } from '../../agents';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { addBreadcrumb } from '../../utils/sentry';
|
||||
import { isWebContentsAvailable } from '../../utils/safe-send';
|
||||
import {
|
||||
buildAgentArgs,
|
||||
@@ -242,6 +243,14 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
||||
}),
|
||||
});
|
||||
|
||||
// Add breadcrumb for crash diagnostics (MAESTRO-5A/4Y)
|
||||
await addBreadcrumb('agent', `Spawn: ${config.toolType}`, {
|
||||
sessionId: config.sessionId,
|
||||
toolType: config.toolType,
|
||||
command: config.command,
|
||||
hasPrompt: !!config.prompt,
|
||||
});
|
||||
|
||||
// Get contextWindow: session-level override takes priority over agent-level config
|
||||
// Falls back to the agent's configOptions default (e.g., 400000 for Codex, 128000 for OpenCode)
|
||||
const contextWindow = getContextWindowValue(
|
||||
@@ -498,6 +507,8 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
||||
withIpcErrorLogging(handlerOpts('kill'), async (sessionId: string) => {
|
||||
const processManager = requireProcessManager(getProcessManager);
|
||||
logger.info(`Killing process: ${sessionId}`, LOG_CONTEXT, { sessionId });
|
||||
// Add breadcrumb for crash diagnostics (MAESTRO-5A/4Y)
|
||||
await addBreadcrumb('agent', `Kill: ${sessionId}`, { sessionId });
|
||||
return processManager.kill(sessionId);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -10,6 +10,16 @@ import { logger } from './logger';
|
||||
/** Sentry severity levels */
|
||||
export type SentrySeverityLevel = 'fatal' | 'error' | 'warning' | 'log' | 'info' | 'debug';
|
||||
|
||||
/** Breadcrumb categories for tracking user actions before crashes */
|
||||
export type BreadcrumbCategory =
|
||||
| 'session'
|
||||
| 'agent'
|
||||
| 'navigation'
|
||||
| 'ui'
|
||||
| 'ipc'
|
||||
| 'memory'
|
||||
| 'file';
|
||||
|
||||
/** Sentry module type for crash reporting */
|
||||
interface SentryModule {
|
||||
captureMessage: (
|
||||
@@ -20,6 +30,12 @@ interface SentryModule {
|
||||
exception: Error | unknown,
|
||||
captureContext?: { level?: SentrySeverityLevel; extra?: Record<string, unknown> }
|
||||
) => string;
|
||||
addBreadcrumb: (breadcrumb: {
|
||||
category?: string;
|
||||
message?: string;
|
||||
level?: SentrySeverityLevel;
|
||||
data?: Record<string, unknown>;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
/** Cached Sentry module reference */
|
||||
@@ -72,3 +88,103 @@ export async function captureMessage(
|
||||
logger.debug('Sentry not available for message reporting', '[Sentry]');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a breadcrumb to track user actions for crash diagnostics.
|
||||
* Breadcrumbs are recorded and sent with crash reports to help identify
|
||||
* what the user was doing before a crash occurred.
|
||||
*
|
||||
* Use this at key interaction points:
|
||||
* - Session switches, creates, deletes
|
||||
* - Agent spawns/kills
|
||||
* - Heavy UI operations (document graph, large history loads)
|
||||
* - File operations
|
||||
*
|
||||
* @param category - Category of the action (session, agent, navigation, ui, ipc, memory, file)
|
||||
* @param message - Brief description of the action
|
||||
* @param data - Optional additional context data
|
||||
* @param level - Severity level (default: 'info')
|
||||
*/
|
||||
export async function addBreadcrumb(
|
||||
category: BreadcrumbCategory,
|
||||
message: string,
|
||||
data?: Record<string, unknown>,
|
||||
level: SentrySeverityLevel = 'info'
|
||||
): Promise<void> {
|
||||
try {
|
||||
if (!sentryModule) {
|
||||
const sentry = await import('@sentry/electron/main');
|
||||
sentryModule = sentry;
|
||||
}
|
||||
sentryModule.addBreadcrumb({
|
||||
category,
|
||||
message,
|
||||
level,
|
||||
data,
|
||||
});
|
||||
} catch {
|
||||
// Sentry not available - silently ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory monitoring interval ID for cleanup
|
||||
*/
|
||||
let memoryMonitorInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
/**
|
||||
* Starts periodic memory monitoring that adds breadcrumbs when memory usage is high.
|
||||
* This helps diagnose crashes that may be related to memory pressure.
|
||||
*
|
||||
* @param thresholdMB - Memory threshold in MB above which to log warnings (default: 500MB)
|
||||
* @param intervalMs - Check interval in milliseconds (default: 60000 = 1 minute)
|
||||
*/
|
||||
export function startMemoryMonitoring(thresholdMB: number = 500, intervalMs: number = 60000): void {
|
||||
// Don't start if already running
|
||||
if (memoryMonitorInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
memoryMonitorInterval = setInterval(async () => {
|
||||
const memUsage = process.memoryUsage();
|
||||
const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
|
||||
const heapTotalMB = Math.round(memUsage.heapTotal / 1024 / 1024);
|
||||
const rssMB = Math.round(memUsage.rss / 1024 / 1024);
|
||||
|
||||
// Always add a breadcrumb with current memory state
|
||||
await addBreadcrumb('memory', `Memory: ${heapUsedMB}MB heap, ${rssMB}MB RSS`, {
|
||||
heapUsedMB,
|
||||
heapTotalMB,
|
||||
rssMB,
|
||||
externalMB: Math.round(memUsage.external / 1024 / 1024),
|
||||
});
|
||||
|
||||
// Log warning if heap usage exceeds threshold
|
||||
if (heapUsedMB > thresholdMB) {
|
||||
logger.warn(`High memory usage: ${heapUsedMB}MB heap (threshold: ${thresholdMB}MB)`, 'Memory', {
|
||||
heapUsedMB,
|
||||
heapTotalMB,
|
||||
rssMB,
|
||||
});
|
||||
await addBreadcrumb(
|
||||
'memory',
|
||||
`HIGH MEMORY: ${heapUsedMB}MB exceeds ${thresholdMB}MB threshold`,
|
||||
{ heapUsedMB, heapTotalMB, rssMB },
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
}, intervalMs);
|
||||
|
||||
logger.info(`Memory monitoring started (threshold: ${thresholdMB}MB, interval: ${intervalMs}ms)`, 'Memory');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the memory monitoring interval.
|
||||
*/
|
||||
export function stopMemoryMonitoring(): void {
|
||||
if (memoryMonitorInterval) {
|
||||
clearInterval(memoryMonitorInterval);
|
||||
memoryMonitorInterval = null;
|
||||
logger.info('Memory monitoring stopped', 'Memory');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user