From db319aaa7d2a5f2085f562ed8884cacb8751b16b Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Mon, 20 Jan 2025 18:11:18 +0100 Subject: [PATCH] feat: start/stop measure from dev UI --- .../devui/commands/InfoCommand.java | 4 +- .../main/resources/dev-ui/qwc-power-info.js | 65 ++++++++++++++----- .../power/quarkus/runtime/Metadata.java | 41 ++++++++++-- .../power/quarkus/runtime/PowerMeasurer.java | 20 ++++-- .../power/quarkus/runtime/ServerSampler.java | 28 ++++++-- .../quarkus/runtime/devui/PowerService.java | 42 +++++++++--- 6 files changed, 158 insertions(+), 42 deletions(-) diff --git a/deployment/src/main/java/net/laprun/sustainability/power/quarkus/deployment/devui/commands/InfoCommand.java b/deployment/src/main/java/net/laprun/sustainability/power/quarkus/deployment/devui/commands/InfoCommand.java index c800f3c..91aec08 100644 --- a/deployment/src/main/java/net/laprun/sustainability/power/quarkus/deployment/devui/commands/InfoCommand.java +++ b/deployment/src/main/java/net/laprun/sustainability/power/quarkus/deployment/devui/commands/InfoCommand.java @@ -1,5 +1,7 @@ package net.laprun.sustainability.power.quarkus.deployment.devui.commands; +import java.util.function.Function; + import org.aesh.command.CommandDefinition; import org.aesh.command.CommandResult; import org.aesh.command.invocation.CommandInvocation; @@ -17,7 +19,7 @@ public InfoCommand(PowerMeasurer powerMeasurer) { @Override public CommandResult doExecute(CommandInvocation commandInvocation) { - commandInvocation.println(powerMeasurer.measureMetadata().toString()); + commandInvocation.println(powerMeasurer.measureMetadata(Function.identity()).toString()); return CommandResult.SUCCESS; } } diff --git a/deployment/src/main/resources/dev-ui/qwc-power-info.js b/deployment/src/main/resources/dev-ui/qwc-power-info.js index caf6ec8..37cd8d7 100644 --- a/deployment/src/main/resources/dev-ui/qwc-power-info.js +++ b/deployment/src/main/resources/dev-ui/qwc-power-info.js @@ -1,14 +1,11 @@ -import {QwcHotReloadElement, css, html} from 'qwc-hot-reload-element'; +import {html, QwcHotReloadElement} from 'qwc-hot-reload-element'; import {JsonRpc} from 'jsonrpc'; +import {notifier} from 'notifier'; import '@vaadin/details'; -import '@vaadin/list-box'; -import '@vaadin/item'; import '@vaadin/horizontal-layout'; import '@vaadin/vertical-layout'; import '@vaadin/icon'; import '@vaadin/vaadin-lumo-styles/vaadin-iconset.js' -import '@vaadin/form-layout'; -import '@vaadin/text-field'; import 'qui-badge'; export class QwcPowerInfo extends QwcHotReloadElement { @@ -18,7 +15,8 @@ export class QwcPowerInfo extends QwcHotReloadElement { static properties = { _remoteMetadata: {state: true}, _localMetadata: {state: true}, - _status: {state: true} + _status: {state: true}, + _running: {state: true} }; constructor() { @@ -34,6 +32,7 @@ export class QwcPowerInfo extends QwcHotReloadElement { this.jsonRpc.remoteMetadata().then(jsonRpcResponse => this._remoteMetadata = jsonRpcResponse.result); this.jsonRpc.localMetadata().then(jsonRpcResponse => this._localMetadata = jsonRpcResponse.result); this.jsonRpc.status().then(jsonRpcResponse => this._status = jsonRpcResponse.result); + this.jsonRpc.isRunning().then(response => this._running = response.result); } render() { @@ -44,8 +43,9 @@ export class QwcPowerInfo extends QwcHotReloadElement { Power metadata: ${clazz(this._status)} - ${this.metadata(this._localMetadata, "Local metadata, including synthetic components")} - ${this.metadata(this._remoteMetadata, "System power metadata")} + ${this.renderStartOrStop()} + ${this.metadata(this._localMetadata, "Local synthetic components (if any)", "No ongoing measure")} + ${this.metadata(this._remoteMetadata, "System power metadata", "Couldn't retrieve metadata")} `; @@ -54,20 +54,55 @@ export class QwcPowerInfo extends QwcHotReloadElement { } } - metadata(metadata, title) { + renderStartOrStop() { + let iconType = this._running ? "stop" : "play"; + let label = this._running ? "Stop" : "Start"; + return html` + + + ${label} + ` + } + + metadata(metadata, title, emptyMsg) { return html` - - ${title} - + ${title} -
-                       ${metadata}
-                   
+ ${this._metadata(metadata, emptyMsg)}
` } + _metadata(metadata, emptyMsg) { + if (Object.keys(metadata).length !== 0) { + return html``; + } else { + return html`${emptyMsg}`; + } + } + + component(component) { + return html`${this.name(component.name)} (index: ${component.index}, unit: ${component.unit}): ${component.description}`; + } + + name(name) { + return html`${name}` + } + + _startOrStop() { + let action = this._running ? "stop" : "start"; + this.jsonRpc.startOrStop({start: !this._running}).then(jsonRpcResponse => { + let outcome = jsonRpcResponse.result; + if (!outcome) { + notifier.showErrorMessage("Couldn't " + action + " power measure"); + } else { + this.hotReload(); + // keep the notification open indefinitely if we're stopped to be able to see the results + notifier.showInfoMessage(outcome, "bottom-start", action === "stop" ? 15 : 5); + } + }); + } } function clazz(msg, isPill) { diff --git a/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/Metadata.java b/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/Metadata.java index 89faf0e..9d0762a 100644 --- a/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/Metadata.java +++ b/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/Metadata.java @@ -1,20 +1,49 @@ package net.laprun.sustainability.power.quarkus.runtime; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +import java.net.URI; +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; + import net.laprun.sustainability.power.SensorMetadata; -import java.net.URI; -import java.util.Optional; +public class Metadata { + private final URI powerServerURI; + private final String status; + private final List local; + private final List remote; + private final Function converter; + private final String documentation; -public record Metadata(URI powerServerURI, Optional remote, Optional local, String status) { + public Metadata(URI powerServerURI, String documentation, List remote, List local, String status, Function converter) { + this.powerServerURI = powerServerURI; + this.converter = converter; + this.remote = converted(remote); + this.local = converted(local); + this.status = status; + this.documentation = documentation; + } + private List converted(List metadata) { + return metadata.stream() + .sorted(Comparator.comparing(SensorMetadata.ComponentMetadata::index)) + .map(converter) + .toList(); + } @Override public String toString() { return "Connected to " + powerServerURI + " (status: " + status + ")\n====\nLocal metadata (including synthetic components, if any):\n" - + Optional.ofNullable(local).map(Object::toString).orElse("No ongoing measure") + + (local.isEmpty() ? "No ongoing measure" : local) + "\n====\nSensor metadata:\n" + remote; } + + public List local() { + return local; + } + + public List remote() { + return remote; + } } diff --git a/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/PowerMeasurer.java b/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/PowerMeasurer.java index 9dd4a96..35440d2 100644 --- a/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/PowerMeasurer.java +++ b/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/PowerMeasurer.java @@ -1,13 +1,17 @@ package net.laprun.sustainability.power.quarkus.runtime; import java.lang.management.ManagementFactory; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Function; import com.sun.management.OperatingSystemMXBean; +import net.laprun.sustainability.power.SensorMetadata; + public class PowerMeasurer { private static final OperatingSystemMXBean osBean; @@ -36,8 +40,12 @@ public ServerSampler sampler() { return sampler; } - public Metadata measureMetadata() { - return new Metadata(sampler.powerServerURI(), sampler.metadata(), sampler.localMetadata(), sampler.status()); + public Metadata measureMetadata(Function converter) { + final var metadata = sampler.metadata(); + 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); } @SuppressWarnings("unused") @@ -65,10 +73,12 @@ public boolean isRunning() { } public void start(long durationInSeconds, long frequencyInMilliseconds) { - sampler.start(durationInSeconds, frequencyInMilliseconds); + if (!isRunning()) { + sampler.start(durationInSeconds, frequencyInMilliseconds); - if (durationInSeconds > 0) { - executor.schedule(this::stop, durationInSeconds, TimeUnit.SECONDS); + if (durationInSeconds > 0) { + executor.schedule(this::stop, durationInSeconds, TimeUnit.SECONDS); + } } } diff --git a/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/ServerSampler.java b/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/ServerSampler.java index 49b6000..0b15cfe 100644 --- a/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/ServerSampler.java +++ b/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/ServerSampler.java @@ -3,10 +3,11 @@ import java.net.ConnectException; import java.net.URI; import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Consumer; -import io.quarkus.logging.Log; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.WebTarget; @@ -14,17 +15,18 @@ import jakarta.ws.rs.sse.InboundSseEvent; import jakarta.ws.rs.sse.SseEventSource; -import net.laprun.sustainability.power.SensorUnit; -import net.laprun.sustainability.power.analysis.DescriptiveStatisticsComponentProcessor; -import net.laprun.sustainability.power.analysis.total.TotalSyntheticComponent; import org.eclipse.microprofile.config.inject.ConfigProperty; import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.logging.Log; import io.quarkus.rest.client.reactive.jackson.runtime.serialisers.ClientJacksonMessageBodyReader; import io.vertx.core.http.HttpClosedException; import net.laprun.sustainability.power.SensorMeasure; import net.laprun.sustainability.power.SensorMetadata; +import net.laprun.sustainability.power.SensorUnit; +import net.laprun.sustainability.power.analysis.DescriptiveStatisticsComponentProcessor; +import net.laprun.sustainability.power.analysis.total.TotalSyntheticComponent; import net.laprun.sustainability.power.measure.OngoingPowerMeasure; import net.laprun.sustainability.power.measure.StoppedPowerMeasure; @@ -146,8 +148,22 @@ URI powerServerURI() { return powerServerURI; } - Optional localMetadata() { - return Optional.ofNullable(measure).map(OngoingPowerMeasure::metadata); + /** + * Only returns local synthetic components, if any + * @return + */ + List localMetadata() { + if (metadata == null) { + return List.of(); + } + return Optional.ofNullable(measure).map(m -> { + // remove server metadata entries + return m.metadata().components().entrySet().stream() + .filter((e) -> !metadata.exists(e.getKey())) + .map(Map.Entry::getValue) + .toList(); + + }).orElse(List.of()); } public String status() { diff --git a/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/devui/PowerService.java b/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/devui/PowerService.java index 27cfd72..59a9242 100644 --- a/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/devui/PowerService.java +++ b/runtime/src/main/java/net/laprun/sustainability/power/quarkus/runtime/devui/PowerService.java @@ -1,26 +1,50 @@ package net.laprun.sustainability.power.quarkus.runtime.devui; +import java.util.List; +import java.util.function.Function; + +import jakarta.annotation.PostConstruct; import jakarta.inject.Inject; +import net.laprun.sustainability.power.SensorMetadata; import net.laprun.sustainability.power.quarkus.runtime.PowerMeasurer; public class PowerService { + public static final Function converter = cm -> new ComponentMetadata(cm.name(), cm.index(), cm.description(), cm.unitAsSymbol()); @Inject PowerMeasurer measurer; + private String status; + + @PostConstruct + public void init() { + measurer.withCompletedHandler((stoppedMeasure) -> status = stoppedMeasure.toString()); + } + + public boolean isRunning() { + return measurer.isRunning(); + } public String status() { - return measurer.measureMetadata().status(); + return measurer.sampler().status(); + } + + public List remoteMetadata() { + return measurer.measureMetadata(converter).remote(); } - public String remoteMetadata() { - return measurer.measureMetadata().remote() - .map(Object::toString) - .orElse("Could not get remote metadata"); + public List localMetadata() { + return measurer.measureMetadata(converter).local(); } - public String localMetadata() { - return measurer.measureMetadata().local() - .map(Object::toString) - .orElse("No ongoing measure"); + public record ComponentMetadata(String name, int index, String description, String unit) {} + + public String startOrStop(boolean start) { + if(start) { + measurer.start(0, 500); + return "Started"; + } else { + measurer.stop(); + return "Stopped: " + status; + } } }