/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2008], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.common; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; /** * An object for maintaining a group of metrics derived from method invocation * times on the instrumented method. This object maintains its own private * internal queue such that the metric values in the group will be updated * only after the internal queue is flushed. Flushing occurs automatically * when the queue capacity is reached but it may be invoked explicitly by any * client wishing to obtain the most recent snapshot of metrics. Clients may * also choose to update the metric values in the group synchronously when a * new method invocation time value is added. In this case, the internal queue * and flush operations are not used. * * Each metric in the group (num invocations/max,min,avg invocation time) is * guaranteed to be consistent with the current flushed state of the group. * There is no guarantee that the metrics within the group will be consistent * with respect to one another especially if there are concurrent threads * flushing and querying the group. */ public class MethodInvocationMetricsGroup { /** * The system property key for the capacity of the internal metrics queue. */ public static final String QUEUE_CAPACITY = "org.hq.diagnostic.method.invocation.metrics.queue.capacity"; /** * The default queue capacity (1000). */ public static final int DEFAULT_QUEUE_CAPACITY = 1000; private static int _queueSize = Integer.getInteger(QUEUE_CAPACITY, DEFAULT_QUEUE_CAPACITY).intValue(); private final Object _lock = new Object(); private final LinkedBlockingQueue _queue; private final String _metricGroupName; private final int _queueCapacity; private long _numInvocations; private long _maxInvocationTime; private long _minInvocationTime; private long _totalInvocationTime; /** * Creates an instance using the invocation time queue capacity specified * by the {@link #QUEUE_CAPACITY} system property value. * * @param metricGroupName The metric group name. */ public MethodInvocationMetricsGroup(String metricGroupName) { this(metricGroupName, _queueSize); } /** * Creates an instance. * * @param metricGroupName The metric group name. A <code>null</code> value * will be converted to the empty string. * @param queueCapacity The capacity of the invocation time queue. * @throws IllegalArgumentException if the capacity is not greater than zero. */ public MethodInvocationMetricsGroup(String metricGroupName, int queueCapacity) { if (metricGroupName == null) { _metricGroupName = ""; } else { _metricGroupName = metricGroupName; } _queueCapacity = queueCapacity; _queue = new LinkedBlockingQueue(_queueCapacity); } /** * @return The metric group name. */ public String getMetricGroupName() { return _metricGroupName; } /** * @return The capacity of the invocation time queue. */ public int getQueueCapacity() { return _queueCapacity; } /** * Add an invocation time to the internal queue, flushing the queue if the * capacity is reached. * * @param invocationTime The invocation time. */ public void addInvocationTime(long invocationTime) { Long latestInvocationTime = new Long(invocationTime); if (!_queue.offer(latestInvocationTime)) { flush(latestInvocationTime, true); } } /** * Add an invocation time and update the metrics in the group immediately. * * @param invocationTime The invocation time. */ public void addInvocationTimeSynch(long invocationTime) { synchronized (_lock) { updateMetrics(invocationTime); } } /** * Flush the metric group, updating the metrics in the group with the * invocation times currently stored in the queue. */ public void flush() { flush(null, false); } /** * @return The total number of added invocation time metrics. */ public long getNumberInvocations() { synchronized (_lock) { return _numInvocations; } } /** * @return The maximum invocation time metric. */ public long getMaxInvocationTime() { synchronized (_lock) { return _maxInvocationTime; } } /** * @return The minimum invocation time metric. */ public long getMinInvocationTime() { synchronized (_lock) { return _minInvocationTime; } } /** * @return The average invocation time metric or {@link Double#NaN} if no * invocation time metric has been added. */ public double getAverageInvocationTime() { synchronized (_lock) { if (_numInvocations==0) { return Double.NaN; } return (double)_totalInvocationTime/(double)_numInvocations; } } /** * Reset all metrics in the group. */ public void reset() { synchronized (_lock) { _numInvocations = 0; _maxInvocationTime = 0; _minInvocationTime = 0; _totalInvocationTime = 0; } } private void flush(Long latestInvocationTime, boolean includeLatest) { synchronized (_lock) { List invocationTimes = new ArrayList(); _queue.drainTo(invocationTimes); if (includeLatest) { invocationTimes.add(latestInvocationTime); } for (Iterator it = invocationTimes.iterator(); it.hasNext();) { Long invocationTime = (Long) it.next(); updateMetrics(invocationTime.longValue()); } } } private void updateMetrics(long nextInvocationTime) { _numInvocations++; _totalInvocationTime+=nextInvocationTime; // handle overflows if (_numInvocations < 0 || _totalInvocationTime < 0) { reset(); _numInvocations = 1; _totalInvocationTime = nextInvocationTime; } if (nextInvocationTime > _maxInvocationTime) { _maxInvocationTime = nextInvocationTime; } if (_numInvocations == 1) { // we always prime min invocation time with the first value _minInvocationTime = nextInvocationTime; } else if (nextInvocationTime < _minInvocationTime) { _minInvocationTime = nextInvocationTime; } } }