001/*
002 * Copyright (C) Photon Vision.
003 *
004 * This program is free software: you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License as published by
006 * the Free Software Foundation, either version 3 of the License, or
007 * (at your option) any later version.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
016 */
017
018package org.photonvision.common.hardware.metrics;
019
020import java.io.PrintWriter;
021import java.io.StringWriter;
022import java.util.HashMap;
023import org.photonvision.common.configuration.ConfigManager;
024import org.photonvision.common.configuration.HardwareConfig;
025import org.photonvision.common.dataflow.DataChangeService;
026import org.photonvision.common.dataflow.events.OutgoingUIEvent;
027import org.photonvision.common.hardware.Platform;
028import org.photonvision.common.hardware.metrics.cmds.CmdBase;
029import org.photonvision.common.hardware.metrics.cmds.FileCmds;
030import org.photonvision.common.hardware.metrics.cmds.LinuxCmds;
031import org.photonvision.common.hardware.metrics.cmds.PiCmds;
032import org.photonvision.common.hardware.metrics.cmds.RK3588Cmds;
033import org.photonvision.common.logging.LogGroup;
034import org.photonvision.common.logging.Logger;
035import org.photonvision.common.networking.NetworkUtils;
036import org.photonvision.common.util.ShellExec;
037
038public class MetricsManager {
039    final Logger logger = new Logger(MetricsManager.class, LogGroup.General);
040
041    CmdBase cmds;
042
043    private final ShellExec runCommand = new ShellExec(true, true);
044
045    public void setConfig(HardwareConfig config) {
046        if (config.hasCommandsConfigured()) {
047            cmds = new FileCmds();
048        } else if (Platform.isRaspberryPi()) {
049            cmds = new PiCmds(); // Pi's can use a hardcoded command set
050        } else if (Platform.isRK3588()) {
051            cmds = new RK3588Cmds(); // RK3588 chipset hardcoded command set
052        } else if (Platform.isLinux()) {
053            cmds = new LinuxCmds(); // Linux/Unix platforms assume a nominal command set
054        } else {
055            cmds = new CmdBase(); // default - base has no commands
056        }
057
058        cmds.initCmds(config);
059    }
060
061    public String safeExecute(String str) {
062        if (str.isEmpty()) return "";
063        try {
064            return execute(str);
065        } catch (Exception e) {
066            return "****";
067        }
068    }
069
070    private String cpuMemSave = null;
071
072    public String getMemory() {
073        if (cmds.cpuMemoryCommand.isEmpty()) return "";
074        if (cpuMemSave == null) {
075            // save the value and only run it once
076            cpuMemSave = execute(cmds.cpuMemoryCommand);
077        }
078        return cpuMemSave;
079    }
080
081    public String getTemp() {
082        return safeExecute(cmds.cpuTemperatureCommand);
083    }
084
085    public String getUtilization() {
086        return safeExecute(cmds.cpuUtilizationCommand);
087    }
088
089    public String getUptime() {
090        return safeExecute(cmds.cpuUptimeCommand);
091    }
092
093    public String getThrottleReason() {
094        return safeExecute(cmds.cpuThrottleReasonCmd);
095    }
096
097    public String getNpuUsage() {
098        return safeExecute(cmds.npuUsageCommand);
099    }
100
101    private String gpuMemSave = null;
102
103    public String getGPUMemorySplit() {
104        if (gpuMemSave == null) {
105            // only needs to run once
106            gpuMemSave = safeExecute(cmds.gpuMemoryCommand);
107        }
108        return gpuMemSave;
109    }
110
111    public String getMallocedMemory() {
112        return safeExecute(cmds.gpuMemUsageCommand);
113    }
114
115    public String getUsedDiskPct() {
116        return safeExecute(cmds.diskUsageCommand);
117    }
118
119    // TODO: Output in MBs for consistency
120    public String getUsedRam() {
121        return safeExecute(cmds.ramUsageCommand);
122    }
123
124    public String getIpAddress() {
125        String dev = ConfigManager.getInstance().getConfig().getNetworkConfig().networkManagerIface;
126        logger.debug("Requesting IP addresses for \"" + dev + "\"");
127        String addr = NetworkUtils.getIPAddresses(dev);
128        logger.debug("Got value \"" + addr + "\"");
129        return addr;
130    }
131
132    public void publishMetrics() {
133        logger.debug("Publishing Metrics...");
134        final var metrics = new HashMap<String, String>();
135
136        metrics.put("cpuTemp", this.getTemp());
137        metrics.put("cpuUtil", this.getUtilization());
138        metrics.put("cpuMem", this.getMemory());
139        metrics.put("cpuThr", this.getThrottleReason());
140        metrics.put("cpuUptime", this.getUptime());
141        metrics.put("gpuMem", this.getGPUMemorySplit());
142        metrics.put("ramUtil", this.getUsedRam());
143        metrics.put("gpuMemUtil", this.getMallocedMemory());
144        metrics.put("diskUtilPct", this.getUsedDiskPct());
145        metrics.put("npuUsage", this.getNpuUsage());
146        metrics.put("ipAddress", this.getIpAddress());
147
148        DataChangeService.getInstance().publishEvent(OutgoingUIEvent.wrappedOf("metrics", metrics));
149    }
150
151    public synchronized String execute(String command) {
152        try {
153            runCommand.executeBashCommand(command);
154            return runCommand.getOutput();
155        } catch (Exception e) {
156            StringWriter sw = new StringWriter();
157            PrintWriter pw = new PrintWriter(sw);
158            e.printStackTrace(pw);
159
160            logger.error(
161                    "Command: \""
162                            + command
163                            + "\" returned an error!"
164                            + "\nOutput Received: "
165                            + runCommand.getOutput()
166                            + "\nStandard Error: "
167                            + runCommand.getError()
168                            + "\nCommand completed: "
169                            + runCommand.isOutputCompleted()
170                            + "\nError completed: "
171                            + runCommand.isErrorCompleted()
172                            + "\nExit code: "
173                            + runCommand.getExitCode()
174                            + "\n Exception: "
175                            + e
176                            + sw);
177            return "";
178        }
179    }
180}