Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ci): run some end to end tests on CI #18

Merged
merged 5 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml
run: mvn -B package -Dquarkus.test.profile.tags='ci' --file pom.xml

# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
# - name: Update dependency graph
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ public class PowerMeasurer {

public Multi<SensorMeasure> startTracking(String pid) throws Exception {
// first make sure that the process with that pid exists
final var parsedPID = Long.parseLong(pid);
ProcessHandle.of(parsedPID).orElseThrow(() -> new IllegalArgumentException("Unknown process: " + pid));
final var parsedPID = validPIDOrFail(pid);

if (!sensor.isStarted()) {
sensor.start(SAMPLING_FREQUENCY_IN_MILLIS);
Expand All @@ -42,6 +41,12 @@ public Multi<SensorMeasure> startTracking(String pid) throws Exception {
.onCancellation().invoke(() -> sensor.unregister(registeredPID));
}

protected long validPIDOrFail(String pid) {
final var parsedPID = Long.parseLong(pid);
ProcessHandle.of(parsedPID).orElseThrow(() -> new IllegalArgumentException("Unknown process: " + pid));
return parsedPID;
}

public SensorMetadata metadata() {
return sensor.metadata();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import jakarta.inject.Singleton;

import io.github.metacosm.power.sensors.linux.rapl.IntelRAPLSensor;
import io.github.metacosm.power.sensors.macos.powermetrics.MacOSPowermetricsSensor;
import io.github.metacosm.power.sensors.macos.powermetrics.ProcessMacOSPowermetricsSensor;

@Singleton
public class PowerSensorProducer {
Expand All @@ -17,7 +17,7 @@ public PowerSensor sensor() {

public static PowerSensor determinePowerSensor() {
if (OS_NAME.contains("mac os x")) {
return new MacOSPowermetricsSensor();
return new ProcessMacOSPowermetricsSensor();
}

if (!OS_NAME.contains("linux")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,23 @@
import io.github.metacosm.power.sensors.PowerSensor;
import io.github.metacosm.power.sensors.RegisteredPID;

public class MacOSPowermetricsSensor implements PowerSensor {
public abstract class MacOSPowermetricsSensor implements PowerSensor {
public static final String CPU = "CPU";
public static final String GPU = "GPU";
public static final String ANE = "ANE";
@SuppressWarnings("unused")
public static final String DRAM = "DRAM";
@SuppressWarnings("unused")
public static final String DCS = "DCS";
public static final String PACKAGE = "Package";
public static final String CPU_SHARE = "cpuShare";

private Process powermetrics;
private final Measures measures = new MapMeasures();
private final CPU cpu;
private CPU cpu;

public MacOSPowermetricsSensor() {
// extract metadata
try {
final var exec = new ProcessBuilder()
.command("sudo", "powermetrics", "--samplers", "cpu_power", "-i", "10", "-n", "1")
.start();
this.cpu = initMetadata(exec.getInputStream());
} catch (Exception e) {
throw new RuntimeException("Couldn't execute powermetrics to extract metadata", e);
}
}

MacOSPowermetricsSensor(InputStream inputStream) {
this.cpu = initMetadata(inputStream);
}

CPU initMetadata(InputStream inputStream) {
void initMetadata(InputStream inputStream) {
try (BufferedReader input = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
CPU cpu = null;
Map<String, SensorMetadata.ComponentMetadata> components = new HashMap<>();
while ((line = input.readLine()) != null) {
if (cpu == null) {
Expand Down Expand Up @@ -78,7 +62,6 @@ CPU initMetadata(InputStream inputStream) {
final var metadata = new SensorMetadata(components,
"macOS powermetrics derived information, see https://firefox-source-docs.mozilla.org/performance/powermetrics.html");
cpu.setMetadata(metadata);
return cpu;
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down Expand Up @@ -200,29 +183,12 @@ Measures extractPowerMeasure(InputStream powerMeasureInput, Long tick) {
return measures;
}

public void start(long frequency) throws Exception {
if (!isStarted()) {
// it takes some time for the external process in addition to the sampling time so adjust the sampling frequency to account for this so that at most one measure occurs during the sampling time window
final var freq = Long.toString(frequency - 50);
powermetrics = new ProcessBuilder().command("sudo", "powermetrics", "--samplers", "cpu_power,tasks",
"--show-process-samp-norm", "--show-process-gpu", "-i", freq).start();
}
}

@Override
public boolean isStarted() {
return powermetrics != null && powermetrics.isAlive();
}

@Override
public Measures update(Long tick) {
return extractPowerMeasure(powermetrics.getInputStream(), tick);
return extractPowerMeasure(getInputStream(), tick);
}

@Override
public void stop() {
powermetrics.destroy();
}
protected abstract InputStream getInputStream();

@Override
public void unregister(RegisteredPID registeredPID) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.github.metacosm.power.sensors.macos.powermetrics;

import java.io.InputStream;

public class ProcessMacOSPowermetricsSensor extends MacOSPowermetricsSensor {
private Process powermetrics;

public ProcessMacOSPowermetricsSensor() {
// extract metadata
try {
final var exec = new ProcessBuilder()
.command("sudo", "powermetrics", "--samplers", "cpu_power", "-i", "10", "-n", "1")
.start();
initMetadata(exec.getInputStream());
} catch (Exception e) {
throw new RuntimeException("Couldn't execute powermetrics to extract metadata", e);
}
}

public void start(long frequency) throws Exception {
if (!isStarted()) {
// it takes some time for the external process in addition to the sampling time so adjust the sampling frequency to account for this so that at most one measure occurs during the sampling time window
final var freq = Long.toString(frequency - 50);
powermetrics = new ProcessBuilder().command("sudo", "powermetrics", "--samplers", "cpu_power,tasks",
"--show-process-samp-norm", "--show-process-gpu", "-i", freq).start();
}
}

@Override
public boolean isStarted() {
return powermetrics != null && powermetrics.isAlive();
}

@Override
protected InputStream getInputStream() {
return powermetrics.getInputStream();
}

@Override
public void stop() {
powermetrics.destroy();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.github.metacosm.power.sensors.macos.powermetrics;

import java.io.InputStream;

public class ResourceMacOSPowermetricsSensor extends MacOSPowermetricsSensor {
private final String resourceName;
private boolean started;

public ResourceMacOSPowermetricsSensor(String resourceName) {
this.resourceName = resourceName;
initMetadata(getInputStream());
}

@Override
protected InputStream getInputStream() {
return Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName);
}

@Override
public boolean isStarted() {
return started;
}

@Override
public void start(long samplingFrequencyInMillis) {
if (!started) {
started = true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.github.metacosm.power;

import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.*;

import java.util.Set;

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;

@QuarkusTest
@TestProfile(CIQuarkusTestProfile.class)
public class CIPowerResourceTest {

protected long getPid() {
return 29419;
}

@Test
public void testPowerEndpoint() {
final var pid = getPid();
given()
.when().get("/power/" + pid)
.then()
.statusCode(200);
}

@Test
public void testMacOSAppleSiliconMetadataEndpoint() {
final var metadata = given()
.when().get("/power/metadata")
.then()
.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");
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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.metacosm.power;

import java.util.Set;

import io.quarkus.test.junit.QuarkusTestProfile;

public class CIQuarkusTestProfile implements QuarkusTestProfile {

@Override
public Set<String> tags() {
return Set.of("ci");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.metacosm.power;

import io.quarkus.test.Mock;

@Mock
@SuppressWarnings("unused")
public class MockPowerMeasurer extends PowerMeasurer {

@Override
protected long validPIDOrFail(String pid) {
return Long.parseLong(pid);
}
}
12 changes: 12 additions & 0 deletions server/src/test/java/io/github/metacosm/power/MockPowerSensor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.github.metacosm.power;

import io.github.metacosm.power.sensors.macos.powermetrics.ResourceMacOSPowermetricsSensor;
import io.quarkus.test.Mock;

@Mock
@SuppressWarnings("unused")
public class MockPowerSensor extends ResourceMacOSPowermetricsSensor {
public MockPowerSensor() {
super("sonoma-m1max.txt");
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.metacosm;
package io.github.metacosm.power;

import io.quarkus.test.junit.QuarkusIntegrationTest;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.metacosm;
package io.github.metacosm.power;

import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.*;
Expand All @@ -10,21 +10,24 @@
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;

import io.github.metacosm.power.SensorMetadata;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class PowerResourceTest {

@Test
public void testPowerEndpoint() {
final var pid = ProcessHandle.current().pid();
final var pid = getPid();
given()
.when().get("/power/" + pid)
.then()
.statusCode(200);
}

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

@Test
@EnabledOnOs(OS.MAC)
@EnabledIfSystemProperty(named = "os.arch", matches = "aarch64")
Expand Down
Loading