mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
Emit a warning when memory usage exceeds 7GB (#7613)
This commit is contained in:
@@ -47,6 +47,7 @@ import { validateAuthMethod } from '../config/auth.js';
|
|||||||
import { loadHierarchicalGeminiMemory } from '../config/config.js';
|
import { loadHierarchicalGeminiMemory } from '../config/config.js';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import { useHistory } from './hooks/useHistoryManager.js';
|
import { useHistory } from './hooks/useHistoryManager.js';
|
||||||
|
import { useMemoryMonitor } from './hooks/useMemoryMonitor.js';
|
||||||
import { useThemeCommand } from './hooks/useThemeCommand.js';
|
import { useThemeCommand } from './hooks/useThemeCommand.js';
|
||||||
import { useAuthCommand } from './auth/useAuth.js';
|
import { useAuthCommand } from './auth/useAuth.js';
|
||||||
import { useQuotaAndFallback } from './hooks/useQuotaAndFallback.js';
|
import { useQuotaAndFallback } from './hooks/useQuotaAndFallback.js';
|
||||||
@@ -123,6 +124,7 @@ const SHELL_HEIGHT_PADDING = 10;
|
|||||||
export const AppContainer = (props: AppContainerProps) => {
|
export const AppContainer = (props: AppContainerProps) => {
|
||||||
const { settings, config, initializationResult } = props;
|
const { settings, config, initializationResult } = props;
|
||||||
const historyManager = useHistory();
|
const historyManager = useHistory();
|
||||||
|
useMemoryMonitor(historyManager);
|
||||||
const [corgiMode, setCorgiMode] = useState(false);
|
const [corgiMode, setCorgiMode] = useState(false);
|
||||||
const [debugMessage, setDebugMessage] = useState<string>('');
|
const [debugMessage, setDebugMessage] = useState<string>('');
|
||||||
const [quittingMessages, setQuittingMessages] = useState<
|
const [quittingMessages, setQuittingMessages] = useState<
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ErrorMessage } from './messages/ErrorMessage.js';
|
|||||||
import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
|
import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
|
||||||
import { GeminiMessageContent } from './messages/GeminiMessageContent.js';
|
import { GeminiMessageContent } from './messages/GeminiMessageContent.js';
|
||||||
import { CompressionMessage } from './messages/CompressionMessage.js';
|
import { CompressionMessage } from './messages/CompressionMessage.js';
|
||||||
|
import { WarningMessage } from './messages/WarningMessage.js';
|
||||||
import { Box } from 'ink';
|
import { Box } from 'ink';
|
||||||
import { AboutBox } from './AboutBox.js';
|
import { AboutBox } from './AboutBox.js';
|
||||||
import { StatsDisplay } from './StatsDisplay.js';
|
import { StatsDisplay } from './StatsDisplay.js';
|
||||||
@@ -66,6 +67,7 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.type === 'info' && <InfoMessage text={item.text} />}
|
{item.type === 'info' && <InfoMessage text={item.text} />}
|
||||||
|
{item.type === 'warning' && <WarningMessage text={item.text} />}
|
||||||
{item.type === 'error' && <ErrorMessage text={item.text} />}
|
{item.type === 'error' && <ErrorMessage text={item.text} />}
|
||||||
{item.type === 'about' && (
|
{item.type === 'about' && (
|
||||||
<AboutBox
|
<AboutBox
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type React from 'react';
|
||||||
|
import { Box, Text } from 'ink';
|
||||||
|
import { Colors } from '../../colors.js';
|
||||||
|
import { RenderInline } from '../../utils/InlineMarkdownRenderer.js';
|
||||||
|
|
||||||
|
interface WarningMessageProps {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WarningMessage: React.FC<WarningMessageProps> = ({ text }) => {
|
||||||
|
const prefix = '⚠ ';
|
||||||
|
const prefixWidth = 3;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box flexDirection="row" marginTop={1}>
|
||||||
|
<Box width={prefixWidth}>
|
||||||
|
<Text color={Colors.AccentYellow}>{prefix}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box flexGrow={1}>
|
||||||
|
<Text wrap="wrap" color={Colors.AccentYellow}>
|
||||||
|
<RenderInline text={text} />
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
import {
|
||||||
|
useMemoryMonitor,
|
||||||
|
MEMORY_CHECK_INTERVAL,
|
||||||
|
MEMORY_WARNING_THRESHOLD,
|
||||||
|
} from './useMemoryMonitor.js';
|
||||||
|
import process from 'node:process';
|
||||||
|
import { MessageType } from '../types.js';
|
||||||
|
|
||||||
|
describe('useMemoryMonitor', () => {
|
||||||
|
const memoryUsageSpy = vi.spyOn(process, 'memoryUsage');
|
||||||
|
const addItem = vi.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not warn when memory usage is below threshold', () => {
|
||||||
|
memoryUsageSpy.mockReturnValue({
|
||||||
|
rss: MEMORY_WARNING_THRESHOLD / 2,
|
||||||
|
} as NodeJS.MemoryUsage);
|
||||||
|
renderHook(() => useMemoryMonitor({ addItem }));
|
||||||
|
vi.advanceTimersByTime(10000);
|
||||||
|
expect(addItem).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should warn when memory usage is above threshold', () => {
|
||||||
|
memoryUsageSpy.mockReturnValue({
|
||||||
|
rss: MEMORY_WARNING_THRESHOLD * 1.5,
|
||||||
|
} as NodeJS.MemoryUsage);
|
||||||
|
renderHook(() => useMemoryMonitor({ addItem }));
|
||||||
|
vi.advanceTimersByTime(MEMORY_CHECK_INTERVAL);
|
||||||
|
expect(addItem).toHaveBeenCalledTimes(1);
|
||||||
|
expect(addItem).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
type: MessageType.WARNING,
|
||||||
|
text: 'High memory usage detected: 10.50 GB. If you experience a crash, please file a bug report by running `/bug`',
|
||||||
|
},
|
||||||
|
expect.any(Number),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only warn once', () => {
|
||||||
|
memoryUsageSpy.mockReturnValue({
|
||||||
|
rss: MEMORY_WARNING_THRESHOLD * 1.5,
|
||||||
|
} as NodeJS.MemoryUsage);
|
||||||
|
const { rerender } = renderHook(() => useMemoryMonitor({ addItem }));
|
||||||
|
vi.advanceTimersByTime(MEMORY_CHECK_INTERVAL);
|
||||||
|
expect(addItem).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Rerender and advance timers, should not warn again
|
||||||
|
memoryUsageSpy.mockReturnValue({
|
||||||
|
rss: MEMORY_WARNING_THRESHOLD * 1.5,
|
||||||
|
} as NodeJS.MemoryUsage);
|
||||||
|
rerender();
|
||||||
|
vi.advanceTimersByTime(MEMORY_CHECK_INTERVAL);
|
||||||
|
expect(addItem).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import process from 'node:process';
|
||||||
|
import { type HistoryItemWithoutId, MessageType } from '../types.js';
|
||||||
|
|
||||||
|
export const MEMORY_WARNING_THRESHOLD = 7 * 1024 * 1024 * 1024; // 7GB in bytes
|
||||||
|
export const MEMORY_CHECK_INTERVAL = 60 * 1000; // one minute
|
||||||
|
|
||||||
|
interface MemoryMonitorOptions {
|
||||||
|
addItem: (item: HistoryItemWithoutId, timestamp: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMemoryMonitor = ({ addItem }: MemoryMonitorOptions) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
const usage = process.memoryUsage().rss;
|
||||||
|
if (usage > MEMORY_WARNING_THRESHOLD) {
|
||||||
|
addItem(
|
||||||
|
{
|
||||||
|
type: MessageType.WARNING,
|
||||||
|
text:
|
||||||
|
`High memory usage detected: ${(
|
||||||
|
usage /
|
||||||
|
(1024 * 1024 * 1024)
|
||||||
|
).toFixed(2)} GB. ` +
|
||||||
|
'If you experience a crash, please file a bug report by running `/bug`',
|
||||||
|
},
|
||||||
|
Date.now(),
|
||||||
|
);
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
}, MEMORY_CHECK_INTERVAL);
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [addItem]);
|
||||||
|
};
|
||||||
@@ -106,6 +106,11 @@ export type HistoryItemError = HistoryItemBase & {
|
|||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type HistoryItemWarning = HistoryItemBase & {
|
||||||
|
type: 'warning';
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type HistoryItemAbout = HistoryItemBase & {
|
export type HistoryItemAbout = HistoryItemBase & {
|
||||||
type: 'about';
|
type: 'about';
|
||||||
cliVersion: string;
|
cliVersion: string;
|
||||||
@@ -170,6 +175,7 @@ export type HistoryItemWithoutId =
|
|||||||
| HistoryItemGeminiContent
|
| HistoryItemGeminiContent
|
||||||
| HistoryItemInfo
|
| HistoryItemInfo
|
||||||
| HistoryItemError
|
| HistoryItemError
|
||||||
|
| HistoryItemWarning
|
||||||
| HistoryItemAbout
|
| HistoryItemAbout
|
||||||
| HistoryItemHelp
|
| HistoryItemHelp
|
||||||
| HistoryItemToolGroup
|
| HistoryItemToolGroup
|
||||||
@@ -186,6 +192,7 @@ export type HistoryItem = HistoryItemWithoutId & { id: number };
|
|||||||
export enum MessageType {
|
export enum MessageType {
|
||||||
INFO = 'info',
|
INFO = 'info',
|
||||||
ERROR = 'error',
|
ERROR = 'error',
|
||||||
|
WARNING = 'warning',
|
||||||
USER = 'user',
|
USER = 'user',
|
||||||
ABOUT = 'about',
|
ABOUT = 'about',
|
||||||
HELP = 'help',
|
HELP = 'help',
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
"src/ui/components/shared/vim-buffer-actions.test.ts",
|
"src/ui/components/shared/vim-buffer-actions.test.ts",
|
||||||
"src/ui/components/StatsDisplay.test.tsx",
|
"src/ui/components/StatsDisplay.test.tsx",
|
||||||
"src/ui/components/ToolStatsDisplay.test.tsx",
|
"src/ui/components/ToolStatsDisplay.test.tsx",
|
||||||
|
"src/ui/components/WarningMessage.test.tsx",
|
||||||
"src/ui/contexts/SessionContext.test.tsx",
|
"src/ui/contexts/SessionContext.test.tsx",
|
||||||
"src/ui/hooks/slashCommandProcessor.test.ts",
|
"src/ui/hooks/slashCommandProcessor.test.ts",
|
||||||
"src/ui/hooks/useAtCompletion.test.ts",
|
"src/ui/hooks/useAtCompletion.test.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user