gobblin.metrics.reporter.ScheduledReporter.java Source code

Java tutorial

Introduction

Here is the source code for gobblin.metrics.reporter.ScheduledReporter.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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.configuration.ConfigurationKeys;
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 = ConfigurationKeys.METRICS_CONFIGURATIONS_PREFIX
            + "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<ScheduledFuture> 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.<ScheduledFuture>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}.
     *
     * <p>
     *   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.
     * </p>
     *
     * @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.
     *
     * <p>
     *   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)}.
     * </p>
     *
     * @param isFinal true if this is the final time report will be called, false otherwise
     */
    protected void report(SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters,
            SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters,
            SortedMap<String, Timer> timers, Map<String, Object> tags, boolean isFinal) {
        report(gauges, counters, histograms, meters, timers, tags);
    }

    /**
     * @see #report(SortedMap, SortedMap, SortedMap, SortedMap, SortedMap, Map)
     */
    protected abstract void report(SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters,
            SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters,
            SortedMap<String, Timer> timers, Map<String, Object> tags);
}