/* * Copyright (c) 2010 StockPlay development team * All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package com.kapti.cache; import java.io.InputStream; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.TimerTask; import net.sf.cache4j.Cache; import net.sf.cache4j.CacheException; import net.sf.cache4j.CacheFactory; import org.apache.log4j.Logger; /** * \brief Cache manager die de cache performantie verhoogt door selectief te refreshen * * Om de performantie van de domme cache (flawed by design, aangezien de backend * geen weet heeft van de selectiecriteria van het object -- zie het verslag) * toch te verhogen, wordt er een aparte entity gebruikt (deze klasse) die zal * inhaken in de Proxy en bijhouden welke queries het meest afgevuurd worden. * Gebaseerd op deze informatie zal wanneer de backend idle is, selectief * nieuwe queries afgevuurd worden waardoor de cache meer relevante data bevat * en de delay voor de eindgebruiker hopelijk geminimaliseerd wordt. */ public class Manager extends TimerTask { // // Member data // private static Logger mLogger = Logger.getLogger(Manager.class); private static CacheFactory mFactory = getFactory(); private static Map<Cache, Object> mCaches = new HashMap<Cache, Object>(); private static Map<Cache, Stats> mCacheStats = new HashMap<Cache, Stats>(); private static int mManagerMisses = 0; private static int mManagerClears = 0; private final static int LIMIT_KEYS = 25; // // Construction // private static CacheFactory getFactory() { CacheFactory tFactory = CacheFactory.getInstance(); try { InputStream tStream = Manager.class.getResourceAsStream("/cache4j_config.xml"); if (tStream == null) { mLogger.error("could not find cache configuration directives"); return null; } tFactory.loadConfig(tStream); } catch (CacheException e) { mLogger.error("could not configure cache factory appropriately", e); return null; } return tFactory; } public static Cache getCache(String iCache) { Cache oCache = null; if (mFactory == null) { mLogger.error("factory not ready for usage, cannot instantiate cache '" + iCache + "'"); return null; } try { oCache = mFactory.getCache(iCache); if (oCache == null) throw new CacheException("cache is null!"); } catch (CacheException e) { mLogger.error("could not load cache '" + iCache + "'", e); } return oCache; } // // Methods // public static void setup(Cache cache, Object proxy) { mCacheStats.put(cache, new Stats()); mCaches.put(cache, proxy); } public static void miss(Cache cache, final CallKey callKey) { mLogger.debug("cache miss on entry " + cache.getCacheConfig().getCacheId() + "." + callKey.method.getName()); mManagerMisses++; if (! mCacheStats.containsKey(cache)) { return; } mCacheStats.get(cache).misses++; mCacheStats.get(cache).add(callKey); } public static void hit(Cache cache, final CallKey callKey) { mLogger.debug("cache hit on entry " + cache.getCacheConfig().getCacheId() + "." + callKey.method.getName()); if (! mCacheStats.containsKey(cache)) { return; } mCacheStats.get(cache).hits++; mCacheStats.get(cache).add(callKey); } public static void clear() { mLogger.info("clearing cache"); mManagerClears++; for (Cache cache : mCacheStats.keySet()) { try { cache.clear(); } catch (CacheException ce) { mLogger.error("could not clear cache " + cache.getCacheConfig().getCacheId(), ce); } mCacheStats.get(cache).clear(); } } public void run() { // Check if the backend is idle boolean isIdle = (mManagerMisses == 0); mManagerMisses = 0; // Only run if we got caches registered if (mCaches.size() == 0) return; // Process all caches int tRefreshCount = 0; for (Cache cache : mCaches.keySet()) { Object target = mCaches.get(cache); Map<CallKey, Entry> tEntries = mCacheStats.get(cache).entries; // Update and skim the entries CallKey[] tCallKeys = new CallKey[tEntries.size()]; tEntries.keySet().toArray(tCallKeys); for (int i = 0; i < tCallKeys.length; i++) { Entry entry = tEntries.get(tCallKeys[i]); entry.cycleratio *= 0.95; if (entry.count > 0) { entry.count = 0; entry.cycleratio += 10; } } final Map<CallKey, Entry> tEntriesFinal = tEntries; Arrays.sort(tCallKeys, new Comparator<CallKey>() { public int compare(CallKey a, CallKey b) { return (int)(tEntriesFinal.get(b).cycleratio - tEntriesFinal.get(a).cycleratio); } }); for (int i = LIMIT_KEYS; i < tCallKeys.length; i++) { tEntries.remove(tCallKeys[i]); } // Refresh the remaining entries if (isIdle) { for (CallKey callKey : tEntries.keySet()) { try { if (cache.get(callKey) == null) { Object result = callKey.method.invoke(target, callKey.args); try { mLogger.debug("refreshing cache entry " + cache.getCacheConfig().getCacheId() + "." + callKey.method.getName()); cache.put(callKey, result); tRefreshCount++; } catch (CacheException ce) { mLogger.error("could not refresh cache entry", ce); } } } catch (CacheException ce) { mLogger.error("could not request cache entry", ce); } catch (IllegalAccessException e) { mLogger.error("access to method denied while refreshing cache", e); } catch (InvocationTargetException e) { mLogger.error("exception thrown while refreshing cache", e); } } } } if (tRefreshCount > 0) { mLogger.debug("refreshed " + tRefreshCount + " cache entries while the backend was idle"); } } public static Map<Cache, ManagerInfo> getManagerInfo() throws Throwable { Map<Cache, ManagerInfo> oStats = new HashMap<Cache, ManagerInfo>(); for (Cache tCache : mCacheStats.keySet()) { Stats tStat = mCacheStats.get(tCache); ManagerInfo tInfo = new ManagerInfo( tStat.entries.size(), tStat.hits, tStat.misses, //Utils.size(mCacheStats.get(tCache)) -1, tStat.entries ); oStats.put(tCache, tInfo); } return oStats; } public static int getManagerClears() { return mManagerClears; } // // Subclasses // public static class ManagerInfo { final public int keys; final public int hits, misses; final public int size; final public Map<CallKey, Entry> entries; public ManagerInfo(int keys, int hits, int misses, int size, Map<CallKey, Entry> entries) { this.keys = keys; this.hits = hits; this.misses = misses; this.size = size; this.entries = entries; } } private static class Stats implements Serializable { public int hits, misses; public Map<CallKey, Entry> entries; public Stats() { entries = new HashMap<CallKey, Entry>(); hits = 0; misses = 0; } public void add(final CallKey callkey) { if (! entries.containsKey(callkey)) { entries.put(callkey, new Entry()); } entries.get(callkey).count++; } public void clear() { entries.clear(); } } public static class Entry implements Serializable { public int count; public double cycleratio; public Entry() { count = 0; cycleratio = 0; } } }