From 2b0a20c748e336db6fe18325ad84d1acf16a045f Mon Sep 17 00:00:00 2001 From: samfreund Date: Thu, 18 Jun 2026 22:01:09 -0700 Subject: [PATCH] add support for rubik npu metrics --- .../src/components/settings/DeviceCard.vue | 18 +++++++++++-- .../stores/settings/GeneralSettingsStore.ts | 7 ++++- photon-client/src/types/SettingTypes.ts | 2 +- .../hardware/metrics/DeviceMetrics.java | 3 ++- .../hardware/metrics/SystemMonitor.java | 16 ++++++----- .../metrics/SystemMonitorQCS6490.java | 27 +++++++++++++++++++ .../hardware/metrics/SystemMonitorRK3588.java | 12 ++++++--- .../metrics/proto/DeviceMetricsProto.java | 20 ++++++++++++-- photon-targeting/src/main/proto/photon.proto | 2 +- 9 files changed, 90 insertions(+), 17 deletions(-) diff --git a/photon-client/src/components/settings/DeviceCard.vue b/photon-client/src/components/settings/DeviceCard.vue index 4f48f863c7..d39f008ee3 100644 --- a/photon-client/src/components/settings/DeviceCard.vue +++ b/photon-client/src/components/settings/DeviceCard.vue @@ -202,6 +202,20 @@ const generalMetrics = computed(() => { return stats; }); +function mapToString(map: Map): string { + let result = ""; + let comma = false; + for (const [key, value] of map.entries()) { + if (comma) { + result += ", "; + } else { + comma = true; + } + result += `${key}: ${value}`; + } + return result; +} + // @ts-expect-error This uses Intl.DurationFormat which is newly implemented and not available in TS. const durationFormatter = new Intl.DurationFormat("en", { style: "narrow" }); const platformMetrics = computed(() => { @@ -228,10 +242,10 @@ const platformMetrics = computed(() => { } ]; - if (metrics.npuUsage && metrics.npuUsage.length > 0) { + if (metrics.npuUsage && metrics.npuUsage.size > 0) { stats.push({ header: "NPU Usage", - value: metrics.npuUsage?.map((usage, index) => `Core${index} ${usage}%`).join(", ") || "Unknown" + value: mapToString(metrics?.npuUsage) || "Unknown" }); } diff --git a/photon-client/src/stores/settings/GeneralSettingsStore.ts b/photon-client/src/stores/settings/GeneralSettingsStore.ts index c7ddcd4e74..c62b872b2f 100644 --- a/photon-client/src/stores/settings/GeneralSettingsStore.ts +++ b/photon-client/src/stores/settings/GeneralSettingsStore.ts @@ -135,6 +135,11 @@ export const useSettingsStore = defineStore("settings", { }, actions: { updateMetricsFromWebsocket(data: Required) { + const npuUsage = + data.npuUsage instanceof Map + ? new Map(data.npuUsage) + : new Map(Object.entries(data.npuUsage as Record)); + this.metrics = { cpuTemp: data.cpuTemp || undefined, cpuUtil: data.cpuUtil || undefined, @@ -145,7 +150,7 @@ export const useSettingsStore = defineStore("settings", { gpuMemUtil: data.gpuMemUtil || undefined, diskUtilPct: data.diskUtilPct || undefined, diskUsableSpace: data.diskUsableSpace || undefined, - npuUsage: data.npuUsage || undefined, + npuUsage, ipAddress: data.ipAddress || undefined, uptime: data.uptime || undefined, sentBitRate: data.sentBitRate || undefined, diff --git a/photon-client/src/types/SettingTypes.ts b/photon-client/src/types/SettingTypes.ts index fc41ed1cb2..d8f1702d4a 100644 --- a/photon-client/src/types/SettingTypes.ts +++ b/photon-client/src/types/SettingTypes.ts @@ -37,7 +37,7 @@ export interface MetricData { gpuMemUtil?: number; diskUtilPct?: number; diskUsableSpace?: number; - npuUsage?: number[]; + npuUsage?: Map; ipAddress?: string; uptime?: number; sentBitRate?: number; diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DeviceMetrics.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DeviceMetrics.java index e8789154c8..bcef0d0404 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DeviceMetrics.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/DeviceMetrics.java @@ -18,6 +18,7 @@ package org.photonvision.common.hardware.metrics; import io.avaje.jsonb.Json; +import java.util.Map; import org.photonvision.common.hardware.metrics.proto.DeviceMetricsProto; @Json @@ -31,7 +32,7 @@ public record DeviceMetrics( double gpuMemUtil, double diskUtilPct, double diskUsableSpace, - double[] npuUsage, + Map npuUsage, String ipAddress, double uptime, double sentBitRate, diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitor.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitor.java index 79b3a702e5..43cb23c07d 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitor.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitor.java @@ -22,6 +22,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import java.util.function.Supplier; import java.util.stream.Collectors; import org.photonvision.common.configuration.ConfigManager; @@ -83,7 +85,7 @@ private record NetworkTraffic(double sentBitRate, double recvBitRate) {} // Set this to true to enable logging the contents of the DeviceMetrics class that is sent to NT // and the UI. - public boolean writeMetricsToLog = false; + public boolean writeMetricsToLog = true; private final String taskName = "SystemMonitorPublisher"; private final double minimumDeltaTime = 0.250; // seconds @@ -223,7 +225,9 @@ private void logMetrics(DeviceMetrics metrics) { sb.append(String.format("System Uptime: %.0f, ", metrics.uptime())); sb.append(String.format("CPU Usage: %.2f%%, ", metrics.cpuUtil())); sb.append(String.format("CPU Temperature: %.2f °C, ", metrics.cpuTemp())); - sb.append(String.format("NPU Usage: %s, ", Arrays.toString(metrics.npuUsage()))); + if (!metrics.npuUsage().isEmpty()) { + sb.append(String.format("NPU Usage: %s, ", metrics.npuUsage().toString())); + } sb.append(String.format("Used Disk: %.2f%%, ", metrics.diskUtilPct())); sb.append(String.format("Usable Disk Space: %.0f MiB, ", metrics.diskUsableSpace() / mebi)); sb.append(String.format("Memory: %.0f / %.0f MiB, ", metrics.ramUtil(), metrics.ramMem())); @@ -430,10 +434,10 @@ public synchronized double getCpuUsage() { * Returns the npu usage, if available. Platforms with NPUs will need to override this method to * return a useful value. * - * @return the NPU usage or an empty array if not available. + * @return the NPU usage or an empty map if not available. */ - public double[] getNpuUsage() { - return new double[0]; + public Map getNpuUsage() { + return new HashMap<>(); } /** @@ -518,7 +522,7 @@ private void testSM() { total += timeIt(sb, () -> String.format("System Uptime: %d", getUptime())); total += timeIt(sb, () -> String.format("CPU Usage: %.2f%%", getCpuUsage())); total += timeIt(sb, () -> String.format("CPU Temperature: %.2f °C", getCpuTemperature())); - total += timeIt(sb, () -> String.format("NPU Usage: %s", Arrays.toString(getNpuUsage()))); + total += timeIt(sb, () -> String.format("NPU Usage: %s", getNpuUsage().toString())); total += timeIt(sb, () -> String.format("Used Disk: %.2f%%", getUsedDiskPct())); total += timeIt( diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorQCS6490.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorQCS6490.java index 658a558d1f..2fbe356af6 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorQCS6490.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorQCS6490.java @@ -17,9 +17,36 @@ package org.photonvision.common.hardware.metrics; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + public class SystemMonitorQCS6490 extends SystemMonitor { @Override protected String getThermalZoneTypes() { return "cpu0-thermal"; } + + @Override + public Map getNpuUsage() { + try { + var contents = Files.readString(Path.of("/tmp/qcnpuperf_stats")); + Map map = new HashMap<>(); + Arrays.stream(contents.split("\n")) + .filter(line -> !line.trim().isEmpty()) + .forEach( + line -> { + String[] parts = line.split("="); + if (parts.length == 2) { + map.put(parts[0].trim(), Double.parseDouble(parts[1].trim())); + } + }); + return map; + } catch (IOException e) { + return new HashMap<>(); + } + } } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorRK3588.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorRK3588.java index ee58fa68c6..903ca72054 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorRK3588.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorRK3588.java @@ -20,6 +20,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -51,15 +53,19 @@ protected String getThermalZoneTypes() { } @Override - public double[] getNpuUsage() { + public Map getNpuUsage() { try { var contents = Files.readString(Path.of("/sys/kernel/debug/rknpu/load")); Matcher matcher = pattern.matcher(contents); double[] results = matcher.results().map(mr -> mr.group(1)).mapToDouble(Double::parseDouble).toArray(); - return results; + Map map = new HashMap<>(); + for (int i = 0; i < results.length; i++) { + map.put("core " + i, results[i]); + } + return map; } catch (IOException e) { - return new double[0]; + return new HashMap<>(); } } } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/proto/DeviceMetricsProto.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/proto/DeviceMetricsProto.java index 33947b8dfe..693dfe6a82 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/proto/DeviceMetricsProto.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/proto/DeviceMetricsProto.java @@ -17,6 +17,8 @@ package org.photonvision.common.hardware.metrics.proto; +import java.util.HashMap; +import java.util.Map; import org.photonvision.common.hardware.metrics.DeviceMetrics; import org.photonvision.proto.Photon.ProtobufDeviceMetrics; import org.wpilib.util.protobuf.Protobuf; @@ -50,7 +52,7 @@ public DeviceMetrics unpack(ProtobufDeviceMetrics msg) { msg.getGpuMemUtil(), msg.getDiskUtilPct(), msg.getDiskUsableSpace(), - msg.getNpuUsage().toArray(), + convertNpuUsage(msg.getNpuUsage()), msg.getIpAddress(), msg.getUptime(), msg.getSentBitRate(), @@ -69,9 +71,23 @@ public void pack(ProtobufDeviceMetrics msg, DeviceMetrics value) { msg.setGpuMemUtil(value.gpuMemUtil()); msg.setDiskUtilPct(value.diskUtilPct()); msg.setDiskUsableSpace(value.diskUsableSpace()); - msg.addAllNpuUsage(value.npuUsage()); + for (var entry : value.npuUsage().entrySet()) { + msg.addNpuUsage( + ProtobufDeviceMetrics.NpuUsageEntry.newInstance() + .setKey(entry.getKey()) + .setValue(entry.getValue())); + } msg.setIpAddress(value.ipAddress()); msg.setSentBitRate(value.sentBitRate()); msg.setRecvBitRate(value.recvBitRate()); } + + private static Map convertNpuUsage( + us.hebi.quickbuf.RepeatedMessage entries) { + var map = new HashMap(); + for (var entry : entries) { + map.put(entry.getKey(), entry.getValue()); + } + return map; + } } diff --git a/photon-targeting/src/main/proto/photon.proto b/photon-targeting/src/main/proto/photon.proto index dea75aab07..d097563154 100644 --- a/photon-targeting/src/main/proto/photon.proto +++ b/photon-targeting/src/main/proto/photon.proto @@ -78,7 +78,7 @@ message ProtobufDeviceMetrics { double gpu_mem = 6; double gpu_mem_util = 7; double disk_util_pct = 8; - repeated double npu_usage = 9; + map npu_usage = 9; string ip_address = 10; double uptime = 11; double sent_bit_rate = 12;