Skip to content

Commit

Permalink
feat: add initial display of method-associated records
Browse files Browse the repository at this point in the history
This currently doesn't include power consumption because most of the
time the measure needed when recording is not yet available so this
will need post-processing and send the power information later.
  • Loading branch information
metacosm committed Jan 30, 2025
1 parent b87bd4d commit 7d05d64
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 50 deletions.
28 changes: 27 additions & 1 deletion deployment/src/main/resources/dev-ui/qwc-power-display.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,37 @@ class QwcPowerDisplay {

measures(measures) {
if (measures) {
return measures.map(measure => this.measure(measure));
return html`${measures.map(measure => this.measureList(measure.name, measure.measures))}`
} else {
return html`No measures`;
}
}

measureList(name, measureList) {
if (measureList) {
return html`
<vaadin-details theme="filled">
<vaadin-details-summary slot="summary">
${this.name(name)} measures (called ${measureList.length} times)
</vaadin-details-summary>
<vaadin-vertical-layout theme="spacing-s">
<ul>
${measureList.map(m => html`<li>${this.measure(m)}</li>`)}
</ul>
</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}]
`
}
}

displayMeasure(measure) {
if (measure) {
return html`
<vaadin-details theme="filled">
Expand Down
6 changes: 1 addition & 5 deletions deployment/src/main/resources/dev-ui/qwc-power-measure.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,14 @@ export class QwcPowerMeasure extends QwcHotReloadElement {
${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.measure(this._measure)}
${display.displayMeasure(this._measure)}
</vaadin-vertical-layout>
</vaadin-details>`;
} else {
return html`Info unavailable`;
}
}

measures() {
return html``
}

renderStartOrStop() {
let iconType = this._running ? "stop" : "play";
let label = this._running ? "Stop" : "Start";
Expand Down
29 changes: 0 additions & 29 deletions deployment/src/main/resources/dev-ui/qwc-power-measures.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {html, QwcHotReloadElement} from 'qwc-hot-reload-element';
import {JsonRpc} from 'jsonrpc';
import {notifier} from 'notifier';
import {display} from './qwc-power-display.js';
import '@vaadin/details';
import '@vaadin/vertical-layout';
Expand Down Expand Up @@ -43,34 +42,6 @@ export class QwcPowerMeasures extends QwcHotReloadElement {
return html`No recorded measures`;
}
}

measures() {
return html`something`
}

renderStartOrStop() {
let iconType = this._running ? "stop" : "play";
let label = this._running ? "Stop" : "Start";
return html`
<vaadin-button style="width: 10%;" theme="secondary" @click="${this._startOrStop}">
<vaadin-icon icon="font-awesome-solid:${iconType}" slot="prefix"></vaadin-icon>
${label}
</vaadin-button>`
}

_startOrStop() {
let stop = this._running;
this.jsonRpc.startOrStop({start: !stop}).then(jsonRpcResponse => {
let msg = "Started";
if (stop) {
this._measure = jsonRpcResponse.result;
msg = "Stopped (" + this._measure.samplesCount + " samples taken)";
}

this.hotReload();
notifier.showInfoMessage(msg);
});
}
}

customElements.define('qwc-power-measures', QwcPowerMeasures);
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@

import jakarta.enterprise.context.ApplicationScoped;

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

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

