Merge pull request #345 from felixrieseberg/claude/kind-hermann

Improve VM info bar: live CPU/disk/net throughput + hover-to-reveal
This commit is contained in:
Felix Rieseberg
2026-04-11 07:57:17 -07:00
committed by GitHub
3 changed files with 103 additions and 60 deletions

View File

@@ -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);
}

View File

@@ -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 (
<div id="status">
Disk: <span>{disk}</span> | CPU Speed: <span>{cpu}</span> |{" "}
<a href="#" onClick={this.props.toggleInfo}>
Hide
</a>
</div>
<>
<div id="status-hotzone" />
<div id="status" className={hidden ? "hidden" : ""}>
CPU: <span>{cpu}M/s</span> | Disk:{" "}
<span>R {this.rate(diskRead)}</span>{" "}
<span>W {this.rate(diskWrite)}</span> | Net:{" "}
<span>{this.rate(netRx)}</span> <span>{this.rate(netTx)}</span> |{" "}
<a href="#" onClick={toggleInfo}>
{hidden ? "Pin" : "Hide"}
</a>
</div>
</>
);
}
@@ -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;
}
}

View File

@@ -271,13 +271,10 @@ export class Emulator extends React.Component<{}, EmulatorState> {
* Render the little info thingy
*/
public renderInfo() {
if (!this.state.isInfoDisplayed) {
return null;
}
return (
<EmulatorInfo
emulator={this.state.emulator}
hidden={!this.state.isInfoDisplayed}
toggleInfo={() => {
this.setState({ isInfoDisplayed: !this.state.isInfoDisplayed });
}}