/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2011 Palo Alto Research Center, Inc.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
* This library 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
* Lesser General Public License for more details. You should have received
* a copy of the GNU Lesser General Public License along with this library;
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
* Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.ccnx.ccn.impl;
import java.util.concurrent.atomic.AtomicLong;
import org.ccnx.ccn.impl.CCNStats.CCNEnumStats.IStatsEnum;
/**
* This is the base class for performance counters. It is the public API that a user
* would access to check or display performance counters. A class that counts statistics
* should implement CCNStatistics.
*
* A typical usage is illustrated in ExampleClassWithStatistics at the end of this
* module. By using the helper class CCNEnumStats, a class with statistics only
* needs to define an enum of the stats it will use, and create a CCNEnumStats
* member with reference to that Enum.
*
* Usage example is also shown in org.ccnx.ccn.test.impl.CCNStatsTest.java
*
* The high-level abstractions are CCNStats and CCNStatistics. A typical user would
* only deal with those two things.
*
* The mid-level abstractions are CCNEnumStats and IStatsEnum. These are for the
* developer of a class that uses CCNStats. The developer could, of course, do any
* implementation that implements CCNStatistics. The CCNEnumStats abstraction is
* to simplify the process so the developer only needs to define an Enum of the
* counters they want to use, and pass that to CCNEnumStats. The Enum must
* implement IStatsEnum. After that, the usage is very simple and automagically
* gives the CCNStats object needed by the CCNStatistics interface.
*
* The interface CCNCategoriezedStatistics is for modules that keep statistics
* in several cateogries (e.g. for each ContentName you express an interest for).
*/
public abstract class CCNStats {
public interface CCNStatistics {
public CCNStats getStats();
}
public interface CCNCategorizedStatistics {
/**
* Get a list of the category keys. The toString() method
* of the key should be meaningful.
*/
public Object [] getCategoryNames();
/**
* Get the statistics associated with the key
* @param name
* @return May be null if #category is not found
* @throws ClassCastException if #cateogry is not appropriate
*/
public CCNStats getStatsByName(Object category) throws ClassCastException;
}
// ===========================================================
// This is the public API for a user
/**
* If enabled, gather stats, otherwise do not
* @param enabled
*/
public abstract void setEnabled(boolean enabled);
/**
* Return a list of statistic counter names, in the preferred display order
* @return
*/
public abstract String [] getCounterNames();
/**
* Return the value of a counter
* @param name
* @return
* @throws IllegalArgumentException if name unrecognized
*/
public abstract long getCounter(String name) throws IllegalArgumentException;
/**
* Return a text description of the units of the counter (e.g. packets, packets per second)
* @param name
* @return
* @throws IllegalArgumentException if name unrecognized
*/
public abstract String getCounterUnits(String name) throws IllegalArgumentException;
/**
* Reset all counters to zero
*/
public abstract void clearCounters();
/**
* Dump the counters in the preferred format to a String for display
*/
public abstract String toString();
// =======================================================================================
// Everything below here is helpers for a developer creating a class that
// implements CCNStatistics
/**
* This is a helper class for implementing statistics
*/
public static class CCNEnumStats<K extends Enum<K>> extends CCNStats {
/**
* The statistics Enum used by "K extends Enum<K>" must implement this
* interface. See the example in ExampleClassWithStatistics below for
* how to do this.
*/
public interface IStatsEnum {
/**
* Given the enum string, return its index value
*/
public int getIndex(String name) throws IllegalArgumentException;
/**
* Given an index value, return the name
*/
public String getName(int index) throws ArrayIndexOutOfBoundsException;
/**
* Return the units of the count
*/
public String getUnits(int index) throws ArrayIndexOutOfBoundsException;
/**
* Return a short description of the counter
*/
public String getDescription(int index) throws ArrayIndexOutOfBoundsException;
/**
* Return all counter names
*/
public String [] getNames();
}
public CCNEnumStats(IStatsEnum stats) {
_resolver = stats;
int size = _resolver.getNames().length;
_counters = new AtomicLong[size];
for(int i = 0; i < size; i++) {
_counters[i] = new AtomicLong(0);
}
}
@Override
public void clearCounters() {
for(AtomicLong al : _counters)
al.set(0);
}
@Override
public long getCounter(String name) throws IllegalArgumentException {
int index = _resolver.getIndex(name);
return _counters[index].get();
}
@Override
public String [] getCounterNames() {
return _resolver.getNames();
}
@Override
public String getCounterUnits(String name) throws IllegalArgumentException {
int index = _resolver.getIndex(name);
return _resolver.getUnits(index);
}
@Override
public void setEnabled(boolean enabled) {
_enabled = enabled;
}
@Override
public String toString() {
// figure out a width
int width = 1;
for(int i = 0; i < _counters.length; i++) {
String key = _resolver.getName(i);
if( key.length() > width )
width = key.length();
}
String format = String.format("%%-%ds", width);
// we should cache this and use a dirty flag
StringBuilder sb = new StringBuilder();
for(int i = 0; i < _counters.length; i++) {
String key = _resolver.getName(i);
long value = _counters[i].get();
String units = _resolver.getUnits(i);
String description = _resolver.getDescription(i);
sb.append(String.format(format, key));
sb.append(": ");
sb.append(value);
sb.append(" (");
sb.append(units);
sb.append(") ");
sb.append(description);
sb.append('\n');
}
return sb.toString();
}
public void increment(K key) {
increment(key, 1);
}
public void increment(K key, int value) {
if(_enabled) {
_counters[key.ordinal()].addAndGet(value);
}
}
// =======================
protected final AtomicLong [] _counters;
protected final IStatsEnum _resolver;
protected boolean _enabled = true;
}
public static class ExampleClassWithStatistics implements CCNStatistics {
public enum MyStats implements IStatsEnum {
// =============================================
// The properties are of the format:
// EnumProperty ("units", "short description")
// To use in a different class, just copy-and-paste, then change this section
SendRequests ("packets", "The number of packets sent"),
RecvMessages ("packets", "The number of packets received"),
SendRate ("packets per second", "The 5 minute moving average of packet/sec transmits");
// ============================================
// Everything below here is the internal implementation for the
// IStatsEnum interface. You should not need to change anything
protected final String _units;
protected final String _description;
protected final static String [] _names;
static {
_names = new String[MyStats.values().length];
for(MyStats stat : MyStats.values() )
_names[stat.ordinal()] = stat.toString();
}
MyStats(String units, String description) {
_units = units;
_description = description;
}
public String getDescription(int index) {
return MyStats.values()[index]._description;
}
public int getIndex(String name) {
MyStats x = MyStats.valueOf(name);
return x.ordinal();
}
public String getName(int index) {
return MyStats.values()[index].toString();
}
public String getUnits(int index) {
return MyStats.values()[index]._units;
}
public String [] getNames() {
return _names;
}
}
/**
* Instantiate our stats counter. Note that we need to pass a Java generic type
* for our Enum and then pass an instance of the Enum to the constructor. It does
* not matter which enum value we pass to the constructor, it just needs a concrete
* object it can reference.
*/
CCNEnumStats<MyStats> _stats = new CCNEnumStats<MyStats>(MyStats.SendRequests);
// These are example methods showing the typical calling conventions
public void send(Object o) {
_stats.increment(MyStats.SendRequests);
}
public void recv(Object o) {
_stats.increment(MyStats.RecvMessages);
}
/**
* Implement the IStatsEnum interface
*/
public CCNStats getStats() {
return _stats;
}
}
}