Skip to content

Commit 596835a

Browse files
committed
feat: add cursor notion to retrieve measures based on timestamp
1 parent 32bad3b commit 596835a

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package net.laprun.sustainability.power.measure;
2+
3+
import java.time.Duration;
4+
import java.util.Arrays;
5+
6+
public enum Cursor {
7+
;
8+
9+
public static PartialCursor cursorOver(long[] timestamps, long timestamp, Duration duration, long initialOffset,
10+
long averagePeriodHint) {
11+
// adjusted timestamp for modding
12+
System.out.println(Arrays.toString(timestamps));
13+
System.out.println("timestamp = " + timestamp);
14+
final var timestampForDiv = timestamp - initialOffset;
15+
final var durationAsMs = duration.toMillis();
16+
System.out.println("durationAsMs = " + durationAsMs);
17+
18+
// cannot find an interval for a timestamp that is before the recording started
19+
if (timestampForDiv < 0) {
20+
return PartialCursor.empty;
21+
}
22+
23+
if (timestamps.length < 2) {
24+
// if we don't have a sample period, use the full measure
25+
double ratio = 1.0;
26+
if (averagePeriodHint > 0) {
27+
ratio = (double) durationAsMs / averagePeriodHint;
28+
}
29+
return new PartialCursor(0, 0, ratio, ratio);
30+
}
31+
32+
// estimate sample period based on 2 samples interval
33+
if (averagePeriodHint <= 0) {
34+
averagePeriodHint = timestamps[1] - timestamps[0];
35+
}
36+
37+
// first, find potential first sample based on timestamp
38+
int startIndex = (int) Math.floorDiv(timestampForDiv, averagePeriodHint);
39+
System.out.println("startIndex = " + startIndex);
40+
int endIndex = (int) Math.floorDiv(timestampForDiv + durationAsMs, averagePeriodHint);
41+
System.out.println("endIndex = " + endIndex);
42+
43+
if (startIndex == endIndex) {
44+
final long previousTimestamp = startIndex == 0 ? initialOffset : timestamps[startIndex - 1];
45+
final long slotDuration = timestamps[startIndex] - previousTimestamp;
46+
var ratio = (double) durationAsMs / slotDuration;
47+
return new PartialCursor(startIndex, endIndex, ratio, -1);
48+
}
49+
50+
// 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
51+
long afterTimestamp = timestamps[startIndex];
52+
final long startOffset = afterTimestamp - timestamp;
53+
double startRatio = 0;
54+
if (startOffset > 0) {
55+
startRatio = (double) startOffset / (afterTimestamp - timestamps[startIndex - 1]);
56+
}
57+
58+
// look for the index that records the first timestamp that's after the one we're looking for added to the duration
59+
afterTimestamp = timestamps[endIndex];
60+
final long slotDuration = afterTimestamp - timestamps[endIndex - 1];
61+
final long endOffset = slotDuration - (afterTimestamp - timestamp - durationAsMs);
62+
double endRatio = 0;
63+
if (endOffset > 0) {
64+
endRatio = (double) endOffset / slotDuration;
65+
}
66+
67+
return new PartialCursor(startIndex, endIndex, startRatio, endRatio);
68+
}
69+
70+
public record PartialCursor(int startIndex, int endIndex, double firstMeasureRatio, double lastMeasureRatio) {
71+
72+
public static final PartialCursor empty = new PartialCursor(-1, -1, 0.0, 0.0);
73+
74+
public double sum(double[] values) {
75+
if (values == null || values.length == 0 || this == empty || values.length < startIndex + endIndex) {
76+
return 0.0;
77+
}
78+
79+
if (startIndex == endIndex) {
80+
return values[startIndex] * firstMeasureRatio;
81+
}
82+
83+
double sum = values[startIndex] * firstMeasureRatio;
84+
for (int i = startIndex + 1; i < endIndex; i++) {
85+
sum += values[i];
86+
}
87+
sum += values[endIndex] * lastMeasureRatio;
88+
89+
return sum;
90+
}
91+
92+
public double[] viewOf(double[] values) {
93+
if (values == null || values.length == 0 || this == empty || values.length < startIndex + endIndex) {
94+
return new double[0];
95+
}
96+
97+
if (startIndex == endIndex) {
98+
return new double[] { values[startIndex] * firstMeasureRatio };
99+
}
100+
101+
final int len = endIndex - startIndex + 1;
102+
final double[] view = new double[len];
103+
view[0] = values[startIndex] * firstMeasureRatio;
104+
System.arraycopy(values, startIndex + 1, view, 1, len - 1 - 1);
105+
view[len - 1] = values[endIndex] * lastMeasureRatio;
106+
return view;
107+
}
108+
}
109+
}

measure/src/main/java/net/laprun/sustainability/power/measure/OngoingPowerMeasure.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,8 @@ public synchronized TimestampedMeasures getNthTimestampedMeasures(int n) {
139139
}
140140
return new TimestampedMeasures(timestamps[n], result);
141141
}
142+
143+
public Cursor.PartialCursor getCursorOver(long timestamp, Duration duration) {
144+
return Cursor.cursorOver(timestamps, timestamp, duration, startedAt, samplePeriod);
145+
}
142146
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package net.laprun.sustainability.power.measure;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import java.time.Duration;
6+
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.ValueSource;
10+
11+
class CursorTest {
12+
13+
@ParameterizedTest
14+
@ValueSource(longs = { -1, 0, 100, 98, 105 })
15+
void cursorOverSimple(long periodHint) {
16+
final var timestamps = new long[] { 100, 200, 300, 400, 500, 600, 700, 800, 900 };
17+
18+
final var cursor = Cursor.cursorOver(timestamps, 225, Duration.ofMillis(540 - 225), 0,
19+
periodHint);
20+
21+
assertEquals(2, cursor.startIndex());
22+
assertEquals(5, cursor.endIndex());
23+
assertEquals(0.75, cursor.firstMeasureRatio(), 0.0001);
24+
assertEquals(0.4, cursor.lastMeasureRatio(), 0.0001);
25+
26+
final var measures = new double[] { 100, 200, 300, 400, 500, 600, 700, 800, 900 };
27+
assertEquals(300 * 0.75 + 400 + 500 + 0.4 * 600, cursor.sum(measures), 0.0001);
28+
final double[] view = cursor.viewOf(measures);
29+
assertEquals(4, view.length);
30+
assertEquals(300 * 0.75, view[0], 0.0001);
31+
assertEquals(400, view[1], 0.0001);
32+
assertEquals(500, view[2], 0.0001);
33+
assertEquals(600 * 0.4, view[3], 0.0001);
34+
}
35+
36+
@Test
37+
void cursorOverOneMeasure() {
38+
final var timestamps = new long[] { 100, 200, 300, 400, 500, 600, 700, 800, 900 };
39+
40+
final var cursor = Cursor.cursorOver(timestamps, 1, Duration.ofMillis(10), 0,
41+
100);
42+
43+
assertEquals(0, cursor.startIndex());
44+
assertEquals(0, cursor.endIndex());
45+
assertEquals(0.1, cursor.firstMeasureRatio(), 0.0001);
46+
assertEquals(-1, cursor.lastMeasureRatio(), 0.0001);
47+
48+
final var measures = new double[] { 100, 200, 300, 400, 500, 600, 700, 800, 900 };
49+
assertEquals(100 * 0.1, cursor.sum(measures), 0.0001);
50+
final double[] view = cursor.viewOf(measures);
51+
assertEquals(1, view.length);
52+
assertEquals(100 * 0.1, view[0], 0.0001);
53+
}
54+
}

0 commit comments

Comments
 (0)