All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavefront.internal.reporter.WavefrontInternalReporter Maven / Gradle / Ivy

The newest version!
package com.wavefront.internal.reporter;

import com.wavefront.internal.EntitiesInstantiator;
import com.wavefront.sdk.common.Constants;
import com.wavefront.sdk.common.NamedThreadFactory;
import com.wavefront.sdk.common.WavefrontSender;
import com.wavefront.sdk.common.metrics.WavefrontSdkDeltaCounter;
import com.wavefront.sdk.common.metrics.WavefrontSdkMetricsRegistry;
import com.wavefront.sdk.entities.histograms.HistogramGranularity;
import com.wavefront.sdk.entities.histograms.WavefrontHistogramImpl;

import java.io.Closeable;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import io.dropwizard.metrics5.Clock;
import io.dropwizard.metrics5.Counter;
import io.dropwizard.metrics5.DeltaCounter;
import io.dropwizard.metrics5.Gauge;
import io.dropwizard.metrics5.Histogram;
import io.dropwizard.metrics5.Meter;
import io.dropwizard.metrics5.Metered;
import io.dropwizard.metrics5.Metric;
import io.dropwizard.metrics5.MetricAttribute;
import io.dropwizard.metrics5.MetricFilter;
import io.dropwizard.metrics5.MetricName;
import io.dropwizard.metrics5.MetricRegistry;
import io.dropwizard.metrics5.ScheduledReporter;
import io.dropwizard.metrics5.SlidingTimeWindowArrayReservoir;
import io.dropwizard.metrics5.Snapshot;
import io.dropwizard.metrics5.Timer;
import io.dropwizard.metrics5.WavefrontHistogram;
import io.dropwizard.metrics5.jvm.BufferPoolMetricSet;
import io.dropwizard.metrics5.jvm.ClassLoadingGaugeSet;
import io.dropwizard.metrics5.jvm.FileDescriptorRatioGauge;
import io.dropwizard.metrics5.jvm.GarbageCollectorMetricSet;
import io.dropwizard.metrics5.jvm.MemoryUsageGaugeSet;
import io.dropwizard.metrics5.jvm.ThreadStatesGaugeSet;

import static com.wavefront.sdk.common.Utils.getSemVerGauge;

/**
 * Wavefront Internal Reporter that reports metrics and histograms to Wavefront via proxy or direct
 * ingestion. This internal reporter supports reporter level as well as metric/histogram level point
 * tags.
 *
 * @author Sushant Dewan ([email protected]).
 */
public class WavefrontInternalReporter implements Reporter, EntitiesInstantiator, Closeable {
  private static final Logger logger =
      Logger.getLogger(WavefrontInternalReporter.class.getCanonicalName());

  private final ScheduledReporter scheduledReporter;
  private final MetricRegistry internalRegistry;
  public final static String DEFAULT_SOURCE_WF_INTERNAL_REPORTER = "wavefront-internal-reporter";

  /**
   * A builder for {@link WavefrontInternalReporter} instances. Defaults to not using a prefix,
   * using the default clock, a host named "unknown", no point Tags, and not filtering any metrics.
   */
  public static class Builder {
    private String prefix;
    private String source;
    private final Map reporterPointTags;
    private final Set histogramGranularities;
    private boolean includeJvmMetrics = false;
    private Clock clock = Clock.defaultClock();
    private ScheduledExecutorService scheduledExecutorService =  Executors.newScheduledThreadPool(1,
        new NamedThreadFactory("wavefront-internal-reporter").setDaemon(true));

    private static String getDefaultSource() {
      try {
        return InetAddress.getLocalHost().getHostName();
      } catch (UnknownHostException ex) {
        return DEFAULT_SOURCE_WF_INTERNAL_REPORTER;
      }
    }

    public Builder() {
      this.prefix = null;
      this.source = getDefaultSource();
      this.reporterPointTags = new HashMap<>();
      this.histogramGranularities = new HashSet<>();
    }

    public Builder withScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
      this.scheduledExecutorService = scheduledExecutorService;
      return this;
    }

    public Builder withClock(Clock clock) {
      this.clock = clock;
      return this;
    }

    /**
     * Prefix all metric names with the given string. Defaults to null.
     *
     * @param prefix the prefix for all metric names
     * @return {@code this}
     */
    public Builder prefixedWith(String prefix) {
      this.prefix = prefix;
      return this;
    }

