Skip to content
Permalink
Browse files
GERONIMO-6673 minimal ui for metrics and opentracing
  • Loading branch information
rmannibucau committed Jan 7, 2019
1 parent 1517dee commit 6f1bdc7f22ccde328f87bdd9a6d0c6b6ff019150
Show file tree
Hide file tree
Showing 35 changed files with 2,955 additions and 1 deletion.
@@ -30,6 +30,7 @@

<properties>
<microprofile.platform.version>1.4</microprofile.platform.version>
<geronimo-microprofile.Automatic-Module-Name>${project.groupId}.microprofile.aggregator</geronimo-microprofile.Automatic-Module-Name>
</properties>

<dependencies>
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>geronimo-microprofile</artifactId>
<groupId>org.apache.geronimo</groupId>
<version>1.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>geronimo-microprofile-reporter</artifactId>
<name>Geronimo Microprofile :: Reporter</name>
<description>
Small module allowing to get a local reporting of Microprofile data - intended for tests and demos.
</description>

<properties>
<geronimo-microprofile.Automatic-Module-Name>${project.groupId}.microprofile.reporter</geronimo-microprofile.Automatic-Module-Name>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.meecrowave</groupId>
<artifactId>meecrowave-specs-api</artifactId>
<version>${meecrowave.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo</groupId>
<artifactId>geronimo-microprofile-aggregator</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
<type>pom</type>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.meecrowave</groupId>
<artifactId>meecrowave-maven-plugin</artifactId>
<!--<version>${meecrowave.version}</version>-->
<version>1.2.5-SNAPSHOT</version> <!-- to avoid to scan poms and break the startup -->
<configuration>
<cdiConversation>false</cdiConversation>
<scanningIncludes>geronimo-metrics</scanningIncludes>
<systemProperties>
<geronimo.microprofile.reporter.dev>true</geronimo.microprofile.reporter.dev>
</systemProperties>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.geronimo</groupId>
<artifactId>geronimo-microprofile-aggregator</artifactId>
<version>${project.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.apache.geronimo</groupId>
<artifactId>geronimo-metrics-sigar</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.geronimo</groupId>
<artifactId>geronimo-metrics-tomcat</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,47 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.microprofile.reporter.storage;

import static java.util.Collections.unmodifiableMap;

import java.util.HashMap;
import java.util.Map;

public class Html {
private final String name;
private final Map<String, Object> data = new HashMap<>();

public Html(final String name) {
this.name = name;
}

public Html with(final String name, final Object data) {
if (data == null) {
return this;
}
this.data.put(name, data);
return this;
}

String getName() {
return name;
}

Map<String, Object> getData() {
return unmodifiableMap(data);
}
}
@@ -0,0 +1,94 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.microprofile.reporter.storage;

import static java.util.stream.Collectors.joining;
import static javax.ws.rs.core.MediaType.TEXT_HTML;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Stream;

import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

@Provider
@Dependent
@Produces(TEXT_HTML)
public class HtmlWriter implements MessageBodyWriter<Html> {
private final boolean development = Boolean.getBoolean("geronimo.microprofile.reporter.dev");

@Inject
private TemplatingEngine templatingEngine;

private final ConcurrentMap<String, String> templates = new ConcurrentHashMap<>();

@Override
public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
final MediaType mediaType) {
return type == Html.class;
}

@Override
public void writeTo(final Html html, final Class<?> type, final Type genericType,
final Annotation[] annotations, final MediaType mediaType,
final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
throws IOException, WebApplicationException {
final String template = templates.computeIfAbsent(html.getName(), this::loadTemplate);
final Function<Object, String> compiled = templatingEngine.compileIfNeeded(template, this::loadTemplate);
entityStream.write(compiled.apply(html.getData()).getBytes(StandardCharsets.UTF_8));
if (development) {
templates.clear();
templatingEngine.clean();
}
}

private String loadTemplate(final String template) {
return Stream.of("geronimo/microprofile/reporter/" + template, template)
.flatMap(it -> Stream.of(it, '/' + it))
.map(it -> {
try (final InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(it)) {
if (stream == null) {
return null;
}
return new BufferedReader(new InputStreamReader(stream)).lines().collect(joining("\n"));
} catch (final IOException e) {
throw new InternalServerErrorException(e);
}
})
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> new InternalServerErrorException("Missing template: " + template));
}
}
@@ -0,0 +1,147 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.microprofile.reporter.storage;

import static java.util.stream.Collectors.toMap;

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

// copy of metrics Histogram impl
public class InMemoryDatabase<T> {
private static final double ALPHA = Double.parseDouble(System.getProperty("geronimo.reporter.storage.alpha", "0.015"));

private static final int BUCKET_SIZE = Integer.getInteger("geronimo.reporter.storage.size", 12 /* one point/5s */ * 60 * 60);

private static final long REFRESH_INTERVAL = TimeUnit.HOURS.toNanos(1);

private final String unit;

private final ReadWriteLock lock = new ReentrantReadWriteLock();

private final AtomicLong count = new AtomicLong();

private final AtomicLong nextRefreshTime = new AtomicLong(System.nanoTime() + REFRESH_INTERVAL);

private volatile long startTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());

private final ConcurrentSkipListMap<Double, Value<T>> bucket = new ConcurrentSkipListMap<>();

public InMemoryDatabase(final String unit) {
this.unit = unit;
}

public String getUnit() {
return unit;
}

public Collection<Value<T>> snapshot() {
ensureUpToDate();
final Lock lock = this.lock.readLock();
lock.lock();
try {
return new ArrayList<>(bucket.values());
} finally {
lock.unlock();
}
}

void add(final T value) {
ensureUpToDate();

final long now = System.currentTimeMillis();
final Lock lock = this.lock.readLock();
lock.lock();
try {
final Value<T> sample = new Value<>(value, now, Math.exp(ALPHA * (TimeUnit.MILLISECONDS.toSeconds(now) - startTime)));
final double priority = sample.weight / Math.random();

final long size = count.incrementAndGet();
if (size <= BUCKET_SIZE) {
bucket.put(priority, sample);
} else { // iterate through the bucket until we need removing low priority entries to get a new space
double first = bucket.firstKey();
if (first < priority && bucket.putIfAbsent(priority, sample) == null) {
while (bucket.remove(first) == null) {
first = bucket.firstKey();
}
}
}
} finally {
lock.unlock();
}
}

private void ensureUpToDate() {
final long next = nextRefreshTime.get();
final long now = System.nanoTime();
if (now < next) {
return;
}

final Lock lock = this.lock.writeLock();
lock.lock();
try {
if (nextRefreshTime.compareAndSet(next, now + REFRESH_INTERVAL)) {
final long oldStartTime = startTime;
startTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
final double updateFactor = Math.exp(-ALPHA * (startTime - oldStartTime));
if (updateFactor != 0.) {
bucket.putAll(new ArrayList<>(bucket.keySet()).stream().collect(toMap(k -> k * updateFactor, k -> {
final Value<T> previous = bucket.remove(k);
return new Value<>(previous.value, previous.timestamp, previous.weight * updateFactor);
})));
count.set(bucket.size()); // N keys can lead to the same key so we must update it
} else {
bucket.clear();
count.set(0);
}
}
} finally {
lock.unlock();
}
}

public static final class Value<T> {

private final T value;

private final long timestamp;

private final double weight;

private Value(final T value, final long timestamp, final double weight) {
this.value = value;
this.weight = weight;
this.timestamp = timestamp;
}

public T getValue() {
return value;
}

public long getTimestamp() {
return timestamp;
}
}
}

0 comments on commit 6f1bdc7

Please sign in to comment.