Skip to content

Commit

Permalink
feat: start/stop measure from dev UI
Browse files Browse the repository at this point in the history
  • Loading branch information
metacosm committed Jan 20, 2025
1 parent 23f4d3f commit db319aa
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
}
65 changes: 50 additions & 15 deletions deployment/src/main/resources/dev-ui/qwc-power-info.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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() {
Expand All @@ -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() {
Expand All @@ -44,8 +43,9 @@ export class QwcPowerInfo extends QwcHotReloadElement {
<qui-badge level="contrast">Power metadata: ${clazz(this._status)}</qui-badge>
</vaadin-details-summary>
<vaadin-vertical-layout style="align-items: stretch;" theme="spacing-s padding-s">
${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")}
</vaadin-vertical-layout>
</vaadin-details>
`;
Expand All @@ -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`
<vaadin-button style="width: 10%;" theme="secondary" @click="${this._startOrStop}">
<vaadin-icon icon="font-awesome-solid:${iconType}" slot="prefix"></vaadin-icon>
${label}
</vaadin-button>`
}

metadata(metadata, title, emptyMsg) {
return html`
<vaadin-details theme="filled">
<vaadin-details-summary slot="summary">
${title}
</vaadin-details-summary>
<vaadin-details-summary slot="summary">${title}</vaadin-details-summary>
<vaadin-vertical-layout theme="spacing-s">
<pre>
${metadata}
</pre>
${this._metadata(metadata, emptyMsg)}
</vaadin-vertical-layout>
</vaadin-details>`
}

_metadata(metadata, emptyMsg) {
if (Object.keys(metadata).length !== 0) {
return html`<ul>${metadata.map(component => html`<li>${this.component(component)}</li>`)}</ul>`;
} else {
return html`${emptyMsg}`;
}
}

component(component) {
return html`${this.name(component.name)} (index: ${component.index}, unit: ${component.unit}): ${component.description}`;
}

name(name) {
return html`<qui-badge>${name}</qui-badge>`
}

_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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<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 record Metadata(URI powerServerURI, Optional<SensorMetadata> remote, Optional<SensorMetadata> local, String status) {
public Metadata(URI powerServerURI, String documentation, List<SensorMetadata.ComponentMetadata> remote, List<SensorMetadata.ComponentMetadata> local, 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;
}

private List<T> converted(List<SensorMetadata.ComponentMetadata> 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<T> local() {
return local;
}

public List<T> remote() {
return remote;
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -36,8 +40,12 @@ public ServerSampler sampler() {
return sampler;
}

public Metadata measureMetadata() {
return new Metadata(sampler.powerServerURI(), sampler.metadata(), sampler.localMetadata(), sampler.status());
public <T> Metadata<T> measureMetadata(Function<SensorMetadata.ComponentMetadata, T> 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")
Expand Down Expand Up @@ -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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,30 @@
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;
import jakarta.ws.rs.core.MediaType;
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;

Expand Down Expand Up @@ -146,8 +148,22 @@ URI powerServerURI() {
return powerServerURI;
}

Optional<SensorMetadata> localMetadata() {
return Optional.ofNullable(measure).map(OngoingPowerMeasure::metadata);
/**
* Only returns local synthetic components, if any
* @return
*/
List<SensorMetadata.ComponentMetadata> 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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SensorMetadata.ComponentMetadata, ComponentMetadata> 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<ComponentMetadata> remoteMetadata() {
return measurer.measureMetadata(converter).remote();
}

public String remoteMetadata() {
return measurer.measureMetadata().remote()
.map(Object::toString)
.orElse("Could not get remote metadata");
public List<ComponentMetadata> 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;
}
}
}

0 comments on commit db319aa

Please sign in to comment.