From fe96c2d317ed16889991bcc934aac107bc76a993 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Thu, 2 Nov 2023 18:39:49 +0100 Subject: [PATCH] fix: better robustness of commands --- .../devui/commands/StartCommand.java | 15 ++++-- .../devui/commands/StopCommand.java | 6 ++- .../power/runtime/PowerMeasurer.java | 35 ++++++++---- .../power/runtime/sensors/PowerSensor.java | 4 +- .../runtime/sensors/StoppedPowerMeasure.java | 53 +++++++++++++++++++ .../powermetrics/MacOSPowermetricsSensor.java | 7 ++- 6 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 runtime/src/main/java/io/quarkiverse/power/runtime/sensors/StoppedPowerMeasure.java 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 aa251f5..4410db1 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 @@ -27,13 +27,18 @@ public StartCommand(PowerMeasurer sensor) { @Override public CommandResult doExecute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { try { - if (duration > 0) { - commandInvocation.println("Measuring power for " + duration + " seconds, every " + frequency + " milliseconds"); + if (!sensor.isRunning()) { + if (duration > 0) { + commandInvocation + .println("Measuring power for " + duration + " seconds, every " + frequency + " milliseconds"); + } else { + commandInvocation.println("Measuring power every " + frequency + + " milliseconds. Execute 'power stop' to stop measurements and get the results."); + } + sensor.start(duration, frequency, commandInvocation::println); } else { - commandInvocation.println("Measuring power every " + frequency - + " milliseconds. Execute 'power stop' to stop measurements and get the results."); + commandInvocation.println("Power measurement is already ongoing. Execute 'power stop' to stop it now."); } - sensor.start(duration, frequency, commandInvocation::println); } catch (Exception e) { commandInvocation.println("Couldn't start power measure: " + e.getMessage()); return CommandResult.FAILURE; 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 65569dc..f271abd 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 @@ -20,7 +20,11 @@ public StopCommand(PowerMeasurer sensor) { @Override public CommandResult doExecute(CommandInvocation commandInvocation) throws CommandException, InterruptedException { - sensor.stop(commandInvocation::println); + if (sensor.isRunning()) { + sensor.stop(commandInvocation::println); + } else { + commandInvocation.println("Power measurement hasn't started. Execute 'power start' to start it first."); + } return CommandResult.SUCCESS; } diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/PowerMeasurer.java b/runtime/src/main/java/io/quarkiverse/power/runtime/PowerMeasurer.java index be9b10b..2f4c09f 100644 --- a/runtime/src/main/java/io/quarkiverse/power/runtime/PowerMeasurer.java +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/PowerMeasurer.java @@ -8,10 +8,7 @@ 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; +import io.quarkiverse.power.runtime.sensors.*; public class PowerMeasurer { public static final OperatingSystemMXBean osBean; @@ -24,6 +21,7 @@ public class PowerMeasurer { private ScheduledFuture scheduled; private final PowerSensor sensor; private OngoingPowerMeasure measure; + private StoppedPowerMeasure lastMeasure; private final static PowerMeasurer instance = new PowerMeasurer<>( PowerSensorProducer.determinePowerSensor()); @@ -36,8 +34,12 @@ public PowerMeasurer(PowerSensor sensor) { this.sensor = sensor; } + public boolean isRunning() { + return measure != null; + } + public void start(long duration, long frequency, PowerSensor.Writer out) throws Exception { - if (measure == null) { + if (!isRunning()) { measure = sensor.start(duration, frequency, out); if (duration > 0) { @@ -55,23 +57,34 @@ private void update(PowerSensor.Writer out) { measure.incrementSamples(); } - public PowerMeasure stop(PowerSensor.Writer out) { - if (measure != null) { + public void stop(PowerSensor.Writer out) { + if (isRunning()) { sensor.stop(); scheduled.cancel(true); + outputConsumptionSinceStarted(out); + lastMeasure = new StoppedPowerMeasure<>(measure); + measure = null; } - outputConsumptionSinceStarted(out); - return measure; } public PowerMeasure current() { - return measure; + // use the ongoing power measure if it exists + if (measure == null) { + // or use the last recorded measure if we have one + if (lastMeasure != null) { + return lastMeasure; + } else { + throw new IllegalStateException("No power measure found. Please start it first."); + } + } else { + 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); + sensor.additionalInfo(measure, out); } } 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 index dea1251..7e9cbeb 100644 --- a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/PowerSensor.java +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/PowerSensor.java @@ -1,5 +1,7 @@ package io.quarkiverse.power.runtime.sensors; +import io.quarkiverse.power.runtime.PowerMeasure; + public interface PowerSensor { OngoingPowerMeasure start(long duration, long frequency, Writer out) throws Exception; @@ -9,7 +11,7 @@ default void stop() { void update(OngoingPowerMeasure ongoingMeasure, Writer out); - default void additionalInfo(Writer out) { + default void additionalInfo(PowerMeasure measure, Writer out) { } interface Writer { diff --git a/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/StoppedPowerMeasure.java b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/StoppedPowerMeasure.java new file mode 100644 index 0000000..82c5716 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/power/runtime/sensors/StoppedPowerMeasure.java @@ -0,0 +1,53 @@ +package io.quarkiverse.power.runtime.sensors; + +import java.util.Optional; + +import io.quarkiverse.power.runtime.PowerMeasure; +import io.quarkiverse.power.runtime.SensorMeasure; + +public class StoppedPowerMeasure implements SensorMeasure, PowerMeasure { + private final M measure; + private final long duration; + private final int samples; + + public StoppedPowerMeasure(PowerMeasure powerMeasure) { + this.measure = powerMeasure.sensorMeasure(); + this.duration = powerMeasure.duration(); + this.samples = powerMeasure.numberOfSamples(); + } + + @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(); + } + + @Override + public int numberOfSamples() { + return samples; + } + + @Override + public long duration() { + return duration; + } + + @Override + public M sensorMeasure() { + return measure; + } +} 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 8f06b0a..b01c77f 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 @@ -6,6 +6,7 @@ import java.io.InputStream; import java.io.InputStreamReader; +import io.quarkiverse.power.runtime.PowerMeasure; import io.quarkiverse.power.runtime.sensors.OngoingPowerMeasure; import io.quarkiverse.power.runtime.sensors.PowerSensor; import io.quarkiverse.power.runtime.sensors.macos.AppleSiliconMeasure; @@ -14,7 +15,7 @@ public class MacOSPowermetricsSensor implements PowerSensor private Process powermetrics; public static PowerSensor instance = new MacOSPowermetricsSensor(); private final static String pid = " " + ProcessHandle.current().pid() + " "; - private double accumulatedCPUShareDiff; + private double accumulatedCPUShareDiff = 0.0; private static class ProcessRecord { final double cpu; @@ -117,6 +118,7 @@ public OngoingPowerMeasure start(long duration, long freque powermetrics = Runtime.getRuntime() .exec("sudo powermetrics --samplers cpu_power,tasks --show-process-samp-norm --show-process-gpu -i " + freq); + accumulatedCPUShareDiff = 0.0; return new OngoingPowerMeasure<>(new AppleSiliconMeasure()); } @@ -125,7 +127,8 @@ public void stop() { powermetrics.destroy(); } - public void additionalInfo(Writer out) { + @Override + public void additionalInfo(PowerMeasure measure, Writer out) { out.println("Powermetrics vs JMX CPU share accumulated difference: " + accumulatedCPUShareDiff); } }