Skip to content

Cleanup UI #59

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

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
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
15 changes: 12 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,20 @@ down:
repo-clean:
rm -rf ~/.m2/repository/com/studiomediatech/query-response-spring-amqp

.PHONY: demo run-demo
##
## Demo targets
##
## These targets are used to run the demo application and its components.
## They depend on the `install` target to ensure that the application is built
## and ready to run. The up target starts the necessary Docker containers,
## and the run-demo target orchestrates the execution of the demo components.
## Please note that the parallel execution of the demo components is what makes
## it possible to start the individual components in the background.
##
.PHONY: demo run-demo run-demo run-query run-response run-ui
demo: install up
${MAKE} -j3 run-demo

.PHONY: run-demo run-query run-response run-ui
run-demo: run-ui run-query run-response

run-query:
Expand All @@ -59,4 +68,4 @@ run-response:
${MAKE} -C examples/responding/

run-ui:
${MAKE} -C ui/
${MAKE} demo -C ui/
13 changes: 7 additions & 6 deletions docs/events.adoc
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
= Query/Response Events

== Metrics or "Stats"
== Telemetry

Participating nodes in a Query/Response topology will broadcast events of
structured statistics for the UI and monitoring applications to consume. The
Participating nodes in a Query/Response network topology will broadcast events
with structured telemetry for the UI and monitoring applications to consume. The
common data-shape encapsulates a way to communicate measurements, events and
node-identifying and describing meta-data.
node-identifying and describing meta data.

The entry structure and format is encoded as a JSON UTF-8 encoded string.
The entry structure and format is encoded as a JSON UTF-8 encoded string
payload.

