Skip to content

Commit

Permalink
feat: provide sampling period information
Browse files Browse the repository at this point in the history
  • Loading branch information
metacosm committed Jan 27, 2025
1 parent 0a07404 commit 6a10d62
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 18 deletions.
42 changes: 27 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,33 @@ without further processing.
This project uses [Quarkus](https://quarkus.io), the Supersonic Subatomic Java Framework and comprises 3 modules:

- `analysis`, which contains utilities helpful to analyze power measures (computations, statistics, histograms, etc…)
- `if-manifest-export`, which provides a means to export a stopped measure as a [Green Software Foundation](https://greensoftware.foundation/) [Impact Framework](https://if.greensoftware.foundation/) manifest
- `if-manifest-export`, which provides a means to export a stopped measure as
a [Green Software Foundation](https://greensoftware.foundation/) [Impact Framework](https://if.greensoftware.foundation/)
manifest
- `measure`, which provides classes to help record and process measures in client applications
- `metadata`, which contains the metadata API that the RESTful server uses to provide information about what is returned
by the power sensors. This artifact contains classes that can be reused in client projects.
- `server` contains the RESTful server, listening by default on port `20432` (as specified
in `[application.properties](https://github.com/metacosm/power-server/blob/87bba3196fa0e552665b4f1d22006377779b0959/server/src/main/resources/application.properties#L1)`)

The server provides two endpoints: `/power/metadata` and `/power/{pid}` where `pid` is a String representation of a
process identifier, identifying a process running on the machine where `power-server` is running.
The metadata endpoint provides information about how measures streamed from the main endpoint is formatted as well as
information about power components. The main endpoint streams `SensorMeasure` objects as defined in the `metadata`
module as an array of double measures. Typically, main sensor components are measured in milli Watts for easier
consumption but clients should check the information provided by the metadata endpoint to learn the layout of the
measures array and which meaning they carry. For example, the macOS implementation provides a decimal percentage measure
of the CPU share of the measured process as part of the returned measure. This information doesn't exist on the Linux
information where the measures are actually energy counters for the whole system, the client being then in charge of
computing the process attribution.
in
`[application.properties](https://github.com/metacosm/power-server/blob/87bba3196fa0e552665b4f1d22006377779b0959/server/src/main/resources/application.properties#L1)`)

The server provides the following endpoints:

- `/power/{pid}` where `pid` is a String representation of a process identifier, identifying a process running on the
machine where `power-server` is running.
- `/power/metadata` provides information about how measures streamed from the main endpoint is formatted as well as
information about power components.
- `/power/sampling` provides the currently configured power measure sampling period, which can be configured using the
`net.laprun.sustainability.power.sampling-period` property, passing it a `String` that can be converted to a Java
`Duration`.

The main endpoint streams `SensorMeasure` objects as defined in the `metadata`module as an array of double measures.
Typically, main sensor components are measured in milli Watts for easier consumption but clients should check the
information provided by the metadata endpoint to learn the layout of the measures array and which meaning they carry.
For example, the macOS implementation provides a decimal percentage
measure of the CPU share of the measured process as part of the returned measure. This information doesn't exist on the
Linux implementation where the measures are actually energy counters for the whole system, the client being then in
charge of computing the process attribution.

Only Linux/amd64 and macOS (amd64/apple silicon) are supported at the moment. Of note, this tool needs to be run
via `sudo` because power consumption information is considered as security sensitive (as it can
Expand All @@ -33,7 +43,8 @@ information.
### macOS

Power monitoring is performed using the
bundled `[powermetrics](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html#//apple_ref/doc/uid/TP40013929-CH35-SW10)`
bundled
`[powermetrics](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html#//apple_ref/doc/uid/TP40013929-CH35-SW10)`
tool, which is run with specific parameters and which
output is then parsed into a usable representation.

Expand Down Expand Up @@ -69,5 +80,6 @@ Once you've performed a full build, you can also compile the server to a native
Once build, you can run the server as follows:

- JVM mode: `sudo java -jar server/target/quarkus-app/quarkus-run.jar` from the project root
- Dev mode: `cd server; sudo mvn quarkus:dev` or `cd server; sudo quarkus dev` (will only work if `root` has Java properly configured)
- Dev mode: `cd server; sudo mvn quarkus:dev` or `cd server; sudo quarkus dev` (will only work if `root` has Java
properly configured)
- Native mode: `sudo ./server/target/*-runner`
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import org.eclipse.microprofile.config.inject.ConfigProperty;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.infrastructure.Infrastructure;
import net.laprun.sustainability.power.sensors.Measures;
Expand All @@ -13,20 +15,23 @@
@ApplicationScoped
public class PowerMeasurer {

public static final int SAMPLING_FREQUENCY_IN_MILLIS = 500;
public static final String DEFAULT_SAMPLING_PERIOD = "PT0.5S";
@Inject
PowerSensor sensor;

@ConfigProperty(name = "net.laprun.sustainability.power.sampling-period", defaultValue = DEFAULT_SAMPLING_PERIOD)
Duration samplingPeriod;

private Multi<Measures> periodicSensorCheck;

public Multi<SensorMeasure> startTracking(String pid) throws Exception {
// first make sure that the process with that pid exists
final var parsedPID = validPIDOrFail(pid);

if (!sensor.isStarted()) {
sensor.start(SAMPLING_FREQUENCY_IN_MILLIS);
sensor.start(samplingPeriod.toMillis());
periodicSensorCheck = Multi.createFrom().ticks()
.every(Duration.ofMillis(SAMPLING_FREQUENCY_IN_MILLIS))
.every(samplingPeriod)
.map(sensor::update)
.broadcast()
.withCancellationAfterLastSubscriberDeparture()
Expand All @@ -50,4 +55,8 @@ protected long validPIDOrFail(String pid) {
public SensorMetadata metadata() {
return sensor.metadata();
}

public Duration getSamplingPeriod() {
return samplingPeriod;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package net.laprun.sustainability.power;

import java.time.Duration;

import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
Expand Down Expand Up @@ -29,4 +31,10 @@ public Multi<SensorMeasure> powerFor(@PathParam("pid") String pid) throws Except
public SensorMetadata metadata() {
return measurer.metadata();
}

@GET
@Path("sampling")
public Duration samplingPeriod() {
return measurer.getSamplingPeriod();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.*;

import java.time.Duration;
import java.util.Set;

import org.junit.jupiter.api.Test;
Expand All @@ -24,6 +25,16 @@ public void testPowerEndpoint() {
.statusCode(200);
}

@Test
public void samplingPeriod() {
final Duration duration = given()
.when().get("/power/sampling")
.then()
.statusCode(200)
.extract().body().as(Duration.class);
assertEquals(Duration.parse(PowerMeasurer.DEFAULT_SAMPLING_PERIOD), duration);
}

protected long getPid() {
return ProcessHandle.current().pid();
}
Expand Down

0 comments on commit 6a10d62

Please sign in to comment.