Skip to content

Commit

Permalink
feat: support individual measure recording, separate measure from met…
Browse files Browse the repository at this point in the history
…adata
  • Loading branch information
metacosm committed Nov 11, 2023
1 parent 0a7abfa commit fdd497e
Show file tree
Hide file tree
Showing 20 changed files with 305 additions and 321 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
@CommandDefinition(name = "start", description = "Starts measuring power consumption of the current application")
public class StartCommand extends QuarkusCommand {
private final PowerMeasurer<? extends SensorMeasure> sensor;
private PowerMeasure<?> baseline;
private PowerMeasure baseline;

@Option(name = "stopAfter", shortName = 's', description = "Automatically stop the measures after the specified duration in seconds", defaultValue = "-1")
private long duration;
Expand Down Expand Up @@ -41,7 +41,10 @@ public CommandResult doExecute(CommandInvocation commandInvocation) {
commandInvocation.println("Establishing baseline for 30s, please do not use your application until done.");
commandInvocation.println("Power measurement will start as configured after this initial measure is done.");
sensor.start(30, 1000);
sensor.onError(e -> commandInvocation.println("An error occurred: " + e.getMessage()));
sensor.onError(e -> {
commandInvocation.println("An error occurred: " + e.getMessage());
e.printStackTrace();
});
sensor.onCompleted((m) -> {
baseline = m;
outputConsumptionSinceStarted(baseline, commandInvocation, true);
Expand Down Expand Up @@ -70,7 +73,7 @@ public CommandResult doExecute(CommandInvocation commandInvocation) {
return CommandResult.SUCCESS;
}

private void outputConsumptionSinceStarted(PowerMeasure<?> measure, CommandInvocation out, boolean isBaseline) {
private void outputConsumptionSinceStarted(PowerMeasure measure, CommandInvocation out, boolean isBaseline) {
final var title = isBaseline ? "\nBaseline => " : "\nMeasured => ";
out.println(title + PowerMeasure.asString(measure));
if (!isBaseline) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package io.quarkiverse.power.runtime;

public interface PowerMeasure<M extends SensorMeasure> extends SensorMeasure {
import java.util.List;

public interface PowerMeasure extends SensorMeasure {
int numberOfSamples();

long duration();

M sensorMeasure();

default double average() {
return total() / numberOfSamples();
}

static String asString(PowerMeasure<?> measure) {
static String asString(PowerMeasure measure) {
final var durationInSeconds = measure.duration() / 1000;
final var samples = measure.numberOfSamples();
final var measuredMilliWatts = measure.total();
Expand All @@ -25,4 +25,6 @@ static String readableWithUnit(double milliWatts) {
double power = milliWatts >= 1000 ? milliWatts / 1000 : milliWatts;
return String.format("%.3f%s", power, unit);
}

List<double[]> measures();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import io.quarkiverse.power.runtime.sensors.*;

public class PowerMeasurer<M extends IncrementableMeasure> {
public class PowerMeasurer<M extends SensorMeasure> {
private static final OperatingSystemMXBean osBean;

static {
Expand All @@ -25,10 +25,10 @@ public class PowerMeasurer<M extends IncrementableMeasure> {
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> scheduled;
private final PowerSensor<M> sensor;
private OngoingPowerMeasure<M> measure;
private OngoingPowerMeasure measure;

private Consumer<PowerMeasure<M>> completed;
private BiConsumer<Integer, PowerMeasure<M>> sampled;
private Consumer<PowerMeasure> completed;
private BiConsumer<Integer, PowerMeasure> sampled;
private Consumer<Exception> errorHandler;

private final static PowerMeasurer<? extends SensorMeasure> instance = new PowerMeasurer<>(
Expand All @@ -48,11 +48,11 @@ public double cpuShareOfJVMProcess() {
return (processCpuLoad < 0 || cpuLoad <= 0) ? 0 : processCpuLoad / cpuLoad;
}

public void onCompleted(Consumer<PowerMeasure<M>> completed) {
public void onCompleted(Consumer<PowerMeasure> completed) {
this.completed = completed;
}

public void onSampled(BiConsumer<Integer, PowerMeasure<M>> sampled) {
public void onSampled(BiConsumer<Integer, PowerMeasure> sampled) {
this.sampled = sampled;
}

Expand Down Expand Up @@ -88,7 +88,6 @@ public void start(long durationInSeconds, long frequencyInMilliseconds)
private void update() {
try {
sensor.update(measure);
measure.incrementSamples();
if (this.sampled != null) {
sampled.accept(measure.numberOfSamples(), measure);
}
Expand Down Expand Up @@ -117,7 +116,7 @@ public void stop() {
sensor.stop();
scheduled.cancel(true);
// record the result
final var measured = new StoppedPowerMeasure<>(measure);
final var measured = new StoppedPowerMeasure(measure);
// then set the measure to null to mark that we're ready for a new measure
measure = null;
// and finally, but only then, run the completion handler
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
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<Double> gpu() {
return Optional.empty();
}

default Optional<Double> byKey(String key) {
return switch (key) {
case CPU -> Optional.of(cpu());
case GPU -> gpu();
case TOTAL -> Optional.of(total());
default -> Optional.empty();
};
}
double total();

default double total() {
return cpu() + gpu().orElse(0.0);
}
SensorMetadata metadata();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.quarkiverse.power.runtime;

public interface SensorMetadata {
int indexFor(String component);

int componentCardinality();
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,64 +1,67 @@
package io.quarkiverse.power.runtime.sensors;

import java.util.Optional;
import java.util.ArrayList;
import java.util.List;

import io.quarkiverse.power.runtime.PowerMeasure;
import io.quarkiverse.power.runtime.SensorMetadata;

public class OngoingPowerMeasure<M extends IncrementableMeasure>
implements IncrementableMeasure, PowerMeasure<M> {
private final M measure;
public class OngoingPowerMeasure implements PowerMeasure {
private final SensorMetadata sensorMetadata;
private final long startedAt;
private int samplesNb;
private final List<double[]> measures = new ArrayList<>();
private double[] current;
private double total;

public OngoingPowerMeasure(M measure) {
public OngoingPowerMeasure(SensorMetadata sensorMetadata) {
startedAt = System.currentTimeMillis();
samplesNb = 0;
this.measure = measure;
this.sensorMetadata = sensorMetadata;
}

public void incrementSamples() {
samplesNb++;
public void startNewMeasure() {
if (current != null) {
throw new IllegalStateException("A new measure cannot be started while one is still ongoing");
}
current = new double[sensorMetadata.componentCardinality()];
}

@Override
public double cpu() {
return measure.cpu();
public void setComponent(int index, double value) {
current[index] = value;
}

@Override
public Optional<Double> gpu() {
return measure.gpu();
public double[] stopMeasure() {
final var recorded = new double[current.length];
System.arraycopy(current, 0, recorded, 0, current.length);
measures.add(recorded);
var currentMeasureTotal = 0.0;
for (double value : recorded) {
currentMeasureTotal += value;
}
total += currentMeasureTotal;
current = null;
return recorded;
}

@Override
public Optional<Double> byKey(String key) {
return measure.byKey(key);
public List<double[]> measures() {
return measures;
}

@Override
public double total() {
return measure.total();
}

public int numberOfSamples() {
return samplesNb;
}

public long duration() {
return System.currentTimeMillis() - startedAt;
return total;
}

@Override
public void addCPU(double v) {
measure.addCPU(v);
public SensorMetadata metadata() {
return sensorMetadata;
}

@Override
public void addGPU(double v) {
measure.addGPU(v);
public int numberOfSamples() {
return measures.size();
}

public M sensorMeasure() {
return measure;
public long duration() {
return System.currentTimeMillis() - startedAt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
import java.util.Optional;

import io.quarkiverse.power.runtime.PowerMeasure;
import io.quarkiverse.power.runtime.SensorMeasure;

public interface PowerSensor<T extends IncrementableMeasure> {
public interface PowerSensor<T extends SensorMeasure> {

OngoingPowerMeasure<T> start(long duration, long frequency) throws Exception;
OngoingPowerMeasure start(long duration, long frequency) throws Exception;

default void stop() {
}

void update(OngoingPowerMeasure<T> ongoingMeasure);
void update(OngoingPowerMeasure ongoingMeasure);

default Optional<String> additionalInfo(PowerMeasure<T> measure) {
default Optional<String> additionalInfo(PowerMeasure measure) {
return Optional.empty();
}

T measureFor(double[] measureComponents);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Singleton;

import io.quarkiverse.power.runtime.SensorMeasure;
import io.quarkiverse.power.runtime.sensors.linux.rapl.IntelRAPLSensor;
import io.quarkiverse.power.runtime.sensors.macos.powermetrics.MacOSPowermetricsSensor;

Expand All @@ -13,7 +14,7 @@ public PowerSensor<?> sensor() {
return determinePowerSensor();
}

public static PowerSensor<? extends IncrementableMeasure> determinePowerSensor() {
public static PowerSensor<? extends SensorMeasure> determinePowerSensor() {
final var originalOSName = System.getProperty("os.name");
String osName = originalOSName.toLowerCase();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,47 @@
package io.quarkiverse.power.runtime.sensors;

import java.util.Optional;
import java.util.List;

import io.quarkiverse.power.runtime.PowerMeasure;
import io.quarkiverse.power.runtime.SensorMeasure;
import io.quarkiverse.power.runtime.SensorMetadata;

public class StoppedPowerMeasure<M extends SensorMeasure> implements SensorMeasure, PowerMeasure<M> {
private final M measure;
public class StoppedPowerMeasure implements PowerMeasure {
private final SensorMetadata metadata;
private final long duration;
private final int samples;
private final List<double[]> measures;
private final double total;

public StoppedPowerMeasure(PowerMeasure<M> powerMeasure) {
this.measure = powerMeasure.sensorMeasure();
public StoppedPowerMeasure(PowerMeasure powerMeasure) {
this.metadata = powerMeasure.metadata();
this.duration = powerMeasure.duration();
this.samples = powerMeasure.numberOfSamples();
this.measures = powerMeasure.measures();
this.total = powerMeasure.total();
}

@Override
public double cpu() {
return measure.cpu();
public int numberOfSamples() {
return samples;
}

@Override
public Optional<Double> gpu() {
return measure.gpu();
public long duration() {
return duration;
}

@Override
public Optional<Double> byKey(String key) {
return measure.byKey(key);
public List<double[]> measures() {
return measures;
}

@Override
public double total() {
return measure.total();
}

@Override
public int numberOfSamples() {
return samples;
}

@Override
public long duration() {
return duration;
return total;
}

@Override
public M sensorMeasure() {
return measure;
public SensorMetadata metadata() {
return metadata;
}
}
Loading

0 comments on commit fdd497e

Please sign in to comment.