/* * (non-Javadoc) * * @see ch.elexis.data.cache.IPersistentObjectCache#stat() */ public void stat() { long total = hits + misses + removed + expired; if (total != 0) { StringBuilder sb = new StringBuilder(); sb.append("--------- cache statistics ------\n") .append("Total read:\t") .append(total) .append("\n") .append("cache hits:\t") .append(hits) .append(" (") .append(hits * 100 / total) .append("%)\n") .append("object expired:\t") .append(expired) .append(" (") .append(expired * 100 / total) .append("%)\n") .append("cache missed:\t") .append(misses) .append(" (") .append(misses * 100 / total) .append("%)\n") .append("object removed:\t") .append(removed) .append(" (") .append(removed * 100 / total) .append("%)\n") .append("Object inserts:\t") .append(inserts) .append("\n"); log.log(sb.toString(), Log.INFOS); } }
/** * Heartbeat (wieder) laufen lassen. * * @param immediately true: Sofort einen ersten beat losschicken, false: im normalen Rhythmus * bleiben. */ public void resume(boolean immediately) { isSuspended = false; log.log("resume", Log.DEBUGMSG); // $NON-NLS-1$ if (immediately) { theBeat.run(); } }
/** * A Cache with soft references and optional expiring items The cache keeps count on numbes of items * that are added, removed or expired and can display its statistic * * @author Gerry */ @SuppressWarnings("unchecked") public class SoftCache<K> implements IPersistentObjectCache<K> { private static boolean enabled = true; protected Map<K, CacheEntry> cache; protected long hits, misses, removed, inserts, expired; protected Log log = Log.get("SoftCache"); public SoftCache() { // must be thread-safe cache = Collections.synchronizedMap(new HashMap<K, CacheEntry>()); } public SoftCache(final int num, final float load) { cache = Collections.synchronizedMap(new HashMap<K, CacheEntry>(num, load)); } public SoftCache(final int num) { cache = Collections.synchronizedMap(new HashMap<K, CacheEntry>(num)); } /* * (non-Javadoc) * * @see ch.elexis.data.cache.IPersistentObjectCache#put(K, java.lang.Object, int) */ public void put(final K key, final Object object, final int timeToCacheInSeconds) { if (enabled) { cache.put(key, new CacheEntry(object, timeToCacheInSeconds)); inserts++; } } /* * (non-Javadoc) * * @see ch.elexis.data.cache.IPersistentObjectCache#get(K) */ public Object get(final K key) { if (!enabled) { return null; } synchronized (cache) { CacheEntry ref = cache.get(key); if (ref == null) { misses++; return null; } Object ret = ref.get(); if (ret == null) { remove(key); return null; } else { hits++; return ret; } } } /* * (non-Javadoc) * * @see ch.elexis.data.cache.IPersistentObjectCache#remove(K) */ public void remove(final K key) { synchronized (cache) { cache.remove(key); removed++; } } /* * (non-Javadoc) * * @see ch.elexis.data.cache.IPersistentObjectCache#clear() */ public void clear() { synchronized (cache) { purge(); cache.clear(); } } /* * (non-Javadoc) * * @see ch.elexis.data.cache.IPersistentObjectCache#stat() */ public void stat() { long total = hits + misses + removed + expired; if (total != 0) { StringBuilder sb = new StringBuilder(); sb.append("--------- cache statistics ------\n") .append("Total read:\t") .append(total) .append("\n") .append("cache hits:\t") .append(hits) .append(" (") .append(hits * 100 / total) .append("%)\n") .append("object expired:\t") .append(expired) .append(" (") .append(expired * 100 / total) .append("%)\n") .append("cache missed:\t") .append(misses) .append(" (") .append(misses * 100 / total) .append("%)\n") .append("object removed:\t") .append(removed) .append(" (") .append(removed * 100 / total) .append("%)\n") .append("Object inserts:\t") .append(inserts) .append("\n"); log.log(sb.toString(), Log.INFOS); } } /* * (non-Javadoc) * * @see ch.elexis.data.cache.IPersistentObjectCache#purge() */ public void purge() { synchronized (cache) { Iterator<Entry<K, CacheEntry>> it = cache.entrySet().iterator(); long freeBefore = Runtime.getRuntime().freeMemory(); while (it.hasNext()) { Entry<K, CacheEntry> e = it.next(); CacheEntry ce = e.getValue(); ce.expires = 0; ce.get(); it.remove(); } if (Hub.plugin.DEBUGMODE) { long freeAfter = Runtime.getRuntime().freeMemory(); StringBuilder sb = new StringBuilder(); sb.append("Cache purge: Free memore before: ") .append(freeBefore) .append(", free memory after: ") .append(freeAfter) .append("\n"); Hub.log.log(sb.toString(), Log.INFOS); } } } /* * (non-Javadoc) * * @see ch.elexis.data.cache.IPersistentObjectCache#reset() */ public synchronized void reset() { purge(); cache.clear(); } public class CacheEntry extends SoftReference { long expires; public CacheEntry(final Object obj, final int timeInSeconds) { super(obj); expires = System.currentTimeMillis() + timeInSeconds * 1000; } @Override public synchronized Object get() { Object ret = super.get(); if (System.currentTimeMillis() > expires) { expired++; super.clear(); ret = null; } return ret; } } }
/** Heartbeat aussetzen (geht im Hintergrund weiter, wird aber nicht mehr weitergeleitet) */ public void suspend() { log.log("suspending", Log.DEBUGMSG); // $NON-NLS-1$ isSuspended = true; }
/** * Heartbeat is an event source, that fires events at user-definable intervals to all * HeartListeners. All actions that must be repeated regularly should be registered as * HeartListener. They will all be called at about the specified rate, but not in a guaranteed * particular order and not necessarily at exactly identical intervals. * * <p>Heartbeat löst das Pinger-Konzept ab. Der Heartbeat ist ein Singleton, das alle * Hub.localCfg.get(heartbeatrate,30) Sekunden einen Event feuert. Wer reglmässige Aktionen * durchführen will, kann sich als HeartbeatListener registrieren. Dieses Konzept hat gegenüber * individuellen update-Threads den Vorteil, dass die Netzwerk- und Datenbankbelastung, sowie die * Zahl der gleichzeitig laufenden Threads limitiert wird. Der Heartbeat sorgt dafür, dass die * listener der Reihe nach (aber ncht in einer definierten Reihenfolge) aufgerufen werden. * * <p>The client registering a listener can define the frequency, whether the listener should be * called at every single heart beat or at a lower frequency. * * @author gerry */ public class Heartbeat { /** Registering a listener using FREQUENCY_HIGH, it is called at every single heartbeat. */ public static final int FREQUENCY_HIGH = 1; /** Registering a listener using FREQUENCY_MEDIUM, it is called at every 4th heartbeat. */ public static final int FREQUENCY_MEDIUM = 2; /** Registering a listener using FREQUENCY_LOW, it is called at every 16th heartbeat. */ public static final int FREQUENCY_LOW = 3; private beat theBeat; private Timer pacer; private boolean isSuspended; private static Heartbeat theHeartbeat; private CopyOnWriteArrayList<HeartListener> highFrequencyListeners; private CopyOnWriteArrayList<HeartListener> mediumFrequencyListeners; private CopyOnWriteArrayList<HeartListener> lowFrequencyListeners; private static Log log = Log.get("Heartbeat"); // $NON-NLS-1$ private Heartbeat() { theBeat = new beat(); highFrequencyListeners = new CopyOnWriteArrayList<HeartListener>(); mediumFrequencyListeners = new CopyOnWriteArrayList<HeartListener>(); lowFrequencyListeners = new CopyOnWriteArrayList<HeartListener>(); pacer = new Timer(true); int interval = Hub.localCfg.get(PreferenceConstants.ABL_HEARTRATE, 30); // $NON-NLS-1$ isSuspended = true; pacer.schedule(theBeat, 0, interval * 1000L); } /** * Das Singleton holen * * @return den Heartbeat der Anwendung */ public static Heartbeat getInstance() { if (theHeartbeat == null) { theHeartbeat = new Heartbeat(); } return theHeartbeat; } /** * Heartbeat (wieder) laufen lassen. * * @param immediately true: Sofort einen ersten beat losschicken, false: im normalen Rhythmus * bleiben. */ public void resume(boolean immediately) { isSuspended = false; log.log("resume", Log.DEBUGMSG); // $NON-NLS-1$ if (immediately) { theBeat.run(); } } /** Heartbeat aussetzen (geht im Hintergrund weiter, wird aber nicht mehr weitergeleitet) */ public void suspend() { log.log("suspending", Log.DEBUGMSG); // $NON-NLS-1$ isSuspended = true; } /** Heartbeat stoppen (kann dann nicht mehr gestartet werden) */ public void stop() { log.log("stopping", Log.DEBUGMSG); // $NON-NLS-1$ pacer.cancel(); } /** * Einen Listener registrieren. Achtung: Muss unbedingt mit removeListener deregistriert werden * Calls addListener(listen, FREQUENCY_HIGH) * * @param listen der Listener */ public void addListener(HeartListener listen) { addListener(listen, FREQUENCY_HIGH); } /** * Add listener using the specified frequency. Must be de-regsitered again using removeListener * * @param listener * @param frequency the frequency to call this listener. One of FREQUENCY_HIGH, FREQUENCY_MEDIUM, * FREQUENCY_LOW */ public void addListener(HeartListener listen, int frequency) { if (!highFrequencyListeners.contains(listen) && !mediumFrequencyListeners.contains(listen) && !lowFrequencyListeners.contains(listen)) { switch (frequency) { case FREQUENCY_HIGH: highFrequencyListeners.add(listen); break; case FREQUENCY_MEDIUM: mediumFrequencyListeners.add(listen); break; case FREQUENCY_LOW: lowFrequencyListeners.add(listen); break; } } } /** * Einen Listener wieder austragen * * @param listen */ public void removeListener(HeartListener listen) { // remove the listener from the three lists // actually, it's contained in only one of them, but we don't know which one highFrequencyListeners.remove(listen); mediumFrequencyListeners.remove(listen); lowFrequencyListeners.remove(listen); } /** * we beat asynchronously, because most listeners will update their views * * @author Gerry */ private class beat extends TimerTask { private static final int FREQUENCY_HIGH_MULTIPLIER = 1; private static final int FREQUENCY_MEDIUM_MULTIPLIER = 4; private static final int FREQUENCY_LOW_MULTIPLIER = 16; // multiplier for resetting counter after each round private static final int RESET_MULTIPLIER = FREQUENCY_LOW_MULTIPLIER; private int counter = 0; @Override public void run() { if (!isSuspended) { Desk.getDisplay() .asyncExec( new Runnable() { public void run() { // low frequency if (counter % FREQUENCY_LOW_MULTIPLIER == 0) { log.log("Heartbeat low", Log.DEBUGMSG); // $NON-NLS-1$ for (HeartListener l : lowFrequencyListeners) { l.heartbeat(); } } // medium frequency if (counter % FREQUENCY_MEDIUM_MULTIPLIER == 0) { log.log("Heartbeat medium", Log.DEBUGMSG); // $NON-NLS-1$ for (HeartListener l : mediumFrequencyListeners) { l.heartbeat(); } } // high frequency if (counter % FREQUENCY_HIGH_MULTIPLIER == 0) { log.log("Heartbeat high", Log.DEBUGMSG); // $NON-NLS-1$ for (HeartListener l : highFrequencyListeners) { l.heartbeat(); } } } }); } counter++; counter %= RESET_MULTIPLIER; } } public interface HeartListener { /** * Die Methode heartbeat wird in "einigermassen" regelmässigen (aber nicht garantiert immer * genau identischen) Abständen aufgerufen */ public void heartbeat(); } }
/** Heartbeat stoppen (kann dann nicht mehr gestartet werden) */ public void stop() { log.log("stopping", Log.DEBUGMSG); // $NON-NLS-1$ pacer.cancel(); }