Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load Monitoring for RoboRIO #39

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/AdvantageKit.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Copy link

@srimanachanta srimanachanta Apr 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Empty file.
221 changes: 221 additions & 0 deletions junction/core/src/org/littletonrobotics/junction/LoadMonitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package org.littletonrobotics.junction;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

// Based on work done by Teams 254 & 1736 in the casserole RioLoadMonitor

public class LoadMonitor {
/**
* Rate of update of the load variables in milliseconds. 1s should be enough?
*/
public static final int UPDATE_RATE_MS = 500;
static final String CPU_LOAD_VIRTUAL_FILE = "/proc/stat";
static final String MEM_LOAD_VIRTUAL_FILE = "/proc/meminfo";
private static final String OS = System.getProperty("os.name").toLowerCase();
public static LoadMonitor instance;
/**
* Overall (all-cpu) load percentage (non-idle time)
*/
public double totalCPULoadPct = 0;
/**
* Memory used percentage including cached memory
*/
public double totalMemUsedPct = 0;
/**
* JVM memory used percentage, may have discrete jumps when garbage collection occurs or jvm dedicated
* memory is resized.
*/
public double totalJVMMemUsedPct = 0;
double prevUserTime = 0;
double prevNicedTime = 0;
double prevSystemTime = 0;
double prevIdleTime = 0;

// Will prevent burning processor cycles if data is unreachable
boolean giveUp = false;
/**
* Constructor. Initializes measurement system and starts a two hertz
* background thread to gather load info
*/
public LoadMonitor() {
if(!OS.contains("nix") && !OS.contains("nux") && !OS.contains("aix")){
System.out.println("Advantage Kit LoadMonitor is only supported for the RoboRIO & other linux based " +
"operating systems. LoadMonitor will not be active.");
return;
}

//Reset give up flag
giveUp = false;

// Kick off monitor in new thread.
// Thanks to Team 254 for an example of how to do this!
Thread monitorThread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
periodicUpdate();
Thread.sleep(UPDATE_RATE_MS);
}
} catch (Exception e) {
e.printStackTrace();
}

});
//Set up thread properties and start it off
monitorThread.setName("Load Monitor Thread");
monitorThread.setPriority(Thread.MIN_PRIORITY);
monitorThread.start();
}

public static LoadMonitor getInstance() {
if (instance == null) {
instance = new LoadMonitor();
}
return instance;
}

/**
* Updates the loads based on info from the /proc virtual
* filesystem. Should be called in the background. will be called
* internally by the thread started in the constructor
*/
private void periodicUpdate() {
String CPUTotalLoadRawLine = "";
File file;

if (!giveUp) {
// CPU load parsing
// Get meaningful line from CPU load virtual file
file = new File(CPU_LOAD_VIRTUAL_FILE);

try {
BufferedReader br = new BufferedReader(new FileReader(file));
CPUTotalLoadRawLine = br.readLine();
while (CPUTotalLoadRawLine != null) {
if (CPUTotalLoadRawLine.startsWith("cpu ")) {
break;
}
CPUTotalLoadRawLine = br.readLine();
}
br.close();
} catch (IOException e) {
System.out.println("WARNING: cannot get raw CPU load data. Giving up future attempts to read.");
e.printStackTrace();
giveUp = true;
}

assert CPUTotalLoadRawLine != null;
String[] tokens = CPUTotalLoadRawLine.split(" ");

double curUserTime = 0;
double curNicedTime = 0;
double curSystemTime = 0;
double curIdleTime = 0;
try {
curUserTime = Double.parseDouble(tokens[2]); //Start at 2, because RIO parses an extra empty token at 1
curNicedTime = Double.parseDouble(tokens[3]);
curSystemTime = Double.parseDouble(tokens[4]);
curIdleTime = Double.parseDouble(tokens[5]);
} catch (Exception e) {
System.out.println("WARNING: cannot parse CPU load. Giving up future attempts to read.");
e.printStackTrace();
giveUp = true;
}

// Calculate change in time counters since last measurement
double deltaUserTime = curUserTime - prevUserTime;
double deltaNicedTime = curNicedTime - prevNicedTime;
double deltaSystemTime = curSystemTime - prevSystemTime;
double deltaIdleTime = curIdleTime - prevIdleTime;

prevUserTime = curUserTime;
prevNicedTime = curNicedTime;
prevSystemTime = curSystemTime;
prevIdleTime = curIdleTime;

// Add up totals
double totalInUseTime = (deltaUserTime + deltaNicedTime + deltaSystemTime);
double totalTime = totalInUseTime + deltaIdleTime;

// Calculate CPU load to nearest tenth of percent
totalCPULoadPct = ((double) Math.round(totalInUseTime / totalTime * 1000.0)) / 10.0;

// Memory parsing and calculation
String memTotalStr = "";
String memFreeStr = "";
String line;

// Get meaningful line from CPU load virtual file
file = new File(MEM_LOAD_VIRTUAL_FILE);
try {
BufferedReader br = new BufferedReader(new FileReader(file));
line = br.readLine();
while (line != null) {
if (line.startsWith("MemTotal: ")) {
memTotalStr = line;
} else if (line.startsWith("MemFree:")) {
memFreeStr = line;
break;
}
line = br.readLine();
}
br.close();
} catch (IOException e) {
System.out.println("WARNING: cannot get raw memory load data. Giving up future attempts to read.");
e.printStackTrace();
giveUp = true;
}

String[] memTotalTokens = memTotalStr.split("\\s+");
String[] memFreeTokens = memFreeStr.split("\\s+");

// Parse values from proper tokens
double curTotalMem = 0;
double curFreeMem = 0;
try {
curTotalMem = Double.parseDouble(memTotalTokens[1]);
curFreeMem = Double.parseDouble(memFreeTokens[1]);
} catch (Exception e) {
System.out.println("WARNING: cannot parse memory load. Giving up future attempts to read.");
e.printStackTrace();
giveUp = true;
}

totalMemUsedPct = ((double) Math.round((1.0 - curFreeMem / curTotalMem) * 1000.0)) / 10.0;
}
// Grab JVM memory (outside give-up loop)
double jvmTotalMem = Runtime.getRuntime().totalMemory();
double jvmFreeMem = Runtime.getRuntime().freeMemory();
totalJVMMemUsedPct = (jvmTotalMem - jvmFreeMem) / (jvmTotalMem) * 100.0;
}

