diff --git a/deployment/src/main/java/io/quarkiverse/power/deployment/PowerProcessor.java b/deployment/src/main/java/io/quarkiverse/power/deployment/PowerProcessor.java index 73f82ba..5f06c9f 100644 --- a/deployment/src/main/java/io/quarkiverse/power/deployment/PowerProcessor.java +++ b/deployment/src/main/java/io/quarkiverse/power/deployment/PowerProcessor.java @@ -1,7 +1,7 @@ package io.quarkiverse.power.deployment; import io.quarkiverse.power.deployment.devui.commands.PowerCommands; -import io.quarkiverse.power.runtime.PowerSensorProducer; +import io.quarkiverse.power.runtime.PowerMeasurer; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -20,7 +20,6 @@ FeatureBuildItem feature() { @BuildStep(onlyIf = IsDevelopment.class) void addConsoleCommands(BuildProducer commands) { // register dev console commands - final var producer = new PowerSensorProducer(); - commands.produce(new ConsoleCommandBuildItem(new PowerCommands(producer.sensor()))); + commands.produce(new ConsoleCommandBuildItem(new PowerCommands(PowerMeasurer.instance()))); } } diff --git a/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/PowerCommands.java b/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/PowerCommands.java index 97d4027..4fc81c9 100644 --- a/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/PowerCommands.java +++ b/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/PowerCommands.java @@ -5,14 +5,15 @@ import org.aesh.command.*; import org.aesh.command.invocation.CommandInvocation; -import io.quarkiverse.power.runtime.PowerSensor; +import io.quarkiverse.power.runtime.PowerMeasurer; +import io.quarkiverse.power.runtime.SensorMeasure; @GroupCommandDefinition(name = "power", description = "Power consumption commands", generateHelp = true) @SuppressWarnings("rawtypes") public class PowerCommands implements GroupCommand { - private final PowerSensor sensor; + private final PowerMeasurer sensor; - public PowerCommands(PowerSensor sensor) { + public PowerCommands(PowerMeasurer sensor) { this.sensor = sensor; } diff --git a/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/StartCommand.java b/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/StartCommand.java index ad22cae..aa251f5 100644 --- a/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/StartCommand.java +++ b/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/StartCommand.java @@ -6,13 +6,13 @@ import org.aesh.command.invocation.CommandInvocation; import org.aesh.command.option.Option; -import io.quarkiverse.power.runtime.PowerSensor; +import io.quarkiverse.power.runtime.PowerMeasurer; +import io.quarkiverse.power.runtime.SensorMeasure; import io.quarkus.deployment.console.QuarkusCommand; @CommandDefinition(name = "start", description = "Starts measuring power consumption of the current application") -@SuppressWarnings("rawtypes") public class StartCommand extends QuarkusCommand { - private final PowerSensor sensor; + private final PowerMeasurer sensor; @Option(name = "stopAfter", shortName = 's', description = "Automatically stop the measures after the specified duration in seconds", defaultValue = "-1") private long duration; @@ -20,7 +20,7 @@ public class StartCommand extends QuarkusCommand { @Option(name = "frequency", shortName = 'f', description = "The frequency at which measurements should be taken, in milliseconds", defaultValue = "1000") private long frequency; - public StartCommand(PowerSensor sensor) { + public StartCommand(PowerMeasurer sensor) { this.sensor = sensor; } diff --git a/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/StopCommand.java b/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/StopCommand.java index 7c30525..65569dc 100644 --- a/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/StopCommand.java +++ b/deployment/src/main/java/io/quarkiverse/power/deployment/devui/commands/StopCommand.java @@ -5,23 +5,22 @@ import org.aesh.command.CommandResult; import org.aesh.command.invocation.CommandInvocation; -import io.quarkiverse.power.runtime.PowerSensor; +import io.quarkiverse.power.runtime.PowerMeasurer; +import io.quarkiverse.power.runtime.SensorMeasure; import io.quarkus.deployment.console.QuarkusCommand; @CommandDefinition(name = "stop", description = "Stops power measurement and outputs accumulated power since measures were started") -@SuppressWarnings("rawtypes") public class StopCommand extends QuarkusCommand { - private final PowerSensor sensor; + private final PowerMeasurer sensor; - public StopCommand(PowerSensor sensor) { + public StopCommand(PowerMeasurer sensor) { this.sensor = sensor; } @Override public CommandResult doExecute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { - sensor.stop(); - sensor.outputConsumptionSinceStarted(commandInvocation::println); + sensor.stop(commandInvocation::println); return CommandResult.SUCCESS; } diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/PowerMeasure.java b/runtime/src/main/java/io/quarkiverse/power/runtime/PowerMeasure.java new file mode 100644 index 0000000..4ed79b1 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/PowerMeasure.java @@ -0,0 +1,9 @@ +package io.quarkiverse.power.runtime; + +public interface PowerMeasure extends SensorMeasure { + int numberOfSamples(); + + long duration(); + + M sensorMeasure(); +} diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/PowerMeasurer.java b/runtime/src/main/java/io/quarkiverse/power/runtime/PowerMeasurer.java new file mode 100644 index 0000000..be9b10b --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/PowerMeasurer.java @@ -0,0 +1,77 @@ +package io.quarkiverse.power.runtime; + +import java.lang.management.ManagementFactory; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import com.sun.management.OperatingSystemMXBean; + +import io.quarkiverse.power.runtime.sensors.IncrementableMeasure; +import io.quarkiverse.power.runtime.sensors.OngoingPowerMeasure; +import io.quarkiverse.power.runtime.sensors.PowerSensor; +import io.quarkiverse.power.runtime.sensors.PowerSensorProducer; + +public class PowerMeasurer { + public static final OperatingSystemMXBean osBean; + + static { + osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + } + + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + private ScheduledFuture scheduled; + private final PowerSensor sensor; + private OngoingPowerMeasure measure; + + private final static PowerMeasurer instance = new PowerMeasurer<>( + PowerSensorProducer.determinePowerSensor()); + + public static PowerMeasurer instance() { + return instance; + } + + public PowerMeasurer(PowerSensor sensor) { + this.sensor = sensor; + } + + public void start(long duration, long frequency, PowerSensor.Writer out) throws Exception { + if (measure == null) { + measure = sensor.start(duration, frequency, out); + + if (duration > 0) { + executor.schedule(() -> stop(out), duration, TimeUnit.SECONDS); + } + + scheduled = executor.scheduleAtFixedRate(() -> update(out), + 0, frequency, + TimeUnit.MILLISECONDS); + } + } + + private void update(PowerSensor.Writer out) { + sensor.update(measure, out); + measure.incrementSamples(); + } + + public PowerMeasure stop(PowerSensor.Writer out) { + if (measure != null) { + sensor.stop(); + scheduled.cancel(true); + } + outputConsumptionSinceStarted(out); + return measure; + } + + public PowerMeasure current() { + return measure; + } + + private void outputConsumptionSinceStarted(PowerSensor.Writer out) { + out = out == null ? System.out::println : out; + out.println("Consumed " + measure.total() + " mW over " + (measure.duration() / 1000) + + " seconds (" + measure.numberOfSamples() + " samples)"); + sensor.additionalInfo(out); + } +} diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/PowerSensor.java b/runtime/src/main/java/io/quarkiverse/power/runtime/PowerSensor.java deleted file mode 100644 index a0f861f..0000000 --- a/runtime/src/main/java/io/quarkiverse/power/runtime/PowerSensor.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.quarkiverse.power.runtime; - -import java.io.IOException; -import java.util.Optional; - -public interface PowerSensor { - - void start(long duration, long frequency, Writer out) throws IOException, Exception; - - T stop(); - - void outputConsumptionSinceStarted(Writer out); - - interface Writer { - void println(String message); - } - - interface Measure { - String CPU = "cpu"; - String GPU = "gpu"; - String TOTAL = "total"; - - double cpu(); - - Optional gpu(); - - Optional byKey(String key); - - double total(); - - int numberOfSamples(); - - long measureDuration(); - } - - interface IncrementableMeasure extends Measure { - - void addCPU(double v); - - void addGPU(double v); - - void incrementSamples(); - } -} diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/SensorMeasure.java b/runtime/src/main/java/io/quarkiverse/power/runtime/SensorMeasure.java new file mode 100644 index 0000000..dc775f4 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/SensorMeasure.java @@ -0,0 +1,28 @@ +package io.quarkiverse.power.runtime; + +import java.util.Optional; + +public interface SensorMeasure { + String CPU = "cpu"; + String GPU = "gpu"; + String TOTAL = "total"; + + double cpu(); + + default Optional gpu() { + return Optional.empty(); + } + + default Optional byKey(String key) { + return switch (key) { + case CPU -> Optional.of(cpu()); + case GPU -> gpu(); + case TOTAL -> Optional.of(total()); + default -> Optional.empty(); + }; + } + + default double total() { + return cpu() + gpu().orElse(0.0); + } +} diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/IncrementableMeasure.java b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/IncrementableMeasure.java new file mode 100644 index 0000000..97afb7c --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/IncrementableMeasure.java @@ -0,0 +1,11 @@ +package io.quarkiverse.power.runtime.sensors; + +import io.quarkiverse.power.runtime.SensorMeasure; + +public interface IncrementableMeasure extends SensorMeasure { + + void addCPU(double v); + + default void addGPU(double v) { + } +} diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/OngoingPowerMeasure.java b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/OngoingPowerMeasure.java new file mode 100644 index 0000000..0fd1781 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/OngoingPowerMeasure.java @@ -0,0 +1,64 @@ +package io.quarkiverse.power.runtime.sensors; + +import java.util.Optional; + +import io.quarkiverse.power.runtime.PowerMeasure; + +public class OngoingPowerMeasure + implements IncrementableMeasure, PowerMeasure { + private final M measure; + private final long startedAt; + private int samplesNb; + + public OngoingPowerMeasure(M measure) { + startedAt = System.currentTimeMillis(); + samplesNb = 0; + this.measure = measure; + } + + public void incrementSamples() { + samplesNb++; + } + + @Override + public double cpu() { + return measure.cpu(); + } + + @Override + public Optional gpu() { + return measure.gpu(); + } + + @Override + public Optional byKey(String key) { + return measure.byKey(key); + } + + @Override + public double total() { + return measure.total(); + } + + public int numberOfSamples() { + return samplesNb; + } + + public long duration() { + return System.currentTimeMillis() - startedAt; + } + + @Override + public void addCPU(double v) { + measure.addCPU(v); + } + + @Override + public void addGPU(double v) { + measure.addGPU(v); + } + + public M sensorMeasure() { + return measure; + } +} diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/PowerSensor.java b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/PowerSensor.java new file mode 100644 index 0000000..dea1251 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/PowerSensor.java @@ -0,0 +1,18 @@ +package io.quarkiverse.power.runtime.sensors; + +public interface PowerSensor { + + OngoingPowerMeasure start(long duration, long frequency, Writer out) throws Exception; + + default void stop() { + } + + void update(OngoingPowerMeasure ongoingMeasure, Writer out); + + default void additionalInfo(Writer out) { + } + + interface Writer { + void println(String message); + } +} diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/PowerSensorProducer.java b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/PowerSensorProducer.java similarity index 80% rename from runtime/src/main/java/io/quarkiverse/power/runtime/PowerSensorProducer.java rename to runtime/src/main/java/io/quarkiverse/power/runtime/sensors/PowerSensorProducer.java index 02776df..85ae755 100644 --- a/runtime/src/main/java/io/quarkiverse/power/runtime/PowerSensorProducer.java +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/PowerSensorProducer.java @@ -1,4 +1,4 @@ -package io.quarkiverse.power.runtime; +package io.quarkiverse.power.runtime.sensors; import jakarta.enterprise.inject.Produces; import jakarta.inject.Singleton; @@ -10,6 +10,10 @@ public class PowerSensorProducer { @Produces public PowerSensor sensor() { + return determinePowerSensor(); + } + + public static PowerSensor determinePowerSensor() { final var originalOSName = System.getProperty("os.name"); String osName = originalOSName.toLowerCase(); diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/linux/rapl/IntelRAPLMeasure.java b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/linux/rapl/IntelRAPLMeasure.java index cb089f3..dbd49cc 100644 --- a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/linux/rapl/IntelRAPLMeasure.java +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/linux/rapl/IntelRAPLMeasure.java @@ -1,15 +1,11 @@ package io.quarkiverse.power.runtime.sensors.linux.rapl; -import java.util.Optional; +import io.quarkiverse.power.runtime.sensors.IncrementableMeasure; -import io.quarkiverse.power.runtime.PowerSensor; - -public class IntelRAPLMeasure implements PowerSensor.IncrementableMeasure { +public class IntelRAPLMeasure implements IncrementableMeasure { private final long initial; - private final long startedAt = System.currentTimeMillis(); - private int samplesNb; - private long cpu; + private final long startedAt = System.currentTimeMillis(); public IntelRAPLMeasure(long initial) { this.initial = initial; @@ -17,46 +13,15 @@ public IntelRAPLMeasure(long initial) { @Override public double cpu() { - return ((double) cpu / measureDuration()) / 1_000; - } - - @Override - public Optional gpu() { - return Optional.empty(); - } - - @Override - public Optional byKey(String key) { - return Optional.empty(); - } - - @Override - public double total() { - return cpu(); - } - - @Override - public int numberOfSamples() { - return samplesNb; - } - - public void incrementSamples() { - samplesNb++; - } - - @Override - public long measureDuration() { - return System.currentTimeMillis() - startedAt; + return ((double) cpu / durationSinceStart()) / 1_000; } @Override public void addCPU(double v) { cpu += ((long) v - initial); - System.out.println("cpu = " + cpu); } - @Override - public void addGPU(double v) { - + private long durationSinceStart() { + return System.currentTimeMillis() - startedAt; } } diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/linux/rapl/IntelRAPLSensor.java b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/linux/rapl/IntelRAPLSensor.java index afe7ea3..752c104 100644 --- a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/linux/rapl/IntelRAPLSensor.java +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/linux/rapl/IntelRAPLSensor.java @@ -5,25 +5,15 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import io.quarkiverse.power.runtime.PowerSensor; +import io.quarkiverse.power.runtime.sensors.OngoingPowerMeasure; +import io.quarkiverse.power.runtime.sensors.PowerSensor; public class IntelRAPLSensor implements PowerSensor { public static final IntelRAPLSensor instance = new IntelRAPLSensor(); - - private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - private ScheduledFuture scheduled; - private final List raplFiles = new ArrayList<>(3); - private IntelRAPLMeasure accumulatedPower; - private boolean running; - public IntelRAPLSensor() { // if we total system energy is not available, read package and DRAM if possible // todo: extract more granular information @@ -48,30 +38,13 @@ private boolean checkAvailablity(String raplFileAsString) { } @Override - public void start(long duration, long frequency, Writer out) throws IOException, Exception { - if (!running) { - accumulatedPower = new IntelRAPLMeasure(extractPowerMeasure()); - running = true; - - if (duration > 0) { - executor.schedule(() -> stop(out), duration, TimeUnit.SECONDS); - } - - scheduled = executor.scheduleAtFixedRate( - this::accumulatePower, - 0, frequency, - TimeUnit.MILLISECONDS); - } - } - - private void stop(Writer out) { - stop(); - outputConsumptionSinceStarted(out); + public OngoingPowerMeasure start(long duration, long frequency, Writer out) throws Exception { + return new OngoingPowerMeasure<>(new IntelRAPLMeasure(extractPowerMeasure())); } - private void accumulatePower() { - accumulatedPower.addCPU(extractPowerMeasure()); - accumulatedPower.incrementSamples(); + @Override + public void update(OngoingPowerMeasure ongoingMeasure, Writer out) { + ongoingMeasure.addCPU(extractPowerMeasure()); } private long extractPowerMeasure() { @@ -85,20 +58,4 @@ private long extractPowerMeasure() { } return energyData; } - - @Override - public IntelRAPLMeasure stop() { - if (running) { - scheduled.cancel(true); - } - running = false; - return accumulatedPower; - } - - @Override - public void outputConsumptionSinceStarted(Writer out) { - out = out == null ? System.out::println : out; - out.println("Consumed " + accumulatedPower.total() + " mW over " + (accumulatedPower.measureDuration() / 1000) - + " seconds (" + accumulatedPower.numberOfSamples() + " samples)"); - } } diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/AppleSiliconMeasure.java b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/AppleSiliconMeasure.java index 1c6afd4..7e75967 100644 --- a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/AppleSiliconMeasure.java +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/AppleSiliconMeasure.java @@ -2,15 +2,12 @@ import java.util.Optional; -import io.quarkiverse.power.runtime.PowerSensor; +import io.quarkiverse.power.runtime.sensors.IncrementableMeasure; -public class AppleSiliconMeasure implements PowerSensor.IncrementableMeasure { +public class AppleSiliconMeasure implements IncrementableMeasure { private double cpu; private double gpu; private double ane; - private int samplesNb; - private final long startedAt = System.currentTimeMillis(); - public static final String ANE = "ane"; @Override @@ -50,18 +47,4 @@ public void addGPU(double v) { public void addANE(double v) { ane += v; } - - @Override - public int numberOfSamples() { - return samplesNb; - } - - public void incrementSamples() { - samplesNb++; - } - - @Override - public long measureDuration() { - return System.currentTimeMillis() - startedAt; - } } diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/jmx/JMXCPUSensor.java b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/jmx/JMXCPUSensor.java index 3550e5f..6340d92 100644 --- a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/jmx/JMXCPUSensor.java +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/jmx/JMXCPUSensor.java @@ -1,59 +1,31 @@ package io.quarkiverse.power.runtime.sensors.macos.jmx; +import static io.quarkiverse.power.runtime.PowerMeasurer.osBean; + import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; -import java.lang.management.ManagementFactory; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import com.sun.management.OperatingSystemMXBean; -import io.quarkiverse.power.runtime.PowerSensor; +import io.quarkiverse.power.runtime.sensors.OngoingPowerMeasure; +import io.quarkiverse.power.runtime.sensors.PowerSensor; import io.quarkiverse.power.runtime.sensors.macos.AppleSiliconMeasure; +@SuppressWarnings("unused") public class JMXCPUSensor implements PowerSensor { - private static final OperatingSystemMXBean osBean; - static { - osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); - } - public static PowerSensor instance = new JMXCPUSensor(); - - private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - private Process powermetrics; - private ScheduledFuture powermetricsSchedule; - - private boolean running; - private AppleSiliconMeasure accumulatedPower = new AppleSiliconMeasure(); @Override - public void start(long duration, long frequency, Writer out) throws IOException, Exception { - if (!running) { - final var freq = Long.toString(Math.round(frequency)); - powermetrics = Runtime.getRuntime().exec("sudo powermetrics --samplers cpu_power -i " + freq); - - accumulatedPower = new AppleSiliconMeasure(); - running = true; - - if (duration > 0) { - executor.schedule(() -> stop(out), duration, TimeUnit.SECONDS); - } - - powermetricsSchedule = executor.scheduleAtFixedRate(() -> extractPowerMeasure(powermetrics.getInputStream()), - 0, frequency, - TimeUnit.MILLISECONDS); - } + public OngoingPowerMeasure start(long duration, long frequency, Writer out) + throws Exception { + final var freq = Long.toString(Math.round(frequency)); + powermetrics = Runtime.getRuntime().exec("sudo powermetrics --samplers cpu_power -i " + freq); + return new OngoingPowerMeasure<>(new AppleSiliconMeasure()); } - void extractPowerMeasure(InputStream powerMeasureInput) { + public void update(OngoingPowerMeasure ongoingMeasure, Writer out) { try { // Should not be closed since it closes the process - BufferedReader input = new BufferedReader(new InputStreamReader(powerMeasureInput)); + BufferedReader input = new BufferedReader(new InputStreamReader(powermetrics.getInputStream())); String line; while ((line = input.readLine()) != null) { if (line.isEmpty() || line.startsWith("*")) { @@ -68,12 +40,10 @@ void extractPowerMeasure(InputStream powerMeasureInput) { break; } final var cpuShare = processCpuLoad / cpuLoad; - accumulatedPower.addCPU(extractAttributedMeasure(line, cpuShare)); + ongoingMeasure.addCPU(extractAttributedMeasure(line, cpuShare)); break; } } - - accumulatedPower.incrementSamples(); } catch (Exception exception) { throw new RuntimeException(exception); } @@ -86,24 +56,7 @@ private static double extractAttributedMeasure(String line, double attributionRa } @Override - public AppleSiliconMeasure stop() { - if (running) { - powermetrics.destroy(); - powermetricsSchedule.cancel(true); - } - running = false; - return accumulatedPower; - } - - @Override - public void outputConsumptionSinceStarted(Writer out) { - out = out == null ? System.out::println : out; - out.println("Consumed " + accumulatedPower.total() + " mW over " + (accumulatedPower.measureDuration() / 1000) - + " seconds"); - } - - private void stop(Writer out) { - stop(); - outputConsumptionSinceStarted(out); + public void stop() { + powermetrics.destroy(); } } diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/powermetrics/MacOSPowermetricsSensor.java b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/powermetrics/MacOSPowermetricsSensor.java index 868dbad..8f06b0a 100644 --- a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/powermetrics/MacOSPowermetricsSensor.java +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/macos/powermetrics/MacOSPowermetricsSensor.java @@ -1,26 +1,17 @@ package io.quarkiverse.power.runtime.sensors.macos.powermetrics; -import java.io.*; -import java.lang.management.ManagementFactory; -import java.util.concurrent.*; +import static io.quarkiverse.power.runtime.PowerMeasurer.osBean; -import com.sun.management.OperatingSystemMXBean; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; -import io.quarkiverse.power.runtime.PowerSensor; +import io.quarkiverse.power.runtime.sensors.OngoingPowerMeasure; +import io.quarkiverse.power.runtime.sensors.PowerSensor; import io.quarkiverse.power.runtime.sensors.macos.AppleSiliconMeasure; public class MacOSPowermetricsSensor implements PowerSensor { - private static final OperatingSystemMXBean osBean; - - static { - osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); - } - private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private Process powermetrics; - private ScheduledFuture powermetricsSchedule; - - private boolean running; - private AppleSiliconMeasure accumulatedPower; public static PowerSensor instance = new MacOSPowermetricsSensor(); private final static String pid = " " + ProcessHandle.current().pid() + " "; private double accumulatedCPUShareDiff; @@ -38,11 +29,19 @@ public ProcessRecord(String line) { } } + @Override + public void update(OngoingPowerMeasure ongoingMeasure, Writer out) { + extractPowerMeasure(ongoingMeasure, powermetrics.getInputStream(), pid); + } + AppleSiliconMeasure extractPowerMeasure(InputStream powerMeasureInput, long pid) { - return extractPowerMeasure(powerMeasureInput, " " + pid + " "); + return extractPowerMeasure(new OngoingPowerMeasure<>(new AppleSiliconMeasure()), powerMeasureInput, " " + pid + " "); } - AppleSiliconMeasure extractPowerMeasure(InputStream powerMeasureInput, String paddedPIDAsString) { + AppleSiliconMeasure extractPowerMeasure(OngoingPowerMeasure ongoingMeasure, + InputStream powerMeasureInput, + String paddedPIDAsString) { + final var accumulatedPower = ongoingMeasure.sensorMeasure(); try { // Should not be closed since it closes the process BufferedReader input = new BufferedReader(new InputStreamReader(powerMeasureInput)); @@ -100,8 +99,6 @@ AppleSiliconMeasure extractPowerMeasure(InputStream powerMeasureInput, String pa break; } } - - accumulatedPower.incrementSamples(); } catch (Exception exception) { throw new RuntimeException(exception); } @@ -115,47 +112,20 @@ private static double extractAttributedMeasure(String line, double attributionRa } @Override - public void start(long duration, long frequency, final Writer out) throws Exception { - if (!running) { - final var freq = Long.toString(Math.round(frequency)); - powermetrics = Runtime.getRuntime() - .exec("sudo powermetrics --samplers cpu_power,tasks --show-process-samp-norm --show-process-gpu -i " - + freq); - - accumulatedPower = new AppleSiliconMeasure(); - running = true; - - if (duration > 0) { - executor.schedule(() -> stop(out), duration, TimeUnit.SECONDS); - } - - powermetricsSchedule = executor.scheduleAtFixedRate( - () -> extractPowerMeasure(powermetrics.getInputStream(), pid), - 0, frequency, - TimeUnit.MILLISECONDS); - } + public OngoingPowerMeasure start(long duration, long frequency, final Writer out) throws Exception { + final var freq = Long.toString(Math.round(frequency)); + powermetrics = Runtime.getRuntime() + .exec("sudo powermetrics --samplers cpu_power,tasks --show-process-samp-norm --show-process-gpu -i " + + freq); + return new OngoingPowerMeasure<>(new AppleSiliconMeasure()); } @Override - public AppleSiliconMeasure stop() { - if (running) { - powermetrics.destroy(); - powermetricsSchedule.cancel(true); - } - running = false; - return accumulatedPower; + public void stop() { + powermetrics.destroy(); } - @Override - public void outputConsumptionSinceStarted(Writer out) { - out = out == null ? System.out::println : out; - out.println("Consumed " + accumulatedPower.total() + " mW over " + (accumulatedPower.measureDuration() / 1000) - + " seconds (" + accumulatedPower.numberOfSamples() + " samples)"); + public void additionalInfo(Writer out) { out.println("Powermetrics vs JMX CPU share accumulated difference: " + accumulatedCPUShareDiff); } - - private void stop(Writer out) { - stop(); - outputConsumptionSinceStarted(out); - } } diff --git a/runtime/src/test/java/io/quarkiverse/power/runtime/PowerMeasurerTest.java b/runtime/src/test/java/io/quarkiverse/power/runtime/PowerMeasurerTest.java new file mode 100644 index 0000000..2ebdf39 --- /dev/null +++ b/runtime/src/test/java/io/quarkiverse/power/runtime/PowerMeasurerTest.java @@ -0,0 +1,16 @@ +package io.quarkiverse.power.runtime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class PowerMeasurerTest { + @Test + void startShouldAccumulateOverSpecifiedDurationAndStop() throws Exception { + final var measurer = PowerMeasurer.instance(); + measurer.start(1, 100, null); + Thread.sleep(2000); + final var measure = measurer.current(); + assertEquals(10, measure.numberOfSamples()); + } +} diff --git a/runtime/src/test/java/io/quarkiverse/power/runtime/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java b/runtime/src/test/java/io/quarkiverse/power/runtime/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java index 2486807..4933750 100644 --- a/runtime/src/test/java/io/quarkiverse/power/runtime/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java +++ b/runtime/src/test/java/io/quarkiverse/power/runtime/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java @@ -13,13 +13,4 @@ void extractPowerMeasure() { .extractPowerMeasure(Thread.currentThread().getContextClassLoader().getResourceAsStream("foo.txt"), 29419); assertEquals(((23.88 / 1222.65) * 211), measure.cpu()); } - - @Test - void startShouldAccumulateOverSpecifiedDurationAndStop() throws Exception { - final var sensor = new MacOSPowermetricsSensor(); - sensor.start(3, 1000, null); - Thread.sleep(5000); - final var measure = sensor.stop(); - assertEquals(3, measure.numberOfSamples()); - } }