content: /**
   * 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.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.aurora.common.stats;
  
  import java.util.concurrent.atomic.AtomicLong;
  
  import org.apache.aurora.common.base.MorePreconditions;
  import org.apache.aurora.common.util.Clock;
  
  import static java.util.Objects.requireNonNull;
  
  /**
   * Tracks event statistics over a sliding window of time. An event is something that has a
   * frequency and associated total.
   *
   * @author William Farner
   */
  public class SlidingStats {
  
    /**
     * An abstraction for an action to be timed by SlidingStats.
     *
     * @param <V> The result of the successfully completed action.
     * @param <E> The exception type that the action might throw.
     */
    @FunctionalInterface
    public interface Timeable<V, E extends Exception> {
  
      /**
       * A convenient typedef for action that throws no checked exceptions - it runs quietly.
       *
       * @param <V> The result of the successfully completed action.
       */
      @FunctionalInterface
      interface Quiet<V> extends Timeable<V, RuntimeException> {
        // empty
      }
  
      /**
       * Encapsulates an action with no result.
       *
       * @param <E> The exception type that the action might throw.
       */
      @FunctionalInterface
      interface NoResult<E extends Exception> extends Timeable<Void, E> {
        @Override
        default Void invoke() throws E {
          execute();
          return null;
        }
  
        /**
         * Similar to {@link Timeable#invoke()} except no result is returned.
         *
         * @throws E If action fails.
         */
        void execute() throws E;
  
        /**
         * A convenient typedef for action with no result that throws no checked exceptions - it runs
         * quietly.
         */
        @FunctionalInterface
        interface Quiet extends NoResult<RuntimeException> {
          // empty
        }
      }
  
      /**
       * Abstracts an action that has a result, but may also throw a specific exception.
       *
       * @return The result of the successfully completed action.
       * @throws E If action fails.
       */
      V invoke() throws E;
    }
  
    private static final int DEFAULT_WINDOW_SIZE = 1;
  
    private final AtomicLong total;
    private final AtomicLong events;
    private final Stat<Double> perEventLatency;
    private final Clock clock;
  
    /**
     * Creates a new sliding statistic with the given name
     *
     * @param name Name for this stat collection.
     * @param totalUnitDisplay String to display for the total counter unit.
     */
    public SlidingStats(String name, String totalUnitDisplay) {
      this(name, totalUnitDisplay, DEFAULT_WINDOW_SIZE, Clock.SYSTEM_CLOCK);
    }
  
    /**
     * Creates a new sliding statistic with the given name
     *
     * @param name Name for this stat collection.
     * @param totalUnitDisplay String to display for the total counter unit.
     * @param windowSize The window size for the per second Rate and Ratio stats.
     * @param clock The clock abstraction to use for timing in {@link #time(Timeable)} calls.
     */
    public SlidingStats(String name, String totalUnitDisplay, int windowSize, Clock clock) {
      MorePreconditions.checkNotBlank(name);
  
      String totalDisplay = name + "_" + totalUnitDisplay + "_total";
      String eventDisplay = name + "_events";
      total = Stats.exportLong(totalDisplay);
      events = Stats.exportLong(eventDisplay);
      perEventLatency = Stats.export(Ratio.of(name + "_" + totalUnitDisplay + "_per_event",
          Rate.of(totalDisplay + "_per_sec", total).withWindowSize(windowSize).build(),
          Rate.of(eventDisplay + "_per_sec", events).withWindowSize(windowSize).build()));
      this.clock = requireNonNull(clock);
    }
  
    public AtomicLong getTotalCounter() {
      return total;
    }
  
    public AtomicLong getEventCounter() {
      return events;
    }
  
    public Stat<Double> getPerEventLatency() {
      return perEventLatency;
    }
  
    /**
     * Accumulates counter by an offset.  This is is useful for tracking things like
     * latency of operations.
     *
     * TODO(William Farner): Implement a wrapper to SlidingStats that expects to accumulate time, and can
     *    convert between time units.
     *
     * @param value The value to accumulate.
     */
    public void accumulate(long value) {
      total.addAndGet(value);
      events.incrementAndGet();
    }
  
    /**
     * Accumulates counter by the nanoseconds it takes to execute the supplied action.
     *
     * @param action An action that produces result of type V and may throw exception E.
     * @param <V> The return type of action.
     * @param <E> The exception type that might be thrown by action.
     * @return The value returned by action.
     * @throws E A subclass of {@link Exception} that might be thrown by action.
     */
    public <V, E extends Exception> V time(Timeable<V, E> action) throws E {
      long start = clock.nowNanos();
      try {
        return action.invoke();
      } finally {
        accumulate(clock.nowNanos() - start);
      }
    }
  
    @Override
    public String toString() {
      return total + " " + events;
    }
  }
  