Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support individual measure recording, separate measure from metadata #18

Merged
merged 2 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,19 +73,13 @@ public CommandResult doExecute(CommandInvocation commandInvocation) {
return CommandResult.SUCCESS;
}

private void outputConsumptionSinceStarted(PowerMeasure<?> measure, CommandInvocation out, boolean isBaseline) {
final var durationInSeconds = measure.duration() / 1000;
final var title = isBaseline ? "Baseline power: " : "Measured power: ";
out.println(title + getReadablePower(measure) + " over " + durationInSeconds
+ " seconds (" + measure.numberOfSamples() + " samples)");
private void outputConsumptionSinceStarted(PowerMeasure measure, CommandInvocation out, boolean isBaseline) {
final var title = isBaseline ? "\nBaseline => " : "\nMeasured => ";
out.println(title + PowerMeasure.asString(measure));
if (!isBaseline) {
sensor.additionalSensorInfo().ifPresent(out::println);
out.println("Baseline power was " + getReadablePower(baseline));
out.println("Baseline => " + PowerMeasure.asString(baseline));
out.println("Average ∆ => " + PowerMeasure.readableWithUnit(measure.average() - baseline.average()));
}
}

private static String getReadablePower(PowerMeasure<?> measure) {
final var measuredMilliWatts = measure.total();
return measuredMilliWatts >= 1000 ? (measuredMilliWatts / 1000) + " W" : measuredMilliWatts + "mW";
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
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) {
final var durationInSeconds = measure.duration() / 1000;
final var samples = measure.numberOfSamples();
final var measuredMilliWatts = measure.total();
return String.format("%s / avg: %s (%ds, %s samples)", readableWithUnit(measuredMilliWatts),
readableWithUnit(measure.average()), durationInSeconds,
samples);
}

static String readableWithUnit(double milliWatts) {
String unit = milliWatts >= 1000 ? "W" : "mW";
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
Loading