diff --git a/package-lock.json b/package-lock.json index e885cebdb..1e0e5ad92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "electron-updater": "^4.6.5", "history": "^5.3.0", "node-fetch": "^3.2.3", + "pidusage": "^3.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-icons": "^4.3.1", @@ -12815,6 +12816,36 @@ "node": ">=0.10" } }, + "node_modules/pidusage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.0.tgz", + "integrity": "sha512-8VJLToXhj+RYZGNVw8oxc7dS54iCQXUJ+MDFHezQ/fwF5B8W4OWodAMboc1wb08S/4LiHwAmkT4ohf/d3YPPsw==", + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pidusage/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -27094,6 +27125,21 @@ "integrity": "sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==", "dev": true }, + "pidusage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.0.tgz", + "integrity": "sha512-8VJLToXhj+RYZGNVw8oxc7dS54iCQXUJ+MDFHezQ/fwF5B8W4OWodAMboc1wb08S/4LiHwAmkT4ohf/d3YPPsw==", + "requires": { + "safe-buffer": "^5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", diff --git a/package.json b/package.json index 8cd8ebafb..830fca97d 100644 --- a/package.json +++ b/package.json @@ -256,6 +256,7 @@ "electron-updater": "^4.6.5", "history": "^5.3.0", "node-fetch": "^3.2.3", + "pidusage": "^3.0.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-icons": "^4.3.1", diff --git a/src/main/geth.ts b/src/main/geth.ts index e75bffbe2..3a1e121d6 100644 --- a/src/main/geth.ts +++ b/src/main/geth.ts @@ -268,3 +268,7 @@ export const initialize = async () => { } registerChildProcess(gethProcess); }; + +export const getPid = () => { + return gethProcess?.pid; +}; diff --git a/src/main/ipc.ts b/src/main/ipc.ts index f65579e98..a1df96fd3 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -10,6 +10,7 @@ import { import { getStatus, startGeth, stopGeth } from './geth'; import { store } from './store'; import logger from './logger'; +import { getMainProcessUsage, getNodeUsage } from './monitor'; // eslint-disable-next-line import/prefer-default-export export const initialize = () => { @@ -32,4 +33,6 @@ export const initialize = () => { }); ipcMain.handle('getGethLogs', getGethLogs); ipcMain.handle('getGethErrorLogs', getGethErrorLogs); + ipcMain.handle('getNodeUsage', getNodeUsage); + ipcMain.handle('getMainProcessUsage', getMainProcessUsage); }; diff --git a/src/main/monitor.ts b/src/main/monitor.ts new file mode 100644 index 000000000..cc894419b --- /dev/null +++ b/src/main/monitor.ts @@ -0,0 +1,23 @@ +import { getPid } from './geth'; + +const pidusage = require('pidusage'); + +export const getProcessUsageByPid = async (pid: number) => { + const stats = await pidusage(pid); + return stats; +}; + +export const getNodeUsage = async () => { + const nodePid = await getPid(); + if (typeof nodePid !== 'number') { + return undefined; + } + const gethUsage = await getProcessUsageByPid(nodePid); + return gethUsage; +}; + +export const getMainProcessUsage = async () => { + const memory = await process.getProcessMemoryInfo(); + const cpu = await process.getCPUUsage(); + return { memory, cpu }; +}; diff --git a/src/main/preload.ts b/src/main/preload.ts index 9614f7681..4a6b2775c 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -25,4 +25,11 @@ contextBridge.exposeInMainWorld('electron', { ipcRenderer.invoke('setStoreValue', key, value), getGethLogs: () => ipcRenderer.invoke('getGethLogs'), getGethErrorLogs: () => ipcRenderer.invoke('getGethErrorLogs'), + getMainProcessUsage: () => ipcRenderer.invoke('getMainProcessUsage'), + getRendererProcessUsage: async () => { + const memory = await process.getProcessMemoryInfo(); + const cpu = await process.getCPUUsage(); + return { memory, cpu }; + }, + getNodeUsage: () => ipcRenderer.invoke('getNodeUsage'), }); diff --git a/src/renderer/Footer/Footer.tsx b/src/renderer/Footer/Footer.tsx index 1d54c9533..d95d2e078 100644 --- a/src/renderer/Footer/Footer.tsx +++ b/src/renderer/Footer/Footer.tsx @@ -15,6 +15,8 @@ const Footer = () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const [sGethErrorLogs, setGethErrorLogs] = useState(); const [sGethDeleteResult, setGethDeleteResult] = useState(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [sMonitoringData, setMonitoringData] = useState(); const getGethLogs = async () => { const gethLogs = await electron.getGethLogs(); @@ -25,15 +27,80 @@ const Footer = () => { setGethErrorLogs(gethLogs); }; + const getMonitoringData = async () => { + const rendererProcessUsage = await electron.getRendererProcessUsage(); + const mainProcessUsage = await electron.getMainProcessUsage(); + const nodeUsage = await electron.getNodeUsage(); + + // --- Memory --- + let totalMemoryUsed = 0; + if (nodeUsage?.memory) { + nodeUsage.memory *= 1e-9; + totalMemoryUsed += nodeUsage.memory; + } + if (rendererProcessUsage?.memory?.residentSet) { + rendererProcessUsage.memory.residentSet *= 1e-9; + totalMemoryUsed += rendererProcessUsage.memory.residentSet; + } + if (mainProcessUsage?.memory?.residentSet) { + mainProcessUsage.memory.residentSet *= 1e-9; + totalMemoryUsed += mainProcessUsage.memory.residentSet; + } + let uiMemoryUsage; + // https://thewebdev.info/2022/01/08/how-to-programmatically-get-memory-usage-in-chrome-with-javascript/ + if (typeof window?.performance?.memory?.usedJSHeapSize === 'number') { + uiMemoryUsage = window.performance.memory.usedJSHeapSize * 1e-9; + totalMemoryUsed += uiMemoryUsage; + } + if (uiMemoryUsage) { + uiMemoryUsage = `${uiMemoryUsage.toFixed(3)}GB`; + } + let totalMemoryUsedStr; + if (totalMemoryUsed) { + totalMemoryUsedStr = `Total memory used: ${totalMemoryUsed.toFixed(3)}GB`; + } + + // --- CPU --- + let totalCpuUsed = 0; + if (nodeUsage?.cpu) { + totalCpuUsed += nodeUsage.cpu; + } + if (rendererProcessUsage?.cpu?.percentCPUUsage) { + totalCpuUsed += rendererProcessUsage.cpu.percentCPUUsage; + } + if (mainProcessUsage?.cpu?.percentCPUUsage) { + totalCpuUsed += rendererProcessUsage.cpu.percentCPUUsage; + } + let totalCpuUsedStr; + if (totalCpuUsed) { + totalCpuUsedStr = `Total cpu used: ${totalCpuUsed.toFixed( + 1 + )}% of virtual CPU cores`; + } + setMonitoringData({ + total: { + memory: totalMemoryUsedStr, + cpu: totalCpuUsedStr, + }, + nodeProcess: nodeUsage, + uiMemoryUsage, + rendererProcess: rendererProcessUsage, + mainProcess: mainProcessUsage, + }); + }; + useEffect(() => { getGethLogs(); getGethErrorLogs(); + getMonitoringData(); }, []); useEffect(() => { if (sSelectedMenuDrawer === 'debugging') { getGethLogs(); getGethErrorLogs(); + } else if (sSelectedMenuDrawer === 'monitoring') { + getMonitoringData(); } }, [sSelectedMenuDrawer]); @@ -102,19 +169,21 @@ const Footer = () => { >

Geth Info Logs

Geth Error Logs

{ isSelected={sSelectedMenuDrawer === 'monitoring'} onClickCloseButton={() => setSelectedMenuDrawer(undefined)} > -
Monitoring
+
+

Memory

+ {sMonitoringData?.total?.memory} +

CPU

+ {sMonitoringData?.total?.cpu} +

All available metrics

+ +
); diff --git a/src/renderer/Header.tsx b/src/renderer/Header.tsx index 07b85f27a..e2a595124 100644 --- a/src/renderer/Header.tsx +++ b/src/renderer/Header.tsx @@ -27,7 +27,7 @@ const Header = () => { }); useEffect(() => { - console.log('qExeuctionIsSyncing: ', qExeuctionIsSyncing); + // console.log('qExeuctionIsSyncing: ', qExeuctionIsSyncing); if (qExeuctionIsSyncing.isError) { setSyncPercent(''); setIsSyncing(false); @@ -45,7 +45,7 @@ const Header = () => { }, [qExeuctionIsSyncing]); useEffect(() => { - console.log('qExecutionPeers: ', qExecutionPeers); + // console.log('qExecutionPeers: ', qExecutionPeers); if (qExecutionPeers.isError) { setPeers(undefined); return; diff --git a/src/renderer/__mocks__/custom-preload-mock.ts b/src/renderer/__mocks__/custom-preload-mock.ts index b8a044c6b..b5faf769d 100644 --- a/src/renderer/__mocks__/custom-preload-mock.ts +++ b/src/renderer/__mocks__/custom-preload-mock.ts @@ -29,4 +29,19 @@ export const getGethErrorLogs = (): any => { return [{ level: 'info', message: 'one log' }]; }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const getRendererProcessUsage = (): any => { + return { cpu: 200000, memory: 'high' }; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const getMainProcessUsage = (): any => { + return { cpu: 200000, memory: 'high' }; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const getNodeUsage = (): any => { + return { cpu: 200000, memory: 'high' }; +}; + export const SENTRY_DSN = 'fake_sentry_dsn'; diff --git a/src/renderer/preload.d.ts b/src/renderer/preload.d.ts index 5947dd2c5..a28094897 100644 --- a/src/renderer/preload.d.ts +++ b/src/renderer/preload.d.ts @@ -1,11 +1,24 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// Since we are using Chrome only in Electron and this is not a web standard yet, +// we extend window.performance to include Chrome's memory stats +interface Performance extends Performance { + memory?: { + /** The maximum size of the heap, in bytes, that is available to the context. */ + jsHeapSizeLimit: number; + /** The total allocated heap size, in bytes. */ + totalJSHeapSize: number; + /** The currently active segment of JS heap, in bytes. */ + usedJSHeapSize: number; + }; +} + declare global { interface Window { electron: { SENTRY_DSN: string; ipcRenderer: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any on(channel: string, func: (...args: any[]) => void): void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any once(channel: string, func: (...args: any[]) => void): void; }; getGethStatus(): string; @@ -14,17 +27,17 @@ declare global { deleteGethDisk(): boolean; getGethDiskUsed(): number; getSystemFreeDiskSpace(): number; - // eslint-disable-next-line @typescript-eslint/no-explicit-any getDebugInfo(): any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any getStoreValue(key: string): any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any setStoreValue(key: string, value: any): void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any getGethLogs(): any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any getGethErrorLogs(): any; + getRendererProcessUsage(): any; + getMainProcessUsage(): any; + getNodeUsage(): any; }; + + performance: Performance; } }