/* * Copyright 2011 Eric F. Savage, code@efsavage.com * * 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.ajah.util.log; import java.io.PrintStream; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import com.ajah.util.compare.EntryValueComparator; import com.ajah.util.text.Strings; /** * An atomic counter for keeping track of things, intended but not restricted to * tally against the values of an enum. * * @author efsavage * @param <T> */ public class Tally<T> { protected final ConcurrentMap<T, Long> map = new ConcurrentHashMap<>(); protected final AtomicLong errors = new AtomicLong(); protected final AtomicLong successes = new AtomicLong(); protected PrintStream out; /** * Public constructor. * * @param out * The output for reporting. */ public Tally(final PrintStream out) { this.out = out; } /** * Increment the error count. */ public void error() { this.errors.incrementAndGet(); } /** * Return the error count. * * @return The error count. */ public long getErrors() { return this.errors.get(); } /** * Return the success count. * * @return The success count. */ public long getSuccesses() { return this.successes.get(); } /** * Return the total count. * * @return The total count. */ public long getTotal() { return this.errors.get() + this.successes.get(); } /** * Generates a report only if the {@link #getTotal()} evenly divides by the * interval, and is greater than zero. * * @param interval * The interval to space reports by * @see #report() */ public void intervalReport(final int interval) { if (getTotal() > 0 && (getTotal() % interval) == 0) { report(); } } /** * Write a report with totals to the configured output. */ public void report() { report(0); } /** * Write a report with totals to the configured output. * * @param threshold * Only print totals of at least this value. */ public void report(final long threshold) { this.out.println(Strings.HYPEN35); this.out.println("Success/Error/Total: " + getSuccesses() + "/" + getErrors() + "/" + getTotal() + " - " + NumberFormat.getPercentInstance().format(1.0 * getSuccesses() / getTotal())); for (final T t : this.map.keySet()) { if (this.map.get(t).longValue() >= threshold) { this.out.println(t.toString() + ": " + this.map.get(t)); } } } public void reportTop(final int number) { this.out.println(Strings.HYPEN35); this.out.println("Success/Error/Total: " + getSuccesses() + "/" + getErrors() + "/" + getTotal() + " - " + NumberFormat.getPercentInstance().format(1.0 * getSuccesses() / getTotal())); List<Entry<T, Long>> top = new ArrayList<>(this.map.entrySet()); Collections.sort(top, new EntryValueComparator<T, Long>()); if (top.size() > number) { top = top.subList(0, number); } for (final Entry<T, Long> entry : top) { this.out.println(entry.getKey().toString() + ": " + entry.getValue()); } } /** * Increment the success count. */ public void success() { this.successes.incrementAndGet(); } /** * Tally an object with an increment of 1. This works best with enums. * * @param tallyObject * The object to tally by. */ public long tally(final T tallyObject) { return tally(tallyObject, 1); } /** * Get but do not increment the tally for an object. * * @param tallyObject * The object to get the current count for. */ public long getValue(final T tallyObject) { final Long value = this.map.get(tallyObject); if (value == null) { return 0; } return value.longValue(); } /** * Tally an object with a specific increment. This works best with enums. * * @param tallyObject * The object to tally by. * @param increment * The number to increment the tally by. */ public long tally(final T tallyObject, final long increment) { final Long oldValue = this.map.get(tallyObject); if (oldValue == null) { this.map.putIfAbsent(tallyObject, Long.valueOf(increment)); } else { final Long newValue = Long.valueOf(oldValue.intValue() + increment); this.map.replace(tallyObject, oldValue, newValue); } return this.map.get(tallyObject).longValue(); } }