/**
 * 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 javax.annotation.Nullable;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A class to represent the statistics associated with a client connection to an external service.
 * Tracks request latency/rate, and error rate.
 *
 * @author William Farner
 */
public class RequestStats implements StatsProvider.RequestTimer {

  private static final float DEFAULT_SAMPLE_PERCENT = 10;
  private static final double[] DEFAULT_PERCENTILES = {10, 50, 90, 99, 99.9, 99.99};

  private final SlidingStats requests;
  private final Percentile<Long> percentile;

  private final AtomicLong errors;
  private final AtomicLong reconnects;
  private final AtomicLong timeouts;

  /**
   * Creates a new request statistics object, using the default percentiles and sampling rate.
   *
   * @param name The unique name for this request type.
   */
  public RequestStats(String name) {
    this(name, new Percentile<Long>(name, DEFAULT_SAMPLE_PERCENT, DEFAULT_PERCENTILES));
  }

  /**
   * Creates a new request statistics object using a custom percentile tracker.
   *
   * @param name The unique name for this request type.
   * @param percentile The percentile tracker, or {@code null} to disable percentile tracking.
   */
  public RequestStats(String name, @Nullable Percentile<Long> percentile) {
    requests = new SlidingStats(name + "_requests", "micros");
    this.percentile = percentile;
    errors = Stats.exportLong(name + "_errors");
    reconnects = Stats.exportLong(name + "_reconnects");
    timeouts = Stats.exportLong(name + "_timeouts");
    Rate<AtomicLong> requestsPerSec =
        Rate.of(name + "_requests_per_sec", requests.getEventCounter()).build();
    Stats.export(Ratio.of(name + "_error_rate",
        Rate.of(name + "_errors_per_sec", errors).build(), requestsPerSec));
    Rate<AtomicLong> timeoutsPerSec = Rate.of(name + "_timeouts_per_sec", timeouts).build();
    Stats.export(timeoutsPerSec);
    Stats.export(Ratio.of(name + "_timeout_rate", timeoutsPerSec, requestsPerSec));
  }

  public SlidingStats getSlidingStats() {
    return requests;
  }

  public AtomicLong getErrorCounter() {
    return errors;
  }

  public AtomicLong getReconnectCounter() {
    return reconnects;
  }

  public AtomicLong getTimeoutCounter() {
    return timeouts;
  }

  public Percentile<Long> getPercentile() {
    return percentile;
  }

  /**
   * Accumulates a request and its latency.
   *
   * @param latencyMicros The elapsed time required to complete the request.
   */
  public void requestComplete(long latencyMicros) {
    requests.accumulate(latencyMicros);
    if (percentile != null) percentile.record(latencyMicros);
  }

  /**
   * Accumulates the error counter and the request counter.
   */
  public void incErrors() {
    requestComplete(0);
    errors.incrementAndGet();
  }

  /**
   * Accumulates the error counter, the request counter and the request latency.
   *
   * @param latencyMicros The elapsed time before the error occurred.
   */
  public void incErrors(long latencyMicros) {
    requestComplete(latencyMicros);
    errors.incrementAndGet();
  }

  /**
   * Accumulates the reconnect counter.
   */
  public void incReconnects() {
    reconnects.incrementAndGet();
  }

  /**
   * Accumulates the timtout counter.
   */
  public void incTimeouts() {
    timeouts.incrementAndGet();
  }

  public long getErrorCount() {
    return errors.get();
  }

  public long getReconnectCount() {
    return reconnects.get();
  }

  public long getTimeoutCount() {
    return timeouts.get();
  }
}
