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

gobblin.metrics.reporter.ScheduledReporter Maven / Gradle / Ivy

There is a newer version: 0.8.0
Show newest version
/*
 * Copyright (C) 2014-2016 LinkedIn Corp. All rights reserved.
 *
 * 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  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.
 */

package gobblin.metrics.reporter;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.joda.time.Period;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.Timer;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigValueFactory;

import lombok.extern.slf4j.Slf4j;

import gobblin.metrics.InnerMetricContext;
import gobblin.metrics.context.ReportableContext;
import gobblin.metrics.metric.filter.MetricFilters;
import gobblin.metrics.metric.filter.MetricNameRegexFilter;
import gobblin.metrics.metric.filter.MetricTypeFilter;
import gobblin.util.ExecutorsUtils;


/**
 * A {@link ContextAwareReporter} that reports on a schedule.
 */
@Slf4j
public abstract class ScheduledReporter extends ContextAwareReporter {

  /**
   * Interval at which metrics are reported. Format: hours, minutes, seconds. Examples: 1h, 1m, 10s, 1h30m, 2m30s, ...
   */
  public static final String REPORTING_INTERVAL = "reporting.interval";
  public static final String DEFAULT_REPORTING_INTERVAL_PERIOD = "1M";

  public static final PeriodFormatter PERIOD_FORMATTER = new PeriodFormatterBuilder().
      appendHours().appendSuffix("H").
      appendMinutes().appendSuffix("M").
      appendSeconds().appendSuffix("S").toFormatter();

  private static final String METRIC_FILTER_NAME_REGEX = "metric.filter.name.regex";
  private static final String METRIC_FILTER_TYPE_LIST = "metric.filter.type.list";

  @VisibleForTesting
  static int parsePeriodToSeconds(String periodStr) {
    try {
      return Period.parse(periodStr.toUpperCase(), PERIOD_FORMATTER).toStandardSeconds().getSeconds();
    } catch(ArithmeticException ae) {
      throw new RuntimeException(String.format("Reporting interval is too long. Max: %d seconds.", Integer.MAX_VALUE));
    }
  }

  public static void setReportingInterval(Properties props, long reportingInterval, TimeUnit reportingIntervalUnit) {
    long seconds = TimeUnit.SECONDS.convert(reportingInterval, reportingIntervalUnit);
    if (seconds > Integer.MAX_VALUE) {
      throw new RuntimeException(String.format("Reporting interval is too long. Max: %d seconds.", Integer.MAX_VALUE));
    }
    props.setProperty(REPORTING_INTERVAL, Long.toString(seconds) + "S");
  }

  public static Config setReportingInterval(Config config, long reportingInterval, TimeUnit reportingIntervalUnit) {
    long seconds = TimeUnit.SECONDS.convert(reportingInterval, reportingIntervalUnit);
    if (seconds > Integer.MAX_VALUE) {
      throw new RuntimeException(String.format("Reporting interval is too long. Max: %d seconds.", Integer.MAX_VALUE));
    }
    return config.withValue(REPORTING_INTERVAL, ConfigValueFactory.fromAnyRef(seconds + "S"));
  }

  private ScheduledExecutorService executor;
  private MetricFilter metricFilter;

  private Optional scheduledTask;
  private int reportingPeriodSeconds;

  public ScheduledReporter(String name, Config config) {
    super(name, config);
    ensureMetricFilterIsInitialized(config);
  }

  private synchronized void ensureMetricFilterIsInitialized(Config config) {
    if (this.metricFilter == null) {
      this.metricFilter = createMetricFilter(config);
    }
  }

  private MetricFilter createMetricFilter(Config config) {
    if (config.hasPath(METRIC_FILTER_NAME_REGEX) && config.hasPath(METRIC_FILTER_TYPE_LIST)) {
      return MetricFilters.and(new MetricNameRegexFilter(config.getString(METRIC_FILTER_NAME_REGEX)),
          new MetricTypeFilter(config.getString(METRIC_FILTER_TYPE_LIST)));
    }
    if (config.hasPath(METRIC_FILTER_NAME_REGEX)) {
      return new MetricNameRegexFilter(config.getString(METRIC_FILTER_NAME_REGEX));
    }
    if (config.hasPath(METRIC_FILTER_TYPE_LIST)) {
      return new MetricTypeFilter(config.getString(METRIC_FILTER_TYPE_LIST));
    }
    return MetricFilter.ALL;
  }

