mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-19 00:41:41 +00:00
Rewrite system monitoring to use OSHI (#2255)
## Description [OSHI](https://github.com/oshi/oshi) is a free (MIT license) JNA-based library for accessing hardware and system performance information. This PR includes a re-write of the metrics monitoring code to be based on OSHI. The original intent was to gain access to data about network traffic for addition to the Settings tab. An additional benefit is that collecting the data is now around two orders of magnitude (or more) faster! ## Meta Merge checklist: - [x] Pull Request title is [short, imperative summary](https://cbea.ms/git-commit/) of proposed changes - [x] The description documents the _what_ and _why_ - [ ] If this PR changes behavior or adds a feature, user documentation is updated - [ ] If this PR touches photon-serde, all messages have been regenerated and hashes have not changed unexpectedly - [x] If this PR touches configuration, this is backwards compatible with settings back to v2025.3.2 - [x] If this PR touches pipeline settings or anything related to data exchange, the frontend typing is updated - [ ] If this PR addresses a bug, a regression test for it is added --------- Co-authored-by: samfreund <samf.236@proton.me>
This commit is contained in:
@@ -72,6 +72,8 @@ PhotonVision was forked from [Chameleon Vision](https://github.com/Chameleon-Vis
|
||||
|
||||
* [FasterXML](https://github.com/FasterXML) - Specifically [jackson](https://github.com/FasterXML/jackson)
|
||||
|
||||
* [OSHI](https://github.com/oshi/oshi)
|
||||
|
||||
## License
|
||||
|
||||
PhotonVision is licensed under the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
import { computed, onBeforeMount, ref } from "vue";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import { computed } from "vue";
|
||||
|
||||
interface MetricItem {
|
||||
header: string;
|
||||
@@ -30,17 +29,17 @@ const platformMetrics = computed<MetricItem[]>(() => {
|
||||
const stats = [
|
||||
{
|
||||
header: "CPU Temp",
|
||||
value: metrics.cpuTemp === undefined || metrics.cpuTemp == -1 ? "Unknown" : `${metrics.cpuTemp}°C`
|
||||
value: metrics.cpuTemp === undefined || metrics.cpuTemp == -1 ? "Unknown" : `${metrics.cpuTemp.toFixed(1)}°C`
|
||||
},
|
||||
{
|
||||
header: "CPU Usage",
|
||||
value: metrics.cpuUtil === undefined ? "Unknown" : `${metrics.cpuUtil}%`
|
||||
value: metrics.cpuUtil === undefined ? "Unknown" : `${metrics.cpuUtil.toFixed(1)}%`
|
||||
},
|
||||
{
|
||||
header: "CPU Memory Usage",
|
||||
value:
|
||||
metrics.ramUtil && metrics.ramMem && metrics.ramUtil >= 0 && metrics.ramMem >= 0
|
||||
? `${metrics.ramUtil}MB of ${metrics.ramMem}MB`
|
||||
? `${metrics.ramUtil} of ${metrics.ramMem} MiB`
|
||||
: "Unknown"
|
||||
},
|
||||
{
|
||||
@@ -64,7 +63,14 @@ const platformMetrics = computed<MetricItem[]>(() => {
|
||||
},
|
||||
{
|
||||
header: "Disk Usage",
|
||||
value: metrics.diskUtilPct === undefined ? "Unknown" : `${metrics.diskUtilPct}%`
|
||||
value: metrics.diskUtilPct === undefined ? "Unknown" : `${metrics.diskUtilPct.toFixed(1)}%`
|
||||
},
|
||||
{
|
||||
header: "Network Traffic",
|
||||
value:
|
||||
metrics.sentBitRate === undefined || metrics.recvBitRate === undefined
|
||||
? "Missing"
|
||||
: `↑${(metrics.sentBitRate / 1e6).toFixed(3).padStart(7, "\u00A0")} Mbps | ↓${(metrics.recvBitRate / 1e6).toFixed(3).padStart(7, "\u00A0")} Mbps`
|
||||
}
|
||||
];
|
||||
|
||||
@@ -78,7 +84,7 @@ const platformMetrics = computed<MetricItem[]>(() => {
|
||||
if (metrics.gpuMem && metrics.gpuMemUtil && metrics.gpuMem > 0 && metrics.gpuMemUtil > 0) {
|
||||
stats.push({
|
||||
header: "GPU Memory Usage",
|
||||
value: `${metrics.gpuMemUtil}MB of ${metrics.gpuMem}MB`
|
||||
value: `${metrics.gpuMemUtil} of ${metrics.gpuMem} MiB`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,35 +98,8 @@ const platformMetrics = computed<MetricItem[]>(() => {
|
||||
return stats;
|
||||
});
|
||||
|
||||
const metricsLastFetched = ref("Never");
|
||||
const fetchMetrics = () => {
|
||||
useSettingsStore()
|
||||
.requestMetricsUpdate()
|
||||
.catch((error) => {
|
||||
if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Unable to fetch metrics! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to fetch metrics."
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
const pad = (num: number): string => {
|
||||
return String(num).padStart(2, "0");
|
||||
};
|
||||
|
||||
const date = new Date();
|
||||
metricsLastFetched.value = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
fetchMetrics();
|
||||
const formattedDate = new Intl.DateTimeFormat(undefined, {
|
||||
timeStyle: "medium"
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -128,10 +107,9 @@ onBeforeMount(() => {
|
||||
<v-card class="mb-3 rounded-12" color="surface">
|
||||
<v-card-title style="display: flex; justify-content: space-between">
|
||||
<span>Metrics</span>
|
||||
<v-btn variant="text" @click="fetchMetrics">
|
||||
<v-icon start class="open-icon" size="large">mdi-reload</v-icon>
|
||||
Last Fetched: {{ metricsLastFetched }}
|
||||
</v-btn>
|
||||
<span class="metrics-update-time">
|
||||
Last Update: <span>{{ formattedDate.format(useSettingsStore().lastMetricsUpdate) }}</span>
|
||||
</span>
|
||||
</v-card-title>
|
||||
<v-card-text class="pt-0 pb-3">
|
||||
<v-card-subtitle class="pa-0" style="font-size: 16px">General</v-card-subtitle>
|
||||
@@ -215,6 +193,12 @@ onBeforeMount(() => {
|
||||
.metrics-table {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-family: monospace !important;
|
||||
}
|
||||
|
||||
.metrics-update-time {
|
||||
font-family: monospace !important;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
$stats-table-border: rgba(255, 255, 255, 0.5);
|
||||
|
||||
@@ -16,6 +16,7 @@ interface GeneralSettingsStore {
|
||||
network: NetworkSettings;
|
||||
lighting: LightingSettings;
|
||||
metrics: MetricData;
|
||||
lastMetricsUpdate: Date;
|
||||
currentFieldLayout;
|
||||
}
|
||||
|
||||
@@ -62,9 +63,12 @@ export const useSettingsStore = defineStore("settings", {
|
||||
gpuMem: undefined,
|
||||
gpuMemUtil: undefined,
|
||||
diskUtilPct: undefined,
|
||||
diskUsableSpace: undefined,
|
||||
npuUsage: undefined,
|
||||
ipAddress: undefined,
|
||||
uptime: undefined
|
||||
uptime: undefined,
|
||||
sentBitRate: undefined,
|
||||
recvBitRate: undefined
|
||||
},
|
||||
currentFieldLayout: {
|
||||
field: {
|
||||
@@ -72,7 +76,8 @@ export const useSettingsStore = defineStore("settings", {
|
||||
width: 8.2296
|
||||
},
|
||||
tags: []
|
||||
}
|
||||
},
|
||||
lastMetricsUpdate: new Date()
|
||||
}),
|
||||
getters: {
|
||||
gpuAccelerationEnabled(): boolean {
|
||||
@@ -83,10 +88,8 @@ export const useSettingsStore = defineStore("settings", {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
requestMetricsUpdate() {
|
||||
return axios.post("/utils/publishMetrics");
|
||||
},
|
||||
updateMetricsFromWebsocket(data: Required<MetricData>) {
|
||||
this.lastMetricsUpdate = new Date();
|
||||
this.metrics = {
|
||||
cpuTemp: data.cpuTemp || undefined,
|
||||
cpuUtil: data.cpuUtil || undefined,
|
||||
@@ -96,9 +99,12 @@ export const useSettingsStore = defineStore("settings", {
|
||||
gpuMem: data.gpuMem || undefined,
|
||||
gpuMemUtil: data.gpuMemUtil || undefined,
|
||||
diskUtilPct: data.diskUtilPct || undefined,
|
||||
diskUsableSpace: data.diskUsableSpace || undefined,
|
||||
npuUsage: data.npuUsage || undefined,
|
||||
ipAddress: data.ipAddress || undefined,
|
||||
uptime: data.uptime || undefined
|
||||
uptime: data.uptime || undefined,
|
||||
sentBitRate: data.sentBitRate || undefined,
|
||||
recvBitRate: data.recvBitRate || undefined
|
||||
};
|
||||
},
|
||||
updateGeneralSettingsFromWebsocket(data: WebsocketSettingsUpdate) {
|
||||
|
||||
@@ -34,9 +34,12 @@ export interface MetricData {
|
||||
gpuMem?: number;
|
||||
gpuMemUtil?: number;
|
||||
diskUtilPct?: number;
|
||||
diskUsableSpace?: number;
|
||||
npuUsage?: number[];
|
||||
ipAddress?: string;
|
||||
uptime?: number;
|
||||
sentBitRate?: number;
|
||||
recvBitRate?: number;
|
||||
}
|
||||
|
||||
export enum NetworkConnectionType {
|
||||
|
||||
@@ -32,6 +32,7 @@ dependencies {
|
||||
implementation 'org.zeroturnaround:zt-zip:1.14'
|
||||
implementation "org.xerial:sqlite-jdbc:3.41.0.0"
|
||||
implementation 'com.diozero:diozero-core:1.4.1'
|
||||
implementation 'com.github.oshi:oshi-core:6.9.1'
|
||||
|
||||
// The JNI libraries use wpilibNatives, the java libraries use implementation
|
||||
if (jniPlatform == "linuxarm64") {
|
||||
|
||||
@@ -41,17 +41,6 @@ public class HardwareConfig {
|
||||
public final String setPWMFrequencyCommand;
|
||||
public final String releaseGPIOCommand;
|
||||
|
||||
// Metrics
|
||||
public final String cpuTempCommand;
|
||||
public final String cpuMemoryCommand;
|
||||
public final String cpuUtilCommand;
|
||||
public final String cpuThrottleReasonCmd;
|
||||
public final String cpuUptimeCommand;
|
||||
public final String gpuMemoryCommand;
|
||||
public final String ramUtilCommand;
|
||||
public final String gpuMemUsageCommand;
|
||||
public final String diskUsageCommand;
|
||||
|
||||
// Device stuff
|
||||
public final String restartHardwareCommand;
|
||||
public final double vendorFOV; // -1 for unmanaged
|
||||
@@ -71,15 +60,6 @@ public class HardwareConfig {
|
||||
String setPWMCommand,
|
||||
String setPWMFrequencyCommand,
|
||||
String releaseGPIOCommand,
|
||||
String cpuTempCommand,
|
||||
String cpuMemoryCommand,
|
||||
String cpuUtilCommand,
|
||||
String cpuThrottleReasonCmd,
|
||||
String cpuUptimeCommand,
|
||||
String gpuMemoryCommand,
|
||||
String ramUtilCommand,
|
||||
String gpuMemUsageCommand,
|
||||
String diskUsageCommand,
|
||||
String restartHardwareCommand,
|
||||
double vendorFOV) {
|
||||
this.deviceName = deviceName;
|
||||
@@ -96,15 +76,6 @@ public class HardwareConfig {
|
||||
this.setPWMCommand = setPWMCommand;
|
||||
this.setPWMFrequencyCommand = setPWMFrequencyCommand;
|
||||
this.releaseGPIOCommand = releaseGPIOCommand;
|
||||
this.cpuTempCommand = cpuTempCommand;
|
||||
this.cpuMemoryCommand = cpuMemoryCommand;
|
||||
this.cpuUtilCommand = cpuUtilCommand;
|
||||
this.cpuThrottleReasonCmd = cpuThrottleReasonCmd;
|
||||
this.cpuUptimeCommand = cpuUptimeCommand;
|
||||
this.gpuMemoryCommand = gpuMemoryCommand;
|
||||
this.ramUtilCommand = ramUtilCommand;
|
||||
this.gpuMemUsageCommand = gpuMemUsageCommand;
|
||||
this.diskUsageCommand = diskUsageCommand;
|
||||
this.restartHardwareCommand = restartHardwareCommand;
|
||||
this.vendorFOV = vendorFOV;
|
||||
}
|
||||
@@ -124,15 +95,6 @@ public class HardwareConfig {
|
||||
setPWMCommand = "";
|
||||
setPWMFrequencyCommand = "";
|
||||
releaseGPIOCommand = "";
|
||||
cpuTempCommand = "";
|
||||
cpuMemoryCommand = "";
|
||||
cpuUtilCommand = "";
|
||||
cpuThrottleReasonCmd = "";
|
||||
cpuUptimeCommand = "";
|
||||
gpuMemoryCommand = "";
|
||||
ramUtilCommand = "";
|
||||
gpuMemUsageCommand = "";
|
||||
diskUsageCommand = "";
|
||||
restartHardwareCommand = "";
|
||||
vendorFOV = -1;
|
||||
}
|
||||
@@ -144,21 +106,6 @@ public class HardwareConfig {
|
||||
return vendorFOV > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if any info command has been configured to be non-empty, false otherwise
|
||||
*/
|
||||
public final boolean hasCommandsConfigured() {
|
||||
return cpuTempCommand != ""
|
||||
|| cpuMemoryCommand != ""
|
||||
|| cpuUtilCommand != ""
|
||||
|| cpuThrottleReasonCmd != ""
|
||||
|| cpuUptimeCommand != ""
|
||||
|| gpuMemoryCommand != ""
|
||||
|| ramUtilCommand != ""
|
||||
|| gpuMemUsageCommand != ""
|
||||
|| diskUsageCommand != "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if any gpio command has been configured to be non-empty, false otherwise
|
||||
*/
|
||||
@@ -200,24 +147,6 @@ public class HardwareConfig {
|
||||
+ setPWMFrequencyCommand
|
||||
+ ", releaseGPIOCommand="
|
||||
+ releaseGPIOCommand
|
||||
+ ", cpuTempCommand="
|
||||
+ cpuTempCommand
|
||||
+ ", cpuMemoryCommand="
|
||||
+ cpuMemoryCommand
|
||||
+ ", cpuUtilCommand="
|
||||
+ cpuUtilCommand
|
||||
+ ", cpuThrottleReasonCmd="
|
||||
+ cpuThrottleReasonCmd
|
||||
+ ", cpuUptimeCommand="
|
||||
+ cpuUptimeCommand
|
||||
+ ", gpuMemoryCommand="
|
||||
+ gpuMemoryCommand
|
||||
+ ", ramUtilCommand="
|
||||
+ ramUtilCommand
|
||||
+ ", gpuMemUsageCommand="
|
||||
+ gpuMemUsageCommand
|
||||
+ ", diskUsageCommand="
|
||||
+ diskUsageCommand
|
||||
+ ", restartHardwareCommand="
|
||||
+ restartHardwareCommand
|
||||
+ ", vendorFOV="
|
||||
|
||||
@@ -35,11 +35,9 @@ import org.photonvision.common.dataflow.networktables.NTDataChangeListener;
|
||||
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
|
||||
import org.photonvision.common.hardware.gpio.CustomAdapter;
|
||||
import org.photonvision.common.hardware.gpio.CustomDeviceFactory;
|
||||
import org.photonvision.common.hardware.metrics.MetricsManager;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.ShellExec;
|
||||
import org.photonvision.common.util.TimedTaskManager;
|
||||
|
||||
public class HardwareManager {
|
||||
private static HardwareManager instance;
|
||||
@@ -50,8 +48,6 @@ public class HardwareManager {
|
||||
private final HardwareConfig hardwareConfig;
|
||||
private final HardwareSettings hardwareSettings;
|
||||
|
||||
private final MetricsManager metricsManager;
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private final StatusLED statusLED;
|
||||
|
||||
@@ -77,12 +73,6 @@ public class HardwareManager {
|
||||
this.hardwareConfig = hardwareConfig;
|
||||
this.hardwareSettings = hardwareSettings;
|
||||
|
||||
this.metricsManager = new MetricsManager();
|
||||
this.metricsManager.setConfig(hardwareConfig);
|
||||
|
||||
TimedTaskManager.getInstance()
|
||||
.addTask("Metrics Publisher", this.metricsManager::publishMetrics, 5000);
|
||||
|
||||
ledModeRequest =
|
||||
NetworkTablesManager.getInstance()
|
||||
.kRootTable
|
||||
@@ -259,8 +249,4 @@ public class HardwareManager {
|
||||
}
|
||||
statusLED.setStatus(status);
|
||||
}
|
||||
|
||||
public void publishMetrics() {
|
||||
metricsManager.publishMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,11 @@ public record DeviceMetrics(
|
||||
double gpuMem,
|
||||
double gpuMemUtil,
|
||||
double diskUtilPct,
|
||||
double diskUsableSpace,
|
||||
double[] npuUsage,
|
||||
String ipAddress,
|
||||
double uptime) {
|
||||
double uptime,
|
||||
double sentBitRate,
|
||||
double recvBitRate) {
|
||||
public static final DeviceMetricsProto proto = new DeviceMetricsProto();
|
||||
}
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
import edu.wpi.first.cscore.CameraServerJNI;
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.ProtobufPublisher;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import org.photonvision.common.configuration.ConfigManager;
|
||||
import org.photonvision.common.configuration.HardwareConfig;
|
||||
import org.photonvision.common.dataflow.DataChangeService;
|
||||
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
|
||||
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.hardware.metrics.cmds.CmdBase;
|
||||
import org.photonvision.common.hardware.metrics.cmds.FileCmds;
|
||||
import org.photonvision.common.hardware.metrics.cmds.LinuxCmds;
|
||||
import org.photonvision.common.hardware.metrics.cmds.PiCmds;
|
||||
import org.photonvision.common.hardware.metrics.cmds.QCS6490Cmds;
|
||||
import org.photonvision.common.hardware.metrics.cmds.RK3588Cmds;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.networking.NetworkUtils;
|
||||
import org.photonvision.common.util.ShellExec;
|
||||
|
||||
public class MetricsManager {
|
||||
final Logger logger = new Logger(MetricsManager.class, LogGroup.General);
|
||||
|
||||
CmdBase cmds;
|
||||
|
||||
ProtobufPublisher<DeviceMetrics> metricPublisher =
|
||||
NetworkTablesManager.getInstance()
|
||||
.kRootTable
|
||||
.getSubTable("/metrics")
|
||||
.getProtobufTopic(CameraServerJNI.getHostname(), DeviceMetrics.proto)
|
||||
.publish();
|
||||
|
||||
private final ShellExec runCommand = new ShellExec(true, true);
|
||||
|
||||
public void setConfig(HardwareConfig config) {
|
||||
if (config.hasCommandsConfigured()) {
|
||||
cmds = new FileCmds();
|
||||
} else if (Platform.isRaspberryPi()) {
|
||||
cmds = new PiCmds(); // Pi's can use a hardcoded command set
|
||||
} else if (Platform.isRK3588()) {
|
||||
cmds = new RK3588Cmds(); // RK3588 chipset hardcoded command set
|
||||
} else if (Platform.isQCS6490()) {
|
||||
cmds = new QCS6490Cmds(); // QCS6490 chipset hardcoded command set
|
||||
} else if (Platform.isLinux()) {
|
||||
cmds = new LinuxCmds(); // Linux/Unix platforms assume a nominal command set
|
||||
} else {
|
||||
cmds = new CmdBase(); // default - base has no commands
|
||||
}
|
||||
|
||||
cmds.initCmds(config);
|
||||
}
|
||||
|
||||
public String safeExecute(String str) {
|
||||
if (str.isEmpty()) return "";
|
||||
try {
|
||||
return execute(str);
|
||||
} catch (Exception e) {
|
||||
return "****";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CPU temperature in Celsius.
|
||||
*
|
||||
* @return The CPU temperature in Celsius, or -1.0 if the command fails or parsing fails.
|
||||
*/
|
||||
public double getCpuTemp() {
|
||||
try {
|
||||
return Double.parseDouble(safeExecute(cmds.cpuTemperatureCommand));
|
||||
} catch (NumberFormatException e) {
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CPU utilization as a percentage.
|
||||
*
|
||||
* @return The CPU utilization as a percentage, or -1.0 if the command fails or parsing fails.
|
||||
*/
|
||||
public double getCpuUtilization() {
|
||||
try {
|
||||
return Double.parseDouble(safeExecute(cmds.cpuUtilizationCommand));
|
||||
} catch (NumberFormatException e) {
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reason for CPU throttling, if applicable.
|
||||
*
|
||||
* @return A string describing the CPU throttle reason, or an empty string if the command fails.
|
||||
*/
|
||||
public String getThrottleReason() {
|
||||
return safeExecute(cmds.cpuThrottleReasonCmd);
|
||||
}
|
||||
|
||||
private double ramMemSave = -2.0;
|
||||
|
||||
/**
|
||||
* Get the total RAM memory in MB. This only runs once, as it won't change over time.
|
||||
*
|
||||
* @return The total RAM memory in MB, or -1.0 if the command fails or parsing fails.
|
||||
*/
|
||||
public double getRamMem() {
|
||||
if (ramMemSave == -2.0) {
|
||||
try {
|
||||
ramMemSave = Double.parseDouble(safeExecute(cmds.ramMemCommand));
|
||||
} catch (NumberFormatException e) {
|
||||
ramMemSave = -1.0;
|
||||
}
|
||||
}
|
||||
return ramMemSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RAM utilization in MBs.
|
||||
*
|
||||
* @return The RAM utilization in MBs, or -1.0 if the command fails or parsing fails.
|
||||
*/
|
||||
public double getRamUtil() {
|
||||
try {
|
||||
return Double.parseDouble(safeExecute(cmds.ramUtilCommand));
|
||||
} catch (NumberFormatException e) {
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
private double gpuMemSave = -2.0;
|
||||
|
||||
/**
|
||||
* Get the total GPU memory in MB. This only runs once, as it won't change over time.
|
||||
*
|
||||
* @return The total GPU memory in MB, or -1.0 if the command fails or parsing fails.
|
||||
*/
|
||||
public double getGpuMem() {
|
||||
if (gpuMemSave == -2.0) {
|
||||
try {
|
||||
gpuMemSave = Double.parseDouble(safeExecute(cmds.gpuMemCommand));
|
||||
} catch (NumberFormatException e) {
|
||||
gpuMemSave = -1.0;
|
||||
}
|
||||
}
|
||||
return gpuMemSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GPU memory utilization as MBs.
|
||||
*
|
||||
* @return The GPU memory utilization in MBs, or -1.0 if the command fails or parsing fails.
|
||||
*/
|
||||
public double getGpuMemUtil() {
|
||||
try {
|
||||
return Double.parseDouble(safeExecute(cmds.gpuMemUtilCommand));
|
||||
} catch (NumberFormatException e) {
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the percentage of disk space used.
|
||||
*
|
||||
* @return The percentage of disk space used, or -1.0 if the command fails or parsing fails.
|
||||
*/
|
||||
public double getUsedDiskPct() {
|
||||
try {
|
||||
return Double.parseDouble(safeExecute(cmds.diskUsageCommand));
|
||||
} catch (NumberFormatException e) {
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// This is here so we don't spam logs if it fails
|
||||
boolean npuParseWarning = false;
|
||||
|
||||
/**
|
||||
* Get the NPU usage as an array of doubles.
|
||||
*
|
||||
* @return An array of doubles representing NPU usage, or null if parsing fails.
|
||||
*/
|
||||
public double[] getNpuUsage() {
|
||||
if (cmds.npuUsageCommand.isBlank()) {
|
||||
return new double[0];
|
||||
}
|
||||
String[] usages = safeExecute(cmds.npuUsageCommand).split(",");
|
||||
double[] usageDoubles = new double[usages.length];
|
||||
for (int i = 0; i < usages.length; i++) {
|
||||
try {
|
||||
usageDoubles[i] = Double.parseDouble(usages[i]);
|
||||
npuParseWarning = false; // Reset warning if parsing succeeds
|
||||
} catch (NumberFormatException e) {
|
||||
if (!npuParseWarning) {
|
||||
logger.error("Failed to parse NPU usage value: " + usages[i], e);
|
||||
npuParseWarning = true;
|
||||
}
|
||||
usageDoubles = new double[0]; // Default to empty array if parsing fails
|
||||
break;
|
||||
}
|
||||
}
|
||||
return usageDoubles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IP address of the device.
|
||||
*
|
||||
* @return The IP address as a string, or an empty string if the command fails.
|
||||
*/
|
||||
public String getIpAddress() {
|
||||
String dev = ConfigManager.getInstance().getConfig().getNetworkConfig().networkManagerIface;
|
||||
String addr = NetworkUtils.getIPAddresses(dev);
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the uptime of the device in seconds.
|
||||
*
|
||||
* @return The uptime in seconds, or -1.0 if the command fails or parsing fails.
|
||||
*/
|
||||
public double getUptime() {
|
||||
try {
|
||||
return Double.parseDouble(safeExecute(cmds.uptimeCommand));
|
||||
} catch (NumberFormatException e) {
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
public void publishMetrics() {
|
||||
// Check that the hostname hasn't changed
|
||||
if (!CameraServerJNI.getHostname()
|
||||
.equals(NetworkTable.basenameKey(metricPublisher.getTopic().getName()))) {
|
||||
logger.warn("Metrics publisher name does not match hostname! Reinitializing publisher...");
|
||||
metricPublisher.close();
|
||||
metricPublisher =
|
||||
NetworkTablesManager.getInstance()
|
||||
.kRootTable
|
||||
.getSubTable("/metrics")
|
||||
.getProtobufTopic(CameraServerJNI.getHostname(), DeviceMetrics.proto)
|
||||
.publish();
|
||||
}
|
||||
|
||||
var metrics =
|
||||
new DeviceMetrics(
|
||||
this.getCpuTemp(),
|
||||
this.getCpuUtilization(),
|
||||
this.getThrottleReason(),
|
||||
this.getRamMem(),
|
||||
this.getRamUtil(),
|
||||
this.getGpuMem(),
|
||||
this.getGpuMemUtil(),
|
||||
this.getUsedDiskPct(),
|
||||
this.getNpuUsage(),
|
||||
this.getIpAddress(),
|
||||
this.getUptime());
|
||||
|
||||
metricPublisher.set(metrics);
|
||||
|
||||
DataChangeService.getInstance().publishEvent(OutgoingUIEvent.wrappedOf("metrics", metrics));
|
||||
}
|
||||
|
||||
public synchronized String execute(String command) {
|
||||
try {
|
||||
runCommand.executeBashCommand(command, true, false);
|
||||
return runCommand.getOutput();
|
||||
} catch (Exception e) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
e.printStackTrace(pw);
|
||||
|
||||
logger.error(
|
||||
"Command: \""
|
||||
+ command
|
||||
+ "\" returned an error!"
|
||||
+ "\nOutput Received: "
|
||||
+ runCommand.getOutput()
|
||||
+ "\nStandard Error: "
|
||||
+ runCommand.getError()
|
||||
+ "\nCommand completed: "
|
||||
+ runCommand.isOutputCompleted()
|
||||
+ "\nError completed: "
|
||||
+ runCommand.isErrorCompleted()
|
||||
+ "\nExit code: "
|
||||
+ runCommand.getExitCode()
|
||||
+ "\n Exception: "
|
||||
+ e
|
||||
+ sw);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
import edu.wpi.first.cscore.CameraServerJNI;
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.ProtobufPublisher;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileStore;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import org.photonvision.common.configuration.ConfigManager;
|
||||
import org.photonvision.common.dataflow.DataChangeService;
|
||||
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
|
||||
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.networking.NetworkUtils;
|
||||
import org.photonvision.common.util.TimedTaskManager;
|
||||
import org.photonvision.common.util.file.ProgramDirectoryUtilities;
|
||||
import oshi.SystemInfo;
|
||||
import oshi.hardware.CentralProcessor;
|
||||
import oshi.hardware.CentralProcessor.PhysicalProcessor;
|
||||
import oshi.hardware.GlobalMemory;
|
||||
import oshi.hardware.GraphicsCard;
|
||||
import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.hardware.NetworkIF;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
import oshi.util.FormatUtil;
|
||||
import oshi.util.GlobalConfig;
|
||||
|
||||
public class SystemMonitor {
|
||||
protected static Logger logger = new Logger(SystemMonitor.class, LogGroup.General);
|
||||
|
||||
private static SystemMonitor instance;
|
||||
|
||||
private record NetworkTraffic(double sentBitRate, double recvBitRate) {}
|
||||
|
||||
ProtobufPublisher<DeviceMetrics> metricPublisher =
|
||||
NetworkTablesManager.getInstance()
|
||||
.kRootTable
|
||||
.getSubTable("/metrics")
|
||||
.getProtobufTopic(CameraServerJNI.getHostname(), DeviceMetrics.proto)
|
||||
.publish();
|
||||
|
||||
private SystemInfo si;
|
||||
private CentralProcessor cpu;
|
||||
private OperatingSystem os;
|
||||
private GlobalMemory mem;
|
||||
private HardwareAbstractionLayer hal;
|
||||
private FileStore fs;
|
||||
|
||||
private double totalMemory = -1.0;
|
||||
|
||||
private double lastCpuLoad = 0;
|
||||
private long lastCpuUpdate = 0;
|
||||
private long[] oldTicks;
|
||||
|
||||
private NetworkIF monitoredIFace = null;
|
||||
private long lastTrafficUpdate = 0;
|
||||
private long lastBytesSent = 0;
|
||||
private long lastBytesRecv = 0;
|
||||
private NetworkTraffic lastResult = new NetworkTraffic(0, 0);
|
||||
|
||||
// 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;
|
||||
|
||||
private final String taskName = "SystemMonitorPublisher";
|
||||
private final double minimumDeltaTime = 0.250; // seconds
|
||||
private final long mebi = (1024 * 1024);
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of SystemMonitor. Creates the instance, thereby initializing it,
|
||||
* on the first call.
|
||||
*
|
||||
* @return instance of SystemMonitor.
|
||||
*/
|
||||
public static SystemMonitor getInstance() {
|
||||
if (instance == null) {
|
||||
if (Platform.isRaspberryPi()) {
|
||||
instance = new SystemMonitorRaspberryPi();
|
||||
} else if (Platform.isRK3588()) {
|
||||
instance = new SystemMonitorRK3588();
|
||||
} else if (Platform.isQCS6490()) {
|
||||
instance = new SystemMonitorQCS6490();
|
||||
} else {
|
||||
instance = new SystemMonitor();
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected SystemMonitor() {
|
||||
logger.info("Starting SystemMonitor");
|
||||
GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_LOADAVERAGE, true);
|
||||
GlobalConfig.set("oshi.os.linux.sensors.cpuTemperature.types", getThermalZoneTypes());
|
||||
|
||||
si = new SystemInfo();
|
||||
hal = si.getHardware();
|
||||
os = si.getOperatingSystem();
|
||||
cpu = hal.getProcessor();
|
||||
mem = hal.getMemory();
|
||||
|
||||
try {
|
||||
// get the filesystem for the directory photonvision is running in
|
||||
fs = Files.getFileStore(Path.of(ProgramDirectoryUtilities.getProgramDirectory()));
|
||||
} catch (IOException e) {
|
||||
logger.error("Couldn't get FileStore for " + Path.of(""));
|
||||
fs = null;
|
||||
}
|
||||
|
||||
// initialize CPU monitoring
|
||||
oldTicks = cpu.getSystemCpuLoadTicks();
|
||||
|
||||
// initialize network traffic monitoring
|
||||
selectNetworkIfByName(
|
||||
ConfigManager.getInstance().getConfig().getNetworkConfig().networkManagerIface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma-separated list of addtional thermal zone types that should be checked to get
|
||||
* the CPU temperature on Unix systems. The temperature will be reported for the first temperature
|
||||
* zone with a type that mateches an item of this list. If the CPU temperature isn't being
|
||||
* reported correctly for a coprocessor, override this method to return a string with type
|
||||
* associated with the thermal zone for that comprocessor.
|
||||
*
|
||||
* @return String containing a comma-separated list of thermal zone types for reading CPU
|
||||
* temperature.
|
||||
*/
|
||||
protected String getThermalZoneTypes() {
|
||||
// Find the thermal zone type by logging on to the coprocessor and running:
|
||||
// `cat /sys/class/thermal/thermal_zone*/type`
|
||||
// This command will show the types for all thermal zones.
|
||||
//
|
||||
return GlobalConfig.get("oshi.os.linux.sensors.cpuTemperature.types");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the periodic system monitor that publishes performance metrics. The metrics are
|
||||
* published every millisUpdateInerval seconds after a millisStartDelay startup delay. Calling
|
||||
* this method when the monitor is running will stop it and restart it with the new delay and
|
||||
* update interval.
|
||||
*
|
||||
* @param millisStartDelay the delay before the metrics are first published.
|
||||
* @param millisUpdateInterval the time between updates in units of milliseconds.
|
||||
*/
|
||||
public void startMonitor(long millisStartDelay, long millisUpdateInterval) {
|
||||
if (TimedTaskManager.getInstance().taskActive(taskName)) {
|
||||
logger.debug("Stopping running SystemMonitor!");
|
||||
TimedTaskManager.getInstance().cancelTask(taskName);
|
||||
}
|
||||
logger.debug("Starting SystemMonitor with " + millisUpdateInterval + " ms update interval.");
|
||||
TimedTaskManager.getInstance()
|
||||
.addTask(taskName, this::publishMetrics, millisStartDelay, millisUpdateInterval);
|
||||
}
|
||||
|
||||
private void publishMetrics() {
|
||||
// Check that the hostname hasn't changed
|
||||
if (!CameraServerJNI.getHostname()
|
||||
.equals(NetworkTable.basenameKey(metricPublisher.getTopic().getName()))) {
|
||||
logger.warn("Metrics publisher name does not match hostname! Reinitializing publisher...");
|
||||
metricPublisher.close();
|
||||
metricPublisher =
|
||||
NetworkTablesManager.getInstance()
|
||||
.kRootTable
|
||||
.getSubTable("/metrics")
|
||||
.getProtobufTopic(CameraServerJNI.getHostname(), DeviceMetrics.proto)
|
||||
.publish();
|
||||
}
|
||||
|
||||
var nt = this.getNetworkTraffic();
|
||||
var metrics =
|
||||
new DeviceMetrics(
|
||||
this.getCpuTemperature(),
|
||||
this.getCpuUsage(),
|
||||
this.getCpuThrottleReason(),
|
||||
this.getTotalMemory(),
|
||||
this.getUsedMemory(),
|
||||
this.getGpuMem(),
|
||||
this.getGpuMemUtil(),
|
||||
this.getUsedDiskPct(),
|
||||
this.getUsableDiskSpace(),
|
||||
this.getNpuUsage(),
|
||||
this.getIpAddress(),
|
||||
this.getUptime(),
|
||||
nt.sentBitRate,
|
||||
nt.recvBitRate);
|
||||
|
||||
metricPublisher.set(metrics);
|
||||
|
||||
if (writeMetricsToLog) {
|
||||
logMetrics(metrics);
|
||||
}
|
||||
|
||||
DataChangeService.getInstance().publishEvent(OutgoingUIEvent.wrappedOf("metrics", metrics));
|
||||
}
|
||||
|
||||
private void logMetrics(DeviceMetrics metrics) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("System Metrics Update: ");
|
||||
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())));
|
||||
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()));
|
||||
sb.append(
|
||||
String.format("GPU Memory: %.0f / %.0f MiB, ", metrics.gpuMemUtil(), metrics.gpuMem()));
|
||||
sb.append(
|
||||
String.format("CPU Throttle: %s, ", metrics.cpuThr().isBlank() ? "N/A" : metrics.cpuThr()));
|
||||
sb.append(
|
||||
String.format(
|
||||
"Data sent: %.0f Kbps, Data recieved: %.0f Kbps",
|
||||
metrics.sentBitRate() / 1000, metrics.recvBitRate() / 1000));
|
||||
logger.debug(sb.toString());
|
||||
}
|
||||
|
||||
private void resetNetworkTraffic() {
|
||||
lastBytesSent = monitoredIFace.getBytesSent();
|
||||
lastBytesRecv = monitoredIFace.getBytesRecv();
|
||||
lastTrafficUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private NetworkIF selectNetworkIfByName(String name) {
|
||||
if (name.isBlank() || monitoredIFace != null && monitoredIFace.getName().equals(name)) {
|
||||
return monitoredIFace;
|
||||
}
|
||||
for (var iFace : hal.getNetworkIFs()) {
|
||||
if (iFace.getName().equals(name)) {
|
||||
logger.debug("Monitoring network traffic on '" + name + "'");
|
||||
monitoredIFace = iFace;
|
||||
resetNetworkTraffic();
|
||||
return iFace;
|
||||
}
|
||||
}
|
||||
logger.warn("Can't monitor network interface '" + name + "'");
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Writes available information about the hardware to the log. */
|
||||
public void logSystemInformation() {
|
||||
var sb = new StringBuilder();
|
||||
sb.append("*** System Information ***\n");
|
||||
sb.append("Operating System: " + os.toString() + "\n");
|
||||
sb.append(" System Uptime: " + FormatUtil.formatElapsedSecs(getUptime()) + "\n");
|
||||
sb.append(" Elevated Privileges: " + os.isElevated() + "\n");
|
||||
|
||||
var computerSystem = hal.getComputerSystem();
|
||||
sb.append("System: " + computerSystem.toString() + "\n");
|
||||
sb.append(" Manufacturer: " + computerSystem.getManufacturer() + "\n");
|
||||
sb.append(" Firmware: " + computerSystem.getFirmware() + "\n");
|
||||
sb.append(" Baseboard: " + computerSystem.getBaseboard() + "\n");
|
||||
sb.append(" Model: " + computerSystem.getModel() + "\n");
|
||||
sb.append(" Serial Number: " + computerSystem.getSerialNumber() + "\n");
|
||||
|
||||
sb.append("CPU Info: " + cpu.toString() + "\n");
|
||||
sb.append(" Max Frequency: " + FormatUtil.formatHertz(cpu.getMaxFreq()) + "\n");
|
||||
sb.append(
|
||||
" Current Frequency: "
|
||||
+ Arrays.stream(cpu.getCurrentFreq())
|
||||
.mapToObj(FormatUtil::formatHertz)
|
||||
.collect(Collectors.joining(", "))
|
||||
+ "\n");
|
||||
for (PhysicalProcessor core : cpu.getPhysicalProcessors()) {
|
||||
sb.append(
|
||||
" Core " + core.getPhysicalProcessorNumber() + " (" + core.getEfficiency() + ")\n");
|
||||
}
|
||||
var myProc = os.getCurrentProcess();
|
||||
sb.append("Current Process: " + myProc.getName() + ", PID: " + myProc.getProcessID() + "\n");
|
||||
// sb.append(" Command Line: " + myProc.getCommandLine());
|
||||
sb.append(" Kernel Time: " + myProc.getKernelTime() + "\n");
|
||||
sb.append(" User Time: " + myProc.getUserTime() + "\n");
|
||||
sb.append(" Cumulative Load: " + myProc.getProcessCpuLoadCumulative() + "\n");
|
||||
sb.append(" Up Time: " + myProc.getUpTime() + "\n");
|
||||
sb.append(" Priority: " + myProc.getPriority() + "\n");
|
||||
sb.append(" User: " + myProc.getUser() + "\n");
|
||||
sb.append(" Threads: " + myProc.getThreadCount() + "\n");
|
||||
|
||||
sb.append("Network Interfaces\n");
|
||||
for (NetworkIF iFace : hal.getNetworkIFs()) {
|
||||
sb.append(" Interface: " + iFace.toString() + "\n");
|
||||
}
|
||||
|
||||
sb.append("Graphics Cards\n");
|
||||
for (GraphicsCard gc : hal.getGraphicsCards()) {
|
||||
sb.append(" Card: " + gc.toString() + "\n");
|
||||
}
|
||||
logger.info(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total space available (in bytes) for the filesystem where PhotonVision is running
|
||||
* (typically "/"). This doesn't report on other mounted filesystems, such as USB sticks.
|
||||
*
|
||||
* @return the number of bytes total, or -1 if the command fails.
|
||||
*/
|
||||
public long getTotalDiskSpace() {
|
||||
if (fs != null) {
|
||||
try {
|
||||
return fs.getTotalSpace();
|
||||
} catch (IOException e) {
|
||||
logger.error("Couldn't retrieve total disk space", e);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the free space available (in bytes) for the filesystem where PhotonVision is running
|
||||
* (typically "/"). This doesn't report on other mounted filesystems, such as USB sticks.
|
||||
*
|
||||
* @return the number of bytes available, or -1 if the command fails.
|
||||
*/
|
||||
public long getUsableDiskSpace() {
|
||||
if (fs != null) {
|
||||
try {
|
||||
return fs.getUsableSpace();
|
||||
} catch (IOException e) {
|
||||
logger.error("Couldn't retrieve usable disk space", e);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the percentage of disk space used.
|
||||
*
|
||||
* @return The percentage of disk space used, or -1.0 if the command fails.
|
||||
*/
|
||||
public double getUsedDiskPct() {
|
||||
double usedPct;
|
||||
if (fs == null) return -1.0;
|
||||
try {
|
||||
double total = fs.getTotalSpace();
|
||||
// note: df matches better with fs.getUnallocatedSpace(), but this is more conservative
|
||||
usedPct = 100.0 * (1.0 - fs.getUsableSpace() / total);
|
||||
} catch (IOException e) {
|
||||
logger.error("Couldn't retrieve used disk space", e);
|
||||
usedPct = -1.0;
|
||||
}
|
||||
return usedPct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the temperature of the CPU.
|
||||
*
|
||||
* @return The temperature of the CPU in °C or -1.0 if it cannot be retrieved.
|
||||
*/
|
||||
public double getCpuTemperature() {
|
||||
double temperature = hal.getSensors().getCpuTemperature();
|
||||
// OSHI returns 0 or NaN if the temperature isn't available.
|
||||
if (temperature == 0.0 || Double.isNaN(temperature)) {
|
||||
temperature = -1.0;
|
||||
}
|
||||
return temperature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total RAM.
|
||||
*
|
||||
* @return total RAM in MiB.
|
||||
*/
|
||||
public double getTotalMemory() {
|
||||
if (totalMemory < 0) {
|
||||
totalMemory = mem.getTotal() / mebi;
|
||||
}
|
||||
return totalMemory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of memory in use.
|
||||
*
|
||||
* @return the used RAM in MiB.
|
||||
*/
|
||||
public double getUsedMemory() {
|
||||
return (mem.getTotal() - mem.getAvailable()) / mebi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time since system boot in seconds.
|
||||
*
|
||||
* @return the uptime in seconds.
|
||||
*/
|
||||
public long getUptime() {
|
||||
return os.getSystemUptime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the average load on the CPU from 0 to 100% since last called by using the tick
|
||||
* counters.
|
||||
*
|
||||
* @return load on the cpu in %.
|
||||
*/
|
||||
public synchronized double getCpuUsage() {
|
||||
long now = System.currentTimeMillis();
|
||||
double dTime = (now - lastCpuUpdate) / 1000.0;
|
||||
if (dTime > minimumDeltaTime) {
|
||||
var newTicks = cpu.getSystemCpuLoadTicks();
|
||||
lastCpuLoad = 100 * cpu.getSystemCpuLoadBetweenTicks(oldTicks, newTicks);
|
||||
oldTicks = newTicks;
|
||||
lastCpuUpdate = now;
|
||||
}
|
||||
return lastCpuLoad;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public double[] getNpuUsage() {
|
||||
return new double[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of the CPU throttle state, if available. Platforms that provide this
|
||||
* information will need to override this method to return a useful value.
|
||||
*
|
||||
* @return the CPU throttle state, or an empty String if not available.
|
||||
*/
|
||||
public String getCpuThrottleReason() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total GPU memory in MiB.
|
||||
*
|
||||
* @return The total GPU memory in MiB, or -1.0 if not avaialable on this platform.
|
||||
*/
|
||||
public double getGpuMem() {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GPU memory utilization as MiBs.
|
||||
*
|
||||
* @return The GPU memory utilization in MiBs, or -1.0 if not available on this platform.
|
||||
*/
|
||||
public double getGpuMemUtil() {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IP address of the device.
|
||||
*
|
||||
* @return The IP address as a string, or an empty string if the command fails.
|
||||
*/
|
||||
public String getIpAddress() {
|
||||
String dev = ConfigManager.getInstance().getConfig().getNetworkConfig().networkManagerIface;
|
||||
return NetworkUtils.getIPAddresses(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a NetworkTraffic instance containing the average sent and recieved network traffic
|
||||
* since the last time this was called.
|
||||
*
|
||||
* @return NetworkTraffic instance with data in bits/second. The traffic values will be -1 if the
|
||||
* data isn't available.
|
||||
*/
|
||||
private synchronized NetworkTraffic getNetworkTraffic() {
|
||||
String activeIFaceName =
|
||||
ConfigManager.getInstance().getConfig().getNetworkConfig().networkManagerIface;
|
||||
var iFace = selectNetworkIfByName(activeIFaceName);
|
||||
if (iFace == null) {
|
||||
return new NetworkTraffic(-1, -1);
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
double dTime = (now - lastTrafficUpdate) / 1000.0;
|
||||
if (dTime > minimumDeltaTime) {
|
||||
// only update if it's been long enough since the last update
|
||||
// otherwise, return the last value
|
||||
iFace.updateAttributes();
|
||||
long bytesSent = iFace.getBytesSent();
|
||||
long bytesRecv = iFace.getBytesRecv();
|
||||
double sentBitRate = 8 * (bytesSent - lastBytesSent) / dTime;
|
||||
double recvBitRate = 8 * (bytesRecv - lastBytesRecv) / dTime;
|
||||
lastBytesSent = bytesSent;
|
||||
lastBytesRecv = bytesRecv;
|
||||
lastResult = new NetworkTraffic(sentBitRate, recvBitRate);
|
||||
lastTrafficUpdate = now;
|
||||
}
|
||||
return lastResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Benchmarks SystemMonitor by timing the calls to retrieve metrics and writes the results to the
|
||||
* log.
|
||||
*/
|
||||
private void testSM() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
double total = 0;
|
||||
|
||||
sb.append("SystemMetrics Test:\n");
|
||||
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("Used Disk: %.2f%%", getUsedDiskPct()));
|
||||
total +=
|
||||
timeIt(
|
||||
sb, () -> String.format("Usable Disk Space: %.0f MiB, ", getUsableDiskSpace() / mebi));
|
||||
total +=
|
||||
timeIt(
|
||||
sb, () -> String.format("Memory: %.0f / %.0f MiB", getUsedMemory(), getTotalMemory()));
|
||||
total +=
|
||||
timeIt(
|
||||
sb, () -> String.format("GPU Memory: %.0f / %.0f MiB", getGpuMemUtil(), getGpuMem()));
|
||||
total += timeIt(sb, () -> String.format("CPU Throttle: %s", getCpuThrottleReason()));
|
||||
|
||||
total +=
|
||||
timeIt(
|
||||
sb,
|
||||
() -> {
|
||||
var nt = getNetworkTraffic();
|
||||
return String.format(
|
||||
"Data sent: %.0f Kbps, Data recieved: %.0f Kbps",
|
||||
nt.sentBitRate() / 1000, nt.recvBitRate() / 1000);
|
||||
});
|
||||
|
||||
sb.append(String.format("==========\n%7.3f ms\n", total));
|
||||
|
||||
logger.info(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a StringBuilder with the result of calling `source` prepended by the time required to
|
||||
* run `source`, and returns the time (in ms) that a String Supplier takes. This can be used to
|
||||
* compare different ways of gathering the same metric.
|
||||
*
|
||||
* @param sb A StringBuilder used to collect the output from the supplier.
|
||||
* @param source A supplier that takes no arguments and returns a String.
|
||||
* @return The time (in ms) required to produce the output.
|
||||
*/
|
||||
private double timeIt(StringBuilder sb, Supplier<String> source) {
|
||||
long start = System.nanoTime();
|
||||
String resp = source.get();
|
||||
var delta = (System.nanoTime() - start) / 1000000.0;
|
||||
sb.append(String.format(" %7.3f ms >> %s\n", delta, resp));
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
@@ -15,25 +15,11 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics.cmds;
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
import org.photonvision.common.configuration.HardwareConfig;
|
||||
|
||||
public class FileCmds extends CmdBase {
|
||||
public class SystemMonitorQCS6490 extends SystemMonitor {
|
||||
@Override
|
||||
public void initCmds(HardwareConfig config) {
|
||||
cpuTemperatureCommand = config.cpuTempCommand;
|
||||
cpuUtilizationCommand = config.cpuUtilCommand;
|
||||
cpuThrottleReasonCmd = config.cpuThrottleReasonCmd;
|
||||
|
||||
ramMemCommand = config.cpuMemoryCommand;
|
||||
ramUtilCommand = config.ramUtilCommand;
|
||||
|
||||
gpuMemCommand = config.gpuMemoryCommand;
|
||||
gpuMemUtilCommand = config.gpuMemUsageCommand;
|
||||
|
||||
diskUsageCommand = config.diskUsageCommand;
|
||||
|
||||
uptimeCommand = config.cpuUptimeCommand;
|
||||
protected String getThermalZoneTypes() {
|
||||
return "cpu0-thermal";
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,20 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics.cmds;
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
import org.photonvision.common.configuration.HardwareConfig;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class RK3588Cmds extends LinuxCmds {
|
||||
/** Applies pi-specific commands, ignoring any input configuration */
|
||||
public void initCmds(HardwareConfig config) {
|
||||
super.initCmds(config);
|
||||
public class SystemMonitorRK3588 extends SystemMonitor {
|
||||
final String regex = "Core\\d:\\s*(\\d+)%";
|
||||
final Pattern pattern = Pattern.compile(regex);
|
||||
|
||||
@Override
|
||||
protected String getThermalZoneTypes() {
|
||||
// CPU Temperature
|
||||
/* The RK3588 chip has 7 thermal zones that can be accessed via:
|
||||
* /sys/class/thermal/thermal_zoneX/temp
|
||||
@@ -42,10 +47,19 @@ public class RK3588Cmds extends LinuxCmds {
|
||||
* - http://forum.armsom.org/t/topic/51/3
|
||||
* - https://lore.kernel.org/lkml/7276280.TLKafQO6qx@archbook/
|
||||
*/
|
||||
cpuTemperatureCommand =
|
||||
"cat /sys/class/thermal/thermal_zone1/temp | awk '{printf \"%.1f\", $1/1000}'";
|
||||
return "bigcore0-thermal";
|
||||
}
|
||||
|
||||
npuUsageCommand =
|
||||
"cat /sys/kernel/debug/rknpu/load | grep -o '[0-9]\\+%' | sed 's/%//g' | paste -sd ','";
|
||||
@Override
|
||||
public double[] 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;
|
||||
} catch (IOException e) {
|
||||
return new double[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.photonvision.common.util.ShellExec;
|
||||
|
||||
public class SystemMonitorRaspberryPi extends SystemMonitor {
|
||||
private final ShellExec runCommand = new ShellExec(true, true);
|
||||
|
||||
@Override
|
||||
public String getCpuThrottleReason() {
|
||||
int state = 0;
|
||||
String output = vcgencmd("get_throttled");
|
||||
try {
|
||||
state = Integer.decode(output);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Could not parse return value: " + output);
|
||||
}
|
||||
if ((state & 0x01) != 0) {
|
||||
return "LOW VOLTAGE";
|
||||
} else if ((state & 0x08) != 0) {
|
||||
return "HIGH TEMP";
|
||||
} else if ((state & 0x10000) != 0) {
|
||||
return "Prev. Low Voltage";
|
||||
} else if ((state & 0x80000) != 0) {
|
||||
return "Prev. High Temp";
|
||||
}
|
||||
return "None";
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getGpuMem() {
|
||||
String output = vcgencmd("get_mem gpu");
|
||||
if (!output.isBlank()) {
|
||||
return Integer.parseInt(output);
|
||||
}
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getGpuMemUtil() {
|
||||
String output = vcgencmd("get_mem malloc");
|
||||
if (!output.isBlank()) {
|
||||
return Integer.parseInt(output);
|
||||
}
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
private String vcgencmd(String cmd) {
|
||||
if (cmd.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
String command = "vcgencmd " + cmd;
|
||||
try {
|
||||
runCommand.executeBashCommand(command, true, false);
|
||||
if (runCommand.getExitCode() != 0) {
|
||||
logger.error("Bad response from vcgencmd: " + runCommand.getOutput());
|
||||
return "";
|
||||
} else {
|
||||
return runCommand.getOutput().split("=")[1].replaceAll("[^\\d.]$", "");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not run `vcgencmd`!", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics.cmds;
|
||||
|
||||
import org.photonvision.common.configuration.HardwareConfig;
|
||||
|
||||
public class CmdBase {
|
||||
// CPU
|
||||
public String cpuTemperatureCommand = "";
|
||||
public String cpuUtilizationCommand = "";
|
||||
public String cpuThrottleReasonCmd = "";
|
||||
// RAM
|
||||
public String ramMemCommand = "";
|
||||
public String ramUtilCommand = "";
|
||||
// GPU
|
||||
public String gpuMemCommand = "";
|
||||
public String gpuMemUtilCommand = "";
|
||||
// NPU
|
||||
public String npuUsageCommand = "";
|
||||
// Disk
|
||||
public String diskUsageCommand = "";
|
||||
// Uptime
|
||||
public String uptimeCommand = "";
|
||||
|
||||
public void initCmds(HardwareConfig config) {
|
||||
// default - do nothing
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics.cmds;
|
||||
|
||||
import org.photonvision.common.configuration.HardwareConfig;
|
||||
|
||||
public class LinuxCmds extends CmdBase {
|
||||
public void initCmds(HardwareConfig config) {
|
||||
// TODO: boards have lots of thermal devices. Hard to pick the CPU
|
||||
|
||||
// CPU
|
||||
cpuUtilizationCommand =
|
||||
"top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk '{print 100 - $1}'";
|
||||
|
||||
// Uptime
|
||||
uptimeCommand = "cat /proc/uptime | cut -d ' ' -f1";
|
||||
|
||||
// RAM
|
||||
ramMemCommand = "free -m | awk 'FNR == 2 {print $2}'";
|
||||
ramUtilCommand = "free -m | awk 'FNR == 2 {print $3}'";
|
||||
|
||||
// Disk
|
||||
diskUsageCommand = "df ./ --output=pcent | tail -n +2 | tr -d '%'";
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics.cmds;
|
||||
|
||||
import org.photonvision.common.configuration.HardwareConfig;
|
||||
|
||||
public class PiCmds extends LinuxCmds {
|
||||
/** Applies pi-specific commands, ignoring any input configuration */
|
||||
public void initCmds(HardwareConfig config) {
|
||||
super.initCmds(config);
|
||||
|
||||
// CPU
|
||||
cpuTemperatureCommand = "sed 's/.\\{3\\}$/.&/' /sys/class/thermal/thermal_zone0/temp";
|
||||
cpuThrottleReasonCmd =
|
||||
"if (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x01 )) != 0x00 )); then echo \"LOW VOLTAGE\"; "
|
||||
+ " elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x08 )) != 0x00 )); then echo \"HIGH TEMP\"; "
|
||||
+ " elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x10000 )) != 0x00 )); then echo \"Prev. Low Voltage\"; "
|
||||
+ " elif (( $(( $(vcgencmd get_throttled | grep -Eo 0x[0-9a-fA-F]*) & 0x80000 )) != 0x00 )); then echo \"Prev. High Temp\"; "
|
||||
+ " else echo \"None\"; fi";
|
||||
|
||||
// GPU
|
||||
gpuMemCommand = "vcgencmd get_mem gpu | grep -Eo '[0-9]+'";
|
||||
gpuMemUtilCommand = "vcgencmd get_mem malloc | grep -Eo '[0-9]+'";
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.common.hardware.metrics.cmds;
|
||||
|
||||
import org.photonvision.common.configuration.HardwareConfig;
|
||||
|
||||
public class QCS6490Cmds extends LinuxCmds {
|
||||
/** Applies pi-specific commands, ignoring any input configuration */
|
||||
public void initCmds(HardwareConfig config) {
|
||||
super.initCmds(config);
|
||||
|
||||
/* Thermal zone information can be found in /sys/class/thermal/thermal_zone* directories:
|
||||
* zone/type: Contains the thermal zone type/name (e.g., "acpi", "x86_pkg_temp")
|
||||
* zone/temp: Current temperature in millidegrees Celsius (divide by 1000 for actual temp)
|
||||
* zone/policy: Thermal governor policy (e.g., "step_wise", "power_allocator")
|
||||
* Each thermal_zone* directory represents a different temperature sensor in the system
|
||||
*/
|
||||
|
||||
cpuTemperatureCommand =
|
||||
"cat /sys/class/thermal/thermal_zone10/temp | awk '{printf \"%.1f\", $1/1000}'";
|
||||
|
||||
// TODO: NPU usage, doesn't seem to be in the same place as the opi. We're gonna just wait on QC
|
||||
// to get back to us on this one.
|
||||
}
|
||||
}
|
||||
@@ -49,9 +49,12 @@ public class DeviceMetricsProto implements Protobuf<DeviceMetrics, ProtobufDevic
|
||||
msg.getGpuMem(),
|
||||
msg.getGpuMemUtil(),
|
||||
msg.getDiskUtilPct(),
|
||||
msg.getDiskUsableSpace(),
|
||||
msg.getNpuUsage().toArray(),
|
||||
msg.getIpAddress(),
|
||||
msg.getUptime());
|
||||
msg.getUptime(),
|
||||
msg.getSentBitRate(),
|
||||
msg.getRecvBitRate());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,7 +68,10 @@ public class DeviceMetricsProto implements Protobuf<DeviceMetrics, ProtobufDevic
|
||||
msg.setRamUtil(value.ramUtil());
|
||||
msg.setGpuMemUtil(value.gpuMemUtil());
|
||||
msg.setDiskUtilPct(value.diskUtilPct());
|
||||
msg.setDiskUsableSpace(value.diskUsableSpace());
|
||||
msg.addAllNpuUsage(value.npuUsage());
|
||||
msg.setIpAddress(value.ipAddress());
|
||||
msg.setSentBitRate(value.sentBitRate());
|
||||
msg.setRecvBitRate(value.recvBitRate());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,6 @@ public class HardwareConfigTest {
|
||||
assertEquals(config.deviceLogoPath, "photonvision.png");
|
||||
assertEquals(config.supportURL, "https://support.photonvision.com");
|
||||
// Ensure defaults are not null
|
||||
assertEquals(config.cpuThrottleReasonCmd, "");
|
||||
assertEquals(config.diskUsageCommand, "");
|
||||
assertArrayEquals(config.ledPins.stream().mapToInt(i -> i).toArray(), new int[] {2, 13});
|
||||
NativeDeviceFactoryInterface deviceFactory = HardwareManager.configureCustomGPIO(config);
|
||||
assertTrue(deviceFactory instanceof CustomDeviceFactory);
|
||||
|
||||
@@ -27,37 +27,20 @@ import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.photonvision.common.LoadJNI;
|
||||
import org.photonvision.common.configuration.HardwareConfig;
|
||||
import org.photonvision.common.hardware.HardwareManager;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.hardware.VisionLED;
|
||||
import org.photonvision.common.hardware.metrics.MetricsManager;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
|
||||
public class HardwareTest {
|
||||
@Test
|
||||
public void testHardware() {
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
LoadJNI.loadLibraries();
|
||||
MetricsManager mm = new MetricsManager();
|
||||
|
||||
if (!Platform.isRaspberryPi()) return;
|
||||
|
||||
System.out.println("Testing on platform: " + Platform.getPlatformName());
|
||||
|
||||
System.out.println("Printing CPU Info:");
|
||||
System.out.println("Memory: " + mm.getRamMem() + "MB");
|
||||
System.out.println("Temperature: " + mm.getCpuTemp() + "C");
|
||||
System.out.println("Utilization: : " + mm.getCpuUtilization() + "%");
|
||||
|
||||
System.out.println("Printing GPU Info:");
|
||||
System.out.println("Memory: " + mm.getGpuMem() + "MB");
|
||||
|
||||
System.out.println("Printing RAM Info: ");
|
||||
System.out.println("Used RAM: : " + mm.getRamUtil() + "MB");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.photonvision.common.hardware.HardwareManager;
|
||||
import org.photonvision.common.hardware.OsImageData;
|
||||
import org.photonvision.common.hardware.PiVersion;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.hardware.metrics.SystemMonitor;
|
||||
import org.photonvision.common.logging.KernelLogLogger;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.LogLevel;
|
||||
@@ -298,6 +299,10 @@ public class Main {
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
logger.debug("Loading SystemMonitor...");
|
||||
SystemMonitor.getInstance().logSystemInformation();
|
||||
SystemMonitor.getInstance().startMonitor(500, 1000);
|
||||
|
||||
// todo - should test mode just add test mode sources, but still allow local usb cameras to be
|
||||
// added?
|
||||
if (!isTestMode) {
|
||||
|
||||
@@ -994,11 +994,6 @@ public class RequestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public static void onMetricsPublishRequest(Context ctx) {
|
||||
HardwareManager.getInstance().publishMetrics();
|
||||
ctx.status(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the calibration JSON for a specific observation. Excludes camera image data
|
||||
*
|
||||
|
||||
@@ -135,7 +135,6 @@ public class Server {
|
||||
app.get("/api/utils/photonvision-journalctl.txt", RequestHandler::onLogExportRequest);
|
||||
app.post("/api/utils/restartProgram", RequestHandler::onProgramRestartRequest);
|
||||
app.post("/api/utils/restartDevice", RequestHandler::onDeviceRestartRequest);
|
||||
app.post("/api/utils/publishMetrics", RequestHandler::onMetricsPublishRequest);
|
||||
app.get("/api/utils/getImageSnapshots", RequestHandler::onImageSnapshotsRequest);
|
||||
app.get("/api/utils/getCalSnapshot", RequestHandler::onCalibrationSnapshotRequest);
|
||||
app.get("/api/utils/getCalibrationJSON", RequestHandler::onCalibrationExportRequest);
|
||||
|
||||
@@ -81,4 +81,7 @@ message ProtobufDeviceMetrics {
|
||||
repeated double npu_usage = 9;
|
||||
string ip_address = 10;
|
||||
double uptime = 11;
|
||||
double sent_bit_rate = 12;
|
||||
double recv_bit_rate = 13;
|
||||
double disk_usable_space = 14;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user