Merge pull request #5635 from naman-bruno/feat/performance-monitor

add: system monitor
This commit is contained in:
naman-bruno
2025-09-29 19:37:56 +05:30
committed by GitHub
parent fa0f3b3b7b
commit aacb1e0b8e
10 changed files with 364 additions and 2 deletions

13
package-lock.json generated
View File

@@ -20235,6 +20235,18 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pidusage": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pidusage/-/pidusage-4.0.1.tgz",
"integrity": "sha512-yCH2dtLHfEBnzlHUJymR/Z1nN2ePG3m392Mv8TFlTP1B0xkpMQNHAnfkY0n2tAi6ceKO6YWhxYfZ96V4vVkh/g==",
"license": "MIT",
"dependencies": {
"safe-buffer": "^5.2.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/pify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
@@ -30164,6 +30176,7 @@
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"nanoid": "3.3.8",
"pidusage": "^4.0.1",
"qs": "^6.11.0",
"socks-proxy-agent": "^8.0.2",
"tough-cookie": "^6.0.0",

View File

@@ -12,7 +12,8 @@ import {
IconCode,
IconChevronDown,
IconTerminal2,
IconNetwork
IconNetwork,
IconDashboard,
} from '@tabler/icons';
import {
closeConsole,
@@ -24,10 +25,12 @@ import {
updateNetworkFilter,
toggleAllNetworkFilters
} from 'providers/ReduxStore/slices/logs';
import NetworkTab from './NetworkTab';
import RequestDetailsPanel from './RequestDetailsPanel';
// import DebugTab from './DebugTab';
import ErrorDetailsPanel from './ErrorDetailsPanel';
import Performance from '../Performance';
import StyledWrapper from './StyledWrapper';
const LogIcon = ({ type }) => {
@@ -384,6 +387,8 @@ const Console = () => {
);
case 'network':
return <NetworkTab />;
case 'performance':
return <Performance />;
// case 'debug':
// return <DebugTab />;
default:
@@ -484,6 +489,14 @@ const Console = () => {
<span>Network</span>
</button>
<button
className={`console-tab ${activeTab === 'performance' ? 'active' : ''}`}
onClick={() => handleTabChange('performance')}
>
<IconDashboard size={16} strokeWidth={1.5} />
<span>Performance</span>
</button>
{/* <button
className={`console-tab ${activeTab === 'debug' ? 'active' : ''}`}
onClick={() => handleTabChange('debug')}

View File

@@ -0,0 +1,120 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.tab-content {
height: 100%;
display: flex;
flex-direction: column;
background: ${props => props.theme.console.bg};
}
.tab-content-area {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.overview-container {
max-width: 1200px;
margin: 0 auto;
}
.overview-section {
margin-bottom: 32px;
&:last-child {
margin-bottom: 0;
}
}
.section-header {
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid ${props => props.theme.console.border};
h3 {
margin: 0 0 4px 0;
font-size: 16px;
font-weight: 600;
color: ${props => props.theme.console.titleColor};
}
p {
margin: 0;
font-size: 13px;
color: ${props => props.theme.console.textMuted};
}
}
.system-resources {
margin-bottom: 16px;
h2 {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 600;
color: ${props => props.theme.console.titleColor};
}
}
.resource-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 8px;
margin-bottom: 16px;
}
.resource-card {
background: ${props => props.theme.console.headerBg};
border: 1px solid ${props => props.theme.console.border};
border-radius: 4px;
padding: 8px;
}
.resource-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
color: ${props => props.theme.console.titleColor};
}
.resource-title {
font-size: 12px;
font-weight: 500;
}
.resource-value {
font-size: 18px;
font-weight: 600;
color: ${props => props.theme.console.titleColor};
margin-bottom: 2px;
}
.resource-subtitle {
font-size: 11px;
color: ${props => props.theme.console.buttonColor};
}
.resource-trend {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
margin-top: 8px;
&.up {
color: #10b981;
}
&.down {
color: #e81123;
}
&.stable {
color: ${props => props.theme.console.buttonColor};
}
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,100 @@
import React from 'react';
import { useSelector } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import {
IconCpu,
IconDatabase,
IconClock,
IconServer,
IconChartLine,
} from '@tabler/icons';
const Performance = () => {
const { systemResources } = useSelector(state => state.performance);
const formatBytes = bytes => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const formatUptime = seconds => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) return `${hours}h ${minutes}m ${secs}s`;
if (minutes > 0) return `${minutes}m ${secs}s`;
return `${secs}s`;
};
const SystemResourceCard = ({ icon: Icon, title, value, subtitle, color = 'default', trend }) => (
<div className={`resource-card ${color}`}>
<div className="resource-header">
<Icon size={20} strokeWidth={1.5} />
<span className="resource-title">{title}</span>
</div>
<div className="resource-value">{value}</div>
{subtitle && <div className="resource-subtitle">{subtitle}</div>}
{trend && (
<div className={`resource-trend ${trend > 0 ? 'up' : trend < 0 ? 'down' : 'stable'}`}>
<IconChartLine size={12} strokeWidth={1.5} />
<span>
{trend > 0 ? '+' : ''}
{trend.toFixed(1)}
%
</span>
</div>
)}
</div>
);
return (
<StyledWrapper>
<div className="tab-content">
<div className="tab-content-area">
<div className="system-resources">
<h2>System Resources</h2>
<div className="resource-cards">
<SystemResourceCard
icon={IconCpu}
title="CPU Usage"
value={`${systemResources.cpu.toFixed(1)}%`}
subtitle="Current process"
color={systemResources.cpu > 80 ? 'danger' : systemResources.cpu > 60 ? 'warning' : 'success'}
/>
<SystemResourceCard
icon={IconDatabase}
title="Memory Usage"
value={formatBytes(systemResources.memory)}
subtitle="Current process"
color={systemResources.memory > 500 * 1024 * 1024 ? 'danger' : 'default'}
/>
<SystemResourceCard
icon={IconClock}
title="Uptime"
value={formatUptime(systemResources.uptime)}
subtitle="Process runtime"
color="info"
/>
<SystemResourceCard
icon={IconServer}
title="Process ID"
value={systemResources.pid || 'N/A'}
subtitle="Current PID"
color="default"
/>
</div>
</div>
</div>
</div>
</StyledWrapper>
);
};
export default Performance;

View File

@@ -26,6 +26,7 @@ import { isElectron } from 'utils/common/platform';
import { globalEnvironmentsUpdateEvent, updateGlobalEnvironments } from 'providers/ReduxStore/slices/global-environments';
import { collectionAddOauth2CredentialsByUrl, updateCollectionLoadingState } from 'providers/ReduxStore/slices/collections/index';
import { addLog } from 'providers/ReduxStore/slices/logs';
import { updateSystemResources } from 'providers/ReduxStore/slices/performance';
const useIpcEvents = () => {
const dispatch = useDispatch();
@@ -145,6 +146,10 @@ const useIpcEvents = () => {
}));
});
const removeSystemResourcesListener = ipcRenderer.on('main:filesync-system-resources', resourceData => {
dispatch(updateSystemResources(resourceData));
});
const removeConfigUpdatesListener = ipcRenderer.on('main:bruno-config-update', (val) =>
dispatch(brunoConfigUpdateEvent(val))
);
@@ -209,6 +214,7 @@ const useIpcEvents = () => {
removeCollectionOauth2CredentialsUpdatesListener();
removeCollectionLoadingStateListener();
removePersistentEnvVariablesUpdateListener();
removeSystemResourcesListener();
};
}, [isElectron]);
};

View File

@@ -7,6 +7,7 @@ import tabsReducer from './slices/tabs';
import notificationsReducer from './slices/notifications';
import globalEnvironmentsReducer from './slices/global-environments';
import logsReducer from './slices/logs';
import performanceReducer from './slices/performance';
import { draftDetectMiddleware } from './middlewares/draft/middleware';
const isDevEnv = () => {
@@ -25,7 +26,8 @@ export const store = configureStore({
tabs: tabsReducer,
notifications: notificationsReducer,
globalEnvironments: globalEnvironmentsReducer,
logs: logsReducer
logs: logsReducer,
performance: performanceReducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware)
});

View File

@@ -0,0 +1,28 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
systemResources: {
cpu: 0,
memory: 0,
pid: null,
uptime: 0,
lastUpdated: null,
},
};
export const performanceSlice = createSlice({
name: 'performance',
initialState,
reducers: {
updateSystemResources: (state, action) => {
state.systemResources = {
...state.systemResources,
...action.payload,
lastUpdated: new Date().toISOString(),
};
},
},
});
export const { updateSystemResources } = performanceSlice.actions;
export default performanceSlice.reducer;

View File

@@ -65,6 +65,7 @@
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"nanoid": "3.3.8",
"pidusage": "^4.0.1",
"qs": "^6.11.0",
"socks-proxy-agent": "^8.0.2",
"tough-cookie": "^6.0.0",

View File

@@ -0,0 +1,71 @@
const pidusage = require('pidusage');
class SystemMonitor {
constructor() {
this.intervalId = null;
this.isMonitoring = false;
this.startTime = Date.now();
}
start(win, intervalMs = 2000) {
if (this.isMonitoring) {
return;
}
this.isMonitoring = true;
this.startTime = Date.now();
// Emit initial stats
this.emitSystemStats(win);
// Set up periodic monitoring
this.intervalId = setInterval(() => {
this.emitSystemStats(win);
}, intervalMs);
}
stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
this.isMonitoring = false;
}
async emitSystemStats(win) {
try {
const pid = process.pid;
const stats = await pidusage(pid);
const uptime = (Date.now() - this.startTime) / 1000;
const systemResources = {
cpu: stats.cpu || 0,
memory: stats.memory || 0,
pid: pid,
uptime: uptime,
timestamp: new Date().toISOString(),
};
win.webContents.send('main:filesync-system-resources', systemResources);
} catch (error) {
console.error('Error getting system stats:', error);
// Fallback stats if pidusage fails
const fallbackStats = {
cpu: 0,
memory: process.memoryUsage().rss,
pid: process.pid,
uptime: (Date.now() - this.startTime) / 1000,
timestamp: new Date().toISOString(),
};
win.webContents.send('main:filesync-system-resources', fallbackStats);
}
}
isRunning() {
return this.isMonitoring;
}
}
module.exports = SystemMonitor;

View File

@@ -45,8 +45,10 @@ const { safeParseJSON, safeStringifyJSON } = require('./utils/common');
const { getDomainsWithCookies } = require('./utils/cookies');
const { cookiesStore } = require('./store/cookies');
const onboardUser = require('./app/onboarding');
const SystemMonitor = require('./app/system-monitor');
const lastOpenedCollections = new LastOpenedCollections();
const systemMonitor = new SystemMonitor();
// Reference: https://content-security-policy.com/
const contentSecurityPolicy = [
@@ -202,6 +204,9 @@ app.on('ready', async () => {
}
mainWindow.webContents.send('main:app-loaded');
// Start system monitoring for FileSync
systemMonitor.start(mainWindow);
});
// register all ipc handlers
@@ -220,6 +225,9 @@ app.on('before-quit', () => {
} catch (err) {
console.warn('Failed to flush cookies on quit', err);
}
// Stop system monitoring
systemMonitor.stop();
});
app.on('window-all-closed', app.quit);