/******************************************************************************* * Copyright (c) 2012-2015 INRIA. * 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: * Damien Dosimont <damien.dosimont@imag.fr> * Youenn Corre <youenn.corret@inria.fr> ******************************************************************************/ package fr.inria.soctrace.tools.ocelotl.core.caches; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fr.inria.soctrace.lib.model.Trace; import fr.inria.soctrace.tools.ocelotl.core.constants.OcelotlConstants; import fr.inria.soctrace.tools.ocelotl.core.exceptions.OcelotlException; import fr.inria.soctrace.tools.ocelotl.core.parameters.OcelotlParameters; import fr.inria.soctrace.tools.ocelotl.core.settings.OcelotlSettings; import fr.inria.soctrace.tools.ocelotl.core.timeregion.TimeRegion; public class DichotomyCache { private static final Logger logger = LoggerFactory .getLogger(DichotomyCache.class); private OcelotlSettings settings; /** * List of the cache files in the current cache directory */ protected HashMap<CacheParameters, File> cachedDichotomy; /** * Dictionary of cache files associated to to trace */ protected HashMap<String, List<CacheParameters>> cacheIndex; /** * Path to the current cache directory */ protected String cacheDirectory = ""; /** * Maximum size of the cache in MB (-1 == no limit size) */ protected long cacheMaxSize = OcelotlConstants.MAX_CACHESIZE; /** * Size of the current cache */ protected long currentCacheSize; protected boolean validDirectory; public long getCacheMaxSize() { return cacheMaxSize; } public void setCacheMaxSize(long l) throws OcelotlException { if (l < -1) { throw new OcelotlException(OcelotlException.INVALID_MAX_CACHE_SIZE); } this.cacheMaxSize = l; settings.setCacheSize(this.cacheMaxSize); } public String getCacheDirectory() { return cacheDirectory; } /** * Perform additional checks on the given path to test its validity * * @param cacheDirectory */ public void setCacheDirectory(String cacheDirectory) { if (!this.cacheDirectory.equals(cacheDirectory)) { validDirectory = checkCacheDirectoryValidity(cacheDirectory); // Everything's OK, set the cache directory this.cacheDirectory = cacheDirectory; // Update settings settings.setCacheDirectory(this.cacheDirectory); // Search the directory for existing cache files readCachedData(); } } /** * Check that the cache directory is a valid one, i.e. does it exist and can * it be read * * @param cacheDirectory * path to the cache directory * @return true if valid, false otherwise */ public boolean checkCacheDirectoryValidity(String cacheDirectory) { // Check the existence of the cache directory File dir = new File(cacheDirectory); if (!dir.exists()) { logger.debug("[DICHOTOMY CACHE] Cache directory (" + cacheDirectory + ") does not exist and will be created now."); // Create the directory if (!dir.mkdirs()) { logger.error("[DICHOTOMY CACHE] Failed to create cache directory: " + cacheDirectory + "."); if (this.cacheDirectory.isEmpty()) { logger.error("[DICHOTOMY CACHE] The current cache directory is still: " + this.cacheDirectory); } else { validDirectory = false; logger.error("[DICHOTOMY CACHE] The cache will be turned off."); } return false; } } // Check that we have at least the reading rights if (!dir.canRead()) { logger.error("[DICHOTOMY CACHE] The application does not have the rights to read in the given directory: " + cacheDirectory + "."); if (this.cacheDirectory.isEmpty()) { validDirectory = false; logger.error("[DICHOTOMY CACHE] The cache will be turned off."); } else { logger.error("[DICHOTOMY CACHE] The current cache directory is still: " + this.cacheDirectory); } return false; } return true; } public boolean isValidDirectory() { return validDirectory; } public void setValidDirectory(boolean validDirectory) { this.validDirectory = validDirectory; } public DichotomyCache() { super(); cachedDichotomy = new HashMap<CacheParameters, File>(); cacheIndex = new HashMap<String, List<CacheParameters>>(); } /** * Set cache parameters from the configuration file * * @param settings * Configuration from file * @throws OcelotlException */ public void setSettings(OcelotlSettings settings) throws OcelotlException { this.settings = settings; setCacheDirectory(settings.getCacheDirectory()); } /** * Check parameter against the cached data parameters, and return the most * appropriate data cache * * @param parameters * parameters to be tested * @return the File of the cached data file if a correspondence was found, * null otherwise */ public File checkCache(OcelotlParameters parameters) { CacheParameters cache = null; CacheParameters cParam = new CacheParameters(parameters); String uniqueID = buildTraceUniqueID(parameters.getTrace()); // Look for the correct trace if (!cacheIndex.containsKey(uniqueID)) { logger.debug("[DICHOTOMY CACHE] No dichotomy cache was found."); return null; } for (CacheParameters op : cacheIndex.get(uniqueID)) { if (similarParameters(cParam, op)) { cache = op; break; } } if (cache == null) { logger.debug("[DICHOTOMY CACHE] No dichotomy cache was found."); return null; } else { logger.debug("[DICHOTOMY CACHE] Found a cache."); return cachedDichotomy.get(cache); } } /** * Check if two traces are similar * * @param newParam * new parameters to be tested * @param cacheParam * parameters of a cached data * @return true if parameters are similar to or compatible with the ones of * the cached data */ protected boolean similarParameters(CacheParameters newParam, CacheParameters cacheParam) { // Check for similar micro model types if (!(newParam.getMicroModelType().equals( cacheParam.getMicroModelType()) && (!newParam .getMicroModelType().equals("null")))) return false; // Check for similar data aggregation types if (!(newParam.getDataAggOperator().equals( cacheParam.getDataAggOperator()) && (!newParam .getDataAggOperator().equals("null")))) return false; // Check for similar time slices number if (newParam.getNbTimeSlice() != cacheParam.getNbTimeSlice()) return false; // Check for similar threshold values if (newParam.getTreshold() != cacheParam.getTreshold()) return false; // Check for similar normalize values if (newParam.isNormalized() != cacheParam.isNormalized()) return false; // Check that timestamps are equal if (!checkCompatibleTimeStamp(newParam, cacheParam)) return false; // Check that the event producers are the same if(!checkCompatibleEventProducers(newParam, cacheParam)) return false; // Check that the event types are the same if(!checkCompatibleEventTypes(newParam, cacheParam)) return false; return true; } /** * Test if the new explored time region is compatible with the cached * dichotomy values * * @param newParam * parameters of the new view * @param cachedParam * parameters of the cached data * @return true if they are compatible, false otherwise */ protected boolean checkCompatibleTimeStamp(CacheParameters newParam, CacheParameters cachedParam) { TimeRegion newTimeRegion = new TimeRegion(newParam.getStartTimestamp(), newParam.getEndTimestamp()); TimeRegion cacheTimeRegion = new TimeRegion( cachedParam.getStartTimestamp(), cachedParam.getEndTimestamp()); // If timestamps are equal then OK if (newTimeRegion.compareTimeRegion(cacheTimeRegion)) { return true; } return false; } /** * Check that all the current event producers are the same than in the * tested cache * * @param newParam * parameters of the new view * @param cachedParam * parameters of the cached data * @return true if they are compatible, false otherwise */ protected boolean checkCompatibleEventProducers(CacheParameters newParam, CacheParameters cachedParam) { if(newParam.isLeafAggregation() != cachedParam.isLeafAggregation()) return false; if(newParam.isLeafAggregation()) if(newParam.getMaxLeaf() != cachedParam.getMaxLeaf()) return false; if (newParam.getEventProducers().size() != cachedParam .getEventProducers().size()) return false; for (Integer anID : newParam.getEventProducers()) if (!cachedParam.getEventProducers().contains(anID)) return false; return true; } /** * Check that all the current event types are the same than in the tested * cache * * @param newParam * parameters of the new view * @param cachedParam * parameters of the cached data * @return true if they are compatible, false otherwise */ protected boolean checkCompatibleEventTypes(CacheParameters newParam, CacheParameters cachedParam) { if (newParam.getEventTypes().size() != cachedParam .getEventTypes().size()) return false; for (Integer anID : newParam.getEventTypes()) if (!cachedParam.getEventTypes().contains(anID)) return false; return true; } /** * Add a newly saved microscopic model to the list of cache file * * @param param * Ocelotl parameters from which trace characteristics are * extracted * @param aFilePath * path to the file where the data were saved */ public void saveDichotomy(OcelotlParameters oParam, String aFilePath) { // TODO check for cache size CacheParameters params = new CacheParameters(oParam); File aFile = new File(aFilePath); cachedDichotomy.put(params, aFile); String uniqueID = buildTraceUniqueID(oParam.getTrace()); // Update dictionary if (!cacheIndex.containsKey(uniqueID)) { cacheIndex.put(uniqueID, new ArrayList<CacheParameters>()); } cacheIndex.get(uniqueID).add(params); } /** * Save the cache of the current trace to the specified path * * @param oParam * current parameters * @param destPath * path to save the file */ public void saveDichotomyCacheTo(OcelotlParameters oParam, String destPath) { // Get the current cache file CacheParameters params = new CacheParameters(oParam); File source = null; // Look for the corresponding file for (CacheParameters par : cachedDichotomy.keySet()) { if (similarParameters(params, par)) { source = cachedDichotomy.get(par); } } if (source != null) { File dest = new File(destPath); try { Files.copy(source.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { logger.error("[DICHOTOMY CACHE] No corresponding cache file was found"); } } /** * Delete all the files in the cache */ public void deleteCache() { for (File aCacheFile : cachedDichotomy.values()) { if (!aCacheFile.delete()) { logger.debug("[DICHOTOMY CACHE]: Deletion of cache file " + aCacheFile + " failed."); } } cachedDichotomy.clear(); currentCacheSize = 0L; } /** * Load the existing cache files from the current cache directory */ private void readCachedData() { File workDir = new File(cacheDirectory); // Clear the current cache files cachedDichotomy.clear(); if (workDir.exists()) { Iterator<File> anIT = FileUtils.iterateFiles(workDir, null, true); while (anIT.hasNext()) { File traceCache = anIT.next(); if (!traceCache.getName().endsWith( OcelotlConstants.DichotomyCacheSuffix)) continue; // Try parsing the file and get the cache parameters CacheParameters param = parseTraceCache(traceCache); // If parsing was successful if (param.getTraceID() != -1) { // Register the cache file cachedDichotomy.put(param, traceCache); logger.debug("[DICHOTOMY CACHE] Found " + param.getTraceName() + " in " + traceCache.toString() + ", " + param.getMicroModelType() + ", " + param.getDataAggOperator() + ", " + param.getStartTimestamp() + ", " + param.getEndTimestamp()); } } computeCacheSize(); } else { System.err.println("[DICHOTOMY CACHE] The provided cache directory (" + cacheDirectory + ")does not exist"); } } /** * Build the cache index * * @param traces * List of all the traces in database */ public void buildDictionary(List<Trace> traces) { cacheIndex = new HashMap<String, List<CacheParameters>>(); for (CacheParameters aCache : cachedDichotomy.keySet()) { // Check if the corresponding trace still exists for (Trace aTrace : traces) { if (aCache.getTraceID() == aTrace.getId()) { String uniqueID = buildTraceUniqueID(aTrace); if (!cacheIndex.containsKey(uniqueID)) { cacheIndex .put(uniqueID, new ArrayList<CacheParameters>()); } cacheIndex.get(uniqueID).add(aCache); } } } removeDeletedTraces(traces); } /** * Check that every cache file have a corresponding trace in the database, * and if not then delete the cache file * * @param traces * list of the traces in the database */ public void removeDeletedTraces(List<Trace> traces) { List<CacheParameters> deletedCache = new ArrayList<CacheParameters>(); for (CacheParameters aCache : cachedDichotomy.keySet()) { boolean deleted = true; // Check if the corresponding trace still exists for (Trace aTrace : traces) { if (aCache.getTraceID() == aTrace.getId()) { deleted = false; break; } } // If not delete the cache file if (deleted) { logger.debug("[DICHOTOMY CACHE]: The trace " + aCache.getTraceName() + " (ID = " + aCache.getTraceID() + ") is no longer in the database: the corresponding cache file will be deleted."); if (!cachedDichotomy.get(aCache).delete()) { logger.debug("[DICHOTOMY CACHE]: Deletion of cache file " + cachedDichotomy.get(aCache).getName() + " failed."); } deletedCache.add(aCache); } } // Remove the deleted cache for (CacheParameters aCache : deletedCache) { cachedDichotomy.remove(aCache); } // Recompute the current cache size computeCacheSize(); } /** * Try parsing the parameters from the given file * * @param aCachefile * file containing cached data * @return a CacheParameters fully instantiated if successful or with init * values otherwise */ private CacheParameters parseTraceCache(File aCachefile) { CacheParameters params = new CacheParameters(); if (aCachefile.canRead() && aCachefile.isFile()) { BufferedReader bufFileReader; try { bufFileReader = new BufferedReader(new FileReader(aCachefile)); String line; // Get header line = bufFileReader.readLine(); if (line != null) { String[] header = line.split(OcelotlConstants.CSVDelimiter); if (header.length != OcelotlConstants.DICHOTOMYCACHE_HEADER_NORMAL_SIZE) { bufFileReader.close(); return params; } // Check that the file is a trace file // magic // number ?? // Name params.setTraceName(header[0]); // Database unique ID params.setTraceID(Integer.parseInt(header[1])); // Time Aggregation Operator params.setMicroModelType(header[2]); // Space Aggregation Operator params.setDataAggOperator(header[3]); // Start timestamp params.setStartTimestamp(Long.parseLong(header[4])); // End timestamp params.setEndTimestamp(Long.parseLong(header[5])); // Number of time Slices params.setNbTimeSlice(Integer.parseInt(header[6])); // Threshold params.setTreshold(Double.parseDouble(header[7])); // Normalized params.setNormalized(Boolean.parseBoolean(header[8])); // hasLeaveAggregated params.setLeafAggregation(Boolean.parseBoolean(header[9])); } // Parse event prod line = bufFileReader.readLine(); if (line != null && !line.isEmpty()) { String[] eventProd = line .split(OcelotlConstants.CSVDelimiter); for (int i = 0; i < eventProd.length; i++) params.getEventProducers().add( Integer.parseInt(eventProd[i])); } // Parse event type line = bufFileReader.readLine(); if (line != null && !line.isEmpty()) { String[] eventType = line .split(OcelotlConstants.CSVDelimiter); for (int i = 0; i < eventType.length; i++) params.getEventTypes().add( Integer.parseInt(eventType[i])); } bufFileReader.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (NumberFormatException e) { logger.error("Could not parse the current cache file: Invalid cache version?"); return params; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return params; } /** * Load a trace from a cache file * * @param cacheFilePath */ public int loadDichotomyCache(String cacheFilePath, OcelotlParameters oParam) throws OcelotlException { File cacheFile = new File(cacheFilePath); CacheParameters params = parseTraceCache(cacheFile); // Invalid data file if (params.getTraceID() == -1) { throw new OcelotlException(OcelotlException.INVALID_CACHEFILE); } else { oParam.setTimeSlicesNumber(params.getNbTimeSlice()); TimeRegion timeRegion = new TimeRegion(params.getStartTimestamp(), params.getEndTimestamp()); oParam.setTimeRegion(timeRegion); oParam.setThreshold(params.getTreshold()); if (!params.getMicroModelType().equals("null")) { oParam.setMicroModelType(params.getMicroModelType()); } if (!params.getDataAggOperator().equals("null")) { oParam.setDataAggOperator(params.getDataAggOperator()); } if (!params.getVisuAggOperator().equals("null")) { oParam.setVisuOperator(params.getVisuAggOperator()); } } return params.getTraceID(); } /** * Check that the new file fits in the cache size limit */ public boolean checkCacheSize(long newFileSize) { if (cacheMaxSize > -1) { if (newFileSize > cacheMaxSize) { return false; } /*while (currentCacheSize + newFileSize > cacheMaxSize && !cachedDichotomy.isEmpty()) { //removeCacheFile(); computeCacheSize(); }*/ } return true; } /** * Compute the current size of the cache in bytes */ public void computeCacheSize() { currentCacheSize = 0; for (File aCacheFile : cachedDichotomy.values()) { currentCacheSize = currentCacheSize + aCacheFile.length(); } logger.debug("[DICHOTOMY CACHE] Size of the current cache is: " + currentCacheSize + " bytes (" + currentCacheSize / 1000000 + " MB)."); } /** * Remove a cache file. The used policy is to suppress the file which has * the oldest accessed time */ public void removeCacheFile() { // Init with current time FileTime oldestDate = FileTime.from(System.currentTimeMillis(), null); CacheParameters oldestParam = null; for (CacheParameters aCacheParam : cachedDichotomy.keySet()) { try { // Get the last access to the file Path path = cachedDichotomy.get(aCacheParam).toPath(); BasicFileAttributes attrs; attrs = Files.readAttributes(path, BasicFileAttributes.class); FileTime currentTime = attrs.lastAccessTime(); // If the access is older than the current oldest if (currentTime.compareTo(oldestDate) < 0) { oldestDate = currentTime; oldestParam = aCacheParam; } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // Delete oldest accessed cache if (!cachedDichotomy.get(oldestParam).delete()) { logger.debug("[DICHOTOMY CACHE]: Deletion of cache file " + cachedDichotomy.get(oldestParam).getName() + " failed."); } cachedDichotomy.remove(oldestParam); } /** * Construct an ID composed of the name of the trace (alias) and the id of * the trace in database * * @param aTrace * @return * the unique ID */ String buildTraceUniqueID(Trace aTrace) { return aTrace.getDbName() + "_" + aTrace.getId(); } }