From c7dc918095f2235241d1d60cdca8308af372801c Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 29 Nov 2023 23:46:36 +0100 Subject: [PATCH] feat: extract all output power components as recorded in metadata --- .../powermetrics/MacOSPowermetricsSensor.java | 74 +++++++++++-------- .../MacOSPowermetricsSensorTest.java | 11 ++- 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java b/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java index e7b0afce..6006c341 100644 --- a/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java +++ b/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java @@ -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"); @@ -137,14 +139,12 @@ Map 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(trackedPIDs.size()); + final var powerComponents = new HashMap(metadata.componentCardinality()); while ((line = input.readLine()) != null) { if (headerLinesToSkip != 0) { headerLinesToSkip--; @@ -176,40 +176,37 @@ Map 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(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) { @@ -217,10 +214,23 @@ Map extractPowerMeasure(InputStream powerMeasureInput) } } - 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 powerComponents) { + // looking for line fitting the: " 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 { diff --git a/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java b/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java index 2fe438d7..f9dd7fce 100644 --- a/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java +++ b/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java @@ -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]); } }