/* * Copyright 2013-2015 EMC Corporation. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://www.apache.org/licenses/LICENSE-2.0.txt * * or in the "license" file accompanying this file. This file 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.emc.ecs.sync.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Iterator; import java.util.LinkedList; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * Tracks statistics for a measurement using a sliding window. For example, this class can track bytes transferred * over time and provide an average bytes/second over the window. */ public class PerformanceWindow implements AutoCloseable { private static final Logger log = LoggerFactory.getLogger(PerformanceWindow.class); private final long sliceInterval; private final int sliceCount; private final AtomicLong currentValue; private final ScheduledExecutorService updater; private final LinkedList<PerformanceSlice> slices; private long windowSum; private long windowDuration; private long windowRate; private long currentWindowStart; /** * Creates a new performance window * @param sliceInterval size of a slice of the window in milliseconds * @param sliceCount number of slices in the window. */ public PerformanceWindow(long sliceInterval, int sliceCount) { this.sliceInterval = sliceInterval; this.sliceCount = sliceCount; currentValue = new AtomicLong(); currentWindowStart = System.currentTimeMillis(); slices = new LinkedList<>(); updater = Executors.newSingleThreadScheduledExecutor(); updater.scheduleAtFixedRate(new Runnable() { @Override public void run() { update(); } }, sliceInterval, sliceInterval, TimeUnit.MILLISECONDS); } /** * Increments the current slice in the performance window. * @param value the number of items (e.g. bytes) to increment the counter by. */ public void increment(final long value) { currentValue.addAndGet(value); } /** * Called by the timer. Updates the statistics. */ private void update() { // Push the current slice into the list and update stats. long now = System.currentTimeMillis(); long value = currentValue.getAndSet(0); PerformanceSlice completedSlice = new PerformanceSlice(); completedSlice.sliceStart = currentWindowStart; completedSlice.sliceEnd = now; currentWindowStart = now; completedSlice.value = value; slices.add(completedSlice); log.trace("New sample: start: {} end: {} value: {}", completedSlice.sliceStart, completedSlice.sliceEnd, completedSlice.value); long sum = 0; long startTime = Long.MAX_VALUE; long endTime = 0; long maxAge = sliceInterval*(sliceCount) + 50; // 50 ms fudge for timing. for(Iterator<PerformanceSlice> i = slices.iterator(); i.hasNext();) { PerformanceSlice p = i.next(); if(now - p.sliceStart > maxAge) { i.remove(); continue; } sum += p.value; startTime = Math.min(startTime, p.sliceStart); endTime = Math.max(endTime, p.sliceEnd); } long duration = endTime - startTime; long rate = (long)((double)sum / (duration/1000.0)); //long rate = sum / (duration/1000L); synchronized(this) { this.windowSum = sum; this.windowDuration = duration; this.windowRate = rate; log.trace("Stat update: sum={} duration={} rate={}", sum, duration, rate); } } @Override public void close() { try { updater.shutdownNow(); } catch (Throwable t) { log.warn("could not shut down updater", t); } } @Override protected void finalize() throws Throwable { try { close(); } finally { super.finalize(); // make sure we call super.finalize() no matter what! } } /** * Gets the current sum for the performance window. * @return sum of the slice counters in the window */ public long getWindowSum() { return windowSum; } /** * Gets the current duration of the performance window in milliseconds. Usually this will be sliceInterval * * sliceCount but during startup it may be shorter. * @return current window duration in milliseconds */ public long getWindowDuration() { return windowDuration; } /** * Gets the rate in items/s for the current window. * @return the rate in items/s. */ public long getWindowRate() { return windowRate; } /** * Inner class used to track the time slices in the current window. */ class PerformanceSlice { long sliceStart; long sliceEnd; long value; } }