refactoring reporter structure to make it easier to be pluggable
diff --git a/geronimo-microprofile-reporter/pom.xml b/geronimo-microprofile-reporter/pom.xml
index 7db4665..fe6028e 100644
--- a/geronimo-microprofile-reporter/pom.xml
+++ b/geronimo-microprofile-reporter/pom.xml
@@ -66,6 +66,12 @@
       <version>4.12</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.meecrowave</groupId>
+      <artifactId>meecrowave-junit</artifactId>
+      <version>${meecrowave.version}</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/FakeCheck1.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/FakeCheck1.java
deleted file mode 100644
index 9c12b18..0000000
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/FakeCheck1.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Copyright (C) 2006-2019 Talend Inc. - www.talend.com
- * <p>
- * Licensed 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
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * 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 javax.enterprise.context.Dependent;
-
-import org.eclipse.microprofile.health.Health;
-import org.eclipse.microprofile.health.HealthCheck;
-import org.eclipse.microprofile.health.HealthCheckResponse;
-
-@Health
-@Dependent
-public class FakeCheck1 implements HealthCheck {
-    @Override
-    public HealthCheckResponse call() {
-        return HealthCheckResponse.named("check1").up().build();
-    }
-}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/FakeCheck2.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/FakeCheck2.java
deleted file mode 100644
index 2bab8bb..0000000
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/FakeCheck2.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Copyright (C) 2006-2019 Talend Inc. - www.talend.com
- * <p>
- * Licensed 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
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * 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 javax.enterprise.context.Dependent;
-
-import org.eclipse.microprofile.health.Health;
-import org.eclipse.microprofile.health.HealthCheck;
-import org.eclipse.microprofile.health.HealthCheckResponse;
-import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
-
-@Health
-@Dependent
-public class FakeCheck2 implements HealthCheck {
-    @Override
-    public HealthCheckResponse call() {
-        final HealthCheckResponseBuilder named = HealthCheckResponse.named("check_2");
-        if (System.currentTimeMillis() % 2 == 0) {
-            return named.up().withData("foo", "bar").withData("another", "dummy").build();
-        }
-        return named.down().withData("foo", "bar").withData("another", "dummy").build();
-    }
-}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/MicroprofileDatabase.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/MicroprofileDatabase.java
deleted file mode 100644
index cf67677..0000000
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/MicroprofileDatabase.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/**
- * 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.lang.Thread.NORM_PRIORITY;
-import static java.util.Optional.ofNullable;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.eclipse.microprofile.metrics.MetricRegistry.Type.BASE;
-import static org.eclipse.microprofile.metrics.MetricRegistry.Type.VENDOR;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.stream.Stream;
-
-import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.context.Destroyed;
-import javax.enterprise.context.Initialized;
-import javax.enterprise.event.Observes;
-import javax.inject.Inject;
-import javax.servlet.ServletContext;
-
-import org.apache.geronimo.microprofile.opentracing.common.impl.FinishedSpan;
-import org.eclipse.microprofile.config.inject.ConfigProperty;
-import org.eclipse.microprofile.health.HealthCheckResponse;
-import org.eclipse.microprofile.metrics.Metered;
-import org.eclipse.microprofile.metrics.MetricRegistry;
-import org.eclipse.microprofile.metrics.Snapshot;
-import org.eclipse.microprofile.metrics.annotation.RegistryType;
-
-import io.opentracing.Span;
-
-// TODO: make span dependency optional and composable (events?)
-@ApplicationScoped
-public class MicroprofileDatabase {
-
-    @Inject
-    @ConfigProperty(name = "geronimo.microprofile.reporter.metrics.pollingInterval", defaultValue = "5000")
-    private Long pollingInterval;
-
-    @Inject
-    @RegistryType(type = BASE)
-    private MetricRegistry baseRegistry;
-
-    @Inject
-    @RegistryType(type = VENDOR)
-    private MetricRegistry vendorRegistry;
-
-    @Inject
-    private MetricRegistry applicationRegistry;
-
-    @Inject
-    private HealthRegistry healthRegistry;
-
-    private ScheduledExecutorService scheduler;
-
-    private ScheduledFuture<?> pollFuture;
-
-    private Map<String, MetricRegistry> metrics;
-
-    private final InMemoryDatabase<Span> spanDatabase = new InMemoryDatabase<>("none");
-    private final Map<String, InMemoryDatabase<Long>> counters = new HashMap<>();
-    private final Map<String, InMemoryDatabase<Double>> gauges = new HashMap<>();
-    private final Map<String, InMemoryDatabase<Snapshot>> histograms = new HashMap<>();
-    private final Map<String, InMemoryDatabase<MeterSnapshot>> meters = new HashMap<>();
-    private final Map<String, InMemoryDatabase<TimerSnapshot>> timers = new HashMap<>();
-    private final Map<String, InMemoryDatabase<CheckSnapshot>> checks = new HashMap<>();
-
-    public InMemoryDatabase<Span> getSpans() {
-        return spanDatabase;
-    }
-
-    public Map<String, InMemoryDatabase<Long>> getCounters() {
-        return counters;
-    }
-
-    public Map<String, InMemoryDatabase<Double>> getGauges() {
-        return gauges;
-    }
-
-    public Map<String, InMemoryDatabase<Snapshot>> getHistograms() {
-        return histograms;
-    }
-
-    public Map<String, InMemoryDatabase<MeterSnapshot>> getMeters() {
-        return meters;
-    }
-
-    public Map<String, InMemoryDatabase<TimerSnapshot>> getTimers() {
-        return timers;
-    }
-
-    public Map<String, InMemoryDatabase<CheckSnapshot>> getChecks() {
-        return checks;
-    }
-
-    private void poll() {
-        metrics.forEach(this::updateMetrics);
-
-        healthRegistry.doCheck().forEach(this::updateHealthCheck);
-    }
-
-    private void updateHealthCheck(final HealthCheckResponse healthCheckResponse) {
-        final String name = healthCheckResponse.getName();
-        InMemoryDatabase<CheckSnapshot> db = checks.get(name);
-        if (db == null) {
-            db = new InMemoryDatabase<>("check");
-            final InMemoryDatabase<CheckSnapshot> existing = checks.putIfAbsent(name, db);
-            if (existing != null) {
-                db = existing;
-            }
-        }
-        db.add(new CheckSnapshot(
-                healthCheckResponse.getName(),
-                ofNullable(healthCheckResponse.getState()).orElse(HealthCheckResponse.State.DOWN).name(),
-                healthCheckResponse.getData().map(HashMap::new).orElseGet(HashMap::new)));
-    }
-
-    private void updateMetrics(final String type, final MetricRegistry registry) {
-        registry.getCounters().forEach((name, counter) -> {
-            final String virtualName = getMetricStorageName(type, name);
-            final long count = counter.getCount();
-            getDb(counters, virtualName, registry, name).add(count);
-        });
-
-        registry.getGauges().forEach((name, gauge) -> {
-            final String virtualName = getMetricStorageName(type, name);
-            final Object value = gauge.getValue();
-            if (Number.class.isInstance(value)) {
-                try {
-                    getDb(gauges, virtualName, registry, name).add(Number.class.cast(value).doubleValue());
-                } catch (final NullPointerException | NumberFormatException nfe) {
-                    // ignore, we can't do much if the value is not a double
-                }
-            } // else ignore, will not be able to do anything of it anyway
-        });
-
-        registry.getHistograms().forEach((name, histogram) -> {
-            final String virtualName = getMetricStorageName(type, name);
-            final Snapshot snapshot = histogram.getSnapshot();
-            getDb(histograms, virtualName, registry, name).add(snapshot);
-        });
-
-        registry.getMeters().forEach((name, meter) -> {
-            final String virtualName = getMetricStorageName(type, name);
-            final MeterSnapshot snapshot = new MeterSnapshot(meter);
-            getDb(meters, virtualName, registry, name).add(snapshot);
-        });
-
-        registry.getTimers().forEach((name, timer) -> {
-            final String virtualName = getMetricStorageName(type, name);
-            final TimerSnapshot snapshot = new TimerSnapshot(new MeterSnapshot(timer), timer.getSnapshot());
-            getDb(timers, virtualName, registry, name).add(snapshot);
-        });
-    }
-
-    // alternatively we can decorate the registries and register/unregister following the registry lifecycle
-    // shouldnt be worth it for now
-    private <T> InMemoryDatabase<T> getDb(final Map<String, InMemoryDatabase<T>> registry,
-                                          final String virtualName, final MetricRegistry source,
-                                          final String key) {
-        InMemoryDatabase<T> db = registry.get(virtualName);
-        if (db == null) {
-            db = new InMemoryDatabase<>(ofNullable(source.getMetadata().get(key).getUnit()).orElse(""));
-            final InMemoryDatabase<T> existing = registry.putIfAbsent(virtualName, db);
-            if (existing != null) {
-                db = existing;
-            }
-        }
-        return db;
-    }
-
-    private String getMetricStorageName(final String type, final String name) {
-        return type + "#" + name;
-    }
-
-    private String name(final Object start) {
-        if (ServletContext.class.isInstance(start)) {
-            final ServletContext context = ServletContext.class.cast(start);
-            try {
-                return "[web=" + context.getVirtualServerName() + '/' + context.getContextPath() + "]";
-            } catch (final Error | Exception e) { // no getVirtualServerName() for this context
-                return "[web=" + context.getContextPath() + "]";
-            }
-        }
-        return start.toString();
-    }
-
-    void onSpan(@Observes final FinishedSpan span) {
-        final Span value = span.getSpan();
-        if (value.getClass().getName().equals("org.apache.geronimo.microprofile.opentracing.common.impl.SpanImpl")) {
-            spanDatabase.add(value);
-        } // else we will not be able to read the metadata
-    }
-
-    void onStart(@Observes @Initialized(ApplicationScoped.class) final Object start) {
-        metrics = new HashMap<>(3);
-        metrics.put("vendor", vendorRegistry);
-        metrics.put("base", baseRegistry);
-        metrics.put("application", applicationRegistry);
-
-        final ClassLoader appLoader = Thread.currentThread().getContextClassLoader();
-        scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
-            final Thread thread = new Thread(r, "geronimo-microprofile-reporter-poller-" + name(start));
-            thread.setContextClassLoader(appLoader);
-            if (thread.isDaemon()) {
-                thread.setDaemon(false);
-            }
-            if (thread.getPriority() != NORM_PRIORITY) {
-                thread.setPriority(NORM_PRIORITY);
-            }
-            return thread;
-        });
-        pollFuture = scheduler.scheduleAtFixedRate(this::poll, pollingInterval, pollingInterval, MILLISECONDS);
-    }
-
-    void onStop(@Observes @Destroyed(ApplicationScoped.class) final Object stop) {
-        if (pollFuture != null) {
-            pollFuture.cancel(true);
-            pollFuture = null;
-        }
-        if (scheduler != null) {
-            scheduler.shutdownNow();
-            try {
-                scheduler.awaitTermination(10, SECONDS);
-            } catch (final InterruptedException e) {
-                Thread.currentThread().interrupt();
-            }
-            scheduler = null;
-        }
-        Stream.of(counters, gauges, histograms, meters, timers).forEach(Map::clear);
-    }
-
-    public static class TimerSnapshot {
-        private final MeterSnapshot meter;
-        private final Snapshot histogram;
-
-        private TimerSnapshot(final MeterSnapshot meter, final Snapshot histogram) {
-            this.meter = meter;
-            this.histogram = histogram;
-        }
-    }
-
-    public static class MeterSnapshot {
-        private final long count;
-        private final double rateMean;
-        private final double rate1;
-        private final double rate5;
-        private final double rate15;
-
-        private MeterSnapshot(final Metered meter) {
-            this(meter.getCount(), meter.getMeanRate(), meter.getOneMinuteRate(), meter.getFiveMinuteRate(), meter.getFifteenMinuteRate());
-        }
-
-        private MeterSnapshot(final long count, final double rateMean,
-                              final double rate1, final double rate5, final double rate15) {
-            this.count = count;
-            this.rateMean = rateMean;
-            this.rate1 = rate1;
-            this.rate5 = rate5;
-            this.rate15 = rate15;
-        }
-    }
-
-    public static class CheckSnapshot {
-        private final String name;
-        private final String state;
-        private final Map<String, Object> data;
-
-        private CheckSnapshot(final String name, final String state, final Map<String, Object> data) {
-            this.name = name;
-            this.state = state;
-            this.data = data;
-        }
-    }
-}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/InMemoryDatabase.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/data/InMemoryDatabase.java
similarity index 97%
rename from geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/InMemoryDatabase.java
rename to geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/data/InMemoryDatabase.java
index 7b3d86c..d1b5213 100644
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/InMemoryDatabase.java
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/data/InMemoryDatabase.java
@@ -14,12 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.geronimo.microprofile.reporter.storage;
+package org.apache.geronimo.microprofile.reporter.storage.data;
 
 import static java.util.stream.Collectors.toMap;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.LinkedList;
 import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.TimeUnit;
@@ -67,7 +66,7 @@
         }
     }
 
-    void add(final T value) {
+    public void add(final T value) {
         ensureUpToDate();
 
         final long now = System.currentTimeMillis();
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/data/MicroprofileDatabase.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/data/MicroprofileDatabase.java
new file mode 100644
index 0000000..b4bc8da
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/data/MicroprofileDatabase.java
@@ -0,0 +1,74 @@
+/**
+ * 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.data;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Destroyed;
+import javax.enterprise.event.Observes;
+
+import org.apache.geronimo.microprofile.reporter.storage.plugins.health.CheckSnapshot;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.metrics.MeterSnapshot;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.metrics.SnapshotStat;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.metrics.TimerSnapshot;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.tracing.SpanEntry;
+
+@ApplicationScoped
+public class MicroprofileDatabase {
+    private final InMemoryDatabase<SpanEntry> spanDatabase = new InMemoryDatabase<>("none");
+    private final Map<String, InMemoryDatabase<Long>> counters = new HashMap<>();
+    private final Map<String, InMemoryDatabase<Double>> gauges = new HashMap<>();
+    private final Map<String, InMemoryDatabase<SnapshotStat>> histograms = new HashMap<>();
+    private final Map<String, InMemoryDatabase<MeterSnapshot>> meters = new HashMap<>();
+    private final Map<String, InMemoryDatabase<TimerSnapshot>> timers = new HashMap<>();
+    private final Map<String, InMemoryDatabase<CheckSnapshot>> checks = new HashMap<>();
+
+    public InMemoryDatabase<SpanEntry> getSpans() {
+        return spanDatabase;
+    }
+
+    public Map<String, InMemoryDatabase<Long>> getCounters() {
+        return counters;
+    }
+
+    public Map<String, InMemoryDatabase<Double>> getGauges() {
+        return gauges;
+    }
+
+    public Map<String, InMemoryDatabase<SnapshotStat>> getHistograms() {
+        return histograms;
+    }
+
+    public Map<String, InMemoryDatabase<MeterSnapshot>> getMeters() {
+        return meters;
+    }
+
+    public Map<String, InMemoryDatabase<TimerSnapshot>> getTimers() {
+        return timers;
+    }
+
+    public Map<String, InMemoryDatabase<CheckSnapshot>> getChecks() {
+        return checks;
+    }
+
+    void onStop(@Observes @Destroyed(ApplicationScoped.class) final Object stop) {
+        Stream.of(counters, gauges, histograms, meters, timers).forEach(Map::clear);
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/Html.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/front/Html.java
similarity index 95%
rename from geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/Html.java
rename to geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/front/Html.java
index 8ed2414..b4e7523 100644
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/Html.java
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/front/Html.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.geronimo.microprofile.reporter.storage;
+package org.apache.geronimo.microprofile.reporter.storage.front;
 
 import static java.util.Collections.unmodifiableMap;
 
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/HtmlWriter.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/front/HtmlWriter.java
similarity index 96%
rename from geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/HtmlWriter.java
rename to geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/front/HtmlWriter.java
index 2efc598..11040be 100644
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/HtmlWriter.java
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/front/HtmlWriter.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.geronimo.microprofile.reporter.storage;
+package org.apache.geronimo.microprofile.reporter.storage.front;
 
 import static java.util.stream.Collectors.joining;
 import static javax.ws.rs.core.MediaType.TEXT_HTML;
@@ -43,6 +43,8 @@
 import javax.ws.rs.ext.MessageBodyWriter;
 import javax.ws.rs.ext.Provider;
 
+import org.apache.geronimo.microprofile.reporter.storage.templating.TemplatingEngine;
+
 @Provider
 @Dependent
 @Produces(TEXT_HTML)
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/ReporterEndpoints.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/front/ReporterEndpoints.java
similarity index 81%
rename from geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/ReporterEndpoints.java
rename to geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/front/ReporterEndpoints.java
index 984adba..c6ac0ad 100644
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/ReporterEndpoints.java
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/front/ReporterEndpoints.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.geronimo.microprofile.reporter.storage;
+package org.apache.geronimo.microprofile.reporter.storage.front;
 
 import static java.util.Arrays.asList;
 import static java.util.Comparator.comparing;
@@ -28,6 +28,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
@@ -40,14 +41,19 @@
 import javax.ws.rs.BadRequestException;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 
-import org.eclipse.microprofile.health.HealthCheckResponse;
-import org.eclipse.microprofile.metrics.Snapshot;
-
-import io.opentracing.Span;
+import org.apache.geronimo.microprofile.reporter.storage.data.InMemoryDatabase;
+import org.apache.geronimo.microprofile.reporter.storage.data.MicroprofileDatabase;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.health.CheckSnapshot;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.health.HealthService;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.metrics.MeterSnapshot;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.metrics.SnapshotStat;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.metrics.TimerSnapshot;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.tracing.SpanEntry;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.tracing.TracingExtension;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
 
 @Path("geronimo/microprofile/reporter")
 @ApplicationScoped
@@ -59,19 +65,21 @@
     private MicroprofileDatabase database;
 
     @Inject
-    private SpanMapper spanMapper;
+    private HealthService health;
 
     @Inject
-    private HealthRegistry healthRegistry;
+    private TracingExtension tracing;
+
+    @Inject
+    @ConfigProperty(name = "geronimo.microprofile.reporter.resources.chartjs",
+            defaultValue = "/META-INF/resources/webjars/chart.js/2.7.3/dist/Chart.bundle.min.js")
+    private String chartJsResource;
 
     private String chartJs;
+    private List<String> tiles;
 
     @PostConstruct
     private void init() {
-        // we load chart.js like that to enable to override it easily and respect our relative path properly
-        final String chartJsResource = System.getProperty( // don't use mp-config, it is optional
-                "geronimo.microprofile.reporter.chartjs.resources",
-                "/META-INF/resources/webjars/chart.js/2.7.3/dist/Chart.bundle.min.js");
         final ClassLoader loader = Thread.currentThread().getContextClassLoader();
         chartJs = (chartJsResource.startsWith("/") ?
                 Stream.of(chartJsResource, chartJsResource.substring(1)) : Stream.of(chartJsResource, '/' + chartJsResource))
@@ -92,6 +100,14 @@
                 .findFirst()
                 .orElseThrow(() -> new IllegalStateException("No " + chartJsResource + " found, did you add org.webjars.bower:chart.js:2.7.3 to your classpath?"));
 
+        tiles = new ArrayList<>(7);
+        if (tracing.isActive()) {
+            tiles.add("Spans");
+        }
+        tiles.addAll(asList("Counters", "Gauges", "Histograms", "Meters", "Timers"));
+        if (health.isActive()) {
+            tiles.add("Health Checks");
+        }
     }
 
     @GET
@@ -100,7 +116,7 @@
                 .with("view", "index.html")
                 .with("colors", COLORS)
                 .with("title", "Home")
-                .with("tiles", asList("Spans", "Counters", "Gauges", "Histograms", "Meters", "Timers", "Health Checks"));
+                .with("tiles", tiles);
     }
 
     @GET
@@ -177,7 +193,7 @@
     @GET
     @Path("histogram")
     public Html getHistogram(@QueryParam("histogram") final String name) {
-        final InMemoryDatabase<Snapshot> db = database.getHistograms().get(name);
+        final InMemoryDatabase<SnapshotStat> db = database.getHistograms().get(name);
         return new Html("main.html")
                 .with("view", "histogram.html")
                 .with("colors", COLORS)
@@ -201,7 +217,7 @@
     @GET
     @Path("meter")
     public Html getMeter(@QueryParam("meter") final String name) {
-        final InMemoryDatabase<MicroprofileDatabase.MeterSnapshot> db = database.getMeters().get(name);
+        final InMemoryDatabase<MeterSnapshot> db = database.getMeters().get(name);
         return new Html("main.html")
                 .with("view", "meter.html")
                 .with("colors", COLORS)
@@ -225,7 +241,7 @@
     @GET
     @Path("timer")
     public Html getTimer(@QueryParam("timer") final String name) {
-        final InMemoryDatabase<MicroprofileDatabase.TimerSnapshot> db = database.getTimers().get(name);
+        final InMemoryDatabase<TimerSnapshot> db = database.getTimers().get(name);
         return new Html("main.html")
                 .with("view", "timer.html")
                 .with("colors", COLORS)
@@ -239,7 +255,7 @@
     @GET
     @Path("spans")
     public Html getSpans() {
-        final InMemoryDatabase<Span> db = database.getSpans();
+        final InMemoryDatabase<SpanEntry> db = database.getSpans();
         return new Html("main.html")
                 .with("view", "spans.html")
                 .with("colors", COLORS)
@@ -247,15 +263,15 @@
                 .with("spans", db == null ?
                         null :
                         db.snapshot().stream()
-                            .map(it -> new Point<>(it.getTimestamp(), spanMapper.map(it.getValue())))
+                            .map(it -> new Point<>(it.getTimestamp(), it.getValue()))
                             .collect(toList()));
     }
 
     @GET
     @Path("span")
     public Html getSpan(@QueryParam("spanId") final String id) {
-        final SpanMapper.SpanEntry value = database.getSpans().snapshot().stream()
-               .map(it -> spanMapper.map(it.getValue()))
+        final SpanEntry value = database.getSpans().snapshot().stream()
+               .map(InMemoryDatabase.Value::getValue)
                .filter(it -> it.getSpanId().equals(id))
                .findFirst()
                .orElseThrow(() -> new BadRequestException("No matching span"));
@@ -279,7 +295,7 @@
     @GET
     @Path("check")
     public Html getHealth(@QueryParam("check") final String name) {
-        final InMemoryDatabase<MicroprofileDatabase.CheckSnapshot> db = database.getChecks().get(name);
+        final InMemoryDatabase<CheckSnapshot> db = database.getChecks().get(name);
         return new Html("main.html")
                 .with("view", "health.html")
                 .with("colors", COLORS)
@@ -292,7 +308,7 @@
     @GET
     @Path("health-check-detail")
     public Html getHealthCheckDetail(@QueryParam("check") final String name) {
-        final InMemoryDatabase.Value<MicroprofileDatabase.CheckSnapshot> last = ofNullable(database.getChecks().get(name))
+        final InMemoryDatabase.Value<CheckSnapshot> last = ofNullable(database.getChecks().get(name))
                 .map(InMemoryDatabase::snapshot)
                 .map(it -> it.isEmpty() ? null : it.getLast())
                 .orElse(null); // todo: orElseGet -> call them all and filter per name?
@@ -309,17 +325,16 @@
     @GET
     @Path("health-application")
     public Html getApplicationHealth() {
-        final List<HealthCheckResponse> checks = healthRegistry.doCheck()
-               .sorted(comparing(HealthCheckResponse::getName))
-               .collect(toList());
-        final boolean stateOk = checks.stream()
-                                   .noneMatch(it -> it.getState().equals(HealthCheckResponse.State.DOWN));
+        final List<CheckSnapshot> checks = health.doCheck()
+                                                 .sorted(comparing(CheckSnapshot::getName))
+                                                 .collect(toList());
         return new Html("main.html")
                 .with("view", "health-application.html")
                 .with("colors", COLORS)
                 .with("title", "Application Health")
-                .with("globalStateOk", stateOk)
-                .with("globalStateKo", !stateOk)
+                .with("globalState", checks.stream()
+                                           .filter(it -> it.getState().equals("DOWN")).findAny()
+                                           .map(CheckSnapshot::getState).orElse("UP"))
                 .with("checks", checks);
     }
 
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/Poller.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/Poller.java
new file mode 100644
index 0000000..6d74c99
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/Poller.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright (C) 2006-2019 Talend Inc. - www.talend.com
+ * <p>
+ * Licensed 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.plugins;
+
+import static java.lang.Thread.NORM_PRIORITY;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Destroyed;
+import javax.enterprise.context.Initialized;
+import javax.enterprise.event.Event;
+import javax.enterprise.event.Observes;
+import javax.inject.Inject;
+import javax.servlet.ServletContext;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+@ApplicationScoped
+public class Poller {
+    private static final Tick TICK = new Tick();
+
+    private ScheduledExecutorService scheduler;
+
+    private ScheduledFuture<?> pollFuture;
+
+    @Inject
+    private Event<Tick> tickEvent;
+
+    void onStart(@Observes @Initialized(ApplicationScoped.class) final Object start,
+                 @ConfigProperty(name = "geronimo.microprofile.reporter.polling.interval", defaultValue = "5000") final Long pollingInterval) {
+        if (pollingInterval <= 0) {
+            return;
+        }
+
+        final ClassLoader appLoader = Thread.currentThread().getContextClassLoader();
+        scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
+            final Thread thread = new Thread(r, "geronimo-microprofile-reporter-poller-" + name(start));
+            thread.setContextClassLoader(appLoader);
+            if (thread.isDaemon()) {
+                thread.setDaemon(false);
+            }
+            if (thread.getPriority() != NORM_PRIORITY) {
+                thread.setPriority(NORM_PRIORITY);
+            }
+            return thread;
+        });
+        pollFuture = scheduler.scheduleAtFixedRate(() -> tickEvent.fire(TICK), pollingInterval, pollingInterval, MILLISECONDS);
+    }
+
+    void onStop(@Observes @Destroyed(ApplicationScoped.class) final Object stop) {
+        if (pollFuture != null) {
+            pollFuture.cancel(true);
+            pollFuture = null;
+        }
+        if (scheduler != null) {
+            scheduler.shutdownNow();
+            try {
+                scheduler.awaitTermination(10, SECONDS);
+            } catch (final InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+            scheduler = null;
+        }
+    }
+
+    private String name(final Object start) {
+        if (ServletContext.class.isInstance(start)) {
+            final ServletContext context = ServletContext.class.cast(start);
+            try {
+                return "[web=" + context.getVirtualServerName() + '/' + context.getContextPath() + "]";
+            } catch (final Error | Exception e) { // no getVirtualServerName() for this context
+                return "[web=" + context.getContextPath() + "]";
+            }
+        }
+        return start.toString();
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/Tick.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/Tick.java
new file mode 100644
index 0000000..0d938bf
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/Tick.java
@@ -0,0 +1,18 @@
+/**
+ * Copyright (C) 2006-2019 Talend Inc. - www.talend.com
+ * <p>
+ * Licensed 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.plugins;
+
+public class Tick {}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/Html.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/CheckSnapshot.java
similarity index 63%
copy from geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/Html.java
copy to geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/CheckSnapshot.java
index 8ed2414..fde661c 100644
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/Html.java
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/CheckSnapshot.java
@@ -14,34 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.geronimo.microprofile.reporter.storage;
+package org.apache.geronimo.microprofile.reporter.storage.plugins.health;
 
-import static java.util.Collections.unmodifiableMap;
-
-import java.util.HashMap;
 import java.util.Map;
 
-public class Html {
+public class CheckSnapshot {
     private final String name;
-    private final Map<String, Object> data = new HashMap<>();
+    private final String state;
+    private final Map<String, Object> data;
 
-    public Html(final String name) {
+    CheckSnapshot(final String name, final String state, final Map<String, Object> data) {
         this.name = name;
+        this.state = state;
+        this.data = data;
     }
 
-    public Html with(final String name, final Object data) {
-        if (data == null) {
-            return this;
-        }
-        this.data.put(name, data);
-        return this;
-    }
-
-    String getName() {
+    public String getName() {
         return name;
     }
 
-    Map<String, Object> getData() {
-        return unmodifiableMap(data);
+    public String getState() {
+        return state;
+    }
+
+    public Map<String, Object> getData() {
+        return data;
     }
 }
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthDataExtractor.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthDataExtractor.java
new file mode 100644
index 0000000..b4a9e05
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthDataExtractor.java
@@ -0,0 +1,54 @@
+/**
+ * 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.plugins.health;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Stream;
+
+import javax.enterprise.inject.Vetoed;
+
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+
+@Vetoed
+class HealthDataExtractor {
+    private final List<HealthCheck> checks = new ArrayList<>();
+
+    Stream<CheckSnapshot> doCheck() {
+        return checks.stream().map(check -> {
+            try {
+                return check.call();
+            } catch (final RuntimeException re) {
+                return HealthCheckResponse.named(check.getClass().getName())
+                                          .down()
+                                          .withData("exceptionMessage", re.getMessage())
+                                          .build();
+            }
+        }).map(healthCheckResponse -> new CheckSnapshot(
+                healthCheckResponse.getName(),
+                ofNullable(healthCheckResponse.getState()).orElse(HealthCheckResponse.State.DOWN).name(),
+                healthCheckResponse.getData().map(HashMap::new).orElseGet(HashMap::new)));
+    }
+
+    void register(final Object check) {
+        checks.add(HealthCheck.class.cast(check));
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/HealthRegistry.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthRegistry.java
similarity index 63%
rename from geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/HealthRegistry.java
rename to geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthRegistry.java
index 0b97764..eab7110 100644
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/HealthRegistry.java
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthRegistry.java
@@ -14,59 +14,59 @@
  * 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.toList;
+package org.apache.geronimo.microprofile.reporter.storage.plugins.health;
 
 import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
-import java.util.stream.Stream;
 
 import javax.enterprise.context.spi.CreationalContext;
 import javax.enterprise.event.Observes;
 import javax.enterprise.inject.spi.AfterDeploymentValidation;
 import javax.enterprise.inject.spi.Bean;
 import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
 import javax.enterprise.inject.spi.BeforeShutdown;
 import javax.enterprise.inject.spi.Extension;
 import javax.enterprise.inject.spi.ProcessBean;
 
-import org.eclipse.microprofile.health.Health;
-import org.eclipse.microprofile.health.HealthCheck;
-import org.eclipse.microprofile.health.HealthCheckResponse;
-
 public class HealthRegistry implements Extension {
     private static final Annotation[] NO_ANNOTATION = new Annotation[0];
 
     private final Collection<Bean<?>> beans = new ArrayList<>();
     private final Collection<CreationalContext<?>> contexts = new ArrayList<>();
-    private final List<HealthCheck> checks = new ArrayList<>();
 
-    public Stream<HealthCheckResponse> doCheck() {
-        return checks.stream().map(check -> invoke(check));
-    }
+    private Class<? extends Annotation> annotationMarker;
+    private Class<?> apiType;
 
-    private HealthCheckResponse invoke(final HealthCheck check) {
+    void onStart(@Observes final BeforeBeanDiscovery beforeBeanDiscovery) {
+        final ClassLoader loader = Thread.currentThread().getContextClassLoader();
         try {
-            return check.call();
-        } catch (final RuntimeException re) {
-            return HealthCheckResponse.named(check.getClass().getName())
-                                      .down()
-                                      .withData("exceptionMessage", re.getMessage())
-                                      .build();
+            annotationMarker = (Class<? extends Annotation>) loader.loadClass("org.eclipse.microprofile.health.Health");
+            apiType = loader.loadClass("org.eclipse.microprofile.health.HealthCheck");
+        } catch (final ClassNotFoundException e) {
+            // no-op
         }
     }
 
+    public Class<?> getApiType() {
+        return apiType;
+    }
+
     void findChecks(@Observes final ProcessBean<?> bean) {
-        if (bean.getAnnotated().isAnnotationPresent(Health.class) && bean.getBean().getTypes().contains(HealthCheck.class)) {
+        if (bean.getAnnotated().isAnnotationPresent(annotationMarker) && bean.getBean().getTypes().contains(apiType)) {
             beans.add(bean.getBean());
         }
     }
 
     void start(@Observes final AfterDeploymentValidation afterDeploymentValidation, final BeanManager beanManager) {
-        checks.addAll(beans.stream().map(it -> lookup(it, beanManager)).collect(toList()));
+        if (beans.isEmpty()) {
+            return;
+        }
+        final HealthService healthService = HealthService.class.cast(
+                beanManager.getReference(beanManager.resolve(beanManager.getBeans(HealthService.class)),
+                        HealthService.class, beanManager.createCreationalContext(null)));
+        beans.stream().map(it -> lookup(it, beanManager)).forEach(healthService::register);
     }
 
     void stop(@Observes final BeforeShutdown beforeShutdown) {
@@ -83,14 +83,14 @@
         }
     }
 
-    private HealthCheck lookup(final Bean<?> bean, final BeanManager manager) {
+    private Object lookup(final Bean<?> bean, final BeanManager manager) {
         final Class<?> beanClass = bean.getBeanClass();
         final Bean<?> resolvedBean = manager.resolve(manager.getBeans(
-                beanClass != null ? beanClass : HealthCheck.class, bean.getQualifiers().toArray(NO_ANNOTATION)));
+                beanClass != null ? beanClass : apiType, bean.getQualifiers().toArray(NO_ANNOTATION)));
         final CreationalContext<Object> creationalContext = manager.createCreationalContext(null);
         if (!manager.isNormalScope(resolvedBean.getScope())) {
             contexts.add(creationalContext);
         }
-        return HealthCheck.class.cast(manager.getReference(resolvedBean, HealthCheck.class, creationalContext));
+        return manager.getReference(resolvedBean, apiType, creationalContext);
     }
 }
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthService.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthService.java
new file mode 100644
index 0000000..a93db8b
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthService.java
@@ -0,0 +1,70 @@
+/**
+ * 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.plugins.health;
+
+import java.util.stream.Stream;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import javax.inject.Inject;
+
+import org.apache.geronimo.microprofile.reporter.storage.data.InMemoryDatabase;
+import org.apache.geronimo.microprofile.reporter.storage.data.MicroprofileDatabase;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.Tick;
+
+// cdi indirection to not require health check api and impl to be present
+@ApplicationScoped
+public class HealthService {
+    private final HealthDataExtractor extractor = new HealthDataExtractor();
+
+    @Inject
+    private HealthRegistry registry;
+
+    @Inject
+    private MicroprofileDatabase database;
+
+    public boolean isActive() {
+        return registry.getApiType() != null;
+    }
+
+    void register(final Object check) {
+        extractor.register(check);
+    }
+
+    public Stream<CheckSnapshot> doCheck() {
+        return extractor.doCheck();
+    }
+
+    public void onTick(@Observes final Tick tick) {
+        if (isActive()) {
+            doCheck().forEach(this::updateHealthCheck);
+        }
+    }
+
+    private void updateHealthCheck(final CheckSnapshot healthCheckResponse) {
+        final String name = healthCheckResponse.getName();
+        InMemoryDatabase<CheckSnapshot> db = database.getChecks().get(name);
+        if (db == null) {
+            db = new InMemoryDatabase<>("check");
+            final InMemoryDatabase<CheckSnapshot> existing = database.getChecks().putIfAbsent(name, db);
+            if (existing != null) {
+                db = existing;
+            }
+        }
+        db.add(healthCheckResponse);
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/MeterSnapshot.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/MeterSnapshot.java
new file mode 100644
index 0000000..47da990
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/MeterSnapshot.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2006-2019 Talend Inc. - www.talend.com
+ * <p>
+ * Licensed 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.plugins.metrics;
+
+public class MeterSnapshot {
+    private final long count;
+    private final double rateMean;
+    private final double rate1;
+    private final double rate5;
+    private final double rate15;
+
+    MeterSnapshot(final long count, final double rateMean,
+                          final double rate1, final double rate5, final double rate15) {
+        this.count = count;
+        this.rateMean = rateMean;
+        this.rate1 = rate1;
+        this.rate5 = rate5;
+        this.rate15 = rate15;
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/MetricsService.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/MetricsService.java
new file mode 100644
index 0000000..124c2c9
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/MetricsService.java
@@ -0,0 +1,132 @@
+/**
+ * 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.plugins.metrics;
+
+import static java.util.Optional.ofNullable;
+import static org.eclipse.microprofile.metrics.MetricRegistry.Type.BASE;
+import static org.eclipse.microprofile.metrics.MetricRegistry.Type.VENDOR;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import javax.inject.Inject;
+
+import org.apache.geronimo.microprofile.reporter.storage.data.InMemoryDatabase;
+import org.apache.geronimo.microprofile.reporter.storage.data.MicroprofileDatabase;
+import org.apache.geronimo.microprofile.reporter.storage.plugins.Tick;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+import org.eclipse.microprofile.metrics.Snapshot;
+import org.eclipse.microprofile.metrics.annotation.RegistryType;
+
+@ApplicationScoped
+public class MetricsService {
+    @Inject
+    private MicroprofileDatabase database;
+
+    @Inject
+    @RegistryType(type = BASE)
+    private MetricRegistry baseRegistry;
+
+    @Inject
+    @RegistryType(type = VENDOR)
+    private MetricRegistry vendorRegistry;
+
+    @Inject
+    private MetricRegistry applicationRegistry;
+
+    private Map<String, MetricRegistry> metricsIndex;
+
+    private void updateMetrics(final String type, final MetricRegistry registry) {
+        registry.getCounters().forEach((name, counter) -> {
+            final String virtualName = getMetricStorageName(type, name);
+            final long count = counter.getCount();
+            getDb(database.getCounters(), virtualName, registry, name).add(count);
+        });
+
+        registry.getGauges().forEach((name, gauge) -> {
+            final String virtualName = getMetricStorageName(type, name);
+            final Object value = gauge.getValue();
+            if (Number.class.isInstance(value)) {
+                try {
+                    getDb(database.getGauges(), virtualName, registry, name).add(Number.class.cast(value).doubleValue());
+                } catch (final NullPointerException | NumberFormatException nfe) {
+                    // ignore, we can't do much if the value is not a double
+                }
+            } // else ignore, will not be able to do anything of it anyway
+        });
+
+        registry.getHistograms().forEach((name, histogram) -> {
+            final String virtualName = getMetricStorageName(type, name);
+            final Snapshot snapshot = histogram.getSnapshot();
+            getDb(database.getHistograms(), virtualName, registry, name)
+                    .add(new SnapshotStat(snapshot.size(), snapshot.getMedian(), snapshot.getMean(), snapshot.getMin(), snapshot.getMax(), snapshot.getStdDev(),
+                            snapshot.get75thPercentile(), snapshot.get95thPercentile(), snapshot.get98thPercentile(), snapshot.get99thPercentile(), snapshot.get999thPercentile()));
+        });
+
+        registry.getMeters().forEach((name, meter) -> {
+            final String virtualName = getMetricStorageName(type, name);
+            final MeterSnapshot snapshot = new MeterSnapshot(
+                    meter.getCount(), meter.getMeanRate(), meter.getOneMinuteRate(), meter.getFiveMinuteRate(), meter.getFifteenMinuteRate());
+            getDb(database.getMeters(), virtualName, registry, name).add(snapshot);
+        });
+
+        registry.getTimers().forEach((name, timer) -> {
+            final String virtualName = getMetricStorageName(type, name);
+            final Snapshot snapshot = timer.getSnapshot();
+            final TimerSnapshot timerSnapshot = new TimerSnapshot(new MeterSnapshot(
+                    timer.getCount(), timer.getMeanRate(), timer.getOneMinuteRate(), timer.getFiveMinuteRate(), timer.getFifteenMinuteRate()),
+                    new SnapshotStat(snapshot.size(), snapshot.getMedian(), snapshot.getMean(), snapshot.getMin(), snapshot.getMax(), snapshot.getStdDev(),
+                    snapshot.get75thPercentile(), snapshot.get95thPercentile(), snapshot.get98thPercentile(), snapshot.get99thPercentile(), snapshot.get999thPercentile()));
+            getDb(database.getTimers(), virtualName, registry, name).add(timerSnapshot);
+        });
+    }
+
+    // alternatively we can decorate the registries and register/unregister following the registry lifecycle
+    // shouldnt be worth it for now
+    private <T> InMemoryDatabase<T> getDb(final Map<String, InMemoryDatabase<T>> registry,
+                                          final String virtualName, final MetricRegistry source,
+                                          final String key) {
+        InMemoryDatabase<T> db = registry.get(virtualName);
+        if (db == null) {
+            db = new InMemoryDatabase<>(ofNullable(source.getMetadata().get(key).getUnit()).orElse(""));
+            final InMemoryDatabase<T> existing = registry.putIfAbsent(virtualName, db);
+            if (existing != null) {
+                db = existing;
+            }
+        }
+        return db;
+    }
+
+    private String getMetricStorageName(final String type, final String name) {
+        return type + "#" + name;
+    }
+
+    @PostConstruct
+    private void init() {
+        metricsIndex = new HashMap<>(3);
+        metricsIndex.put("vendor", vendorRegistry);
+        metricsIndex.put("base", baseRegistry);
+        metricsIndex.put("application", applicationRegistry);
+    }
+
+    void onTick(@Observes final Tick tick) {
+        metricsIndex.forEach(this::updateMetrics);
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/SnapshotStat.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/SnapshotStat.java
new file mode 100644
index 0000000..4f12976
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/SnapshotStat.java
@@ -0,0 +1,79 @@
+/**
+ * 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.plugins.metrics;
+
+public class SnapshotStat {
+
+    private final int size;
+
+    private final double median;
+
+    private final double mean;
+
+    private final double min;
+
+    private final double max;
+
+    private final double stdDev;
+
+    private final double pc75;
+
+    private final double pc95;
+
+    private final double pc98;
+
+    private final double pc99;
+
+    private final double pc999;
+
+    SnapshotStat(final int size, final double median, final double mean,
+                        final double min, final double max, final double stdDev,
+                        final double pc75, final double pc95, final double pc98,
+                        final double pc99, final double pc999) {
+        this.size = size;
+        this.median = median;
+        this.mean = mean;
+        this.min = min;
+        this.max = max;
+        this.stdDev = stdDev;
+        this.pc75 = pc75;
+        this.pc95 = pc95;
+        this.pc98 = pc98;
+        this.pc99 = pc99;
+        this.pc999 = pc999;
+    }
+
+    public double get75thPercentile() {
+        return pc75;
+    }
+
+    public double get95thPercentile() {
+        return pc95;
+    }
+
+    public double get98thPercentile() {
+        return pc98;
+    }
+
+    public double get99thPercentile() {
+        return pc99;
+    }
+
+    public double get999thPercentile() {
+        return pc999;
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/TimerSnapshot.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/TimerSnapshot.java
new file mode 100644
index 0000000..3f40b19
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/metrics/TimerSnapshot.java
@@ -0,0 +1,27 @@
+/**
+ * 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.plugins.metrics;
+
+public class TimerSnapshot {
+    private final MeterSnapshot meter;
+    private final SnapshotStat histogram;
+
+    TimerSnapshot(final MeterSnapshot meter, final SnapshotStat histogram) {
+        this.meter = meter;
+        this.histogram = histogram;
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/SpanEntry.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/SpanEntry.java
new file mode 100644
index 0000000..bebeea8
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/SpanEntry.java
@@ -0,0 +1,102 @@
+/**
+ * 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.plugins.tracing;
+
+import java.util.Collection;
+import java.util.Map;
+
+public class SpanEntry {
+    private final String spanId;
+    private final String traceId;
+    private final String parentId;
+    private final String name;
+    private final long timestamp;
+    private final long duration;
+    private final String kind;
+    private final Map<String, Object> tags;
+    private final Collection<LogEntry> getLogs;
+
+    SpanEntry(final String spanId, final String traceId, final String parentId, final String name,
+                      final long timestamp, final long duration, final String kind, final Map<String, Object> tags,
+                      final Collection<LogEntry> getLogs) {
+        this.spanId = spanId;
+        this.traceId = traceId;
+        this.parentId = parentId;
+        this.name = name;
+        this.timestamp = timestamp;
+        this.duration = duration;
+        this.kind = kind;
+        this.tags = tags;
+        this.getLogs = getLogs;
+    }
+
+    public String getSpanId() {
+        return spanId;
+    }
+
+    public String getTraceId() {
+        return traceId;
+    }
+
+    public String getParentId() {
+        return parentId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    public long getDuration() {
+        return duration;
+    }
+
+    public String getKind() {
+        return kind;
+    }
+
+    public Map<String, Object> getTags() {
+        return tags;
+    }
+
+    public Collection<LogEntry> getGetLogs() {
+        return getLogs;
+    }
+
+    public static class LogEntry {
+
+        private final long timestampMicros;
+
+        private final Map<String, Object> fields;
+
+        LogEntry(final long timestampMicros, final Map<String, Object> fields) {
+            this.timestampMicros = timestampMicros;
+            this.fields = fields;
+        }
+
+        public long getTimestampMicros() {
+            return timestampMicros;
+        }
+
+        public Map<String, Object> getFields() {
+            return fields;
+        }
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/SpanMapper.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/SpanMapper.java
similarity index 63%
rename from geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/SpanMapper.java
rename to geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/SpanMapper.java
index 9d525d9..03daf7a 100644
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/SpanMapper.java
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/SpanMapper.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.geronimo.microprofile.reporter.storage;
+package org.apache.geronimo.microprofile.reporter.storage.plugins.tracing;
 
 import static java.util.Optional.ofNullable;
 import static java.util.stream.Collectors.toList;
@@ -78,10 +78,10 @@
             return null;
         }
         try {
-            final Collection<LogEntry> logs = ofNullable((Collection<?>) getLogs.invoke(span))
+            final Collection<SpanEntry.LogEntry> logs = ofNullable((Collection<?>) getLogs.invoke(span))
                     .map(it -> it.stream().map(log -> {
                         try {
-                            return new LogEntry(Long.class.cast(logGetTimestampMicros.invoke(log)), Map.class.cast(logGetFields.invoke(log)));
+                            return new SpanEntry.LogEntry(Long.class.cast(logGetTimestampMicros.invoke(log)), Map.class.cast(logGetFields.invoke(log)));
                         } catch (final IllegalAccessException e) {
                             throw new IllegalStateException(e);
                         } catch (final InvocationTargetException e) {
@@ -108,86 +108,4 @@
     private static String stringify(final Object value) {
         return value == null ? null : String.valueOf(value);
     }
-
-    public static class LogEntry {
-
-        private final long timestampMicros;
-
-        private final Map<String, Object> fields;
-
-        private LogEntry(final long timestampMicros, final Map<String, Object> fields) {
-            this.timestampMicros = timestampMicros;
-            this.fields = fields;
-        }
-
-        public long getTimestampMicros() {
-            return timestampMicros;
-        }
-
-        public Map<String, Object> getFields() {
-            return fields;
-        }
-    }
-
-    public static class SpanEntry {
-        private final String spanId;
-        private final String traceId;
-        private final String parentId;
-        private final String name;
-        private final long timestamp;
-        private final long duration;
-        private final String kind;
-        private final Map<String, Object> tags;
-        private final Collection<LogEntry> getLogs;
-
-        private SpanEntry(final String spanId, final String traceId, final String parentId, final String name,
-                final long timestamp, final long duration, final String kind, final Map<String, Object> tags,
-                final Collection<LogEntry> getLogs) {
-            this.spanId = spanId;
-            this.traceId = traceId;
-            this.parentId = parentId;
-            this.name = name;
-            this.timestamp = timestamp;
-            this.duration = duration;
-            this.kind = kind;
-            this.tags = tags;
-            this.getLogs = getLogs;
-        }
-
-        public String getSpanId() {
-            return spanId;
-        }
-
-        public String getTraceId() {
-            return traceId;
-        }
-
-        public String getParentId() {
-            return parentId;
-        }
-
-        public String getName() {
-            return name;
-        }
-
-        public long getTimestamp() {
-            return timestamp;
-        }
-
-        public long getDuration() {
-            return duration;
-        }
-
-        public String getKind() {
-            return kind;
-        }
-
-        public Map<String, Object> getTags() {
-            return tags;
-        }
-
-        public Collection<LogEntry> getGetLogs() {
-            return getLogs;
-        }
-    }
 }
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/TracingExtension.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/TracingExtension.java
new file mode 100644
index 0000000..d741727
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/TracingExtension.java
@@ -0,0 +1,69 @@
+/**
+ * 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.plugins.tracing;
+
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AfterDeploymentValidation;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+
+import org.apache.geronimo.microprofile.reporter.storage.data.MicroprofileDatabase;
+
+public class TracingExtension implements Extension {
+    private boolean active;
+    private Class<?> finishedSpan;
+    private TracingService service;
+
+    public boolean isActive() {
+        return active;
+    }
+
+    void onStart(@Observes final BeforeBeanDiscovery beforeBeanDiscovery) {
+        final ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try {
+            loader.loadClass("io.opentracing.Span");
+            finishedSpan = loader.loadClass("org.apache.geronimo.microprofile.opentracing.common.impl.FinishedSpan");
+            active = true;
+        } catch (final ClassNotFoundException e) {
+            // no-op
+        }
+    }
+
+    void addObserver(@Observes final AfterBeanDiscovery afterBeanDiscovery) {
+        if (active) {
+            // like that to avoid to require tracing in the app
+            afterBeanDiscovery.addObserverMethod()
+                              .observedType(finishedSpan)
+                              .notifyWith(e -> service.onSpan(e.getEvent()));
+        }
+    }
+
+    void createService(@Observes final AfterDeploymentValidation afterDeploymentValidation, final BeanManager manager) {
+        if (active) {
+            service = new TracingService(lookup(manager, MicroprofileDatabase.class), lookup(manager, SpanMapper.class));
+        }
+    }
+
+    // only lookup normal bean so ignore cc
+    private <T> T lookup(final BeanManager beanManager, final Class<T> type) {
+        final Bean<?> bean = beanManager.resolve(beanManager.getBeans(type));
+        return type.cast(beanManager.getReference(bean, type, beanManager.createCreationalContext(null)));
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/TracingService.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/TracingService.java
new file mode 100644
index 0000000..873f5dd
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/plugins/tracing/TracingService.java
@@ -0,0 +1,48 @@
+/**
+ * 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.plugins.tracing;
+
+import javax.enterprise.inject.Vetoed;
+
+import org.apache.geronimo.microprofile.opentracing.common.impl.FinishedSpan;
+import org.apache.geronimo.microprofile.reporter.storage.data.MicroprofileDatabase;
+
+import io.opentracing.Span;
+
+@Vetoed
+class TracingService {
+    private final MicroprofileDatabase database;
+    private final SpanMapper mapper;
+
+    TracingService(final MicroprofileDatabase database, final SpanMapper mapper) {
+        this.database = database;
+        this.mapper = mapper;
+    }
+
+    void onSpan(final Object event) {
+        final FinishedSpan finishedSpan = FinishedSpan.class.cast(event);
+        final Span value = finishedSpan.getSpan();
+        if (!value.getClass().getName().equals("org.apache.geronimo.microprofile.opentracing.common.impl.SpanImpl")) {
+            return;
+        }
+        final SpanEntry mapped = mapper.map(value);
+        if (mapped == null) {
+            return;
+        }
+        database.getSpans().add(mapped);
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/TemplateHelper.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/templating/TemplateHelper.java
similarity index 99%
rename from geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/TemplateHelper.java
rename to geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/templating/TemplateHelper.java
index 4eb8849..10b32df 100644
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/TemplateHelper.java
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/templating/TemplateHelper.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.geronimo.microprofile.reporter.storage;
+package org.apache.geronimo.microprofile.reporter.storage.templating;
 
 import static java.util.stream.Collectors.joining;
 
diff --git a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/TemplatingEngine.java b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/templating/TemplatingEngine.java
similarity index 99%
rename from geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/TemplatingEngine.java
rename to geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/templating/TemplatingEngine.java
index 142e6ee..6a9c1da 100644
--- a/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/TemplatingEngine.java
+++ b/geronimo-microprofile-reporter/src/main/java/org/apache/geronimo/microprofile/reporter/storage/templating/TemplatingEngine.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.geronimo.microprofile.reporter.storage;
+package org.apache.geronimo.microprofile.reporter.storage.templating;
 
 import static java.util.Locale.ROOT;
 import static java.util.stream.Collectors.joining;
@@ -365,7 +365,7 @@
         return registry;
     }
 
-    void clean() {
+    public void clean() {
         templates.clear();
     }
 
diff --git a/geronimo-microprofile-reporter/src/main/resources/META-INF/beans.xml b/geronimo-microprofile-reporter/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..70dd4a9
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<!--
+    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.
+-->
+<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+        http://xmlns.jcp.org/xml/ns/javaee
+        http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
+       bean-discovery-mode="all"
+       version="2.0">
+  <trim/>
+</beans>
diff --git a/geronimo-microprofile-reporter/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/geronimo-microprofile-reporter/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
index d0ef3e1..9b0ba85 100644
--- a/geronimo-microprofile-reporter/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
+++ b/geronimo-microprofile-reporter/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -1 +1,2 @@
-org.apache.geronimo.microprofile.reporter.storage.HealthRegistry
+org.apache.geronimo.microprofile.reporter.storage.plugins.health.HealthRegistry
+org.apache.geronimo.microprofile.reporter.storage.plugins.tracing.TracingExtension
diff --git a/geronimo-microprofile-reporter/src/main/resources/geronimo/microprofile/reporter/health-application.html b/geronimo-microprofile-reporter/src/main/resources/geronimo/microprofile/reporter/health-application.html
index cc93320..9d1db9b 100644
--- a/geronimo-microprofile-reporter/src/main/resources/geronimo/microprofile/reporter/health-application.html
+++ b/geronimo-microprofile-reporter/src/main/resources/geronimo/microprofile/reporter/health-application.html
@@ -18,7 +18,9 @@
 
 <div>
     <h2>Overall State</h2>
-    <div class="health-check-state @if($globalStateOk,inline:green)@if($globalStateKo,inline:red)"></div>
+    <div class="health-check-state health-check-@lowercase($globalState)">
+        <span>$globalState</span>
+    </div>
 
     <h2>Checks</h2>
     <table>
@@ -26,7 +28,7 @@
         <tr><th>Name</th><th>State</th><th>Data</th></tr>
         </thead>
         <tbody>
-        @each($checks,incline:<tr><td>$$value.name</td><td>$$value.state</td><td>$$value.data</td></tr>)
+        @each($checks,inline:<tr><td>$$value.name</td><td>$$value.state</td><td>$$value.data</td></tr>)
         </tbody>
     </table>
 </div>
diff --git a/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/html/TemplatingEngineTest.java b/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/html/TemplatingEngineTest.java
index d61aaa8..5f1e0e6 100644
--- a/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/html/TemplatingEngineTest.java
+++ b/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/html/TemplatingEngineTest.java
@@ -24,7 +24,7 @@
 import java.util.Map;
 import java.util.function.Function;
 
-import org.apache.geronimo.microprofile.reporter.storage.TemplatingEngine;
+import org.apache.geronimo.microprofile.reporter.storage.templating.TemplatingEngine;
 import org.junit.Test;
 
 public class TemplatingEngineTest {
diff --git a/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/Check1.java b/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/Check1.java
new file mode 100644
index 0000000..dd77f2c
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/Check1.java
@@ -0,0 +1,32 @@
+/**
+ * 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.plugins.health;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.health.Health;
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+
+@Health
+@ApplicationScoped
+public class Check1 implements HealthCheck {
+    @Override
+    public HealthCheckResponse call() {
+        return HealthCheckResponse.named("check1").up().build();
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/Check2.java b/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/Check2.java
new file mode 100644
index 0000000..d6bd2ff
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/Check2.java
@@ -0,0 +1,32 @@
+/**
+ * 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.plugins.health;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.health.Health;
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+
+@Health
+@ApplicationScoped
+public class Check2 implements HealthCheck {
+    @Override
+    public HealthCheckResponse call() {
+        return HealthCheckResponse.named("check2").down().withData("failure", "oops for test").build();
+    }
+}
diff --git a/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthServiceTest.java b/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthServiceTest.java
new file mode 100644
index 0000000..2b0fd6d
--- /dev/null
+++ b/geronimo-microprofile-reporter/src/test/java/org/apache/geronimo/microprofile/reporter/storage/plugins/health/HealthServiceTest.java
@@ -0,0 +1,43 @@
+/**
+ * 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.plugins.health;
+
+import static org.junit.Assert.assertEquals;
+
+import javax.inject.Inject;
+
+import org.apache.meecrowave.junit.InjectRule;
+import org.apache.meecrowave.junit.MeecrowaveRule;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class HealthServiceTest {
+    @ClassRule
+    public static final MeecrowaveRule SERVER = new MeecrowaveRule();
+
+    @Rule
+    public final InjectRule injector = new InjectRule(this);
+
+    @Inject
+    private HealthService service;
+
+    @Test
+    public void ensureHealthChecksWereRegistered() {
+        assertEquals(2, service.doCheck().count());
+    }
+}
diff --git a/geronimo-microprofile-site/src/main/jbake/content/reporter.adoc b/geronimo-microprofile-site/src/main/jbake/content/reporter.adoc
index 8d40572..082da85 100644
--- a/geronimo-microprofile-site/src/main/jbake/content/reporter.adoc
+++ b/geronimo-microprofile-site/src/main/jbake/content/reporter.adoc
@@ -2,11 +2,14 @@
 :jbake-date: 2019-01-07
 :icons: font
 
-Apache Geronimo Microprofile Reporter allows to visualize spans (OpenTracing) and Metrics
+Apache Geronimo Microprofile Reporter allows to visualize spans (OpenTracing), the health of your application and Metrics
 in a small webapp.
 
 IMPORTANT: this is for test and demo purposes, this is not yet intended to be deployed in production.
 
+Also note that part of the UI are designed to be auto activated when relevant, typically, if you don't have Microprofile
+Health, the associated UI will not be active.
+
 == Dependencies
 
 [source,xml]