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: display color-coded method-level energy consumption #159

Merged
merged 6 commits into from
Feb 5, 2025
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
28 changes: 22 additions & 6 deletions deployment/src/main/resources/dev-ui/qwc-power-display.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@ class QwcPowerDisplay {
}
}

hilitePower(power) {
if (power) {
let level;
if (power < 0.5) {
level = 'contrast';
} else if (power < 1) {
level = 'default';
} else if (power < 5) {
level = 'success';
} else if (power < 10) {
level = 'warning';
} else {
level = 'error';
}
return html`<qui-badge level="${level}" primary>${power}</qui-badge>`;
}
}

name(name) {
return html`<qui-badge>${name}</qui-badge>`
}
Expand All @@ -42,19 +60,17 @@ class QwcPowerDisplay {
${this.name(name)} measures (called ${measureList.length} times)
</vaadin-details-summary>
<vaadin-vertical-layout theme="spacing-s">
<ul>
<ol>
${measureList.map(m => html`<li>${this.measure(m)}</li>`)}
</ul>
</ol>
</vaadin-vertical-layout>
</vaadin-details>`
}
}

measure(measure) {
if (measure) {
return html`
Started at: ${measure.date}, ran for ${measure.duration}ms on ${measure.threadName} thread (id: ${measure.threadId}) [thread %: ${measure.threadCpuShare} / jvm %: ${measure.jvmCpuShare}]
`
return html`${this.hilitePower(measure.power)}mW (${measure.durationMs}ms)`;
}
}

Expand All @@ -67,7 +83,7 @@ class QwcPowerDisplay {
</vaadin-details-summary>
<vaadin-vertical-layout theme="spacing-s">
<ul>
${measure.measures.map(m => html`<li>${m}</li>`)}
${measure.measures.map(m => html`<li>${this.hilitePower(m)}mW</li>`)}
</ul>
</vaadin-vertical-layout>
</vaadin-details>`
Expand Down
9 changes: 3 additions & 6 deletions deployment/src/main/resources/dev-ui/qwc-power-measure.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ export class QwcPowerMeasure extends QwcHotReloadElement {
jsonRpc = new JsonRpc(this);

static properties = {
_remoteMetadata: {state: true},
_localMetadata: {state: true},
_metadata: {state: true},
_status: {state: true},
_running: {state: true},
_measure: {state: true},
Expand All @@ -29,8 +28,7 @@ export class QwcPowerMeasure extends QwcHotReloadElement {
}

hotReload() {
this.jsonRpc.remoteMetadata().then(jsonRpcResponse => this._remoteMetadata = jsonRpcResponse.result);
this.jsonRpc.localMetadata().then(jsonRpcResponse => this._localMetadata = jsonRpcResponse.result);
this.jsonRpc.metadata().then(jsonRpcResponse => this._metadata = jsonRpcResponse.result);
this.jsonRpc.status().then(jsonRpcResponse => this._status = jsonRpcResponse.result);
this.jsonRpc.isRunning().then(response => this._running = response.result);
}
Expand All @@ -44,8 +42,7 @@ export class QwcPowerMeasure extends QwcHotReloadElement {
</vaadin-details-summary>
<vaadin-vertical-layout style="align-items: stretch;" theme="spacing-s padding-s">
${this.renderStartOrStop()}
${display.metadata(this._localMetadata, "Local synthetic components (if any)", "No ongoing measure")}
${display.metadata(this._remoteMetadata, "System power metadata", "Couldn't retrieve metadata")}
${display.metadata(this._metadata, "System power metadata", "Couldn't retrieve metadata")}
${display.displayMeasure(this._measure)}
</vaadin-vertical-layout>
</vaadin-details>`;
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<quarkus.version>3.18.1</quarkus.version>
<power-server.version>0.1.5</power-server.version>
<power-server.version>0.2.0</power-server.version>

<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.5.2</maven-surefire-plugin.version>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package net.laprun.sustainability.power.quarkus.runtime;

public record DisplayableMeasure(double total, double min, double max, double avg, double stdDev, double[] measures) {
import net.laprun.sustainability.power.measure.Timing;

public record DisplayableMeasure(double total, double min, double max, double avg, double stdDev, double[] measures, Timing timestamps) {
@Override
public String toString() {
return String.format("total: %s (min: %s / max: %s / avg: %s / σ: %s)", withUnit(total), withUnit(min), withUnit(max), withUnit(avg), withUnit(stdDev));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package net.laprun.sustainability.power.quarkus.runtime;

import java.time.Duration;
import java.util.List;
import java.util.Optional;

import net.laprun.sustainability.power.SensorMetadata;
import net.laprun.sustainability.power.SensorUnit;
import net.laprun.sustainability.power.measure.MeasureBackedCursor;
import net.laprun.sustainability.power.measure.OngoingPowerMeasure;

class Measure {
private OngoingPowerMeasure measure;
private DisplayableMeasure measured;
private TotalRecorder totalComp;
private boolean running;

List<SensorMetadata.ComponentMetadata> localMetadata() {
// todo: generify
return Optional.ofNullable(measure)
.map(m -> List.of(m.metadata().metadataFor(4)))
.orElse(List.of());
}

boolean isRunning() {
return running;
}

void startWith(SensorMetadata metadata) {
if (totalComp == null) {
// default total aggregation: all known components that W (or similar) as unit
final var integerIndices = metadata.components().values().stream()
.filter(cm -> SensorUnit.W.isCommensurableWith(cm.unit()))
.map(SensorMetadata.ComponentMetadata::index)
.toArray(Integer[]::new);
if (integerIndices.length > 0) {
final int[] totalIndices = new int[integerIndices.length];
for (int i = 0; i < totalIndices.length; i++) {
totalIndices[i] = integerIndices[i];
}
totalComp = new TotalRecorder(metadata, SensorUnit.mW, totalIndices);
}
} else {
totalComp.reset();
}

if(measure == null) {
// fixme: remove hardcoded sample period
measure = new OngoingPowerMeasure(metadata, 500, totalComp);
} else {
measure.reset();
}

measured = null;
running = true;
}

void recordMeasure(double[] components) {
measure.recordMeasure(components);
}

DisplayableMeasure stop() {
running = false;
final var stats = totalComp.statistics();
final var timing = measure.timingInfo();
measured = new DisplayableMeasure(stats.getSum(), stats.getMin(), stats.getMax(), stats.getMean(), stats.getStandardDeviation(), stats.getValues(), timing);
return measured;
}

MeasureBackedCursor cursorOver(long timestamp, Duration duration) {
final var timing = measured != null ? measured.timestamps() : measure.timingInfo();
return new MeasureBackedCursor(totalComp, timing.cursorOver(timestamp, duration));
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package net.laprun.sustainability.power.quarkus.runtime;

import jakarta.enterprise.context.ApplicationScoped;

import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class Measures {
private final Map<String, List<Measure>> measures = new HashMap<>();

public Measure add(String measureName, String threadName, long threadId, long startTime, long duration, double threadCpu, double jvmCpu) {
public Measure add(String measureName, String threadName, long threadId, long startTime, Duration duration, double threadCpu, double jvmCpu) {
final Measure measure = new Measure(threadName, threadId, startTime, duration, threadCpu, jvmCpu);
measures.computeIfAbsent(measureName, (unused) -> new LinkedList<>()).add(measure);
return measure;
Expand All @@ -22,7 +23,7 @@ public Map<String, List<Measure>> measures() {
return measures;
}

public record Measure(String threadName, long threadId, long startTime, long duration, double threadCpuShare, double jvmCpuShare) {
public record Measure(String threadName, long threadId, long startTime, Duration duration, double threadCpuShare, double jvmCpuShare) {
@SuppressWarnings("unused")
public String getDate() {
return new Date(startTime).toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@
public class Metadata<T> {
private final URI powerServerURI;
private final String status;
private final List<T> local;
private final List<T> remote;
private final Function<SensorMetadata.ComponentMetadata, T> converter;
private final String documentation;

public Metadata(URI powerServerURI, String documentation, List<SensorMetadata.ComponentMetadata> remote, List<SensorMetadata.ComponentMetadata> local, String status, Function<SensorMetadata.ComponentMetadata, T> converter) {
public Metadata(URI powerServerURI, String documentation, List<SensorMetadata.ComponentMetadata> remote, String status, Function<SensorMetadata.ComponentMetadata, T> converter) {
this.powerServerURI = powerServerURI;
this.converter = converter;
this.remote = converted(remote);
this.local = converted(local);
this.status = status;
this.documentation = documentation;
}
Expand All @@ -34,16 +32,11 @@ private List<T> converted(List<SensorMetadata.ComponentMetadata> metadata) {
@Override
public String toString() {
return "Connected to " + powerServerURI + " (status: " + status
+ ")\n====\nLocal metadata (including synthetic components, if any):\n"
+ (local.isEmpty() ? "No ongoing measure" : local)
+ "\n====\nSensor metadata:\n" + remote;
+ ")\n====\nMetadata (including synthetic components, if any):\n"
+ remote;
}

public List<T> local() {
return local;
}

public List<T> remote() {
public List<T> components() {
return remote;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package net.laprun.sustainability.power.quarkus.runtime;

import java.util.concurrent.TimeUnit;
import java.time.Duration;

import jakarta.annotation.Priority;
import jakarta.inject.Inject;
Expand Down Expand Up @@ -36,7 +36,7 @@ public Object aroundInvoke(InvocationContext ctx) throws Exception {
} finally {
final var duration = System.nanoTime() - startTime;
final var cpu = Platform.consumedThreadCpuTime(duration, startCPU, Platform.threadCpuTime(threadId));
measurer.recordMethodMeasure(measureAnn.name(), threadName, threadId, startTimeMs, TimeUnit.NANOSECONDS.toMillis(duration), cpu);
measurer.recordMethodMeasure(measureAnn.name(), threadName, threadId, startTimeMs, Duration.ofNanos(duration), cpu);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package net.laprun.sustainability.power.quarkus.runtime;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executors;
Expand Down Expand Up @@ -35,10 +37,19 @@ public ServerSampler sampler() {

public <T> Metadata<T> measureMetadata(Function<SensorMetadata.ComponentMetadata, T> converter) {
final var metadata = sampler.metadata();
final var local = sampler.localMetadata();
final var remote = metadata.map(sm -> {
var list = sm.components().values().stream().toList();
if (!local.isEmpty()) {
list = new ArrayList<>(list);
list.addAll(local);
}
return list;
})
.orElse(List.of());
return new Metadata<>(sampler.powerServerURI(),
metadata.map(SensorMetadata::documentation).orElse(null),
metadata.map(sm -> sm.components().values().stream().toList()).orElse(List.of()),
sampler.localMetadata(), sampler.status(), converter);
remote, sampler.status(), converter);
}

public PowerMeasurer withCompletedHandler(Consumer<DisplayableMeasure> completed) {
Expand Down Expand Up @@ -72,8 +83,8 @@ public Optional<DisplayableMeasure> stop() {
return isRunning() ? Optional.of(sampler.stop()) : Optional.empty();
}

public void recordMethodMeasure(String methodKey, String threadName, long threadId, long startTime, long duration,
double threadCPU) {
public void recordMethodMeasure(String methodKey, String threadName, long threadId, long startTime, Duration duration,
double threadCPU) {
if (!isRunning()) {
throw new IllegalStateException("Not running");
}
Expand Down
Loading