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
@@ -0,0 +1,5 @@
package net.laprun.sustainability.power.measure;

public interface Analyzer {
void recordComponentValue(double value, long timestamp);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package net.laprun.sustainability.power.measure;

import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;

@SuppressWarnings("unused")
public class DescriptiveStatisticsAnalyzer implements Analyzer {
private final DescriptiveStatistics statistics;

public DescriptiveStatisticsAnalyzer() {
statistics = new DescriptiveStatistics();
}

@Override
public void recordComponentValue(double value, long timestamp) {
statistics.addValue(value);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.laprun.sustainability.power.measure;

import org.HdrHistogram.IntCountsHistogram;

@SuppressWarnings("unused")
public class HdrHistogramAnalyzer implements Analyzer {
private static final int HIGHEST_TRACKABLE_VALUE = 1_000_000;
private static final int NUMBER_OF_SIGNIFICANT_VALUE_DIGITS = 4;
private static final int CONVERSION_FACTOR = 1000;
private final IntCountsHistogram histogram;

public HdrHistogramAnalyzer() {
histogram = new IntCountsHistogram(HIGHEST_TRACKABLE_VALUE, NUMBER_OF_SIGNIFICANT_VALUE_DIGITS);
}

@Override
public void recordComponentValue(double value, long timestamp) {
histogram.recordValue((long) (CONVERSION_FACTOR * value));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
package net.laprun.sustainability.power.measure;

import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
import java.util.BitSet;
import java.util.Objects;

import org.apache.commons.math3.util.FastMath;

import net.laprun.sustainability.power.SensorMetadata;

public class OngoingPowerMeasure implements PowerMeasure {
private static final int DEFAULT_SIZE = 32;
private final SensorMetadata sensorMetadata;
private final ComponentMeasure[] measures;
private final long startedAt;
private final double[] averages;
private final Set<Integer> nonZeroComponents;
private final BitSet nonZeroComponents;
private final int[] totalComponents;
private final int totalIndex;
private double minTotal = Double.MAX_VALUE;
private double maxTotal;
private double accumulatedTotal;
private int samples;
private final double[][] measures;
private long[] timestamps;
private final Analyzer[] analyzers;

public OngoingPowerMeasure(SensorMetadata sensorMetadata, ComponentMeasure.Factory<?> componentMeasureFactory) {
public OngoingPowerMeasure(SensorMetadata sensorMetadata, Analyzer... analyzers) {
this.sensorMetadata = sensorMetadata;
startedAt = System.currentTimeMillis();

final var numComponents = sensorMetadata.componentCardinality();
// we also record the aggregated total for each component participating in the aggregated value
final var measuresNb = numComponents + 1;
measures = new ComponentMeasure[measuresNb];
for (int i = 0; i < measuresNb; i++) {
measures[i] = componentMeasureFactory.create();
}
measures = new double[measuresNb][DEFAULT_SIZE];
timestamps = new long[DEFAULT_SIZE];
totalIndex = numComponents;
averages = new double[measuresNb];
// 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 HashSet<>(numComponents);
nonZeroComponents = new BitSet(numComponents);
totalComponents = sensorMetadata.totalComponents();
this.analyzers = Objects.requireNonNullElseGet(analyzers, () -> new Analyzer[0]);
}

@Override
Expand All @@ -56,16 +58,16 @@ public void recordMeasure(double[] components) {
final var componentValue = components[component];
// record that the value is not zero
if (componentValue != 0) {
nonZeroComponents.add(component);
nonZeroComponents.set(component);
}
measures[component].recordComponentValue(componentValue);
recordComponentValue(component, componentValue);
averages[component] = averages[component] == 0 ? componentValue
: (previousSize * averages[component] + componentValue) / samples;
}

// record min / max totals
final var recordedTotal = PowerMeasure.sumOfSelectedComponents(components, totalComponents);
measures[totalIndex].recordComponentValue(recordedTotal);
recordComponentValue(totalIndex, recordedTotal);
accumulatedTotal += recordedTotal;
averages[components.length] = averages[components.length] == 0 ? recordedTotal
: (previousSize * averages[components.length] + recordedTotal) / samples;
Expand All @@ -77,6 +79,27 @@ public void recordMeasure(double[] components) {
}
}

private void recordComponentValue(int component, double value) {
final var currentSize = measures[component].length;
if (currentSize <= samples) {
final var newSize = currentSize * 2;
for (int index = 0; index < measures.length; index++) {
final var newArray = new double[newSize];
System.arraycopy(measures[index], 0, newArray, 0, currentSize);
measures[index] = newArray;
}
final var newTimestamps = new long[newSize];
System.arraycopy(timestamps, 0, newTimestamps, 0, currentSize);
timestamps = newTimestamps;
}
final var timestamp = System.currentTimeMillis();
timestamps[component] = timestamp;
measures[component][samples - 1] = value;
for (var analyzer : analyzers) {
analyzer.recordComponentValue(value, timestamp);
}
}

@Override
public double total() {
return accumulatedTotal;
Expand Down Expand Up @@ -113,21 +136,28 @@ public StdDev standardDeviations() {
}

private double standardDeviation(int component) {
final var values = measures[component].getComponentRawValues();
final var values = measures[component];
if (samples <= 1) {
return 0.0;
}
final double mean = averages[component];
double geometric_deviation_total = 0.0;
for (double value : values) {
double deviation = value - mean;
geometric_deviation_total += (deviation * deviation);
double geometricDeviationTotal = 0.0;
for (int index = 0; index < samples; index++) {
double deviation = values[index] - mean;
geometricDeviationTotal += (deviation * deviation);
}
return FastMath.sqrt(geometric_deviation_total / (samples - 1));
return FastMath.sqrt(geometricDeviationTotal / (samples - 1));
}

@Override
public double[] getMeasuresFor(int component) {
return measures[component].getComponentRawValues();
final var dest = new double[samples];
System.arraycopy(measures[component], 0, dest, 0, samples);
return dest;
}

@Override
public Analyzer[] analyzers() {
return analyzers;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ default double average() {

double[] getMeasuresFor(int component);

Analyzer[] analyzers();

/**
* Records the standard deviations for the aggregated energy comsumption value (as returned by {@link #total()}) and
* per component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class StoppedPowerMeasure implements PowerMeasure {
private final double[] averages;
private final StdDev standardDeviations;
private final double[][] measures;
private final Analyzer[] analyzers;

public StoppedPowerMeasure(PowerMeasure powerMeasure) {
this.sensorMetadata = powerMeasure.metadata();
Expand All @@ -30,6 +31,7 @@ public StoppedPowerMeasure(PowerMeasure powerMeasure) {
for (int i = 0; i < cardinality; i++) {
measures[i] = powerMeasure.getMeasuresFor(i);
}
analyzers = powerMeasure.analyzers();
}

@Override
Expand Down Expand Up @@ -76,4 +78,9 @@ public StdDev standardDeviations() {
public double[] getMeasuresFor(int component) {
return measures[component];
}

@Override
public Analyzer[] analyzers() {
return analyzers;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package net.laprun.sustainability.power.measure;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.api.Test;

import net.laprun.sustainability.power.SensorMetadata;

Expand All @@ -19,21 +19,22 @@ public int componentCardinality() {
}
};

static List<ComponentMeasure.Factory<?>> factories() {
return List.of(DescriptiveStatisticsComponentMeasure.factory(2), HdrHistogramComponentMeasure::new);
}

@ParameterizedTest
@MethodSource("factories")
void testStatistics(ComponentMeasure.Factory<?> factory) {
@Test
void testStatistics() {
final var m1c1 = 10.0;
final var m1c2 = 12.0;
final var m1c3 = 0.0;
final var m1total = m1c1 + m1c2 + m1c3;
final var m2c1 = 8.0;
final var m2c2 = 17.0;
final var m2c3 = 0.0;
final var m2total = m2c1 + m2c2 + m2c3;
final var m3c1 = 5.0;
final var m3c2 = 5.0;
final var m3c3 = 0.0;
final var m3total = m3c1 + m3c2 + m3c3;

final var measure = new OngoingPowerMeasure(metadata, factory);
final var measure = new OngoingPowerMeasure(metadata);

final var components = new double[metadata.componentCardinality()];
components[0] = m1c1;
Expand All @@ -46,19 +47,30 @@ void testStatistics(ComponentMeasure.Factory<?> factory) {
components[2] = m2c3;
measure.recordMeasure(components);

assertEquals(m1c1 + m1c2 + m2c1 + m2c2 + m1c3 + m2c3, measure.total());
assertEquals((m1c1 + m1c2 + m2c1 + m2c2 + m1c3 + m2c3) / 2, measure.average());
assertEquals(Math.min(m1c1 + m1c2 + m1c3, m2c1 + m2c2 + m2c3), measure.minMeasuredTotal());
assertEquals(Math.max(m1c1 + m1c2 + m1c3, m2c1 + m2c2 + m2c3), measure.maxMeasuredTotal());
components[0] = m3c1;
components[1] = m3c2;
components[2] = m3c3;
measure.recordMeasure(components);

assertArrayEquals(new double[] { m1c1, m2c1, m3c1 }, measure.getMeasuresFor(0));
assertArrayEquals(new double[] { m1c2, m2c2, m3c2 }, measure.getMeasuresFor(1));
assertArrayEquals(new double[] { m1c3, m2c3, m3c3 }, measure.getMeasuresFor(2));

assertEquals(m1c1 + m1c2 + m1c3 + m2c1 + m2c2 + m2c3 + m3c1 + m3c2 + m3c3, measure.total());
assertEquals((m1c1 + m1c2 + m1c3 + m2c1 + m2c2 + m2c3 + m3c1 + m3c2 + m3c3) / 3, measure.average());
assertEquals(Stream.of(m1total, m2total, m3total).min(Double::compareTo).orElseThrow(), measure.minMeasuredTotal());
assertEquals(Stream.of(m1total, m2total, m3total).max(Double::compareTo).orElseThrow(), measure.maxMeasuredTotal());
final var c1Avg = measure.averagesPerComponent()[0];
final var c2Avg = measure.averagesPerComponent()[1];
final var c3Avg = measure.averagesPerComponent()[2];
assertEquals((m1c1 + m2c1) / 2, c1Avg);
assertEquals((m1c2 + m2c2) / 2, c2Avg);
assertEquals((m1c1 + m2c1 + m3c1) / 3, c1Avg);
assertEquals((m1c2 + m2c2 + m3c2) / 3, c2Avg);
assertEquals(0, c3Avg);

final var stdVarForC1 = Math.sqrt((Math.pow(m1c1 - c1Avg, 2) + Math.pow(m2c1 - c1Avg, 2)) / (2 - 1));
final var stdVarForC2 = Math.sqrt((Math.pow(m1c2 - c2Avg, 2) + Math.pow(m2c2 - c2Avg, 2)) / (2 - 1));
final var stdVarForC1 = Math
.sqrt((Math.pow(m1c1 - c1Avg, 2) + Math.pow(m2c1 - c1Avg, 2) + Math.pow(m3c1 - c1Avg, 2)) / (3 - 1));
final var stdVarForC2 = Math
.sqrt((Math.pow(m1c2 - c2Avg, 2) + Math.pow(m2c2 - c2Avg, 2) + Math.pow(m3c2 - c2Avg, 2)) / (3 - 1));

assertEquals(stdVarForC1, measure.standardDeviations().perComponent()[0], 0.0001,
"Standard Deviation did not match the expected value");
Expand Down