Skip to content

Commit

Permalink
feat: intel support for powermetrics (#12)
Browse files Browse the repository at this point in the history
* refactor: rename constants

* doc: clean-up, add JoularJX as inspiration

* refactor: introduce CPU class to handle different powermetrics output

For some reason, this refactor breaks the process handling! 🤔

* feat: add Intel support for powermetrics-based measures
  • Loading branch information
metacosm committed Jan 17, 2024
1 parent 8bd81e6 commit 6d74e21
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 144 deletions.
57 changes: 4 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,9 @@
# power-server

This project is meant to provide a REST endpoint streaming power consumption
This project is meant to provide a REST endpoint streaming power consumption, inspired
by [JoularJX](https://github.com/joular/joularjx) but focusing on exposing power information and metadata over REST
without further processing.

This project uses Quarkus, the Supersonic Subatomic Java Framework.

If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .

## Running the application in dev mode

You can run your application in dev mode that enables live coding using:
```shell script
./mvnw compile quarkus:dev
```

> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/.
## Packaging and running the application

The application can be packaged using:
```shell script
./mvnw package
```
It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory.
Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/quarkus-app/lib/` directory.

The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`.

If you want to build an _über-jar_, execute the following command:
```shell script
./mvnw package -Dquarkus.package.type=uber-jar
```

The application, packaged as an _über-jar_, is now runnable using `java -jar target/*-runner.jar`.

## Creating a native executable

You can create a native executable using:
```shell script
./mvnw package -Dnative
```

Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
```shell script
./mvnw package -Dnative -Dquarkus.native.container-build=true
```

You can then execute your native executable with: `./target/power-server-1.0.0-SNAPSHOT-runner`

If you want to learn more about building native executables, please consult https://quarkus.io/guides/maven-tooling.

## Provided Code

### RESTEasy Reactive

Easily start your Reactive RESTful Web Services

[Related guide section...](https://quarkus.io/guides/getting-started-reactive#reactive-jax-rs-resources)
If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public SensorMetadata(@JsonProperty("metadata") Map<String, ComponentMetadata> c
@JsonProperty("documentation")
private final String documentation;

public boolean exists(String component) {
return components.containsKey(component);
}

public ComponentMetadata metadataFor(String component) {
final var componentMetadata = components.get(component);
if (componentMetadata == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.github.metacosm.power.sensors.macos.powermetrics;

import io.github.metacosm.power.SensorMetadata;

import java.util.HashMap;
import java.util.Map;

import static io.github.metacosm.power.sensors.macos.powermetrics.MacOSPowermetricsSensor.*;

class AppleSiliconCPU extends io.github.metacosm.power.sensors.macos.powermetrics.CPU {
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, "GPU power", true, "mW");
private static final SensorMetadata.ComponentMetadata aneComponent = new SensorMetadata.ComponentMetadata(ANE, 2, "Apple Neural Engine power", false, "mW");
private static final SensorMetadata.ComponentMetadata cpuShareComponent = new SensorMetadata.ComponentMetadata(CPU_SHARE, 3, "Computed share of CPU", false, "decimal percentage");
private static final String COMBINED = "Combined";
private static final String POWER_INDICATOR = " Power: ";
private static final int POWER_INDICATOR_LENGTH = POWER_INDICATOR.length();

public AppleSiliconCPU() {

}

@Override
public void addComponentIfFound(String line, Map<String, SensorMetadata.ComponentMetadata> components) {
// looking for line fitting the: "<name> Power: xxx mW" pattern, where "name" will be a considered metadata component
final var powerIndex = line.indexOf(" Power");
// lines with `-` as the second char are disregarded as of the form: "E-Cluster Power: 6 mW" which fits the metadata pattern but shouldn't be considered
if (powerIndex >= 0 && '-' != line.charAt(1)) {
addComponentTo(line.substring(0, powerIndex), components);
}
}

private static void addComponentTo(String name, Map<String, SensorMetadata.ComponentMetadata> components) {
switch (name) {
case CPU, GPU, ANE:
// already pre-added
break;
case COMBINED:
// should be ignored
break;
default:
components.put(name, new SensorMetadata.ComponentMetadata(name, components.size(), name, false, "mW"));
}
}

@Override
public boolean doneExtractingPowerComponents(String line, HashMap<String, Number> powerComponents) {
// looking for line fitting the: "<name> Power: xxx mW" pattern and add all of the associated values together
final var powerIndex = line.indexOf(POWER_INDICATOR);
// lines with `-` as the second char are disregarded as of the form: "E-Cluster Power: 6 mW" which fits the pattern but shouldn't be considered
// also ignore Combined Power if available since it is the sum of the other components
if (powerIndex >= 0 && '-' != line.charAt(1) && !line.startsWith("Combined")) {
// get component name
final var name = line.substring(0, powerIndex);
// extract power value
final int value;
try {
value = Integer.parseInt(line.substring(powerIndex + POWER_INDICATOR_LENGTH, line.indexOf('m') - 1));
} catch (Exception e) {
throw new IllegalStateException("Cannot parse power value from line '" + line + "'", e);
}
powerComponents.put(name, value);
}

// we break out once we 've found all the extracted components (in this case, only cpuShare is not extracted)
return powerComponents.size() == metadata().componentCardinality() - 1;
}

@Override
boolean doneAfterComponentsInitialization(Map<String, SensorMetadata.ComponentMetadata> components) {
// init map with known components
components.put(MacOSPowermetricsSensor.CPU, cpuComponent);
components.put(GPU, gpuComponent);
components.put(ANE, aneComponent);
components.put(CPU_SHARE, cpuShareComponent);
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.github.metacosm.power.sensors.macos.powermetrics;

import io.github.metacosm.power.SensorMetadata;

import java.util.HashMap;
import java.util.Map;

abstract class CPU {
private SensorMetadata metadata;

void addComponentIfFound(String line, Map<String, SensorMetadata.ComponentMetadata> components) {
throw new IllegalStateException("Shouldn't be called as this processing is unneeded for this implementation");
}

abstract boolean doneExtractingPowerComponents(String line, HashMap<String, Number> powerComponents);

SensorMetadata metadata() {
return metadata;
}

void setMetadata(SensorMetadata metadata) {
this.metadata = metadata;
}

abstract boolean doneAfterComponentsInitialization(Map<String, SensorMetadata.ComponentMetadata> components);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.github.metacosm.power.sensors.macos.powermetrics;

import io.github.metacosm.power.SensorMetadata;

import java.util.HashMap;
import java.util.Map;

import static io.github.metacosm.power.sensors.macos.powermetrics.MacOSPowermetricsSensor.CPU_SHARE;
import static io.github.metacosm.power.sensors.macos.powermetrics.MacOSPowermetricsSensor.PACKAGE;

class IntelCPU extends CPU {

private static final SensorMetadata.ComponentMetadata packageComponent = new SensorMetadata.ComponentMetadata(PACKAGE, 0, "Intel energy model derived package power (CPUs+GT+SA)", true, "W");
private static final SensorMetadata.ComponentMetadata cpuShareComponent = new SensorMetadata.ComponentMetadata(CPU_SHARE, 1, "Computed share of CPU", false, "decimal percentage");

@Override
public boolean doneExtractingPowerComponents(String line, HashMap<String, Number> powerComponents) {
// line should look like: Intel energy model derived package power (CPUs+GT+SA): 8.53W
final var powerIndex = line.indexOf("Intel ");
if(powerIndex >= 0) {
final var powerStartIndex = line.indexOf(':') + 1;
final float value;
try {
value = Float.parseFloat(line.substring(powerStartIndex, line.indexOf('W')));
} catch (Exception e) {
throw new IllegalStateException("Cannot parse power value from line '" + line + "'", e);
}
powerComponents.put(PACKAGE, value);
return true;
}

return false;
}

@Override
boolean doneAfterComponentsInitialization(Map<String, SensorMetadata.ComponentMetadata> components) {
components.put(PACKAGE, packageComponent);
components.put(CPU_SHARE, cpuShareComponent);
return true;
}
}
Loading

0 comments on commit 6d74e21

Please sign in to comment.