From 02e988530f977f13219afb09c5f36d250e451eaa Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Fri, 3 Jan 2025 11:39:48 +0100 Subject: [PATCH 1/3] refactor: simplify total synthetic component --- .../power/analysis/total/TotalSyntheticComponent.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalSyntheticComponent.java b/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalSyntheticComponent.java index 0e669c6..f8e0035 100644 --- a/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalSyntheticComponent.java +++ b/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalSyntheticComponent.java @@ -23,12 +23,9 @@ public TotalSyntheticComponent(SensorMetadata metadata, SensorUnit expectedResul final var totalComponents = Arrays.stream(totalComponentIndices) .mapToObj(i -> toTotalComponent(metadata, i, errors)) .toArray(TotalComponent[]::new); - final String description = Arrays.stream(totalComponents) - .map(TotalComponent::name) - .collect(Collectors.joining(" + ", "Aggregated total from (", ")")); final String name = Arrays.stream(totalComponents) .map(TotalComponent::name) - .collect(Collectors.joining("_", "total", "")); + .collect(Collectors.joining(" + ", "total (", ")")); final var isAttributed = metadata.components().values().stream() .map(SensorMetadata.ComponentMetadata::isAttributed) .reduce(Boolean::logicalAnd).orElse(false); @@ -48,7 +45,7 @@ public TotalSyntheticComponent(SensorMetadata metadata, SensorUnit expectedResul throw new IllegalArgumentException(errors.formatErrors()); } - this.metadata = new SensorMetadata.ComponentMetadata(name, description, isAttributed, expectedResultUnit); + this.metadata = new SensorMetadata.ComponentMetadata(name, "Aggregated " + name, isAttributed, expectedResultUnit); } private double convertToExpectedUnit(double value) { From 5952ebe51393097e701f7fb1667811ea07b2c864 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Fri, 3 Jan 2025 11:40:56 +0100 Subject: [PATCH 2/3] chore: check that metadata is updated when using synthetic components --- .../power/measure/OngoingPowerMeasureTest.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java b/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java index ce104ad..c265e10 100644 --- a/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java +++ b/measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java @@ -121,7 +121,10 @@ public double synthesizeFrom(double[] components, long timestamp) { final var measure = new OngoingPowerMeasure(metadata, doubler); final var testProc = new TestComponentProcessor(); // need to get updated metadata - final var doublerIndex = measure.metadata().metadataFor(doublerName).index(); + final var updatedMetadata = measure.metadata(); + assertThat(updatedMetadata.componentCardinality()).isEqualTo(metadata.componentCardinality() + 1); + final var doublerIndex = updatedMetadata.metadataFor(doublerName).index(); + assertThat(doublerIndex).isEqualTo(metadata.componentCardinality()); measure.registerProcessorFor(doublerIndex, testProc); final var components = new double[metadata.componentCardinality()]; @@ -133,6 +136,7 @@ public double synthesizeFrom(double[] components, long timestamp) { assertThat(testProc.values.getFirst().value()).isEqualTo(m1c1 * 2); assertThat(testProc.values.getLast().value()).isEqualTo(m2c1 * 2); + assertThat(PowerMeasure.asString(measure)).contains("doubler", testProc.values.toString()); } private static class TestComponentProcessor implements ComponentProcessor { @@ -142,6 +146,11 @@ private static class TestComponentProcessor implements ComponentProcessor { public void recordComponentValue(double value, long timestamp) { values.add(new PowerMeasure.TimestampedValue(timestamp, value)); } + + @Override + public String output() { + return values.toString(); + } } private static class TestMeasureProcessor implements MeasureProcessor { From e49645e6d308186f58d94ef609b2f40aefb7f5b5 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Fri, 3 Jan 2025 13:30:07 +0100 Subject: [PATCH 3/3] refactor: extract Totaler class to gather common code --- .../analysis/total/TotalMeasureProcessor.java | 65 ++----------- .../total/TotalSyntheticComponent.java | 57 ++--------- .../power/analysis/total/Totaler.java | 97 +++++++++++++++++++ .../measure/StoppedPowerMeasureTest.java | 2 +- 4 files changed, 113 insertions(+), 108 deletions(-) create mode 100644 measure/src/main/java/net/laprun/sustainability/power/analysis/total/Totaler.java diff --git a/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalMeasureProcessor.java b/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalMeasureProcessor.java index b4acd28..b05438c 100644 --- a/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalMeasureProcessor.java +++ b/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalMeasureProcessor.java @@ -1,93 +1,46 @@ package net.laprun.sustainability.power.analysis.total; -import java.util.Arrays; -import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Collectors; - -import net.laprun.sustainability.power.Errors; import net.laprun.sustainability.power.SensorMetadata; import net.laprun.sustainability.power.SensorUnit; import net.laprun.sustainability.power.analysis.MeasureProcessor; public class TotalMeasureProcessor implements MeasureProcessor { - private final String name; + private final Totaler totaler; private double minTotal = Double.MAX_VALUE; private double maxTotal; private double accumulatedTotal; - private final Function formula; - private final SensorUnit expectedResultUnit; public TotalMeasureProcessor(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) { - Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total"); - this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit"); - - final var errors = new Errors(); - final var totalComponents = Arrays.stream(totalComponentIndices) - .mapToObj(i -> toTotalComponent(metadata, i, errors)) - .toArray(TotalComponent[]::new); - name = Arrays.stream(totalComponents) - .map(TotalComponent::name) - .collect(Collectors.joining(" + ", "Aggregated total from (", ")")); - formula = components -> { - double result = 0; - for (var totalComponent : totalComponents) { - result += components[totalComponent.index] * totalComponent.factor; - } - return result; - }; - - if (errors.hasErrors()) { - throw new IllegalArgumentException(errors.formatErrors()); - } - } - - private TotalComponent toTotalComponent(SensorMetadata metadata, int index, Errors errors) { - final var cm = metadata.metadataFor(index); - final var name = cm.name(); - final var unit = cm.unit(); - if (!unit.isCommensurableWith(expectedResultUnit)) { - errors.addError("Component " + name - + " is not commensurable with the expected base unit: " + expectedResultUnit); - } - - final var factor = unit.factor(); - return new TotalComponent(name, index, factor); + this.totaler = new Totaler(metadata, expectedResultUnit, totalComponentIndices); + totaler.validate(); } public double total() { - return convertToExpectedUnit(accumulatedTotal); + return accumulatedTotal; } public double minMeasuredTotal() { - return minTotal == Double.MAX_VALUE ? 0.0 : convertToExpectedUnit(minTotal); + return minTotal == Double.MAX_VALUE ? 0.0 : minTotal; } public double maxMeasuredTotal() { - return convertToExpectedUnit(maxTotal); - } - - private double convertToExpectedUnit(double value) { - return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit); - } - - private record TotalComponent(String name, int index, double factor) { + return maxTotal; } @Override public String name() { - return name; + return totaler.name(); } @Override public String output() { - final var symbol = expectedResultUnit.symbol(); + final var symbol = totaler.expectedResultUnit().symbol(); return String.format("%.2f%s (min: %.2f / max: %.2f)", total(), symbol, minMeasuredTotal(), maxMeasuredTotal()); } @Override public void recordMeasure(double[] measure, long timestamp) { - final double recordedTotal = formula.apply(measure); + final double recordedTotal = totaler.computeTotalFrom(measure); accumulatedTotal += recordedTotal; if (recordedTotal < minTotal) { minTotal = recordedTotal; diff --git a/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalSyntheticComponent.java b/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalSyntheticComponent.java index f8e0035..701f0f6 100644 --- a/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalSyntheticComponent.java +++ b/measure/src/main/java/net/laprun/sustainability/power/analysis/total/TotalSyntheticComponent.java @@ -1,73 +1,28 @@ package net.laprun.sustainability.power.analysis.total; -import java.util.Arrays; -import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Collectors; - -import net.laprun.sustainability.power.Errors; import net.laprun.sustainability.power.SensorMetadata; import net.laprun.sustainability.power.SensorUnit; import net.laprun.sustainability.power.analysis.SyntheticComponent; public class TotalSyntheticComponent implements SyntheticComponent { - private final Function formula; - private final SensorUnit expectedResultUnit; + private final Totaler totaler; private final SensorMetadata.ComponentMetadata metadata; public TotalSyntheticComponent(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) { - Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total"); - this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit"); - - final var errors = new Errors(); - final var totalComponents = Arrays.stream(totalComponentIndices) - .mapToObj(i -> toTotalComponent(metadata, i, errors)) - .toArray(TotalComponent[]::new); - final String name = Arrays.stream(totalComponents) - .map(TotalComponent::name) - .collect(Collectors.joining(" + ", "total (", ")")); + this.totaler = new Totaler(metadata, expectedResultUnit, totalComponentIndices); final var isAttributed = metadata.components().values().stream() .map(SensorMetadata.ComponentMetadata::isAttributed) .reduce(Boolean::logicalAnd).orElse(false); - formula = components -> { - double result = 0; - for (var totalComponent : totalComponents) { - result += components[totalComponent.index] * totalComponent.factor; - } - return result; - }; - + final var name = totaler.name(); if (metadata.exists(name)) { - errors.addError("Component " + name + " already exists"); + totaler.addError("Component " + name + " already exists"); } - if (errors.hasErrors()) { - throw new IllegalArgumentException(errors.formatErrors()); - } + totaler.validate(); this.metadata = new SensorMetadata.ComponentMetadata(name, "Aggregated " + name, isAttributed, expectedResultUnit); } - private double convertToExpectedUnit(double value) { - return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit); - } - - private record TotalComponent(String name, int index, double factor) { - } - - private TotalComponent toTotalComponent(SensorMetadata metadata, int index, Errors errors) { - final var cm = metadata.metadataFor(index); - final var name = cm.name(); - final var unit = cm.unit(); - if (!unit.isCommensurableWith(expectedResultUnit)) { - errors.addError("Component " + name - + " is not commensurable with the expected base unit: " + expectedResultUnit); - } - - final var factor = unit.factor(); - return new TotalComponent(name, index, factor); - } - @Override public SensorMetadata.ComponentMetadata metadata() { return metadata; @@ -75,6 +30,6 @@ public SensorMetadata.ComponentMetadata metadata() { @Override public double synthesizeFrom(double[] components, long timestamp) { - return convertToExpectedUnit(formula.apply(components)); + return totaler.computeTotalFrom(components); } } diff --git a/measure/src/main/java/net/laprun/sustainability/power/analysis/total/Totaler.java b/measure/src/main/java/net/laprun/sustainability/power/analysis/total/Totaler.java new file mode 100644 index 0000000..f6c11e2 --- /dev/null +++ b/measure/src/main/java/net/laprun/sustainability/power/analysis/total/Totaler.java @@ -0,0 +1,97 @@ +package net.laprun.sustainability.power.analysis.total; + +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import net.laprun.sustainability.power.Errors; +import net.laprun.sustainability.power.SensorMetadata; +import net.laprun.sustainability.power.SensorUnit; + +class Totaler { + private final SensorUnit expectedResultUnit; + private final Function formula; + private final String name; + private Errors errors; + + Totaler(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) { + Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total"); + 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); + name = Arrays.stream(totalComponents) + .map(TotalComponent::name) + .collect(Collectors.joining(" + ", "total (", ")")); + formula = formulaFrom(totalComponents); + } + + void validate() { + if (errors.hasErrors()) { + throw new IllegalArgumentException(errors.formatErrors()); + } + errors = null; + } + + public void addError(String message) { + if (errors == null) { + throw new IllegalStateException("Totaler has already been validated!"); + } + errors.addError(message); + } + + public String name() { + return name; + } + + public SensorUnit expectedResultUnit() { + return expectedResultUnit; + } + + public double computeTotalFrom(double[] measure) { + checkValidated(); + return convertToExpectedUnit(formula.apply(measure)); + } + + private void checkValidated() { + if (errors != null) { + throw new IllegalStateException("Totaler must be validated before use!"); + } + } + + private double convertToExpectedUnit(double value) { + return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit); + } + + private record TotalComponent(String name, int index, double factor) { + 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); + final var name = cm.name(); + final var unit = cm.unit(); + if (!unit.isCommensurableWith(expectedResultUnit)) { + errors.addError("Component " + name + + " is not commensurable with the expected base unit: " + expectedResultUnit); + } + + final var factor = unit.factor(); + return new TotalComponent(name, index, factor); + } + + private static Function formulaFrom(TotalComponent[] totalComponents) { + return components -> { + double result = 0; + for (var totalComponent : totalComponents) { + result += totalComponent.scaledValueFrom(components); + } + return result; + }; + } +} diff --git a/measure/src/test/java/net/laprun/sustainability/power/measure/StoppedPowerMeasureTest.java b/measure/src/test/java/net/laprun/sustainability/power/measure/StoppedPowerMeasureTest.java index 305031f..5322e8a 100644 --- a/measure/src/test/java/net/laprun/sustainability/power/measure/StoppedPowerMeasureTest.java +++ b/measure/src/test/java/net/laprun/sustainability/power/measure/StoppedPowerMeasureTest.java @@ -93,7 +93,7 @@ public String output() { final var output = PowerMeasure.asString(stopped); assertThat(output).contains(stoppedCompProcOutput, stoppedMeasureOutput, ongoingCompProcOutput); // second anonymous class - assertThat(output).contains(stoppedCompProcName, stoppedMeasureProcName, "Aggregated total from (cp1)", + assertThat(output).contains(stoppedCompProcName, stoppedMeasureProcName, "total (cp1)", getClass().getName() + "$2"); assertThat(output).contains("0.00mW"); assertThat(output).doesNotContain("Infinity");