Skip to content

Commit 6718903

Browse files
committed
feat: rewrite SensorUnit with own unit system, total now works properly
Fixes #161
1 parent a5008df commit 6718903

File tree

11 files changed

+286
-162
lines changed

11 files changed

+286
-162
lines changed

analysis/src/test/java/ComputeTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313

1414
public class ComputeTest {
1515
private final static SensorMetadata metadata = SensorMetadata
16-
.withNewComponent("cp1", null, true, null, false)
17-
.withNewComponent("cp2", null, true, null, false)
18-
.withNewComponent("cp3", null, true, null, false)
16+
.withNewComponent("cp1", null, true, "mW", false)
17+
.withNewComponent("cp2", null, true, "mW", false)
18+
.withNewComponent("cp3", null, true, "mW", false)
1919
.build();
2020

2121
@Test

measure/src/main/java/net/laprun/sustainability/power/analysis/TotalMeasureProcessor.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ public class TotalMeasureProcessor implements MeasureProcessor {
1515
private double maxTotal;
1616
private double accumulatedTotal;
1717
private final Function<double[], Double> formula;
18+
private final SensorUnit expectedResultUnit;
1819

19-
public TotalMeasureProcessor(SensorMetadata metadata, int... totalComponentIndices) {
20+
public TotalMeasureProcessor(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {
2021
Objects.requireNonNull(totalComponentIndices, "Must specify component indices that will aggregated in a total");
22+
this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit");
2123

2224
final var errors = new Errors();
2325
final var totalComponents = Arrays.stream(totalComponentIndices)
@@ -42,25 +44,30 @@ public TotalMeasureProcessor(SensorMetadata metadata, int... totalComponentIndic
4244
private TotalComponent toTotalComponent(SensorMetadata metadata, int index, Errors errors) {
4345
final var cm = metadata.metadataFor(index);
4446
final var name = cm.name();
45-
if (!cm.isWattCommensurable()) {
47+
final var unit = cm.unit();
48+
if (!unit.isCommensurableWith(expectedResultUnit)) {
4649
errors.addError("Component " + name
47-
+ " is not commensurate with a power measure. It needs to be expressible in Watts.");
50+
+ " is not commensurable with the expected base unit: " + expectedResultUnit);
4851
}
4952

50-
final var factor = SensorUnit.of(cm.unit()).getUnit().factor();
53+
final var factor = unit.factor();
5154
return new TotalComponent(name, index, factor);
5255
}
5356

5457
public double total() {
55-
return accumulatedTotal;
58+
return convertToExpectedUnit(accumulatedTotal);
5659
}
5760

5861
public double minMeasuredTotal() {
59-
return minTotal;
62+
return convertToExpectedUnit(minTotal);
6063
}
6164

6265
public double maxMeasuredTotal() {
63-
return maxTotal;
66+
return convertToExpectedUnit(maxTotal);
67+
}
68+
69+
private double convertToExpectedUnit(double value) {
70+
return value * expectedResultUnit.base().conversionFactorTo(expectedResultUnit);
6471
}
6572

6673
private record TotalComponent(String name, int index, double factor) {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package net.laprun.sustainability.power.analysis;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import java.util.stream.Stream;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
import net.laprun.sustainability.power.SensorMetadata;
10+
import net.laprun.sustainability.power.SensorUnit;
11+
12+
class TotalMeasureProcessorTest {
13+
14+
@Test
15+
void totalShouldFailIfAllComponentsAreNotCommensurable() {
16+
final var inError = "cp2";
17+
final var metadata = SensorMetadata
18+
.withNewComponent("cp1", null, true, "mW", true)
19+
.withNewComponent(inError, null, true, "mJ", true)
20+
.build();
21+
22+
final var expectedResultUnit = SensorUnit.W;
23+
final var e = assertThrows(IllegalArgumentException.class,
24+
() -> new TotalMeasureProcessor(metadata, expectedResultUnit, 0, 1));
25+
assertTrue(e.getMessage().contains("Component " + inError
26+
+ " is not commensurable with the expected base unit: " + expectedResultUnit));
27+
}
28+
29+
@Test
30+
void testTotal() {
31+
final var metadata = SensorMetadata
32+
.withNewComponent("cp1", null, true, "mW", true)
33+
.withNewComponent("cp2", null, true, "mW", true)
34+
.withNewComponent("cp3", null, true, "mW", true)
35+
.build();
36+
37+
final var m1c1 = 10.0;
38+
final var m1c2 = 12.0;
39+
final var m1c3 = 0.0;
40+
final var m1total = m1c1 + m1c2 + m1c3;
41+
final var m2c1 = 8.0;
42+
final var m2c2 = 17.0;
43+
final var m2c3 = 0.0;
44+
final var m2total = m2c1 + m2c2 + m2c3;
45+
final var m3c1 = 5.0;
46+
final var m3c2 = 5.0;
47+
final var m3c3 = 0.0;
48+
final var m3total = m3c1 + m3c2 + m3c3;
49+
50+
final var totalProc = new TotalMeasureProcessor(metadata, SensorUnit.of("mW"), 0, 1, 2);
51+
52+
final var components = new double[metadata.componentCardinality()];
53+
components[0] = m1c1;
54+
components[1] = m1c2;
55+
components[2] = m1c3;
56+
totalProc.recordMeasure(components, System.currentTimeMillis());
57+
58+
components[0] = m2c1;
59+
components[1] = m2c2;
60+
components[2] = m2c3;
61+
totalProc.recordMeasure(components, System.currentTimeMillis());
62+
63+
components[0] = m3c1;
64+
components[1] = m3c2;
65+
components[2] = m3c3;
66+
totalProc.recordMeasure(components, System.currentTimeMillis());
67+
68+
assertEquals(m1c1 + m1c2 + m1c3 + m2c1 + m2c2 + m2c3 + m3c1 + m3c2 + m3c3, totalProc.total());
69+
assertEquals(Stream.of(m1total, m2total, m3total).min(Double::compareTo).orElseThrow(), totalProc.minMeasuredTotal());
70+
assertEquals(Stream.of(m1total, m2total, m3total).max(Double::compareTo).orElseThrow(), totalProc.maxMeasuredTotal());
71+
}
72+
73+
}

measure/src/test/java/net/laprun/sustainability/power/measure/OngoingPowerMeasureTest.java

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
package net.laprun.sustainability.power.measure;
22

33
import static org.assertj.core.api.Assertions.assertThat;
4-
import static org.junit.jupiter.api.Assertions.assertEquals;
54

65
import java.util.ArrayList;
76
import java.util.List;
87
import java.util.Random;
98
import java.util.random.RandomGenerator;
10-
import java.util.stream.Stream;
119

1210
import org.junit.jupiter.api.Test;
1311

1412
import net.laprun.sustainability.power.SensorMetadata;
1513
import net.laprun.sustainability.power.analysis.ComponentProcessor;
16-
import net.laprun.sustainability.power.analysis.TotalMeasureProcessor;
1714

1815
public class OngoingPowerMeasureTest {
1916
private final static SensorMetadata metadata = SensorMetadata
@@ -25,9 +22,9 @@ public class OngoingPowerMeasureTest {
2522
@Test
2623
void checkThatTotalComponentIsProperlyAdded() {
2724
final var metadata = SensorMetadata
28-
.withNewComponent("cp1", null, true, null, false)
29-
.withNewComponent("cp2", null, true, null, false)
30-
.withNewComponent("cp3", null, true, null, false)
25+
.withNewComponent("cp1", null, true, "mW", false)
26+
.withNewComponent("cp2", null, true, "mW", false)
27+
.withNewComponent("cp3", null, true, "mW", false)
3128
.build();
3229
var measure = new OngoingPowerMeasure(metadata);
3330
assertThat(measure.metadata().totalComponents()).isEmpty();
@@ -75,46 +72,6 @@ void testBasics() {
7572
assertThat(measures.measures()).isEqualTo(new double[] { m3c1, m3c2, m3c3 });
7673
}
7774

78-
@Test
79-
void testTotal() {
80-
final var m1c1 = 10.0;
81-
final var m1c2 = 12.0;
82-
final var m1c3 = 0.0;
83-
final var m1total = m1c1 + m1c2 + m1c3;
84-
final var m2c1 = 8.0;
85-
final var m2c2 = 17.0;
86-
final var m2c3 = 0.0;
87-
final var m2total = m2c1 + m2c2 + m2c3;
88-
final var m3c1 = 5.0;
89-
final var m3c2 = 5.0;
90-
final var m3c3 = 0.0;
91-
final var m3total = m3c1 + m3c2 + m3c3;
92-
93-
final var measure = new OngoingPowerMeasure(metadata);
94-
final var totalProc = new TotalMeasureProcessor(metadata, 0, 1, 2);
95-
measure.registerMeasureProcessor(totalProc);
96-
97-
final var components = new double[metadata.componentCardinality()];
98-
components[0] = m1c1;
99-
components[1] = m1c2;
100-
components[2] = m1c3;
101-
measure.recordMeasure(components);
102-
103-
components[0] = m2c1;
104-
components[1] = m2c2;
105-
components[2] = m2c3;
106-
measure.recordMeasure(components);
107-
108-
components[0] = m3c1;
109-
components[1] = m3c2;
110-
components[2] = m3c3;
111-
measure.recordMeasure(components);
112-
113-
assertEquals(m1c1 + m1c2 + m1c3 + m2c1 + m2c2 + m2c3 + m3c1 + m3c2 + m3c3, totalProc.total());
114-
assertEquals(Stream.of(m1total, m2total, m3total).min(Double::compareTo).orElseThrow(), totalProc.minMeasuredTotal());
115-
assertEquals(Stream.of(m1total, m2total, m3total).max(Double::compareTo).orElseThrow(), totalProc.maxMeasuredTotal());
116-
}
117-
11875
@Test
11976
void processorsShouldBeCalled() {
12077
final var random = Random.from(RandomGenerator.getDefault());

metadata/pom.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@
1313
<description>Metadata model used by the power-server project, extracted to allow reuse in client projects</description>
1414

1515
<dependencies>
16-
<dependency>
17-
<groupId>eu.hoefel</groupId>
18-
<artifactId>units</artifactId>
19-
</dependency>
2016
<dependency>
2117
<groupId>org.assertj</groupId>
2218
<artifactId>assertj-core</artifactId>

metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import java.util.Objects;
1212

1313
import com.fasterxml.jackson.annotation.JsonCreator;
14-
import com.fasterxml.jackson.annotation.JsonIgnore;
1514
import com.fasterxml.jackson.annotation.JsonProperty;
1615

1716
/**
@@ -23,6 +22,7 @@ public class SensorMetadata {
2322
private final Map<String, ComponentMetadata> components;
2423
@JsonProperty("documentation")
2524
private final String documentation;
25+
// todo: remove
2626
@JsonProperty("totalComponents")
2727
private final int[] totalComponents;
2828

@@ -42,6 +42,7 @@ public SensorMetadata(List<ComponentMetadata> components, String documentation)
4242
final var errors = new Errors();
4343
final var indices = new BitSet(cardinality);
4444
final var totalIndices = new BitSet(cardinality);
45+
final var baseUnit = new SensorUnit[1];
4546
components.forEach(component -> {
4647
// check that index is valid
4748
final var index = component.index;
@@ -58,18 +59,6 @@ public SensorMetadata(List<ComponentMetadata> components, String documentation)
5859
indices.set(index);
5960
}
6061

61-
// check that component's unit is commensurable to Watts if included in total
62-
if (component.isIncludedInTotal) {
63-
if (indexValid) {
64-
totalIndices.set(index);
65-
}
66-
67-
if (!component.isWattCommensurable()) {
68-
errors.addError("Component " + component.name
69-
+ " is not commensurate with a power measure. It needs to be expressible in Watts.");
70-
}
71-
}
72-
7362
if (this.components.containsKey(component.name)) {
7463
errors.addError("Multiple components are named '" + component.name + "': "
7564
+ components.stream().filter(cm -> cm.name.equals(component.name)).toList());
@@ -100,7 +89,14 @@ public SensorMetadata(List<ComponentMetadata> components, String documentation)
10089
this.totalComponents = totalComponents;
10190
}
10291

103-
public static SensorMetadata.Builder withNewComponent(String name, String description, boolean isAttributed, String unit,
92+
public static SensorMetadata.Builder withNewComponent(String name, String description, boolean isAttributed,
93+
String unitSymbol,
94+
boolean participatesInTotal) {
95+
return new SensorMetadata.Builder().withNewComponent(name, description, isAttributed, unitSymbol, participatesInTotal);
96+
}
97+
98+
public static SensorMetadata.Builder withNewComponent(String name, String description, boolean isAttributed,
99+
SensorUnit unit,
104100
boolean participatesInTotal) {
105101
return new SensorMetadata.Builder().withNewComponent(name, description, isAttributed, unit, participatesInTotal);
106102
}
@@ -207,7 +203,14 @@ public static class Builder {
207203
private int currentIndex = 0;
208204
private String documentation;
209205

210-
public Builder withNewComponent(String name, String description, boolean isAttributed, String unit,
206+
public Builder withNewComponent(String name, String description, boolean isAttributed, String unitSymbol,
207+
boolean isIncludedInTotal) {
208+
components
209+
.add(new ComponentMetadata(name, currentIndex++, description, isAttributed, unitSymbol, isIncludedInTotal));
210+
return this;
211+
}
212+
213+
public Builder withNewComponent(String name, String description, boolean isAttributed, SensorUnit unit,
211214
boolean isIncludedInTotal) {
212215
components.add(new ComponentMetadata(name, currentIndex++, description, isAttributed, unit, isIncludedInTotal));
213216
return this;
@@ -240,23 +243,21 @@ public SensorMetadata build() {
240243
* metric for that sensor. Components that take part of the total computation must use a unit commensurable with
241244
* {@link SensorUnit#W}
242245
*/
243-
public record ComponentMetadata(String name, int index, String description, boolean isAttributed, String unit,
246+
public record ComponentMetadata(String name, int index, String description, boolean isAttributed, SensorUnit unit,
244247
boolean isIncludedInTotal) {
245248

246249
public ComponentMetadata {
247250
if (name == null) {
248251
throw new IllegalArgumentException("Component name cannot be null");
249252
}
253+
if (unit == null) {
254+
throw new IllegalArgumentException("Component unit cannot be null");
255+
}
250256
}
251257

252-
/**
253-
* Determines whether or not this component is measuring power (i.e. its value can be converted to Watts)
254-
*
255-
* @return {@code true} if this component's unit is commensurable to Watts, {@code false} otherwise
256-
*/
257-
@JsonIgnore
258-
public boolean isWattCommensurable() {
259-
return unit != null && SensorUnit.of(unit).isWattCommensurable();
258+
public ComponentMetadata(String name, int index, String description, boolean isAttributed, String unitSymbol,
259+
boolean isIncludedInTotal) {
260+
this(name, index, description, isAttributed, SensorUnit.of(unitSymbol), isIncludedInTotal);
260261
}
261262
}
262263
}

0 commit comments

Comments
 (0)