mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 21:32:56 -07:00
feat: add minimal V8 heap snapshot utility for memory diagnostics (#26440)
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import v8 from 'node:v8';
|
||||
import fs from 'node:fs';
|
||||
import { captureHeapSnapshot } from './heap-snapshot.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
|
||||
vi.mock('node:v8');
|
||||
vi.mock('node:fs');
|
||||
vi.mock('../utils/debugLogger.js', () => ({
|
||||
debugLogger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('heap-snapshot', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should capture a heap snapshot to a secure directory', () => {
|
||||
vi.mocked(fs.mkdtempSync).mockReturnValue('/tmp/gemini-heap-abc123');
|
||||
|
||||
const filePath = captureHeapSnapshot();
|
||||
|
||||
expect(filePath).toContain('gemini-heap-abc123');
|
||||
expect(filePath).toContain('.heapsnapshot');
|
||||
expect(v8.writeHeapSnapshot).toHaveBeenCalledWith(filePath);
|
||||
});
|
||||
|
||||
it('should return null and log an error if capture fails', () => {
|
||||
vi.mocked(fs.mkdtempSync).mockImplementation(() => {
|
||||
throw new Error('Disk full');
|
||||
});
|
||||
|
||||
const result = captureHeapSnapshot();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(debugLogger.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Failed to capture heap snapshot'),
|
||||
expect.any(Error),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import v8 from 'node:v8';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
|
||||
/**
|
||||
* Utility to capture a V8 heap snapshot.
|
||||
* Snapshots are saved to a secure, uniquely named temporary directory.
|
||||
*
|
||||
* @returns The absolute path to the generated .heapsnapshot file, or null if it failed.
|
||||
*/
|
||||
export function captureHeapSnapshot(): string | null {
|
||||
try {
|
||||
const timestamp = Date.now();
|
||||
const filename = `gemini-heap-${timestamp}.heapsnapshot`;
|
||||
|
||||
// Use mkdtempSync for a secure, uniquely named directory (mitigates symlink attacks)
|
||||
const snapshotsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-heap-'));
|
||||
const filePath = path.join(snapshotsDir, filename);
|
||||
|
||||
// Note: v8.writeHeapSnapshot is a synchronous, blocking operation.
|
||||
// This is intentional during diagnostics to capture a consistent heap state.
|
||||
v8.writeHeapSnapshot(filePath);
|
||||
|
||||
return filePath;
|
||||
} catch (error) {
|
||||
// Telemetry/diagnostic failures should not crash the application
|
||||
debugLogger.error('Failed to capture heap snapshot:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,7 @@ export {
|
||||
startGlobalMemoryMonitoring,
|
||||
stopGlobalMemoryMonitoring,
|
||||
} from './memory-monitor.js';
|
||||
export { captureHeapSnapshot } from './heap-snapshot.js';
|
||||
export type { MemorySnapshot, ProcessMetrics } from './memory-monitor.js';
|
||||
export {
|
||||
EventLoopMonitor,
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
isPerformanceMonitoringActive,
|
||||
} from './metrics.js';
|
||||
import { RateLimiter } from './rate-limiter.js';
|
||||
import { captureHeapSnapshot } from './heap-snapshot.js';
|
||||
|
||||
export interface MemorySnapshot {
|
||||
timestamp: number;
|
||||
@@ -386,6 +387,14 @@ export class MemoryMonitor {
|
||||
this.highWaterMarkTracker.resetAllHighWaterMarks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture a V8 heap snapshot for memory diagnostics.
|
||||
* @returns The absolute path to the generated .heapsnapshot file, or null if it failed.
|
||||
*/
|
||||
captureHeapSnapshot(): string | null {
|
||||
return captureHeapSnapshot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup resources
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user