    /**
     * Set the source for this reporter. This is equivalent to withHost.
     *
     * @param source the host for all metrics
     * @return {@code this}
     */
    public Builder withSource(String source) {
      this.source = source;
      return this;
    }

    /**
     * Set the Point Tags for this reporter.
     *
     * @param reporterPointTags the pointTags Map for all metrics
     * @return {@code this}
     */
    public Builder withReporterPointTags(Map reporterPointTags) {
      this.reporterPointTags.putAll(reporterPointTags);
      return this;
    }

    /**
     * Set a point tag for this reporter.
     *
     * @param tagKey the key of the Point Tag
     * @param tagVal the value of the Point Tag
     * @return {@code this}
     */
    public Builder withReporterPointTag(String tagKey, String tagVal) {
      this.reporterPointTags.put(tagKey, tagVal);
      return this;
    }

    /**
     * Report histogram distributions aggregated into minute intervals
     *
     * @return {@code this}
     */
    public Builder reportMinuteDistribution() {
      this.histogramGranularities.add(HistogramGranularity.MINUTE);
      return this;
    }

    /**
     * Report histogram distributions aggregated into hour intervals
     *
     * @return {@code this}
     */
    public Builder reportHourDistribution() {
      this.histogramGranularities.add(HistogramGranularity.HOUR);
      return this;
    }

    /**
     * Report histogram distributions aggregated into day intervals
     *
     * @return {@code this}
     */
    public Builder reportDayDistribution() {
      this.histogramGranularities.add(HistogramGranularity.DAY);
      return this;
    }

    /**
     * Report JVM metrics for the JVM inside which this reporter is running.
     *
     * @return {@code this}
     */
    public Builder includeJvmMetrics() {
      this.includeJvmMetrics = true;
      return this;
    }

