blob: c88cbf3fb785d36194a93d3626df85ed27de0dc7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 University of Illinois at Urbana-Champaign and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* UIUC - Initial API and implementation
*******************************************************************************/
package org.eclipse.rephraserengine.core.vpg.db.caching;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.NoSuchElementException;
import org.eclipse.core.runtime.Assert;
import org.eclipse.rephraserengine.core.util.Pair;
import org.eclipse.rephraserengine.core.vpg.IVPGNode;
import org.eclipse.rephraserengine.core.vpg.NodeRef;
import org.eclipse.rephraserengine.core.vpg.VPGDB;
import org.eclipse.rephraserengine.core.vpg.VPGDependency;
import org.eclipse.rephraserengine.core.vpg.VPGEdge;
import org.eclipse.rephraserengine.core.vpg.db.cdt.CDTDB;
/**
* Base class for a typical Virtual Program Graph database the caches the result of another
* (on-disk) database (usually a {@link CDTDB}).
*
* @author Jeff Overbey
*
* @param <A> AST type
* @param <T> token type
* @param <R> {@link IVPGNode}/{@link NodeRef} type
*
* @since 1.0
*/
public class CachingDB<A, T, R extends IVPGNode<T>>
extends VPGDB<A, T, R>
{
private final class CacheKey
{
public final R tokenRef;
public final int id;
public CacheKey(R tokenRef, int edgeType)
{
this.tokenRef = tokenRef;
this.id = edgeType;
}
@SuppressWarnings("unchecked")
@Override public boolean equals(Object o)
{
try
{
if (o == null) return false;
CacheKey other = (CacheKey)o;
return this.tokenRef.equals(other.tokenRef)
&& this.id == other.id;
}
catch (ClassCastException e)
{
return false;
}
}
@Override public int hashCode()
{
return tokenRef.hashCode() * 17 + id;
}
}
public VPGDB<A, T, R> db;
private int maxEdgeCacheEntries;
private int maxAnnotationCacheEntries;
private HashMap<CacheKey, Iterable<? extends VPGEdge<A, T, R>>> incomingEdgeCache;
private HashMap<CacheKey, Iterable<? extends VPGEdge<A, T, R>>> outgoingEdgeCache;
private HashMap<CacheKey, Serializable> annotationCache;
private long edgeHits = 0, edgeMisses = 0, totalEdgeListBuildTime = 0;
private long annotationHits = 0, annotationMisses = 0, totalDeserializationTime = 0;
public CachingDB(VPGDB<A, T, R> diskDatabase)
{
this(diskDatabase, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
public CachingDB(VPGDB<A, T, R> diskDatabase, int maxEdgeCacheEntries, int maxAnnotationCacheEntries)
{
super(diskDatabase);
Assert.isNotNull(diskDatabase);
Assert.isTrue(diskDatabase != this);
Assert.isTrue(maxEdgeCacheEntries > 0, "maxEdgeCacheEntries must be a positive integer"); //$NON-NLS-1$
Assert.isTrue(maxAnnotationCacheEntries > 0, "maxAnnotationCacheEntries must be a positive integer"); //$NON-NLS-1$
this.db = diskDatabase;
this.maxEdgeCacheEntries = maxEdgeCacheEntries;
this.maxAnnotationCacheEntries = maxAnnotationCacheEntries;
this.incomingEdgeCache = new HashMap<CacheKey, Iterable<? extends VPGEdge<A, T, R>>>();
this.outgoingEdgeCache = new HashMap<CacheKey, Iterable<? extends VPGEdge<A, T, R>>>();
this.annotationCache = new HashMap<CacheKey, Serializable>();
}
////////////////////////////////////////////////////////////////////////////
// VPG DATABASE METHODS
////////////////////////////////////////////////////////////////////////////
@Override public void flush()
{
db.flush();
}
@Override public void close()
{
db.close();
}
private void clearCache()
{
incomingEdgeCache.clear();
outgoingEdgeCache.clear();
annotationCache.clear();
}
@Override public void clearDatabase()
{
clearCache();
db.clearDatabase();
}
// HYPOTHETICAL UPDATING ///////////////////////////////////////////////////
@Override public void enterHypotheticalMode() throws IOException
{
clearCache();
db.enterHypotheticalMode();
}
@Override public void leaveHypotheticalMode() throws IOException
{
clearCache();
db.leaveHypotheticalMode();
}
@Override public boolean isInHypotheticalMode()
{
return db.isInHypotheticalMode();
}
// FILES ///////////////////////////////////////////////////////////////////
@Override public void updateModificationStamp(String filename)
{
db.updateModificationStamp(filename);
}
@Override public boolean isOutOfDate(String filename)
{
return db.isOutOfDate(filename);
}
@Override public void deleteAllEntriesFor(String filename)
{
clearCache();
db.deleteAllEntriesFor(filename);
}
@Override public void deleteAllEdgesAndAnnotationsFor(String filename)
{
clearCache();
db.deleteAllEdgesAndAnnotationsFor(filename);
}
@Override public void deleteAllIncomingDependenciesFor(String filename)
{
db.deleteAllIncomingDependenciesFor(filename);
}
@Override public void deleteAllOutgoingDependenciesFor(String filename)
{
db.deleteAllOutgoingDependenciesFor(filename);
}
@Override public Iterable<String> listAllFilenames()
{
return db.listAllFilenames();
}
@Override public Iterable<String> listAllFilenamesWithDependents()
{
return db.listAllFilenamesWithDependents();
}
@Override public Iterable<String> listAllDependentFilenames()
{
return db.listAllDependentFilenames();
}
// DEPENDENCIES ////////////////////////////////////////////////////////////
@Override public void ensure(VPGDependency<A, T, R> dependency)
{
db.ensure(dependency);
}
@Override public void delete(VPGDependency<A, T, R> dependency)
{
db.delete(dependency);
}
@Override public Iterable<String> getOutgoingDependenciesFrom(String filename)
{
return db.getOutgoingDependenciesFrom(filename);
}
@Override public Iterable<String> getIncomingDependenciesTo(String filename)
{
return db.getIncomingDependenciesTo(filename);
}
// EDGES ///////////////////////////////////////////////////////////////////
@Override public void ensure(VPGEdge<A, T, R> edge)
{
removeFromCache(edge);
db.ensure(edge);
}
@Override public void delete(VPGEdge<A, T, R> edge)
{
removeFromCache(edge);
db.delete(edge);
}
private void removeFromCache(VPGEdge<A, T, R> edge)
{
incomingEdgeCache.remove(new CacheKey(edge.getSink(), edge.getType()));
outgoingEdgeCache.remove(new CacheKey(edge.getSource(), edge.getType()));
}
@Override public Iterable<? extends VPGEdge<A, T, R>> getAllEdgesFor(String filename)
{
return db.getAllEdgesFor(filename);
}
@Override public Iterable<? extends VPGEdge<A, T, R>> getOutgoingEdgesFrom(R tokenRef, int edgeType)
{
CacheKey key = new CacheKey(tokenRef, edgeType);
if (outgoingEdgeCache.containsKey(key))
{
edgeHits++;
//System.out.println("Edge cache hit");
return outgoingEdgeCache.get(key);
}
else
{
edgeMisses++;
//System.out.println("Edge cache miss");
return buildEdgeCache(outgoingEdgeCache, key, db.getOutgoingEdgesFrom(tokenRef, edgeType));
}
}
@Override public Iterable<? extends VPGEdge<A, T, R>> getIncomingEdgesTo(R tokenRef, int edgeType)
{
CacheKey key = new CacheKey(tokenRef, edgeType);
if (incomingEdgeCache.containsKey(key))
{
edgeHits++;
//System.out.println("Edge cache hit");
return incomingEdgeCache.get(key);
}
else
{
edgeMisses++;
//System.out.println("Edge cache miss");
return buildEdgeCache(incomingEdgeCache, key, db.getIncomingEdgesTo(tokenRef, edgeType));
}
}
private Iterable<? extends VPGEdge<A, T, R>> buildEdgeCache(
HashMap<CacheKey, Iterable<? extends VPGEdge<A, T, R>>> cache,
CacheKey key,
Iterable<? extends VPGEdge<A, T, R>> iterable)
{
try
{
if (cache.size() > maxEdgeCacheEntries)
{
// Iterator<CacheKey> it = cache.keySet().iterator();
// if (it.next() != null)
// it.remove();
try
{
cache.remove(cache.keySet().iterator().next());
}
catch (NoSuchElementException e)
{
// This should not happen since cache.size() > 1,
// but it's been reported (S Kalogerakos on Photran
// mailing list), so we should handle it
cache.clear();
}
}
if (maxEdgeCacheEntries > 0)
{
long start = System.currentTimeMillis();
ArrayList<VPGEdge<A, T, R>> list = new ArrayList<VPGEdge<A, T, R>>();
for (VPGEdge<A, T, R> edge : iterable)
list.add(edge);
long buildTime = System.currentTimeMillis() - start;
totalEdgeListBuildTime += buildTime;
//System.out.println("Edge list build time: " + buildTime + " ms");
cache.put(key, list);
return list;
}
else
{
return iterable;
}
}
catch (OutOfMemoryError e)
{
maxEdgeCacheEntries = cache.size()-1;
return iterable;
}
}
// ANNOTATIONS /////////////////////////////////////////////////////////////
@Override public void setAnnotation(R token, int annotationID, Serializable annotation)
{
removeFromCache(token, annotationID);
db.setAnnotation(token, annotationID, annotation);
}
@Override public void deleteAnnotation(R token, int annotationID)
{
removeFromCache(token, annotationID);
db.deleteAnnotation(token, annotationID);
}
private void removeFromCache(R token, int annotationID)
{
annotationCache.remove(new CacheKey(token, annotationID));
}
@Override public Serializable getAnnotation(R tokenRef, int annotationID)
{
CacheKey key = new CacheKey(tokenRef, annotationID);
if (annotationCache.containsKey(key))
{
annotationHits++;
//System.out.println("Annotation cache hit");
return annotationCache.get(key);
}
else
{
annotationMisses++;
//System.out.println("Annotation cache miss");
if (annotationCache.size() > maxAnnotationCacheEntries)
{
// Iterator<CacheKey> it = annotationCache.keySet().iterator();
// if (it.next() != null)
// it.remove();
annotationCache.remove(annotationCache.keySet().iterator().next());
}
long start = System.currentTimeMillis();
Serializable ann = db.getAnnotation(tokenRef, annotationID);
long deserTime = System.currentTimeMillis() - start;
totalDeserializationTime += deserTime;
//System.out.println("Annotation deserialization time: " + deserTime + " ms");
annotationCache.put(key, ann);
return ann;
}
}
@Override public Iterable<Pair<R, Integer>> getAllAnnotationsFor(String filename)
{
return db.getAllAnnotationsFor(filename);
}
// UTILITY METHODS /////////////////////////////////////////////////////////
@Override public void printOn(PrintStream out)
{
printStatisticsOn(out);
out.println();
db.printOn(out);
}
@Override public void printStatisticsOn(PrintStream out)
{
out.println("Database Cache Statistics:"); //$NON-NLS-1$
long edgeTotal = edgeHits + edgeMisses;
float edgeHitRatio = edgeTotal == 0 ? 0 : ((float)edgeHits) / edgeTotal * 100;
out.println(" Edge Cache Hit Ratio: " + edgeHits + "/" + edgeTotal + " (" + (long)Math.round(edgeHitRatio) + "%)"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
long annotationTotal = annotationHits + annotationMisses;
float annotationHitRatio = annotationTotal == 0 ? 0 : ((float)annotationHits) / annotationTotal * 100;
out.println(" Annotation Cache Hit Ratio: " + annotationHits + "/" + annotationTotal + " (" + (long)Math.round(annotationHitRatio) + "%)"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
if (edgeMisses > 0)
out.println(" Average edge list build time: " + (totalEdgeListBuildTime/edgeMisses) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$
if (annotationMisses > 0)
out.println(" Average annotation deserialization time: " + (totalDeserializationTime/annotationMisses) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$
// out.println();
// out.println("Cache Sizes:");
// out.println(" Incoming Edge Cache: " + incomingEdgeCache.size() + " entries (max " + maxEdgeCacheEntries + ")");
// out.println(" Incoming Edge Cache: " + outgoingEdgeCache.size() + " entries (max " + maxEdgeCacheEntries + ")");
// out.println(" Annotation Cache: " + annotationCache.size() + " entries (max " + maxAnnotationCacheEntries + ")");
db.printStatisticsOn(out);
}
@Override public void resetStatistics()
{
edgeHits = edgeMisses = annotationHits = annotationMisses = totalEdgeListBuildTime = totalDeserializationTime = 0;
db.resetStatistics();
}
}