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,11 @@
package net.laprun.sustainability.power.measure;

public interface ComponentMeasure {
void recordComponentValue(double value);

double[] getComponentRawValues();

interface Factory<T extends ComponentMeasure> {
T create();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package net.laprun.sustainability.power.measure;

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

public class DescriptiveStatisticsComponentMeasure implements ComponentMeasure {
private final DescriptiveStatistics statistics;

public DescriptiveStatisticsComponentMeasure(int initialWindow) {
statistics = new DescriptiveStatistics(initialWindow);
}

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

@Override
public double[] getComponentRawValues() {
return statistics.getValues();
}

public static Factory<DescriptiveStatisticsComponentMeasure> factory(int initialWindow) {
return () -> new DescriptiveStatisticsComponentMeasure(initialWindow);
}
}

This file was deleted.

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

import org.HdrHistogram.HistogramIterationValue;
import org.HdrHistogram.IntCountsHistogram;

public class HdrHistogramComponentMeasure implements ComponentMeasure {
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 HdrHistogramComponentMeasure() {
histogram = new IntCountsHistogram(HIGHEST_TRACKABLE_VALUE, NUMBER_OF_SIGNIFICANT_VALUE_DIGITS);
}

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

@Override
public double[] getComponentRawValues() {
final var totalCount = histogram.getTotalCount();
if (totalCount == 0) {
return new double[0];
}

final var result = new double[(int) totalCount];
int index = 0;
for (HistogramIterationValue value : histogram.recordedValues()) {
result[index++] = (double) value.getValueIteratedTo() / CONVERSION_FACTOR;
}
return result;
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,37 @@
import java.util.HashSet;
import java.util.Set;

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

import net.laprun.sustainability.power.SensorMetadata;

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

public OngoingPowerMeasure(SensorMetadata sensorMetadata, Duration duration, Duration frequency) {
public OngoingPowerMeasure(SensorMetadata sensorMetadata, ComponentMeasure.Factory<?> componentMeasureFactory) {
this.sensorMetadata = sensorMetadata;
startedAt = System.currentTimeMillis();
final var numComponents = metadata().componentCardinality();
averages = new double[numComponents];

final var initialWindow = (int) (duration.toMillis() / frequency.toMillis());
measures = new DescriptiveStatisticsMeasureStore(numComponents, initialWindow);

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();
}
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);
totalComponents = sensorMetadata.totalComponents();
}
Expand All @@ -49,14 +58,17 @@ public void recordMeasure(double[] components) {
if (componentValue != 0) {
nonZeroComponents.add(component);
}
measures.recordComponentValue(component, componentValue);
measures[component].recordComponentValue(componentValue);
averages[component] = averages[component] == 0 ? componentValue
: (previousSize * averages[component] + componentValue) / samples;
}

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

@Override
public double total() {
return measures.getMeasuredTotal();
return accumulatedTotal;
}

public Duration duration() {
Expand All @@ -94,13 +106,28 @@ public StdDev standardDeviations() {
final var stdDevs = new double[cardinality];
nonZeroComponents.stream()
.parallel()
.forEach(component -> stdDevs[component] = measures.getComponentStandardDeviation(component));
.forEach(component -> stdDevs[component] = standardDeviation(component));

return new StdDev(measures.getTotalStandardDeviation(), stdDevs);
final double aggregate = maxTotal == 0 ? 0 : standardDeviation(totalIndex);
return new StdDev(aggregate, stdDevs);
}

private double standardDeviation(int component) {
final var values = measures[component].getComponentRawValues();
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);
}
return FastMath.sqrt(geometric_deviation_total / (samples - 1));
}

@Override
public double[] getMeasuresFor(int component) {
return measures.getComponentRawValues(component);
return measures[component].getComponentRawValues();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,38 @@

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

import java.time.Duration;
import java.util.List;
import java.util.Map;

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

import net.laprun.sustainability.power.SensorMetadata;

public class OngoingPowerMeasureTest {
private final static SensorMetadata metadata = new SensorMetadata(Map.of(), null, new int[0]) {

@Test
void testStatistics() {
@Override
public int componentCardinality() {
return 3;
}
};

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

@ParameterizedTest
@MethodSource("factories")
void testStatistics(ComponentMeasure.Factory<?> factory) {
final var m1c1 = 10.0;
final var m1c2 = 12.0;
final var m1c3 = 0.0;
final var m2c1 = 8.0;
final var m2c2 = 17.0;
final var m2c3 = 0.0;
final var metadata = new SensorMetadata(Map.of(), null, new int[0]) {

@Override
public int componentCardinality() {
return 3;
}
};
final var measure = new OngoingPowerMeasure(metadata, Duration.ofSeconds(1), Duration.ofMillis(500));

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

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