diff --git a/backend/src/main/java/net/laprun/sustainability/power/sensors/AbstractPowerSensor.java b/backend/src/main/java/net/laprun/sustainability/power/sensors/AbstractPowerSensor.java index c83e4270..c447b9b3 100644 --- a/backend/src/main/java/net/laprun/sustainability/power/sensors/AbstractPowerSensor.java +++ b/backend/src/main/java/net/laprun/sustainability/power/sensors/AbstractPowerSensor.java @@ -71,11 +71,11 @@ public void unregister(RegisteredPID registeredPID) { } @Override - public void start(long samplingPeriodInMillis) throws Exception { + public void start() throws Exception { if (!started) { lastUpdateEpoch = System.currentTimeMillis(); started = true; - doStart(samplingPeriodInMillis); + doStart(); } } @@ -95,14 +95,14 @@ public Set getRegisteredPIDs() { return measures.trackedPIDsAsString(); } - protected abstract void doStart(long samplingFrequencyInMillis); + protected abstract void doStart(); @Override public Measures update(Long tick, Map cpuShares) { final long newUpdateStartEpoch = System.currentTimeMillis(); + Log.debugf("Sensor update last called: %dms ago", newUpdateStartEpoch - lastUpdateEpoch); final var measures = doUpdate(lastUpdateEpoch, newUpdateStartEpoch, cpuShares); - lastUpdateEpoch = measures.lastMeasuredUpdateEndEpoch() > 0 ? measures.lastMeasuredUpdateEndEpoch() - : newUpdateStartEpoch; + lastUpdateEpoch = newUpdateStartEpoch; return measures; } diff --git a/backend/src/main/java/net/laprun/sustainability/power/sensors/MapMeasures.java b/backend/src/main/java/net/laprun/sustainability/power/sensors/MapMeasures.java index f110833b..18cd5a64 100644 --- a/backend/src/main/java/net/laprun/sustainability/power/sensors/MapMeasures.java +++ b/backend/src/main/java/net/laprun/sustainability/power/sensors/MapMeasures.java @@ -3,13 +3,11 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.function.Consumer; import net.laprun.sustainability.power.SensorMeasure; public class MapMeasures implements Measures { private final ConcurrentMap measures = new ConcurrentHashMap<>(); - private long lastMeasuredUpdateStartEpoch; private final PIDRegistry registry = new PIDRegistry(); @Override @@ -43,7 +41,6 @@ public int numberOfTrackedPIDs() { @Override public void record(RegisteredPID pid, SensorMeasure sensorMeasure) { - lastMeasuredUpdateStartEpoch = sensorMeasure.endMs(); measures.put(pid, sensorMeasure); } @@ -51,14 +48,4 @@ public void record(RegisteredPID pid, SensorMeasure sensorMeasure) { public SensorMeasure getOrDefault(RegisteredPID pid) { return measures.getOrDefault(pid, SensorMeasure.missing); } - - @Override - public long lastMeasuredUpdateEndEpoch() { - return lastMeasuredUpdateStartEpoch; - } - - @Override - public void forEach(Consumer consumer) { - measures.keySet().forEach(pid -> consumer.accept(getOrDefault(pid))); - } } diff --git a/backend/src/main/java/net/laprun/sustainability/power/sensors/Measures.java b/backend/src/main/java/net/laprun/sustainability/power/sensors/Measures.java index 25896497..25c62701 100644 --- a/backend/src/main/java/net/laprun/sustainability/power/sensors/Measures.java +++ b/backend/src/main/java/net/laprun/sustainability/power/sensors/Measures.java @@ -1,7 +1,6 @@ package net.laprun.sustainability.power.sensors; import java.util.Set; -import java.util.function.Consumer; import net.laprun.sustainability.power.SensorMeasure; @@ -63,16 +62,8 @@ public interface Measures { */ SensorMeasure getOrDefault(RegisteredPID pid); - /** - * Returns the last measured end epoch of an update, if it exists. - * - * @return the last measured end epoch of an update, or {@code -1} if the measure didn't provide that information - */ - long lastMeasuredUpdateEndEpoch(); - + @SuppressWarnings("unused") default SensorMeasure getSystemTotal() { return getOrDefault(RegisteredPID.SYSTEM_TOTAL_REGISTERED_PID); } - - void forEach(Consumer consumer); } diff --git a/backend/src/main/java/net/laprun/sustainability/power/sensors/PowerSensor.java b/backend/src/main/java/net/laprun/sustainability/power/sensors/PowerSensor.java index 22ccdab1..a8dd243e 100644 --- a/backend/src/main/java/net/laprun/sustainability/power/sensors/PowerSensor.java +++ b/backend/src/main/java/net/laprun/sustainability/power/sensors/PowerSensor.java @@ -26,6 +26,10 @@ default boolean supportsProcessAttribution() { void enableCPUShareSampling(boolean enable); + default long adjustSamplingPeriodIfNeeded(long requestedSamplingPeriodInMillis) { + return requestedSamplingPeriodInMillis; + } + /** * Stops measuring power consumption */ @@ -50,10 +54,9 @@ default void stop() { /** * Starts emitting power consumption measures at the given frequency * - * @param samplingFrequencyInMillis the number of milliseconds between emitted measures * @throws Exception if the sensor couldn't be started for some reason */ - void start(long samplingFrequencyInMillis) throws Exception; + void start() throws Exception; /** * Registers the provided process identifier (pid) with the sensor in case it can provide per-process measures. For sensors diff --git a/backend/src/main/java/net/laprun/sustainability/power/sensors/SamplingMeasurer.java b/backend/src/main/java/net/laprun/sustainability/power/sensors/SamplingMeasurer.java index fc2207f0..b31dada4 100644 --- a/backend/src/main/java/net/laprun/sustainability/power/sensors/SamplingMeasurer.java +++ b/backend/src/main/java/net/laprun/sustainability/power/sensors/SamplingMeasurer.java @@ -13,6 +13,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; +import io.quarkus.logging.Log; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.infrastructure.Infrastructure; import io.smallrye.mutiny.subscription.Cancellable; @@ -26,7 +27,7 @@ @ApplicationScoped public class SamplingMeasurer { - public static final String DEFAULT_SAMPLING_PERIOD = "PT0.5S"; + public static final String DEFAULT_SAMPLING_PERIOD = "PT1S"; @Inject PowerSensor sensor; @@ -67,7 +68,9 @@ RegisteredPID track(long pid) throws Exception { final var registeredPID = sensor.register(pid); if (!sensor.isStarted()) { - sensor.start(samplingPeriod.toMillis()); + final var adjusted = sensor.adjustSamplingPeriodIfNeeded(samplingPeriod.toMillis()); + Log.infof("%s sensor adjusted its sampling period to %dms", sensor.getClass().getSimpleName(), adjusted); + sensor.start(); final var samplingTicks = Multi.createFrom().ticks().every(samplingPeriod); @@ -76,7 +79,7 @@ RegisteredPID track(long pid) throws Exception { final var cpuSharesMulti = Multi.createFrom().ticks() // over sample but over a shorter period to ensure we have an average that covers most of the sampling period .every(samplingPeriod.minus(50, ChronoUnit.MILLIS).dividedBy(overSamplingFactor)) - .runSubscriptionOn(Infrastructure.getDefaultExecutor()) + .runSubscriptionOn(Infrastructure.getDefaultWorkerPool()) .map(tick -> CPUShare.cpuSharesFor(sensor.getRegisteredPIDs())) .group() .intoLists() @@ -100,7 +103,6 @@ RegisteredPID track(long pid) throws Exception { .combining() .streams(samplingTicks, cpuSharesMulti) .asTuple() - .log() .map(this::updateSensor); } else { periodicSensorCheck = samplingTicks @@ -152,6 +154,7 @@ public void stop() { manuallyTrackedProcesses.values().forEach(Cancellable::cancel); } + @SuppressWarnings("unused") public void stopTrackingProcess(long processId) { sensor.unregister(RegisteredPID.create(processId)); // cancel associated process tracking diff --git a/backend/src/main/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensor.java b/backend/src/main/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensor.java index 51a0449f..8cb6078d 100644 --- a/backend/src/main/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensor.java +++ b/backend/src/main/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensor.java @@ -58,6 +58,7 @@ private static SortedMap defaultRAPLFiles() { return files; } + @SuppressWarnings("NonAsciiCharacters") IntelRAPLSensor(SortedMap files) { if (files.isEmpty()) throw new RuntimeException("Failed to get RAPL energy readings, probably due to lack of read access "); @@ -101,7 +102,7 @@ private static boolean addFileIfReadable(String raplFileAsString, SortedMap trackedPIDs = new HashSet<>(); - private SensorMeasure measure; - private final PIDRegistry registry = new PIDRegistry(); - - void singleMeasure(SensorMeasure sensorMeasure) { - this.measure = sensorMeasure; - } - - @Override - public RegisteredPID register(long pid) { - final var registeredPID = RegisteredPID.create(pid); - trackedPIDs.add(registeredPID); - registry.register(registeredPID); - return registeredPID; - } - - @Override - public void unregister(RegisteredPID registeredPID) { - trackedPIDs.remove(registeredPID); - registry.unregister(registeredPID); - } - - @Override - public Set trackedPIDs() { - return trackedPIDs; - } - - @Override - public Set trackedPIDsAsString() { - return registry.pids(); - } - - @Override - public int numberOfTrackedPIDs() { - return trackedPIDs.size(); - } - - @Override - public void record(RegisteredPID pid, SensorMeasure sensorMeasure) { - throw new UnsupportedOperationException("Shouldn't be needed"); - } - - @Override - public SensorMeasure getOrDefault(RegisteredPID pid) { - return trackedPIDs.contains(pid) && measure != null ? measure : SensorMeasure.missing; - } - - @Override - public void forEach(Consumer consumer) { - throw new UnsupportedOperationException("todo: not implemented yet"); - } - - @Override - public long lastMeasuredUpdateEndEpoch() { - return -1; - } -} diff --git a/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java b/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java index 34703689..8ec3f32a 100644 --- a/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java +++ b/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java @@ -106,7 +106,7 @@ protected SensorMetadata nativeMetadata() { } @Override - protected void doStart(long samplingFrequencyInMillis) { + protected void doStart() { // nothing to do here by default lastCalled = System.currentTimeMillis(); } diff --git a/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/NuProcessWrapper.java b/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/NuProcessWrapper.java index 091fbdc9..e33ce079 100644 --- a/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/NuProcessWrapper.java +++ b/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/NuProcessWrapper.java @@ -10,6 +10,7 @@ public class NuProcessWrapper implements ProcessWrapper { private PowermetricsProcessHandler measureHandler; private String periodInMilliSecondsAsString; + @SuppressWarnings("UnusedReturnValue") private NuProcess exec(PowermetricsProcessHandler handler) { if (handler == null) throw new IllegalArgumentException("Handler cannot be null"); @@ -29,8 +30,6 @@ public InputStream streamForMetadata() { @Override public void start(long periodInMilliSeconds) { - // todo? check if asked period is the same as the current used one - periodInMilliSeconds = periodInMilliSeconds > 100 ? periodInMilliSeconds - 50 : periodInMilliSeconds; this.periodInMilliSecondsAsString = Long.toString(periodInMilliSeconds); } diff --git a/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/ProcessMacOSPowermetricsSensor.java b/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/ProcessMacOSPowermetricsSensor.java index 71c19058..dfea1f9b 100644 --- a/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/ProcessMacOSPowermetricsSensor.java +++ b/backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/ProcessMacOSPowermetricsSensor.java @@ -3,7 +3,9 @@ import java.io.InputStream; public class ProcessMacOSPowermetricsSensor extends MacOSPowermetricsSensor { + private static final int MINIMAL_PERIOD_MS = 700; private final ProcessWrapper processWrapper = new NuProcessWrapper(); + private long samplingPeriodInMillis; public ProcessMacOSPowermetricsSensor() { // extract metadata @@ -15,8 +17,21 @@ public ProcessMacOSPowermetricsSensor() { } @Override - public void doStart(long frequency) { - processWrapper.start(frequency); + public long adjustSamplingPeriodIfNeeded(long requestedSamplingPeriodInMillis) { + if (requestedSamplingPeriodInMillis < MINIMAL_PERIOD_MS) { + throw new IllegalArgumentException( + "powermetrics takes around 500ms of incompressible time for each sample, set the sampling period to at least " + + + MINIMAL_PERIOD_MS + " milliseconds to get useful results to also account for processing time."); + } + samplingPeriodInMillis = requestedSamplingPeriodInMillis - 600; + return samplingPeriodInMillis; + } + + @Override + public void doStart() { + super.doStart(); + processWrapper.start(samplingPeriodInMillis); } @Override @@ -26,7 +41,9 @@ protected InputStream getInputStream() { @Override public void stop() { - processWrapper.stop(); - super.stop(); + if (isStarted()) { + processWrapper.stop(); + super.stop(); + } } } diff --git a/backend/src/main/java/net/laprun/sustainability/power/sensors/test/TestPowerSensor.java b/backend/src/main/java/net/laprun/sustainability/power/sensors/test/TestPowerSensor.java index 2a09c460..5cd751d9 100644 --- a/backend/src/main/java/net/laprun/sustainability/power/sensors/test/TestPowerSensor.java +++ b/backend/src/main/java/net/laprun/sustainability/power/sensors/test/TestPowerSensor.java @@ -34,7 +34,7 @@ protected SensorMetadata nativeMetadata() { } @Override - public void doStart(long samplingFrequencyInMillis) { + public void doStart() { // nothing to do } diff --git a/backend/src/test/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensorTest.java b/backend/src/test/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensorTest.java index 7eab2b97..3b8f1634 100644 --- a/backend/src/test/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensorTest.java +++ b/backend/src/test/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensorTest.java @@ -100,7 +100,7 @@ protected void readAndRecordSensor(BiConsumer onReadingSensorValu void wattComputationShouldWork() throws Exception { final var raplFile = new TestRAPLFile(10000L, 20000L, 30000L); final var sensor = new TestIntelRAPLSensor(new TreeMap<>(Map.of("sensor", raplFile))); - sensor.start(500); + sensor.start(); Thread.sleep(10); // ensure we get enough time between the measure performed during start and the first update final var pid = sensor.register(1234L); final var measures = sensor.update(1L, Map.of()); @@ -118,7 +118,7 @@ void shouldIncludeCPUShareIfRequested() throws Exception { final var raplFile = new TestRAPLFile(10000L, 20000L, 30000L); final var sensor = new TestIntelRAPLSensor(new TreeMap<>(Map.of("sensor", raplFile))); sensor.enableCPUShareSampling(true); - sensor.start(500); + sensor.start(); final var pid = sensor.register(1234L); double cpuShare = 0.3; final var measures = sensor.update(1L, Map.of("1234", cpuShare)); @@ -132,6 +132,7 @@ void shouldIncludeCPUShareIfRequested() throws Exception { assertEquals(cpuShare, components[2]); } + @SuppressWarnings("SameParameterValue") private SensorMetadata loadMetadata(String... fileNames) { Class clazz = getClass(); final var files = Arrays.stream(fileNames) diff --git a/backend/src/test/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java b/backend/src/test/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java index 9affdda4..99ef92af 100644 --- a/backend/src/test/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java +++ b/backend/src/test/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java @@ -50,12 +50,12 @@ private static void checkComponent(SensorMetadata metadata, String name, int ind @Test void extractPowerMeasureForM1Max() { - checkPowerMeasure("sonoma-m1max.txt", 211, MacOSPowermetricsSensor.CPU, 1012); + checkPowerMeasure("sonoma-m1max.txt", 211, MacOSPowermetricsSensor.CPU); } @Test void extractPowerMeasureForM2() { - checkPowerMeasure("monterey-m2.txt", 10, MacOSPowermetricsSensor.CPU, 1012); + checkPowerMeasure("monterey-m2.txt", 10, MacOSPowermetricsSensor.CPU); } @Test @@ -74,7 +74,6 @@ void extractPowerMeasureForM4() { // Process CPU power should be equal to sample ms/s divided for process (here: 116.64) by total samples (1222.65) times total CPU power final var pidCPUShare = 224.05 / totalCPUTime; assertEquals(pidCPUShare * totalCPUPower, getComponent(measure, pid0, cpu)); - assertEquals(startUpdateEpoch + 10458, measure.lastMeasuredUpdateEndEpoch()); } @Test @@ -94,12 +93,11 @@ void checkTotalPowerMeasureEvenWhenRegisteredProcessIsNotFound() { assertEquals(0, getTotalSystemComponent(measure, metadata, MacOSPowermetricsSensor.GPU)); assertEquals(25, getTotalSystemComponent(measure, metadata, MacOSPowermetricsSensor.PACKAGE)); assertEquals(1.0, getTotalSystemComponent(measure, metadata, MacOSPowermetricsSensor.CPU_SHARE)); - assertEquals(startUpdateEpoch + 1012, measure.lastMeasuredUpdateEndEpoch()); } @Test void extractPowerMeasureForIntel() { - checkPowerMeasure("sonoma-intel.txt", 8.53f, MacOSPowermetricsSensor.PACKAGE, 1002); + checkPowerMeasure("sonoma-intel.txt", 8.53f, MacOSPowermetricsSensor.PACKAGE); } @Test @@ -120,8 +118,7 @@ void extractionShouldWorkForLowProcessIds() { assertEquals(pidCPUShare * 211, getComponent(measure, pid1, cpu)); } - private static void checkPowerMeasure(String testFileName, float total, String totalMeasureName, - long expectedMeasureDuration) { + private static void checkPowerMeasure(String testFileName, float total, String totalMeasureName) { final var startUpdateEpoch = System.currentTimeMillis(); final var sensor = new ResourceMacOSPowermetricsSensor(testFileName, startUpdateEpoch); final var metadata = sensor.metadata(); @@ -145,7 +142,6 @@ private static void checkPowerMeasure(String testFileName, float total, String t assertEquals(0.0, getComponent(measure, pid1, gpuMetadata)); assertEquals(0.0, getComponent(measure, pid2, gpuMetadata)); } - assertEquals(startUpdateEpoch + expectedMeasureDuration, measure.lastMeasuredUpdateEndEpoch()); } private static double getComponent(Measures measure, RegisteredPID pid1, SensorMetadata.ComponentMetadata metadata) {