  @Override
  public void startImpl() {
    this.executor = Executors.newSingleThreadScheduledExecutor(
            ExecutorsUtils.newDaemonThreadFactory(Optional.of(log), Optional.of("metrics-" + name + "-scheduler")));
    this.reportingPeriodSeconds = parsePeriodToSeconds(
            config.hasPath(REPORTING_INTERVAL) ? config.getString(REPORTING_INTERVAL) : DEFAULT_REPORTING_INTERVAL_PERIOD);
    ensureMetricFilterIsInitialized(config);
    this.scheduledTask = Optional.of(this.executor.scheduleAtFixedRate(new Runnable() {
      @Override public void run() {
        report();
      }
    }, 0, this.reportingPeriodSeconds, TimeUnit.SECONDS));
  }

  @Override
  public void stopImpl() {
    this.scheduledTask.get().cancel(false);
    this.scheduledTask = Optional.absent();
    ExecutorsUtils.shutdownExecutorService(this.executor, Optional.of(log), 10, TimeUnit.SECONDS);

    // Report metrics one last time - this ensures any metrics values updated between intervals are reported
    report(true);
  }

  @Override
  public void close() throws IOException {
    super.close();
  }

  @Override
  protected void removedMetricContext(InnerMetricContext context) {
    if (shouldReportInnerMetricContext(context)) {
      report(context, true);
    }
    super.removedMetricContext(context);
  }

  /**
   * Trigger emission of a report.
   */
  public void report() {
    report(false);
  }

  /***
   * @param isFinal true if this is the final time report will be called for this reporter, false otherwise
   * @see #report()
   */
  protected void report(boolean isFinal) {
    for (ReportableContext metricContext : getMetricContextsToReport()) {
      report(metricContext, isFinal);
    }
  }

  /**
   * Report as {@link InnerMetricContext}.
   *
   * 

* This method is marked as final because it is not directly invoked from the framework, so this method should not * be overloaded. Overload {@link #report(ReportableContext, boolean)} instead. *

* * @param context {@link InnerMetricContext} to report. * @see #report(ReportableContext, boolean) */ protected final void report(ReportableContext context) { report(context, false); } /** * @param context {@link InnerMetricContext} to report. * @param isFinal true if this is the final time report will be called for the given context, false otherwise * @see #report(ReportableContext) */ protected void report(ReportableContext context, boolean isFinal) { report(context.getGauges(this.metricFilter), context.getCounters(this.metricFilter), context.getHistograms(this.metricFilter), context.getMeters(this.metricFilter), context.getTimers(this.metricFilter), context.getTagMap(), isFinal); } /** * Report the input metrics. The input tags apply to all input metrics. * *

* The default implementation of this method is to ignore the value of isFinal. Sub-classes that are interested in * using the value of isFinal should override this method as well as * {@link #report(SortedMap, SortedMap, SortedMap, SortedMap, SortedMap, Map)}. If they are not interested in the * value of isFinal, they should just override * {@link #report(SortedMap, SortedMap, SortedMap, SortedMap, SortedMap, Map)}. *

* * @param isFinal true if this is the final time report will be called, false otherwise */ protected void report(SortedMap gauges, SortedMap counters, SortedMap histograms, SortedMap meters, SortedMap timers, Map tags, boolean isFinal) { report(gauges, counters, histograms, meters, timers, tags); } /** * @see #report(SortedMap, SortedMap, SortedMap, SortedMap, SortedMap, Map) */ protected abstract void report(SortedMap gauges, SortedMap counters, SortedMap histograms, SortedMap meters, SortedMap timers, Map tags); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy