diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..dce0df9e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/project.yml b/.github/project.yml new file mode 100644 index 00000000..74cefe42 --- /dev/null +++ b/.github/project.yml @@ -0,0 +1,5 @@ +name: power-server +release: + current-version: 0.0.1 + next-version: 0.0.2-SNAPSHOT + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..3806c4be --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,62 @@ +name: Release + +on: + pull_request: + types: [ closed ] + paths: + - '.github/project.yml' + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + release: + runs-on: ubuntu-latest + name: Release power-server + if: ${{github.event.pull_request.merged == true}} + + steps: + - uses: radcortez/project-metadata-action@main + name: Retrieve project metadata + id: metadata + with: + github-token: ${{secrets.GITHUB_TOKEN}} + metadata-file-path: '.github/project.yml' + + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + cache: 'maven' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + + - name: Configure Git author + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Maven release ${{steps.metadata.outputs.current-version}} + run: | + git checkout -b release + mvn -B release:prepare -Prelease -Darguments="-DperformRelease -Dno-samples -DskipTests" -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}} + git checkout ${{github.base_ref}} + git rebase release + mvn -B release:perform -Darguments="-DperformRelease -Dno-samples -DskipTests" -DperformRelease -Prelease + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + + - name: Push tags + run: git push && git push --tags \ No newline at end of file diff --git a/build-tools/pom.xml b/build-tools/pom.xml index 0b44d213..cd03407f 100644 --- a/build-tools/pom.xml +++ b/build-tools/pom.xml @@ -7,10 +7,11 @@ net.laprun.sustainability power-server-parent - 1.0.0-SNAPSHOT + 0.0.1-SNAPSHOT build-tools - + power-server : code formatting support + Support to automatically format the code for the power-server project \ No newline at end of file diff --git a/metadata/pom.xml b/metadata/pom.xml index 25e1514d..adcc1c04 100644 --- a/metadata/pom.xml +++ b/metadata/pom.xml @@ -7,10 +7,12 @@ net.laprun.sustainability power-server-parent - 1.0.0-SNAPSHOT + 0.0.1-SNAPSHOT power-server-metadata + power-server : metadata model + Metadata model used by the power-server project, extracted to allow reuse in client projects @@ -22,7 +24,7 @@ net.laprun.sustainability build-tools - 1.0.0-SNAPSHOT + 0.0.1-SNAPSHOT diff --git a/metadata/src/main/java/net/laprun/sustainability/power/SensorMeasure.java b/metadata/src/main/java/net/laprun/sustainability/power/SensorMeasure.java index 24fb0b7a..efdc9126 100644 --- a/metadata/src/main/java/net/laprun/sustainability/power/SensorMeasure.java +++ b/metadata/src/main/java/net/laprun/sustainability/power/SensorMeasure.java @@ -1,4 +1,12 @@ package net.laprun.sustainability.power; +/** + * A power consumption measure as recorded by a sensor, recorded over a given period of time, with an ordering information + * provided by a tick. The meaning of each component measure is provided by the {@link SensorMetadata} information associated + * with the sensor. + * + * @param components an array recording the power consumption reported by each component of this sensor + * @param tick the ordinal tick associated with this measure + */ public record SensorMeasure(double[] components, long tick) { } diff --git a/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java b/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java index c7b12d75..175a6441 100644 --- a/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java +++ b/metadata/src/main/java/net/laprun/sustainability/power/SensorMetadata.java @@ -6,10 +6,33 @@ import com.fasterxml.jackson.annotation.JsonCreator; 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, String unit) { } + /** + * 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 + */ @JsonCreator public SensorMetadata(@JsonProperty("metadata") Map components, @JsonProperty("documentation") String documentation) { @@ -23,11 +46,24 @@ public SensorMetadata(@JsonProperty("metadata") Map c @JsonProperty("documentation") private final String documentation; + /** + * Determines whether a component with the specified name is known for this sensor + * + * @param component the name of the component + * @return {@code true} if a component with the specified name exists for the associated sensor, {@code false} otherwise + */ public boolean exists(String component) { return components.containsKey(component); } - public ComponentMetadata metadataFor(String component) { + /** + * Retrieves the {@link ComponentMetadata} associated with the specified component name if it exists + * + * @param component the name of the component which metadata is to be retrieved + * @return the {@link ComponentMetadata} associated with the specified name + * @throws IllegalArgumentException if no component with the specified name is known for the associated sensor + */ + public ComponentMetadata metadataFor(String component) throws IllegalArgumentException { final var componentMetadata = components.get(component); if (componentMetadata == null) { throw new IllegalArgumentException("Unknown component: " + component); @@ -35,14 +71,30 @@ public ComponentMetadata metadataFor(String component) { return componentMetadata; } + /** + * Retrieves the number of known components for the associated sensor + * + * @return the cardinality of known components + */ public int componentCardinality() { return components.size(); } + /** + * Retrieves the known {@link ComponentMetadata} for the associated sensor as an unmodifiable Map keyed by the components' + * name + * + * @return an unmodifiable Map of the known {@link ComponentMetadata} + */ public Map components() { return Collections.unmodifiableMap(components); } + /** + * Retrieves the documentation, if any, associated with this SensorMetadata + * + * @return the documentation relevant for the associated sensor + */ public String documentation() { return documentation; } diff --git a/pom.xml b/pom.xml index e46fe4aa..1f38ef22 100644 --- a/pom.xml +++ b/pom.xml @@ -1,199 +1,318 @@ - - 4.0.0 - net.laprun.sustainability - power-server-parent - 1.0.0-SNAPSHOT - pom + + 4.0.0 + net.laprun.sustainability + power-server-parent + 0.0.1-SNAPSHOT + pom - - 3.11.0 - 21 - UTF-8 - UTF-8 - quarkus-bom - io.quarkus - 3.7.1 - true - 3.1.2 - 2.23.0 - 1.9.0 - + power-server : parent + An application allowing to retrieve power consumption and associated metadata on a per-process basis, + via a RESTful endpoint + + https://github.com/metacosm/power-server - - build-tools - server - metadata - + + + Apache 2 License + https://www.apache.org/licenses/LICENSE-2.0.html + + + + + Christophe Laprun + metacosm@gmail.com + + - + + scm:git:git://github.com/metacosm/power-server.git + scm:git:git@github.com/metacosm/power-server.git + https://github.com/metacosm/power-server/tree/main + + + + + 3.11.0 + 21 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus + 3.7.1 + true + 3.1.2 + 2.23.0 + 1.9.0 + 3.6.3 + 3.3.0 + 3.1.0 + 1.6.13 + 0.3.0 + + + + build-tools + server + metadata + + + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots/ + + true + always + + + + + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + - - ${quarkus.platform.group-id} - ${quarkus.platform.artifact-id} - ${quarkus.platform.version} - pom - import - + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + - - - - io.quarkus - quarkus-arc - - - io.quarkus - quarkus-resteasy-reactive-jackson - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - - - - - - ${quarkus.platform.group-id} - quarkus-maven-plugin - ${quarkus.platform.version} - true - - - - build - generate-code - generate-code-tests - - - - - - maven-compiler-plugin - ${compiler-plugin.version} - - - -parameters - - - - - maven-surefire-plugin - ${surefire-plugin.version} - - - org.jboss.logmanager.LogManager - ${maven.home} - - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - - integration-test - verify - - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - ${maven.home} - - - - - - - net.revelc.code.formatter - formatter-maven-plugin - ${formatter-plugin.version} - - - 17 - eclipse-format.xml - LF - ${format.skip} - - - - net.revelc.code - impsort-maven-plugin - ${impsort-plugin.version} - - - 17 - java.,javax.,jakarta.,org.,com. - * - ${format.skip} - true - - - - - - - format - - - - !no-format - - - + - - net.revelc.code.formatter - formatter-maven-plugin - - - format-sources - process-sources - - format - - - - - - net.revelc.code - impsort-maven-plugin - - true - - - - sort-imports - process-sources - - sort - - - - + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + net.revelc.code.formatter + formatter-maven-plugin + ${formatter-plugin.version} + + + 17 + eclipse-format.xml + LF + ${format.skip} + + + + net.revelc.code + impsort-maven-plugin + ${impsort-plugin.version} + + + 17 + java.,javax.,jakarta.,org.,com. + * + ${format.skip} + true + + - - - - native - - - native - - - - false - native - - - + + + + format + + + + !no-format + + + + + + net.revelc.code.formatter + formatter-maven-plugin + + + format-sources + process-sources + + format + + + + + + net.revelc.code + impsort-maven-plugin + + true + + + + sort-imports + process-sources + + sort + + + + + + + + + release + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + --pinentry-mode + loopback + + + + + + + org.sonatype.central + central-publishing-maven-plugin + ${central-maven-publishing-plugin.version} + true + + central + true + build-tools + + + + + + + native + + + native + + + + false + native + + + diff --git a/server/pom.xml b/server/pom.xml index 56c8cf00..7c6c445b 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -7,9 +7,12 @@ net.laprun.sustainability power-server-parent - 1.0.0-SNAPSHOT + 0.0.1-SNAPSHOT power-server + power-server : server + A RESTful endpoint to retrieve power consumption information on a per-process basis + 21 21 @@ -19,7 +22,7 @@ net.laprun.sustainability power-server-metadata - 1.0.0-SNAPSHOT + 0.0.1-SNAPSHOT @@ -32,7 +35,7 @@ net.laprun.sustainability build-tools - 1.0.0-SNAPSHOT + 0.0.1-SNAPSHOT diff --git a/server/src/main/java/net/laprun/sustainability/power/sensors/Measures.java b/server/src/main/java/net/laprun/sustainability/power/sensors/Measures.java index e4bf6760..169ff8cd 100644 --- a/server/src/main/java/net/laprun/sustainability/power/sensors/Measures.java +++ b/server/src/main/java/net/laprun/sustainability/power/sensors/Measures.java @@ -4,18 +4,62 @@ import net.laprun.sustainability.power.SensorMeasure; +/** + * A representation of ongoing {@link PowerSensor} measures. + */ public interface Measures { + /** + * Represents an invalid or somehow missed measure. + */ SensorMeasure missing = new SensorMeasure(new double[] { -1.0 }, -1); + /** + * Tracks the provided process identifier (pid) in the measures. For sensors that only provide system-wide measures, this + * probably won't be doing much more than track which processes are of interest to clients of the sensor. + * + * @param pid the process identifier which power consumption is supposed to be tracked + * @return a {@link RegisteredPID} recording the tracking of the specified pid by the sensor + */ RegisteredPID register(long pid); + /** + * Unregisters the specified {@link RegisteredPID} thus signaling that clients are not interested in tracking the + * consumption of the associated process anymore + * + * @param registeredPID the {@link RegisteredPID} that was returned when the process we want to stop tracking was first + * registered + */ void unregister(RegisteredPID registeredPID); + /** + * Retrieves the set of tracked process identifiers + * + * @return the set of tracked process identifiers + */ Set trackedPIDs(); + /** + * Retrieves the number of tracked processes + * + * @return the number of tracked processes + */ int numberOfTrackerPIDs(); + /** + * Records the specified measure and associates it to the specified tracked process, normally called once per tick + * + * @param pid the {@link RegisteredPID} representing the tracked process with which the recorded measure needs to be + * associated + * @param sensorMeasure the {@link SensorMeasure} to be recorded + */ void record(RegisteredPID pid, SensorMeasure sensorMeasure); + /** + * Retrieves the last recorded {@link SensorMeasure} associated with the specified {@link RegisteredPID} + * + * @param pid the tracked process identifier which measure we want to retrieve + * @return the last recorded {@link SensorMeasure} associated with the specified process or {@link #missing} if it cannot be + * retrieved for any reason + */ SensorMeasure getOrDefault(RegisteredPID pid); } diff --git a/server/src/main/java/net/laprun/sustainability/power/sensors/PowerSensor.java b/server/src/main/java/net/laprun/sustainability/power/sensors/PowerSensor.java index 901f8fdb..912a4bf1 100644 --- a/server/src/main/java/net/laprun/sustainability/power/sensors/PowerSensor.java +++ b/server/src/main/java/net/laprun/sustainability/power/sensors/PowerSensor.java @@ -2,20 +2,65 @@ import net.laprun.sustainability.power.SensorMetadata; +/** + * A representation of a power-consumption sensor. + */ public interface PowerSensor { + /** + * Stops measuring power consumption + */ default void stop() { } + /** + * Retrieves the metadata associated with the sensor, in particular, which components are supported and how they are laid + * out in the {@link net.laprun.sustainability.power.SensorMeasure} that the sensor outputs + * + * @return the metadata associated with the sensor + */ SensorMetadata metadata(); + /** + * Whether the sensor has started measuring power consumption or not + * + * @return {@code true} if measures are ongoing, {@code false} otherwise + */ boolean isStarted(); + /** + * Starts emitting power consumption measures at the given frequency + * + * @param samplingFrequencyInMillis the number of milliseconds between emitted measures + * @throws Exception if the sensor couldn't be started for some reason + */ void start(long samplingFrequencyInMillis) throws Exception; + /** + * Registers the provided process identifier (pid) with the sensor in case it can provide per-process measures. For sensors + * that only provide system-wide measures, this probably won't be doing much more than track which processes are of interest + * to clients of the sensor. + * + * @param pid the process identifier which power consumption is supposed to be tracked + * @return a {@link RegisteredPID} recording the tracking of the specified pid by the sensor + */ RegisteredPID register(long pid); + /** + * Updates the ongoing {@link Measures} being recorded by this sensor for the given tick + * + * @param tick an ordinal value tracking the number of recorded measures being taken by the sensor since it started + * measuring power consumption + * @return the {@link Measures} object recording the measures this sensor has taken since it started measuring + */ Measures update(Long tick); + /** + * Unregisters the specified {@link RegisteredPID} with this sensor thus signaling that clients are not interested in + * tracking the consumption of the associated process anymore + * + * @param registeredPID the {@link RegisteredPID} that was returned when the process we want to stop tracking was first + * registered with this sensor + */ void unregister(RegisteredPID registeredPID); } diff --git a/server/src/main/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensor.java b/server/src/main/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensor.java index a7feed03..900f7376 100644 --- a/server/src/main/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensor.java +++ b/server/src/main/java/net/laprun/sustainability/power/sensors/linux/rapl/IntelRAPLSensor.java @@ -11,6 +11,9 @@ import net.laprun.sustainability.power.sensors.PowerSensor; import net.laprun.sustainability.power.sensors.RegisteredPID; +/** + * A sensor using Intel's RAPL accessed via Linux' powercap system. + */ public class IntelRAPLSensor implements PowerSensor { private final RAPLFile[] raplFiles; private final SensorMetadata metadata; @@ -18,6 +21,9 @@ public class IntelRAPLSensor implements PowerSensor { private long frequency; private final SingleMeasureMeasures measures = new SingleMeasureMeasures(); + /** + * Initializes the RAPL sensor + */ public IntelRAPLSensor() { // if we total system energy is not available, read package and DRAM if possible // todo: check Intel doc diff --git a/server/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java b/server/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java index efd3d994..3c6a9698 100644 --- a/server/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java +++ b/server/src/main/java/net/laprun/sustainability/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java @@ -16,15 +16,36 @@ import net.laprun.sustainability.power.sensors.PowerSensor; import net.laprun.sustainability.power.sensors.RegisteredPID; +/** + * A macOS powermetrics based {@link PowerSensor} implementation. + */ public abstract class MacOSPowermetricsSensor implements PowerSensor { + /** + * The Central Processing Unit component name + */ public static final String CPU = "CPU"; + /** + * The Graphics Procssing Unit component name + */ public static final String GPU = "GPU"; + /** + * The Apple Neural Engine component name + */ public static final String ANE = "ANE"; + /** + * The Dynamic Random Access Memory component name + */ @SuppressWarnings("unused") public static final String DRAM = "DRAM"; @SuppressWarnings("unused") public static final String DCS = "DCS"; + /** + * The package component name + */ public static final String PACKAGE = "Package"; + /** + * The extracted CPU share component name, this represents the process' share of the measured power consumption + */ public static final String CPU_SHARE = "cpuShare"; private final Measures measures = new MapMeasures();