
Author: ceki Date: Tue Sep 2 18:34:48 2008 New Revision: 1790 Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/LRUCache.java logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/PackageInfo.java logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/LRUCacheTest.java logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/Simulator.java logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/UtilTest.java logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/lru/ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/lru/Event.java logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/lru/T_Entry.java logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/lru/T_LRUCache.java Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/Util.java logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/ThrowableInformation.java Log: LBGENERAL-23 Extracting package information is a time consuming process. Improve performance by keeping previously found results in a cache. Added an LRUCache with accompanying test cases for this purpose. Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/LRUCache.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/LRUCache.java Tue Sep 2 18:34:48 2008 @@ -0,0 +1,156 @@ +package ch.qos.logback.classic.pattern; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class LRUCache<K, V> { + + Map<K, Entry> map = new HashMap<K, Entry>(); + Entry head; + Entry tail; + + int limit; + + LRUCache(int limit) { + if(limit < 1) { + throw new IllegalArgumentException("limit cannnot be smaller than 1"); + } + + this.limit = limit; + + head = new Entry(null, null); + tail = head; + } + + public void put(K key, V value) { + Entry entry = map.get(key); + if (entry == null) { + entry = new Entry(key, value); + map.put(key, entry); + } + moveToTail(entry); + while(map.size() > limit) { + removeHead(); + } + } + + public V get(K key) { + Entry existing = map.get(key); + if (existing == null) { + return null; + } else { + moveToTail(existing); + return existing.value; + } + } + + private void removeHead() { + //System.out.println("RemoveHead called"); + map.remove(head.key); + head = head.next; + head.prev = null; + } + + private void moveToTail(Entry e) { + rearrangePreexistingLinks(e); + rearrangeTailLinks(e); + } + + private void rearrangePreexistingLinks(Entry e) { + if (e.prev != null) { + e.prev.next = e.next; + } + if (e.next != null) { + e.next.prev = e.prev; + } + if(head == e) { + head = e.next; + } + } + + private void rearrangeTailLinks(Entry e) { + if(head == tail) { + head = e; + } + Entry preTail = tail.prev; + if(preTail != null) { + preTail.next = e; + } + e.prev = preTail; + e.next = tail; + tail.prev = e; + } + + + public void dump() { + Entry e = head; + System.out.print("N:"); + while (e != null) { + //System.out.print(e+"->"); + System.out.print(e.key+", "); + e = e.next; + } + System.out.println(); + } + + List<K> keyList() { + List<K> result = new LinkedList<K>(); + Entry e = head; + while (e != tail) { + result.add(e.key); + e = e.next; + } + return result; + } + + // ================================================================ + private class Entry { + Entry next; + Entry prev; + K key; + V value; + + Entry(K k, V v) { + this.key = k; + this.value = v; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((key == null) ? 0 : key.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final Entry other = (Entry) obj; + if (key == null) { + if (other.key != null) + return false; + } else if (!key.equals(other.key)) + return false; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } + + @Override + public String toString() { + return "(" + key + ", " + value + ")"; + } + } + +} Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/PackageInfo.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/PackageInfo.java Tue Sep 2 18:34:48 2008 @@ -0,0 +1,13 @@ +package ch.qos.logback.classic.pattern; + +public class PackageInfo { + + + String jarName; + String version; + + PackageInfo(String jarName, String version) { + this.jarName = jarName; + this.version = version; + } +} Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/Util.java ============================================================================== --- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/Util.java (original) +++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/Util.java Tue Sep 2 18:34:48 2008 @@ -1,33 +1,128 @@ /** - * LOGBack: the reliable, fast and flexible logging library for Java. - * - * Copyright (C) 1999-2006, QOS.ch - * - * This library is free software, you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation. + * Logback: the generic, reliable, fast and flexible logging framework. + * + * Copyright (C) 2000-2008, QOS.ch + * + * This library is free software, you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation. */ package ch.qos.logback.classic.pattern; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + import org.slf4j.Marker; +/** + * + * @author James Strachan + * @author Ceki Gulcu + */ public class Util { + static Map<String, PackageInfo> cache = new HashMap<String, PackageInfo>(); + static public boolean match(Marker marker, Marker[] markerArray) { - if(markerArray == null) { + if (markerArray == null) { throw new IllegalArgumentException("markerArray should not be null"); } - - //System.out.println("event marker="+marker); - + + // System.out.println("event marker="+marker); + final int size = markerArray.length; - for(int i = 0; i < size; i++) { - //System.out.println("other:"+markerArray[i]); - - if(marker.contains(markerArray[i])) { + for (int i = 0; i < size; i++) { + // System.out.println("other:"+markerArray[i]); + + if (marker.contains(markerArray[i])) { return true; } } return false; } + + static String getVersion(String className) { + String packageName = getPackageName(className); + Package aPackage = Package.getPackage(packageName); + if (aPackage != null) { + String v = aPackage.getImplementationVersion(); + if (v == null) { + return "na"; + } else { + return v; + } + } + return "na"; + } + + static public PackageInfo getPackageInfo(String className) { + PackageInfo pi = cache.get(className); + if(pi != null) { + return pi; + } + String version = getVersion(className); + String jarname = getJarNameOfClass(className); + pi = new PackageInfo(jarname, version); + //cache.put(className, pi); + return pi; + } + + static String getPackageName(String className) { + int j = className.lastIndexOf('.'); + return className.substring(0, j); + } + + /** + * Uses the context class path or the current global class loader to deduce + * the file that the given class name comes from + */ + static String getJarNameOfClass(String className) { + try { + Class type = findClass(className); + if (type != null) { + URL resource = type.getClassLoader().getResource( + type.getName().replace('.', '/') + ".class"); + // "jar:file:/C:/java/../repo/groupId/artifact/1.3/artifact-1.3.jar!/com/some/package/Some.class + if (resource != null) { + String text = resource.toString(); + int idx = text.lastIndexOf('!'); + if (idx > 0) { + text = text.substring(0, idx); + // now lets remove all but the file name + idx = text.lastIndexOf('/'); + if (idx > 0) { + text = text.substring(idx + 1); + } + idx = text.lastIndexOf('\\'); + if (idx > 0) { + text = text.substring(idx + 1); + } + return text; + } + } + } + } catch (Exception e) { + // ignore + } + return "na"; + } + + static private Class findClass(String className) { + try { + return Thread.currentThread().getContextClassLoader() + .loadClass(className); + } catch (ClassNotFoundException e) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e1) { + try { + return Util.class.getClassLoader().loadClass(className); + } catch (ClassNotFoundException e2) { + return null; + } + } + } + } + } Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/ThrowableInformation.java ============================================================================== --- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/ThrowableInformation.java (original) +++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/ThrowableInformation.java Tue Sep 2 18:34:48 2008 @@ -29,7 +29,7 @@ } /** - * The string representation of the exceptopn (throwable) that this object + * The string representation of the throwable that this object * represents. */ public String[] getThrowableStrRep() { Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/LRUCacheTest.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/LRUCacheTest.java Tue Sep 2 18:34:48 2008 @@ -0,0 +1,71 @@ +package ch.qos.logback.classic.pattern; + +import static org.junit.Assert.assertEquals; + +import java.util.LinkedList; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import ch.qos.logback.classic.pattern.lru.Event; +import ch.qos.logback.classic.pattern.lru.T_LRUCache; + +public class LRUCacheTest { + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + + } + + @Test + public void smoke() { + LRUCache<String, String> cache = new LRUCache<String, String>(2); + cache.put("a", "a"); + cache.put("b", "b"); + cache.put("c", "c"); + List<String> witness = new LinkedList<String>(); + witness.add("b"); + witness.add("c"); + assertEquals(witness, cache.keyList()); + } + + @Test + public void typicalScenarioTest() { + int simulationLen = 1000 * 20; + int cacheSize = 500; + int worldSize = 10000; + doScenario(simulationLen, cacheSize, worldSize); + } + + @Test + public void scenarioCoverageTest() { + int simulationLen = 1000 * 20; + int[] cacheSizes = new int[] {1,5,10,100,1000,5000,10000}; + int[] worldSizes = new int[] {1,10,100,1000,20000}; + for (int i = 0; i < cacheSizes.length; i++) { + for (int j = 0; j < worldSizes.length; j++) { + System.out.println("cacheSize="+cacheSizes[i]+", worldSize="+worldSizes[j]); + doScenario(simulationLen, cacheSizes[i], worldSizes[j]); + } + } + } + + void doScenario(int simulationLen, int chacheSize, int worldSize) { + int cacheSize = 500; + int get2PutRatio = 10; + + Simulator simulator = new Simulator(worldSize, get2PutRatio); + List<Event> scenario = simulator.generateScenario(simulationLen); + LRUCache<String, String> lruCache = new LRUCache<String, String>(cacheSize); + T_LRUCache<String> tlruCache = new T_LRUCache<String>(cacheSize); + simulator.simulate(scenario, lruCache, tlruCache); + assertEquals(tlruCache.ketList(), lruCache.keyList()); + } +} Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/Simulator.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/Simulator.java Tue Sep 2 18:34:48 2008 @@ -0,0 +1,70 @@ +package ch.qos.logback.classic.pattern; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import ch.qos.logback.classic.pattern.lru.Event; +import ch.qos.logback.classic.pattern.lru.T_LRUCache; + +public class Simulator { + + + Random random; + + int worldSize; + int get2PutRatio; + + public Simulator(int worldSize, int get2PutRatio) { + this.worldSize = worldSize; + this.get2PutRatio = get2PutRatio; + long seed = System.nanoTime(); + System.out.println("seed is "+seed); + random = new Random(seed); + } + + public List<Event> generateScenario(int len) { + List<Event> scenario = new ArrayList<Event>(); + + for(int i = 0; i < len; i++) { + + int r = random.nextInt(get2PutRatio); + boolean put = false; + if(r == 0) { + put = true; + } + r = random.nextInt(worldSize); + Event<String> e = new Event<String>(put, String.valueOf(r)); + scenario.add(e); + } + return scenario; + } + + public void simulate(List<Event> scenario, LRUCache<String, String> lruCache, T_LRUCache<String> tlruCache) { + for(Event<String> e: scenario) { + if(e.put) { + lruCache.put(e.k, e.k); + tlruCache.put(e.k); + } else { + String r0 = lruCache.get(e.k); + String r1 = tlruCache.get(e.k); + if(r0 != null) { + assertEquals(r0, e.k); + } + assertEquals(r0, r1); + } + } + } + +// void compareAndDumpIfDifferent(LRUCache<String, String> lruCache, T_LRUCache<String> tlruCache) { +// lruCache.dump(); +// tlruCache.dump(); +// if(!lruCache.keyList().equals(tlruCache.ketList())) { +// lruCache.dump(); +// tlruCache.dump(); +// throw new AssertionFailedError("s"); +// } +// } +} Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/UtilTest.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/UtilTest.java Tue Sep 2 18:34:48 2008 @@ -0,0 +1,84 @@ +package ch.qos.logback.classic.pattern; + +import java.util.Random; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import com.icegreen.greenmail.util.GreenMail; +import com.icegreen.greenmail.util.ServerSetup; + +public class UtilTest { + + int diff = 1024 + new Random().nextInt(10000); + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void withGreenMail() { + try { + ServerSetup serverSetup = new ServerSetup(-1, "localhost", + ServerSetup.PROTOCOL_SMTP); + GreenMail greenMail = new GreenMail((ServerSetup) null); + // greenMail.start(); + } catch (Throwable e) { + // e.printStackTrace(); + StackTraceElement[] stea = e.getStackTrace(); + for (StackTraceElement ste : stea) { + String className = ste.getClassName(); + PackageInfo pi = Util.getPackageInfo(className); + System.out.println(" at " + className + "." + ste.getMethodName() + + "(" + ste.getFileName() + ":" + ste.getLineNumber() + ") [" + + pi.jarName + ":" + pi.version + "]"); + } + } + } + + public void doPerf(boolean versionExtraction) { + try { + ServerSetup serverSetup = new ServerSetup(-1, "localhost", + ServerSetup.PROTOCOL_SMTP); + GreenMail greenMail = new GreenMail((ServerSetup) null); + // greenMail.start(); + } catch (Throwable e) { + StackTraceElement[] stea = e.getStackTrace(); + if (versionExtraction) { + for (StackTraceElement ste : stea) { + String className = ste.getClassName(); + PackageInfo pi = Util.getPackageInfo(className); + } + } + } + } + + double loop(int len, boolean ve) { + long start = System.nanoTime(); + for (int i = 0; i < len; i++) { + doPerf(ve); + } + return (1.0*System.nanoTime() - start)/len/1000; + } + + @Test + @Ignore + public void perfTest() { + int len = 1000; + loop(len, false); + double d0 = loop(len, false); + + System.out.println("false " + d0); + + loop(len, true); + double d1 = loop(len, true); + + System.out.println("false " + d1); + } +} Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/lru/Event.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/lru/Event.java Tue Sep 2 18:34:48 2008 @@ -0,0 +1,20 @@ +package ch.qos.logback.classic.pattern.lru; + +public class Event<K> { + + final public boolean put; + final public K k; + + public Event(boolean put, K k) { + this.put = put; + this.k = k; + } + + public String toString() { + if(put) { + return "Event: put, "+k; + } else { + return "Event: get, "+k; + } + } +} Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/lru/T_Entry.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/lru/T_Entry.java Tue Sep 2 18:34:48 2008 @@ -0,0 +1,32 @@ +package ch.qos.logback.classic.pattern.lru; + +public class T_Entry<K> implements Comparable { + + K k; + long sequenceNumber; + + T_Entry(K k, long sn) { + this.k = k; + this.sequenceNumber = sn; + } + + public int compareTo(Object o) { + if(!(o instanceof T_Entry)) { + throw new IllegalArgumentException("arguments must be of type "+T_Entry.class); + } + + T_Entry other = (T_Entry) o; + if(sequenceNumber > other.sequenceNumber) { + return 1; + } + if(sequenceNumber == other.sequenceNumber) { + return 0; + } + return -1; + } + @Override + public String toString() { + return "("+k+","+sequenceNumber+")"; + //return "("+k+")"; + } +} Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/lru/T_LRUCache.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/pattern/lru/T_LRUCache.java Tue Sep 2 18:34:48 2008 @@ -0,0 +1,88 @@ +/** + * Logback: the generic, reliable, fast and flexible logging framework. + * + * Copyright (C) 2000-2008, QOS.ch + * + * This library is free software, you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation. + */ +package ch.qos.logback.classic.pattern.lru; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * This is an alternative (slower) implementation of LRUCache for testing + * purposes. + * + * @author Ceki Gulcu + */ +public class T_LRUCache<K> { + + int sequenceNumber; + final int cacheSize; + List<T_Entry<K>> cacheList = new LinkedList<T_Entry<K>>(); + + public T_LRUCache(int size) { + this.cacheSize = size; + } + + @SuppressWarnings("unchecked") + public void put(K k) { + sequenceNumber++; + T_Entry<K> te = getEntry(k); + if (te != null) { + te.sequenceNumber = sequenceNumber; + } else { + te = new T_Entry<K>(k, sequenceNumber); + cacheList.add(te); + } + Collections.sort(cacheList); + while(cacheList.size() > cacheSize) { + cacheList.remove(0); + } + } + + @SuppressWarnings("unchecked") + public K get(K k) { + T_Entry<K> te = getEntry(k); + if (te == null) { + return null; + } else { + te.sequenceNumber = ++sequenceNumber; + Collections.sort(cacheList); + return te.k; + } + } + + public List<K> ketList() { + List<K> keyList = new ArrayList<K>(); + for (T_Entry<K> e : cacheList) { + keyList.add(e.k); + } + return keyList; + } + + private T_Entry<K> getEntry(K k) { + for (int i = 0; i < cacheList.size(); i++) { + T_Entry<K> te = cacheList.get(i); + if (te.k.equals(k)) { + return te; + } + } + return null; + } + + public void dump() { + System.out.print("T:"); + for (T_Entry<K> te : cacheList) { + //System.out.print(te.toString()+"->"); + System.out.print(te.k+", "); + } + System.out.println(); + } + +}