From bcb4268561dffb20d7e31d95ab57962a86a2f2f3 Mon Sep 17 00:00:00 2001 From: Eric Wheeler Date: Wed, 21 May 2025 19:01:49 -0700 Subject: [PATCH] feat: add global state storage size logging with threshold filter Adds a utility function to log global state storage sizes with a configurable threshold filter (default 10KB) to help users identify large items. This provides visibility into which global state items are consuming significant storage space, making it easier to diagnose performance issues. Fixes: #3809 Signed-off-by: Eric Wheeler --- src/extension.ts | 21 +++++++++-- src/utils/storageUtils.ts | 73 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/utils/storageUtils.ts diff --git a/src/extension.ts b/src/extension.ts index 5304a5624b..2389046a31 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,6 +21,7 @@ import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider" import { TerminalRegistry } from "./integrations/terminal/TerminalRegistry" import { McpServerManager } from "./services/mcp/McpServerManager" import { telemetryService } from "./services/telemetry/TelemetryService" +import { logGlobalStorageSize } from "./utils/storageUtils" import { API } from "./exports/api" import { migrateSettings } from "./utils/migrateSettings" import { formatLanguage } from "./shared/language" @@ -43,12 +44,23 @@ import { initializeI18n } from "./i18n" */ let outputChannel: vscode.OutputChannel -let extensionContext: vscode.ExtensionContext +let _extensionContext: vscode.ExtensionContext | undefined + +/** + * Returns the extension context. + * @throws Error if the extension context is not available. + */ +export function getExtensionContext(): vscode.ExtensionContext { + if (!_extensionContext) { + throw new Error("Extension context is not available.") + } + return _extensionContext +} // This method is called when your extension is activated. // Your extension is activated the very first time the command is executed. export async function activate(context: vscode.ExtensionContext) { - extensionContext = context + _extensionContext = context outputChannel = vscode.window.createOutputChannel(Package.outputChannel) context.subscriptions.push(outputChannel) outputChannel.appendLine(`${Package.name} extension activated`) @@ -130,6 +142,9 @@ export async function activate(context: vscode.ExtensionContext) { const socketPath = process.env.ROO_CODE_IPC_SOCKET_PATH const enableLogging = typeof socketPath === "string" + // Log global storage items larger than 10KB + logGlobalStorageSize(10 * 1024) + // Watch the core files and automatically reload the extension host const enableCoreAutoReload = process.env?.NODE_ENV === "development" if (enableCoreAutoReload) { @@ -150,7 +165,7 @@ export async function activate(context: vscode.ExtensionContext) { // This method is called when your extension is deactivated. export async function deactivate() { outputChannel.appendLine(`${Package.name} extension deactivated`) - await McpServerManager.cleanup(extensionContext) + await McpServerManager.cleanup(getExtensionContext()) telemetryService.shutdown() TerminalRegistry.cleanup() } diff --git a/src/utils/storageUtils.ts b/src/utils/storageUtils.ts new file mode 100644 index 0000000000..065305feb2 --- /dev/null +++ b/src/utils/storageUtils.ts @@ -0,0 +1,73 @@ +import { getExtensionContext } from "../extension" + +/** + * Formats a number of bytes into a human-readable string with units (Bytes, KB, MB, GB, etc.). + * + * @param bytes The number of bytes. + * @param decimals The number of decimal places to include (default is 2). + * @returns A string representing the formatted bytes. + */ +export function formatBytes(bytes: number, decimals = 2): string { + if (bytes === 0) { + return "0 Bytes" + } + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i] +} + +/** + * Logs the estimated sizes of items in the VS Code global state to the console. + * Only logs items that exceed the specified minimum size threshold. + * + * @param minSizeBytes The minimum size in bytes to log (default: 10KB) + */ +export function logGlobalStorageSize(minSizeBytes: number = 10 * 1024): void { + const context = getExtensionContext() + try { + console.log(`[Roo Code] Global State Storage Estimates (items > ${formatBytes(minSizeBytes)}):`) + const globalStateKeys = context.globalState.keys() + const stateSizes: { key: string; size: number }[] = [] + let totalSize = 0 + let itemsSkipped = 0 + + for (const key of globalStateKeys) { + const value = context.globalState.get(key) + try { + const valueString = JSON.stringify(value) + const size = valueString.length + totalSize += size + + if (size >= minSizeBytes) { + stateSizes.push({ key, size }) + } else { + itemsSkipped++ + } + } catch (e) { + // Handle cases where value might not be stringifiable + stateSizes.push({ key, size: -1 }) // Indicate an error or unmeasurable size + console.log(` - ${key}: (Error calculating size)`) + } + } + + stateSizes.sort((a, b) => b.size - a.size) + + stateSizes.forEach((item) => { + if (item.size === -1) { + // Already logged error + } else if (item.size === undefined) { + console.log(` - ${item.key}: (undefined value)`) + } else { + console.log(` - ${item.key}: ${formatBytes(item.size)}`) + } + }) + + console.log(` Total size of all items: ${formatBytes(totalSize)}`) + console.log(` Items below threshold (${itemsSkipped}): not shown`) + console.log("---") + } catch (e: any) { + console.log(`Error displaying global state sizes: ${e.message}`) + } +}