From 85e25ed3ab9962c62a25ab1dbb0163f5d704dc62 Mon Sep 17 00:00:00 2001 From: Felix Rieseberg Date: Sat, 11 Apr 2026 07:51:39 -0700 Subject: [PATCH] Improve VM info bar: CPU M/s, disk & net throughput, hover-to-reveal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show CPU as M/s (millions of instructions/sec) instead of raw count - Replace Disk Idle/Read with actual R/W throughput (B/K/M per sec) - Add Net ↓/↑ throughput from eth-receive-end / eth-transmit-end - Always mount the bar; when hidden it slides off-screen and reveals on hover near the top edge (Pin/Hide toggle) - Center via translateX(-50%) so the wider bar stays centered --- src/less/status.less | 23 +++++- src/renderer/emulator-info.tsx | 135 +++++++++++++++++++-------------- src/renderer/emulator.tsx | 5 +- 3 files changed, 103 insertions(+), 60 deletions(-) diff --git a/src/less/status.less b/src/less/status.less index 425df3e..b19cd49 100644 --- a/src/less/status.less +++ b/src/less/status.less @@ -1,8 +1,19 @@ +#status-hotzone { + position: absolute; + z-index: 99; + top: 0; + left: 0; + right: 0; + height: 8px; +} + #status { user-select: none; position: absolute; z-index: 100; - left: calc(50vw - 110px); + left: 50vw; + transform: translateX(-50%); + white-space: nowrap; background: white; font-size: 10px; padding-bottom: 3px; @@ -13,4 +24,14 @@ padding-right: 10px; max-height: 18px; top: 0; + transition: transform 0.12s ease-out; + + &.hidden { + transform: translateX(-50%) translateY(-100%); + } +} + +#status-hotzone:hover + #status.hidden, +#status.hidden:hover { + transform: translateX(-50%) translateY(0); } diff --git a/src/renderer/emulator-info.tsx b/src/renderer/emulator-info.tsx index 988dd08..e7d8535 100644 --- a/src/renderer/emulator-info.tsx +++ b/src/renderer/emulator-info.tsx @@ -3,11 +3,15 @@ import * as React from "react"; interface EmulatorInfoProps { toggleInfo: () => void; emulator: any; + hidden: boolean; } interface EmulatorInfoState { cpu: number; - disk: string; + diskRead: number; + diskWrite: number; + netRx: number; + netTx: number; lastCounter: number; lastTick: number; } @@ -16,33 +20,49 @@ export class EmulatorInfo extends React.Component< EmulatorInfoProps, EmulatorInfoState > { - private cpuInterval = -1; + private tickInterval = -1; + private diskReadBytes = 0; + private diskWriteBytes = 0; + private netRxBytes = 0; + private netTxBytes = 0; constructor(props: EmulatorInfoProps) { super(props); - this.cpuCount = this.cpuCount.bind(this); - this.onIDEReadStart = this.onIDEReadStart.bind(this); - this.onIDEReadWriteEnd = this.onIDEReadWriteEnd.bind(this); + this.tick = this.tick.bind(this); + this.onIDEReadEnd = this.onIDEReadEnd.bind(this); + this.onIDEWriteEnd = this.onIDEWriteEnd.bind(this); + this.onEthReceiveEnd = this.onEthReceiveEnd.bind(this); + this.onEthTransmitEnd = this.onEthTransmitEnd.bind(this); this.state = { cpu: 0, - disk: "Idle", + diskRead: 0, + diskWrite: 0, + netRx: 0, + netTx: 0, lastCounter: 0, lastTick: 0, }; } public render() { - const { cpu, disk } = this.state; + const { cpu, diskRead, diskWrite, netRx, netTx } = this.state; + const { hidden, toggleInfo } = this.props; return ( -
- Disk: {disk} | CPU Speed: {cpu} |{" "} - - Hide - -
+ <> +
+
+ CPU: {cpu}M/s | Disk:{" "} + R {this.rate(diskRead)}{" "} + W {this.rate(diskWrite)} | Net:{" "} + ↓{this.rate(netRx)} ↑{this.rate(netTx)} |{" "} + + {hidden ? "Pin" : "Hide"} + +
+ ); } @@ -79,21 +99,17 @@ export class EmulatorInfo extends React.Component< return; } - // CPU - if (this.cpuInterval > -1) { - clearInterval(this.cpuInterval); + if (this.tickInterval > -1) { + clearInterval(this.tickInterval); } // TypeScript think's we're using a Node.js setInterval. We're not. - this.cpuInterval = setInterval(this.cpuCount, 500) as unknown as number; + this.tickInterval = setInterval(this.tick, 500) as unknown as number; - // Disk - emulator.add_listener("ide-read-start", this.onIDEReadStart); - emulator.add_listener("ide-read-end", this.onIDEReadWriteEnd); - emulator.add_listener("ide-write-end", this.onIDEReadWriteEnd); - - // Screen - emulator.add_listener("screen-set-size-graphical", console.log); + emulator.add_listener("ide-read-end", this.onIDEReadEnd); + emulator.add_listener("ide-write-end", this.onIDEWriteEnd); + emulator.add_listener("eth-receive-end", this.onEthReceiveEnd); + emulator.add_listener("eth-transmit-end", this.onEthTransmitEnd); } /** @@ -109,58 +125,67 @@ export class EmulatorInfo extends React.Component< return; } - // CPU - if (this.cpuInterval > -1) { - clearInterval(this.cpuInterval); + if (this.tickInterval > -1) { + clearInterval(this.tickInterval); } - // Disk - emulator.remove_listener("ide-read-start", this.onIDEReadStart); - emulator.remove_listener("ide-read-end", this.onIDEReadWriteEnd); - emulator.remove_listener("ide-write-end", this.onIDEReadWriteEnd); + emulator.remove_listener("ide-read-end", this.onIDEReadEnd); + emulator.remove_listener("ide-write-end", this.onIDEWriteEnd); + emulator.remove_listener("eth-receive-end", this.onEthReceiveEnd); + emulator.remove_listener("eth-transmit-end", this.onEthTransmitEnd); + } - // Screen - emulator.remove_listener("screen-set-size-graphical", console.log); + private onIDEReadEnd(args: number[]) { + this.diskReadBytes += args[1]; + } + + private onIDEWriteEnd(args: number[]) { + this.diskWriteBytes += args[1]; + } + + private onEthReceiveEnd(args: number[]) { + this.netRxBytes += args[0]; + } + + private onEthTransmitEnd(args: number[]) { + this.netTxBytes += args[0]; } /** - * The virtual IDE is handling read (start). + * Format bytes/sec into a compact human string. */ - private onIDEReadStart() { - this.requestIdle(() => this.setState({ disk: "Read" })); + private rate(bytesPerSec: number) { + if (bytesPerSec <= 0) return "0"; + if (bytesPerSec < 1024) return `${bytesPerSec}B/s`; + if (bytesPerSec < 1024 * 1024) return `${Math.round(bytesPerSec / 1024)}K/s`; + return `${(bytesPerSec / 1024 / 1024).toFixed(1)}M/s`; } /** - * The virtual IDE is handling read/write (end). + * Once per interval, compute CPU speed and I/O throughput. */ - private onIDEReadWriteEnd() { - this.requestIdle(() => this.setState({ disk: "Idle" })); - } - - /** - * Request an idle callback with a 3s timeout. - * - * @param fn - */ - private requestIdle(fn: () => void) { - (window as any).requestIdleCallback(fn, { timeout: 3000 }); - } - - /** - * Calculates what's up with the virtual cpu. - */ - private cpuCount() { + private tick() { const { lastCounter, lastTick } = this.state; const now = Date.now(); const instructionCounter = this.props.emulator.get_instruction_counter(); const ips = instructionCounter - lastCounter; const deltaTime = now - lastTick; + const deltaSec = deltaTime / 1000; this.setState({ lastTick: now, lastCounter: instructionCounter, - cpu: Math.round(ips / deltaTime), + cpu: Math.round(ips / deltaTime / 1000), + diskRead: Math.round(this.diskReadBytes / deltaSec), + diskWrite: Math.round(this.diskWriteBytes / deltaSec), + netRx: Math.round(this.netRxBytes / deltaSec), + netTx: Math.round(this.netTxBytes / deltaSec), }); + + this.diskReadBytes = 0; + this.diskWriteBytes = 0; + this.netRxBytes = 0; + this.netTxBytes = 0; } } diff --git a/src/renderer/emulator.tsx b/src/renderer/emulator.tsx index 13fde46..9fdc994 100644 --- a/src/renderer/emulator.tsx +++ b/src/renderer/emulator.tsx @@ -261,13 +261,10 @@ export class Emulator extends React.Component<{}, EmulatorState> { * Render the little info thingy */ public renderInfo() { - if (!this.state.isInfoDisplayed) { - return null; - } - return (