diff --git a/src/zutil/ClassUtil.java b/src/zutil/ClassUtil.java index 9d77e7a..8be10f2 100755 --- a/src/zutil/ClassUtil.java +++ b/src/zutil/ClassUtil.java @@ -169,7 +169,8 @@ public class ClassUtil { * @return the first class in the stack that do not match the filter */ public static String getCallingClass(Class... filter){ - ArrayList filterStr = new ArrayList(); + ArrayList filterStr = new ArrayList(filter.length + 1); + filterStr.add(ClassUtil.class.getName()); for (Class clazz : filter) filterStr.add(clazz.getName()); diff --git a/src/zutil/log/CounterManager.java b/src/zutil/log/CounterManager.java new file mode 100644 index 0000000..85e50f1 --- /dev/null +++ b/src/zutil/log/CounterManager.java @@ -0,0 +1,170 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package zutil.log; + +import zutil.ClassUtil; + +import javax.management.MBeanServer; +import javax.management.ObjectName; +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * This class is used for performance measurements on an application. + * The counter values will be published in JMX and can be viewed in JConsole. + */ +public class CounterManager { + /** a two dimensional map [Class][Counter ID] for storing singleton objects **/ + private static HashMap> counters = new HashMap<>(); + + /** + * @param name a unique name/id of the counter + * @return a singleton instance of the counter name with the calling class as context. Will always return a valid object. + */ + public static Counter getCounter(String name) { + return getCounter(ClassUtil.getCallingClass(CounterManager.class), name); + } + /** + * @param clazz the counter context + * @param name a unique name/id of the counter + * @return a singleton instance of the counter name. Will always return a valid object. + */ + public static Counter getCounter(Class clazz, String name) { + return getCounter(clazz.getName(), name); + } + private static synchronized Counter getCounter(String clazz, String name) { + Counter counter; + if ( ! counters.containsKey(clazz) || ! counters.get(clazz).containsKey(name)) { + // Get the platform MBeanServer + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + // Unique identification of MBeans + counter = new Counter(name); + + try { + // Uniquely identify the MBeans and register them with the platform MBeanServer + ObjectName objectName = new ObjectName(clazz + ":name=" + counter.getName()); + mbs.registerMBean(counter, objectName); + // Register the singleton + if ( ! counters.containsKey(clazz)) + counters.put(clazz, new HashMap()); + counters.get(clazz).put(name, counter); + } catch (Exception e) { + e.printStackTrace(); + } + } + else + counter = counters.get(clazz).get(name); + return counter; + } + + + /** + * MBean Counter definition for publishing data in JMX + */ + public interface CounterMBean { + String getName(); + + int getValue(); + int getMax(); + int getMin(); + double getAverage(); + } + + + public static class Counter implements CounterMBean{ + private final String name; + private int max; + private int min; + private double average; + private int sampleCount; + private AtomicInteger counter = new AtomicInteger(); + + + protected Counter(String name){ + this.name = name; + } + + + public void add(int i){ + int prev = counter.getAndAdd(i); + updateMetaData(prev + i); + } + public void set(int i){ + counter.getAndSet(i); + updateMetaData(i); + } + public void increment(){ + int i = counter.incrementAndGet(); + updateMetaData(i); + } + public void decrement(){ + int i = counter.decrementAndGet(); + updateMetaData(i); + } + private void updateMetaData(int i){ + if (max < i) + max = i; + if (min > i) + min = i; + + average = (average*sampleCount + i) / ++sampleCount; + } + + + @Override + public String getName() { + return name; + } + /** + * @return current value of the counter + */ + @Override + public int getValue(){ + return counter.intValue(); + } + /** + * @return the maximum registered value over the lifetime of the counter + */ + @Override + public int getMax() { + return max; + } + /** + * @return the minimum registered value over the lifetime of the counter + */ + @Override + public int getMin() { + return min; + } + /** + * @return the average value of the counter + */ + @Override + public double getAverage() { + return average; + } + } +} diff --git a/test/zutil/log/CounterManagerTest.java b/test/zutil/log/CounterManagerTest.java new file mode 100644 index 0000000..3062973 --- /dev/null +++ b/test/zutil/log/CounterManagerTest.java @@ -0,0 +1,148 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package zutil.log; + +import org.junit.Test; +import zutil.log.CounterManager.Counter; + +import static org.junit.Assert.*; + +public class CounterManagerTest { + + @Test + public void singletonTest(){ + Counter c1 = CounterManager.getCounter("TestCounter"); + Counter c2 = CounterManager.getCounter("TestCounter"); + assertSame(c1, c2); + + c1 = CounterManager.getCounter("TestCounter"); + c2 = CounterManager.getCounter(CounterManagerTest.class, "TestCounter"); + assertSame(c1, c2); + + c1 = CounterManager.getCounter(CounterManagerTest.class, "TestCounter"); + c2 = CounterManager.getCounter(CounterManagerTest.class, "TestCounter"); + assertSame(c1, c2); + } + + @Test + public void addTest(){ + Counter c1 = CounterManager.getCounter("AddTestCounter"); + assertEquals(0, c1.getValue()); + + c1.add(10); + assertEquals(10, c1.getValue()); + + c1.add(20); + c1.add(30); + assertEquals(60, c1.getValue()); + } + + @Test + public void setTest(){ + Counter c1 = CounterManager.getCounter("SetTestCounter"); + assertEquals(0, c1.getValue()); + + c1.set(10); + assertEquals(10, c1.getValue()); + + c1.set(20); + c1.set(30); + assertEquals(30, c1.getValue()); + } + + @Test + public void incrementTest(){ + Counter c1 = CounterManager.getCounter("IncrementTestCounter"); + assertEquals(0, c1.getValue()); + + c1.increment(); + assertEquals(1, c1.getValue()); + + c1.increment(); + c1.increment(); + c1.increment(); + assertEquals(4, c1.getValue()); + } + + @Test + public void decrementTest(){ + Counter c1 = CounterManager.getCounter("DecrementTestCounter"); + assertEquals(0, c1.getValue()); + + c1.decrement(); + assertEquals(-1, c1.getValue()); + + c1.decrement(); + c1.decrement(); + c1.decrement(); + assertEquals(-4, c1.getValue()); + } + + @Test + public void maxTest(){ + Counter c1 = CounterManager.getCounter("MaxTestCounter"); + assertEquals(0, c1.getMax()); + + c1.set(10); + assertEquals(10, c1.getMax()); + + c1.set(50); + assertEquals(50, c1.getMax()); + + c1.set(0); + assertEquals(50, c1.getMax()); + } + + @Test + public void minTest(){ + Counter c1 = CounterManager.getCounter("MinTestCounter"); + assertEquals(0, c1.getMin()); + + c1.set(10); + assertEquals(0, c1.getMin()); + + c1.set(-50); + assertEquals(-50, c1.getMin()); + + c1.set(0); + assertEquals(-50, c1.getMin()); + } + + @Test + public void averageTest(){ + Counter c1 = CounterManager.getCounter("MinTestCounter"); + assertEquals(0, (int)c1.getAverage()); + + c1.set(10); + assertEquals(10, (int)c1.getAverage()); + + c1.set(10); + assertEquals(10, (int)c1.getAverage()); + + c1.set(70); + assertEquals(30, (int)c1.getAverage()); + } + +} \ No newline at end of file