blob: f2590bb837b481c4e9604f761f314f24fbeec421 [file] [log] [blame]
/**
* 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;
}
}