diff --git a/src/extension.ts b/src/extension.ts index 5304a5624bb..2389046a318 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 00000000000..065305feb2d --- /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}`) + } +}