frontend UI improvements + compile metrics script fixes
This commit is contained in:
1 parent
eab675795a
commit
0b6bd68a24
4 files changed
+152
-53
No files matched your search
+8
-11
@@ -3,18 +3,15 @@ set -euo pipefail
|
||||
|
||||
BUILD_DIR="${BUILD_DIR:-target}"
|
||||
CSV_FILE="${CSV_FILE:-compile_metrics.csv}"
|
||||
DEFAULT_PKG="server"
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "Usage: $0 cargo build [--release]"
|
||||
exit 1
|
||||
if [ "$#" -eq 0 ]; then
|
||||
set -- cargo build
|
||||
fi
|
||||
|
||||
if [ -d "$BUILD_DIR" ]; then
|
||||
find "$BUILD_DIR" -type f -name '*.ll' -delete
|
||||
fi
|
||||
|
||||
if [ "$1" = "cargo" ] && [ "$2" = "build" ]; then
|
||||
BUILD_CMD=(cargo rustc)
|
||||
if [ "${1:-}" = "cargo" ] && [ "${2:-}" = "build" ]; then
|
||||
cargo clean
|
||||
BUILD_CMD=(cargo rustc -p "$DEFAULT_PKG")
|
||||
shift 2
|
||||
BUILD_CMD+=("$@")
|
||||
BUILD_CMD+=(-- --emit=llvm-ir -C debuginfo=0)
|
||||
@@ -57,7 +54,6 @@ else
|
||||
binary_size_bytes=0
|
||||
fi
|
||||
|
||||
|
||||
finished_timestamp_ms=$(($(date +%s%N) / 1000000))
|
||||
finished_datetime=$(date -Iseconds)
|
||||
|
||||
@@ -67,4 +63,5 @@ fi
|
||||
|
||||
echo "${finished_datetime},${finished_timestamp_ms},${compile_ms},${llvm_ir_lines},${binary_size_bytes}" \
|
||||
>> "$CSV_FILE"
|
||||
echo "Metrics recorded in $CSV_FILE"
|
||||
|
||||
echo "Metrics recorded in $CSV_FILE"
|
||||
@@ -25,6 +25,8 @@ export function getUnitIcon(unit: Unit): IconName {
|
||||
return "lu:Gauge";
|
||||
case "mHz":
|
||||
return "lu:AudioWaveform";
|
||||
case "Hz":
|
||||
return "lu:AudioWaveform";
|
||||
case "W":
|
||||
return "lu:Zap";
|
||||
case "V":
|
||||
@@ -37,6 +39,16 @@ export function getUnitIcon(unit: Unit): IconName {
|
||||
return "lu:Waves";
|
||||
case "%":
|
||||
return "lu:ChartNoAxesColumn";
|
||||
case "µs":
|
||||
return "lu:RefreshCcw";
|
||||
case "s":
|
||||
return "lu:Clock";
|
||||
case "MiB":
|
||||
return "lu:MemoryStick";
|
||||
case "Mbit/s":
|
||||
return "lu:Network";
|
||||
case "/s":
|
||||
return "lu:Gauge";
|
||||
default:
|
||||
return "lu:ChartNoAxesColumn";
|
||||
}
|
||||
@@ -64,6 +76,8 @@ export function renderUnitSymbol(unit: Unit | undefined): string {
|
||||
return "m/min";
|
||||
case "mHz":
|
||||
return "mHz";
|
||||
case "Hz":
|
||||
return "Hz";
|
||||
case "W":
|
||||
return "W";
|
||||
case "V":
|
||||
@@ -76,6 +90,16 @@ export function renderUnitSymbol(unit: Unit | undefined): string {
|
||||
return "l/min";
|
||||
case "%":
|
||||
return "%";
|
||||
case "µs":
|
||||
return "µs";
|
||||
case "s":
|
||||
return "s";
|
||||
case "MiB":
|
||||
return "MiB";
|
||||
case "Mbit/s":
|
||||
return "Mbit/s";
|
||||
case "/s":
|
||||
return "/s";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -125,6 +149,8 @@ export function renderUnitSymbolLong(unit: Unit): string {
|
||||
return "meters/minute";
|
||||
case "mHz":
|
||||
return "millihertz";
|
||||
case "Hz":
|
||||
return "hertz";
|
||||
case "W":
|
||||
return "watts";
|
||||
case "V":
|
||||
@@ -137,6 +163,16 @@ export function renderUnitSymbolLong(unit: Unit): string {
|
||||
return "liters/minute";
|
||||
case "%":
|
||||
return "percent";
|
||||
case "µs":
|
||||
return "microseconds";
|
||||
case "s":
|
||||
return "seconds";
|
||||
case "MiB":
|
||||
return "mebibytes";
|
||||
case "Mbit/s":
|
||||
return "megabits/second";
|
||||
case "/s":
|
||||
return "per second";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -153,12 +189,18 @@ export const units = [
|
||||
"bar",
|
||||
"m/min",
|
||||
"mHz",
|
||||
"Hz",
|
||||
"W",
|
||||
"V",
|
||||
"A",
|
||||
"kWh",
|
||||
"l/min",
|
||||
"%",
|
||||
"µs",
|
||||
"s",
|
||||
"MiB",
|
||||
"Mbit/s",
|
||||
"/s",
|
||||
] as const;
|
||||
|
||||
export type Unit = (typeof units)[number];
|
||||
|
||||
@@ -1,17 +1,54 @@
|
||||
import React from "react";
|
||||
import { Page } from "@/components/Page";
|
||||
import { useRuntimeMetrics } from "./useRuntimeMetrics";
|
||||
import { SectionTitle } from "@/components/SectionTitle";
|
||||
import {
|
||||
renderValueToReactNode,
|
||||
renderUnitSymbol,
|
||||
getUnitIcon,
|
||||
type Unit,
|
||||
} from "@/control/units";
|
||||
import { Icon } from "@/components/Icon";
|
||||
|
||||
type MetricCardProps = {
|
||||
title: string;
|
||||
value: string;
|
||||
value: React.ReactNode;
|
||||
unit?: Unit;
|
||||
};
|
||||
|
||||
function MetricCard({ title, value }: MetricCardProps) {
|
||||
function renderMetricWithUnit(
|
||||
value: number | null | undefined,
|
||||
unit: Unit,
|
||||
digits = 1,
|
||||
) {
|
||||
if (value == null) {
|
||||
return "—";
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderValueToReactNode(value, unit, (v) => v.toFixed(digits))}
|
||||
<span className="text-muted-foreground ml-1 text-xs">
|
||||
{renderUnitSymbol(unit)}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function MetricCard({ title, value, unit }: MetricCardProps) {
|
||||
const iconName = unit ? getUnitIcon(unit) : "lu:StickyNote";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-sm font-medium text-slate-700">{title}</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
{iconName && (
|
||||
<span className="inline-flex h-7 w-7 items-center justify-center rounded-full bg-slate-100 text-slate-500">
|
||||
<Icon name={iconName} className="h-4 w-4" />
|
||||
</span>
|
||||
)}
|
||||
<h2 className="text-sm font-medium text-slate-700">{title}</h2>
|
||||
</div>
|
||||
<span className="text-base font-semibold text-slate-900">{value}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,55 +76,71 @@ export function MetricsControlPage() {
|
||||
return (
|
||||
<Page>
|
||||
<div className="space-y-6">
|
||||
<p className="text-sm text-slate-600">
|
||||
Runtime metrics are refreshed immediately once and then every 5
|
||||
seconds.
|
||||
</p>
|
||||
|
||||
<SectionTitle title="Backend Metrics" />
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{/* Loop jitter: max, in µs */}
|
||||
<MetricCard
|
||||
title="Loop jitter (max, µs)"
|
||||
value={jitterMaxUs != null ? `${jitterMaxUs.toFixed(1)} µs` : "—"}
|
||||
title="Max Loop Jitter"
|
||||
unit={"µs"}
|
||||
value={renderMetricWithUnit(jitterMaxUs, "µs")}
|
||||
/>
|
||||
|
||||
{/* CPU usage */}
|
||||
<MetricCard
|
||||
title="CPU usage / time"
|
||||
value={
|
||||
cpuTimeSec != null && cpuPercent != null
|
||||
? `${cpuPercent.toFixed(1)} % • ${cpuTimeSec.toFixed(1)} s`
|
||||
: cpuTimeSec != null
|
||||
? `${cpuTimeSec.toFixed(1)} s`
|
||||
: "—"
|
||||
}
|
||||
title="CPU Usage"
|
||||
unit={"%"}
|
||||
value={renderMetricWithUnit(cpuPercent, "%")}
|
||||
/>
|
||||
|
||||
{/* CPU time */}
|
||||
<MetricCard
|
||||
title="Memory usage"
|
||||
value={rssMb != null ? `${rssMb.toFixed(1)} MiB` : "—"}
|
||||
title="CPU Time"
|
||||
unit={"s"}
|
||||
value={renderMetricWithUnit(cpuTimeSec, "s")}
|
||||
/>
|
||||
|
||||
{/* Memory usage */}
|
||||
<MetricCard
|
||||
title="Page faults (total)"
|
||||
title="Memory Usage"
|
||||
unit={"MiB"}
|
||||
value={renderMetricWithUnit(rssMb, "MiB")}
|
||||
/>
|
||||
|
||||
{/* Total page faults (composite, no single unit) */}
|
||||
<MetricCard
|
||||
title="Total Page Faults"
|
||||
value={
|
||||
minorFaults != null || majorFaults != null
|
||||
? `${minorFaults ?? "–"} minor / ${majorFaults ?? "–"} major`
|
||||
? `${minorFaults ?? "–"} minor | ${majorFaults ?? "–"} major`
|
||||
: "—"
|
||||
}
|
||||
/>
|
||||
|
||||
{/* EtherCAT throughput (RX | TX) */}
|
||||
<MetricCard
|
||||
title="EtherCAT throughput (RX / TX)"
|
||||
title="EtherCAT Throughput (RX | TX)"
|
||||
unit={"Mbit/s"}
|
||||
value={
|
||||
rxMbit != null && txMbit != null
|
||||
? `${rxMbit.toFixed(2)} / ${txMbit.toFixed(2)} Mbit/s`
|
||||
: "—"
|
||||
rxMbit != null && txMbit != null ? (
|
||||
<span className="flex items-center gap-1">
|
||||
{renderMetricWithUnit(rxMbit, "Mbit/s")}
|
||||
<span className="text-muted-foreground mx-1 text-xs">|</span>
|
||||
{renderMetricWithUnit(txMbit, "Mbit/s")}
|
||||
</span>
|
||||
) : (
|
||||
"—"
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Preemptions per second */}
|
||||
<MetricCard
|
||||
title="Preemptions"
|
||||
unit={"/s"}
|
||||
value={
|
||||
preemptionRate != null ? `${preemptionRate.toFixed(1)} /s` : "—"
|
||||
preemptionRate != null
|
||||
? renderMetricWithUnit(preemptionRate, "/s")
|
||||
: "—"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@/components/graph";
|
||||
import { useRuntimeMetrics } from "./useRuntimeMetrics";
|
||||
import { useRuntimeMetricSeries } from "./runtimeSeries";
|
||||
import { getUnitIcon, type Unit } from "@/control/units";
|
||||
|
||||
export function MetricsGraphsPage() {
|
||||
// Start / keep the global poller alive
|
||||
@@ -18,9 +19,15 @@ export function MetricsGraphsPage() {
|
||||
|
||||
const syncHook = useGraphSync("runtime-metrics");
|
||||
|
||||
const jitterUnit: Unit = "µs";
|
||||
const cpuUnit: Unit = "%";
|
||||
const memUnit: Unit = "MiB";
|
||||
const ioUnit: Unit = "Mbit/s";
|
||||
const preemptUnit: Unit = "/s";
|
||||
|
||||
const jitterConfig: GraphConfig = {
|
||||
title: "Loop jitter (avg, µs)",
|
||||
icon: "lu:Activity",
|
||||
title: "Average Loop Jitter",
|
||||
icon: getUnitIcon(jitterUnit),
|
||||
colors: {
|
||||
primary: "#f97316",
|
||||
grid: "#e2e8f0",
|
||||
@@ -30,8 +37,8 @@ export function MetricsGraphsPage() {
|
||||
};
|
||||
|
||||
const cpuConfig: GraphConfig = {
|
||||
title: "CPU usage (%)",
|
||||
icon: "lu:Cpu",
|
||||
title: "CPU Usage",
|
||||
icon: getUnitIcon(cpuUnit),
|
||||
colors: {
|
||||
primary: "#3b82f6",
|
||||
grid: "#e2e8f0",
|
||||
@@ -41,8 +48,8 @@ export function MetricsGraphsPage() {
|
||||
};
|
||||
|
||||
const memConfig: GraphConfig = {
|
||||
title: "Memory usage (MiB)",
|
||||
icon: "lu:MemoryStick",
|
||||
title: "Memory Usage",
|
||||
icon: getUnitIcon(memUnit),
|
||||
colors: {
|
||||
primary: "#10b981",
|
||||
grid: "#e2e8f0",
|
||||
@@ -52,8 +59,8 @@ export function MetricsGraphsPage() {
|
||||
};
|
||||
|
||||
const ioConfig: GraphConfig = {
|
||||
title: "EtherCAT throughput (Mbit/s)",
|
||||
icon: "lu:Network",
|
||||
title: "EtherCAT Throughput",
|
||||
icon: getUnitIcon(ioUnit),
|
||||
colors: {
|
||||
primary: "#10b981",
|
||||
grid: "#e2e8f0",
|
||||
@@ -64,8 +71,8 @@ export function MetricsGraphsPage() {
|
||||
};
|
||||
|
||||
const preemptConfig: GraphConfig = {
|
||||
title: "Preemptions per second (1/s)",
|
||||
icon: "lu:Timer",
|
||||
title: "Preemptions Per Second",
|
||||
icon: getUnitIcon(preemptUnit),
|
||||
colors: {
|
||||
primary: "#6366f1",
|
||||
grid: "#e2e8f0",
|
||||
@@ -84,7 +91,7 @@ export function MetricsGraphsPage() {
|
||||
newData: series.jitter,
|
||||
color: "#f97316",
|
||||
}}
|
||||
unit={undefined}
|
||||
unit={jitterUnit}
|
||||
renderValue={(v) => v.toFixed(1)}
|
||||
config={jitterConfig}
|
||||
graphId="runtime_jitter"
|
||||
@@ -98,7 +105,7 @@ export function MetricsGraphsPage() {
|
||||
newData: series.cpu,
|
||||
color: "#3b82f6",
|
||||
}}
|
||||
unit="%"
|
||||
unit={cpuUnit}
|
||||
renderValue={(v) => v.toFixed(1)}
|
||||
config={cpuConfig}
|
||||
graphId="runtime_cpu"
|
||||
@@ -112,7 +119,7 @@ export function MetricsGraphsPage() {
|
||||
newData: series.mem,
|
||||
color: "#10b981",
|
||||
}}
|
||||
unit={undefined}
|
||||
unit={memUnit}
|
||||
renderValue={(v) => v.toFixed(1)}
|
||||
config={memConfig}
|
||||
graphId="runtime_memory"
|
||||
@@ -134,7 +141,7 @@ export function MetricsGraphsPage() {
|
||||
color: "#ef4444",
|
||||
},
|
||||
]}
|
||||
unit={undefined}
|
||||
unit={ioUnit}
|
||||
renderValue={(v) => v.toFixed(2)}
|
||||
config={ioConfig}
|
||||
graphId="runtime_io"
|
||||
@@ -148,7 +155,7 @@ export function MetricsGraphsPage() {
|
||||
newData: series.preempt,
|
||||
color: "#6366f1",
|
||||
}}
|
||||
unit={undefined}
|
||||
unit={preemptUnit}
|
||||
renderValue={(v) => v.toFixed(1)}
|
||||
config={preemptConfig}
|
||||
graphId="runtime_preemptions"
|
||||
|
||||
Reference in new issue
Block a user