Skip to content
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
11 changes: 5 additions & 6 deletions cli/src/main/java/net/laprun/sustainability/cli/Power.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import net.laprun.sustainability.power.Measure;
import net.laprun.sustainability.power.ProcessUtils;
import net.laprun.sustainability.power.SensorUnit;
import net.laprun.sustainability.power.analysis.total.TotalSyntheticComponent;
import net.laprun.sustainability.power.analysis.total.Totaler;
import net.laprun.sustainability.power.nuprocess.BaseProcessHandler;
import net.laprun.sustainability.power.persistence.Persistence;
import net.laprun.sustainability.power.sensors.SamplingMeasurer;
Expand Down Expand Up @@ -83,13 +83,12 @@ private Measure extractPowerConsumption(String applicationName) {
// first read metadata
final var metadata = measurer.metadata();

// create a synthetic component to get the total power
final var totaler = new TotalSyntheticComponent(metadata, SensorUnit.W, 0, 1, 2);

// get the total power
final var totaler = new Totaler(metadata, SensorUnit.W);
final var appPower = measurer.persistence()
.synthesizeAndAggregateForSession(applicationName, session,
m -> totaler.synthesizeFrom(m.components, m.startTime))
.map(measure -> new Measure(measure, totaler.metadata().unit()))
m -> totaler.computeTotalFrom(m.components))
.map(measure -> new Measure(measure, totaler.expectedResultUnit()))
.orElseThrow(() -> new RuntimeException("Could not extract power consumption"));

