package org.dcache.commons.stats; import com.google.common.base.Function; import com.google.common.collect.Ordering; import java.lang.reflect.Method; import java.util.Formatter; import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import static org.dcache.util.Strings.toStringSignature; /** * This class provides a convinient way to collect statistics about * the execution of the requests in a collection of the RequestCounter objects * This named collection is organized as a map with with keys of generic type T * It provides utility methods for increments and discovery of the count of * request invocations, * failures and folds (number of times when this request did not have to be * executed because the results were calculated alredy for the same type of the * request ahead in the request queue) * This class is thread safe. * @param <T> the type of the keys in the map into the values of type * RequestCounter * @author timur */ public class RequestCounters<T> { private final String name; private final boolean autoCreate ; private final Map<T,RequestCounterImpl> counters = new HashMap<>(); private static final Ordering<RequestCounter> ORDERING = Ordering.natural().onResultOf(new Function<RequestCounter, String>() { @Override public String apply(RequestCounter counter) { return counter.getName(); } }); /** * Creates an instance of the RequestCounters collection * The new counters will be created automatically if the increment * methods are invoked for not existing keys * @param name of the RequestCounters collection */ public RequestCounters(String name) { this(name,true); } /** * Creates an instance of the RequestCounters collection * @param name of the RequestCounters collection * @param autoCreate if this value is true then * new counters will be created automatically if the increment * methods are invoked for not existing keys. * If it is false then NoSuchElementException will be thrown by the increment * methods.if the increment methods are invoked for not existing keys. * */ public RequestCounters(String name, boolean autoCreate) { this.name = name; this.autoCreate = autoCreate; } public String getName() { return name; } /** * Adds a new counter to the collection of counters * name of the counter will be computed as key.toString() * @param key the same key will be needed in the increment and get * functions to change or access the counter value */ public void addCounter(T key) { String counterName; if(key instanceof Class) { Class<?> ckey = (Class<?>) key; counterName = ckey.getSimpleName(); } else if(key instanceof Method){ Method mkey = (Method)key; // use '|' as delimiter to keep JMX happy counterName = toStringSignature(mkey, '|'); } else { counterName = key.toString(); } addCounter(key,counterName); } /** * Adds a new counter to the collection of counters * @param key the same key will be needed in the increment and get * functions to change or access the counter value * @param name name of the counter */ public synchronized void addCounter(T key, String name) { if(counters.containsKey(key)) { return; } RequestCounterImpl counter = new RequestCounterImpl(name, this.name); counters.put(key,counter); } /** * @return A string representation of the RequestCounters * which is a name of the collection followed by the table of * counter names and values */ @Override public String toString() { StringBuilder sb = new StringBuilder(); try (Formatter formatter = new Formatter(sb)) { formatter.format("%-36s %9s %9s", name, "requests", "failed"); int totalRequests=0; int totalFailed=0; synchronized(this) { for (RequestCounter counter: ORDERING.sortedCopy(counters.values())) { totalRequests += counter.getTotalRequests(); totalFailed += counter.getFailed(); sb.append("\n ").append(counter); } } formatter.format("\n%-36s %9s %9s", " Total", totalRequests, totalFailed); } return sb.toString(); } /** * * @param counterKey a key corresponding to a counter * @return a String representation of a RequestCounter associated with counterKey * @throws NoSuchElementException if counter counterKey for is not defined */ public String counterToString(T counterKey) { RequestCounter counter; synchronized(this) { counter = counters.get(counterKey); } if(counter == null) { throw new NoSuchElementException("counter "+ counterKey+" is not defined in "+name+" counters" ); } return counter.toString(); } /** * * @param key a key corresponding to a counter * @return a RequestCounter associated with key * @throws NoSuchElementException if counter for key is not defined */ public RequestCounter getCounter(T key) { return getCounterImpl(key); } private synchronized RequestCounterImpl getCounterImpl(T key) { RequestCounterImpl counter = counters.get(key); if (counter == null) { if (!autoCreate) { throw new NoSuchElementException("counter with name "+ key+" is not defined in "+name+" counters" ); } addCounter(key); counter = counters.get(key); } return counter; } /** * * @param counterKey a key corresponding to a counter * @return a number of request invocations counted by the * RequestCounter associated with counterKey */ public int getCounterRequests(T counterKey) { return getCounter(counterKey).getTotalRequests(); } /** * * @param counterKey a key corresponding to a counter * @return a number of failed request invocations counted by the * RequestCounter associated with counterKey */ public int getCounterFailed(T counterKey) { return getCounter(counterKey).getFailed(); } /** * * @param counterKey a key corresponding to a counter * @return a number of successful request invocations counted by the * RequestCounter associated with counterKey * The number of Successful requests is accurate only if both * number of requests executed and the number of failed requests * are recorded accurately */ public int getCounterSuccessful(T counterKey) { return getCounter(counterKey).getSuccessful(); } /** * increments count of the request invocations by one * @param counterKey a key corresponding to a counter */ public void incrementRequests(T counterKey) { getCounterImpl(counterKey).incrementRequests(); } /** * increments count of the failed request invocations by one * @param counterKey a key corresponding to a counter */ public void incrementFailed(T counterKey) { getCounterImpl(counterKey).incrementFailed(); } /** * increments count of the request invocations * @param counterKey a key corresponding to a counter * @param increment a value by which to increment the counter */ public void incrementRequests(T counterKey,int increment) { getCounterImpl(counterKey).incrementRequests(increment); } /** * increments count of the failed request invocations * @param counterKey a key corresponding to a counter * @param increment */ public void incrementFailed(T counterKey,int increment) { getCounterImpl(counterKey).incrementFailed(increment); } /** * * @return sum of number of request invocations from all counters in the * collection */ public synchronized int getTotalRequests() { int totalRequests=0; for(RequestCounterImpl counter : counters.values()) { totalRequests += counter.getTotalRequests(); } return totalRequests; } /** * * @return sum of number of failed request invocations from all counters in the * collection */ public synchronized int getTotalFailed() { int totalFailed=0; for(RequestCounterImpl counter : counters.values()) { totalFailed += counter.getFailed(); } return totalFailed; } /** * * @return sum of number of successful request invocations from all counters in the * collection */ public synchronized int getTotalSuccessful() { int totalFailed=0; for(RequestCounterImpl counter : counters.values()) { totalFailed += counter.getSuccessful(); } return totalFailed; } /** * Reset all counters. */ public synchronized void reset() { for(RequestCounterImpl counter : counters.values()) { counter.reset(); } } /** * Shutdown all counters. */ public synchronized void shutdown() { for(RequestCounterImpl counter : counters.values()) { counter.shutdown(); } } /** * * @return keyset */ public Set<T> keySet() { return counters.keySet(); } public RequestCounter getTotalRequestCounter() { return new RequestCounter() { @Override public int getFailed() { return getTotalFailed(); } @Override public String getName(){ return name+"Totals"; } @Override public int getTotalRequests(){ return RequestCounters.this.getTotalRequests(); } @Override public void reset() { RequestCounters.this.reset(); } @Override public void shutdown() { RequestCounters.this.shutdown(); } @Override public int getSuccessful() { return RequestCounters.this.getTotalSuccessful(); } }; } }