content: /*
   * 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 com.alibaba.metrics.reporter;
  
  import com.alibaba.metrics.Counter;
  import com.alibaba.metrics.Gauge;
  import com.alibaba.metrics.Histogram;
  import com.alibaba.metrics.Meter;
  import com.alibaba.metrics.MetricFilter;
  import com.alibaba.metrics.MetricName;
  import com.alibaba.metrics.MetricRegistry;
  import com.alibaba.metrics.Timer;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.io.Closeable;
  import java.util.Locale;
  import java.util.SortedMap;
  import java.util.concurrent.Executors;
  import java.util.concurrent.ScheduledExecutorService;
  import java.util.concurrent.ThreadFactory;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.atomic.AtomicInteger;
  
  /**
   * The abstract base class for all scheduled reporters (i.e., reporters which process a registry's
   * metrics periodically).
   *
   * @see ConsoleReporter
   * @see CsvReporter
   * @see Slf4jReporter
   */
  public abstract class ScheduledReporter implements Closeable {
  
      private static final Logger LOG = LoggerFactory.getLogger(ScheduledReporter.class);
  
      /**
       * A simple named thread factory.
       */
      @SuppressWarnings("NullableProblems")
      private static class NamedThreadFactory implements ThreadFactory {
          private final ThreadGroup group;
          private final AtomicInteger threadNumber = new AtomicInteger(1);
          private final String namePrefix;
  
          private NamedThreadFactory(String name) {
              final SecurityManager s = System.getSecurityManager();
              this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
              this.namePrefix = "metrics-" + name + "-thread-";
          }
  
          @Override
          public Thread newThread(Runnable r) {
              final Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
              t.setDaemon(true);
              if (t.getPriority() != Thread.NORM_PRIORITY) {
                  t.setPriority(Thread.NORM_PRIORITY);
              }
              return t;
          }
      }
  
      private static final AtomicInteger FACTORY_ID = new AtomicInteger();
  
      private final MetricRegistry registry;
      private final ScheduledExecutorService executor;
      private final MetricFilter filter;
      private final double durationFactor;
      private final String durationUnit;
      private final double rateFactor;
      private final String rateUnit;
  
      /**
       * Creates a new {@link ScheduledReporter} instance.
       *
       * @param registry the {@link com.alibaba.metrics.MetricRegistry} containing the metrics this
       *                 reporter will report
       * @param name     the reporter's name
       * @param filter   the filter for which metrics to report
       * @param rateUnit a unit of time
       * @param durationUnit a unit of time
       */
      protected ScheduledReporter(MetricRegistry registry,
                                  String name,
                                  MetricFilter filter,
                                  TimeUnit rateUnit,
                                  TimeUnit durationUnit) {
          this(registry, filter, rateUnit, durationUnit,
                  Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory(name + '-' + FACTORY_ID.incrementAndGet())));
      }
  
      /**
       * Creates a new {@link ScheduledReporter} instance.
       *
       * @param registry the {@link com.alibaba.metrics.MetricRegistry} containing the metrics this
       *                 reporter will report
       * @param filter   the filter for which metrics to report
       * @param executor the executor to use while scheduling reporting of metrics.
       */
      protected ScheduledReporter(MetricRegistry registry,
                                  MetricFilter filter,
                                  TimeUnit rateUnit,
                                  TimeUnit durationUnit,
                                  ScheduledExecutorService executor) {
          this.registry = registry;
          this.filter = filter;
          this.executor = executor;
          this.rateFactor = rateUnit.toSeconds(1);
          this.rateUnit = calculateRateUnit(rateUnit);
          this.durationFactor = 1.0 / durationUnit.toNanos(1);
          this.durationUnit = durationUnit.toString().toLowerCase(Locale.US);
      }
  
      /**
       * Starts the reporter polling at the given period.
       *
       * @param period the amount of time between polls
       * @param unit   the unit for {@code period}
       */
      public void start(long period, TimeUnit unit) {
          executor.scheduleWithFixedDelay(new Runnable() {
              @Override
              public void run() {
                  try {
                      report();
                  } catch (Throwable ex) {
                      LOG.error("Throwable RuntimeException thrown from {}#report. Exception was suppressed.", ScheduledReporter.this.getClass().getSimpleName(), ex);
                  }
              }
          }, period, period, unit);
      }
  
      /**
       * Stops the reporter and shuts down its thread of execution.
       *
       * Uses the shutdown pattern from http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html
       */
      public void stop() {
          executor.shutdown(); // Disable new tasks from being submitted
          try {
              // Wait a while for existing tasks to terminate
              if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
                  executor.shutdownNow(); // Cancel currently executing tasks
                  // Wait a while for tasks to respond to being cancelled
                  if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
                      LOG.warn(getClass().getSimpleName() + ": ScheduledExecutorService did not terminate");
                  }
              }
          } catch (InterruptedException ie) {
              // (Re-)Cancel if current thread also interrupted
              executor.shutdownNow();
              // Preserve interrupt status
              Thread.currentThread().interrupt();
          }
      }
  
      /**
       * Stops the reporter and shuts down its thread of execution.
       */
      @Override
      public void close() {
          stop();
      }
  
      /**
       * Report the current values of all metrics in the registry.
       */
      public void report() {
          synchronized (this) {
              report(registry.getGauges(filter),
                      registry.getCounters(filter),
                      registry.getHistograms(filter),
                      registry.getMeters(filter),
                      registry.getTimers(filter));
          }
      }
  
      /**
       * 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
       */
      public abstract void report(SortedMap<MetricName, Gauge> gauges,
                                  SortedMap<MetricName, Counter> counters,
                                  SortedMap<MetricName, Histogram> histograms,
                                  SortedMap<MetricName, Meter> meters,
                                  SortedMap<MetricName, Timer> timers);
  
      protected String getRateUnit() {
          return rateUnit;
      }
  
      protected String getDurationUnit() {
          return durationUnit;
      }
  
      protected double convertDuration(double duration) {
          return duration * durationFactor;
      }
  
      protected double convertRate(double rate) {
          return rate * rateFactor;
      }
  
      private String calculateRateUnit(TimeUnit unit) {
          final String s = unit.toString().toLowerCase(Locale.US);
          return s.substring(0, s.length() - 1);
      }
  }
  