/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.engine.utilities.general;
import com.google.common.collect.Lists;
import com.google.dart.engine.AnalysisEngine;
import java.util.LinkedList;
/**
* Helper for measuring how much time is spent doing some operation. Each call to
* {@link #recordElapsedNanos(long)} or each pair of calls to {@link #start()} and
* {@link TimeCounterHandle#stop()} adds the specified time interval to the total recorded time.
*/
public class TimeCounter {
/**
* The handle object that should be used to stop and update counter.
*/
public class TimeCounterHandle {
final long startTime = System.nanoTime();
/**
* Stops counting time and calls {@link TimeCounter#recordElapsedNanos(long)} to add the elapse
* time to the counter.
*
* @return The number of elapsed nanoseconds
*/
public long stop() {
long elapsed = System.nanoTime() - startTime;
recordElapsedNanos(elapsed);
return elapsed;
}
}
private static final ThreadLocal<LinkedList<TimeCounter>> stacks = new ThreadLocal<LinkedList<TimeCounter>>();
public static final int NANOS_PER_MILLI = 1000 * 1000;
/**
* Returns the stack of {@link TimeCounter} started on the current {@link Thread} and not stopped
* yet.
*/
private static LinkedList<TimeCounter> getCountersStack() {
LinkedList<TimeCounter> stack = stacks.get();
if (stack == null) {
stack = Lists.newLinkedList();
stacks.set(stack);
}
return stack;
}
private long totalTime = 0L;
private long correctionTime = 0L;
private long maxInterval = 0L;
private long minInterval = Long.MAX_VALUE;
private int intervalCount = 0;
/**
* @return the average time interval in milliseconds as recorded by
* {@link #recordElapsedNanos(long)} or {@link #start()} and
* {@link TimeCounterHandle#stop()}
*/
public long getAverage() {
if (intervalCount == 0) {
return 0;
}
return totalTime / (NANOS_PER_MILLI * intervalCount);
}
/**
* @return the number of times that {@link #recordElapsedNanos(long)} and {@link #start()} and
* {@link TimeCounterHandle#stop()} were called
*/
public int getCount() {
return intervalCount;
}
/**
* @return the maximum time interval in milliseconds as recorded by
* {@link #recordElapsedNanos(long)} or {@link #start()} and
* {@link TimeCounterHandle#stop()}
*/
public long getMax() {
return maxInterval / NANOS_PER_MILLI;
}
/**
* @return the minimum time interval in milliseconds as recorded by
* {@link #recordElapsedNanos(long)} or {@link #start()} and
* {@link TimeCounterHandle#stop()}
*/
public long getMin() {
if (intervalCount == 0) {
return 0;
}
return minInterval / NANOS_PER_MILLI;
}
/**
* @return the number of milliseconds spent between {@link #start()} and {@link #stop()}.
*/
public long getResult() {
return totalTime / NANOS_PER_MILLI;
}
/**
* Adds the specified time interval to the total time and updates the minimum, maximum, and
* average time intervals.
*
* @param delta the number of nanoseconds
*/
public synchronized void recordElapsedNanos(long delta) {
// apply correction to the other counters on the thread stack
LinkedList<TimeCounter> stack = getCountersStack();
TimeCounter removed = stack.removeFirst();
if (removed != this) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Unexpected TimeCounter instance stack in " + Thread.currentThread(),
new IllegalStateException());
}
for (TimeCounter timeCounter : stack) {
timeCounter.correctionTime += delta;
}
// update statistics
totalTime += delta - correctionTime;
correctionTime = 0;
intervalCount++;
minInterval = Math.min(minInterval, delta);
maxInterval = Math.max(maxInterval, delta);
}
/**
* Starts counting time.
*
* @return the {@link TimeCounterHandle} that should be used to stop counting.
*/
public synchronized TimeCounterHandle start() {
getCountersStack().addFirst(this);
return new TimeCounterHandle();
}
}