Fix isRaspian() to properly detect Buster image (#637)

* Revised isRaspian() call to look in multiple spots to check if we're a Pi or not

* wpiformat

* linefeed fixup

* whoops

* WIP updating platform

* More platform fixups WIP

* Condensed metrics classes, but expanded the configuration to default to file, but fall back on hardcoded commands for certain platforms

* Migrate unixSupported to isLinux

* applied spotless

* wpiformat

* Linux metrics (#641)

* Move generic commands from PiCmds to LinuxCmds; have PiCmds inherit from LinuxCmds

* Better names for variables to save the total memory values

* Remove "Bionic" from the architecture; that is not actually determined.

* Trigger PhotonVision CI

* Dummy change to trigger CI

* Run format

Update index.html

Co-authored-by: Mohammad Durrani <46766905+mdurrani808@users.noreply.github.com>
Co-authored-by: Paul Rensing <prensing@gmail.com>
Co-authored-by: Matt <matthew.morley.ca@gmail.com>
This commit is contained in:
Chris Gerth
2022-12-26 21:51:34 -06:00
committed by GitHub
parent 548f52e117
commit b1905954bc
22 changed files with 532 additions and 424 deletions

View File

@@ -128,7 +128,22 @@ public class HardwareConfig {
this.blacklistedResIndices = blacklistedResIndices;
}
/** @return True if the FOV has been preset to a sane value, false otherwise */
public final boolean hasPresetFOV() {
return vendorFOV > 0;
}
/** @return True if any command has been configured to a non-default empty, false otherwise */
public final boolean hasCommandsConfigured() {
return cpuTempCommand != ""
|| cpuMemoryCommand != ""
|| cpuUtilCommand != ""
|| cpuThrottleReasonCmd != ""
|| cpuUptimeCommand != ""
|| gpuMemoryCommand != ""
|| ramUtilCommand != ""
|| ledBlinkCommand != ""
|| gpuMemUsageCommand != ""
|| diskUsageCommand != "";
}
}

View File

@@ -114,7 +114,7 @@ public class PhotonConfiguration {
? "Zerocopy MMAL on " + PicamJNI.getSensorModel().getFriendlyName()
: ""); // TODO add support for other types of GPU accel
generalSubmap.put("hardwareModel", hardwareConfig.deviceName);
generalSubmap.put("hardwarePlatform", Platform.getCurrentPlatform().toString());
generalSubmap.put("hardwarePlatform", Platform.getPlatformName());
settingsSubmap.put("general", generalSubmap);
map.put("settings", settingsSubmap);

View File

@@ -27,7 +27,7 @@ import org.photonvision.common.dataflow.networktables.NTDataChangeListener;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.hardware.GPIO.CustomGPIO;
import org.photonvision.common.hardware.GPIO.pi.PigpioSocket;
import org.photonvision.common.hardware.metrics.MetricsBase;
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;
@@ -41,6 +41,8 @@ public class HardwareManager {
private final HardwareConfig hardwareConfig;
private final HardwareSettings hardwareSettings;
private final MetricsManager metricsManager;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final StatusLED statusLED;
@@ -65,8 +67,11 @@ public class HardwareManager {
private HardwareManager(HardwareConfig hardwareConfig, HardwareSettings hardwareSettings) {
this.hardwareConfig = hardwareConfig;
this.hardwareSettings = hardwareSettings;
this.metricsManager = new MetricsManager();
this.metricsManager.setConfig(hardwareConfig);
CustomGPIO.setConfig(hardwareConfig);
MetricsBase.setConfig(hardwareConfig);
if (Platform.isRaspberryPi()) {
pigpioSocket = new PigpioSocket();
@@ -162,4 +167,8 @@ public class HardwareManager {
public HardwareConfig getConfig() {
return hardwareConfig;
}
public void publishMetrics() {
metricsManager.publishMetrics();
}
}

View File

@@ -17,6 +17,9 @@
package org.photonvision.common.hardware;
import java.io.IOException;
import org.photonvision.common.util.ShellExec;
public enum PiVersion {
PI_B("Pi Model B"),
COMPUTE_MODULE("Compute Module Rev"),
@@ -28,17 +31,41 @@ public enum PiVersion {
UNKNOWN("UNKNOWN");
private final String identifier;
private static final ShellExec shell = new ShellExec(true, false);
private static final PiVersion currentPiVersion = calcPiVersion();
PiVersion(String s) {
this.identifier = s.toLowerCase();
}
public static PiVersion getPiVersion() {
return currentPiVersion;
}
private static PiVersion calcPiVersion() {
if (!Platform.isRaspberryPi()) return PiVersion.UNKNOWN;
String piString = Platform.currentPiVersionStr;
String piString = getPiVersionString();
for (PiVersion p : PiVersion.values()) {
if (piString.toLowerCase().contains(p.identifier)) return p;
}
return UNKNOWN;
}
// Query /proc/device-tree/model. This should return the model of the pi
// Versions here:
// https://github.com/raspberrypi/linux/blob/rpi-5.10.y/arch/arm/boot/dts/bcm2710-rpi-cm3.dts
private static String getPiVersionString() {
if (!Platform.isRaspberryPi()) return "";
try {
shell.executeBashCommand("cat /proc/device-tree/model");
} catch (IOException e) {
e.printStackTrace();
}
if (shell.getExitCode() == 0) {
// We expect it to be in the format "raspberry pi X model X"
return shell.getOutput();
}
return "";
}
}

View File

@@ -17,6 +17,7 @@
package org.photonvision.common.hardware;
import com.jogamp.common.os.Platform.OSType;
import edu.wpi.first.util.RuntimeDetector;
import java.io.BufferedReader;
import java.io.IOException;
@@ -27,53 +28,80 @@ import org.photonvision.common.util.ShellExec;
@SuppressWarnings("unused")
public enum Platform {
// WPILib Supported (JNI)
WINDOWS_32("Windows x32"),
WINDOWS_64("Windows x64"),
LINUX_64("Linux x64"),
LINUX_RASPBIAN("Linux Raspbian"), // Raspberry Pi 3/4
LINUX_AARCH64BIONIC("Linux AARCH64 Bionic"), // Jetson Nano, Jetson TX2
// PhotonVision Supported (Manual install)
LINUX_ARM32("Linux ARM32"), // ODROID XU4, C1+
LINUX_ARM64("Linux ARM64"), // ODROID C2, N2
WINDOWS_64("Windows x64", false, OSType.WINDOWS, true),
LINUX_32("Linux x86", false, OSType.LINUX, true),
LINUX_64("Linux x64", false, OSType.LINUX, true),
LINUX_RASPBIAN32(
"Linux Raspbian 32-bit", true, OSType.LINUX, true), // Raspberry Pi 3/4 with a 32-bit image
LINUX_RASPBIAN64(
"Linux Raspbian 64-bit", true, OSType.LINUX, true), // Raspberry Pi 3/4 with a 64-bit image
LINUX_AARCH64("Linux AARCH64", false, OSType.LINUX, true), // Jetson Nano, Jetson TX2
// PhotonVision Supported (Manual build/install)
LINUX_ARM32("Linux ARM32", false, OSType.LINUX, true), // ODROID XU4, C1+
LINUX_ARM64("Linux ARM64", false, OSType.LINUX, true), // ODROID C2, N2
// Completely unsupported
UNSUPPORTED("Unsupported Platform");
WINDOWS_32("Windows x86", false, OSType.WINDOWS, false),
MACOS("Mac OS", false, OSType.MACOS, false),
UNKNOWN("Unsupported Platform", false, OSType.UNKNOWN, false);
private enum OSType {
WINDOWS,
LINUX,
MACOS,
UNKNOWN
}
private static final ShellExec shell = new ShellExec(true, false);
public final String value;
public static final boolean isRoot = checkForRoot();
public final String description;
public final boolean isPi;
public final OSType osType;
public final boolean isSupported;
Platform(String value) {
this.value = value;
// Set once at init, shouldn't be needed after.
private static final Platform currentPlatform = getCurrentPlatform();
private static final boolean isRoot = checkForRoot();
Platform(String description, boolean isPi, OSType osType, boolean isSupported) {
this.description = description;
this.isPi = isPi;
this.osType = osType;
this.isSupported = isSupported;
}
private static final String OS_NAME = System.getProperty("os.name");
private static final String OS_ARCH = System.getProperty("os.arch");
// These are queried on init and should never change after
public static final Platform currentPlatform = getCurrentPlatform();
static final String currentPiVersionStr = getPiVersionString();
public static final PiVersion currentPiVersion = PiVersion.getPiVersion();
private static final String UnknownPlatformString =
String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH);
public static boolean isWindows() {
return currentPlatform == WINDOWS_64 || currentPlatform == WINDOWS_32;
}
//////////////////////////////////////////////////////
// Public API
// Checks specifically if unix shell and API are supported
public static boolean isLinux() {
return currentPlatform == LINUX_64
|| currentPlatform == LINUX_RASPBIAN
|| currentPlatform == LINUX_AARCH64BIONIC
|| currentPlatform == LINUX_ARM32
|| currentPlatform == LINUX_ARM64;
return currentPlatform.osType == OSType.LINUX;
}
public static boolean isRaspberryPi() {
return currentPlatform.equals(LINUX_RASPBIAN);
return currentPlatform.isPi;
}
public static String getPlatformName() {
if (currentPlatform.equals(UNKNOWN)) {
return UnknownPlatformString;
} else {
return currentPlatform.description;
}
}
public static boolean isRoot() {
return isRoot;
}
//////////////////////////////////////////////////////
// Debug info related to unknown platforms for debug help
private static final String OS_NAME = System.getProperty("os.name");
private static final String OS_ARCH = System.getProperty("os.arch");
private static final String UnknownPlatformString =
String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH);
@SuppressWarnings("StatementWithEmptyBody")
private static boolean checkForRoot() {
if (isLinux()) {
@@ -97,59 +125,90 @@ public enum Platform {
return false;
}
public static Platform getCurrentPlatform() {
private static Platform getCurrentPlatform() {
if (RuntimeDetector.isWindows()) {
if (RuntimeDetector.is32BitIntel()) return WINDOWS_32;
if (RuntimeDetector.is64BitIntel()) return WINDOWS_64;
if (RuntimeDetector.is32BitIntel()) {
return WINDOWS_32;
} else if (RuntimeDetector.is64BitIntel()) {
return WINDOWS_64;
} else {
// please don't try this
return UNKNOWN;
}
}
if (RuntimeDetector.isMac()) {
if (RuntimeDetector.is32BitIntel()) return UNSUPPORTED;
// TODO - once we have real support, this might have to be more granular
return MACOS;
}
if (RuntimeDetector.isLinux()) {
if (RuntimeDetector.is32BitIntel()) return UNSUPPORTED;
if (RuntimeDetector.is64BitIntel()) return LINUX_64;
if (isRaspbian()) return LINUX_RASPBIAN;
}
System.out.println(UnknownPlatformString);
return Platform.UNSUPPORTED;
}
public String toString() {
if (this.equals(UNSUPPORTED)) {
return UnknownPlatformString;
} else {
return this.value;
}
}
// Querry /proc/device-tree/model. This should return the model of the pi
// Versions here:
// https://github.com/raspberrypi/linux/blob/rpi-5.10.y/arch/arm/boot/dts/bcm2710-rpi-cm3.dts
private static String getPiVersionString() {
if (!isRaspberryPi()) return "";
try {
shell.executeBashCommand("cat /proc/device-tree/model");
} catch (IOException e) {
e.printStackTrace();
}
if (shell.getExitCode() == 0) {
// We expect it to be in the format "raspberry pi X model X"
return shell.getOutput();
}
return "";
}
private static boolean isRaspbian() {
try (BufferedReader reader = Files.newBufferedReader(Paths.get("/etc/os-release"))) {
String value = reader.readLine();
if (value == null) {
return false;
if (isPiSBC()) {
if (RuntimeDetector.isArm32()) {
return LINUX_RASPBIAN32;
} else if (RuntimeDetector.isArm64()) {
return LINUX_RASPBIAN64;
} else {
// Unknown/exotic installation
return UNKNOWN;
}
} else if (isJetsonSBC()) {
if (RuntimeDetector.isArm64()) {
// TODO - do we need to check OS version?
return LINUX_AARCH64;
} else {
// Unknown/exotic installation
return UNKNOWN;
}
} else if (RuntimeDetector.is64BitIntel()) {
return LINUX_64;
} else if (RuntimeDetector.is32BitIntel()) {
return LINUX_32;
} else if (RuntimeDetector.isArm64()) {
// TODO - os detection needed?
return LINUX_AARCH64;
} else {
// Unknown or otherwise unsupported platform
return Platform.UNKNOWN;
}
}
// If we fall through all the way to here,
return Platform.UNKNOWN;
}
// Check for various known SBC types
private static boolean isPiSBC() {
return fileHasText("/proc/cpuinfo", "Raspberry Pi");
}
private static boolean isJetsonSBC() {
// https://forums.developer.nvidia.com/t/how-to-recognize-jetson-nano-device/146624
return fileHasText("/proc/device-tree/model", "NVIDIA Jetson");
}
// Checks for various names of linux OS
private static boolean isStretch() {
// TODO - this is a total guess
return fileHasText("/etc/os-release", "Stretch");
}
private static boolean isBuster() {
// TODO - this is a total guess
return fileHasText("/etc/os-release", "Buster");
}
private static boolean fileHasText(String filename, String text) {
try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {
while (true) {
String value = reader.readLine();
if (value == null) {
return false;
} else if (value.contains(text)) {
return true;
} // else, next line
}
return value.contains("Raspbian");
} catch (IOException ex) {
return false;
}

View File

@@ -1,51 +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;
public class CPUMetrics extends MetricsBase {
private String cpuMemSplit = null;
public String getMemory() {
if (cpuMemoryCommand.isEmpty()) return "";
if (cpuMemSplit == null) {
cpuMemSplit = execute(cpuMemoryCommand);
}
return cpuMemSplit;
}
public String getTemp() {
if (cpuTemperatureCommand.isEmpty()) return "";
try {
return execute(cpuTemperatureCommand);
} catch (Exception e) {
return "N/A";
}
}
public String getUtilization() {
return execute(cpuUtilizationCommand);
}
public String getUptime() {
return execute(cpuUptimeCommand);
}
public String getThrottleReason() {
return execute(cpuThrottleReasonCmd);
}
}

View File

@@ -1,25 +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;
public class DiskMetrics extends MetricsBase {
public String getUsedDiskPct() {
if (diskUsageCommand.isEmpty()) return "";
return execute(diskUsageCommand);
}
}

View File

@@ -1,103 +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 java.io.PrintWriter;
import java.io.StringWriter;
import org.photonvision.common.configuration.HardwareConfig;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.ShellExec;
public abstract class MetricsBase {
static final Logger logger = new Logger(MetricsBase.class, LogGroup.General);
// CPU
public static String cpuMemoryCommand = "vcgencmd get_mem arm | grep -Eo '[0-9]+'";
public static String cpuTemperatureCommand =
"sed 's/.\\{3\\}$/.&/' <<< cat /sys/class/thermal/thermal_zone0/temp";
public static String cpuUtilizationCommand =
"top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk '{print 100 - $1}'";
public static String 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";
public static String cpuUptimeCommand = "uptime -p | cut -c 4-";
// GPU
public static String gpuMemoryCommand = "vcgencmd get_mem gpu | grep -Eo '[0-9]+'";
public static String gpuMemUsageCommand = "vcgencmd get_mem malloc | grep -Eo '[0-9]+'";
// RAM
public static String ramUsageCommand = "free --mega | awk -v i=2 -v j=3 'FNR == i {print $j}'";
// Disk
public static String diskUsageCommand = "df ./ --output=pcent | tail -n +2";
private static ShellExec runCommand = new ShellExec(true, true);
public static void setConfig(HardwareConfig config) {
if (Platform.isRaspberryPi()) return;
cpuMemoryCommand = config.cpuMemoryCommand;
cpuTemperatureCommand = config.cpuTempCommand;
cpuUtilizationCommand = config.cpuUtilCommand;
cpuThrottleReasonCmd = config.cpuThrottleReasonCmd;
cpuUptimeCommand = config.cpuUptimeCommand;
gpuMemoryCommand = config.gpuMemoryCommand;
gpuMemUsageCommand = config.gpuMemUsageCommand;
diskUsageCommand = config.diskUsageCommand;
ramUsageCommand = config.ramUtilCommand;
}
public static synchronized String execute(String command) {
try {
runCommand.executeBashCommand(command);
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.toString()
+ sw.toString());
return "";
}
}
}

View File

@@ -0,0 +1,161 @@
/*
* 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.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import org.photonvision.common.configuration.HardwareConfig;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
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.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.ShellExec;
public class MetricsManager {
final Logger logger = new Logger(MetricsManager.class, LogGroup.General);
CmdBase cmds;
private 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.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 "****";
}
}
private String cpuMemSave = null;
public String getMemory() {
if (cmds.cpuMemoryCommand.isEmpty()) return "";
if (cpuMemSave == null) {
// save the value and only run it once
cpuMemSave = execute(cmds.cpuMemoryCommand);
}
return cpuMemSave;
}
public String getTemp() {
return safeExecute(cmds.cpuTemperatureCommand);
}
public String getUtilization() {
return safeExecute(cmds.cpuUtilizationCommand);
}
public String getUptime() {
return safeExecute(cmds.cpuUptimeCommand);
}
public String getThrottleReason() {
return safeExecute(cmds.cpuThrottleReasonCmd);
}
private String gpuMemSave = null;
public String getGPUMemorySplit() {
if (gpuMemSave == null) {
// only needs to run once
gpuMemSave = safeExecute(cmds.gpuMemoryCommand);
}
return gpuMemSave;
}
public String getMallocedMemory() {
return safeExecute(cmds.gpuMemUsageCommand);
}
public String getUsedDiskPct() {
return safeExecute(cmds.diskUsageCommand);
}
// TODO: Output in MBs for consistency
public String getUsedRam() {
return safeExecute(cmds.ramUsageCommand);
}
public void publishMetrics() {
logger.debug("Publishing Metrics...");
final var metrics = new HashMap<String, String>();
metrics.put("cpuTemp", this.getTemp());
metrics.put("cpuUtil", this.getUtilization());
metrics.put("cpuMem", this.getMemory());
metrics.put("cpuThr", this.getThrottleReason());
metrics.put("cpuUptime", this.getUptime());
metrics.put("gpuMem", this.getGPUMemorySplit());
metrics.put("ramUtil", this.getUsedRam());
metrics.put("gpuMemUtil", this.getMallocedMemory());
metrics.put("diskUtilPct", this.getUsedDiskPct());
DataChangeService.getInstance().publishEvent(OutgoingUIEvent.wrappedOf("metrics", metrics));
}
public synchronized String execute(String command) {
try {
runCommand.executeBashCommand(command);
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.toString()
+ sw.toString());
return "";
}
}
}

View File

@@ -1,76 +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 java.util.HashMap;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.TimedTaskManager;
public class MetricsPublisher {
private static final Logger logger = new Logger(MetricsPublisher.class, LogGroup.General);
private static CPUMetrics cpuMetrics;
private static GPUMetrics gpuMetrics;
private static RAMMetrics ramMetrics;
private static DiskMetrics diskMetrics;
public static MetricsPublisher getInstance() {
return Singleton.INSTANCE;
}
private MetricsPublisher() {
cpuMetrics = new CPUMetrics();
gpuMetrics = new GPUMetrics();
ramMetrics = new RAMMetrics();
diskMetrics = new DiskMetrics();
}
public void stopTask() {
TimedTaskManager.getInstance().cancelTask("Metrics");
logger.info("This device does not support running bash commands. Stopped metrics thread.");
}
public void publish() {
if (!Platform.isRaspberryPi()) {
logger.debug("Ignoring metrics on non-Pi devices");
return;
}
logger.debug("Publishing Metrics...");
final var metrics = new HashMap<String, String>();
metrics.put("cpuTemp", cpuMetrics.getTemp());
metrics.put("cpuUtil", cpuMetrics.getUtilization());
metrics.put("cpuMem", cpuMetrics.getMemory());
metrics.put("cpuThr", cpuMetrics.getThrottleReason());
metrics.put("cpuUptime", cpuMetrics.getUptime());
metrics.put("gpuMem", gpuMetrics.getGPUMemorySplit());
metrics.put("ramUtil", ramMetrics.getUsedRam());
metrics.put("gpuMemUtil", gpuMetrics.getMallocedMemory());
metrics.put("diskUtilPct", diskMetrics.getUsedDiskPct());
DataChangeService.getInstance().publishEvent(OutgoingUIEvent.wrappedOf("metrics", metrics));
}
private static class Singleton {
public static final MetricsPublisher INSTANCE = new MetricsPublisher();
}
}

View File

@@ -1,26 +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;
public class RAMMetrics extends MetricsBase {
// TODO: Output in MBs for consistency
public String getUsedRam() {
if (ramUsageCommand.isEmpty()) return "";
return execute(ramUsageCommand);
}
}

View File

@@ -15,19 +15,26 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.hardware.metrics;
package org.photonvision.common.hardware.metrics.cmds;
public class GPUMetrics extends MetricsBase {
private String gpuMemSplit = null;
import org.photonvision.common.configuration.HardwareConfig;
public String getGPUMemorySplit() {
if (gpuMemSplit == null) {
gpuMemSplit = execute(gpuMemoryCommand);
}
return gpuMemSplit;
}
public class CmdBase {
// CPU
public String cpuMemoryCommand = "";
public String cpuTemperatureCommand = "";
public String cpuUtilizationCommand = "";
public String cpuThrottleReasonCmd = "";
public String cpuUptimeCommand = "";
// GPU
public String gpuMemoryCommand = "";
public String gpuMemUsageCommand = "";
// RAM
public String ramUsageCommand = "";
// Disk
public String diskUsageCommand = "";
public String getMallocedMemory() {
return execute(gpuMemUsageCommand);
public void initCmds(HardwareConfig config) {
return; // default - do nothing
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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 FileCmds extends CmdBase {
@Override
public void initCmds(HardwareConfig config) {
cpuMemoryCommand = config.cpuMemoryCommand;
cpuTemperatureCommand = config.cpuTempCommand;
cpuUtilizationCommand = config.cpuUtilCommand;
cpuThrottleReasonCmd = config.cpuThrottleReasonCmd;
cpuUptimeCommand = config.cpuUptimeCommand;
gpuMemoryCommand = config.gpuMemoryCommand;
gpuMemUsageCommand = config.gpuMemUsageCommand;
diskUsageCommand = config.diskUsageCommand;
ramUsageCommand = config.ramUtilCommand;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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) {
// CPU
cpuMemoryCommand = "awk '/MemTotal:/ {print int($2 / 1000);}' /proc/meminfo";
// TODO: boards have lots of thermal devices. Hard to pick the CPU
cpuUtilizationCommand =
"top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk '{print 100 - $1}'";
cpuUptimeCommand = "uptime -p | cut -c 4-";
// RAM
ramUsageCommand = "awk '/MemFree:/ {print int($2 / 1000);}' /proc/meminfo";
// Disk
diskUsageCommand = "df ./ --output=pcent | tail -n +2";
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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
cpuMemoryCommand = "vcgencmd get_mem arm | grep -Eo '[0-9]+'";
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
gpuMemoryCommand = "vcgencmd get_mem gpu | grep -Eo '[0-9]+'";
gpuMemUsageCommand = "vcgencmd get_mem malloc | grep -Eo '[0-9]+'";
}
}

View File

@@ -47,7 +47,7 @@ public class NetworkManager {
var config = ConfigManager.getInstance().getConfig().getNetworkConfig();
logger.info("Setting " + config.connectionType + " with team team " + config.teamNumber);
if (Platform.isLinux()) {
if (!Platform.isRoot) {
if (!Platform.isRoot()) {
logger.error("Cannot manage network without root!");
return;
}

View File

@@ -128,7 +128,7 @@ public class ScriptManager {
}
public static void queueEvent(ScriptEventType eventType) {
if (!Platform.isWindows()) {
if (Platform.isLinux()) {
try {
queuedEvents.putLast(eventType);
logger.info("Queued event: " + eventType.name());

View File

@@ -78,7 +78,7 @@ public class FileUtils {
}
public static void setFilePerms(Path path) throws IOException {
if (!Platform.isWindows()) {
if (Platform.isLinux()) {
File thisFile = path.toFile();
Set<PosixFilePermission> perms =
Files.readAttributes(path, PosixFileAttributes.class).permissions();
@@ -96,7 +96,7 @@ public class FileUtils {
}
public static void setAllPerms(Path path) {
if (!Platform.isWindows()) {
if (Platform.isLinux()) {
String command = String.format("chmod 777 -R %s", path.toString());
try {
Process p = Runtime.getRuntime().exec(command);

View File

@@ -87,14 +87,18 @@ public class PicamJNI {
}
public static boolean isSupported() {
var piVer = PiVersion.getPiVersion();
boolean hwSupported =
(piVer == PiVersion.PI_3
|| piVer == PiVersion.COMPUTE_MODULE_3
|| piVer == PiVersion.ZERO_2_W);
return libraryLoaded
&& enabled
&& isVCSMSupported()
&& getSensorModel() != SensorModel.Disconnected
&& Platform.isRaspberryPi()
&& (Platform.currentPiVersion == PiVersion.PI_3
|| Platform.currentPiVersion == PiVersion.COMPUTE_MODULE_3
|| Platform.currentPiVersion == PiVersion.ZERO_2_W);
&& hwSupported;
}
public static SensorModel getSensorModel() {

View File

@@ -24,31 +24,27 @@ import org.photonvision.common.hardware.GPIO.CustomGPIO;
import org.photonvision.common.hardware.GPIO.GPIOBase;
import org.photonvision.common.hardware.GPIO.pi.PigpioPin;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.hardware.metrics.CPUMetrics;
import org.photonvision.common.hardware.metrics.GPUMetrics;
import org.photonvision.common.hardware.metrics.RAMMetrics;
import org.photonvision.common.hardware.metrics.MetricsManager;
public class HardwareTest {
@Test
public void testHardware() {
CPUMetrics cpuMetrics = new CPUMetrics();
RAMMetrics ramMetrics = new RAMMetrics();
GPUMetrics gpuMetrics = new GPUMetrics();
MetricsManager mm = new MetricsManager();
if (!Platform.isRaspberryPi()) return;
System.out.println("Testing on platform: " + Platform.currentPlatform);
System.out.println("Testing on platform: " + Platform.getPlatformName());
System.out.println("Printing CPU Info:");
System.out.println("Memory: " + cpuMetrics.getMemory() + "MB");
System.out.println("Temperature: " + cpuMetrics.getTemp() + "C");
System.out.println("Utilization: : " + cpuMetrics.getUtilization() + "%");
System.out.println("Memory: " + mm.getMemory() + "MB");
System.out.println("Temperature: " + mm.getTemp() + "C");
System.out.println("Utilization: : " + mm.getUtilization() + "%");
System.out.println("Printing GPU Info:");
System.out.println("Memory: " + gpuMetrics.getGPUMemorySplit() + "MB");
System.out.println("Memory: " + mm.getGPUMemorySplit() + "MB");
System.out.println("Printing RAM Info: ");
System.out.println("Used RAM: : " + ramMetrics.getUsedRam() + "MB");
System.out.println("Used RAM: : " + mm.getUsedRam() + "MB");
}
@Test

View File

@@ -29,6 +29,7 @@ import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.hardware.HardwareManager;
import org.photonvision.common.hardware.PiVersion;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.LogLevel;
@@ -321,8 +322,8 @@ public class Main {
"Starting PhotonVision version "
+ PhotonVersion.versionString
+ " on "
+ Platform.currentPlatform.toString()
+ (Platform.isRaspberryPi() ? (" (Pi " + Platform.currentPiVersion.name() + ")") : ""));
+ Platform.getPlatformName()
+ (Platform.isRaspberryPi() ? (" (Pi " + PiVersion.getPiVersion() + ")") : ""));
ConfigManager.getInstance().load(); // init config manager
ConfigManager.getInstance().requestSave();

View File

@@ -35,7 +35,6 @@ import org.photonvision.common.configuration.NetworkConfig;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.hardware.HardwareManager;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.hardware.metrics.MetricsPublisher;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.networking.NetworkManager;
@@ -113,37 +112,29 @@ public class RequestHandler {
logger.info("New .jar uploaded successfully.");
if (file != null) {
if (Platform.isLinux()) {
try {
Path filePath =
Paths.get(ProgramDirectoryUtilities.getProgramDirectory(), "photonvision.jar");
File targetFile = new File(filePath.toString());
var stream = new FileOutputStream(targetFile);
try {
Path filePath =
Paths.get(ProgramDirectoryUtilities.getProgramDirectory(), "photonvision.jar");
File targetFile = new File(filePath.toString());
var stream = new FileOutputStream(targetFile);
logger.info(
"Streaming user-provided " + file.getFilename() + " into " + targetFile.toString());
logger.info(
"Streaming user-provided " + file.getFilename() + " into " + targetFile.toString());
file.getContent().transferTo(stream);
stream.close();
file.getContent().transferTo(stream);
stream.close();
ctx.status(200);
logger.info("New .jar in place, going down for restart...");
restartProgram();
} catch (FileNotFoundException e) {
logger.error(
".jar of this program could not be found. How the heck this program started in the first place is a mystery.");
ctx.status(500);
} catch (IOException e) {
logger.error("Could not overwrite the .jar for this instance of photonvision.");
ctx.status(500);
}
} else {
logger.error("Hot .jar replace currently only supported on Linux. Ignoring.");
ctx.status(200);
logger.info("New .jar in place, going down for restart...");
restartProgram();
} catch (FileNotFoundException e) {
logger.error(
".jar of this program could not be found. How the heck this program started in the first place is a mystery.");
ctx.status(500);
} catch (IOException e) {
logger.error("Could not overwrite the .jar for this instance of photonvision.");
ctx.status(500);
}
} else {
logger.error("Couldn't read provided file for new .jar! Ignoring.");
ctx.status(500);
@@ -274,7 +265,7 @@ public class RequestHandler {
}
public static void sendMetrics(Context ctx) {
MetricsPublisher.getInstance().publish();
HardwareManager.getInstance().publishMetrics();
// TimedTaskManager.getInstance().addOneShotTask(() -> RoborioFinder.getInstance().findRios(),
// 0);
ctx.status(200);