/**
* Getter for load percentage on CPU. Aggregate of all cores on the system, including
* both system and user processes.
*
* @return percentage of non-idle time, or 0 if percentage unavailable
*/
public double getCPUUtilization() {
return totalCPULoadPct;
}

/**
* Getter for the load percentage on memory. Total memory utilization includes cached memory.
*
* @return percentage of available system RAM, or 0 if percentage unavailable.
*/
public double getSystemMemoryUtilization() {
return totalMemUsedPct;
}

/**
* Getter for the load percentage on memory in the Java Virtual Machine.
*
* @return percentage of available JVM RAM, or 0 if percentage unavailable.
*/
public double getJVMMemoryUtilization() {
return totalJVMMemUsedPct;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.littletonrobotics.junction.inputs;

import org.littletonrobotics.conduit.ConduitApi;
import org.littletonrobotics.junction.LoadMonitor;
import org.littletonrobotics.junction.LogTable;
import org.littletonrobotics.junction.Logger;

Expand Down Expand Up @@ -45,6 +46,9 @@ public static class SystemStatsInputs implements LoggableInputs {
public boolean systemActive;
public CANStatus canStatus = new CANStatus();
public long epochTime;
public double cpuUtilizationPercent;
public double memoryUtilizationPercent;
public double jvmMemoryUtilizationPercent;

@Override
public void toLog(LogTable table) {
Expand Down Expand Up @@ -75,6 +79,10 @@ public void toLog(LogTable table) {
table.put("CANBus/ReceiveErrorCount", canStatus.receiveErrorCount);
table.put("CANBus/TransmitErrorCount", canStatus.transmitErrorCount);
table.put("EpochTimeMicros", epochTime);

table.put("LoadMonitor/CPUUsagePercent", cpuUtilizationPercent);
table.put("LoadMonitor/SystemMemoryUsagePercent", memoryUtilizationPercent);
table.put("LoadMonitor/JVMMemoryUsagePercent", jvmMemoryUtilizationPercent);
}

@Override
Expand Down Expand Up @@ -107,6 +115,10 @@ public void fromLog(LogTable table) {
(int) table.getInteger("CANBus/ReceiveErrorCount", canStatus.receiveErrorCount),
(int) table.getInteger("CANBus/TransmitErrorCount", canStatus.transmitErrorCount));
epochTime = table.getInteger("EpochTimeMicros", epochTime);

cpuUtilizationPercent = table.getDouble("LoadMonitor/CPUUsagePercent", cpuUtilizationPercent);
memoryUtilizationPercent = table.getDouble("LoadMonitor/SystemMemoryUsagePercent", memoryUtilizationPercent);
jvmMemoryUtilizationPercent = table.getDouble("LoadMonitor/JVMMemoryUsagePercent", jvmMemoryUtilizationPercent);
}
}

Expand Down Expand Up @@ -142,6 +154,10 @@ public void periodic() {
(int) conduit.getReceiveErrorCount(),
(int) conduit.getTransmitErrorCount());
sysInputs.epochTime = conduit.getEpochTime();

sysInputs.cpuUtilizationPercent = LoadMonitor.getInstance().getCPUUtilization();
sysInputs.memoryUtilizationPercent = LoadMonitor.getInstance().getSystemMemoryUtilization();
sysInputs.jvmMemoryUtilizationPercent = LoadMonitor.getInstance().getJVMMemoryUtilization();
}

logger.processInputs("SystemStats", sysInputs);
Expand Down