refactor(perf): Remove the Memory Usage settings page

It was a live metrics view, not a setting, and duplicated the memory
indicator's popup, its status bar toggle and Clear Caches.

Claude-Session: https://claude.ai/code/session_01B65qLEKnkeYTGdbwhLpjCu
This commit is contained in:
l0ng-ai
2026-10-23 21:35:02 +08:00
parent 29e89f2525
commit deec86fab9
6 changed files with 3 additions and 255 deletions
+1 -2
View File
@@ -21,7 +21,7 @@ import { activate as templates, activateSettingsPages as templatesPages } from "
import { activate as todo, activateSettingsPages as todoPages } from "./todo";
import { activate as settings, activateSettingsWindow } from "./settings";
import { activate as app } from "./app";
import { activate as perf, activateSettingsPages as perfPages } from "./perf";
import { activate as perf } from "./perf";
import { activate as plugins, activateSettingsPages as pluginsPages } from "./plugins";
import { activate as tools, activateSettingsPages as toolsPages } from "./tools";
import { activate as scratch } from "./scratch";
@@ -90,7 +90,6 @@ export function activateSettingsWindowFeatures(parent: string): () => void {
["templates", templatesPages],
["todo", todoPages],
["plugins", pluginsPages],
["perf", perfPages],
["tools", toolsPages],
];
const disposers: Array<() => void> = [];
-85
View File
@@ -1,85 +0,0 @@
.page {
display: flex;
flex-direction: column;
gap: 22px;
max-width: 680px;
}
.title {
display: flex;
align-items: center;
gap: 8px;
height: 22px;
margin-bottom: 6px;
color: var(--fg-strong);
}
.rule {
flex: 1;
height: 1px;
background: var(--border);
}
.body {
display: flex;
flex-direction: column;
padding-left: 18px;
}
.row {
display: flex;
align-items: center;
gap: 8px;
min-height: 26px;
}
.label {
color: var(--fg);
min-width: 220px;
}
.value {
min-width: 72px;
text-align: right;
font-family: var(--font-mono);
font-size: var(--fs-small);
font-variant-numeric: tabular-nums;
color: var(--fg-strong);
}
.meter {
position: relative;
height: 8px;
max-width: 420px;
margin: 4px 0 8px;
border-radius: var(--radius-small);
background: var(--bg-control);
overflow: hidden;
}
.fill {
position: absolute;
inset: 0 auto 0 0;
background: var(--accent);
}
.hint {
color: var(--fg-subtle);
font-size: var(--fs-small);
}
.actions {
display: flex;
align-items: center;
gap: 12px;
margin-top: 8px;
}
.error {
color: var(--danger);
}
.empty {
color: var(--fg-subtle);
}
.strong .label,
.strong .value {
color: var(--fg-strong);
font-weight: 600;
}
.indented .label {
padding-left: 16px;
min-width: 204px;
}
.group {
display: flex;
flex-direction: column;
}
-143
View File
@@ -1,143 +0,0 @@
/**
* Settings › Memory Usage: memory of OpenIDEA, its language servers and other
* processes, subsystem sizes, "Clear caches" and the status bar indicator toggle.
*/
import { useEffect, type ReactNode } from "react";
import clsx from "clsx";
import { Button, Checkbox } from "@/ui";
import { executeAction } from "@/platform/actions";
import { isTauri } from "@/platform/ipc";
import { isStatusItemVisible, setStatusWidgetVisible, useStatusWidgetOverrides } from "@/workbench/statusBarWidgets";
import { formatBytes, formatCount, formatMs, formatSize, formatUptime, memoryRatio } from "./format";
import { memorySections, summarizeMemory } from "./memory";
import { MEMORY_WIDGET } from "./MemoryStatus";
import { startPolling } from "./poll";
import { refreshMetrics, usePerf } from "./store";
import styles from "./MemoryUsage.module.css";
function Row({
label,
value,
strong,
indented,
}: {
label: string;
value: string;
strong?: boolean;
indented?: boolean;
}) {
return (
<div className={clsx(styles.row, strong && styles.strong, indented && styles.indented)}>
<span className={styles.label}>{label}:</span>
<span className={styles.value}>{value}</span>
</div>
);
}
/** The same setting as the status bar context menu's "Memory Indicator". */
function IndicatorToggle() {
const overrides = useStatusWidgetOverrides();
const shown = isStatusItemVisible(MEMORY_WIDGET, overrides);
return (
<Checkbox
checked={shown}
onChange={(on) => void setStatusWidgetVisible(MEMORY_WIDGET.id, on)}
label="Show memory indicator in the status bar"
/>
);
}
function Section({ title, children }: { title: string; children: ReactNode }) {
return (
<section className={styles.section} aria-label={title}>
<div className={styles.title}>
{title}
<span className={styles.rule} aria-hidden />
</div>
<div className={styles.body}>{children}</div>
</section>
);
}
export function MemoryUsagePage() {
const m = usePerf((s) => s.metrics);
const error = usePerf((s) => s.error);
useEffect(() => {
if (!isTauri()) return;
return startPolling(() => void refreshMetrics(), { intervalMs: 2000 });
}, []);
if (!isTauri()) return <div className={styles.empty}>Memory usage is available in the desktop app.</div>;
if (!m) return <div className={styles.empty}>Loading…</div>;
const summary = summarizeMemory(m);
const own = (summary.app ?? 0) + (summary.languageServers ?? 0);
const ratio = memoryRatio(own, m.totalMemoryBytes);
return (
<div className={styles.page}>
<Section title="Memory">
<IndicatorToggle />
<div
className={styles.meter}
role="meter"
aria-label="OpenIDEA and language servers, share of physical memory"
aria-valuenow={Math.round(ratio * 100)}
aria-valuemin={0}
aria-valuemax={100}
>
<span className={styles.fill} style={{ width: `${Math.max(ratio * 100, ratio > 0 ? 1 : 0)}%` }} />
</div>
<div className={styles.hint}>
OpenIDEA and its language servers use {formatSize(own)} ({Math.round(ratio * 100)}%) of{" "}
{formatSize(m.totalMemoryBytes)} physical memory.
</div>
{memorySections(m.memory ?? []).map((sec) => (
<div key={sec.title} className={styles.group}>
<Row label={sec.title} value={sec.total} strong />
{sec.rows.map((r) => (
<Row key={r.key} label={r.detail ? `${r.label} (${r.detail})` : r.label} value={r.value} indented />
))}
</div>
))}
<Row label="Total" value={formatSize(summary.total)} strong />
{m.heapBytes != null && <Row label="Backend malloc in use" value={formatBytes(m.heapBytes)} />}
<div className={styles.hint}>
Memory as the OS reports it (macOS: Activity Monitor’s “Memory”; Linux: PSS; Windows: private bytes),
including child processes.
</div>
</Section>
<Section title="Caches">
<Row label="Open projects" value={formatCount(m.projects)} />
<Row label="Open documents" value={formatCount(m.openDocuments)} />
<Row
label={m.indexing ? "Index (indexing…)" : "Index"}
value={`${formatCount(m.indexFiles)} files · ${formatCount(m.indexSymbols)} symbols`}
/>
<Row label="Language servers" value={formatCount(m.languageServers)} />
<Row
label="Syntax trees"
value={`${m.syntax.trees} / ${m.syntax.documents} documents · ${formatBytes(m.syntax.treeTextBytes)}`}
/>
<div className={styles.actions}>
<Button icon="trash" onClick={() => void executeAction("perf.clearCaches")}>
Clear Caches
</Button>
<span className={styles.hint}>
Drops syntax trees and EditorConfig data and compacts the index; they are rebuilt on demand.
</span>
</div>
</Section>
<Section title="Startup">
<Row label="Uptime" value={formatUptime(m.uptimeMs)} />
<Row label="Process → main" value={formatMs(m.startup.processToMainMs)} />
<Row label="Process → backend ready" value={formatMs(m.startup.processToSetupMs)} />
<Row label="Backend set-up" value={formatMs(m.startup.setupMs)} />
<Row label="Process → first IPC" value={formatMs(m.startup.firstIpcMs)} />
<div className={styles.hint}>
“Process → main” is spent before any OpenIDEA code runs: exec, code-signature checks and dynamic loading. macOS
scans a freshly built, unsigned debug binary for several seconds on its first launch.
</div>
</Section>
{error && <div className={styles.error}>{error}</div>}
</div>
);
}
+1 -4
View File
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import type { ProcessMemory } from "@/platform/ipc";
import { formatBytes, formatCount, formatMs, formatSize, formatUptime, memoryRatio } from "../format";
import { formatBytes, formatCount, formatMs, formatSize, formatUptime } from "../format";
import { indicatorLabel, indicatorTooltip, memorySections, summarizeMemory } from "../memory";
import { RollingWindow, percentile } from "../stats";
import { isEditorTypingEntry, keystrokeLatencies, supportsEntryType } from "../events";
@@ -26,9 +26,6 @@ describe("format", () => {
expect(formatSize(1.1 * GB)).toBe("1.1 GB");
expect(formatSize(12 * 1024)).toBe("12 KB");
expect(formatSize(null)).toBe("–");
expect(memoryRatio(GB, 2 * GB)).toBe(0.5);
expect(memoryRatio(3 * GB, 2 * GB)).toBe(1);
expect(memoryRatio(null, 2 * GB)).toBe(0);
});
it("formats durations and counts", () => {
-6
View File
@@ -29,12 +29,6 @@ export function formatSize(bytes: number | null | undefined): string {
return `${trim(bytes / GB)} GB`;
}
/** Share (0..1) of `total`, clamped. */
export function memoryRatio(used: number | null | undefined, total: number | null | undefined): number {
if (!used || !total || total <= 0) return 0;
return Math.min(1, Math.max(0, used / total));
}
/** `0.4 ms`, `16 ms`, `2.3 s`. */
export function formatMs(ms: number | null | undefined): string {
if (ms == null || !Number.isFinite(ms)) return "–";
+1 -15
View File
@@ -1,30 +1,16 @@
/** Memory indicator, "Clear caches" and the dev performance overlay. */
import { lazyComponent } from "@/ui";
import { registerActions } from "@/platform/actions";
import { registerOverlay, registerSettingsPage, registerStatusItem } from "@/workbench/contributions";
import { registerOverlay, registerStatusItem } from "@/workbench/contributions";
import { perfActions } from "./actions";
import { MEMORY_WIDGET, MemoryStatus } from "./MemoryStatus";
import { PerfOverlay } from "./PerfOverlay";
/** Settings › Memory Usage (also used by the Settings window). */
export function activateSettingsPages(): () => void {
// Top level after Plugins, as in IDEA.
return registerSettingsPage({
id: "memory",
title: "Memory Usage",
order: 950,
keywords: ["memory", "ram", "clear caches", "index"],
component: lazyComponent(() => import("./MemoryUsagePage").then((m) => m.MemoryUsagePage)),
});
}
export function activate(): () => void {
const disposers = [
registerActions(perfActions()),
// Far right and hidden by default, like IDEA's memory indicator.
registerStatusItem({ ...MEMORY_WIDGET, align: "right", priority: 100, component: MemoryStatus }),
registerOverlay({ id: "perf.overlay", component: PerfOverlay }),
activateSettingsPages(),
];
return () => disposers.reverse().forEach((d) => d());
}