From dd38cf6e11c9f8c5c3aabd46257f38b54e707dc8 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 29 Nov 2023 10:20:29 +0100 Subject: [PATCH 1/7] refactor: rename file more appropriately --- .../sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java | 2 +- server/src/test/resources/{foo.txt => sonoma-m1max.txt} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename server/src/test/resources/{foo.txt => sonoma-m1max.txt} (100%) diff --git a/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java b/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java index 6d49e1c0..f3177932 100644 --- a/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java +++ b/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java @@ -19,7 +19,7 @@ void extractPowerMeasure() { final var pid1 = sensor.register(29419); final var pid2 = sensor.register(391); final var measure = sensor - .extractPowerMeasure(Thread.currentThread().getContextClassLoader().getResourceAsStream("foo.txt")); + .extractPowerMeasure(Thread.currentThread().getContextClassLoader().getResourceAsStream("sonoma-m1max.txt")); final var metadata = sensor.metadata(); assertEquals(((23.88 / 1222.65) * 211), measure.get(pid1)[metadata.metadataFor(MacOSPowermetricsSensor.CPU).index()]); assertEquals(((283.25 / 1222.65) * 211), measure.get(pid2)[metadata.metadataFor(MacOSPowermetricsSensor.CPU).index()]); diff --git a/server/src/test/resources/foo.txt b/server/src/test/resources/sonoma-m1max.txt similarity index 100% rename from server/src/test/resources/foo.txt rename to server/src/test/resources/sonoma-m1max.txt From 6624dcfaea7095b000ca555474b9a92e0316b63f Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 29 Nov 2023 14:23:56 +0100 Subject: [PATCH 2/7] fix: make metadata more flexible, consolidate implementations --- .../github/metacosm/power/SensorMetadata.java | 23 +++-- .../sensors/linux/rapl/IntelRAPLSensor.java | 22 +---- .../powermetrics/MacOSPowermetricsSensor.java | 99 ++++++++++++++----- .../MacOSPowermetricsSensorTest.java | 55 +++++++++-- server/src/test/resources/monterey-m2.txt | 58 +++++++++++ 5 files changed, 198 insertions(+), 59 deletions(-) create mode 100644 server/src/test/resources/monterey-m2.txt diff --git a/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java b/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java index 0174fd49..0a6fbc65 100644 --- a/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java +++ b/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java @@ -4,13 +4,24 @@ import java.util.Map; -public interface SensorMetadata { - record ComponentMetadata(String name, int index, String description, boolean isAttributed, String unit){}; +public class SensorMetadata { + public record ComponentMetadata(String name, int index, String description, boolean isAttributed, String unit){}; - ComponentMetadata metadataFor(String component); + public SensorMetadata(Map components) { + this.components = components; + } - int componentCardinality(); + private final Map components; - @JsonProperty("metadata") - Map getComponents(); + public ComponentMetadata metadataFor(String component) { + final var componentMetadata = components.get(component); + if (componentMetadata == null) { + throw new IllegalArgumentException("Unknown component: " + component); + } + return componentMetadata; + } + + public int componentCardinality() { + return components.size(); + } } diff --git a/server/src/main/java/io/github/metacosm/power/sensors/linux/rapl/IntelRAPLSensor.java b/server/src/main/java/io/github/metacosm/power/sensors/linux/rapl/IntelRAPLSensor.java index 5960e7bf..e8284521 100644 --- a/server/src/main/java/io/github/metacosm/power/sensors/linux/rapl/IntelRAPLSensor.java +++ b/server/src/main/java/io/github/metacosm/power/sensors/linux/rapl/IntelRAPLSensor.java @@ -35,27 +35,7 @@ public IntelRAPLSensor() { for (String name : files.keySet()) { metadata.put(name, new SensorMetadata.ComponentMetadata(name, fileNb++, name, false, "µJ")); } - this.metadata = new SensorMetadata() { - - @Override - public ComponentMetadata metadataFor(String component) { - final var componentMetadata = metadata.get(component); - if (componentMetadata == null) { - throw new IllegalArgumentException("Unknown component: " + component); - } - return componentMetadata; - } - - @Override - public int componentCardinality() { - return metadata.size(); - } - - @Override - public Map getComponents() { - return metadata; - } - }; + this.metadata = new SensorMetadata(metadata); lastMeasuredSensorValues = new double[raplFiles.length]; } diff --git a/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java b/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java index 4d0415c6..c1212536 100644 --- a/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java +++ b/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java @@ -5,47 +5,98 @@ import io.github.metacosm.power.sensors.RegisteredPID; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; public class MacOSPowermetricsSensor implements PowerSensor { - public static final String CPU = "cpu"; - public static final String GPU = "gpu"; - public static final String ANE = "ane"; + public static final String CPU = "CPU"; + public static final String GPU = "GPU"; + public static final String ANE = "ANE"; + public static final String DRAM = "DRAM"; + public static final String DCS = "DCS"; + public static final String PACKAGE = "Package"; + private static final String COMBINED = "Combined"; public static final String CPU_SHARE = "cpuShare"; private Process powermetrics; - private final SensorMetadata.ComponentMetadata cpu = new SensorMetadata.ComponentMetadata(CPU, 0, "CPU power", true, "mW"); - private final SensorMetadata.ComponentMetadata gpu = new SensorMetadata.ComponentMetadata(GPU, 1, "GPU power", true, "mW"); - private final SensorMetadata.ComponentMetadata ane = new SensorMetadata.ComponentMetadata(ANE, 2, "Apple Neural Engine power", false, "mW"); - private final SensorMetadata.ComponentMetadata cpuShare = new SensorMetadata.ComponentMetadata(CPU_SHARE, 3, "Computed share of CPU", false, "decimal percentage"); + private static final SensorMetadata.ComponentMetadata cpu = new SensorMetadata.ComponentMetadata(CPU, 0, "CPU power", true, "mW"); + private static final SensorMetadata.ComponentMetadata gpu = new SensorMetadata.ComponentMetadata(GPU, 1, "GPU power", true, "mW"); + private static final SensorMetadata.ComponentMetadata ane = new SensorMetadata.ComponentMetadata(ANE, 2, "Apple Neural Engine power", false, "mW"); + private static final SensorMetadata.ComponentMetadata cpuShare = new SensorMetadata.ComponentMetadata(CPU_SHARE, 3, "Computed share of CPU", false, "decimal percentage"); private final Map trackedPIDs = new ConcurrentHashMap<>(); - private final SensorMetadata metadata = new SensorMetadata() { - @Override - public ComponentMetadata metadataFor(String component) { - return switch (component) { - case CPU -> cpu; - case GPU -> gpu; - case ANE -> ane; - case CPU_SHARE -> cpuShare; - default -> throw new IllegalArgumentException("Unknown component: '" + component + "'"); - }; + private final SensorMetadata metadata; + + public MacOSPowermetricsSensor() { + // extract metadata + try { + final var exec = Runtime.getRuntime().exec("sudo powermetrics --samplers cpu_power -i 10 -n 1"); + exec.waitFor(20, TimeUnit.MILLISECONDS); + this.metadata = initMetadata(exec.getInputStream()); + } catch (Exception e) { + throw new RuntimeException(e); } + } + + MacOSPowermetricsSensor(InputStream inputStream) { + this.metadata = initMetadata(inputStream); + } + + SensorMetadata initMetadata(InputStream inputStream) { + // init map with known components + Map components = new HashMap<>(); + components.put(CPU, cpu); + components.put(GPU, gpu); + components.put(ANE, ane); + components.put(CPU_SHARE, cpuShare); + + int headerLinesToSkip = 10; + try (BufferedReader input = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = input.readLine()) != null) { + if (headerLinesToSkip != 0) { + headerLinesToSkip--; + continue; + } + + // skip empty / header lines + if (line.isEmpty() || line.startsWith("*")) { + continue; + } + + // looking for line fitting the: " 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); + } + } - @Override - public int componentCardinality() { - return 4; + } catch (IOException e) { + throw new RuntimeException(e); } - @Override - public Map getComponents() { - return Map.of(cpu.name(), cpu, gpu.name(), gpu, ane.name(), ane, cpuShare.name(), cpuShare); + return new SensorMetadata(components); + } + + + private static void addComponentTo(String name, Map 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 SensorMetadata metadata() { diff --git a/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java b/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java index f3177932..214b651f 100644 --- a/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java +++ b/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java @@ -1,26 +1,65 @@ package io.github.metacosm.power.sensors.macos.powermetrics; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import io.github.metacosm.power.SensorMetadata; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.io.InputStream; + class MacOSPowermetricsSensorTest { @Test - void checkMetadata() { - final var sensor = new MacOSPowermetricsSensor(); - final var metadata = sensor.metadata(); - assertEquals("cpu", metadata.metadataFor(MacOSPowermetricsSensor.CPU).name()); + void checkMetadata() throws IOException { + var metadata = loadMetadata("sonoma-m1max.txt"); + assertEquals(4, metadata.componentCardinality()); + checkComponent(metadata, "CPU", 0); + checkComponent(metadata, "GPU", 1); + checkComponent(metadata, "ANE", 2); + checkComponent(metadata, "cpuShare", 3); + + metadata = loadMetadata("monterey-m2.txt"); + assertEquals(7, metadata.componentCardinality()); + checkComponent(metadata, "CPU", 0); + checkComponent(metadata, "GPU", 1); + checkComponent(metadata, "ANE", 2); + checkComponent(metadata, "cpuShare", 3); + checkComponent(metadata, "DRAM", 4); + checkComponent(metadata, "DCS", 5); + checkComponent(metadata, "Package", 6); + } + + private static SensorMetadata loadMetadata(String fileName) throws IOException { + try (var in = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)) { + return loadMetadata(in); + } + } + + private static SensorMetadata loadMetadata(InputStream in) { + final var sensor = new MacOSPowermetricsSensor(in); + return sensor.metadata(); + } + + private static void checkComponent(SensorMetadata metadata, String name, int index) { + // check string instead of constants to ensure "API" compatibility as these keys will be published + final var component = metadata.metadataFor(name); + assertEquals(name, component.name()); + assertEquals(index, component.index()); } @Test void extractPowerMeasure() { - final var sensor = new MacOSPowermetricsSensor(); + var in = Thread.currentThread().getContextClassLoader().getResourceAsStream("sonoma-m1max.txt"); + final var sensor = new MacOSPowermetricsSensor(in); + final var metadata = sensor.metadata(); final var pid1 = sensor.register(29419); final var pid2 = sensor.register(391); - final var measure = sensor - .extractPowerMeasure(Thread.currentThread().getContextClassLoader().getResourceAsStream("sonoma-m1max.txt")); - final var metadata = sensor.metadata(); + + // re-open the stream to read the measure this time + in = Thread.currentThread().getContextClassLoader().getResourceAsStream("sonoma-m1max.txt"); + final var measure = sensor.extractPowerMeasure(in); assertEquals(((23.88 / 1222.65) * 211), measure.get(pid1)[metadata.metadataFor(MacOSPowermetricsSensor.CPU).index()]); assertEquals(((283.25 / 1222.65) * 211), measure.get(pid2)[metadata.metadataFor(MacOSPowermetricsSensor.CPU).index()]); } diff --git a/server/src/test/resources/monterey-m2.txt b/server/src/test/resources/monterey-m2.txt new file mode 100644 index 00000000..fecaeb09 --- /dev/null +++ b/server/src/test/resources/monterey-m2.txt @@ -0,0 +1,58 @@ +Machine model: Mac14,2 +OS version: 21G72 +Boot arguments: +Boot time: Mon Oct 2 10:37:21 2023 + + + +*** Sampled system activity (Tue Nov 28 11:28:55 2023 +0100) (1002.80ms elapsed) *** + + +**** Processor usage **** + +E-Cluster Power: 6 mW +E-Cluster HW active frequency: 919 MHz +E-Cluster HW active residency: 5.37% (600 MHz: 0% 912 MHz: 100% 1284 MHz: 0% 1752 MHz: 0% 2004 MHz: 0% 2256 MHz: 0% 2424 MHz: .44%) +E-Cluster idle residency: 94.63% +E-Cluster instructions retired: 5.66564e+07 +E-Cluster instructions per clock: 0.996462 +CPU 0 frequency: 920 MHz +CPU 0 idle residency: 97.09% +CPU 0 active residency: 2.91% (600 MHz: 0% 912 MHz: 2.9% 1284 MHz: 0% 1752 MHz: 0% 2004 MHz: 0% 2256 MHz: 0% 2424 MHz: .02%) +CPU 1 frequency: 922 MHz +CPU 1 idle residency: 98.03% +CPU 1 active residency: 1.97% (600 MHz: 0% 912 MHz: 2.0% 1284 MHz: 0% 1752 MHz: 0% 2004 MHz: 0% 2256 MHz: 0% 2424 MHz: .01%) +CPU 2 frequency: 943 MHz +CPU 2 idle residency: 98.82% +CPU 2 active residency: 1.18% (600 MHz: 0% 912 MHz: 1.2% 1284 MHz: 0% 1752 MHz: 0% 2004 MHz: 0% 2256 MHz: 0% 2424 MHz: .02%) +CPU 3 frequency: 913 MHz +CPU 3 idle residency: 99.04% +CPU 3 active residency: 0.96% (600 MHz: 0% 912 MHz: .96% 1284 MHz: 0% 1752 MHz: 0% 2004 MHz: 0% 2256 MHz: 0% 2424 MHz: .00%) + +P-Cluster Power: 4 mW +P-Cluster HW active frequency: 667 MHz +P-Cluster HW active residency: 0.00% (660 MHz: 100% 924 MHz: 0% 1188 MHz: 0% 1452 MHz: 0% 1704 MHz: 0% 1968 MHz: 0% 2208 MHz: 0% 2400 MHz: 0% 2568 MHz: 0% 2724 MHz: 0% 2868 MHz: 0% 2988 MHz: 0% 3096 MHz: 0% 3204 MHz: .03% 3324 MHz: .25% 3408 MHz: 0% 3504 MHz: 0%) +P-Cluster idle residency: 100.00% +P-Cluster instructions retired: 6.11748e+07 +P-Cluster instructions per clock: 2.88735 +CPU 4 frequency: 3079 MHz +CPU 4 idle residency: 99.81% +CPU 4 active residency: 0.19% (660 MHz: .03% 924 MHz: 0% 1188 MHz: 0% 1452 MHz: 0% 1704 MHz: 0% 1968 MHz: 0% 2208 MHz: 0% 2400 MHz: 0% 2568 MHz: 0% 2724 MHz: 0% 2868 MHz: 0% 2988 MHz: 0% 3096 MHz: 0% 3204 MHz: 0% 3324 MHz: .04% 3408 MHz: 0% 3504 MHz: .12%) +CPU 5 frequency: 1814 MHz +CPU 5 idle residency: 99.99% +CPU 5 active residency: 0.01% (660 MHz: .00% 924 MHz: 0% 1188 MHz: 0% 1452 MHz: 0% 1704 MHz: 0% 1968 MHz: 0% 2208 MHz: 0% 2400 MHz: 0% 2568 MHz: 0% 2724 MHz: 0% 2868 MHz: 0% 2988 MHz: 0% 3096 MHz: 0% 3204 MHz: 0% 3324 MHz: .00% 3408 MHz: 0% 3504 MHz: 0%) +CPU 6 frequency: 660 MHz +CPU 6 idle residency: 100.00% +CPU 6 active residency: 0.00% (660 MHz: .00% 924 MHz: 0% 1188 MHz: 0% 1452 MHz: 0% 1704 MHz: 0% 1968 MHz: 0% 2208 MHz: 0% 2400 MHz: 0% 2568 MHz: 0% 2724 MHz: 0% 2868 MHz: 0% 2988 MHz: 0% 3096 MHz: 0% 3204 MHz: 0% 3324 MHz: 0% 3408 MHz: 0% 3504 MHz: 0%) +CPU 7 frequency: 660 MHz +CPU 7 idle residency: 100.00% +CPU 7 active residency: 0.00% (660 MHz: .00% 924 MHz: 0% 1188 MHz: 0% 1452 MHz: 0% 1704 MHz: 0% 1968 MHz: 0% 2208 MHz: 0% 2400 MHz: 0% 2568 MHz: 0% 2724 MHz: 0% 2868 MHz: 0% 2988 MHz: 0% 3096 MHz: 0% 3204 MHz: 0% 3324 MHz: 0% 3408 MHz: 0% 3504 MHz: 0%) + +System instructions retired: 1.17831e+08 +System instructions per clock: 1.50979 +ANE Power: 0 mW +DRAM Power: 19 mW +DCS Power: 36 mW +CPU Power: 10 mW +GPU Power: 0 mW +Package Power: 25 mW \ No newline at end of file From bca022cfbb4d24a59714b364ddd289c859194a09 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 29 Nov 2023 14:26:48 +0100 Subject: [PATCH 3/7] fix: properly output JSON --- .../src/main/java/io/github/metacosm/power/SensorMetadata.java | 1 + 1 file changed, 1 insertion(+) diff --git a/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java b/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java index 0a6fbc65..94312950 100644 --- a/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java +++ b/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java @@ -11,6 +11,7 @@ public SensorMetadata(Map components) { this.components = components; } + @JsonProperty("metadata") private final Map components; public ComponentMetadata metadataFor(String component) { From f1553c08350ae775731e39f7f20612ddde18ab02 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 29 Nov 2023 17:54:49 +0100 Subject: [PATCH 4/7] chore: remove unneeded file --- .../resources/META-INF/resources/index.html | 279 ------------------ 1 file changed, 279 deletions(-) delete mode 100644 server/src/main/resources/META-INF/resources/index.html diff --git a/server/src/main/resources/META-INF/resources/index.html b/server/src/main/resources/META-INF/resources/index.html deleted file mode 100644 index e4f04ca1..00000000 --- a/server/src/main/resources/META-INF/resources/index.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - power-server - 1.0.0-SNAPSHOT - - - -
-
-
- - - - - quarkus_logo_horizontal_rgb_1280px_reverse - - - - - - - - - - - - - - - - - - -
-
-
- -
-
-
-

You just made a Quarkus application.

-

This page is served by Quarkus.

- Visit the Dev UI -

This page: src/main/resources/META-INF/resources/index.html

-

App configuration: src/main/resources/application.properties

-

Static assets: src/main/resources/META-INF/resources/

-

Code: src/main/java

-

Generated starter code:

-
    -
  • - RESTEasy Reactive Easily start your Reactive RESTful Web Services -
    @Path: /hello -
    Related guide -
  • - -
-
-
-
Documentation
-

Practical step-by-step guides to help you achieve a specific goal. Use them to help get your work - done.

-
Set up your IDE
-

Everyone has a favorite IDE they like to use to code. Learn how to configure yours to maximize your - Quarkus productivity.

-
-
-
- - From 37c47d6db9eb39e5dc724a32a18d1474620d4a23 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 29 Nov 2023 17:55:26 +0100 Subject: [PATCH 5/7] fix: make SensorMetadata deserializable --- .../github/metacosm/power/SensorMetadata.java | 7 +++ .../io/github/metacosm/PowerResourceTest.java | 44 ++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java b/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java index 94312950..7eb9d3a6 100644 --- a/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java +++ b/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java @@ -1,12 +1,15 @@ package io.github.metacosm.power; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; import java.util.Map; public class SensorMetadata { public record ComponentMetadata(String name, int index, String description, boolean isAttributed, String unit){}; + @JsonCreator public SensorMetadata(Map components) { this.components = components; } @@ -25,4 +28,8 @@ public ComponentMetadata metadataFor(String component) { public int componentCardinality() { return components.size(); } + + public Map components() { + return Collections.unmodifiableMap(components); + } } diff --git a/server/src/test/java/io/github/metacosm/PowerResourceTest.java b/server/src/test/java/io/github/metacosm/PowerResourceTest.java index 03f69c3c..6e106190 100644 --- a/server/src/test/java/io/github/metacosm/PowerResourceTest.java +++ b/server/src/test/java/io/github/metacosm/PowerResourceTest.java @@ -1,15 +1,23 @@ package io.github.metacosm; +import io.github.metacosm.power.SensorMetadata; import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import java.util.Set; import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.*; @QuarkusTest public class PowerResourceTest { @Test - public void testHelloEndpoint() { + public void testPowerEndpoint() { final var pid = ProcessHandle.current().pid(); given() .when().get("/power/" + pid) @@ -17,4 +25,38 @@ public void testHelloEndpoint() { .statusCode(200); } + @Test + @EnabledOnOs(OS.MAC) + public void testMacOSMetadataEndpoint() { + final var metadata = given() + .when().get("/power/metadata") + .then() + .statusCode(200) + .extract().body().as(SensorMetadata.class); + assertEquals(4, metadata.componentCardinality()); + assertTrue(metadata.components().keySet().containsAll(Set.of("CPU", "GPU", "ANE", "cpuShare"))); + + final var cpu = metadata.metadataFor("CPU"); + assertEquals(0, cpu.index()); + assertEquals("CPU", cpu.name()); + assertEquals("mW", cpu.unit()); + assertTrue(cpu.isAttributed()); + + final var cpuShare = metadata.metadataFor("cpuShare"); + assertEquals(3, cpuShare.index()); + assertEquals("cpuShare", cpuShare.name()); + assertEquals("decimal percentage", cpuShare.unit()); + assertFalse(cpuShare.isAttributed()); + } + + @Test + @EnabledOnOs(OS.LINUX) + public void testLinuxMetadataEndpoint() { + final var metadata = given() + .when().get("/power/metadata") + .then() + .statusCode(200) + .extract().body().as(SensorMetadata.class); + } + } \ No newline at end of file From be159f0044ea1ac43099bd5df0a1d3d6cd90279a Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 29 Nov 2023 18:44:27 +0100 Subject: [PATCH 6/7] feat: add documentation field to metadata --- .../java/io/github/metacosm/power/SensorMetadata.java | 10 +++++++++- .../power/sensors/linux/rapl/IntelRAPLSensor.java | 2 +- .../macos/powermetrics/MacOSPowermetricsSensor.java | 2 +- .../java/io/github/metacosm/PowerResourceTest.java | 2 ++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java b/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java index 7eb9d3a6..ec2557ea 100644 --- a/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java +++ b/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java @@ -10,13 +10,17 @@ public class SensorMetadata { public record ComponentMetadata(String name, int index, String description, boolean isAttributed, String unit){}; @JsonCreator - public SensorMetadata(Map components) { + public SensorMetadata(Map components, String documentation) { this.components = components; + this.documentation = documentation; } @JsonProperty("metadata") private final Map components; + @JsonProperty("documentation") + private final String documentation; + public ComponentMetadata metadataFor(String component) { final var componentMetadata = components.get(component); if (componentMetadata == null) { @@ -32,4 +36,8 @@ public int componentCardinality() { public Map components() { return Collections.unmodifiableMap(components); } + + public String documentation() { + return documentation; + } } diff --git a/server/src/main/java/io/github/metacosm/power/sensors/linux/rapl/IntelRAPLSensor.java b/server/src/main/java/io/github/metacosm/power/sensors/linux/rapl/IntelRAPLSensor.java index e8284521..1a52aaf0 100644 --- a/server/src/main/java/io/github/metacosm/power/sensors/linux/rapl/IntelRAPLSensor.java +++ b/server/src/main/java/io/github/metacosm/power/sensors/linux/rapl/IntelRAPLSensor.java @@ -35,7 +35,7 @@ public IntelRAPLSensor() { for (String name : files.keySet()) { metadata.put(name, new SensorMetadata.ComponentMetadata(name, fileNb++, name, false, "µJ")); } - this.metadata = new SensorMetadata(metadata); + this.metadata = new SensorMetadata(metadata, "Linux RAPL derived information, see https://www.kernel.org/doc/html/latest/power/powercap/powercap.html"); lastMeasuredSensorValues = new double[raplFiles.length]; } diff --git a/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java b/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java index c1212536..e7b0afce 100644 --- a/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java +++ b/server/src/main/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensor.java @@ -81,7 +81,7 @@ SensorMetadata initMetadata(InputStream inputStream) { throw new RuntimeException(e); } - return new SensorMetadata(components); + return new SensorMetadata(components, "macOS powermetrics derived information, see https://firefox-source-docs.mozilla.org/performance/powermetrics.html"); } diff --git a/server/src/test/java/io/github/metacosm/PowerResourceTest.java b/server/src/test/java/io/github/metacosm/PowerResourceTest.java index 6e106190..f09f004f 100644 --- a/server/src/test/java/io/github/metacosm/PowerResourceTest.java +++ b/server/src/test/java/io/github/metacosm/PowerResourceTest.java @@ -34,6 +34,7 @@ public void testMacOSMetadataEndpoint() { .statusCode(200) .extract().body().as(SensorMetadata.class); assertEquals(4, metadata.componentCardinality()); + assertTrue(metadata.documentation().contains("powermetrics")); assertTrue(metadata.components().keySet().containsAll(Set.of("CPU", "GPU", "ANE", "cpuShare"))); final var cpu = metadata.metadataFor("CPU"); @@ -57,6 +58,7 @@ public void testLinuxMetadataEndpoint() { .then() .statusCode(200) .extract().body().as(SensorMetadata.class); + assertTrue(metadata.documentation().contains("RAPL")); } } \ No newline at end of file From 32f770318f76ca49cbef8a96b9e74de8ecaa3b93 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 29 Nov 2023 18:47:12 +0100 Subject: [PATCH 7/7] chore: clean up --- .../main/java/io/github/metacosm/power/SensorMetadata.java | 2 +- .../src/test/java/io/github/metacosm/PowerResourceTest.java | 2 -- .../macos/powermetrics/MacOSPowermetricsSensorTest.java | 5 ++--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java b/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java index ec2557ea..f7511621 100644 --- a/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java +++ b/metadata/src/main/java/io/github/metacosm/power/SensorMetadata.java @@ -7,7 +7,7 @@ import java.util.Map; public class SensorMetadata { - public record ComponentMetadata(String name, int index, String description, boolean isAttributed, String unit){}; + public record ComponentMetadata(String name, int index, String description, boolean isAttributed, String unit){} @JsonCreator public SensorMetadata(Map components, String documentation) { diff --git a/server/src/test/java/io/github/metacosm/PowerResourceTest.java b/server/src/test/java/io/github/metacosm/PowerResourceTest.java index f09f004f..16db10a2 100644 --- a/server/src/test/java/io/github/metacosm/PowerResourceTest.java +++ b/server/src/test/java/io/github/metacosm/PowerResourceTest.java @@ -9,8 +9,6 @@ import java.util.Set; import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.*; @QuarkusTest diff --git a/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java b/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java index 214b651f..2fe438d7 100644 --- a/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java +++ b/server/src/test/java/io/github/metacosm/power/sensors/macos/powermetrics/MacOSPowermetricsSensorTest.java @@ -1,14 +1,13 @@ package io.github.metacosm.power.sensors.macos.powermetrics; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - import io.github.metacosm.power.SensorMetadata; import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; +import static org.junit.jupiter.api.Assertions.assertEquals; + class MacOSPowermetricsSensorTest { @Test