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
Expand Up @@ -54,6 +54,8 @@ public Cancellable startTrackingApp(String appName, long pid, String session) th
}

RegisteredPID track(long pid) throws Exception {
final var registeredPID = sensor.register(pid);

if (!sensor.isStarted()) {
sensor.start(samplingPeriod.toMillis());
periodicSensorCheck = Multi.createFrom().ticks()
Expand All @@ -65,9 +67,7 @@ RegisteredPID track(long pid) throws Exception {
.toAtLeast(1)
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool());
}
final var registeredPID = sensor.register(pid);
// todo: the timing of things could make it so that the pid has been removed before the map operation occurs so
// currently return -1 instead of null but this needs to be properly addressed

periodicSensorCheck = periodicSensorCheck.onCancellation().invoke(() -> sensor.unregister(registeredPID));
return registeredPID;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AppleSiliconCPU extends CPU {
private static final SensorMetadata.ComponentMetadata gpuComponent = new SensorMetadata.ComponentMetadata(GPU, 1,
"GPU power", true, mW);
private static final SensorMetadata.ComponentMetadata aneComponent = new SensorMetadata.ComponentMetadata(ANE, 2,
"Apple Neural Engine power", false, mW);
"Apple Neural Engine power", true, mW);
private static final SensorMetadata.ComponentMetadata cpuShareComponent = new SensorMetadata.ComponentMetadata(CPU_SHARE, 3,
"Computed share of CPU", false, decimalPercentage);
private static final String COMBINED = "Combined";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import net.laprun.sustainability.power.SensorMetadata;
import net.laprun.sustainability.power.SensorUnit;

/**
* Totaler provides a way to compute the aggregated total for a subset of sensor measures, ensuring that the proper conversions
* between commensurate units are used.
*/
public class Totaler {
private final SensorUnit expectedResultUnit;
private final Function<double[], Double> formula;
Expand All @@ -17,6 +21,19 @@ public class Totaler {
private final boolean isAttributed;
private Errors errors;

/**
* Create a new Totaler instance working with the specified {@link SensorMetadata} and computing a total measure using the
* provided expected result unit, adding (and converting to the target unit, if needed) values for the provided component
* indices. If no indices are provided, components will be automatically chosen as follows: only components using units
* compatible with the target unit will be used, and, among these, as mixing attributed and non-attributed values will
* result in useless results, priority will be given to attributed components. If no attributed component is found, then
* unattributed components will be considered.
*
* @param metadata the sensor metadata providing the component information to use as basis to compute an aggregate value
* @param expectedResultUnit a {@link SensorUnit} representing the unit with which the aggregate should be calculated
* @param totalComponentIndices optional list of component indices to take into account, if no such indices are provided,
* the Totaler will select "appropriate" indices automatically, if possible
*/
public Totaler(SensorMetadata metadata, SensorUnit expectedResultUnit, int... totalComponentIndices) {
this.expectedResultUnit = Objects.requireNonNull(expectedResultUnit, "Must specify expected result unit");

Expand All @@ -26,12 +43,21 @@ public Totaler(SensorMetadata metadata, SensorUnit expectedResultUnit, int... to
final var attributed = new Boolean[1];
if (totalComponentIndices == null || totalComponentIndices.length == 0) {
// automatically aggregate components commensurate with the expected result unit
totalComponents = metadata.components().values().stream()
.filter(cm -> cm.unit().isCommensurableWith(expectedResultUnit))
.map(cm -> new TotalComponent(cm.name(), cm.index(), cm.unit().factor(),
checkAggregatedAttribution(cm.isAttributed(), attributed)))
.toArray(TotalComponent[]::new);
// first, only select attributed components
var maybeComponents = createTotalComponents(metadata, expectedResultUnit, true);
attributed[0] = true;
// if there are no commensurate attributed components, look for unattributed ones
if (maybeComponents.length == 0) {
maybeComponents = createTotalComponents(metadata, expectedResultUnit, false);
attributed[0] = false;
}
if (maybeComponents.length == 0) {
addError("No components are compatible with the expected result unit " + expectedResultUnit);
validate(); // exit immediately
}

// record total indices
totalComponents = maybeComponents;
totalComponentIndices = new int[totalComponents.length];
int i = 0;
for (var component : totalComponents) {
Expand Down Expand Up @@ -61,12 +87,23 @@ public Totaler(SensorMetadata metadata, SensorUnit expectedResultUnit, int... to
validate();
}

private static boolean checkAggregatedAttribution(boolean isComponentAttributed, Boolean[] aggregateAttribution) {
private TotalComponent[] createTotalComponents(SensorMetadata metadata, SensorUnit expectedResultUnit,
boolean attributed) {
return metadata.components().values().stream()
.filter(cm -> attributed == cm.isAttributed())
.filter(cm -> cm.unit().isCommensurableWith(expectedResultUnit))
.map(cm -> new TotalComponent(cm.name(), cm.index(), cm.unit().factor(), cm.isAttributed()))
.toArray(TotalComponent[]::new);
}

private boolean checkAggregatedAttribution(boolean isComponentAttributed, Boolean[] aggregateAttribution) {
var currentAttribution = aggregateAttribution[0];
if (currentAttribution == null) {
currentAttribution = isComponentAttributed;
} else {
currentAttribution = currentAttribution && isComponentAttributed;
if (currentAttribution != isComponentAttributed) {
addError(Errors.ATTRIBUTION_MIX_ERROR);
}
}
aggregateAttribution[0] = currentAttribution;
return isComponentAttributed;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package net.laprun.sustainability.power.analysis.total;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.*;

import java.util.stream.Stream;

import org.junit.jupiter.api.Test;

import net.laprun.sustainability.power.Errors;
import net.laprun.sustainability.power.SensorMetadata;
import net.laprun.sustainability.power.SensorUnit;

Expand Down Expand Up @@ -45,23 +47,25 @@ void attributedShouldWork() {
assertTrue(totaler.isAttributed());
totaler = new Totaler(metadata, expectedResultUnit, 3);
assertFalse(totaler.isAttributed());
totaler = new Totaler(metadata, expectedResultUnit, 0, 2, 3);
assertFalse(totaler.isAttributed());

assertThatThrownBy(() -> new Totaler(metadata, expectedResultUnit, 0, 2, 3))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(Errors.ATTRIBUTION_MIX_ERROR);
}

@Test
void shouldAutomaticallyPickCommensurateComponentsIfNoIndicesAreProvided() {
final var metadata = SensorMetadata
.withNewComponent("cp1", null, true, "mW")
.withNewComponent("cp2", null, true, "mJ")
.withNewComponent("cp3", null, true, "mW")
.withNewComponent("cp24", null, false, "W")
.withNewComponent("cp3", null, true, "µW")
.withNewComponent("cp4", null, false, "W")
.build();

final var expectedResultUnit = SensorUnit.W;
var totaler = new Totaler(metadata, expectedResultUnit);
assertFalse(totaler.isAttributed());
assertArrayEquals(new int[] { 0, 2, 3 }, totaler.componentIndices());
assertTrue(totaler.isAttributed());
assertArrayEquals(new int[] { 0, 2 }, totaler.componentIndices());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@

public class Errors {
private List<String> errors;
public static final String ATTRIBUTION_MIX_ERROR = "Cannot aggregate attributed and non-attributed components";

public void addError(String error) {
if (errors == null) {
errors = new ArrayList<>();
}
errors.add(error);
if (!errors.contains(error)) {
errors.add(error);
}
}

public boolean hasErrors() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public class Persistence {

@Transactional
public Measure save(SensorMeasure measure, String appName, String session) {
if (SensorMeasure.missing == measure) {
Log.debugf("Ignoring missing measure for app: %s, session: %s", appName, session);
return null;
}
final var persisted = new Measure();
persisted.components = measure.components();
persisted.appName = appName;
Expand All @@ -35,7 +39,6 @@ public Measure save(SensorMeasure measure, String appName) {
public Optional<Double> synthesizeAndAggregateForSession(String appName, String session,
Function<Measure, Double> synthesizer) {
return Measure.forApplicationSession(appName, session)
.filter(measure -> measure.components.length != 1)
.map(synthesizer)
.reduce(Double::sum);
}
Expand Down
Loading