/**
* Copyright 2011 LiveRamp
*
* 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 com.liveramp.hank.util;
import com.liveramp.hank.partition_server.DoublePopulationStatisticsAggregator;
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
public class HankTimerEventAggregator {
private static Logger LOG = LoggerFactory.getLogger(HankTimerEventAggregator.class);
private final String name;
private double[] durations;
private int count;
private final int statsComputationWindow;
private final boolean isActive;
private long statsComputationWindowStart;
private long statsComputationWindowEnd;
private long statsComputationWindowDuration;
private double minDuration;
private double maxDuration;
private double totalDuration;
private long totalUnderlyingCount;
private DoublePopulationStatisticsAggregator populationStatistics;
private DoublePopulationStatisticsAggregator previousPopulationStatistics;
/**
* @param name
* @param statsComputationWindow Number of timers to aggregate before computing and
* logging statistics. 0 means no timer aggregation.
*/
public HankTimerEventAggregator(String name, int statsComputationWindow) {
this.name = name;
this.statsComputationWindow = statsComputationWindow;
this.isActive = statsComputationWindow != 0;
this.populationStatistics = null;
this.previousPopulationStatistics = new DoublePopulationStatisticsAggregator();
this.durations = new double[statsComputationWindow];
clear();
}
// Return a new HankTimer if active, null otherwise
public HankTimer getTimer() {
if (!isActive) {
return null;
}
return new HankTimer();
}
public void add(HankTimer timer) {
add(timer, 1);
}
// Aggregate the given timer only if the aggregator is active
// Will not add synchronization overhead if not active.
// underlyingCount is used when the timed event represent a number of underlying events
public void add(HankTimer timer, int underlyingCount) {
if (!isActive) {
return;
}
_add(timer.getStartTime(), timer.getDuration(), underlyingCount);
}
public synchronized DoublePopulationStatisticsAggregator getAndResetPopulationStatistics() {
// If there are new statistics, return them and reset the current statistics
if (populationStatistics != null) {
previousPopulationStatistics = populationStatistics;
populationStatistics = null;
}
return previousPopulationStatistics;
}
private synchronized void _add(long startTimeNanos, long durationNanos, int underlyingCount) {
++count;
// Determine computation window start and end
if (startTimeNanos < statsComputationWindowStart) {
statsComputationWindowStart = startTimeNanos;
}
if ((startTimeNanos + durationNanos) > statsComputationWindowEnd) {
statsComputationWindowEnd = startTimeNanos + durationNanos;
}
// Compute statistics
double duration = durationNanos / 1000000d;
totalDuration += duration;
if (duration < minDuration) {
minDuration = duration;
}
if (duration > maxDuration) {
maxDuration = duration;
}
durations[count - 1] = duration;
totalUnderlyingCount += underlyingCount;
// Dump stats if needed
if (count == statsComputationWindow) {
// Determine computation window duration
statsComputationWindowDuration = Math.abs(statsComputationWindowEnd - statsComputationWindowStart);
logStats();
clear();
}
}
private void clear() {
// Durations doesn't need to be cleared
count = 0;
totalDuration = 0;
statsComputationWindowStart = Long.MAX_VALUE;
statsComputationWindowEnd = Long.MIN_VALUE;
minDuration = Double.MAX_VALUE;
maxDuration = Double.MIN_VALUE;
totalUnderlyingCount = 0;
}
private void logStats() {
// Build log string
StringBuilder logStr = new StringBuilder();
logStr.append("Statistics for Timer: ");
logStr.append(name);
logStr.append(", count: ");
logStr.append(count);
logStr.append(", underlying count: ");
logStr.append(totalUnderlyingCount);
logStr.append(", window duration: ");
logStr.append(statsComputationWindowDuration / 1000000d);
logStr.append("ms");
logStr.append(", min duration: ");
logStr.append(minDuration);
logStr.append("ms");
logStr.append(", avg duration: ");
logStr.append((totalDuration / (double) count));
logStr.append("ms");
if (totalUnderlyingCount != count) {
logStr.append(", underlying avg duration: ");
logStr.append((totalDuration / (double) totalUnderlyingCount));
logStr.append("ms");
}
logStr.append(", max duration: ");
logStr.append(maxDuration);
logStr.append("ms");
logStr.append(", QPS: ");
logStr.append(count / (statsComputationWindowDuration / 1000000000d));
if (totalUnderlyingCount != count) {
logStr.append(", Underlying QPS: ");
logStr.append(totalUnderlyingCount / (statsComputationWindowDuration / 1000000000d));
}
LOG.info(logStr.toString());
// Aggregate population statistics
if (populationStatistics == null) {
populationStatistics = new DoublePopulationStatisticsAggregator();
}
populationStatistics.aggregate(minDuration, maxDuration, count, totalDuration, durations);
}
}