gobblin.metrics.reporter.ScheduledReporter Maven / Gradle / Ivy
                 Go to download
                
        
                    Show more of this group  Show more artifacts with this name
Show all versions of gobblin-metrics Show documentation
                Show all versions of gobblin-metrics Show documentation
Gobblin Ingestion Framework
                
            /*
 * 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