Skip to content

Commit

Permalink
feat: metadata now defines which components are used to compute total
Browse files Browse the repository at this point in the history
  • Loading branch information
metacosm committed Jul 7, 2024
1 parent 50fd4df commit e1bcd60
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ static double sumOfComponents(double[] recorded) {
return componentSum;
}

static double sumOfSelectedComponents(double[] recorded, int... indices) {
if (indices == null || indices.length == 0) {
return sumOfComponents(recorded);
}
var componentSum = 0.0;
for (int index : indices) {
componentSum += recorded[index];
}
return componentSum;
}

default StdDev standardDeviations() {
final var cardinality = metadata().componentCardinality();
final var stdDevs = new double[cardinality];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Map;

import org.junit.jupiter.api.Test;

import net.laprun.sustainability.power.SensorMetadata;
Expand All @@ -14,7 +16,7 @@ void testStatistics() {
final var m1c2 = 12.0;
final var m2c1 = 8.0;
final var m2c2 = 17.0;
final var metadata = new SensorMetadata(null, null) {
final var metadata = new SensorMetadata(Map.of(), null, new int[0]) {

@Override
public int componentCardinality() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,66 @@
package net.laprun.sustainability.power;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* The metadata associated with a power-consumption recording sensor. This allows to make sense of the data sent by the power
* server by providing information about each component (e.g. CPU) recorded by the sensor during each periodical measure.
*/
public class SensorMetadata {
/**
* The information associated with a recorded component
*
* @param name the name of the component (e.g. CPU)
* @param index the index at which the measure for this component is recorded in the {@link SensorMeasure#components()}
* array
* @param description a short textual description of what this component is about when available (for automatically
* extracted components, this might be identical to the name)
* @param isAttributed whether or not this component provides an attributed value i.e. whether the value is already computed
* for the process during a measure or, to the contrary, if the measure is done globally and the computation of the
* attributed share for each process needs to be performed. This is needed because some sensors only provide
* system-wide measures instead of on a per-process basis.
* @param unit a textual representation of the unit used for measures associated with this component (e.g. mW)
*/
public record ComponentMetadata(String name, int index, String description, boolean isAttributed, SensorUnit unit) {
}
@JsonProperty("metadata")
private final Map<String, ComponentMetadata> components;
@JsonProperty("documentation")
private final String documentation;
@JsonProperty("totalComponents")
private final int[] totalComponents;

/**
* Initializes sensor metadata information
*
* @param components a map describing the metadata for each component
* @param documentation a text providing any relevant information associated with the described sensor
* @param totalComponents an array of indices indicating which components can be used to compute a total power consumption
* metric for that sensor. Must use a unit commensurable with {@link SensorUnit#W}
* @throws IllegalArgumentException if indices specified in {@code totalComponents} do not represent power measures
* expressible in Watts or are not a valid index
*/
@JsonCreator
public SensorMetadata(@JsonProperty("metadata") Map<String, ComponentMetadata> components,
@JsonProperty("documentation") String documentation) {
this.components = components;
@JsonProperty("documentation") String documentation,
@JsonProperty("totalComponents") int[] totalComponents) {
this.components = Objects.requireNonNull(components, "Must provide components");
this.documentation = documentation;
this.totalComponents = Objects.requireNonNull(totalComponents, "Must provide total components");
final var errors = new Errors();
for (int index : totalComponents) {
if (index < 0) {
errors.addError(index + " is not a valid index");
continue;
}
components.values().stream()
.filter(cm -> index == cm.index)
.findFirst()
.ifPresentOrElse(component -> {
if (!component.isWattCommensurable()) {
errors.addError("Component " + component.name
+ " is not commensurate with a power measure. It needs to be expressible in Watts.");
}
}, () -> errors.addError(index + " is not a valid index"));
}
if (errors.hasErrors()) {
throw new IllegalArgumentException(errors.formatErrors());
}
}

@JsonProperty("metadata")
private final Map<String, ComponentMetadata> components;

@JsonProperty("documentation")
private final String documentation;

/**
* Determines whether a component with the specified name is known for this sensor
*
Expand Down Expand Up @@ -99,4 +113,61 @@ public Map<String, ComponentMetadata> components() {
public String documentation() {
return documentation;
}

/**
* Retrieves the indices of the components that can be used to compute a total
*
* @return the indices of the components that can be used to compute a total
*/
public int[] totalComponents() {
return totalComponents;
}

private static class Errors {
private List<String> errors;

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

boolean hasErrors() {
return errors != null && !errors.isEmpty();
}

String formatErrors() {
if (errors == null) {
return "";
}
return errors.stream().collect(Collectors.joining("\n- ", "\n- ", ""));
}
}

/**
* The information associated with a recorded component
*
* @param name the name of the component (e.g. CPU)
* @param index the index at which the measure for this component is recorded in the {@link SensorMeasure#components()}
* array
* @param description a short textual description of what this component is about when available (for automatically
* extracted components, this might be identical to the name)
* @param isAttributed whether or not this component provides an attributed value i.e. whether the value is already computed
* for the process during a measure or, on the contrary, if the measure is done globally and the computation of the
* attributed share for each process needs to be performed. This is needed because some sensors only provide
* system-wide measures instead of on a per-process basis.
* @param unit a textual representation of the unit used for measures associated with this component (e.g. mW)
*/
public record ComponentMetadata(String name, int index, String description, boolean isAttributed, String unit) {
/**
* Determines whether or not this component is measuring power (i.e. its value can be converted to Watts)
*
* @return {@code true} if this component's unit is commensurable to Watts, {@code false} otherwise
*/
@JsonIgnore
public boolean isWattCommensurable() {
return SensorUnit.of(unit).isWattCommensurable();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,42 @@

import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;

import eu.hoefel.unit.Unit;

public class SensorUnit {

private final Unit unit;
private final String symbol;

@JsonCreator
public SensorUnit(String symbol) {
private SensorUnit(String symbol) {
this.symbol = symbol;
this.unit = Unit.of(symbol);
}

public static SensorUnit of(String unit) {
return switch (unit) {
case mW -> mWUnit;
case W -> WUnit;
case µJ -> µJUnit;
case decimalPercentage -> decimalPercentageUnit;
default -> new SensorUnit(unit);
};
}

@SuppressWarnings("unused")
public String getSymbol() {
return symbol;
}

@JsonIgnore
public Unit getUnit() {
return unit;
}

@Override
public String toString() {
return symbol;
}

@Override
public boolean equals(Object o) {
if (this == o)
Expand All @@ -42,8 +53,17 @@ public int hashCode() {
return Objects.hashCode(symbol);
}

public static final SensorUnit mW = new SensorUnit("mW");
public static final SensorUnit W = new SensorUnit("W");
public static final SensorUnit µJ = new SensorUnit("µJ");
public static final SensorUnit decimalPercentage = new SensorUnit("decimal percentage");
public static final String mW = "mW";
public static final String W = "W";
public static final String µJ = "µJ";
public static final String decimalPercentage = "decimal percentage";

private static final SensorUnit mWUnit = new SensorUnit(mW);
private static final SensorUnit WUnit = new SensorUnit(W);
private static final SensorUnit µJUnit = new SensorUnit(µJ);
private static final SensorUnit decimalPercentageUnit = new SensorUnit(decimalPercentage);

public boolean isWattCommensurable() {
return equals(SensorUnit.WUnit) || unit.compatibleUnits().contains(SensorUnit.WUnit.unit);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.laprun.sustainability.power;

import static org.junit.jupiter.api.Assertions.*;

import java.util.Collections;
import java.util.Map;

import org.junit.jupiter.api.Test;

class SensorMetadataTest {
@Test
void shouldFailIfTotalComponentsAreOutOfRange() {
final var e = assertThrows(IllegalArgumentException.class,
() -> new SensorMetadata(Collections.emptyMap(), "", new int[] { 0, 1, -1 }));
final var message = e.getMessage();
assertTrue(message.contains("0"));
assertTrue(message.contains("1"));
assertTrue(message.contains("-1"));
}

@Test
void shouldFailIfTotalComponentsAreNotCommensurateToWatts() {
final var e = assertThrows(IllegalArgumentException.class,
() -> new SensorMetadata(
Map.of("foo", new SensorMetadata.ComponentMetadata("foo", 0, "", false, SensorUnit.decimalPercentage)),
"", new int[] { 0, 1, -1 }));
final var message = e.getMessage();
assertTrue(message.contains("1"));
assertTrue(message.contains("-1"));
assertTrue(message.contains("foo"));
}

@Test
void shouldFailIfNoTotalComponentsAreProvided() {
final var e = assertThrows(NullPointerException.class,
() -> new SensorMetadata(Collections.emptyMap(), "", null));
final var message = e.getMessage();
assertTrue(message.contains("Must provide total components"));
}

@Test
void shouldFailIfNoComponentsAreProvided() {
final var e = assertThrows(NullPointerException.class,
() -> new SensorMetadata(null, "", null));
final var message = e.getMessage();
assertTrue(message.contains("Must provide components"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,18 @@ private IntelRAPLSensor(SortedMap<String, RAPLFile> files) {
final var rawOffset = files.size();
final var metadata = new HashMap<String, SensorMetadata.ComponentMetadata>(rawOffset * 2);
int fileNb = 0;
final int[] totalComponents = new int[rawOffset];
for (String name : files.keySet()) {
metadata.put(name, new SensorMetadata.ComponentMetadata(name, fileNb, name, false, mW));
totalComponents[fileNb] = fileNb;
final var rawName = name + "_uj";
metadata.put(rawName, new SensorMetadata.ComponentMetadata(rawName, fileNb + rawOffset,
name + " (raw micro Joule data)", false, µJ));
fileNb++;
}
this.metadata = new SensorMetadata(metadata,
"Linux RAPL derived information, see https://www.kernel.org/doc/html/latest/power/powercap/powercap.html");
"Linux RAPL derived information, see https://www.kernel.org/doc/html/latest/power/powercap/powercap.html",
totalComponents);
lastMeasuredSensorValues = new double[raplFiles.length];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import static net.laprun.sustainability.power.SensorUnit.*;
import static net.laprun.sustainability.power.sensors.macos.powermetrics.MacOSPowermetricsSensor.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.laprun.sustainability.power.SensorMetadata;

class AppleSiliconCPU extends CPU {
private static final List<Integer> defaultTotalComponents = List.of(0, 1, 2);
private static final SensorMetadata.ComponentMetadata cpuComponent = new SensorMetadata.ComponentMetadata(CPU, 0,
"CPU power", true, mW);
private static final SensorMetadata.ComponentMetadata gpuComponent = new SensorMetadata.ComponentMetadata(GPU, 1,
Expand All @@ -20,9 +23,14 @@ class AppleSiliconCPU extends CPU {
private static final String COMBINED = "Combined";
private static final String POWER_INDICATOR = " Power: ";
private static final int POWER_INDICATOR_LENGTH = POWER_INDICATOR.length();
private final List<Integer> totalComponents = new ArrayList<>();

public AppleSiliconCPU() {
}

@Override
int[] getTotalComponents() {
return totalComponents.stream().mapToInt(i -> i).toArray();
}

@Override
Expand All @@ -35,7 +43,7 @@ public void addComponentIfFound(String line, Map<String, SensorMetadata.Componen
}
}

private static void addComponentTo(String name, Map<String, SensorMetadata.ComponentMetadata> components) {
private void addComponentTo(String name, Map<String, SensorMetadata.ComponentMetadata> components) {
switch (name) {
case CPU, GPU, ANE:
// already pre-added
Expand All @@ -44,7 +52,9 @@ private static void addComponentTo(String name, Map<String, SensorMetadata.Compo
// should be ignored
break;
default:
components.put(name, new SensorMetadata.ComponentMetadata(name, components.size(), name, false, mW));
final var index = components.size();
components.put(name, new SensorMetadata.ComponentMetadata(name, index, name, false, mW));
totalComponents.add(index);
}
}

Expand Down Expand Up @@ -78,6 +88,7 @@ boolean doneAfterComponentsInitialization(Map<String, SensorMetadata.ComponentMe
components.put(GPU, gpuComponent);
components.put(ANE, aneComponent);
components.put(CPU_SHARE, cpuShareComponent);
totalComponents.addAll(defaultTotalComponents);
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ void setMetadata(SensorMetadata metadata) {
}

abstract boolean doneAfterComponentsInitialization(Map<String, SensorMetadata.ComponentMetadata> components);

abstract int[] getTotalComponents();
}
Loading

0 comments on commit e1bcd60

Please sign in to comment.