feat: add --show_memory_usage flag to display memory usage in status bar (#606)

This commit is contained in:
Jacob Richman 2025-05-30 22:18:01 +00:00 committed by GitHub
parent 3291ffbe09
commit 01768d7759
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 205 additions and 12 deletions

View File

@ -7,16 +7,10 @@
// packages/cli/src/config/config.test.ts // packages/cli/src/config/config.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// import * as fsPromises from 'fs/promises';
// import * as fsSync from 'fs';
import * as os from 'os'; import * as os from 'os';
// import * as path from 'path'; // Unused, so removing import { loadCliConfig } from './config.js';
// import { readPackageUp } from 'read-package-up'; import { Settings } from './settings.js';
// import { import * as ServerConfig from '@gemini-code/server';
// loadHierarchicalGeminiMemory,
// } from './config';
// import { Settings } from './settings';
// import * as ServerConfig from '@gemini-code/server';
const MOCK_HOME_DIR = '/mock/home/user'; const MOCK_HOME_DIR = '/mock/home/user';
@ -28,7 +22,92 @@ vi.mock('os', async (importOriginal) => {
}; };
}); });
// Further mocking of fs, read-package-up, etc. would go here if tests were active. vi.mock('read-package-up', () => ({
readPackageUp: vi.fn(() =>
Promise.resolve({ packageJson: { version: 'test-version' } }),
),
}));
vi.mock('@gemini-code/server', async () => {
const actualServer = await vi.importActual<typeof ServerConfig>(
'@gemini-code/server',
);
return {
...actualServer,
loadEnvironment: vi.fn(),
createServerConfig: vi.fn((params) => ({
// Mock the config object and its methods
getApiKey: () => params.apiKey,
getModel: () => params.model,
getSandbox: () => params.sandbox,
getTargetDir: () => params.targetDir,
getDebugMode: () => params.debugMode,
getQuestion: () => params.question,
getFullContext: () => params.fullContext,
getCoreTools: () => params.coreTools,
getToolDiscoveryCommand: () => params.toolDiscoveryCommand,
getToolCallCommand: () => params.toolCallCommand,
getMcpServerCommand: () => params.mcpServerCommand,
getMcpServers: () => params.mcpServers,
getUserAgent: () => params.userAgent,
getUserMemory: () => params.userMemory,
getGeminiMdFileCount: () => params.geminiMdFileCount,
getVertexAI: () => params.vertexai,
getShowMemoryUsage: () => params.showMemoryUsage, // Added for the test
// Add any other methods that are called on the config object
setUserMemory: vi.fn(),
setGeminiMdFileCount: vi.fn(),
})),
loadServerHierarchicalMemory: vi.fn(() =>
Promise.resolve({ memoryContent: '', fileCount: 0 }),
),
};
});
describe('loadCliConfig', () => {
const originalArgv = process.argv;
const originalEnv = { ...process.env };
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(os.homedir).mockReturnValue(MOCK_HOME_DIR);
process.env.GEMINI_API_KEY = 'test-api-key'; // Ensure API key is set for tests
});
afterEach(() => {
process.argv = originalArgv;
process.env = originalEnv;
vi.restoreAllMocks();
});
it('should set showMemoryUsage to true when --memory flag is present', async () => {
process.argv = ['node', 'script.js', '--show_memory_usage'];
const settings: Settings = {};
const config = await loadCliConfig(settings);
expect(config.getShowMemoryUsage()).toBe(true);
});
it('should set showMemoryUsage to false when --memory flag is not present', async () => {
process.argv = ['node', 'script.js'];
const settings: Settings = {};
const config = await loadCliConfig(settings);
expect(config.getShowMemoryUsage()).toBe(false);
});
it('should set showMemoryUsage to false by default from settings if CLI flag is not present', async () => {
process.argv = ['node', 'script.js'];
const settings: Settings = { showMemoryUsage: false };
const config = await loadCliConfig(settings);
expect(config.getShowMemoryUsage()).toBe(false);
});
it('should prioritize CLI flag over settings for showMemoryUsage (CLI true, settings false)', async () => {
process.argv = ['node', 'script.js', '--show_memory_usage'];
const settings: Settings = { showMemoryUsage: false };
const config = await loadCliConfig(settings);
expect(config.getShowMemoryUsage()).toBe(true);
});
});
describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => { describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => {
beforeEach(() => { beforeEach(() => {

View File

@ -35,6 +35,7 @@ interface CliArgs {
debug: boolean | undefined; debug: boolean | undefined;
prompt: string | undefined; prompt: string | undefined;
all_files: boolean | undefined; all_files: boolean | undefined;
show_memory_usage: boolean | undefined;
} }
async function parseArguments(): Promise<CliArgs> { async function parseArguments(): Promise<CliArgs> {
@ -67,6 +68,11 @@ async function parseArguments(): Promise<CliArgs> {
description: 'Include ALL files in context?', description: 'Include ALL files in context?',
default: false, default: false,
}) })
.option('show_memory_usage', {
type: 'boolean',
description: 'Show memory usage in status bar',
default: false,
})
.help() .help()
.alias('h', 'help') .alias('h', 'help')
.strict().argv; .strict().argv;
@ -152,6 +158,7 @@ export async function loadCliConfig(settings: Settings): Promise<Config> {
userMemory: memoryContent, userMemory: memoryContent,
geminiMdFileCount: fileCount, geminiMdFileCount: fileCount,
vertexai: useVertexAI, vertexai: useVertexAI,
showMemoryUsage: argv.show_memory_usage || false,
}; };
return createServerConfig(configParams); return createServerConfig(configParams);

View File

@ -27,6 +27,7 @@ export interface Settings {
toolCallCommand?: string; toolCallCommand?: string;
mcpServerCommand?: string; mcpServerCommand?: string;
mcpServers?: Record<string, MCPServerConfig>; mcpServers?: Record<string, MCPServerConfig>;
showMemoryUsage?: boolean;
// Add other settings here. // Add other settings here.
} }

View File

@ -442,6 +442,9 @@ export const App = ({
corgiMode={corgiMode} corgiMode={corgiMode}
errorCount={errorCount} errorCount={errorCount}
showErrorDetails={showErrorDetails} showErrorDetails={showErrorDetails}
showMemoryUsage={
config.getDebugMode() || config.getShowMemoryUsage()
}
/> />
</Box> </Box>
</Box> </Box>

View File

@ -9,6 +9,8 @@ import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { Colors } from '../colors.js';
import { shortenPath, tildeifyPath } from '@gemini-code/server'; import { shortenPath, tildeifyPath } from '@gemini-code/server';
import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js'; import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js';
import process from 'node:process';
import { MemoryUsageDisplay } from './MemoryUsageDisplay.js';
interface FooterProps { interface FooterProps {
model: string; model: string;
@ -20,6 +22,7 @@ interface FooterProps {
corgiMode: boolean; corgiMode: boolean;
errorCount: number; errorCount: number;
showErrorDetails: boolean; showErrorDetails: boolean;
showMemoryUsage?: boolean;
} }
export const Footer: React.FC<FooterProps> = ({ export const Footer: React.FC<FooterProps> = ({
@ -31,6 +34,7 @@ export const Footer: React.FC<FooterProps> = ({
corgiMode, corgiMode,
errorCount, errorCount,
showErrorDetails, showErrorDetails,
showMemoryUsage,
}) => ( }) => (
<Box marginTop={1} justifyContent="space-between" width="100%"> <Box marginTop={1} justifyContent="space-between" width="100%">
<Box> <Box>
@ -86,6 +90,7 @@ export const Footer: React.FC<FooterProps> = ({
<ConsoleSummaryDisplay errorCount={errorCount} /> <ConsoleSummaryDisplay errorCount={errorCount} />
</Box> </Box>
)} )}
{showMemoryUsage && <MemoryUsageDisplay />}
</Box> </Box>
</Box> </Box>
); );

View File

@ -0,0 +1,40 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import React, { useEffect, useState } from 'react';
import { Box, Text } from 'ink';
import { Colors } from '../colors.js';
import process from 'node:process';
import { formatMemoryUsage } from '../utils/formatters.js';
export const MemoryUsageDisplay: React.FC = () => {
const [memoryUsage, setMemoryUsage] = useState<string>('');
const [memoryUsageColor, setMemoryUsageColor] = useState<string>(
Colors.SubtleComment,
);
useEffect(() => {
const updateMemory = () => {
const usage = process.memoryUsage().rss;
setMemoryUsage(formatMemoryUsage(usage));
setMemoryUsageColor(
usage >= 2 * 1024 * 1024 * 1024
? Colors.AccentRed
: Colors.SubtleComment,
);
};
const intervalId = setInterval(updateMemory, 2000);
updateMemory(); // Initial update
return () => clearInterval(intervalId);
}, []);
return (
<Box>
<Text color={Colors.SubtleComment}>| </Text>
<Text color={memoryUsageColor}>{memoryUsage}</Text>
</Box>
);
};

View File

@ -9,9 +9,37 @@ const { mockProcessExit } = vi.hoisted(() => ({
})); }));
vi.mock('node:process', () => ({ vi.mock('node:process', () => ({
default: {
exit: mockProcessExit, exit: mockProcessExit,
cwd: vi.fn(() => '/mock/cwd'), cwd: vi.fn(() => '/mock/cwd'),
env: { ...process.env }, get env() {
return process.env;
}, // Use a getter to ensure current process.env is used
platform: 'test-platform',
version: 'test-node-version',
memoryUsage: vi.fn(() => ({
rss: 12345678,
heapTotal: 23456789,
heapUsed: 10234567,
external: 1234567,
arrayBuffers: 123456,
})),
},
// Provide top-level exports as well for compatibility
exit: mockProcessExit,
cwd: vi.fn(() => '/mock/cwd'),
get env() {
return process.env;
}, // Use a getter here too
platform: 'test-platform',
version: 'test-node-version',
memoryUsage: vi.fn(() => ({
rss: 12345678,
heapTotal: 23456789,
heapUsed: 10234567,
external: 1234567,
arrayBuffers: 123456,
})),
})); }));
vi.mock('node:fs/promises', () => ({ vi.mock('node:fs/promises', () => ({
@ -227,7 +255,7 @@ describe('useSlashCommandProcessor', () => {
seatbeltProfileVar?: string, seatbeltProfileVar?: string,
) => { ) => {
const cliVersion = 'test-version'; const cliVersion = 'test-version';
const osVersion = `${process.platform} ${process.version}`; const osVersion = 'test-platform test-node-version';
let sandboxEnvStr = 'no sandbox'; let sandboxEnvStr = 'no sandbox';
if (sandboxEnvVar && sandboxEnvVar !== 'sandbox-exec') { if (sandboxEnvVar && sandboxEnvVar !== 'sandbox-exec') {
sandboxEnvStr = sandboxEnvVar.replace(/^gemini-(?:code-)?/, ''); sandboxEnvStr = sandboxEnvVar.replace(/^gemini-(?:code-)?/, '');
@ -235,6 +263,8 @@ describe('useSlashCommandProcessor', () => {
sandboxEnvStr = `sandbox-exec (${seatbeltProfileVar || 'unknown'})`; sandboxEnvStr = `sandbox-exec (${seatbeltProfileVar || 'unknown'})`;
} }
const modelVersion = 'test-model'; const modelVersion = 'test-model';
// Use the mocked memoryUsage value
const memoryUsage = '11.8 MB';
const diagnosticInfo = ` const diagnosticInfo = `
## Describe the bug ## Describe the bug
@ -249,6 +279,7 @@ Add any other context about the problem here.
* **Operating System:** ${osVersion} * **Operating System:** ${osVersion}
* **Sandbox Environment:** ${sandboxEnvStr} * **Sandbox Environment:** ${sandboxEnvStr}
* **Model Version:** ${modelVersion} * **Model Version:** ${modelVersion}
* **Memory Usage:** ${memoryUsage}
`; `;
let url = let url =
'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.md'; 'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.md';

View File

@ -7,11 +7,13 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { type PartListUnion } from '@google/genai'; import { type PartListUnion } from '@google/genai';
import open from 'open'; import open from 'open';
import process from 'node:process';
import { UseHistoryManagerReturn } from './useHistoryManager.js'; import { UseHistoryManagerReturn } from './useHistoryManager.js';
import { Config } from '@gemini-code/server'; import { Config } from '@gemini-code/server';
import { Message, MessageType, HistoryItemWithoutId } from '../types.js'; import { Message, MessageType, HistoryItemWithoutId } from '../types.js';
import { createShowMemoryAction } from './useShowMemoryCommand.js'; import { createShowMemoryAction } from './useShowMemoryCommand.js';
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js'; import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
import { formatMemoryUsage } from '../utils/formatters.js';
export interface SlashCommandActionReturn { export interface SlashCommandActionReturn {
shouldScheduleTool?: boolean; shouldScheduleTool?: boolean;
@ -206,6 +208,7 @@ export const useSlashCommandProcessor = (
sandboxEnv = `sandbox-exec (${process.env.SEATBELT_PROFILE || 'unknown'})`; sandboxEnv = `sandbox-exec (${process.env.SEATBELT_PROFILE || 'unknown'})`;
} }
const modelVersion = config?.getModel() || 'Unknown'; const modelVersion = config?.getModel() || 'Unknown';
const memoryUsage = formatMemoryUsage(process.memoryUsage().rss);
const diagnosticInfo = ` const diagnosticInfo = `
## Describe the bug ## Describe the bug
@ -220,6 +223,7 @@ Add any other context about the problem here.
* **Operating System:** ${osVersion} * **Operating System:** ${osVersion}
* **Sandbox Environment:** ${sandboxEnv} * **Sandbox Environment:** ${sandboxEnv}
* **Model Version:** ${modelVersion} * **Model Version:** ${modelVersion}
* **Memory Usage:** ${memoryUsage}
`; `;
let bugReportUrl = let bugReportUrl =

View File

@ -0,0 +1,16 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
export const formatMemoryUsage = (bytes: number): string => {
const gb = bytes / (1024 * 1024 * 1024);
if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(1)} KB`;
}
if (bytes < 1024 * 1024 * 1024) {
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
return `${gb.toFixed(2)} GB`;
};

View File

@ -54,6 +54,7 @@ export interface ConfigParameters {
geminiMdFileCount?: number; geminiMdFileCount?: number;
alwaysSkipModificationConfirmation?: boolean; alwaysSkipModificationConfirmation?: boolean;
vertexai?: boolean; vertexai?: boolean;
showMemoryUsage?: boolean;
} }
export class Config { export class Config {
@ -75,6 +76,7 @@ export class Config {
private geminiMdFileCount: number; private geminiMdFileCount: number;
private alwaysSkipModificationConfirmation: boolean; private alwaysSkipModificationConfirmation: boolean;
private readonly vertexai: boolean | undefined; private readonly vertexai: boolean | undefined;
private readonly showMemoryUsage: boolean;
constructor(params: ConfigParameters) { constructor(params: ConfigParameters) {
this.apiKey = params.apiKey; this.apiKey = params.apiKey;
@ -95,6 +97,7 @@ export class Config {
this.alwaysSkipModificationConfirmation = this.alwaysSkipModificationConfirmation =
params.alwaysSkipModificationConfirmation ?? false; params.alwaysSkipModificationConfirmation ?? false;
this.vertexai = params.vertexai; this.vertexai = params.vertexai;
this.showMemoryUsage = params.showMemoryUsage ?? false;
this.toolRegistry = createToolRegistry(this); this.toolRegistry = createToolRegistry(this);
} }
@ -181,6 +184,10 @@ export class Config {
getVertexAI(): boolean | undefined { getVertexAI(): boolean | undefined {
return this.vertexai; return this.vertexai;
} }
getShowMemoryUsage(): boolean {
return this.showMemoryUsage;
}
} }
function findEnvFile(startDir: string): string | null { function findEnvFile(startDir: string): string | null {