diff --git a/measure/pom.xml b/measure/pom.xml
new file mode 100644
index 0000000..27ea832
--- /dev/null
+++ b/measure/pom.xml
@@ -0,0 +1,40 @@
+
+
+ 4.0.0
+
+
+ net.laprun.sustainability
+ power-server-parent
+ 0.0.9-SNAPSHOT
+
+
+ power-server-measure
+ power-server : measure
+ A library to gather and analyze power consumption measures
+
+
+
+ net.laprun.sustainability
+ power-server-metadata
+ ${project.version}
+
+
+
+
+
+
+
+ net.revelc.code.formatter
+ formatter-maven-plugin
+
+
+ net.laprun.sustainability
+ build-tools
+ 0.0.9-SNAPSHOT
+
+
+
+
+
+
+
diff --git a/measure/src/main/java/net/laprun/sustainability/power/measure/AbstractPowerMeasure.java b/measure/src/main/java/net/laprun/sustainability/power/measure/AbstractPowerMeasure.java
new file mode 100644
index 0000000..29a75fe
--- /dev/null
+++ b/measure/src/main/java/net/laprun/sustainability/power/measure/AbstractPowerMeasure.java
@@ -0,0 +1,33 @@
+package net.laprun.sustainability.power.measure;
+
+import java.util.List;
+
+import net.laprun.sustainability.power.SensorMetadata;
+
+public abstract class AbstractPowerMeasure implements PowerMeasure {
+ private final SensorMetadata sensorMetadata;
+ private final List measures;
+
+ protected AbstractPowerMeasure(SensorMetadata sensorMetadata, List measures) {
+ this.sensorMetadata = sensorMetadata;
+ this.measures = measures;
+ }
+
+ @Override
+ public List measures() {
+ return measures;
+ }
+
+ @Override
+ public SensorMetadata metadata() {
+ return sensorMetadata;
+ }
+
+ public int numberOfSamples() {
+ return measures.size();
+ }
+
+ double[] getComponentData(int componentIndex) {
+ return measures.parallelStream().mapToDouble(measure -> measure[componentIndex]).toArray();
+ }
+}
diff --git a/measure/src/main/java/net/laprun/sustainability/power/measure/OngoingPowerMeasure.java b/measure/src/main/java/net/laprun/sustainability/power/measure/OngoingPowerMeasure.java
new file mode 100644
index 0000000..5dbdebd
--- /dev/null
+++ b/measure/src/main/java/net/laprun/sustainability/power/measure/OngoingPowerMeasure.java
@@ -0,0 +1,67 @@
+package net.laprun.sustainability.power.measure;
+
+import java.util.ArrayList;
+
+import net.laprun.sustainability.power.SensorMetadata;
+
+public class OngoingPowerMeasure extends AbstractPowerMeasure {
+ private final long startedAt;
+ private double minTotal = Double.MAX_VALUE;
+ private double maxTotal;
+ private final double[] totals;
+ private final double[] averages;
+
+ public OngoingPowerMeasure(SensorMetadata sensorMetadata, long duration, long frequency) {
+ super(sensorMetadata, new ArrayList<>((int) (duration / frequency)));
+ startedAt = System.currentTimeMillis();
+ final var numComponents = metadata().componentCardinality();
+ totals = new double[numComponents];
+ averages = new double[numComponents];
+ }
+
+ public void recordMeasure(double[] components) {
+ final var recorded = new double[components.length];
+ System.arraycopy(components, 0, recorded, 0, components.length);
+ final var previousSize = numberOfSamples();
+ measures().add(recorded);
+
+ for (int i = 0; i < recorded.length; i++) {
+ totals[i] += recorded[i];
+ averages[i] = averages[i] == 0 ? recorded[i]
+ : (previousSize * averages[i] + recorded[i]) / numberOfSamples();
+ }
+
+ // record min / max totals
+ final var recordedTotal = PowerMeasure.sumOfComponents(recorded);
+ if (recordedTotal < minTotal) {
+ minTotal = recordedTotal;
+ }
+ if (recordedTotal > maxTotal) {
+ maxTotal = recordedTotal;
+ }
+ }
+
+ @Override
+ public double total() {
+ return PowerMeasure.sumOfComponents(totals);
+ }
+
+ public long duration() {
+ return System.currentTimeMillis() - startedAt;
+ }
+
+ @Override
+ public double minMeasuredTotal() {
+ return minTotal;
+ }
+
+ @Override
+ public double maxMeasuredTotal() {
+ return maxTotal;
+ }
+
+ @Override
+ public double[] averagesPerComponent() {
+ return averages;
+ }
+}
diff --git a/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java b/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java
new file mode 100644
index 0000000..fe52192
--- /dev/null
+++ b/measure/src/main/java/net/laprun/sustainability/power/measure/PowerMeasure.java
@@ -0,0 +1,91 @@
+package net.laprun.sustainability.power.measure;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import net.laprun.sustainability.power.SensorMetadata;
+
+public interface PowerMeasure {
+ int numberOfSamples();
+
+ long duration();
+
+ default double average() {
+ return total() / numberOfSamples();
+ }
+
+ double total();
+
+ SensorMetadata metadata();
+
+ double[] averagesPerComponent();
+
+ double minMeasuredTotal();
+
+ double maxMeasuredTotal();
+
+ static double sumOfComponents(double[] recorded) {
+ var componentSum = 0.0;
+ for (double value : recorded) {
+ componentSum += value;
+ }
+ return componentSum;
+ }
+
+ default StdDev standardDeviations() {
+ final var cardinality = metadata().componentCardinality();
+ final var stdDevs = new double[cardinality];
+ final var aggregate = new double[1];
+ final var samples = numberOfSamples() - 1; // unbiased so we remove one sample
+ final var sqrdAverages = Arrays.stream(averagesPerComponent()).map(m -> m * m).toArray();
+ final var sqrdAverage = average() * average();
+ // need to compute the average of variances then square root that to get the "aggregate" standard deviation,
+ // see: https://stats.stackexchange.com/a/26647
+ // "vectorize" computation of variances: compute the variance for each component in parallel
+ IntStream.range(0, cardinality).parallel()
+ // compute variances for each component of the measure
+ .forEach(component -> {
+ final var sumOfSquares = measures().stream().parallel().peek(m -> {
+ // compute the std dev for total measure
+ final var total = sumOfComponents(m);
+ aggregate[0] += total * total;
+ }).mapToDouble(m -> m[component] * m[component]).sum();
+ stdDevs[component] = stdDev(sumOfSquares, sqrdAverages[component], samples);
+ aggregate[0] = stdDev(aggregate[0], sqrdAverage, samples);
+ });
+ return new StdDev(aggregate[0], stdDevs);
+ }
+
+ private static double stdDev(double sumOfSquares, double squaredAvg, int samples) {
+ return Math.sqrt((sumOfSquares / samples) - (((samples + 1) * squaredAvg) / samples));
+ }
+
+ /**
+ * Records the standard deviations for the aggregated energy comsumption value (as returned by {@link #total()}) and
+ * per component
+ *
+ * @param aggregate
+ * @param perComponent
+ */
+ record StdDev(double aggregate, double[] perComponent) {
+ }
+
+ static String asString(PowerMeasure measure) {
+ final var durationInSeconds = measure.duration() / 1000;
+ final var samples = measure.numberOfSamples();
+ final var measuredMilliWatts = measure.total();
+ final var stdDevs = measure.standardDeviations();
+ return String.format("%s / avg: %s / std dev: %.3f [min: %.3f, max: %.3f] (%ds, %s samples)",
+ readableWithUnit(measuredMilliWatts), readableWithUnit(measure.average()), stdDevs.aggregate,
+ measure.minMeasuredTotal(), measure.maxMeasuredTotal(), durationInSeconds, samples);
+ }
+
+ static String readableWithUnit(double milliWatts) {
+ String unit = milliWatts >= 1000 ? "W" : "mW";
+ double power = milliWatts >= 1000 ? milliWatts / 1000 : milliWatts;
+ return String.format("%.3f%s", power, unit);
+ }
+
+ List measures();
+}
diff --git a/measure/src/main/java/net/laprun/sustainability/power/measure/StoppedPowerMeasure.java b/measure/src/main/java/net/laprun/sustainability/power/measure/StoppedPowerMeasure.java
new file mode 100644
index 0000000..1aa3f99
--- /dev/null
+++ b/measure/src/main/java/net/laprun/sustainability/power/measure/StoppedPowerMeasure.java
@@ -0,0 +1,43 @@
+package net.laprun.sustainability.power.measure;
+
+public class StoppedPowerMeasure extends AbstractPowerMeasure {
+ private final long duration;
+ private final double total;
+ private final double min;
+ private final double max;
+ private final double[] averages;
+
+ public StoppedPowerMeasure(PowerMeasure powerMeasure) {
+ super(powerMeasure.metadata(), powerMeasure.measures());
+ this.duration = powerMeasure.duration();
+ this.total = powerMeasure.total();
+ this.min = powerMeasure.minMeasuredTotal();
+ this.max = powerMeasure.maxMeasuredTotal();
+ this.averages = powerMeasure.averagesPerComponent();
+ }
+
+ @Override
+ public long duration() {
+ return duration;
+ }
+
+ @Override
+ public double minMeasuredTotal() {
+ return min;
+ }
+
+ @Override
+ public double maxMeasuredTotal() {
+ return max;
+ }
+
+ @Override
+ public double total() {
+ return total;
+ }
+
+ @Override
+ public double[] averagesPerComponent() {
+ return averages;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 6ed5b02..602a97d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,8 +66,9 @@
build-tools
- server
metadata
+ measure
+ server