Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
import java.util.stream.DoubleStream;

public interface Recorder {
DoubleStream measures();
default DoubleStream measures() {
return DoubleStream.of(liveMeasures());
}

double[] liveMeasures();
}
Original file line number Diff line number Diff line change
@@ -1,109 +1,41 @@
package net.laprun.sustainability.power.measure;

import java.time.Duration;
import java.util.Arrays;
public record Cursor(int startIndex, int endIndex, double firstMeasureRatio, double lastMeasureRatio) {

public enum Cursor {
;
public static final Cursor empty = new Cursor(-1, -1, 0.0, 0.0);

public static PartialCursor cursorOver(long[] timestamps, long timestamp, Duration duration, long initialOffset,
long averagePeriodHint) {
// adjusted timestamp for modding
System.out.println(Arrays.toString(timestamps));
System.out.println("timestamp = " + timestamp);
final var timestampForDiv = timestamp - initialOffset;
final var durationAsMs = duration.toMillis();
System.out.println("durationAsMs = " + durationAsMs);

// cannot find an interval for a timestamp that is before the recording started
if (timestampForDiv < 0) {
return PartialCursor.empty;
}

if (timestamps.length < 2) {
// if we don't have a sample period, use the full measure
double ratio = 1.0;
if (averagePeriodHint > 0) {
ratio = (double) durationAsMs / averagePeriodHint;
}
return new PartialCursor(0, 0, ratio, ratio);
}

// estimate sample period based on 2 samples interval
if (averagePeriodHint <= 0) {
averagePeriodHint = timestamps[1] - timestamps[0];
public double sum(double[] values) {
if (values == null || values.length == 0 || this == empty || values.length <= endIndex) {
return 0.0;
}

// first, find potential first sample based on timestamp
int startIndex = (int) Math.floorDiv(timestampForDiv, averagePeriodHint);
System.out.println("startIndex = " + startIndex);
int endIndex = (int) Math.floorDiv(timestampForDiv + durationAsMs, averagePeriodHint);
System.out.println("endIndex = " + endIndex);

if (startIndex == endIndex) {
final long previousTimestamp = startIndex == 0 ? initialOffset : timestamps[startIndex - 1];
final long slotDuration = timestamps[startIndex] - previousTimestamp;
var ratio = (double) durationAsMs / slotDuration;
return new PartialCursor(startIndex, endIndex, ratio, -1);
}

// get the index with the timestamp right after the one we're looking for since what we're interested in is the portion of the measure that gets recorded after the timestamp we want
long afterTimestamp = timestamps[startIndex];
final long startOffset = afterTimestamp - timestamp;
double startRatio = 0;
if (startOffset > 0) {
startRatio = (double) startOffset / (afterTimestamp - timestamps[startIndex - 1]);
return values[startIndex] * firstMeasureRatio;
}

// look for the index that records the first timestamp that's after the one we're looking for added to the duration
afterTimestamp = timestamps[endIndex];
final long slotDuration = afterTimestamp - timestamps[endIndex - 1];
final long endOffset = slotDuration - (afterTimestamp - timestamp - durationAsMs);
double endRatio = 0;
if (endOffset > 0) {
endRatio = (double) endOffset / slotDuration;
double sum = values[startIndex] * firstMeasureRatio;
for (int i = startIndex + 1; i < endIndex; i++) {
sum += values[i];
}
sum += values[endIndex] * lastMeasureRatio;

return new PartialCursor(startIndex, endIndex, startRatio, endRatio);
return sum;
}

public record PartialCursor(int startIndex, int endIndex, double firstMeasureRatio, double lastMeasureRatio) {

public static final PartialCursor empty = new PartialCursor(-1, -1, 0.0, 0.0);

public double sum(double[] values) {
if (values == null || values.length == 0 || this == empty || values.length < startIndex + endIndex) {
return 0.0;
}

if (startIndex == endIndex) {
return values[startIndex] * firstMeasureRatio;
}

double sum = values[startIndex] * firstMeasureRatio;
for (int i = startIndex + 1; i < endIndex; i++) {
sum += values[i];
}
sum += values[endIndex] * lastMeasureRatio;

return sum;
public double[] viewOf(double[] values) {
if (values == null || values.length == 0 || this == empty || values.length < startIndex + endIndex) {
return new double[0];
}

public double[] viewOf(double[] values) {
if (values == null || values.length == 0 || this == empty || values.length < startIndex + endIndex) {
return new double[0];
}

if (startIndex == endIndex) {
return new double[] { values[startIndex] * firstMeasureRatio };
}

final int len = endIndex - startIndex + 1;
final double[] view = new double[len];
view[0] = values[startIndex] * firstMeasureRatio;
System.arraycopy(values, startIndex + 1, view, 1, len - 1 - 1);
view[len - 1] = values[endIndex] * lastMeasureRatio;
return view;
if (startIndex == endIndex) {
return new double[] { values[startIndex] * firstMeasureRatio };
}

final int len = endIndex - startIndex + 1;
final double[] view = new double[len];
view[0] = values[startIndex] * firstMeasureRatio;
System.arraycopy(values, startIndex + 1, view, 1, len - 1 - 1);
view[len - 1] = values[endIndex] * lastMeasureRatio;
return view;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package net.laprun.sustainability.power.measure;

import java.time.Duration;

public enum Cursors {
;

public static Cursor cursorOver(long[] timestamps, long timestamp, Duration duration, long initialOffset,
long averagePeriodHint) {
// adjusted timestamp for modding
final var timestampForDiv = timestamp - initialOffset;
final var durationAsMs = duration.toMillis();

// cannot find an interval for a timestamp that is before the recording started
if (timestampForDiv < 0) {
return Cursor.empty;
}

if (timestamps.length < 2) {
// if we don't have a sample period, use the full measure
double ratio = 1.0;
if (averagePeriodHint > 0) {
ratio = (double) durationAsMs / averagePeriodHint;
}
return new Cursor(0, 0, ratio, ratio);
}

// estimate sample period based on 2 samples interval
if (averagePeriodHint <= 0) {
averagePeriodHint = timestamps[1] - timestamps[0];
}

// first, find potential first sample based on timestamp
int startIndex = (int) Math.floorDiv(timestampForDiv, averagePeriodHint);
int endIndex = (int) Math.floorDiv(timestampForDiv + durationAsMs, averagePeriodHint);

if (startIndex == endIndex) {
final long previousTimestamp = startIndex == 0 ? initialOffset : timestamps[startIndex - 1];
final long slotDuration = timestamps[startIndex] - previousTimestamp;
var ratio = (double) durationAsMs / slotDuration;
return new Cursor(startIndex, endIndex, ratio, -1);
}

// get the index with the timestamp right after the one we're looking for since what we're interested in is the portion of the measure that gets recorded after the timestamp we want
long afterTimestamp = timestamps[startIndex];
final long startOffset = afterTimestamp - timestamp;
double startRatio = 0;
if (startOffset > 0) {
startRatio = (double) startOffset / (afterTimestamp - timestamps[startIndex - 1]);
}

// look for the index that records the first timestamp that's after the one we're looking for added to the duration
afterTimestamp = timestamps[endIndex];
final long slotDuration = afterTimestamp - timestamps[endIndex - 1];
final long endOffset = slotDuration - (afterTimestamp - timestamp - durationAsMs);
double endRatio = 0;
if (endOffset > 0) {
endRatio = (double) endOffset / slotDuration;
}

return new Cursor(startIndex, endIndex, startRatio, endRatio);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.laprun.sustainability.power.measure;

import net.laprun.sustainability.power.analysis.Recorder;

public record MeasureBackedCursor(Recorder measure, Cursor cursor) {
public double sum() {
return cursor.sum(measure.liveMeasures());
}

public double[] view() {
return cursor.viewOf(measure.liveMeasures());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
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 final long samplePeriod;
private double[][] measures;
private long startedAt;
private int samples;
private long[] timestamps;
private long samplePeriod;

public OngoingPowerMeasure(SensorMetadata metadata, SyntheticComponent... syntheticComponents) {
this(metadata, -1, syntheticComponents);
Expand All @@ -30,11 +30,9 @@ public OngoingPowerMeasure(SensorMetadata metadata, SyntheticComponent... synthe
public OngoingPowerMeasure(SensorMetadata metadata, long samplePeriod, SyntheticComponent... syntheticComponents) {
super(Processors.empty);

startedAt = System.currentTimeMillis();
final var numComponents = metadata.componentCardinality();
measures = new double[numComponents][DEFAULT_SIZE];
nonZeroComponents = new BitSet(numComponents);
timestamps = new long[DEFAULT_SIZE];
final int componentCardinality = metadata.componentCardinality();
nonZeroComponents = new BitSet(componentCardinality);
reset(componentCardinality);
this.samplePeriod = samplePeriod;

if (syntheticComponents != null) {
Expand All @@ -52,6 +50,18 @@ public OngoingPowerMeasure(SensorMetadata metadata, long samplePeriod, Synthetic
}
}

public void reset() {
reset(metadata.componentCardinality());
}

private synchronized void reset(int componentCardinality) {
startedAt = System.currentTimeMillis();
nonZeroComponents.clear();
measures = new double[componentCardinality][DEFAULT_SIZE];
timestamps = new long[DEFAULT_SIZE];
samples = 0;
}

@Override
public synchronized int numberOfSamples() {
return samples;
Expand Down Expand Up @@ -111,19 +121,27 @@ public Duration duration() {

@Override
public DoubleStream getMeasuresFor(int component) {
final double[] measuresForComponent = getLiveMeasuresFor(component);
if (measuresForComponent == null || measuresForComponent.length == 0) {
return DoubleStream.empty();
}
return Arrays.stream(measuresForComponent, 0, samples);
}

public double[] getLiveMeasuresFor(int component) {
if (nonZeroComponents.get(component)) {
return Arrays.stream(measures[component], 0, samples);
return measures[component];
} else {
final var match = syntheticComponents.stream()
.filter(rsc -> targetComponentExistsAndIsRecorder(component, rsc))
.map(rsc -> (Recorder) rsc.syntheticComponent())
.findFirst()
.orElse(null);
if (match != null) {
return match.measures();
return match.liveMeasures();
}
}
return DoubleStream.empty();
return new double[0];
}

private static boolean targetComponentExistsAndIsRecorder(int component, RegisteredSyntheticComponent rsc) {
Expand All @@ -140,7 +158,13 @@ public synchronized TimestampedMeasures getNthTimestampedMeasures(int n) {
return new TimestampedMeasures(timestamps[n], result);
}

public Cursor.PartialCursor getCursorOver(long timestamp, Duration duration) {
return Cursor.cursorOver(timestamps, timestamp, duration, startedAt, samplePeriod);
public Timing timingInfo() {
final var result = new long[timestamps.length];
System.arraycopy(timestamps, 0, result, 0, timestamps.length);
return new Timing(result, startedAt, samplePeriod);
}

public List<RegisteredSyntheticComponent> syntheticComponents() {
return syntheticComponents;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.laprun.sustainability.power.measure;

import java.time.Duration;

public record Timing(long[] timestamps, long startedAt, long samplePeriod) {
public Cursor cursorOver(long timestamp, Duration duration) {
return Cursors.cursorOver(timestamps, timestamp, duration, startedAt, samplePeriod);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class CursorTest {
class CursorsTest {

@ParameterizedTest
@ValueSource(longs = { -1, 0, 100, 98, 105 })
void cursorOverSimple(long periodHint) {
final var timestamps = new long[] { 100, 200, 300, 400, 500, 600, 700, 800, 900 };

final var cursor = Cursor.cursorOver(timestamps, 225, Duration.ofMillis(540 - 225), 0,
final var cursor = Cursors.cursorOver(timestamps, 225, Duration.ofMillis(540 - 225), 0,
periodHint);

assertEquals(2, cursor.startIndex());
Expand All @@ -37,7 +37,7 @@ void cursorOverSimple(long periodHint) {
void cursorOverOneMeasure() {
final var timestamps = new long[] { 100, 200, 300, 400, 500, 600, 700, 800, 900 };

final var cursor = Cursor.cursorOver(timestamps, 1, Duration.ofMillis(10), 0,
final var cursor = Cursors.cursorOver(timestamps, 1, Duration.ofMillis(10), 0,
100);

assertEquals(0, cursor.startIndex());
Expand Down