From f0b0e55937b049841b5e8868b36bac3597ab03e2 Mon Sep 17 00:00:00 2001 From: Kuingsmile <96409857+Kuingsmile@users.noreply.github.com> Date: Wed, 13 Aug 2025 15:11:55 +0800 Subject: [PATCH] :zap: Perf(custom): add memory monitor and optimiz memory usage --- src/main/apis/app/window/windowList.ts | 18 ++-- src/main/apis/core/bus/index.ts | 36 ++++++- src/main/lifeCycle/index.ts | 6 ++ src/main/utils/clipboardPoll.ts | 2 +- src/main/utils/common.ts | 1 - src/main/utils/performanceOptimizer.ts | 31 ++++++ src/main/utils/windowHelper.ts | 129 +++++++++++++------------ 7 files changed, 144 insertions(+), 79 deletions(-) create mode 100644 src/main/utils/performanceOptimizer.ts diff --git a/src/main/apis/app/window/windowList.ts b/src/main/apis/app/window/windowList.ts index daca0fc8..2caa402a 100644 --- a/src/main/apis/app/window/windowList.ts +++ b/src/main/apis/app/window/windowList.ts @@ -62,8 +62,8 @@ const trayWindowOptions = { preload: preloadPath, nodeIntegration: false, contextIsolation: true, - nodeIntegrationInWorker: true, - backgroundThrottling: false, + nodeIntegrationInWorker: false, + backgroundThrottling: true, webSecurity: false } } @@ -83,11 +83,11 @@ const settingWindowOptions = { webPreferences: { sandbox: false, webviewTag: true, - backgroundThrottling: false, + backgroundThrottling: true, preload: preloadPath, nodeIntegration: false, contextIsolation: true, - nodeIntegrationInWorker: true, + nodeIntegrationInWorker: false, webSecurity: false } } as IBrowserWindowOptions @@ -112,8 +112,8 @@ const miniWindowOptions = { preload: preloadPath, nodeIntegration: false, contextIsolation: true, - backgroundThrottling: false, - nodeIntegrationInWorker: true + backgroundThrottling: true, + nodeIntegrationInWorker: false } } as IBrowserWindowOptions @@ -133,7 +133,7 @@ const renameWindowOptions = { preload: preloadPath, nodeIntegration: false, contextIsolation: true, - nodeIntegrationInWorker: true, + nodeIntegrationInWorker: false, backgroundThrottling: false } } as IBrowserWindowOptions @@ -158,11 +158,11 @@ const toolboxWindowOptions = { icon: logo, webPreferences: { sandbox: false, - backgroundThrottling: false, + backgroundThrottling: true, preload: preloadPath, nodeIntegration: false, contextIsolation: true, - nodeIntegrationInWorker: true, + nodeIntegrationInWorker: false, webSecurity: false } } as IBrowserWindowOptions diff --git a/src/main/apis/core/bus/index.ts b/src/main/apis/core/bus/index.ts index 9be75001..60995993 100644 --- a/src/main/apis/core/bus/index.ts +++ b/src/main/apis/core/bus/index.ts @@ -1,5 +1,31 @@ -import { EventEmitter } from 'node:events' - -const bus = new EventEmitter() - -export default bus +import { EventEmitter } from 'node:events' + +class OptimizedBus extends EventEmitter { + constructor () { + super() + this.setMaxListeners(50) + } + + once (event: string | symbol, listener: (...args: any[]) => void): this { + const wrappedListener = (...args: any[]) => { + try { + listener(...args) + } finally { + this.removeListener(event, wrappedListener) + } + } + return super.once(event, wrappedListener) + } + + cleanupListeners () { + const events = this.eventNames() + events.forEach(event => { + const listenerCount = this.listenerCount(event) + console.log(` listener count (${listenerCount}) for event: ${String(event)}`) + }) + } +} + +const bus = new OptimizedBus() + +export default bus diff --git a/src/main/lifeCycle/index.ts b/src/main/lifeCycle/index.ts index 649c368b..a6787a3c 100644 --- a/src/main/lifeCycle/index.ts +++ b/src/main/lifeCycle/index.ts @@ -34,6 +34,7 @@ import { II18nLanguage, IRemoteNoticeTriggerHook, ISartMode, IWindowList } from import { getUploadFiles } from '~/utils/handleArgv' import { initI18n } from '~/utils/handleI18n' import { notificationList } from '~/utils/notification' +import { MemoryMonitor } from '~/utils/performanceOptimizer' import { CLIPBOARD_IMAGE_FOLDER } from '~/utils/static' import updateChecker from '~/utils/updateChecker' @@ -164,6 +165,10 @@ class LifeCycle { initI18n() rpcServer.start() busEventList.listen() + + if (process.env.NODE_ENV === 'development') { + MemoryMonitor.start(30000) + } } #onReady () { @@ -315,6 +320,7 @@ class LifeCycle { server.shutdown() webServer.stop() stopFileServer() + MemoryMonitor.stop() }) // Exit cleanly on request from parent process in development mode. if (isDevelopment) { diff --git a/src/main/utils/clipboardPoll.ts b/src/main/utils/clipboardPoll.ts index 3af3685a..684fc83d 100644 --- a/src/main/utils/clipboardPoll.ts +++ b/src/main/utils/clipboardPoll.ts @@ -15,7 +15,7 @@ class ClipboardWatcher extends EventEmitter { this.timer = null } - startListening (watchDelay = 500) { + startListening (watchDelay = 1000) { this.stopListening(false) this.timer = setInterval(() => { diff --git a/src/main/utils/common.ts b/src/main/utils/common.ts index af5bb2d3..e185ddcc 100644 --- a/src/main/utils/common.ts +++ b/src/main/utils/common.ts @@ -70,7 +70,6 @@ export const showNotification = ( const notification = new Notification({ title: options.title, body: options.body - // icon: options.icon || undefined }) const handleClick = () => { if (options.clickToCopy) { diff --git a/src/main/utils/performanceOptimizer.ts b/src/main/utils/performanceOptimizer.ts new file mode 100644 index 00000000..24c66d4c --- /dev/null +++ b/src/main/utils/performanceOptimizer.ts @@ -0,0 +1,31 @@ +export class MemoryMonitor { + // eslint-disable-next-line no-undef + private static interval: NodeJS.Timeout | null = null + + static start (intervalMs: number = 30000) { + if (this.interval) return + + this.interval = setInterval(() => { + const memUsage = process.memoryUsage() + const mbUsage = { + rss: Math.round(memUsage.rss / 1024 / 1024), + heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024), + heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024), + external: Math.round(memUsage.external / 1024 / 1024) + } + console.log(`[Memory] RSS: ${mbUsage.rss}MB, Heap: ${mbUsage.heapUsed}/${mbUsage.heapTotal}MB, External: ${mbUsage.external}MB`) + + if (mbUsage.heapUsed / mbUsage.heapTotal > 0.8 && global.gc) { + console.log('[Memory] Triggering garbage collection') + global.gc() + } + }, intervalMs) + } + + static stop () { + if (this.interval) { + clearInterval(this.interval) + this.interval = null + } + } +} diff --git a/src/main/utils/windowHelper.ts b/src/main/utils/windowHelper.ts index d7156b31..ffc29e4b 100644 --- a/src/main/utils/windowHelper.ts +++ b/src/main/utils/windowHelper.ts @@ -1,63 +1,66 @@ -import db from '@core/datastore' -import windowManager from 'apis/app/window/windowManager' -import { screen } from 'electron' - -import { configPaths } from '~/utils/configPaths' -import { IWindowList } from '~/utils/enum' - -export function openMiniWindow (hideSettingWindow: boolean = true) { - const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! - miniWindow.removeAllListeners() - if (db.get(configPaths.settings.miniWindowOntop)) { - miniWindow.setAlwaysOnTop(true) - } - const { width, height } = screen.getPrimaryDisplay().workAreaSize - const lastPosition = db.get(configPaths.settings.miniWindowPosition) - const setPositionFunc = () => { - const position = miniWindow.getPosition() - db.set(configPaths.settings.miniWindowPosition, position) - } - if (lastPosition) { - if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) { - miniWindow.setPosition(width - 100, height - 100) - db.set(configPaths.settings.miniWindowPosition, [width - 100, height - 100]) - } else if (lastPosition[0] + miniWindow.getSize()[0] > width || lastPosition[1] + miniWindow.getSize()[1] > height) { - miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]) - db.set(configPaths.settings.miniWindowPosition, [width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]]) - } else { - miniWindow.setPosition(lastPosition[0], lastPosition[1]) - } - } else { - miniWindow.setPosition(width - 100, height - 100) - } - - miniWindow.on('close', setPositionFunc) - miniWindow.on('move', setPositionFunc) - miniWindow.show() - miniWindow.focus() - if (hideSettingWindow) { - const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! - settingWindow.hide() - } else { - const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false - if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) { - windowManager.get(IWindowList.SETTING_WINDOW)!.hide() - } - } -} - -export const openMainWindow = () => { - const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) - const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false - settingWindow!.show() - settingWindow!.focus() - if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) { - windowManager.get(IWindowList.MINI_WINDOW)!.hide() - } -} - -export const hideMiniWindow = () => { - if (windowManager.has(IWindowList.MINI_WINDOW)) { - windowManager.get(IWindowList.MINI_WINDOW)!.hide() - } -} +import db from '@core/datastore' +import windowManager from 'apis/app/window/windowManager' +import { screen } from 'electron' + +import { configPaths } from '~/utils/configPaths' +import { IWindowList } from '~/utils/enum' + +export function openMiniWindow (hideSettingWindow: boolean = true) { + const miniWindow = windowManager.get(IWindowList.MINI_WINDOW)! + + miniWindow.removeAllListeners('close') + miniWindow.removeAllListeners('move') + + if (db.get(configPaths.settings.miniWindowOntop)) { + miniWindow.setAlwaysOnTop(true) + } + const { width, height } = screen.getPrimaryDisplay().workAreaSize + const lastPosition = db.get(configPaths.settings.miniWindowPosition) + const setPositionFunc = () => { + const position = miniWindow.getPosition() + db.set(configPaths.settings.miniWindowPosition, position) + } + if (lastPosition) { + if (lastPosition[0] < 0 || lastPosition[0] > width || lastPosition[1] < 0 || lastPosition[1] > height) { + miniWindow.setPosition(width - 100, height - 100) + db.set(configPaths.settings.miniWindowPosition, [width - 100, height - 100]) + } else if (lastPosition[0] + miniWindow.getSize()[0] > width || lastPosition[1] + miniWindow.getSize()[1] > height) { + miniWindow.setPosition(width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]) + db.set(configPaths.settings.miniWindowPosition, [width - miniWindow.getSize()[0], height - miniWindow.getSize()[1]]) + } else { + miniWindow.setPosition(lastPosition[0], lastPosition[1]) + } + } else { + miniWindow.setPosition(width - 100, height - 100) + } + + miniWindow.on('close', setPositionFunc) + miniWindow.on('move', setPositionFunc) + miniWindow.show() + miniWindow.focus() + if (hideSettingWindow) { + const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW)! + settingWindow.hide() + } else { + const autoCloseMainWindow = db.get(configPaths.settings.autoCloseMainWindow) || false + if (windowManager.has(IWindowList.SETTING_WINDOW) && autoCloseMainWindow) { + windowManager.get(IWindowList.SETTING_WINDOW)!.hide() + } + } +} + +export const openMainWindow = () => { + const settingWindow = windowManager.get(IWindowList.SETTING_WINDOW) + const autoCloseMiniWindow = db.get(configPaths.settings.autoCloseMiniWindow) || false + settingWindow!.show() + settingWindow!.focus() + if (windowManager.has(IWindowList.MINI_WINDOW) && autoCloseMiniWindow) { + windowManager.get(IWindowList.MINI_WINDOW)!.hide() + } +} + +export const hideMiniWindow = () => { + if (windowManager.has(IWindowList.MINI_WINDOW)) { + windowManager.get(IWindowList.MINI_WINDOW)!.hide() + } +}