Quarkus.asyncExit();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public class TotalMeasureProcessor implements MeasureProcessor {

public TotalMeasureProcessor(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {
this.totaler = new Totaler(metadata, expectedResultUnit, totalComponentIndices);
totaler.validate();
}

public double total() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,14 @@ public class TotalSyntheticComponent implements SyntheticComponent {
private final SensorMetadata.ComponentMetadata metadata;

public TotalSyntheticComponent(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {
this.totaler = new Totaler(metadata, expectedResultUnit, totalComponentIndices);
final var isAttributed = metadata.components().values().stream()
.map(SensorMetadata.ComponentMetadata::isAttributed)
.reduce(Boolean::logicalAnd).orElse(false);
final var name = totaler.name();
if (metadata.exists(name)) {
totaler.addError("Component " + name + " already exists");
}

totaler.validate();
this(new Totaler(metadata, expectedResultUnit, totalComponentIndices));
}

this.metadata = new SensorMetadata.ComponentMetadata(name, "Aggregated " + name, isAttributed, expectedResultUnit);
private TotalSyntheticComponent(Totaler totaler) {
this.totaler = totaler;
final var name = totaler.name();
this.metadata = new SensorMetadata.ComponentMetadata(name, "Aggregated " + name, totaler.isAttributed(),
totaler.expectedResultUnit());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,70 @@
import net.laprun.sustainability.power.SensorMetadata;
import net.laprun.sustainability.power.SensorUnit;

class Totaler {
public class Totaler {
private final SensorUnit expectedResultUnit;
private final Function<double[], Double> formula;
private final String name;
private final int[] totalComponentIndices;
private final boolean isAttributed;
private Errors errors;

Totaler(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {
Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total");
public Totaler(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {
this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit");

errors = new Errors();
final var totalComponents = Arrays.stream(totalComponentIndices)
.mapToObj(i -> from(metadata, i, expectedResultUnit, errors))
.toArray(TotalComponent[]::new);

final TotalComponent[] totalComponents;
final var attributed = new Boolean[1];
if (totalComponentIndices == null || totalComponentIndices.length == 0) {
// automatically aggregate components commensurate with the expected result unit
totalComponents = metadata.components().values().stream()
.filter(cm -> cm.unit().isCommensurableWith(expectedResultUnit))
.map(cm -> new TotalComponent(cm.name(), cm.index(), cm.unit().factor(),
checkAggregatedAttribution(cm.isAttributed(), attributed)))
.toArray(TotalComponent[]::new);
// record total indices
totalComponentIndices = new int[totalComponents.length];
int i = 0;
for (var component : totalComponents) {
totalComponentIndices[i++] = component.index();
}
} else {
totalComponents = Arrays.stream(totalComponentIndices)
.mapToObj(i -> {
final var cm = metadata.metadataFor(i);
checkAggregatedAttribution(cm.isAttributed(), attributed);
return from(cm, expectedResultUnit, errors);
})
.toArray(TotalComponent[]::new);
}

name = Arrays.stream(totalComponents)
.map(TotalComponent::name)
.collect(Collectors.joining(" + ", "total (", ")"));
if (metadata.exists(name)) {
addError("Component " + name + " already exists");
}

isAttributed = attributed[0];

formula = formulaFrom(totalComponents);
this.totalComponentIndices = totalComponentIndices;
validate();
}

void validate() {
private static boolean checkAggregatedAttribution(boolean isComponentAttributed, Boolean[] aggregateAttribution) {
var currentAttribution = aggregateAttribution[0];
if (currentAttribution == null) {
currentAttribution = isComponentAttributed;
} else {
currentAttribution = currentAttribution && isComponentAttributed;
}
aggregateAttribution[0] = currentAttribution;
return isComponentAttributed;
}

private void validate() {
if (errors.hasErrors()) {
throw new IllegalArgumentException(errors.formatErrors());
}
Expand Down Expand Up @@ -63,6 +104,14 @@ public double computeTotalFrom(double[] measure) {
return convertToExpectedUnit(formula.apply(measure));
}

public boolean isAttributed() {
return isAttributed;
}

int[] componentIndices() {
return totalComponentIndices;
}

private void checkValidated() {
if (errors != null) {
throw new IllegalStateException("Totaler must be validated before use!");
Expand All @@ -73,14 +122,13 @@ private double convertToExpectedUnit(double value) {
return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit);
}

private record TotalComponent(String name, int index, double factor) {
private record TotalComponent(String name, int index, double factor, boolean isAttributed) {
double scaledValueFrom(double[] componentValues) {
return componentValues[index] * factor;
}
}

private static TotalComponent from(SensorMetadata metadata, int index, SensorUnit expectedResultUnit, Errors errors) {
final var cm = metadata.metadataFor(index);
private static TotalComponent from(SensorMetadata.ComponentMetadata cm, SensorUnit expectedResultUnit, Errors errors) {
final var name = cm.name();
final var unit = cm.unit();
if (!unit.isCommensurableWith(expectedResultUnit)) {
Expand All @@ -89,7 +137,7 @@ private static TotalComponent from(SensorMetadata metadata, int index, SensorUni
}

final var factor = unit.factor();
return new TotalComponent(name, index, factor);
return new TotalComponent(name, cm.index(), factor, cm.isAttributed());
}

private static Function<double[], Double> formulaFrom(TotalComponent[] totalComponents) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,39 @@ void totalShouldFailIfAllComponentsAreNotCommensurable() {
+ " is not commensurable with the expected base unit: " + expectedResultUnit));
}

@Test
void attributedShouldWork() {
final var metadata = SensorMetadata
.withNewComponent("cp1", null, true, "mW")
.withNewComponent("cp2", null, true, "mJ")
.withNewComponent("cp3", null, true, "mW")
.withNewComponent("cp24", null, false, "W")
.build();

final var expectedResultUnit = SensorUnit.W;
var totaler = new Totaler(metadata, expectedResultUnit, 0, 2);
assertTrue(totaler.isAttributed());
totaler = new Totaler(metadata, expectedResultUnit, 3);
assertFalse(totaler.isAttributed());
totaler = new Totaler(metadata, expectedResultUnit, 0, 2, 3);
assertFalse(totaler.isAttributed());
}

@Test
void shouldAutomaticallyPickCommensurateComponentsIfNoIndicesAreProvided() {
final var metadata = SensorMetadata
.withNewComponent("cp1", null, true, "mW")
.withNewComponent("cp2", null, true, "mJ")
.withNewComponent("cp3", null, true, "mW")
.withNewComponent("cp24", null, false, "W")
.build();

final var expectedResultUnit = SensorUnit.W;
var totaler = new Totaler(metadata, expectedResultUnit);
assertFalse(totaler.isAttributed());
assertArrayEquals(new int[] { 0, 2, 3 }, totaler.componentIndices());
}

@Test
void testTotal() {
final var metadata = SensorMetadata
Expand Down
Loading