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
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,22 @@ public void recordMeasure(double[] components, long timestamp) {
}

for (var index = 0; index < components.length; index++) {
final var fIndex = index;
final var componentProcessors = processors[index];
if (componentProcessors != null) {
componentProcessors.forEach(proc -> proc.recordComponentValue(components[fIndex], timestamp));
}
recordComponentValue(components[index], timestamp, index);
}
}

private void recordComponentValue(double value, long timestamp, int componentIndex) {
final var componentProcessors = processors[componentIndex];
if (componentProcessors != null) {
componentProcessors.forEach(proc -> proc.recordComponentValue(value, timestamp));
}
}

@Override
public void recordSyntheticComponentValue(double syntheticValue, long timestamp, int componentIndex) {
recordComponentValue(syntheticValue, componentIndex, componentIndex);
}

@Override
public void registerProcessorFor(int componentIndex, ComponentProcessor processor) {
if (processor != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ default List<MeasureProcessor> measureProcessors() {
default String output(SensorMetadata metadata) {
return "";
}

default void recordSyntheticComponentValue(double syntheticValue, long timestamp, int componentIndex) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package net.laprun.sustainability.power.analysis;

import net.laprun.sustainability.power.SensorMetadata;

public class RegisteredSyntheticComponent implements SyntheticComponent {
private final SyntheticComponent syntheticComponent;
private final int computedIndex;

public RegisteredSyntheticComponent(SyntheticComponent syntheticComponent, int computedIndex) {
this.syntheticComponent = syntheticComponent;
this.computedIndex = computedIndex;
}

@Override
public SensorMetadata.ComponentMetadata metadata() {
return syntheticComponent.metadata();
}

@Override
public double synthesizeFrom(double[] components, long timestamp) {
return syntheticComponent.synthesizeFrom(components, timestamp);
}

public int computedIndex() {
return computedIndex;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.laprun.sustainability.power.analysis;

import net.laprun.sustainability.power.SensorMetadata;

public interface SyntheticComponent {

SensorMetadata.ComponentMetadata metadata();

double synthesizeFrom(double[] components, long timestamp);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.laprun.sustainability.power.analysis;
package net.laprun.sustainability.power.analysis.total;

import java.util.Arrays;
import java.util.Objects;
Expand All @@ -8,6 +8,7 @@
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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<double[], Double> formula;
private final SensorUnit expectedResultUnit;
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 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", ""));
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;
};

if (metadata.exists(name)) {
errors.addError("Component " + name + " already exists");
}

if (errors.hasErrors()) {
throw new IllegalArgumentException(errors.formatErrors());
}

this.metadata = new SensorMetadata.ComponentMetadata(name, description, 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;
}

@Override
public double synthesizeFrom(double[] components, long timestamp) {
return convertToExpectedUnit(formula.apply(components));
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
package net.laprun.sustainability.power.measure;

import java.time.Duration;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import java.util.Optional;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import net.laprun.sustainability.power.SensorMetadata;
import net.laprun.sustainability.power.analysis.Processors;
import net.laprun.sustainability.power.analysis.RegisteredSyntheticComponent;
import net.laprun.sustainability.power.analysis.SyntheticComponent;

public class OngoingPowerMeasure extends ProcessorAware implements PowerMeasure {
private static final int DEFAULT_SIZE = 32;
private final SensorMetadata metadata;
private final long startedAt;
private final BitSet nonZeroComponents;
private final double[][] measures;
private final List<RegisteredSyntheticComponent> syntheticComponents;
private int samples;
private long[] timestamps;

public OngoingPowerMeasure(SensorMetadata metadata) {
public OngoingPowerMeasure(SensorMetadata metadata, SyntheticComponent... syntheticComponents) {
super(Processors.empty);

startedAt = System.currentTimeMillis();
this.metadata = metadata;

final var numComponents = metadata.componentCardinality();
measures = new double[numComponents][DEFAULT_SIZE];
timestamps = new long[DEFAULT_SIZE];
// we don't need to record the total component as a non-zero component since it's almost never zero and we compute the std dev separately
nonZeroComponents = new BitSet(numComponents);
timestamps = new long[DEFAULT_SIZE];

if (syntheticComponents != null) {
final var builder = SensorMetadata.from(metadata);
for (var component : syntheticComponents) {
builder.withNewComponent(component.metadata());
}
this.metadata = builder.build();
this.syntheticComponents = Arrays.stream(syntheticComponents)
.map(sc -> new RegisteredSyntheticComponent(sc, this.metadata.metadataFor(sc.metadata().name()).index()))
.toList();
} else {
this.syntheticComponents = List.of();
this.metadata = metadata;
}
}

@Override
Expand All @@ -54,7 +70,14 @@ public void recordMeasure(double[] components) {
recordComponentValue(component, componentValue, timestamp);
}

processors().recordMeasure(components, timestamp);
final var processors = processors();
processors.recordMeasure(components, timestamp);

if (!syntheticComponents.isEmpty()) {
syntheticComponents.forEach(sc -> processors.recordSyntheticComponentValue(sc.synthesizeFrom(components, timestamp),
timestamp, sc.computedIndex()));

}
}

private void recordComponentValue(int component, double value, long timestamp) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.laprun.sustainability.power.analysis;
package net.laprun.sustainability.power.analysis.total;

import static org.junit.jupiter.api.Assertions.*;

Expand All @@ -9,7 +9,7 @@
import net.laprun.sustainability.power.SensorMetadata;
import net.laprun.sustainability.power.SensorUnit;

class TotalMeasureProcessorTest {
class TotalComputationTest {

@Test
void totalShouldFailIfAllComponentsAreNotCommensurable() {
Expand All @@ -20,10 +20,15 @@ void totalShouldFailIfAllComponentsAreNotCommensurable() {
.build();

final var expectedResultUnit = SensorUnit.W;
final var e = assertThrows(IllegalArgumentException.class,
var e = assertThrows(IllegalArgumentException.class,
() -> new TotalMeasureProcessor(metadata, expectedResultUnit, 0, 1));
assertTrue(e.getMessage().contains("Component " + inError
+ " is not commensurable with the expected base unit: " + expectedResultUnit));

e = assertThrows(IllegalArgumentException.class,
() -> new TotalSyntheticComponent(metadata, expectedResultUnit, 0, 1));
assertTrue(e.getMessage().contains("Component " + inError
+ " is not commensurable with the expected base unit: " + expectedResultUnit));
}

@Test
Expand All @@ -48,22 +53,28 @@ void testTotal() {
final var m3total = m3c1 + m3c2 + m3c3;

final var totalProc = new TotalMeasureProcessor(metadata, SensorUnit.of("mW"), 0, 1, 2);
final var totalSyncComp = new TotalSyntheticComponent(metadata, SensorUnit.W, 0, 1, 2);

final var components = new double[metadata.componentCardinality()];
components[0] = m1c1;
components[1] = m1c2;
components[2] = m1c3;
totalProc.recordMeasure(components, System.currentTimeMillis());
// original components use mW as unit but we're asking for a synthetic total in W so the resulting total should be factored
final var mWtoWFactor = SensorUnit.mW.conversionFactorTo(SensorUnit.W);
assertEquals(m1total * mWtoWFactor, totalSyncComp.synthesizeFrom(components, 0));

components[0] = m2c1;
components[1] = m2c2;
components[2] = m2c3;
totalProc.recordMeasure(components, System.currentTimeMillis());
assertEquals(m2total * mWtoWFactor, totalSyncComp.synthesizeFrom(components, 0));

components[0] = m3c1;
components[1] = m3c2;
components[2] = m3c3;
totalProc.recordMeasure(components, System.currentTimeMillis());
assertEquals(m3total * mWtoWFactor, totalSyncComp.synthesizeFrom(components, 0));

assertEquals(m1c1 + m1c2 + m1c3 + m2c1 + m2c2 + m2c3 + m3c1 + m3c2 + m3c3, totalProc.total());
assertEquals(Stream.of(m1total, m2total, m3total).min(Double::compareTo).orElseThrow(), totalProc.minMeasuredTotal());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
import org.junit.jupiter.api.Test;

import net.laprun.sustainability.power.SensorMetadata;
import net.laprun.sustainability.power.SensorUnit;
import net.laprun.sustainability.power.analysis.ComponentProcessor;
import net.laprun.sustainability.power.analysis.MeasureProcessor;
import net.laprun.sustainability.power.analysis.SyntheticComponent;

public class OngoingPowerMeasureTest {
private final static SensorMetadata metadata = SensorMetadata
Expand Down Expand Up @@ -97,6 +99,42 @@ void processorsShouldBeCalled() {
assertThat(measureProc.values.getLast().measures()).isEqualTo(new double[] { m2c1, m2c2, 0 });
}

@Test
void syntheticComponentsShouldWork() {
final var random = Random.from(RandomGenerator.getDefault());
final var m1c1 = random.nextDouble();
final var m2c1 = random.nextDouble();

final var doublerName = "doubler";
final var doubler = new SyntheticComponent() {
@Override
public SensorMetadata.ComponentMetadata metadata() {
return new SensorMetadata.ComponentMetadata(doublerName, "doubler desc", true, SensorUnit.mW);
}

@Override
public double synthesizeFrom(double[] components, long timestamp) {
return components[0] * 2;
}
};

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();
measure.registerProcessorFor(doublerIndex, testProc);

final var components = new double[metadata.componentCardinality()];
components[0] = m1c1;
measure.recordMeasure(components);

components[0] = m2c1;
measure.recordMeasure(components);

assertThat(testProc.values.getFirst().value()).isEqualTo(m1c1 * 2);
assertThat(testProc.values.getLast().value()).isEqualTo(m2c1 * 2);
}

private static class TestComponentProcessor implements ComponentProcessor {
final List<PowerMeasure.TimestampedValue> values = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import net.laprun.sustainability.power.analysis.ComponentProcessor;
import net.laprun.sustainability.power.analysis.MeasureProcessor;
import net.laprun.sustainability.power.analysis.Processors;
import net.laprun.sustainability.power.analysis.TotalMeasureProcessor;
import net.laprun.sustainability.power.analysis.total.TotalMeasureProcessor;

public class StoppedPowerMeasureTest {
private final static SensorMetadata metadata = SensorMetadata
Expand Down
Loading
Loading