/* * Copyright (c) 2002-2009 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.cache; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Logger; public class AdaptiveCacheManager { private static final Logger log = Logger .getLogger( AdaptiveCacheManager.class.getName() ); private float decreaseRatio = 1.15f; private float increaseRatio = 1.1f; private final List<AdaptiveCacheElement> caches = new LinkedList<AdaptiveCacheElement>(); private final List<SoftLruCache<?,?>> softCaches = new ArrayList<SoftLruCache<?,?>>(); private AdaptiveCacheWorker workerThread; public synchronized void registerCache( Cache<?,?> cache, float ratio, int minSize ) { if ( cache == null || ratio >= 1 || ratio <= 0 || minSize < 0 ) { throw new IllegalArgumentException( " cache=" + cache + " ratio =" + ratio + " minSize=" + minSize ); } if ( cache instanceof SoftLruCache<?,?> ) { softCaches.add( (SoftLruCache<?,?>) cache ); return; } for ( AdaptiveCacheElement element : caches ) { if ( element.getCache() == cache ) { log.fine( "Cache[" + cache.getName() + "] already registered." ); return; } } AdaptiveCacheElement element = new AdaptiveCacheElement( cache, ratio, minSize ); caches.add( element ); cache.setAdaptiveStatus( true ); log.fine( "Cache[" + cache.getName() + "] threshold=" + ratio + "minSize=" + minSize + " registered." ); } public synchronized void unregisterCache( Cache<?,?> cache ) { if ( cache == null ) { throw new IllegalArgumentException( "Null cache" ); } if ( cache instanceof SoftLruCache<?,?> ) { softCaches.remove( cache ); return; } Iterator<AdaptiveCacheElement> itr = caches.iterator(); while ( itr.hasNext() ) { AdaptiveCacheElement element = itr.next(); if ( element.getCache() == cache ) { itr.remove(); break; } } log.fine( "Cache[" + cache.getName() + "] removed." ); } synchronized AdaptiveCacheElement getAdaptiveCacheElementIndex( Cache<?,?> cache ) { for ( AdaptiveCacheElement element : caches ) { if ( element.getCache() == cache ) { return element; } } return null; } private void parseParams( Map<Object,Object> params ) { if ( params == null ) { return; } if ( params.containsKey( "adaptive_cache_worker_sleep_time" ) ) { Object value = params.get( "adaptive_cache_worker_sleep_time" ); int sleepTime = 3000; try { sleepTime = Integer.parseInt( (String) value ); } catch ( NumberFormatException e ) { log.warning( "Unable to parse apdaptive_cache_worker_sleep_time " + value ); } workerThread.setSleepTime( sleepTime ); } if ( params.containsKey( "adaptive_cache_manager_decrease_ratio" ) ) { Object value = params.get( "adaptive_cache_manager_decrease_ratio" ); try { decreaseRatio = Float.parseFloat( (String) value ); } catch ( NumberFormatException e ) { log.warning( "Unable to parse adaptive_cache_manager_decrease_ratio " + value ); } if ( decreaseRatio < 1 ) { decreaseRatio = 1.0f; } } if ( params.containsKey( "adaptive_cache_manager_increase_ratio" ) ) { Object value = params.get( "adaptive_cache_manager_increase_ratio" ); try { increaseRatio = Float.parseFloat( (String) value ); } catch ( NumberFormatException e ) { log.warning( "Unable to parse adaptive_cache_manager_increase_ratio " + value ); } if ( increaseRatio < 1 ) { increaseRatio = 1.0f; } } } public void start( Map<Object,Object> params ) { workerThread = new AdaptiveCacheWorker(); parseParams( params ); workerThread.start(); } public void stop() { workerThread.markDone(); workerThread = null; } Collection<AdaptiveCacheElement> getCaches() { return caches; } private class AdaptiveCacheWorker extends Thread { private boolean done = false; private int sleepTime = 3000; AdaptiveCacheWorker() { super( "AdaptiveCacheWorker" ); } void setSleepTime( int sleepTime ) { this.sleepTime = sleepTime; } public synchronized void run() { while ( !done ) { try { adaptCaches(); adaptSoftCaches(); this.wait( sleepTime ); } catch ( InterruptedException e ) { Thread.interrupted(); } } } void markDone() { done = true; } } public void adaptCaches() { List<AdaptiveCacheElement> copy = new LinkedList<AdaptiveCacheElement>(); synchronized ( this ) { copy.addAll( caches ); } for ( AdaptiveCacheElement element : copy ) { adaptCache( element ); } } public synchronized void adaptSoftCaches() { for ( SoftLruCache<?,?> cache : softCaches ) { cache.pollAll(); } } public void adaptCache( Cache<?,?> cache ) { if ( cache == null ) { throw new IllegalArgumentException( "Null cache" ); } AdaptiveCacheElement element = getAdaptiveCacheElementIndex( cache ); if ( element != null ) { adaptCache( element ); } } private void adaptCache( AdaptiveCacheElement element ) { long max = Runtime.getRuntime().maxMemory(); long total = Runtime.getRuntime().totalMemory(); long free = Runtime.getRuntime().freeMemory(); float ratio = (float) (max - free) / max; float allocationRatio = (float) total / max; if ( allocationRatio < element.getRatio() ) { // allocation ratio < 1 means JVM can still increase heap size // we won't decrease caches until element ratio is less // then allocationRatio ratio = 0; } // System.out.println( "max= " + max + " total=" + total + // " ratio= " + ratio + " ... free=" + free ); if ( ratio > element.getRatio() ) { // decrease cache size // after decrease we resize again with +1000 to avoid // spam of this method Cache<?,?> cache = element.getCache(); int newCacheSize = (int) ( cache.maxSize() / decreaseRatio / (1 + (ratio - element.getRatio())) ); int minSize = element.minSize(); if ( newCacheSize < minSize ) { log.fine( "Cache[" + cache.getName() + "] cannot decrease under " + minSize + " (allocation ratio=" + allocationRatio + " threshold status=" + ratio + ")" ); cache.resize( minSize ); cache.resize( minSize + 1000 ); return; } if ( newCacheSize + 1200 > cache.size() ) { if ( cache.size() - 1200 >= minSize ) { newCacheSize = cache.size() - 1200; } else { newCacheSize = minSize; } } log.fine( "Cache[" + cache.getName() + "] decreasing from " + cache.size() + " to " + newCacheSize + " (allocation ratio=" + allocationRatio + " threshold status=" + ratio + ")" ); if ( newCacheSize <= 1000 ) { cache.clear(); } else { cache.resize( newCacheSize ); } cache.resize( newCacheSize + 1000 ); } else { // increase cache size Cache<?,?> cache = element.getCache(); if ( cache.size() / (float) cache.maxSize() < 0.9f ) { return; } int newCacheSize = (int) (cache.maxSize() * increaseRatio); log.fine( "Cache[" + cache.getName() + "] increasing from " + cache.size() + " to " + newCacheSize + " (allocation ratio=" + allocationRatio + " threshold status=" + ratio + ")" ); cache.resize( newCacheSize ); } } private static class AdaptiveCacheElement { private final Cache<?,?> cache; private final float ratio; private final int minSize; AdaptiveCacheElement( Cache<?,?> cache, float ratio, int minSize ) { this.cache = cache; this.ratio = ratio; this.minSize = minSize; } String getName() { return cache.getName(); } Cache<?,?> getCache() { return cache; } float getRatio() { return ratio; } int minSize() { return minSize; } } }