public void add(DisplayableMeasure measure, long duration, String name) {
measures.put(name, measure);
public Measure add(String measureName, String threadName, long threadId, long startTime, long 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;
}

public Map<String, DisplayableMeasure> measures() {
public Map<String, List<Measure>> measures() {
return measures;
}

public record Measure(String threadName, long threadId, long startTime, long duration, double threadCpuShare, double jvmCpuShare) {
@SuppressWarnings("unused")
public String getDate() {
return new Date(startTime).toString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,13 @@ public static double cpuShareOfJVMProcess() {
final var cpuLoad = os.getCpuLoad();
return (processCpuLoad < 0 || cpuLoad <= 0) ? 0 : processCpuLoad / cpuLoad;
}

public static double threadCpuTime(long threadId) {
// todo: check if available
return threads.getThreadCpuTime(threadId);
}

public static double consumedThreadCpuTime(long overDurationNanos, double startThreadTime, double endThreadTime) {
return (endThreadTime - startThreadTime) / overDurationNanos;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package net.laprun.sustainability.power.quarkus.runtime;

import java.util.concurrent.TimeUnit;

import jakarta.annotation.Priority;
import jakarta.inject.Inject;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;

import net.laprun.sustainability.power.quarkus.annotations.PowerMeasure;

@PowerMeasure(name = "net.laprun.sustainability.power.interceptor")
Expand All @@ -14,24 +17,26 @@ public class PowerMeasuredInterceptor {
@Inject
PowerMeasurer measurer;

@Inject
Measures measures;

@AroundInvoke
@SuppressWarnings("unused")
public Object aroundInvoke(InvocationContext ctx) throws Exception {
final var measureAnn = ctx.getInterceptorBinding(PowerMeasure.class);
if(measureAnn == null) {
if(measureAnn == null || !measurer.isRunning()) {
return ctx.proceed();
}

final var startTimeMs = System.currentTimeMillis();
final var startTime = System.nanoTime();
final var thread = Thread.currentThread();
final var threadName = thread.getName();
final var threadId = thread.threadId();
final var startCPU = Platform.threadCpuTime(threadId);
try {
measurer.start(0, 100);
return ctx.proceed();
} finally {
final var duration = System.nanoTime() - startTime;
final var stop = measurer.stop();
stop.ifPresent(measure -> measures.add(measure, duration, measureAnn.name()));
final var cpu = Platform.consumedThreadCpuTime(duration, startCPU, Platform.threadCpuTime(threadId));
measurer.recordMethodMeasure(measureAnn.name(), threadName, threadId, startTimeMs, TimeUnit.NANOSECONDS.toMillis(duration), cpu);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@
import java.util.function.Consumer;
import java.util.function.Function;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import net.laprun.sustainability.power.SensorMetadata;

@ApplicationScoped
public class PowerMeasurer {
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private final ServerSampler sampler;
@Inject
Measures measures;

public PowerMeasurer() {
this(new ServerSampler());
Expand Down Expand Up @@ -65,4 +71,13 @@ public void start(long durationInSeconds, long frequencyInMilliseconds) {
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) {
if (!isRunning()) {
throw new IllegalStateException("Not running");
}

measures.add(methodKey, threadName, threadId, startTime, duration, threadCPU, Platform.cpuShareOfJVMProcess());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ public void stopOnError(Throwable e) {
if (!(e instanceof HttpClosedException) && errorHandler != null) {
errorHandler.accept(e);
}
status = "error: measure failed (" + e.getMessage() + ")";
synchronized (this) {
status = "error: measure failed (" + e.getMessage() + ")";
if (measure != null && measure.numberOfSamples() > 0) {
stop();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package net.laprun.sustainability.power.quarkus.runtime.devui;

import java.util.List;
import java.util.Map;
import java.util.function.Function;

import jakarta.inject.Inject;
Expand All @@ -11,6 +10,7 @@
import net.laprun.sustainability.power.quarkus.runtime.Measures;
import net.laprun.sustainability.power.quarkus.runtime.PowerMeasurer;

@SuppressWarnings("unused")
public class PowerService {
public static final Function<SensorMetadata.ComponentMetadata, ComponentMetadata> converter = cm -> new ComponentMetadata(cm.name(), cm.index(), cm.description(), cm.unitAsSymbol());
@Inject
Expand All @@ -35,8 +35,18 @@ public List<ComponentMetadata> localMetadata() {
return measurer.measureMetadata(converter).local();
}

public Map<String, DisplayableMeasure> measures() {
return measures.measures();
public List<DisplayMeasure> measures() {
return measures.measures().entrySet().stream()
.map((e) -> new DisplayMeasure(e.getKey(), e.getValue()))
.sorted()
.toList();
}

public record DisplayMeasure(String name, List<Measures.Measure> measures) implements Comparable<DisplayMeasure> {
@Override
public int compareTo(DisplayMeasure o) {
return name.compareTo(o.name);
}
}

public record ComponentMetadata(String name, int index, String description, String unit) {}
Expand Down

0 comments on commit 7d05d64

Please sign in to comment.