mirror of
https://github.com/everoddandeven/monerod-gui.git
synced 2025-01-03 09:29:36 +00:00
Monerod process stats
This commit is contained in:
parent
10d832a614
commit
cd2bb56522
10 changed files with 233 additions and 38 deletions
103
app/main.ts
103
app/main.ts
|
@ -1,4 +1,4 @@
|
|||
import { app, BrowserWindow, ipcMain, screen, dialog, Tray, Menu, MenuItemConstructorOptions } from 'electron';
|
||||
import { app, BrowserWindow, ipcMain, screen, dialog, Tray, Menu, MenuItemConstructorOptions, IpcMainInvokeEvent } from 'electron';
|
||||
import { ChildProcessWithoutNullStreams, exec, ExecException, spawn } from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
@ -6,6 +6,44 @@ import * as https from 'https';
|
|||
import { createHash } from 'crypto';
|
||||
import * as tar from 'tar';
|
||||
import * as os from 'os';
|
||||
import * as pidusage from 'pidusage';
|
||||
|
||||
interface Stats {
|
||||
/**
|
||||
* percentage (from 0 to 100*vcore)
|
||||
*/
|
||||
cpu: number;
|
||||
|
||||
/**
|
||||
* bytes
|
||||
*/
|
||||
memory: number;
|
||||
|
||||
/**
|
||||
* PPID
|
||||
*/
|
||||
ppid: number;
|
||||
|
||||
/**
|
||||
* PID
|
||||
*/
|
||||
pid: number;
|
||||
|
||||
/**
|
||||
* ms user + system time
|
||||
*/
|
||||
ctime: number;
|
||||
|
||||
/**
|
||||
* ms since the start of the process
|
||||
*/
|
||||
elapsed: number;
|
||||
|
||||
/**
|
||||
* ms since epoch
|
||||
*/
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
//import bz2 from 'unbzip2-stream';
|
||||
//import * as bz2 from 'unbzip2-stream';
|
||||
|
@ -14,6 +52,7 @@ const bz2 = require('unbzip2-stream');
|
|||
let win: BrowserWindow | null = null;
|
||||
let isHidden: boolean = false;
|
||||
let isQuitting: boolean = false;
|
||||
let monerodProcess: ChildProcessWithoutNullStreams | null = null;
|
||||
|
||||
const args = process.argv.slice(1),
|
||||
serve = args.some(val => val === '--serve');
|
||||
|
@ -133,28 +172,6 @@ function createWindow(): BrowserWindow {
|
|||
return win;
|
||||
}
|
||||
|
||||
function isWifiConnectedOld() {
|
||||
console.log("isWifiConnected()");
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
|
||||
console.log(`isWifiConnected(): network interfaces ${networkInterfaces}`);
|
||||
|
||||
for (const interfaceName in networkInterfaces) {
|
||||
const networkInterface = networkInterfaces[interfaceName];
|
||||
|
||||
if (networkInterface) {
|
||||
for (const network of networkInterface) {
|
||||
if (network.family === 'IPv4' && !network.internal && network.mac !== '00:00:00:00:00:00') {
|
||||
if (interfaceName.toLowerCase().includes('wifi') || interfaceName.toLowerCase().includes('wlan')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isConnectedToWiFi(): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const platform = os.platform(); // Use os to get the platform
|
||||
|
@ -229,8 +246,13 @@ function startMoneroDaemon(commandOptions: string[]): ChildProcessWithoutNullStr
|
|||
const monerodPath = commandOptions.shift();
|
||||
|
||||
if (!monerodPath) {
|
||||
win?.webContents.send('monero-sterr', `Invalid monerod path provided: ${monerodPath}`);
|
||||
throw Error("Invalid monerod path provided");
|
||||
win?.webContents.send('monero-stderr', `Invalid monerod path provided: ${monerodPath}`);
|
||||
throw new Error("Invalid monerod path provided");
|
||||
}
|
||||
|
||||
if (monerodProcess != null) {
|
||||
win?.webContents.send('monero-stderr', 'Monerod already started');
|
||||
throw new Error("Monerod already started");
|
||||
}
|
||||
|
||||
console.log("Starting monerod daemon with options: " + commandOptions.join(" "));
|
||||
|
@ -238,7 +260,7 @@ function startMoneroDaemon(commandOptions: string[]): ChildProcessWithoutNullStr
|
|||
moneroFirstStdout = true;
|
||||
|
||||
// Avvia il processo usando spawn
|
||||
const monerodProcess = spawn(monerodPath, commandOptions);
|
||||
monerodProcess = spawn(monerodPath, commandOptions);
|
||||
|
||||
// Gestisci l'output di stdout in streaming
|
||||
monerodProcess.stdout.on('data', (data) => {
|
||||
|
@ -271,11 +293,34 @@ function startMoneroDaemon(commandOptions: string[]): ChildProcessWithoutNullStr
|
|||
console.log(`monerod exited with code: ${code}`);
|
||||
win?.webContents.send('monero-stdout', `monerod exited with code: ${code}`);
|
||||
win?.webContents.send('monero-close', code);
|
||||
monerodProcess = null;
|
||||
});
|
||||
|
||||
return monerodProcess;
|
||||
}
|
||||
|
||||
function monitorMonerod(): void {
|
||||
if (!monerodProcess) {
|
||||
win?.webContents.send('on-monitor-monerod-error', 'Monerod not running');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!monerodProcess.pid) {
|
||||
win?.webContents.send('on-monitor-monerod-error', 'Unknown monero pid');
|
||||
return;
|
||||
}
|
||||
|
||||
pidusage(monerodProcess.pid, (error: Error | null, stats: Stats) => {
|
||||
if (error) {
|
||||
win?.webContents.send('on-monitor-monerod-error', `${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
win?.webContents.send('on-monitor-monerod', stats);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
const downloadFile = (url: string, destinationDir: string, onProgress: (progress: number) => void): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = (url: string) => {
|
||||
|
@ -524,14 +569,18 @@ try {
|
|||
|
||||
ipcMain.handle('is-wifi-connected', async (event) => {
|
||||
isWifiConnected();
|
||||
//win?.webContents.send('is-wifi-connected-result', isWifiConnected());
|
||||
});
|
||||
|
||||
ipcMain.handle('get-os-type', (event) => {
|
||||
win?.webContents.send('got-os-type', { platform: os.platform(), arch: os.arch() });
|
||||
})
|
||||
|
||||
ipcMain.handle('monitor-monerod', (event: IpcMainInvokeEvent) => {
|
||||
monitorMonerod();
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
// Catch Error
|
||||
console.error(e);
|
||||
// throw e;
|
||||
}
|
||||
|
|
63
app/package-lock.json
generated
63
app/package-lock.json
generated
|
@ -1,16 +1,20 @@
|
|||
{
|
||||
"name": "monerod-gui",
|
||||
"version": "14.0.1",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "monerod-gui",
|
||||
"version": "14.0.1",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"os": "^0.1.2",
|
||||
"pidusage": "^3.0.2",
|
||||
"tar": "^7.4.3",
|
||||
"unbzip2-stream": "^1.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pidusage": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
|
@ -49,6 +53,12 @@
|
|||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/pidusage": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/pidusage/-/pidusage-2.0.5.tgz",
|
||||
"integrity": "sha512-MIiyZI4/MK9UGUXWt0jJcCZhVw7YdhBuTOuqP/BjuLDLZ2PmmViMIQgZiWxtaMicQfAz/kMrZ5T7PKxFSkTeUA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
|
@ -339,6 +349,17 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/pidusage": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz",
|
||||
"integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
||||
|
@ -353,6 +374,25 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"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/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
@ -636,6 +676,12 @@
|
|||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"optional": true
|
||||
},
|
||||
"@types/pidusage": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/pidusage/-/pidusage-2.0.5.tgz",
|
||||
"integrity": "sha512-MIiyZI4/MK9UGUXWt0jJcCZhVw7YdhBuTOuqP/BjuLDLZ2PmmViMIQgZiWxtaMicQfAz/kMrZ5T7PKxFSkTeUA==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
|
@ -813,6 +859,14 @@
|
|||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"pidusage": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz",
|
||||
"integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.2.1"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
||||
|
@ -821,6 +875,11 @@
|
|||
"glob": "^10.3.7"
|
||||
}
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
|
|
@ -11,7 +11,11 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"os": "^0.1.2",
|
||||
"pidusage": "^3.0.2",
|
||||
"tar": "^7.4.3",
|
||||
"unbzip2-stream": "^1.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pidusage": "^2.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,15 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||
onMonerodStarted: (callback) => {
|
||||
ipcRenderer.on('monerod-started', callback);
|
||||
},
|
||||
monitorMonerod: () => {
|
||||
ipcRenderer.invoke('monitor-monerod');
|
||||
},
|
||||
onMonitorMonerod: (callback) => {
|
||||
ipcRenderer.on('on-monitor-monerod', callback);
|
||||
},
|
||||
onMonitorMonerodError: (callback) => {
|
||||
ipcRenderer.on('on-monitor-monerod-error', callback);
|
||||
},
|
||||
unsubscribeOnMonerodStarted: () => {
|
||||
const listeners = ipcRenderer.listeners('monerod-started');
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { EventEmitter, Injectable, NgZone } from '@angular/core';
|
||||
import { DaemonService } from './daemon.service';
|
||||
import { BlockCount, BlockHeader, Chain, Connection, CoreIsBusyError, DaemonInfo, MinerData, MiningStatus, NetStats, NetStatsHistory, PeerInfo, PublicNode, SyncInfo, TxBacklogEntry, TxPool } from '../../../../common';
|
||||
import { BlockCount, BlockHeader, Chain, Connection, CoreIsBusyError, DaemonInfo, MinerData, MiningStatus, NetStats, NetStatsHistory, PeerInfo, ProcessStats, PublicNode, SyncInfo, TxBacklogEntry, TxPool } from '../../../../common';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -16,6 +16,9 @@ export class DaemonDataService {
|
|||
|
||||
private _daemonRunning: boolean = false;
|
||||
|
||||
private _processStats?: ProcessStats;
|
||||
private _gettingProcessStats: boolean = false;
|
||||
|
||||
private _daemonInfo?: DaemonInfo;
|
||||
private _gettingDaemonInfo: boolean = false;
|
||||
|
||||
|
@ -110,6 +113,14 @@ export class DaemonDataService {
|
|||
return this._refreshing;
|
||||
}
|
||||
|
||||
public get processStats(): ProcessStats | undefined {
|
||||
return this._processStats;
|
||||
}
|
||||
|
||||
public get gettingProcessStats(): boolean {
|
||||
return this._gettingProcessStats;
|
||||
}
|
||||
|
||||
public get info(): DaemonInfo | undefined {
|
||||
return this._daemonInfo;
|
||||
}
|
||||
|
@ -268,10 +279,6 @@ export class DaemonDataService {
|
|||
return Date.now() - this._lastRefresh <= this.refreshTimeoutMs;
|
||||
}
|
||||
|
||||
private async getInfo(): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
private async refreshMiningStatus(): Promise<void> {
|
||||
this._gettingMiningStatus = true;
|
||||
|
||||
|
@ -409,7 +416,7 @@ export class DaemonDataService {
|
|||
}
|
||||
this._gettingIsBlockchainPruned = false;
|
||||
|
||||
await this.refreshAltChains();
|
||||
if (this._daemonInfo.synchronized) await this.refreshAltChains();
|
||||
|
||||
this._gettingNetStats = true;
|
||||
this.netStatsRefreshStart.emit();
|
||||
|
@ -418,7 +425,7 @@ export class DaemonDataService {
|
|||
this.netStatsRefreshEnd.emit();
|
||||
this._gettingNetStats = false;
|
||||
|
||||
await this.refreshMiningStatus();
|
||||
if (this._daemonInfo.synchronized) await this.refreshMiningStatus();
|
||||
|
||||
if (this._daemonInfo.synchronized) await this.refreshMinerData();
|
||||
|
||||
|
@ -440,6 +447,8 @@ export class DaemonDataService {
|
|||
this._connections = await this.daemonService.getConnections();
|
||||
this._gettingConnections = false;
|
||||
|
||||
await this.refreshProcessStats();
|
||||
|
||||
this._lastRefreshHeight = this._daemonInfo.heightWithoutBootstrap;
|
||||
this._lastRefresh = Date.now();
|
||||
} catch(error) {
|
||||
|
@ -468,4 +477,18 @@ export class DaemonDataService {
|
|||
this._refreshing = false;
|
||||
}
|
||||
|
||||
private async refreshProcessStats(): Promise<void> {
|
||||
this._gettingProcessStats = true;
|
||||
|
||||
try {
|
||||
this._processStats = await this.daemonService.getProcessStats();
|
||||
}
|
||||
catch(error: any) {
|
||||
console.error(error);
|
||||
this._processStats = undefined;
|
||||
}
|
||||
|
||||
this._gettingProcessStats = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ import { TxInfo } from '../../../../common/TxInfo';
|
|||
import { DaemonSettings } from '../../../../common/DaemonSettings';
|
||||
import { MethodNotFoundError } from '../../../../common/error/MethodNotFoundError';
|
||||
import { openDB, IDBPDatabase } from "idb"
|
||||
import { PeerInfo, TxPool } from '../../../../common';
|
||||
import { PeerInfo, ProcessStats, TxPool } from '../../../../common';
|
||||
import { MoneroInstallerService } from '../monero-installer/monero-installer.service';
|
||||
|
||||
@Injectable({
|
||||
|
@ -1120,6 +1120,26 @@ export class DaemonService {
|
|||
return "0.1.0-alpha";
|
||||
}
|
||||
|
||||
public async getProcessStats(): Promise<ProcessStats> {
|
||||
if (!await this.isRunning()) {
|
||||
throw new Error("Daemon not running");
|
||||
}
|
||||
|
||||
const getProcessStatsPromise = new Promise<ProcessStats>((resolve, reject) => {
|
||||
window.electronAPI.onMonitorMonerodError((event: any, error: string) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
window.electronAPI.onMonitorMonerod((event: any, stats: ProcessStats) => {
|
||||
resolve(stats);
|
||||
});
|
||||
})
|
||||
|
||||
window.electronAPI.monitorMonerod();
|
||||
|
||||
return await getProcessStatsPromise;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface RpcError { code: number, message: string }
|
|
@ -232,7 +232,7 @@ export class DetailComponent implements AfterViewInit, OnDestroy {
|
|||
if (this.daemonData.initializing || this.daemonService.starting) {
|
||||
return this.createLoadingCards();
|
||||
}
|
||||
return [
|
||||
const cards = [
|
||||
new Card('Connection Status', this.connectionStatus),
|
||||
new Card('Network Type', this.networkType),
|
||||
new Card('Node Type', this.nodeType),
|
||||
|
@ -246,6 +246,15 @@ export class DetailComponent implements AfterViewInit, OnDestroy {
|
|||
new Card('Transaction count', `${this.txCount}`),
|
||||
new Card('Pool size', `${this.poolSize}`)
|
||||
];
|
||||
|
||||
if (this.daemonData.processStats) {
|
||||
cards.push(
|
||||
new Card('CPU usage', `${this.daemonData.processStats.cpu.toFixed(2)} %`),
|
||||
new Card('Memory usage', `${(this.daemonData.processStats.memory / 1024 / 1024).toFixed(2)} MB`)
|
||||
);
|
||||
}
|
||||
|
||||
return cards;
|
||||
}
|
||||
|
||||
public getPeers(): Connection[] {
|
||||
|
|
9
src/common/ProcessStats.ts
Normal file
9
src/common/ProcessStats.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export interface ProcessStats {
|
||||
cpu: number;
|
||||
memory: number;
|
||||
ppid: number;
|
||||
pid: number;
|
||||
ctime: number;
|
||||
elapsed: number;
|
||||
timestamp: number;
|
||||
}
|
|
@ -38,6 +38,7 @@ export { NetStatsHistory, NetStatsHistoryEntry } from './NetStatsHistory';
|
|||
export { UnconfirmedTx } from './UnconfirmedTx';
|
||||
export { SpentKeyImage } from './SpentKeyImage';
|
||||
export { TxPool } from './TxPool';
|
||||
export { ProcessStats } from './ProcessStats';
|
||||
|
||||
export * from './error';
|
||||
export * from './request';
|
||||
|
|
|
@ -59,6 +59,18 @@ declare global {
|
|||
interface Window {
|
||||
electronAPI: {
|
||||
startMonerod: (options: string[]) => void;
|
||||
monitorMonerod: () => void;
|
||||
onMonitorMonerod: (callback: (event: any, stats: {
|
||||
cpu: number;
|
||||
memory: number;
|
||||
ppid: number;
|
||||
pid: number;
|
||||
ctime: number;
|
||||
elapsed: number;
|
||||
timestamp: number;
|
||||
}
|
||||
) => void) => void;
|
||||
onMonitorMonerodError: (callback: (event: any, error: string) => void) => void;
|
||||
onMonerodStarted: (callback: (event: any, started: boolean) => void) => void;
|
||||
unsubscribeOnMonerodStarted: () => void;
|
||||
onMoneroClose: (callback: (event: any, code: number) => void) => void;
|
||||
|
|
Loading…
Reference in a new issue