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

feat: extract all output power components as recorded in metadata #6

Merged
merged 1 commit into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class MacOSPowermetricsSensor implements PowerSensor {
public static final String PACKAGE = "Package";
private static final String COMBINED = "Combined";
public static final String CPU_SHARE = "cpuShare";
private static final String POWER_INDICATOR = " Power: ";
private static final int POWER_INDICATOR_LENGTH = POWER_INDICATOR.length();
private Process powermetrics;
private static final SensorMetadata.ComponentMetadata cpu = new SensorMetadata.ComponentMetadata(CPU, 0, "CPU power", true, "mW");
private static final SensorMetadata.ComponentMetadata gpu = new SensorMetadata.ComponentMetadata(GPU, 1, "GPU power", true, "mW");
Expand Down Expand Up @@ -137,14 +139,12 @@ Map<RegisteredPID, double[]> extractPowerMeasure(InputStream powerMeasureInput)

double totalSampledCPU = -1;
double totalSampledGPU = -1;
double totalCPUPower = 0;
double totalGPUPower = 0;
double totalANEPower = 0;
int headerLinesToSkip = 6;
int headerLinesToSkip = 10;
// copy the pids so that we can remove them as soon as we've processed them
final var pidsToProcess = new HashSet<>(trackedPIDs.keySet());
// start measure
final var measures = new HashMap<RegisteredPID, ProcessRecord>(trackedPIDs.size());
final var powerComponents = new HashMap<String, Integer>(metadata.componentCardinality());
while ((line = input.readLine()) != null) {
if (headerLinesToSkip != 0) {
headerLinesToSkip--;
Expand Down Expand Up @@ -176,51 +176,61 @@ Map<RegisteredPID, double[]> extractPowerMeasure(InputStream powerMeasureInput)
continue;
}

if (totalCPUPower == 0) {
// look for line that contains CPU power measure
if (line.startsWith("CPU Power")) {
totalCPUPower = extractMeasure(line);
}
continue;
}

if (line.startsWith("GPU Power")) {
totalGPUPower = extractMeasure(line);
continue;
}
extractPowerComponents(line, powerComponents);

if (line.startsWith("ANE Power")) {
totalANEPower = extractMeasure(line);
// we need an exit condition to break out of the loop, otherwise we'll just keep looping forever since there are always new lines since the process is periodical
// so break out once we've found all the extracted components (in this case, only cpuShare is not extracted)
// fixme: perhaps we should relaunch the process on each update loop instead of keeping it running? Not sure which is more efficient
if(powerComponents.size() == metadata.componentCardinality() - 1) {
break;
}
}

final var noGPU = totalSampledGPU == 0;
final var hasGPU = totalSampledGPU != 0;
double finalTotalSampledGPU = totalSampledGPU;
double finalTotalGPUPower = totalGPUPower;
double finalTotalSampledCPU = totalSampledCPU;
double finalTotalCPUPower = totalCPUPower;
double finalTotalANEPower = totalANEPower;
final var results = new HashMap<RegisteredPID, double[]>(measures.size());
measures.forEach((pid, record) -> {
final var attributedGPU = noGPU ? 0.0 : record.gpu / finalTotalSampledGPU * finalTotalGPUPower;
final var cpuShare = record.cpu / finalTotalSampledCPU;
results.put(pid, new double[]{
cpuShare * finalTotalCPUPower,
attributedGPU,
finalTotalANEPower,
cpuShare});
final var measure = new double[metadata.componentCardinality()];

metadata.components().forEach((name, cm) -> {
final var index = cm.index();
final var value = CPU_SHARE.equals(name) ? cpuShare : powerComponents.getOrDefault(name, 0);

if (cm.isAttributed()) {
final var attributionFactor = hasGPU && GPU.equals(name) ? record.gpu / finalTotalSampledGPU : cpuShare;
measure[index] = value * attributionFactor;
} else {
measure[index] = value;
}

results.put(pid, measure);
});
});
return results;
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}

private static double extractMeasure(String line) {
final var powerValue = line.split(":")[1];
final var powerInMilliwatts = powerValue.split("m")[0];
return Double.parseDouble(powerInMilliwatts);
private static void extractPowerComponents(String line, HashMap<String, Integer> powerComponents) {
// looking for line fitting the: "<name> Power: xxx mW" pattern and add all of the associated values together
final var powerIndex = line.indexOf(POWER_INDICATOR);
// lines with `-` as the second char are disregarded as of the form: "E-Cluster Power: 6 mW" which fits the pattern but shouldn't be considered
// also ignore Combined Power if available since it is the sum of the other components
if (powerIndex >= 0 && '-' != line.charAt(1) && !line.startsWith("Combined")) {
// get component name
final var name = line.substring(0, powerIndex);
// extract power value
final int value;
try {
value = Integer.parseInt(line.substring(powerIndex + POWER_INDICATOR_LENGTH, line.indexOf('m') - 1));
} catch (Exception e) {
throw new IllegalStateException("Cannot parse power value from line '" + line + "'", e);
}
powerComponents.put(name, value);
}
}

public void start(long frequency) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,14 @@ void extractPowerMeasure() {
// re-open the stream to read the measure this time
in = Thread.currentThread().getContextClassLoader().getResourceAsStream("sonoma-m1max.txt");
final var measure = sensor.extractPowerMeasure(in);
assertEquals(((23.88 / 1222.65) * 211), measure.get(pid1)[metadata.metadataFor(MacOSPowermetricsSensor.CPU).index()]);
assertEquals(((283.25 / 1222.65) * 211), measure.get(pid2)[metadata.metadataFor(MacOSPowermetricsSensor.CPU).index()]);
final var cpuIndex = metadata.metadataFor(MacOSPowermetricsSensor.CPU).index();
final var pid1CPUShare = 23.88 / 1222.65;
assertEquals((pid1CPUShare * 211), measure.get(pid1)[cpuIndex]);
final var pid2CPUShare = 283.25 / 1222.65;
assertEquals((pid2CPUShare * 211), measure.get(pid2)[cpuIndex]);
// check cpu share
final var cpuShareIndex = metadata.metadataFor(MacOSPowermetricsSensor.CPU_SHARE).index();
assertEquals(pid1CPUShare, measure.get(pid1)[cpuShareIndex]);
assertEquals(pid2CPUShare, measure.get(pid2)[cpuShareIndex]);
}
}