Skip to content

Commit c9a7628

Browse files
committed
feat: attempt to run a single powermetrics measure
This doesn't work because killing powermetrics should output the measure file completely but doesn't. Instead, we get a broken pipe error (141) and the file doesn't contain the expected sample information.
1 parent 110ed58 commit c9a7628

File tree

2 files changed

+100
-10
lines changed

2 files changed

+100
-10
lines changed

cli/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,20 @@
1616
<artifactId>power-server-backend</artifactId>
1717
<version>${project.version}</version>
1818
</dependency>
19+
<dependency>
20+
<groupId>net.laprun.sustainability</groupId>
21+
<artifactId>power-server-measure</artifactId>
22+
<version>${project.version}</version>
23+
</dependency>
1924
<dependency>
2025
<groupId>io.quarkus</groupId>
2126
<artifactId>quarkus-picocli</artifactId>
2227
</dependency>
28+
<dependency>
29+
<groupId>io.smallrye.config</groupId>
30+
<artifactId>smallrye-config-crypto</artifactId>
31+
<optional>true</optional>
32+
</dependency>
2333
</dependencies>
2434

2535
<build>

cli/src/main/java/net/laprun/sustainability/cli/Power.java

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,116 @@
11
package net.laprun.sustainability.cli;
22

3+
import java.io.File;
4+
import java.io.IOException;
35
import java.util.List;
46

5-
import net.laprun.sustainability.power.PowerMeasurer;
7+
import io.quarkus.logging.Log;
8+
import io.quarkus.runtime.Quarkus;
9+
import io.smallrye.mutiny.Uni;
10+
import io.smallrye.mutiny.infrastructure.Infrastructure;
11+
import net.laprun.sustainability.power.Measure;
12+
import net.laprun.sustainability.power.SensorUnit;
13+
import net.laprun.sustainability.power.analysis.total.TotalSyntheticComponent;
14+
import net.laprun.sustainability.power.sensors.PowerSensor;
15+
import net.laprun.sustainability.power.sensors.macos.powermetrics.FileMacOSPowermetricsSensor;
616
import picocli.CommandLine;
717

818
@CommandLine.Command
919
public class Power implements Runnable {
1020

11-
@CommandLine.Parameters(hidden = true)
21+
@CommandLine.Parameters(index = "0..*", hidden = true)
1222
List<String> cmd;
1323

1424
@CommandLine.Option(names = { "-n",
1525
"--name" }, description = "Optional name for the application, defaults to passed command line")
1626
String name;
1727

18-
private final PowerMeasurer measurer;
28+
private final File output;
29+
private final PowerSensor sensor;
30+
private Process powermetrics;
1931

20-
public Power(PowerMeasurer measurer) {
21-
this.measurer = measurer;
32+
public Power() throws IOException {
33+
/*
34+
* this.output = File.createTempFile("power-", ".tmp");
35+
* output.deleteOnExit();
36+
*/
37+
this.output = new File("output.txt");
38+
output.createNewFile();
39+
sensor = new FileMacOSPowermetricsSensor(output);
40+
Log.info(output.getAbsolutePath());
2241
}
2342

2443
@Override
2544
public void run() {
45+
if (cmd == null || cmd.isEmpty()) {
46+
Log.info("No command specified, exiting.");
47+
return;
48+
}
49+
50+
final var cmdPid = Uni.createFrom()
51+
.item(this::runPowermetrics)
52+
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
53+
.chain(powermetrics -> Uni.createFrom()
54+
.item(this::runCommandToMeasure)
55+
.onTermination()
56+
.invoke(powermetrics::destroy))
57+
.await()
58+
.indefinitely();
59+
60+
final int exit;
61+
try {
62+
Log.infof("powermetrics exit code: %d", powermetrics.waitFor());
63+
} catch (InterruptedException e) {
64+
throw new RuntimeException(e);
65+
}
66+
extractPowerConsumption(cmdPid);
67+
68+
Quarkus.waitForExit();
69+
}
70+
71+
private Measure extractPowerConsumption(long pid) {
72+
sensor.stop();
73+
// first read metadata
74+
final var metadata = sensor.metadata();
75+
Log.infof("Metadata:\n%s", metadata);
76+
// register pid
77+
final var registeredPID = sensor.register(pid);
78+
// re-open output file to read process info
79+
final var measure = sensor.update(0L);
80+
final var power = new TotalSyntheticComponent(metadata, SensorUnit.W, 0, 1, 2)
81+
.asMeasure(measure.getOrDefault(registeredPID).components());
82+
83+
Log.info("Measured power: " + power);
84+
Quarkus.asyncExit();
85+
86+
return power;
87+
}
88+
89+
private Process runPowermetrics() {
90+
Log.info("Starting powermetrics");
91+
try {
92+
powermetrics = new ProcessBuilder()
93+
.command("powermetrics", "--samplers",
94+
"cpu_power,tasks",
95+
"--show-process-samp-norm",
96+
"--show-process-gpu",
97+
"--show-usage-summary",
98+
"-i", "0") // no sampling frequency, just record aggregate
99+
.redirectOutput(output)
100+
.start();
101+
return powermetrics;
102+
} catch (IOException e) {
103+
throw new RuntimeException(e);
104+
}
105+
}
106+
107+
private long runCommandToMeasure() {
108+
Log.infof("Recording energy consumption for: %s", cmd);
26109
final var command = new ProcessBuilder().command(cmd);
27-
final var start = System.nanoTime();
28110
try {
29111
final var process = command.start();
30-
var duration = System.nanoTime() - start;
31-
System.out.println("start duration: " + duration);
32-
final var processTracker = measurer.startTrackingProcess(process);
33-
process.onExit().thenAccept(unused -> processTracker.cancel());
112+
process.waitFor();
113+
return process.pid();
34114
} catch (Exception e) {
35115
throw new RuntimeException(e);
36116
}

0 commit comments

Comments
 (0)