Skip to content

Commit 01ed1e7

Browse files
committed
feat: SensorMeasure is now an interface with multiple implementations
1 parent d2a6e45 commit 01ed1e7

File tree

8 files changed

+127
-28
lines changed

8 files changed

+127
-28
lines changed

backend/src/main/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.quarkus.logging.Log;
1111
import net.laprun.sustainability.power.SensorMeasure;
1212
import net.laprun.sustainability.power.SensorMetadata;
13+
import net.laprun.sustainability.power.measures.NoDurationSensorMeasure;
1314
import net.laprun.sustainability.power.sensors.AbstractPowerSensor;
1415
import net.laprun.sustainability.power.sensors.Measures;
1516
import net.laprun.sustainability.power.sensors.RegisteredPID;
@@ -148,7 +149,7 @@ protected Measures doUpdate(long lastUpdateEpoch, long newUpdateStartEpoch, Map<
148149
newUpdateStartEpoch);
149150

150151
final var needMultipleMeasures = wantsCPUShareSamplingEnabled() && externalCPUShareComponentIndex() > 0;
151-
final var single = new SensorMeasure(measure, lastUpdateEpoch, newUpdateStartEpoch);
152+
final var single = new NoDurationSensorMeasure(measure, lastUpdateEpoch, newUpdateStartEpoch);
152153
measures.trackedPIDs().forEach(pid -> {
153154
final SensorMeasure m;
154155
if (needMultipleMeasures) {
@@ -163,7 +164,7 @@ protected Measures doUpdate(long lastUpdateEpoch, long newUpdateStartEpoch, Map<
163164
final var copy = new double[measure.length];
164165
System.arraycopy(measure, 0, copy, 0, measure.length);
165166
copy[externalCPUShareComponentIndex()] = cpuShare;
166-
m = new SensorMeasure(copy, lastUpdateEpoch, newUpdateStartEpoch);
167+
m = new NoDurationSensorMeasure(copy, lastUpdateEpoch, newUpdateStartEpoch);
167168
} else {
168169
m = single;
169170
}

backend/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
import java.util.Map;
1212

1313
import io.quarkus.logging.Log;
14-
import net.laprun.sustainability.power.SensorMeasure;
1514
import net.laprun.sustainability.power.SensorMetadata;
15+
import net.laprun.sustainability.power.measures.NoDurationSensorMeasure;
16+
import net.laprun.sustainability.power.measures.PartialSensorMeasure;
1617
import net.laprun.sustainability.power.sensors.AbstractPowerSensor;
1718
import net.laprun.sustainability.power.sensors.Measures;
1819
import net.laprun.sustainability.power.sensors.PowerSensor;
@@ -108,7 +109,9 @@ protected SensorMetadata nativeMetadata() {
108109
@Override
109110
protected void doStart() {
110111
// nothing to do here by default
111-
lastCalled = System.currentTimeMillis();
112+
if (Log.isDebugEnabled()) {
113+
lastCalled = System.currentTimeMillis();
114+
}
112115
}
113116

114117
private static class Section {
@@ -117,10 +120,12 @@ private static class Section {
117120

118121
Measures extractPowerMeasure(InputStream powerMeasureInput, long lastUpdateEpoch, long newUpdateEpoch,
119122
Map<String, Double> cpuShares) {
120-
final var startMs = System.currentTimeMillis();
121-
Log.debugf("powermetrics measure extraction last called %dms ago", (startMs - lastCalled));
122-
lastCalled = startMs;
123-
final long start = lastUpdateEpoch;
123+
if (Log.isDebugEnabled()) {
124+
final var start = System.currentTimeMillis();
125+
Log.debugf("powermetrics measure extraction last called %dms ago", (start - lastCalled));
126+
lastCalled = start;
127+
}
128+
final long startMs = lastUpdateEpoch;
124129
try {
125130
// Should not be closed since it closes the process
126131
BufferedReader input = new BufferedReader(new InputStreamReader(powerMeasureInput));
@@ -136,7 +141,7 @@ Measures extractPowerMeasure(InputStream powerMeasureInput, long lastUpdateEpoch
136141
final var pidMeasures = new HashMap<RegisteredPID, ProcessRecord>(measures.numberOfTrackedPIDs());
137142
final var metadata = metadata();
138143
final var powerComponents = new HashMap<String, Number>(metadata.componentCardinality());
139-
var endUpdateEpoch = -1L;
144+
var duration = -1L;
140145
Section processes = null;
141146
Section cpuSection = null;
142147
while ((line = input.readLine()) != null) {
@@ -146,12 +151,11 @@ Measures extractPowerMeasure(InputStream powerMeasureInput, long lastUpdateEpoch
146151

147152
if (line.charAt(0) == '*') {
148153
// check if we have the sample duration
149-
if (endUpdateEpoch == -1 && line.endsWith(DURATION_SUFFIX)) {
154+
if (duration == -1 && line.endsWith(DURATION_SUFFIX)) {
150155
final var startLookingIndex = line.length() - DURATION_SUFFIX_LENGTH;
151156
final var lastOpenParenIndex = line.lastIndexOf('(', startLookingIndex);
152157
if (lastOpenParenIndex > 0) {
153-
endUpdateEpoch = start
154-
+ Math.round(Float.parseFloat(line.substring(lastOpenParenIndex + 1, startLookingIndex)));
158+
duration = Math.round(Float.parseFloat(line.substring(lastOpenParenIndex + 1, startLookingIndex)));
155159
}
156160
continue;
157161
}
@@ -207,7 +211,8 @@ Measures extractPowerMeasure(InputStream powerMeasureInput, long lastUpdateEpoch
207211

208212
double finalTotalSampledGPU = totalSampledGPU;
209213
double finalTotalSampledCPU = totalSampledCPU;
210-
final var endMs = endUpdateEpoch != -1 ? endUpdateEpoch : newUpdateEpoch;
214+
final var endMs = newUpdateEpoch;
215+
final var durationMs = duration;
211216

212217
// handle external cpu share if enabled
213218
if (cpuShares != null && !cpuShares.isEmpty()) {
@@ -216,20 +221,25 @@ Measures extractPowerMeasure(InputStream powerMeasureInput, long lastUpdateEpoch
216221
}
217222

218223
// handle total system measure separately
219-
measures.record(RegisteredPID.SYSTEM_TOTAL_REGISTERED_PID,
220-
new SensorMeasure(getSystemTotalMeasure(metadata, powerComponents), start, endMs));
224+
final var systemTotalMeasure = getSystemTotalMeasure(metadata, powerComponents);
225+
recordMeasure(RegisteredPID.SYSTEM_TOTAL_REGISTERED_PID, systemTotalMeasure, startMs, endMs, durationMs);
221226

222227
pidMeasures.forEach((pid, record) -> {
223228
final var attributedMeasure = record.asAttributedMeasure(metadata, powerComponents, finalTotalSampledCPU,
224229
finalTotalSampledGPU);
225-
measures.record(pid, new SensorMeasure(attributedMeasure, start, endMs));
230+
recordMeasure(pid, attributedMeasure, startMs, endMs, durationMs);
226231
});
227232
} catch (Exception exception) {
228233
throw new RuntimeException(exception);
229234
}
230235
return measures;
231236
}
232237

238+
private void recordMeasure(RegisteredPID pid, double[] components, long startMs, long endMs, long duration) {
239+
measures.record(pid, duration > 0 ? new PartialSensorMeasure(components, startMs, endMs, duration)
240+
: new NoDurationSensorMeasure(components, startMs, endMs));
241+
}
242+
233243
private static double[] getSystemTotalMeasure(SensorMetadata metadata, Map<String, Number> powerComponents) {
234244
final var measure = new double[metadata.componentCardinality()];
235245
metadata.components().forEach((name, cm) -> {

backend/src/main/java/net/laprun/sustainability/power/sensors/test/TestPowerSensor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import java.util.List;
66
import java.util.Map;
77

8-
import net.laprun.sustainability.power.SensorMeasure;
98
import net.laprun.sustainability.power.SensorMetadata;
9+
import net.laprun.sustainability.power.measures.NoDurationSensorMeasure;
1010
import net.laprun.sustainability.power.sensors.AbstractPowerSensor;
1111
import net.laprun.sustainability.power.sensors.MapMeasures;
1212
import net.laprun.sustainability.power.sensors.Measures;
@@ -41,7 +41,7 @@ public void doStart() {
4141
@Override
4242
protected Measures doUpdate(long lastUpdateEpoch, long newUpdateStartEpoch, Map<String, Double> cpuShares) {
4343
measures.trackedPIDs().forEach(pid -> measures.record(pid,
44-
new SensorMeasure(new double[] { Math.random() }, lastUpdateEpoch, newUpdateStartEpoch)));
44+
new NoDurationSensorMeasure(new double[] { Math.random() }, lastUpdateEpoch, newUpdateStartEpoch)));
4545
return measures;
4646
}
4747
}

backend/src/test/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ void extractPowerMeasureForM4() {
7474
// Process CPU power should be equal to sample ms/s divided for process (here: 116.64) by total samples (1222.65) times total CPU power
7575
final var pidCPUShare = 224.05 / totalCPUTime;
7676
assertEquals(pidCPUShare * totalCPUPower, getComponent(measure, pid0, cpu));
77+
assertEquals(10458, measure.getOrDefault(pid0).durationMs());
78+
assertEquals(10458, measure.getSystemTotal().durationMs());
7779
}
7880

7981
@Test
Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,65 @@
11
package net.laprun.sustainability.power;
22

33
/**
4-
* A power consumption measure as recorded by a sensor, recorded over a given period of time, with an ordering information
5-
* provided by a tick. The meaning of each component measure is provided by the {@link SensorMetadata} information associated
4+
* A power consumption measure as recorded by a sensor, recorded over a given period of time. The meaning of each component
5+
* measure is provided by the {@link SensorMetadata} information associated
66
* with the sensor.
7-
*
8-
* @param components an array recording the power consumption reported by each component of this sensor
9-
* @param startMs the start timestamp in milliseconds for this measure
10-
* @param endMs the end timestamp in milliseconds for this measure
117
*/
12-
public record SensorMeasure(double[] components, long startMs, long endMs) {
8+
public interface SensorMeasure {
9+
10+
/**
11+
* Array recording the power consumption reported by each component of this sensor
12+
*
13+
* @return the values for each power component, as described in the {@link SensorMetadata} associated with the sensor
14+
*/
15+
default double[] components() {
16+
return new double[] { -1.0 };
17+
}
18+
19+
/**
20+
* The start timestamp in milliseconds for this measure
21+
*
22+
* @return the start timestamp in milliseconds for this measure
23+
*/
24+
default long startMs() {
25+
return -1;
26+
}
27+
28+
/**
29+
* The end timestamp in milliseconds for this measure
30+
*
31+
* @return the end timestamp in milliseconds for this measure
32+
*/
33+
default long endMs() {
34+
return -1;
35+
}
36+
37+
/**
38+
* The measure duration in milliseconds. Note that while this could be determined from the start and end
39+
* timestamps of the measure, some sensors don't provide values for the whole duration of the measure. In that case, if
40+
* the sensor provides this information, it is provided using this parameter. If the sensor recorded for the whole
41+
* duration or doesn't provide the recorded measure, this value will be equal to the difference between start and end
42+
* timestamps.
43+
*
44+
* @return the measure duration in milliseconds
45+
*/
46+
default long durationMs() {
47+
return endMs() - startMs();
48+
}
49+
50+
/**
51+
* Whether this measure didn't cover the totatility of the recorded time
52+
*
53+
* @return {@code true} if the components were only recorded for part of the interval defined by the difference between end
54+
* and start times, {@code false} otherwise
55+
*/
56+
default boolean isPartial() {
57+
return false;
58+
}
59+
1360
/**
1461
* Represents an invalid or somehow missed measure.
1562
*/
16-
public static final SensorMeasure missing = new SensorMeasure(new double[] { -1.0 }, -1, -1);
63+
SensorMeasure missing = new SensorMeasure() {
64+
};
1765
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package net.laprun.sustainability.power.measures;
2+
3+
import net.laprun.sustainability.power.SensorMeasure;
4+
5+
/**
6+
* A {@link SensorMeasure} without explicit duration so assumed to last for the whole interval defined by the difference between
7+
* end and start times.
8+
*
9+
* @param components an array recording the power consumption reported by each component of this sensor
10+
* @param startMs the start timestamp in milliseconds for this measure
11+
* @param endMs the end timestamp in milliseconds for this measure
12+
*/
13+
public record NoDurationSensorMeasure(double[] components, long startMs, long endMs) implements SensorMeasure {
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package net.laprun.sustainability.power.measures;
2+
3+
import net.laprun.sustainability.power.SensorMeasure;
4+
5+
/**
6+
* A {@link SensorMeasure} with an explicit duration, so not covering the entirety of the recorded interval. Useful for sensors
7+
* not recording for the whole duration of the measure.
8+
*
9+
* @param components an array recording the power consumption reported by each component of this sensor
10+
* @param startMs the start timestamp in milliseconds for this measure
11+
* @param endMs the end timestamp in milliseconds for this measure
12+
* @param durationMs the duration of the effective measure done by the sensor
13+
*/
14+
public record PartialSensorMeasure(double[] components, long startMs, long endMs,
15+
long durationMs) implements SensorMeasure {
16+
}

persistence/src/main/java/net/laprun/sustainability/power/persistence/Measure.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88

99
import io.quarkus.hibernate.orm.panache.PanacheEntity;
1010
import net.laprun.sustainability.power.SensorMeasure;
11+
import net.laprun.sustainability.power.measures.NoDurationSensorMeasure;
12+
import net.laprun.sustainability.power.measures.PartialSensorMeasure;
1113

1214
@Entity
1315
public class Measure extends PanacheEntity {
1416
public String appName;
1517
public long startTime;
1618
public long endTime;
19+
public long duration;
1720
public double[] components;
1821
public String session;
1922

@@ -30,11 +33,16 @@ public static Stream<Measure> all() {
3033
}
3134

3235
public SensorMeasure asSensorMeasure() {
33-
return new SensorMeasure(components, startTime, endTime);
36+
return isPartial() ? new PartialSensorMeasure(components, startTime, endTime, duration)
37+
: new NoDurationSensorMeasure(components, startTime, endTime);
3438
}
3539

3640
public long duration() {
37-
return endTime - startTime;
41+
return isPartial() ? duration : endTime - startTime;
42+
}
43+
44+
public boolean isPartial() {
45+
return duration > 0;
3846
}
3947

4048
@Override

0 commit comments

Comments
 (0)