    /**
     * Builds a {@link WavefrontInternalReporter} with the given properties, sending metrics and
     * histograms directly to a given Wavefront server using either proxy or direct ingestion APIs.
     *
     * @param wavefrontSender Wavefront Sender to send various Wavefront atoms.
     * @return a {@link WavefrontInternalReporter}
     */
    public WavefrontInternalReporter build(WavefrontSender wavefrontSender) {
      return new WavefrontInternalReporter(new MetricRegistry(), wavefrontSender,
          prefix, source, reporterPointTags, histogramGranularities, includeJvmMetrics, clock,
          scheduledExecutorService);
    }
  }

  private final WavefrontSender wavefrontSender;
  private final Clock clock;
  private final String prefix;
  private final String source;
  private final Map reporterPointTags;
  private final Set histogramGranularities;
  private final WavefrontSdkMetricsRegistry sdkMetricsRegistry;

  private final WavefrontSdkDeltaCounter gaugesReported;
  private final WavefrontSdkDeltaCounter deltaCountersReported;
  private final WavefrontSdkDeltaCounter countersReported;
  private final WavefrontSdkDeltaCounter wfHistogramsReported;
  private final WavefrontSdkDeltaCounter histogramsReported;
  private final WavefrontSdkDeltaCounter metersReported;
  private final WavefrontSdkDeltaCounter timersReported;
  private final WavefrontSdkDeltaCounter reportErrors;

  private WavefrontInternalReporter(MetricRegistry registry,
                                    WavefrontSender wavefrontSender,
                                    String prefix,
                                    String source,
                                    Map reporterPointTags,
                                    Set histogramGranularities,
                                    boolean includeJvmMetrics,
                                    Clock clock,
                                    ScheduledExecutorService scheduledExecutorService) {
    this.clock = clock;
    internalRegistry = registry;
    scheduledReporter = new ScheduledReporter(registry, "wavefront-reporter", MetricFilter.ALL,
        TimeUnit.SECONDS, TimeUnit.MILLISECONDS, scheduledExecutorService, true, Collections.emptySet()) {

      /**
       * Called periodically by the polling thread. Subclasses should report all the given metrics.
       *
       * @param gauges     all of the gauges in the registry
       * @param counters   all of the counters in the registry
       * @param histograms all of the histograms in the registry
       * @param meters     all of the meters in the registry
       * @param timers     all of the timers in the registry
       */
      @Override
      public void report(SortedMap> gauges,
                         SortedMap counters,
                         SortedMap histograms,
                         SortedMap meters,
                         SortedMap timers) {
        try {
          final long timestamp = clock.getTime();
          for (Map.Entry> entry : gauges.entrySet()) {
            if (entry.getValue().getValue() instanceof Number) {
              reportGauge(entry.getKey(), (Gauge) entry.getValue(), timestamp);
              gaugesReported.inc();
            }
          }

          for (Map.Entry entry : counters.entrySet()) {
            reportCounter(entry.getKey(), entry.getValue(), timestamp);
            if (entry.getValue() instanceof DeltaCounter) {
              deltaCountersReported.inc();
            } else {
              countersReported.inc();
            }
          }

          for (Map.Entry entry : histograms.entrySet()) {
            reportHistogram(entry.getKey(), entry.getValue(), timestamp);
            if (entry.getValue() instanceof WavefrontHistogram) {
              wfHistogramsReported.inc();
            } else {
              histogramsReported.inc();
            }
          }

          for (Map.Entry entry : meters.entrySet()) {
            reportMetered(entry.getKey(), entry.getValue(), timestamp);
            metersReported.inc();
          }

          for (Map.Entry entry : timers.entrySet()) {
            reportTimer(entry.getKey(), entry.getValue(), timestamp);
            timersReported.inc();
          }
        } catch (IOException e) {
          reportErrors.inc();
          logger.log(Level.WARNING, "Unable to report to Wavefront", e);
        }
      }
    };

    this.wavefrontSender = wavefrontSender;
    this.prefix = prefix;
    this.source = source;
    this.reporterPointTags = reporterPointTags;
    this.histogramGranularities = histogramGranularities;

    if (includeJvmMetrics) {
      tryRegister(registry, "jvm.uptime",
          (Gauge) () -> ManagementFactory.getRuntimeMXBean().getUptime());
      tryRegister(registry, "jvm.current_time", (Gauge) clock::getTime);
      tryRegister(registry, "jvm.classes", new ClassLoadingGaugeSet());
      tryRegister(registry, "jvm.fd_usage", new FileDescriptorRatioGauge());
      tryRegister(registry, "jvm.buffers",
          new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer()));
      tryRegister(registry, "jvm.gc", new GarbageCollectorMetricSet());
      tryRegister(registry, "jvm.memory", new MemoryUsageGaugeSet());
      tryRegister(registry, "jvm.thread-states", new ThreadStatesGaugeSet());
    }

    sdkMetricsRegistry = new WavefrontSdkMetricsRegistry.Builder(this.wavefrontSender).
        prefix(Constants.SDK_METRIC_PREFIX + ".internal_reporter").
        source(this.source).
        tags(this.reporterPointTags).
        build();

    double sdkVersion = getSemVerGauge("wavefront-internal-reporter-java");
    sdkMetricsRegistry.newGauge("version", () -> sdkVersion);

    gaugesReported = sdkMetricsRegistry.newDeltaCounter("gauges.reported");
    deltaCountersReported = sdkMetricsRegistry.newDeltaCounter("delta_counters.reported");
    countersReported = sdkMetricsRegistry.newDeltaCounter("counters.reported");
    wfHistogramsReported = sdkMetricsRegistry.newDeltaCounter("wavefront_histograms.reported");
    histogramsReported = sdkMetricsRegistry.newDeltaCounter("histograms.reported");
    metersReported = sdkMetricsRegistry.newDeltaCounter("meters.reported");
    timersReported = sdkMetricsRegistry.newDeltaCounter("timers.reported");
    reportErrors = sdkMetricsRegistry.newDeltaCounter("errors");
  }

  private  void tryRegister(MetricRegistry registry, String name, T metric) {
    // Dropwizard services automatically include JVM metrics,
    // so adding them again would throw an exception
    try {
      registry.register(name, metric);
    } catch (IllegalArgumentException e) {
      logger.log(Level.INFO, e.getMessage());
    }
  }

  private void reportTimer(MetricName metricName, Timer timer, long timestamp)
      throws IOException {
    final Snapshot snapshot = timer.getSnapshot();
    sendIfEnabled(MetricAttribute.MAX, metricName,
        scheduledReporter.convertDuration(snapshot.getMax()), timestamp);
    sendIfEnabled(MetricAttribute.MEAN, metricName,
        scheduledReporter.convertDuration(snapshot.getMean()), timestamp);
    sendIfEnabled(MetricAttribute.MIN, metricName,
        scheduledReporter.convertDuration(snapshot.getMin()), timestamp);
    sendIfEnabled(MetricAttribute.STDDEV, metricName,
        scheduledReporter.convertDuration(snapshot.getStdDev()), timestamp);
    sendIfEnabled(MetricAttribute.P50, metricName,
        scheduledReporter.convertDuration(snapshot.getMedian()), timestamp);
    sendIfEnabled(MetricAttribute.P75, metricName,
        scheduledReporter.convertDuration(snapshot.get75thPercentile()), timestamp);
    sendIfEnabled(MetricAttribute.P95, metricName,
        scheduledReporter.convertDuration(snapshot.get95thPercentile()), timestamp);
    sendIfEnabled(MetricAttribute.P98, metricName,
        scheduledReporter.convertDuration(snapshot.get98thPercentile()), timestamp);
    sendIfEnabled(MetricAttribute.P99, metricName,
        scheduledReporter.convertDuration(snapshot.get99thPercentile()), timestamp);
    sendIfEnabled(MetricAttribute.P999, metricName,
        scheduledReporter.convertDuration(snapshot.get999thPercentile()), timestamp);

    reportMetered(metricName, timer, timestamp);
  }

  private void reportMetered(MetricName metricName, Metered meter, long timestamp)
      throws IOException {
    sendIfEnabled(MetricAttribute.COUNT, metricName, meter.getCount(), timestamp);
    sendIfEnabled(MetricAttribute.M1_RATE, metricName,
        scheduledReporter.convertRate(meter.getOneMinuteRate()), timestamp);
    sendIfEnabled(MetricAttribute.M5_RATE, metricName,
        scheduledReporter.convertRate(meter.getFiveMinuteRate()), timestamp);
    sendIfEnabled(MetricAttribute.M15_RATE, metricName,
        scheduledReporter.convertRate(meter.getFifteenMinuteRate()), timestamp);
    sendIfEnabled(MetricAttribute.MEAN_RATE, metricName,
        scheduledReporter.convertRate(meter.getMeanRate()), timestamp);
  }

  private void reportHistogram(MetricName metricName, Histogram histogram, long timestamp)
      throws IOException {
    if (histogram instanceof WavefrontHistogram) {
      String histogramName = prefixAndSanitize(metricName.getKey());
      for (WavefrontHistogramImpl.Distribution distribution :
          ((WavefrontHistogram) histogram).flushDistributions()) {
        wavefrontSender.sendDistribution(histogramName, distribution.centroids,
            histogramGranularities, distribution.timestamp, source, getMetricTags(metricName));
      }
    } else {
      final Snapshot snapshot = histogram.getSnapshot();
      sendIfEnabled(MetricAttribute.COUNT, metricName, histogram.getCount(), timestamp);
      sendIfEnabled(MetricAttribute.MAX, metricName, snapshot.getMax(), timestamp);
      sendIfEnabled(MetricAttribute.MEAN, metricName, snapshot.getMean(), timestamp);
      sendIfEnabled(MetricAttribute.MIN, metricName, snapshot.getMin(), timestamp);
      sendIfEnabled(MetricAttribute.STDDEV, metricName, snapshot.getStdDev(), timestamp);
      sendIfEnabled(MetricAttribute.P50, metricName, snapshot.getMedian(), timestamp);
      sendIfEnabled(MetricAttribute.P75, metricName, snapshot.get75thPercentile(), timestamp);
      sendIfEnabled(MetricAttribute.P95, metricName, snapshot.get95thPercentile(), timestamp);
      sendIfEnabled(MetricAttribute.P98, metricName, snapshot.get98thPercentile(), timestamp);
      sendIfEnabled(MetricAttribute.P99, metricName, snapshot.get99thPercentile(), timestamp);
      sendIfEnabled(MetricAttribute.P999, metricName, snapshot.get999thPercentile(), timestamp);
    }
  }

  private void reportCounter(MetricName metricName, Counter counter, long timestamp)
      throws IOException {
    if (counter instanceof DeltaCounter) {
      long count = counter.getCount();
      String name = Constants.DELTA_PREFIX +
          prefixAndSanitize(metricName.getKey().substring(1), "count");
      wavefrontSender.sendDeltaCounter(name, count, timestamp, source,
          getMetricTags(metricName));
      counter.dec(count);
    } else {
      wavefrontSender.sendMetric(prefixAndSanitize(metricName.getKey(), "count"),
          counter.getCount(), timestamp, source, getMetricTags(metricName));
    }
  }

  private void reportGauge(MetricName metricName, Gauge gauge, long timestamp)
      throws IOException {
    wavefrontSender.sendMetric(prefixAndSanitize(metricName.getKey()),
        gauge.getValue().doubleValue(), timestamp,
        source, getMetricTags(metricName));
  }

  private void sendIfEnabled(MetricAttribute type, MetricName metricName, double value,
                             long timestamp) throws IOException {
    if (!scheduledReporter.getDisabledMetricAttributes().contains(type)) {
      wavefrontSender.sendMetric(prefixAndSanitize(metricName.getKey(), type.getCode()), value,
          timestamp, source, getMetricTags(metricName));
    }
  }

  private Map getMetricTags(MetricName metricName) {
    int tagCount = reporterPointTags.size() + metricName.getTags().size();
    // If there are no tags(point tag(s) or global return an empty map
    if (tagCount == 0) {
      return Collections.emptyMap();
    }

    // NOTE: If the individual metric share the same key as the global point tag key, the
    // metric level value will override global level value for that point tag.
    // Example: Global point tag is    <"Key1", "Value-Global">
    // and metric level point tag is:  <"Key1", "Value-Metric1">
    // the point tag sent to Wavefront will be <"Key1", "Value-Metric1">
    HashMap metricTags = new HashMap<>();
    metricTags.putAll(metricName.getTags());
    reporterPointTags.forEach(metricTags::putIfAbsent);
    return metricTags;
  }

  private String prefixAndSanitize(String... components) {
    return sanitize(MetricRegistry.name(prefix, components).getKey());
  }

  private static String sanitize(String name) {
    return SIMPLE_NAMES.matcher(name).replaceAll("_");
  }

  private static final Pattern SIMPLE_NAMES = Pattern.compile("[^a-zA-Z0-9_.\\-~]");

  public void report() {
    scheduledReporter.report();
  }

  @Override
  public void start(long period, TimeUnit unit) {
    scheduledReporter.start(period, unit);
  }

  @Override
  public void stop() {
    scheduledReporter.stop();
    sdkMetricsRegistry.close();
  }

  @Override
  public void close() {
    this.stop();
  }

  @Override
  public Counter newCounter(MetricName metricName) {
    return internalRegistry.counter(metricName);
  }

  @Override
  public DeltaCounter newDeltaCounter(MetricName metricName) {
    return DeltaCounter.get(internalRegistry, metricName);
  }

  @Override
  public Gauge newGauge(MetricName metricName, MetricRegistry.MetricSupplier supplier) {
    return internalRegistry.gauge(metricName, supplier);
  }

  @Override
  public Histogram newHistogram(MetricName metricName) {
    return internalRegistry.histogram(metricName);
  }

  @Override
  public Timer newTimer(MetricName metricName) {
    return internalRegistry.timer(metricName);
  }

  @Override
  public Timer newTimer(MetricName metricName, SlidingTimeWindowArrayReservoir slidingTimeWindowArrayReservoir) {
    MetricRegistry.MetricSupplier timerMetricSupplier = () -> new Timer(slidingTimeWindowArrayReservoir);
    return internalRegistry.timer(metricName, timerMetricSupplier);
  }

  @Override
  public Meter newMeter(MetricName metricName) {
    return internalRegistry.meter(metricName);
  }

  @Override
  public WavefrontHistogram newWavefrontHistogram(MetricName metricName) {
    return WavefrontHistogram.get(internalRegistry, metricName);
  }

  @Override
  public WavefrontHistogram newWavefrontHistogram(MetricName metricName, Supplier clock) {
    return WavefrontHistogram.get(internalRegistry, metricName, clock);
  }

  @Override
  public int getFailureCount() {
    return wavefrontSender.getFailureCount();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy