/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.codahale.helios; import java.io.IOException; import java.util.Map; import java.util.Map.Entry; import java.util.SortedMap; import java.util.concurrent.TimeUnit; import org.helios.apmrouter.metric.MetricType; import org.helios.apmrouter.trace.ITracer; import org.helios.apmrouter.trace.TracerFactory; import org.helios.apmrouter.util.SystemClock; import com.yammer.metrics.Metrics; import com.yammer.metrics.core.Counter; import com.yammer.metrics.core.Gauge; import com.yammer.metrics.core.Histogram; import com.yammer.metrics.core.Metered; import com.yammer.metrics.core.Metric; import com.yammer.metrics.core.MetricName; import com.yammer.metrics.core.MetricPredicate; import com.yammer.metrics.core.MetricProcessor; import com.yammer.metrics.core.MetricsRegistry; import com.yammer.metrics.core.MetricsRegistryListener; import com.yammer.metrics.core.Sampling; import com.yammer.metrics.core.Summarizable; import com.yammer.metrics.core.Timer; import com.yammer.metrics.reporting.AbstractPollingReporter; import com.yammer.metrics.reporting.MetricDispatcher; import com.yammer.metrics.stats.Snapshot; /** * <p>Title: HeliosReporter</p> * <p>Description: Codahale metrics reporter for Helios APMRouter</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.codahale.helios.HeliosReporter</code></p> */ public class HeliosReporter extends AbstractPollingReporter implements MetricProcessor<Long>, MetricsRegistryListener { /** The tracer used by this reporter */ protected final ITracer tracer; /** The provided metric predicate */ protected final MetricPredicate predicate; /** The metric dispatcher */ protected final MetricDispatcher dispatcher = new MetricDispatcher(); /** Indicates if metrics should be mapped */ protected final boolean mappedMetrics; /** The default reporter name */ public static final String DEFAULT_NAME = "helios-reporter"; /** * Enables the helios reporter to send data to the apmrouter server with the specified period. * @param metricsRegistry the metrics registry * @param period the period between successive outputs * @param unit the time unit of {@code period} * @param predicate filters metrics to be reported * @param mappedMetrics if true, metrics will be mapped, if false, they will be flat */ public static void enable(MetricsRegistry metricsRegistry, long period, TimeUnit unit, MetricPredicate predicate, boolean mappedMetrics) { HeliosReporter reporter = new HeliosReporter(metricsRegistry, predicate, DEFAULT_NAME, mappedMetrics); reporter.start(period, unit); metricsRegistry.addListener(reporter); } /** * Creates a new HeliosReporter */ public HeliosReporter() { this(Metrics.defaultRegistry(), null, DEFAULT_NAME, true); } /** * Creates a new HeliosReporter * @param registry the {@link MetricsRegistry} containing the metrics this reporter will report * @param predicate The metric filter * @param name The name of this reporter * @param mappedMetrics If true, metrics will be mapped, if false, they will be flat */ public HeliosReporter(MetricsRegistry registry, MetricPredicate predicate, String name, boolean mappedMetrics) { super(registry==null ? Metrics.defaultRegistry() : registry, name); this.mappedMetrics = mappedMetrics; tracer = TracerFactory.getTracer(); this.predicate = predicate==null ? MetricPredicate.ALL : predicate; this.getMetricsRegistry().addListener(this); } /** * Sanitizes a metric name * @param name The name to sanitize * @param subNames An optional array of additional sub-names to be appended to the returned array * @return the helios tracer namespace */ public String[] ns(MetricName name, String...subNames) { return mappedMetrics ? nsMapped(name, subNames) : nsFlat(name, subNames); } /** * Sanitizes a metric name to flat metric names * @param name The name to sanitize * @param subNames An optional array of additional sub-names to be appended to the returned array * @return the helios tracer namespace */ public String[] nsFlat(MetricName name, String...subNames) { boolean scope = name.hasScope(); int baseLength = scope ? 4 : 3; String[] ns = new String[baseLength+subNames.length]; ns[0] = name.getDomain(); ns[1] = name.getType(); if(scope) { ns[2] = name.getScope(); ns[3] = name.getName(); } else { ns[2] = name.getName(); } for(int i = 0; i < subNames.length; i++) { ns[i+baseLength] = subNames[i]; } return ns; } /** * Sanitizes a metric name to mapped metric names * @param name The name to sanitize * @param subNames An optional array of additional sub-names to be appended to the returned array, * although for mapped, only the first subName is appended to the metric name. * @return the helios tracer namespace */ public String[] nsMapped(MetricName name, String...subNames) { boolean scope = name.hasScope(); int baseLength = scope ? 4 : 3; String[] ns = new String[baseLength+(subNames.length==0 ? 0 : 1)]; ns[0] = "domain=" + name.getDomain(); ns[1] = "type=" + name.getType(); if(scope) { ns[2] = "scope=" + name.getScope(); ns[3] = "name=" + name.getName(); } else { ns[2] = "name=" + name.getName(); } if(subNames.length>0) { ns[baseLength] = "aggregate=" + subNames[0]; } return ns; } /** * {@inheritDoc} * @see com.yammer.metrics.core.MetricProcessor#processMeter(com.yammer.metrics.core.MetricName, com.yammer.metrics.core.Metered, java.lang.Object) */ @Override public void processMeter(MetricName name, Metered meter, Long epoch) throws Exception { final String[] namespace = ns(name, "Meter"); tracer.traceGauge(meter.getCount(), "Count", namespace); tracer.traceGauge((long)meter.getMeanRate(), "MeanRate", namespace); tracer.traceGauge((long)meter.getOneMinuteRate(), "1MinuteRate", namespace); tracer.traceGauge((long)meter.getFiveMinuteRate(), "5MinuteRate", namespace); tracer.traceGauge((long)meter.getFifteenMinuteRate(), "15MinuteRate", namespace); } /** * {@inheritDoc} * @see com.yammer.metrics.core.MetricProcessor#processCounter(com.yammer.metrics.core.MetricName, com.yammer.metrics.core.Counter, java.lang.Object) */ @Override public void processCounter(MetricName name, Counter counter, Long context) throws Exception { //tracer.traceIncrement(counter.getCount(), "count", ns(name)); //tracer.traceGauge(counter.getCount(), "Count", ns(name)); } /** * {@inheritDoc} * @see com.yammer.metrics.core.MetricProcessor#processHistogram(com.yammer.metrics.core.MetricName, com.yammer.metrics.core.Histogram, java.lang.Object) */ @Override public void processHistogram(MetricName name, Histogram histogram, Long context) throws Exception { final String[] ns = ns(name, "Histogram"); sendSummarizable(ns, histogram); sendSummarizable(ns, histogram); sendSampling(ns, histogram); } /** * {@inheritDoc} * @see com.yammer.metrics.core.MetricProcessor#processTimer(com.yammer.metrics.core.MetricName, com.yammer.metrics.core.Timer, java.lang.Object) */ @Override public void processTimer(MetricName name, Timer timer, Long epoch) throws Exception { processMeter(name, timer, epoch); sendSummarizable(ns(name), timer); sendSampling(ns(name, "Sampling"), timer); } /** * Sends all the metrics associated with the passed {@link Summarizable} * @param namespace The metric namespace * @param metric The {@link Summarizable} to trace * @throws IOException thrown on any tracing error */ protected void sendSummarizable(String[] namespace, Summarizable metric) throws IOException { tracer.traceGauge((long)metric.getMin(), "Min", namespace); tracer.traceGauge((long)metric.getMax(), "Max", namespace); tracer.traceGauge((long)metric.getMean(), "Mean", namespace); tracer.traceGauge((long)metric.getStdDev(), "Stddev", namespace); } /** * Sends all the metrics associated with the passed {@link Sampling} * @param namespace The metric namespace * @param metric The {@link Sampling} to trace * @throws IOException thrown on any tracing error */ protected void sendSampling(String[] namespace, Sampling metric) throws IOException { final Snapshot snapshot = metric.getSnapshot(); tracer.traceGauge((long)snapshot.getMedian(), "Median", namespace); tracer.traceGauge((long)snapshot.get75thPercentile(), "75percentile", namespace); tracer.traceGauge((long)snapshot.get95thPercentile(), "95percentile", namespace); tracer.traceGauge((long)snapshot.get98thPercentile(), "98percentile", namespace); tracer.traceGauge((long)snapshot.get99thPercentile(), "99percentile", namespace); tracer.traceGauge((long)snapshot.get999thPercentile(), "999percentile", namespace); } /** * {@inheritDoc} * @see com.yammer.metrics.core.MetricProcessor#processGauge(com.yammer.metrics.core.MetricName, com.yammer.metrics.core.Gauge, java.lang.Object) */ @Override public void processGauge(MetricName name, Gauge<?> gauge, Long context) throws Exception { tracer.trace(gauge.getValue(), "value", MetricType.LONG_GAUGE, ns(name)); } /** * {@inheritDoc} * @see com.yammer.metrics.reporting.AbstractPollingReporter#run() */ @Override public void run() { final Map<String,SortedMap<MetricName,Metric>> metricMap = getMetricsRegistry().getGroupedMetrics(predicate); if(!metricMap.isEmpty()) { for (Map.Entry<String,SortedMap<MetricName,Metric>> entry : metricMap.entrySet()) { for (Entry<MetricName, Metric> subEntry : entry.getValue().entrySet()) { //System.out.println("Processing MetricName [" + subEntry.getKey() + "]"); final Metric metric = subEntry.getValue(); if (metric != null) { try { dispatcher.dispatch(subEntry.getValue(), subEntry.getKey(), this, SystemClock.unixTime()); } catch (Exception ignored) { System.err.println("Error printing regular metrics. Stack trace follows"); ignored.printStackTrace(System.err); } } } } } } /** * {@inheritDoc} * @see com.yammer.metrics.core.MetricsRegistryListener#onMetricAdded(com.yammer.metrics.core.MetricName, com.yammer.metrics.core.Metric) */ @Override public void onMetricAdded(MetricName name, Metric metric) { if (metric != null) { try { dispatcher.dispatch(metric, name, this, SystemClock.unixTime()); } catch (Exception e) { System.err.println("Error processing " + name + ". Stack trace fillows:"); e.printStackTrace(System.err); } } } /** * {@inheritDoc} * @see com.yammer.metrics.core.MetricsRegistryListener#onMetricRemoved(com.yammer.metrics.core.MetricName) */ @Override public void onMetricRemoved(MetricName name) { // TODO Auto-generated method stub } }