```json
{
Expand All @@ -33,7 +34,7 @@ means that in practice there are operational metrics which may _come and go_
as well as logical statistics which may pertain to the topology or cluster of
nodes.

=== Table of published statistics
=== Table of published telemetry

[cols="2,2,1,1,3"]
|===
Expand Down
4 changes: 2 additions & 2 deletions ui-frontend/src/ws.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ const connectSocket = () => {
};

sock.onmessage = (msg) => {
// console.log("RECEIVED EVENT", msg);
// console.log("RECEIVED EVENT PAYLOAD", JSON.stringify(JSON.parse(msg.data)));
//console.log("RECEIVED EVENT", msg);
console.log("RECEIVED EVENT PAYLOAD", JSON.stringify(JSON.parse(msg.data)));
listeners.forEach((listener) => listener(msg));
};
} catch (err) {
Expand Down
8 changes: 5 additions & 3 deletions ui/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
JAVA_HOME=$(shell unset JAVA_HOME; /usr/libexec/java_home -v 21)
MVN := ../mvnw

.PHONY: run build test verify v clean tidy
.PHONY: run demo build test verify v clean tidy

run:
${MVN} spring-boot:run
Expand All @@ -10,14 +10,16 @@ test verify v:
${MVN} clean verify

build:
${MVN} clean prepare-package -DskipTests
${MVN} clean prepare-package
mkdir -p target/classes/static
@$(MAKE) -C ../ui-frontend build
cp -r ../ui-frontend/dist/* target/classes/static/
${MVN} package -DskipTests
${MVN} package

clean:
rm -rf target/

tidy:
${MVN} formatter:format

demo: build run
29 changes: 28 additions & 1 deletion ui/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@

<build>
<finalName>${project.artifactId}</finalName>

<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>

<plugins>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
Expand All @@ -81,7 +97,18 @@
</goals>
</execution>
</executions>
</plugin>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<includes>
<include>**/*IT.java</include>
</includes>
</configuration>
</plugin>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
Expand Down
94 changes: 11 additions & 83 deletions ui/src/main/java/com/studiomediatech/QueryPublisher.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package com.studiomediatech;

import java.io.IOException;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
Expand All @@ -24,24 +22,23 @@
import org.springframework.context.event.EventListener;
import org.springframework.util.StringUtils;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.studiomediatech.events.QueryRecordedEvent;
import com.studiomediatech.queryresponse.QueryBuilder;
import com.studiomediatech.queryresponse.ui.QueryResponseUIApp;
import com.studiomediatech.queryresponse.ui.api.RestApiAdapter;
import com.studiomediatech.queryresponse.ui.api.WebSocketApiHandler;
import com.studiomediatech.queryresponse.ui.api.WebSocketApiHandlerPort;
import com.studiomediatech.queryresponse.ui.app.QueryResponseUIApp;
import com.studiomediatech.queryresponse.ui.app.adapter.RestApiAdapter;
import com.studiomediatech.queryresponse.ui.messaging.Stat;
import com.studiomediatech.queryresponse.ui.messaging.Stats;
import com.studiomediatech.queryresponse.util.Loggable;

public class QueryPublisher implements Loggable, RestApiAdapter {

private static final ObjectMapper MAPPER = new ObjectMapper();

// This is a Fib!
private static final int MAX_SIZE = 2584;
private static final int SLIDING_WINDOW = 40;
private static final int DEFAULT_QUERY_TIMEOUT = 1500;

private static final ObjectMapper MAPPER = new ObjectMapper();

static ToLongFunction<Stat> statToLong = s -> ((Number) s.value()).longValue();

Expand All @@ -53,67 +50,16 @@ public class QueryPublisher implements Loggable, RestApiAdapter {
private List<Double> tps = new LinkedList<>();

private final QueryBuilder queryBuilder;
private final WebSocketApiHandler handler;
private final WebSocketApiHandlerPort handler;

private final Map<String, Instant> nodes = new ConcurrentHashMap<>();

public QueryPublisher(WebSocketApiHandler handler, QueryBuilder queryBuilder) {
public QueryPublisher(WebSocketApiHandlerPort handler, QueryBuilder queryBuilder) {

this.handler = handler;
this.queryBuilder = queryBuilder;
}

@Override
public Map<String, Object> query(String q, int timeout, int limit) {

List<Object> defaults = List.of("No responses");

final Collection<Object> responses;

long start = System.nanoTime();

if (q.contains(" ")) {
responses = queryParsed(q, defaults);
} else {
responses = queryStrict(q, timeout, limit, defaults);
}

return Map.of("response", responses, "duration", Duration.ofNanos(System.nanoTime() - start));
}

private Collection<Object> queryParsed(String q, List<Object> defaults) {

var query = QueryRecordedEvent.valueOf(q, "none");

query.getQuery();
query.getTimeout();

Optional<Integer> maybe = query.getLimit();

if (maybe.isPresent()) {

return queryBuilder.queryFor(query.getQuery(), Object.class).waitingFor(query.getTimeout())
.takingAtMost(maybe.get()).orDefaults(defaults);

}

return queryBuilder.queryFor(query.getQuery(), Object.class).waitingFor(query.getTimeout())
.orDefaults(defaults);

}

private Collection<Object> queryStrict(String q, int timeout, int limit, List<Object> defaults) {
long queryTimeout = timeout > 0 ? timeout : DEFAULT_QUERY_TIMEOUT;

if (limit > 0) {
return queryBuilder.queryFor(q, Object.class).waitingFor(queryTimeout).takingAtMost(limit)
.orDefaults(defaults);
}

return queryBuilder.queryFor(q, Object.class).waitingFor(queryTimeout).orDefaults(defaults);

}

@EventListener
void on(QueryRecordedEvent event) {

Expand Down Expand Up @@ -142,7 +88,7 @@ void on(QueryRecordedEvent event) {
void onQueryResponseStats(Message message) {

try {
handle(MAPPER.readValue(message.getBody(), Stats.class).elements);
handle(MAPPER.readValue(message.getBody(), Stats.class).elements());
} catch (RuntimeException | IOException ex) {
logger().error("Failed to consumed stats", ex);
}
Expand All @@ -158,17 +104,13 @@ protected void handle(Collection<Stat> stats) {
handleNodes(stats);
}

@Override
public Map<String, Object> nodes() {
return Map.of("nodes", this.nodes, "timestamp", Instant.now(Clock.systemUTC()));
}

private void handleNodes(Collection<Stat> stats) {

Map<String, List<Stat>> nodes = stats.stream().filter(stat -> StringUtils.hasText(stat.uuid()))
.collect(Collectors.groupingBy(stat -> stat.uuid()));

for (Entry<String, List<Stat>> node : nodes.entrySet()) {

String uuid = node.getKey();

this.nodes.put(uuid, Instant.now());
Expand Down Expand Up @@ -332,18 +274,4 @@ private double calculateThroughput(String key, Collection<Stat> source, List<Sta
return Math.round((sum / duration) * 1000000.0) / 1000000.0;
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class Stats {

@JsonProperty
public Collection<Stat> elements;

@Override
public String toString() {

return Optional.ofNullable(elements).orElse(Collections.emptyList()).stream().map(Object::toString)
.collect(Collectors.joining(", "));
}
}

}
Loading