mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-19 12:04:18 +02:00
remove logging
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { app, BrowserWindow, desktopCapturer, dialog, ipcMain, Menu, screen, session, shell } from 'electron'
|
import { app, BrowserWindow, desktopCapturer, dialog, ipcMain, Menu, screen, session, shell } from 'electron'
|
||||||
import type { BrowserWindowConstructorOptions, MenuItemConstructorOptions, OpenDialogOptions, WebContents } from 'electron'
|
import type { BrowserWindowConstructorOptions, MenuItemConstructorOptions, OpenDialogOptions, WebContents } from 'electron'
|
||||||
import { appendFile, mkdir, writeFile } from 'node:fs/promises'
|
|
||||||
import { extname, join, resolve } from 'path'
|
import { extname, join, resolve } from 'path'
|
||||||
import type {
|
import type {
|
||||||
AstraControlCommand,
|
AstraControlCommand,
|
||||||
@@ -23,11 +22,6 @@ import type {
|
|||||||
ThemeLibrarySnapshot,
|
ThemeLibrarySnapshot,
|
||||||
} from '../types/theme'
|
} from '../types/theme'
|
||||||
import { RESIZE_DIRECTIONS, type ResizeDirection } from '../types/windowResize'
|
import { RESIZE_DIRECTIONS, type ResizeDirection } from '../types/windowResize'
|
||||||
import type {
|
|
||||||
PerformanceMemoryLogRecord,
|
|
||||||
PerformanceMemorySnapshot,
|
|
||||||
PerformanceRendererProcessSnapshot,
|
|
||||||
} from '../types/performance'
|
|
||||||
import { normalizeProfile } from '../shared/profileState'
|
import { normalizeProfile } from '../shared/profileState'
|
||||||
import { calculateResizedWindowBounds } from '../shared/windowResize'
|
import { calculateResizedWindowBounds } from '../shared/windowResize'
|
||||||
import { FileBackedProfileLibrary } from './profileLibrary'
|
import { FileBackedProfileLibrary } from './profileLibrary'
|
||||||
@@ -75,186 +69,6 @@ const POPOUT_DEFAULTS = {
|
|||||||
minWidth: 220,
|
minWidth: 220,
|
||||||
minHeight: 160,
|
minHeight: 160,
|
||||||
}
|
}
|
||||||
const MEMORY_LOG_DIRECTORY_NAME = 'Prism Logs'
|
|
||||||
const MEMORY_LOG_FILE_NAME = 'memory-trace-latest.csv'
|
|
||||||
const memoryLogSessionStartedAt = Date.now()
|
|
||||||
let memoryLogInitialized = false
|
|
||||||
let memoryLogInitialization: Promise<void> | null = null
|
|
||||||
let memoryLogWriteQueue: Promise<void> = Promise.resolve()
|
|
||||||
|
|
||||||
function kilobytesToMegabytes(value: number | undefined): number {
|
|
||||||
return Math.round((((value ?? 0) / 1024) * 10)) / 10
|
|
||||||
}
|
|
||||||
|
|
||||||
function workingSetForMetric(metric: Electron.ProcessMetric | undefined): number {
|
|
||||||
return kilobytesToMegabytes(metric?.memory.workingSetSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
function sumWorkingSet(metrics: Electron.ProcessMetric[], predicate: (metric: Electron.ProcessMetric) => boolean): number {
|
|
||||||
let totalKilobytes = 0
|
|
||||||
for (const metric of metrics) {
|
|
||||||
if (!predicate(metric)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
totalKilobytes += metric.memory.workingSetSize
|
|
||||||
}
|
|
||||||
return kilobytesToMegabytes(totalKilobytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRendererProcessSnapshots(metrics: Electron.ProcessMetric[]): PerformanceRendererProcessSnapshot[] {
|
|
||||||
const rendererMetrics = metrics.filter((metric) => metric.type === 'Tab')
|
|
||||||
const metricsByPid = new Map(rendererMetrics.map((metric) => [metric.pid, metric]))
|
|
||||||
const snapshots: PerformanceRendererProcessSnapshot[] = []
|
|
||||||
const seenPids = new Set<number>()
|
|
||||||
|
|
||||||
const pushSnapshot = (label: string, webContents: WebContents | null | undefined): void => {
|
|
||||||
if (!webContents || webContents.isDestroyed()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const pid = webContents.getOSProcessId()
|
|
||||||
if (!pid || seenPids.has(pid)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const metric = metricsByPid.get(pid)
|
|
||||||
if (!metric) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshots.push({
|
|
||||||
pid,
|
|
||||||
label,
|
|
||||||
workingSetMb: workingSetForMetric(metric),
|
|
||||||
})
|
|
||||||
seenPids.add(pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
pushSnapshot('main', mainWindow?.webContents ?? null)
|
|
||||||
for (const [kind, window] of scopePopoutWindows) {
|
|
||||||
pushSnapshot(`popout:${kind}`, window.webContents)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const metric of rendererMetrics) {
|
|
||||||
if (seenPids.has(metric.pid)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshots.push({
|
|
||||||
pid: metric.pid,
|
|
||||||
label: metric.name ? `renderer:${metric.name}` : `renderer:${metric.pid}`,
|
|
||||||
workingSetMb: workingSetForMetric(metric),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshots.sort((left, right) => left.label.localeCompare(right.label) || left.pid - right.pid)
|
|
||||||
return snapshots
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMemoryLogDirectory(): string {
|
|
||||||
return join(app.getPath('documents'), MEMORY_LOG_DIRECTORY_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMemoryLogPath(): string {
|
|
||||||
return join(getMemoryLogDirectory(), MEMORY_LOG_FILE_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeCsvField(value: string | number | boolean | null): string {
|
|
||||||
if (value === null) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = String(value)
|
|
||||||
if (!/[",\n]/.test(text)) {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
return `"${text.replace(/"/g, '""')}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ensureMemoryLogFile(): Promise<void> {
|
|
||||||
if (memoryLogInitialized) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!memoryLogInitialization) {
|
|
||||||
memoryLogInitialization = (async () => {
|
|
||||||
await mkdir(getMemoryLogDirectory(), { recursive: true })
|
|
||||||
const header = [
|
|
||||||
'session_started_at_iso',
|
|
||||||
'captured_at_iso',
|
|
||||||
'elapsed_s',
|
|
||||||
'app_mb',
|
|
||||||
'main_mb',
|
|
||||||
'renderer_mb',
|
|
||||||
'renderer_private_mb',
|
|
||||||
'renderer_total_mb',
|
|
||||||
'renderer_process_count',
|
|
||||||
'renderer_processes',
|
|
||||||
'gpu_mb',
|
|
||||||
'utility_mb',
|
|
||||||
'js_heap_used_mb',
|
|
||||||
'js_heap_limit_mb',
|
|
||||||
'renderer_delta_mb',
|
|
||||||
'renderer_total_delta_mb',
|
|
||||||
'app_delta_mb',
|
|
||||||
'frame_target',
|
|
||||||
'docked_render_fps',
|
|
||||||
'is_capturing',
|
|
||||||
'capture_status',
|
|
||||||
'visible_scopes',
|
|
||||||
'popped_out_scopes',
|
|
||||||
].join(',')
|
|
||||||
await writeFile(getMemoryLogPath(), `${header}\n`, 'utf8')
|
|
||||||
memoryLogInitialized = true
|
|
||||||
})().finally(() => {
|
|
||||||
memoryLogInitialization = null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await memoryLogInitialization
|
|
||||||
}
|
|
||||||
|
|
||||||
function queueMemoryLogWrite(record: PerformanceMemoryLogRecord): void {
|
|
||||||
const line = [
|
|
||||||
new Date(memoryLogSessionStartedAt).toISOString(),
|
|
||||||
new Date(record.capturedAt).toISOString(),
|
|
||||||
record.elapsedSeconds.toFixed(1),
|
|
||||||
record.appMb.toFixed(1),
|
|
||||||
record.mainMb.toFixed(1),
|
|
||||||
record.rendererMb.toFixed(1),
|
|
||||||
record.rendererPrivateMb === null ? '' : record.rendererPrivateMb.toFixed(1),
|
|
||||||
record.rendererTotalMb.toFixed(1),
|
|
||||||
Math.round(record.rendererProcessCount),
|
|
||||||
record.rendererProcesses
|
|
||||||
.map((process) => `${process.label}@${process.pid}=${process.workingSetMb.toFixed(1)}`)
|
|
||||||
.join('|'),
|
|
||||||
record.gpuMb.toFixed(1),
|
|
||||||
record.utilityMb.toFixed(1),
|
|
||||||
record.jsHeapUsedMb === null ? '' : record.jsHeapUsedMb.toFixed(1),
|
|
||||||
record.jsHeapLimitMb === null ? '' : record.jsHeapLimitMb.toFixed(1),
|
|
||||||
record.rendererDeltaMb.toFixed(1),
|
|
||||||
record.rendererTotalDeltaMb.toFixed(1),
|
|
||||||
record.appDeltaMb.toFixed(1),
|
|
||||||
record.frameTarget,
|
|
||||||
Math.round(record.dockedRenderFps),
|
|
||||||
record.isCapturing,
|
|
||||||
record.captureStatus,
|
|
||||||
record.visibleScopes.join('|'),
|
|
||||||
record.poppedOutScopes.join('|'),
|
|
||||||
]
|
|
||||||
.map((field) => escapeCsvField(field))
|
|
||||||
.join(',')
|
|
||||||
|
|
||||||
memoryLogWriteQueue = memoryLogWriteQueue
|
|
||||||
.then(async () => {
|
|
||||||
await ensureMemoryLogFile()
|
|
||||||
await appendFile(getMemoryLogPath(), `${line}\n`, 'utf8')
|
|
||||||
})
|
|
||||||
.catch((error: unknown) => {
|
|
||||||
console.error('Failed to write memory trace log:', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getProfileLibrary(): FileBackedProfileLibrary {
|
function getProfileLibrary(): FileBackedProfileLibrary {
|
||||||
if (!profileLibrary) {
|
if (!profileLibrary) {
|
||||||
@@ -1089,43 +903,6 @@ function setupIPC(): void {
|
|||||||
return getWindowFromSender(event.sender)?.isAlwaysOnTop() ?? true
|
return getWindowFromSender(event.sender)?.isAlwaysOnTop() ?? true
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('performance:get-memory-log-path', async () => {
|
|
||||||
await ensureMemoryLogFile()
|
|
||||||
return getMemoryLogPath()
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('performance:reveal-memory-log', async () => {
|
|
||||||
await ensureMemoryLogFile()
|
|
||||||
shell.showItemInFolder(getMemoryLogPath())
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('performance:get-memory-snapshot', async (event): Promise<PerformanceMemorySnapshot> => {
|
|
||||||
const metrics = app.getAppMetrics()
|
|
||||||
const senderPid = event.sender.getOSProcessId()
|
|
||||||
const browserMetric = metrics.find((metric) => metric.type === 'Browser')
|
|
||||||
const rendererMetric = metrics.find((metric) => metric.type === 'Tab' && metric.pid === senderPid)
|
|
||||||
const rendererProcesses = getRendererProcessSnapshots(metrics)
|
|
||||||
|
|
||||||
return {
|
|
||||||
capturedAt: Date.now(),
|
|
||||||
appMb: sumWorkingSet(metrics, () => true),
|
|
||||||
mainMb: workingSetForMetric(browserMetric),
|
|
||||||
rendererMb: workingSetForMetric(rendererMetric),
|
|
||||||
rendererPrivateMb: null,
|
|
||||||
rendererTotalMb: sumWorkingSet(metrics, (metric) => metric.type === 'Tab'),
|
|
||||||
rendererProcessCount: rendererProcesses.length,
|
|
||||||
rendererProcesses,
|
|
||||||
gpuMb: sumWorkingSet(metrics, (metric) => metric.type === 'GPU'),
|
|
||||||
utilityMb: sumWorkingSet(metrics, (metric) => metric.type === 'Utility'),
|
|
||||||
jsHeapUsedMb: null,
|
|
||||||
jsHeapLimitMb: null,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('performance:append-memory-log', async (_event, record: PerformanceMemoryLogRecord) => {
|
|
||||||
queueMemoryLogWrite(record)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('audio:get-desktop-sources', async () => {
|
ipcMain.handle('audio:get-desktop-sources', async () => {
|
||||||
const sources = await desktopCapturer.getSources({ types: ['screen'] })
|
const sources = await desktopCapturer.getSources({ types: ['screen'] })
|
||||||
return sources.map((s) => ({ id: s.id, name: s.name }))
|
return sources.map((s) => ({ id: s.id, name: s.name }))
|
||||||
|
|||||||
@@ -28,14 +28,9 @@ import type {
|
|||||||
} from '../types/theme'
|
} from '../types/theme'
|
||||||
import type { ResizeDirection } from '../types/windowResize'
|
import type { ResizeDirection } from '../types/windowResize'
|
||||||
import type { VisualizerDSP } from '../renderer/audio/native/visualizer-dsp'
|
import type { VisualizerDSP } from '../renderer/audio/native/visualizer-dsp'
|
||||||
import type { PerformanceMemoryLogRecord, PerformanceMemorySnapshot } from '../types/performance'
|
|
||||||
|
|
||||||
type NativeAddonModule = VisualizerDSP & NativeCaptureAPI
|
type NativeAddonModule = VisualizerDSP & NativeCaptureAPI
|
||||||
|
|
||||||
function kilobytesToMegabytes(value: number | undefined): number {
|
|
||||||
return Math.round((((value ?? 0) / 1024) * 10)) / 10
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expose Electron API to renderer
|
// Expose Electron API to renderer
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
@@ -50,23 +45,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
repositionWindow: (position: 'top' | 'bottom') => ipcRenderer.send('window:reposition', position),
|
repositionWindow: (position: 'top' | 'bottom') => ipcRenderer.send('window:reposition', position),
|
||||||
toggleAlwaysOnTop: () => ipcRenderer.send('window:toggle-always-on-top'),
|
toggleAlwaysOnTop: () => ipcRenderer.send('window:toggle-always-on-top'),
|
||||||
isAlwaysOnTop: () => ipcRenderer.invoke('window:is-always-on-top'),
|
isAlwaysOnTop: () => ipcRenderer.invoke('window:is-always-on-top'),
|
||||||
getPerformanceMemoryLogPath: () => ipcRenderer.invoke('performance:get-memory-log-path') as Promise<string>,
|
|
||||||
revealPerformanceMemoryLog: () => ipcRenderer.invoke('performance:reveal-memory-log') as Promise<void>,
|
|
||||||
appendPerformanceMemoryLog: (record: PerformanceMemoryLogRecord) => {
|
|
||||||
return ipcRenderer.invoke('performance:append-memory-log', record) as Promise<void>
|
|
||||||
},
|
|
||||||
getPerformanceMemorySnapshot: async () => {
|
|
||||||
const snapshot = await ipcRenderer.invoke('performance:get-memory-snapshot') as PerformanceMemorySnapshot
|
|
||||||
try {
|
|
||||||
const rendererMemory = await process.getProcessMemoryInfo()
|
|
||||||
return {
|
|
||||||
...snapshot,
|
|
||||||
rendererPrivateMb: kilobytesToMegabytes(rendererMemory.private),
|
|
||||||
} satisfies PerformanceMemorySnapshot
|
|
||||||
} catch {
|
|
||||||
return snapshot
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getDesktopSources: () => ipcRenderer.invoke('audio:get-desktop-sources') as Promise<{ id: string; name: string }[]>,
|
getDesktopSources: () => ipcRenderer.invoke('audio:get-desktop-sources') as Promise<{ id: string; name: string }[]>,
|
||||||
getCaptureBackendSupport: async () => {
|
getCaptureBackendSupport: async () => {
|
||||||
const support = await ipcRenderer.invoke('capture:get-backend-support') as CaptureBackendSupport
|
const support = await ipcRenderer.invoke('capture:get-backend-support') as CaptureBackendSupport
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import WindowResizeOverlay from './components/WindowResizeOverlay'
|
|||||||
import { useSettingsStore } from './stores/settingsStore'
|
import { useSettingsStore } from './stores/settingsStore'
|
||||||
import { useAstraStore } from './stores/astraStore'
|
import { useAstraStore } from './stores/astraStore'
|
||||||
import { useAudioStore } from './stores/audioStore'
|
import { useAudioStore } from './stores/audioStore'
|
||||||
import { usePerformanceStore } from './stores/performanceStore'
|
|
||||||
import { useThemeStore } from './stores/themeStore'
|
import { useThemeStore } from './stores/themeStore'
|
||||||
import { SCOPE_KINDS } from '../types/scope'
|
import { SCOPE_KINDS } from '../types/scope'
|
||||||
|
|
||||||
@@ -28,8 +27,6 @@ export default function App(): JSX.Element {
|
|||||||
const guardProfileTransition = useSettingsStore((s) => s.guardProfileTransition)
|
const guardProfileTransition = useSettingsStore((s) => s.guardProfileTransition)
|
||||||
const importProfileFromPath = useSettingsStore((s) => s.importProfileFromPath)
|
const importProfileFromPath = useSettingsStore((s) => s.importProfileFromPath)
|
||||||
const updateMainWindowBounds = useSettingsStore((s) => s.updateMainWindowBounds)
|
const updateMainWindowBounds = useSettingsStore((s) => s.updateMainWindowBounds)
|
||||||
const startMemoryMonitoring = usePerformanceStore((s) => s.startMemoryMonitoring)
|
|
||||||
const stopMemoryMonitoring = usePerformanceStore((s) => s.stopMemoryMonitoring)
|
|
||||||
const initializeThemes = useThemeStore((s) => s.initializeThemes)
|
const initializeThemes = useThemeStore((s) => s.initializeThemes)
|
||||||
const applyExternalThemeSnapshot = useThemeStore((s) => s.applyExternalThemeSnapshot)
|
const applyExternalThemeSnapshot = useThemeStore((s) => s.applyExternalThemeSnapshot)
|
||||||
const initializeAstra = useAstraStore((s) => s.initialize)
|
const initializeAstra = useAstraStore((s) => s.initialize)
|
||||||
@@ -105,13 +102,6 @@ export default function App(): JSX.Element {
|
|||||||
updateMainWindowBounds,
|
updateMainWindowBounds,
|
||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
startMemoryMonitoring()
|
|
||||||
return () => {
|
|
||||||
stopMemoryMonitoring()
|
|
||||||
}
|
|
||||||
}, [startMemoryMonitoring, stopMemoryMonitoring])
|
|
||||||
|
|
||||||
const measuredSettingsHeight = settingsPanelHeight > 0 && bottomBarHeight > 0
|
const measuredSettingsHeight = settingsPanelHeight > 0 && bottomBarHeight > 0
|
||||||
? settingsPanelHeight + bottomBarHeight
|
? settingsPanelHeight + bottomBarHeight
|
||||||
: DEFAULT_SETTINGS_HEIGHT
|
: DEFAULT_SETTINGS_HEIGHT
|
||||||
|
|||||||
@@ -40,16 +40,12 @@ export default function BottomBar({ onClose, onHeightChange }: BottomBarProps):
|
|||||||
const rootRef = useRef<HTMLDivElement | null>(null)
|
const rootRef = useRef<HTMLDivElement | null>(null)
|
||||||
const [astraBaseUrlInput, setAstraBaseUrlInput] = useState('')
|
const [astraBaseUrlInput, setAstraBaseUrlInput] = useState('')
|
||||||
const [astraTokenInput, setAstraTokenInput] = useState('')
|
const [astraTokenInput, setAstraTokenInput] = useState('')
|
||||||
const [memoryLogPath, setMemoryLogPath] = useState('')
|
|
||||||
|
|
||||||
const hiddenScopes = useSettingsStore((s) => s.hiddenScopes)
|
const hiddenScopes = useSettingsStore((s) => s.hiddenScopes)
|
||||||
const scopeOrder = useSettingsStore((s) => s.scopeOrder)
|
const scopeOrder = useSettingsStore((s) => s.scopeOrder)
|
||||||
const toggleScope = useSettingsStore((s) => s.toggleScope)
|
const toggleScope = useSettingsStore((s) => s.toggleScope)
|
||||||
const frameTarget = usePerformanceStore((s) => s.frameTarget)
|
const frameTarget = usePerformanceStore((s) => s.frameTarget)
|
||||||
const dockedRenderFps = usePerformanceStore((s) => s.dockedRenderFps)
|
const dockedRenderFps = usePerformanceStore((s) => s.dockedRenderFps)
|
||||||
const memorySample = usePerformanceStore((s) => s.memorySample)
|
|
||||||
const rendererMemoryDeltaMb = usePerformanceStore((s) => s.rendererMemoryDeltaMb)
|
|
||||||
const appMemoryDeltaMb = usePerformanceStore((s) => s.appMemoryDeltaMb)
|
|
||||||
const setFrameTarget = usePerformanceStore((s) => s.setFrameTarget)
|
const setFrameTarget = usePerformanceStore((s) => s.setFrameTarget)
|
||||||
const themeId = useSettingsStore((s) => s.themeId)
|
const themeId = useSettingsStore((s) => s.themeId)
|
||||||
const setThemeId = useSettingsStore((s) => s.setThemeId)
|
const setThemeId = useSettingsStore((s) => s.setThemeId)
|
||||||
@@ -96,26 +92,6 @@ export default function BottomBar({ onClose, onHeightChange }: BottomBarProps):
|
|||||||
setAstraTokenInput(astraState.config.token)
|
setAstraTokenInput(astraState.config.token)
|
||||||
}, [astraState.config.baseUrl, astraState.config.token])
|
}, [astraState.config.baseUrl, astraState.config.token])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let disposed = false
|
|
||||||
void (async () => {
|
|
||||||
try {
|
|
||||||
const path = await window.electronAPI.getPerformanceMemoryLogPath()
|
|
||||||
if (!disposed) {
|
|
||||||
setMemoryLogPath(path)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
if (!disposed) {
|
|
||||||
setMemoryLogPath('')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
disposed = true
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!onHeightChange || !rootRef.current) return
|
if (!onHeightChange || !rootRef.current) return
|
||||||
|
|
||||||
@@ -179,9 +155,6 @@ export default function BottomBar({ onClose, onHeightChange }: BottomBarProps):
|
|||||||
const trimPercent = Math.min(100, Math.max(0, ((inputGainDb + 12) / 24) * 100))
|
const trimPercent = Math.min(100, Math.max(0, ((inputGainDb + 12) / 24) * 100))
|
||||||
const roundedDockedRenderFps = Math.max(0, Math.round(dockedRenderFps))
|
const roundedDockedRenderFps = Math.max(0, Math.round(dockedRenderFps))
|
||||||
const themeEntries = Object.entries(themes)
|
const themeEntries = Object.entries(themes)
|
||||||
const rendererMemoryMb = memorySample?.rendererPrivateMb ?? memorySample?.rendererMb ?? 0
|
|
||||||
const jsHeapUsedMb = memorySample?.jsHeapUsedMb ?? null
|
|
||||||
const jsHeapLimitMb = memorySample?.jsHeapLimitMb ?? null
|
|
||||||
|
|
||||||
const handleThemeChange = async (value: string): Promise<void> => {
|
const handleThemeChange = async (value: string): Promise<void> => {
|
||||||
await loadTheme(value)
|
await loadTheme(value)
|
||||||
@@ -461,40 +434,7 @@ export default function BottomBar({ onClose, onHeightChange }: BottomBarProps):
|
|||||||
<div className="settings-status-pill bottom-bar__fps-pill" title="Docked visualizer render FPS">
|
<div className="settings-status-pill bottom-bar__fps-pill" title="Docked visualizer render FPS">
|
||||||
<span>{roundedDockedRenderFps} FPS</span>
|
<span>{roundedDockedRenderFps} FPS</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="settings-chip"
|
|
||||||
title={memoryLogPath || 'Reveal the latest memory trace CSV'}
|
|
||||||
onClick={() => {
|
|
||||||
void window.electronAPI.revealPerformanceMemoryLog()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Log File
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{memorySample ? (
|
|
||||||
<div className="bottom-bar__inline bottom-bar__inline--memory">
|
|
||||||
<div className="settings-status-pill bottom-bar__memory-pill" title="Whole-app working set from Electron process metrics">
|
|
||||||
<span>App {memorySample.appMb.toFixed(1)} MB</span>
|
|
||||||
</div>
|
|
||||||
<div className="settings-status-pill bottom-bar__memory-pill" title="Current renderer private memory when available, otherwise working set">
|
|
||||||
<span>Renderer {rendererMemoryMb.toFixed(1)} MB</span>
|
|
||||||
</div>
|
|
||||||
<div className="settings-status-pill bottom-bar__memory-pill" title="Growth since the first sample in this session">
|
|
||||||
<span>ΔR {rendererMemoryDeltaMb >= 0 ? '+' : ''}{rendererMemoryDeltaMb.toFixed(1)} MB</span>
|
|
||||||
</div>
|
|
||||||
<div className="settings-status-pill bottom-bar__memory-pill" title="Whole-app growth since the first sample in this session">
|
|
||||||
<span>ΔA {appMemoryDeltaMb >= 0 ? '+' : ''}{appMemoryDeltaMb.toFixed(1)} MB</span>
|
|
||||||
</div>
|
|
||||||
{jsHeapUsedMb !== null ? (
|
|
||||||
<div className="settings-status-pill bottom-bar__memory-pill" title={jsHeapLimitMb !== null ? `Renderer JS heap (${jsHeapUsedMb.toFixed(1)} / ${jsHeapLimitMb.toFixed(1)} MB)` : 'Renderer JS heap'}>
|
|
||||||
<span>JS {jsHeapUsedMb.toFixed(1)} MB</span>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Vendored
-5
@@ -29,7 +29,6 @@ import type {
|
|||||||
ThemeLibrarySnapshot,
|
ThemeLibrarySnapshot,
|
||||||
} from '../types/theme'
|
} from '../types/theme'
|
||||||
import type { ResizeDirection } from '../types/windowResize'
|
import type { ResizeDirection } from '../types/windowResize'
|
||||||
import type { PerformanceMemoryLogRecord, PerformanceMemorySnapshot } from '../types/performance'
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -48,10 +47,6 @@ declare global {
|
|||||||
repositionWindow: (position: 'top' | 'bottom') => void
|
repositionWindow: (position: 'top' | 'bottom') => void
|
||||||
toggleAlwaysOnTop: () => void
|
toggleAlwaysOnTop: () => void
|
||||||
isAlwaysOnTop: () => Promise<boolean>
|
isAlwaysOnTop: () => Promise<boolean>
|
||||||
getPerformanceMemoryLogPath: () => Promise<string>
|
|
||||||
revealPerformanceMemoryLog: () => Promise<void>
|
|
||||||
appendPerformanceMemoryLog: (record: PerformanceMemoryLogRecord) => Promise<void>
|
|
||||||
getPerformanceMemorySnapshot: () => Promise<PerformanceMemorySnapshot>
|
|
||||||
getDesktopSources: () => Promise<{ id: string; name: string }[]>
|
getDesktopSources: () => Promise<{ id: string; name: string }[]>
|
||||||
getCaptureBackendSupport: () => Promise<CaptureBackendSupport>
|
getCaptureBackendSupport: () => Promise<CaptureBackendSupport>
|
||||||
getAstraConfig: () => Promise<AstraIntegrationConfig>
|
getAstraConfig: () => Promise<AstraIntegrationConfig>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import {
|
import {
|
||||||
isVisualizerFrameTarget,
|
isVisualizerFrameTarget,
|
||||||
type PerformanceMemoryLogRecord,
|
|
||||||
type PerformanceMemorySnapshot,
|
|
||||||
type VisualizerFrameTarget,
|
type VisualizerFrameTarget,
|
||||||
} from '../../types/performance'
|
} from '../../types/performance'
|
||||||
|
|
||||||
@@ -16,14 +14,8 @@ interface PersistedPerformanceState {
|
|||||||
interface PerformanceState {
|
interface PerformanceState {
|
||||||
frameTarget: VisualizerFrameTarget
|
frameTarget: VisualizerFrameTarget
|
||||||
dockedRenderFps: number
|
dockedRenderFps: number
|
||||||
memorySample: PerformanceMemorySnapshot | null
|
|
||||||
memoryHistory: PerformanceMemorySnapshot[]
|
|
||||||
rendererMemoryDeltaMb: number
|
|
||||||
appMemoryDeltaMb: number
|
|
||||||
setFrameTarget: (target: VisualizerFrameTarget) => void
|
setFrameTarget: (target: VisualizerFrameTarget) => void
|
||||||
setDockedRenderFps: (fps: number) => void
|
setDockedRenderFps: (fps: number) => void
|
||||||
startMemoryMonitoring: () => void
|
|
||||||
stopMemoryMonitoring: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StorageLike {
|
interface StorageLike {
|
||||||
@@ -77,180 +69,10 @@ function persistPerformancePreferences(target: VisualizerFrameTarget, storage =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const storedPreferences = loadPerformancePreferences()
|
const storedPreferences = loadPerformancePreferences()
|
||||||
const MEMORY_SAMPLE_INTERVAL_MS = 5000
|
|
||||||
const MEMORY_HISTORY_LIMIT = 720
|
|
||||||
|
|
||||||
interface ChromiumPerformanceMemory {
|
|
||||||
jsHeapSizeLimit: number
|
|
||||||
totalJSHeapSize: number
|
|
||||||
usedJSHeapSize: number
|
|
||||||
}
|
|
||||||
|
|
||||||
let memoryMonitorTimer: ReturnType<typeof setInterval> | null = null
|
|
||||||
let memoryMonitorRefCount = 0
|
|
||||||
let memoryMonitorInFlight = false
|
|
||||||
let contextStoreAccessorsPromise: Promise<{
|
|
||||||
getAudioState: () => {
|
|
||||||
isCapturing: boolean
|
|
||||||
captureStatus: 'idle' | 'connecting' | 'capturing' | 'error'
|
|
||||||
}
|
|
||||||
getSettingsState: () => {
|
|
||||||
scopeOrder: string[]
|
|
||||||
hiddenScopes: Set<string>
|
|
||||||
scopePopouts: Record<string, { poppedOut?: boolean }>
|
|
||||||
}
|
|
||||||
}> | null = null
|
|
||||||
|
|
||||||
function isMainWindowContext(): boolean {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search)
|
|
||||||
return params.get('window') !== 'scope-popout'
|
|
||||||
}
|
|
||||||
|
|
||||||
function bytesToMegabytes(value: number | undefined): number {
|
|
||||||
return Math.round((((value ?? 0) / (1024 * 1024)) * 10)) / 10
|
|
||||||
}
|
|
||||||
|
|
||||||
async function collectMemorySnapshot(): Promise<PerformanceMemorySnapshot | null> {
|
|
||||||
if (typeof window === 'undefined' || typeof window.electronAPI?.getPerformanceMemorySnapshot !== 'function') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const snapshot = await window.electronAPI.getPerformanceMemorySnapshot()
|
|
||||||
const performanceWithMemory = performance as Performance & { memory?: ChromiumPerformanceMemory }
|
|
||||||
const heap = performanceWithMemory.memory
|
|
||||||
return {
|
|
||||||
...snapshot,
|
|
||||||
jsHeapUsedMb: heap ? bytesToMegabytes(heap.usedJSHeapSize) : null,
|
|
||||||
jsHeapLimitMb: heap ? bytesToMegabytes(heap.jsHeapSizeLimit) : null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getContextStoreAccessors(): Promise<{
|
|
||||||
getAudioState: () => {
|
|
||||||
isCapturing: boolean
|
|
||||||
captureStatus: 'idle' | 'connecting' | 'capturing' | 'error'
|
|
||||||
}
|
|
||||||
getSettingsState: () => {
|
|
||||||
scopeOrder: string[]
|
|
||||||
hiddenScopes: Set<string>
|
|
||||||
scopePopouts: Record<string, { poppedOut?: boolean }>
|
|
||||||
}
|
|
||||||
} | null> {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!contextStoreAccessorsPromise) {
|
|
||||||
contextStoreAccessorsPromise = Promise.all([
|
|
||||||
import('./audioStore'),
|
|
||||||
import('./settingsStore'),
|
|
||||||
]).then(([audioStoreModule, settingsStoreModule]) => ({
|
|
||||||
getAudioState: () => audioStoreModule.useAudioStore.getState(),
|
|
||||||
getSettingsState: () => settingsStoreModule.useSettingsStore.getState(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return contextStoreAccessorsPromise
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sampleMemory(): Promise<void> {
|
|
||||||
if (memoryMonitorInFlight) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
memoryMonitorInFlight = true
|
|
||||||
try {
|
|
||||||
const snapshot = await collectMemorySnapshot()
|
|
||||||
if (!snapshot) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const contextStoreAccessors = await getContextStoreAccessors()
|
|
||||||
let logRecord: PerformanceMemoryLogRecord | null = null
|
|
||||||
usePerformanceStore.setState((state) => {
|
|
||||||
const baseline = state.memoryHistory[0] ?? snapshot
|
|
||||||
const nextHistory = [...state.memoryHistory, snapshot]
|
|
||||||
if (nextHistory.length > MEMORY_HISTORY_LIMIT) {
|
|
||||||
nextHistory.splice(0, nextHistory.length - MEMORY_HISTORY_LIMIT)
|
|
||||||
}
|
|
||||||
|
|
||||||
const baselineRendererMb = baseline.rendererPrivateMb ?? baseline.rendererMb
|
|
||||||
const currentRendererMb = snapshot.rendererPrivateMb ?? snapshot.rendererMb
|
|
||||||
const rendererDeltaMb = Math.round((currentRendererMb - baselineRendererMb) * 10) / 10
|
|
||||||
const rendererTotalDeltaMb = Math.round((snapshot.rendererTotalMb - baseline.rendererTotalMb) * 10) / 10
|
|
||||||
const appDeltaMb = Math.round((snapshot.appMb - baseline.appMb) * 10) / 10
|
|
||||||
const settingsState = contextStoreAccessors?.getSettingsState()
|
|
||||||
const audioState = contextStoreAccessors?.getAudioState()
|
|
||||||
const visibleScopes = settingsState
|
|
||||||
? settingsState.scopeOrder.filter((kind) => !settingsState.hiddenScopes.has(kind))
|
|
||||||
: []
|
|
||||||
const poppedOutScopes = settingsState
|
|
||||||
? settingsState.scopeOrder.filter((kind) => settingsState.scopePopouts[kind]?.poppedOut)
|
|
||||||
: []
|
|
||||||
|
|
||||||
logRecord = {
|
|
||||||
...snapshot,
|
|
||||||
elapsedSeconds: Math.round(((snapshot.capturedAt - baseline.capturedAt) / 100)) / 10,
|
|
||||||
rendererDeltaMb,
|
|
||||||
rendererTotalDeltaMb,
|
|
||||||
appDeltaMb,
|
|
||||||
frameTarget: state.frameTarget,
|
|
||||||
dockedRenderFps: state.dockedRenderFps,
|
|
||||||
isCapturing: audioState?.isCapturing ?? false,
|
|
||||||
captureStatus: audioState?.captureStatus ?? 'idle',
|
|
||||||
visibleScopes,
|
|
||||||
poppedOutScopes,
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
memorySample: snapshot,
|
|
||||||
memoryHistory: nextHistory,
|
|
||||||
rendererMemoryDeltaMb: rendererDeltaMb,
|
|
||||||
appMemoryDeltaMb: appDeltaMb,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (logRecord && typeof window !== 'undefined' && typeof window.electronAPI?.appendPerformanceMemoryLog === 'function') {
|
|
||||||
void window.electronAPI.appendPerformanceMemoryLog(logRecord)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
memoryMonitorInFlight = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureMemoryMonitor(): void {
|
|
||||||
if (memoryMonitorTimer || !isMainWindowContext()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
void sampleMemory()
|
|
||||||
memoryMonitorTimer = setInterval(() => {
|
|
||||||
void sampleMemory()
|
|
||||||
}, MEMORY_SAMPLE_INTERVAL_MS)
|
|
||||||
}
|
|
||||||
|
|
||||||
function releaseMemoryMonitor(): void {
|
|
||||||
if (memoryMonitorRefCount > 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memoryMonitorTimer) {
|
|
||||||
clearInterval(memoryMonitorTimer)
|
|
||||||
memoryMonitorTimer = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePerformanceStore = create<PerformanceState>((set) => ({
|
export const usePerformanceStore = create<PerformanceState>((set) => ({
|
||||||
frameTarget: storedPreferences.frameTarget,
|
frameTarget: storedPreferences.frameTarget,
|
||||||
dockedRenderFps: 0,
|
dockedRenderFps: 0,
|
||||||
memorySample: null,
|
|
||||||
memoryHistory: [],
|
|
||||||
rendererMemoryDeltaMb: 0,
|
|
||||||
appMemoryDeltaMb: 0,
|
|
||||||
|
|
||||||
setFrameTarget: (target: VisualizerFrameTarget) => {
|
setFrameTarget: (target: VisualizerFrameTarget) => {
|
||||||
persistPerformancePreferences(target)
|
persistPerformancePreferences(target)
|
||||||
@@ -268,16 +90,6 @@ export const usePerformanceStore = create<PerformanceState>((set) => ({
|
|||||||
return { ...state, dockedRenderFps: nextFps }
|
return { ...state, dockedRenderFps: nextFps }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
startMemoryMonitoring: () => {
|
|
||||||
memoryMonitorRefCount += 1
|
|
||||||
ensureMemoryMonitor()
|
|
||||||
},
|
|
||||||
|
|
||||||
stopMemoryMonitoring: () => {
|
|
||||||
memoryMonitorRefCount = Math.max(0, memoryMonitorRefCount - 1)
|
|
||||||
releaseMemoryMonitor()
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
let syncChannel: BroadcastChannel | null = null
|
let syncChannel: BroadcastChannel | null = null
|
||||||
|
|||||||
@@ -1489,13 +1489,6 @@ select {
|
|||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-bar__memory-pill {
|
|
||||||
justify-content: center;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-bar__error-text {
|
.bottom-bar__error-text {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
|
|||||||
@@ -2,40 +2,6 @@ export const VISUALIZER_FRAME_TARGETS = [10, 30, 60, 120, 144, 'display-sync'] a
|
|||||||
|
|
||||||
export type VisualizerFrameTarget = typeof VISUALIZER_FRAME_TARGETS[number]
|
export type VisualizerFrameTarget = typeof VISUALIZER_FRAME_TARGETS[number]
|
||||||
|
|
||||||
export interface PerformanceRendererProcessSnapshot {
|
|
||||||
pid: number
|
|
||||||
label: string
|
|
||||||
workingSetMb: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PerformanceMemorySnapshot {
|
|
||||||
capturedAt: number
|
|
||||||
appMb: number
|
|
||||||
mainMb: number
|
|
||||||
rendererMb: number
|
|
||||||
rendererPrivateMb: number | null
|
|
||||||
rendererTotalMb: number
|
|
||||||
rendererProcessCount: number
|
|
||||||
rendererProcesses: PerformanceRendererProcessSnapshot[]
|
|
||||||
gpuMb: number
|
|
||||||
utilityMb: number
|
|
||||||
jsHeapUsedMb: number | null
|
|
||||||
jsHeapLimitMb: number | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PerformanceMemoryLogRecord extends PerformanceMemorySnapshot {
|
|
||||||
elapsedSeconds: number
|
|
||||||
rendererDeltaMb: number
|
|
||||||
rendererTotalDeltaMb: number
|
|
||||||
appDeltaMb: number
|
|
||||||
frameTarget: VisualizerFrameTarget
|
|
||||||
dockedRenderFps: number
|
|
||||||
isCapturing: boolean
|
|
||||||
captureStatus: 'idle' | 'connecting' | 'capturing' | 'error'
|
|
||||||
visibleScopes: string[]
|
|
||||||
poppedOutScopes: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isVisualizerFrameTarget(value: unknown): value is VisualizerFrameTarget {
|
export function isVisualizerFrameTarget(value: unknown): value is VisualizerFrameTarget {
|
||||||
return VISUALIZER_FRAME_TARGETS.includes(value as VisualizerFrameTarget)
|
return VISUALIZER_FRAME_TARGETS.includes(value as VisualizerFrameTarget)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user