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: alternate JMX/powermetrics hybrid sensor #6

Merged
merged 4 commits into from
Oct 27, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import org.aesh.command.invocation.CommandInvocation;
import org.aesh.command.option.Option;

import io.quarkiverse.power.runtime.MacOSPowermetricsSensor;
import io.quarkiverse.power.runtime.PowerSensor;
import io.quarkiverse.power.runtime.sensors.macos.powermetrics.MacOSPowermetricsSensor;

@CommandDefinition(name = "start", description = "Starts measuring power consumption of the current application", generateHelp = true)
@SuppressWarnings("rawtypes")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import org.aesh.command.CommandResult;
import org.aesh.command.invocation.CommandInvocation;

import io.quarkiverse.power.runtime.MacOSPowermetricsSensor;
import io.quarkiverse.power.runtime.PowerSensor;
import io.quarkiverse.power.runtime.sensors.macos.powermetrics.MacOSPowermetricsSensor;

@CommandDefinition(name = "stop", description = "Stops power measurement and outputs accumulated power since measures were started", generateHelp = true)
@SuppressWarnings("rawtypes")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,13 @@ interface Measure {

long measureDuration();
}

interface IncrementableMeasure extends Measure {

void addCPU(double v);

void addGPU(double v);

void incrementSamples();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Singleton;

import io.quarkiverse.power.runtime.sensors.macos.powermetrics.MacOSPowermetricsSensor;

@Singleton
public class PowerSensorProducer {
@Produces
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.quarkiverse.power.runtime;
package io.quarkiverse.power.runtime.sensors.macos;

import java.util.Optional;

public class AppleSiliconMeasure implements PowerSensor.Measure {
import io.quarkiverse.power.runtime.PowerSensor;

public class AppleSiliconMeasure implements PowerSensor.IncrementableMeasure {
private double cpu;
private double gpu;
private double ane;
Expand Down Expand Up @@ -37,15 +39,15 @@ public double total() {
return cpu + gpu + ane;
}

void addCPU(double v) {
public void addCPU(double v) {
cpu += v;
}

void addGPU(double v) {
public void addGPU(double v) {
gpu += v;
}

void addANE(double v) {
public void addANE(double v) {
ane += v;
}

Expand All @@ -54,7 +56,7 @@ public int numberOfSamples() {
return samplesNb;
}

void incrementSamples() {
public void incrementSamples() {
samplesNb++;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package io.quarkiverse.power.runtime.sensors.macos.jmx;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import com.sun.management.OperatingSystemMXBean;

import io.quarkiverse.power.runtime.PowerSensor;
import io.quarkiverse.power.runtime.sensors.macos.AppleSiliconMeasure;

public class JMXCPUSensor implements PowerSensor<AppleSiliconMeasure> {
private static final OperatingSystemMXBean osBean;
static {
osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
}

public static PowerSensor<AppleSiliconMeasure> instance = new JMXCPUSensor();

private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

private Process powermetrics;
private ScheduledFuture<?> powermetricsSchedule;

private boolean running;
private AppleSiliconMeasure accumulatedPower = new AppleSiliconMeasure();

@Override
public void start(long duration, long frequency, Writer out) throws IOException, Exception {
if (!running) {
final var freq = Long.toString(Math.round(frequency));
powermetrics = Runtime.getRuntime().exec("sudo powermetrics --samplers cpu_power -i " + freq);

accumulatedPower = new AppleSiliconMeasure();
running = true;

if (duration > 0) {
executor.schedule(() -> stop(out), duration, TimeUnit.SECONDS);
}

powermetricsSchedule = executor.scheduleAtFixedRate(() -> extractPowerMeasure(powermetrics.getInputStream()),
0, frequency,
TimeUnit.MILLISECONDS);
}
}

void extractPowerMeasure(InputStream powerMeasureInput) {
try {
// Should not be closed since it closes the process
BufferedReader input = new BufferedReader(new InputStreamReader(powerMeasureInput));
String line;
while ((line = input.readLine()) != null) {
if (line.isEmpty() || line.startsWith("*")) {
continue;
}

// look for line that contains CPU power measure
if (line.startsWith("CPU Power")) {
final var processCpuLoad = osBean.getProcessCpuLoad();
final var cpuLoad = osBean.getCpuLoad();
if (processCpuLoad < 0 || cpuLoad <= 0) {
break;
}
final var cpuShare = processCpuLoad / cpuLoad;
accumulatedPower.addCPU(extractAttributedMeasure(line, cpuShare));
break;
}
}

accumulatedPower.incrementSamples();
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}

private static double extractAttributedMeasure(String line, double attributionRatio) {
final var powerValue = line.split(":")[1];
final var powerInMilliwatts = powerValue.split("m")[0];
return Double.parseDouble(powerInMilliwatts) * attributionRatio;
}

@Override
public AppleSiliconMeasure stop() {
if (running) {
powermetrics.destroy();
powermetricsSchedule.cancel(true);
}
running = false;
return accumulatedPower;
}

@Override
public void outputConsumptionSinceStarted(Writer out) {
out = out == null ? System.out::println : out;
out.println("Consumed " + accumulatedPower.total() + " mW over " + (accumulatedPower.measureDuration() / 1000)
+ " seconds");
}

private void stop(Writer out) {
stop();
outputConsumptionSinceStarted(out);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.quarkiverse.power.runtime;
package io.quarkiverse.power.runtime.sensors.macos.powermetrics;

import java.io.*;
import java.util.concurrent.*;

import io.quarkiverse.power.runtime.PowerSensor;
import io.quarkiverse.power.runtime.sensors.macos.AppleSiliconMeasure;

public class MacOSPowermetricsSensor implements PowerSensor<AppleSiliconMeasure> {
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkiverse.power.runtime;
package io.quarkiverse.power.runtime.sensors.macos.powermetrics;

import static org.junit.jupiter.api.Assertions.assertEquals;

Expand Down