You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:22:26 UTC
[sling-org-apache-sling-commons-metrics] 06/23: SLING-4080 - API to
capture/measure application-level metrics
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to annotated tag org.apache.sling.commons.metrics-0.0.2
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-metrics.git
commit a41c22d453176cdfdd91431ff62ce3fe00fc4a71
Author: Chetan Mehrotra <ch...@apache.org>
AuthorDate: Tue Jan 5 15:21:34 2016 +0000
SLING-4080 - API to capture/measure application-level metrics
Add a web console plugin to display all metrics in a nice table
git-svn-id: https://svn.apache.org/repos/asf/sling/whiteboard/chetanm/metrics@1723093 13f79535-47bb-0310-9956-ffa450edef68
---
.../sling/metrics/internal/MetricPrinter.java | 346 ++++++++++++++++++++-
1 file changed, 344 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/apache/sling/metrics/internal/MetricPrinter.java b/src/main/java/org/apache/sling/metrics/internal/MetricPrinter.java
index 7ac78bd..44f467a 100644
--- a/src/main/java/org/apache/sling/metrics/internal/MetricPrinter.java
+++ b/src/main/java/org/apache/sling/metrics/internal/MetricPrinter.java
@@ -19,15 +19,31 @@
package org.apache.sling.metrics.internal;
+import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Locale;
import java.util.Map;
+import java.util.SortedMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import com.codahale.metrics.ConsoleReporter;
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
import org.apache.commons.io.output.WriterOutputStream;
import org.apache.felix.inventory.Format;
import org.apache.felix.inventory.InventoryPrinter;
@@ -45,14 +61,18 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
-@Service(value = InventoryPrinter.class)
+@Service(value = {InventoryPrinter.class, Servlet.class})
@Properties({
+ @Property(name = "felix.webconsole.label", value = "slingmetrics"),
+ @Property(name = "felix.webconsole.title", value = "Metrics"),
+ @Property(name = "felix.webconsole.category", value = "Sling"),
@Property(name = InventoryPrinter.FORMAT, value = {"TEXT" }),
@Property(name = InventoryPrinter.NAME, value = "slingmetrics"),
@Property(name = InventoryPrinter.TITLE, value = "Sling Metrics"),
@Property(name = InventoryPrinter.WEBCONSOLE, boolValue = true)
})
-public class MetricPrinter implements InventoryPrinter, ServiceTrackerCustomizer<MetricRegistry, MetricRegistry>{
+public class MetricPrinter extends HttpServlet implements
+ InventoryPrinter, ServiceTrackerCustomizer<MetricRegistry, MetricRegistry>{
/**
* Service property name which stores the MetricRegistry name as a given OSGi
* ServiceRegistry might have multiple instances of MetricRegistry
@@ -64,9 +84,16 @@ public class MetricPrinter implements InventoryPrinter, ServiceTrackerCustomizer
private ConcurrentMap<ServiceReference, MetricRegistry> registries
= new ConcurrentHashMap<ServiceReference, MetricRegistry>();
+ private TimeUnit rateUnit = TimeUnit.SECONDS;
+ private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
+ private Map<String, TimeUnit> specificDurationUnits = Collections.emptyMap();
+ private Map<String, TimeUnit> specificRateUnits = Collections.emptyMap();
+ private MetricTimeUnits timeUnit;
+
@Activate
private void activate(BundleContext context){
this.context = context;
+ this.timeUnit = new MetricTimeUnits(rateUnit, durationUnit, specificRateUnits, specificDurationUnits);
tracker = new ServiceTracker<MetricRegistry, MetricRegistry>(context, MetricRegistry.class, this);
tracker.open();
}
@@ -110,6 +137,291 @@ public class MetricPrinter implements InventoryPrinter, ServiceTrackerCustomizer
registries.remove(serviceReference);
}
+ //~----------------------------------------------< Servlet >
+
+ @Override
+ protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
+ final PrintWriter pw = resp.getWriter();
+ MetricRegistry registry = getConsolidatedRegistry();
+
+ appendMetricStatus(pw, registry);
+ addCounterDetails(pw, registry.getCounters());
+ addGaugeDetails(pw, registry.getGauges());
+ addMeterDetails(pw, registry.getMeters());
+ addTimerDetails(pw, registry.getTimers());
+ addHistogramDetails(pw, registry.getHistograms());
+ }
+
+ private static void appendMetricStatus(PrintWriter pw, MetricRegistry registry) {
+ pw.printf(
+ "<p class='statline'>Metrics: %d gauges, %d timers, %d meters, %d counters, %d histograms</p>%n",
+ registry.getGauges().size(),
+ registry.getTimers().size(),
+ registry.getMeters().size(),
+ registry.getCounters().size(),
+ registry.getHistograms().size());
+ }
+
+ private void addMeterDetails(PrintWriter pw, SortedMap<String, Meter> meters) {
+ if (meters.isEmpty()) {
+ return;
+ }
+ pw.println("<br>");
+ pw.println("<div class='table'>");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Meters</div>");
+ pw.println("<table class='nicetable'>");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th class='header'>Name</th>");
+ pw.println("<th class='header'>Count</th>");
+ pw.println("<th class='header'>Mean Rate</th>");
+ pw.println("<th class='header'>OneMinuteRate</th>");
+ pw.println("<th class='header'>FiveMinuteRate</th>");
+ pw.println("<th class='header'>FifteenMinuteRate</ th>");
+ pw.println("<th>RateUnit</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+ pw.println("<tbody>");
+
+ String rowClass = "odd";
+ for (Map.Entry<String, Meter> e : meters.entrySet()) {
+ Meter m = e.getValue();
+ String name = e.getKey();
+
+ double rateFactor = timeUnit.rateFor(name).toSeconds(1);
+ String rateUnit = "events/" + calculateRateUnit(timeUnit.rateFor(name));
+ pw.printf("<tr class='%s ui-state-default'>%n", rowClass);
+
+ pw.printf("<td>%s</td>", name);
+ pw.printf("<td>%d</td>", m.getCount());
+ pw.printf("<td>%f</td>", m.getMeanRate() * rateFactor);
+ pw.printf("<td>%f</td>", m.getOneMinuteRate() * rateFactor);
+ pw.printf("<td>%f</td>", m.getFiveMinuteRate() * rateFactor);
+ pw.printf("<td>%f</td>", m.getFifteenMinuteRate() * rateFactor);
+ pw.printf("<td>%s</td>", rateUnit);
+
+ pw.println("</tr>");
+ rowClass = "odd".equals(rowClass) ? "even" : "odd";
+ }
+
+ pw.println("</tbody>");
+ pw.println("</table>");
+ pw.println("</div>");
+ }
+
+ private void addTimerDetails(PrintWriter pw, SortedMap<String, Timer> timers) {
+ if (timers.isEmpty()) {
+ return;
+ }
+
+ pw.println("<br>");
+ pw.println("<div class='table'>");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Timers</div>");
+ pw.println("<table class='nicetable'>");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th class='header'>Name</th>");
+ pw.println("<th class='header'>Count</th>");
+ pw.println("<th class='header'>Mean Rate</th>");
+ pw.println("<th class='header'>1 min rate</th>");
+ pw.println("<th class='header'>5 mins rate</th>");
+ pw.println("<th class='header'>15 mins rate</th>");
+ pw.println("<th class='header'>50%</th>");
+ pw.println("<th class='header'>Min</th>");
+ pw.println("<th class='header'>Max</th>");
+ pw.println("<th class='header'>Mean</th>");
+ pw.println("<th class='header'>StdDev</th>");
+ pw.println("<th class='header'>75%</th>");
+ pw.println("<th class='header'>95%</th>");
+ pw.println("<th class='header'>98%</th>");
+ pw.println("<th class='header'>99%</th>");
+ pw.println("<th class='header'>999%</th>");
+ pw.println("<th>Rate Unit</th>");
+ pw.println("<th>Duration Unit</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+ pw.println("<tbody>");
+
+ String rowClass = "odd";
+ for (Map.Entry<String, Timer> e : timers.entrySet()) {
+ Timer t = e.getValue();
+ Snapshot s = t.getSnapshot();
+ String name = e.getKey();
+
+ double rateFactor = timeUnit.rateFor(name).toSeconds(1);
+ String rateUnit = "events/" + calculateRateUnit(timeUnit.rateFor(name));
+
+ double durationFactor = 1.0 / timeUnit.durationFor(name).toNanos(1);
+ String durationUnit = timeUnit.durationFor(name).toString().toLowerCase(Locale.US);
+
+ pw.printf("<tr class='%s ui-state-default'>%n", rowClass);
+
+ pw.printf("<td>%s</td>", name);
+ pw.printf("<td>%d</td>", t.getCount());
+ pw.printf("<td>%f</td>", t.getMeanRate() * rateFactor);
+ pw.printf("<td>%f</td>", t.getOneMinuteRate() * rateFactor);
+ pw.printf("<td>%f</td>", t.getFiveMinuteRate() * rateFactor);
+ pw.printf("<td>%f</td>", t.getFifteenMinuteRate() * rateFactor);
+
+ pw.printf("<td>%f</td>", s.getMedian() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMin() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMax() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMean() * durationFactor);
+ pw.printf("<td>%f</td>", s.getStdDev() * durationFactor);
+
+ pw.printf("<td>%f</td>", s.get75thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get95thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get98thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get99thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get999thPercentile() * durationFactor);
+
+ pw.printf("<td>%s</td>", rateUnit);
+ pw.printf("<td>%s</td>", durationUnit);
+
+ pw.println("</tr>");
+ rowClass = "odd".equals(rowClass) ? "even" : "odd";
+ }
+
+ pw.println("</tbody>");
+ pw.println("</table>");
+ pw.println("</div>");
+ }
+
+ private void addHistogramDetails(PrintWriter pw, SortedMap<String, Histogram> histograms) {
+ if (histograms.isEmpty()) {
+ return;
+ }
+
+ pw.println("<br>");
+ pw.println("<div class='table'>");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Histograms</div>");
+ pw.println("<table class='nicetable'>");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th class='header'>Name</th>");
+ pw.println("<th class='header'>Count</th>");
+ pw.println("<th class='header'>50%</th>");
+ pw.println("<th class='header'>Min</th>");
+ pw.println("<th class='header'>Max</th>");
+ pw.println("<th class='header'>Mean</th>");
+ pw.println("<th class='header'>StdDev</th>");
+ pw.println("<th class='header'>75%</th>");
+ pw.println("<th class='header'>95%</th>");
+ pw.println("<th class='header'>98%</th>");
+ pw.println("<th class='header'>99%</th>");
+ pw.println("<th class='header'>999%</th>");
+ pw.println("<th>Duration Unit</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+ pw.println("<tbody>");
+
+ String rowClass = "odd";
+ for (Map.Entry<String, Histogram> e : histograms.entrySet()) {
+ Histogram h = e.getValue();
+ Snapshot s = h.getSnapshot();
+ String name = e.getKey();
+
+ double durationFactor = 1.0 / timeUnit.durationFor(name).toNanos(1);
+ String durationUnit = timeUnit.durationFor(name).toString().toLowerCase(Locale.US);
+ pw.printf("<tr class='%s ui-state-default'>%n", rowClass);
+
+ pw.printf("<td>%s</td>", name);
+ pw.printf("<td>%d</td>", h.getCount());
+ pw.printf("<td>%f</td>", s.getMedian() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMin() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMax() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMean() * durationFactor);
+ pw.printf("<td>%f</td>", s.getStdDev() * durationFactor);
+
+ pw.printf("<td>%f</td>", s.get75thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get95thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get98thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get99thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get999thPercentile() * durationFactor);
+
+ pw.printf("<td>%s</td>", durationUnit);
+
+ pw.println("</tr>");
+ rowClass = "odd".equals(rowClass) ? "even" : "odd";
+ }
+
+ pw.println("</tbody>");
+ pw.println("</table>");
+ pw.println("</div>");
+ }
+
+ private void addCounterDetails(PrintWriter pw, SortedMap<String, Counter> counters) {
+ if (counters.isEmpty()) {
+ return;
+ }
+ pw.println("<br>");
+ pw.println("<div class='table'>");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Counters</div>");
+ pw.println("<table class='nicetable'>");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th class='header'>Name</th>");
+ pw.println("<th class='header'>Count</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+ pw.println("<tbody>");
+
+ String rowClass = "odd";
+ for (Map.Entry<String, Counter> e : counters.entrySet()) {
+ Counter c = e.getValue();
+ String name = e.getKey();
+
+ pw.printf("<tr class='%s ui-state-default'>%n", rowClass);
+
+ pw.printf("<td>%s</td>", name);
+ pw.printf("<td>%d</td>", c.getCount());
+
+ pw.println("</tr>");
+ rowClass = "odd".equals(rowClass) ? "even" : "odd";
+ }
+
+ pw.println("</tbody>");
+ pw.println("</table>");
+ pw.println("</div>");
+ }
+
+ private void addGaugeDetails(PrintWriter pw, SortedMap<String, Gauge> gauges) {
+ if (gauges.isEmpty()) {
+ return;
+ }
+
+ pw.println("<br>");
+ pw.println("<div class='table'>");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Guages</div>");
+ pw.println("<table class='nicetable'>");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th class='header'>Name</th>");
+ pw.println("<th class='header'>Value</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+ pw.println("<tbody>");
+
+ String rowClass = "odd";
+ for (Map.Entry<String, Gauge> e : gauges.entrySet()) {
+ Gauge g = e.getValue();
+ String name = e.getKey();
+
+ pw.printf("<tr class='%s ui-state-default'>%n", rowClass);
+
+ pw.printf("<td>%s</td>", name);
+ pw.printf("<td>%s</td>", g.getValue());
+
+ pw.println("</tr>");
+ rowClass = "odd".equals(rowClass) ? "even" : "odd";
+ }
+
+ pw.println("</tbody>");
+ pw.println("</table>");
+ pw.println("</div>");
+ }
+
+
//~----------------------------------------------< internal >
private MetricRegistry getConsolidatedRegistry() {
@@ -130,4 +442,34 @@ public class MetricPrinter implements InventoryPrinter, ServiceTrackerCustomizer
}
return registry;
}
+
+ private static String calculateRateUnit(TimeUnit unit) {
+ final String s = unit.toString().toLowerCase(Locale.US);
+ return s.substring(0, s.length() - 1);
+ }
+
+ private static class MetricTimeUnits {
+ private final TimeUnit defaultRate;
+ private final TimeUnit defaultDuration;
+ private final Map<String, TimeUnit> rateOverrides;
+ private final Map<String, TimeUnit> durationOverrides;
+
+ MetricTimeUnits(TimeUnit defaultRate,
+ TimeUnit defaultDuration,
+ Map<String, TimeUnit> rateOverrides,
+ Map<String, TimeUnit> durationOverrides) {
+ this.defaultRate = defaultRate;
+ this.defaultDuration = defaultDuration;
+ this.rateOverrides = rateOverrides;
+ this.durationOverrides = durationOverrides;
+ }
+
+ public TimeUnit durationFor(String name) {
+ return durationOverrides.containsKey(name) ? durationOverrides.get(name) : defaultDuration;
+ }
+
+ public TimeUnit rateFor(String name) {
+ return rateOverrides.containsKey(name) ? rateOverrides.get(name) : defaultRate;
+ }
+ }
}
--
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.