mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
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:
@@ -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> = [];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,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", () => {
|
||||
|
||||
@@ -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,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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user