mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
Merge pull request #5635 from naman-bruno/feat/performance-monitor
add: system monitor
This commit is contained in:
13
package-lock.json
generated
13
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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;
|
||||
100
packages/bruno-app/src/components/Devtools/Performance/index.js
Normal file
100
packages/bruno-app/src/components/Devtools/Performance/index.js
Normal 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;
|
||||
@@ -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]);
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -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",
|
||||
|
||||
71
packages/bruno-electron/src/app/system-monitor.js
Normal file
71
packages/bruno-electron/src/app/system-monitor.js
Normal 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;
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user