tfcache

added caching storage for savedtimepertimeincache computing.

9/10/2020 8:31:40 PM

Details

diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Arguments.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Arguments.java
new file mode 100644
index 0000000..8d7d6bd
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Arguments.java
@@ -0,0 +1,24 @@
+package br.ufrgs.inf.prosoft.tfcache.configuration;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class Arguments {
+
+    public static Map<String, String> parse(String[] args) {
+        Map<String, String> arguments = Stream.of(args).map(arg -> {
+            arg = arg.replaceFirst("--", "");
+            int indexOf = arg.indexOf("=");
+            if (indexOf == -1) {
+                return new String[]{arg, ""};
+            }
+            return new String[]{arg.substring(0, indexOf), arg.substring(indexOf + 1)};
+        }).collect(Collectors.toMap(array -> {
+            return array[0];
+        }, array -> {
+            return array[1];
+        }));
+        return arguments;
+    }
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Configuration.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Configuration.java
index a0f1548..0c689ed 100644
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Configuration.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/configuration/Configuration.java
@@ -1,16 +1,16 @@
 package br.ufrgs.inf.prosoft.tfcache.configuration;
 
+import com.google.gson.JsonObject;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-/**
- *
- * @author root
- */
 public class Configuration {
 
     private static final Logger LOGGER = Logger.getLogger(Configuration.class.getName());
 
+    private static String input = null;
+    private static String store = null;
     private static String level = "method";
     private static String output = null;
     private static String changeability = "allow";
@@ -18,13 +18,60 @@ public class Configuration {
     private static String kernel = "exhaustive";
     private static boolean verbose = false;
 
+    public static void process(String[] args) {
+        process(Arguments.parse(args));
+    }
+
+    public static void process(Map<String, String> arguments) {
+        setInput(arguments.get("trace"));
+        setStore(arguments.get("store"));
+        setLevel(arguments.get("level"));
+        setOutput(arguments.get("output"));
+        if (getLevel().equals("input") && getOutput() == null) {
+            LOGGER.severe("Output is required for input-level recommendations");
+            System.exit(1);
+        }
+        setChangeability(arguments.get("changeability"));
+        setStaleness(arguments.get("staleness"));
+        setKernel(arguments.get("kernel"));
+        setVerbose(arguments.get("verbose"));
+    }
+
+    public static void setInput(String input) {
+        if (input == null || input.isBlank()) {
+            LOGGER.severe("<TracePath> is required");
+            System.exit(1);
+        }
+        Configuration.input = input;
+    }
+
+    public static String getInput() {
+        return input;
+    }
+
+    public static void setStore(String store) {
+        if (store == null) {
+            LOGGER.info("store disabled");
+            return;
+        }
+        if (store.isBlank()) {
+            LOGGER.severe("storePath cannot be empty");
+            System.exit(1);
+        }
+        Configuration.store = store;
+    }
+
+    public static String getStore() {
+        return store;
+    }
+
     public static void setLevel(String level) {
         if (level == null) {
             LOGGER.log(Level.INFO, "Using default level: {0}", Configuration.level);
             return;
         }
         if (!level.equals("method") && !level.equals("input")) {
-            LOGGER.log(Level.SEVERE, "Unrecognised option for level");
+            LOGGER.severe("Unrecognised option for level");
             System.exit(1);
         }
         Configuration.level = level;
@@ -51,7 +98,7 @@ public class Configuration {
             return;
         }
         if (!changeability.equals("allow") && !changeability.equals("deny")) {
-            LOGGER.log(Level.SEVERE, "Unrecognised option for changeability");
+            LOGGER.severe("Unrecognised option for changeability");
             System.exit(1);
         }
         Configuration.changeability = changeability;
@@ -67,7 +114,7 @@ public class Configuration {
             return;
         }
         if (!staleness.equals("ignore") && !staleness.equals("shrink")) {
-            LOGGER.log(Level.SEVERE, "Unrecognised option for staleness");
+            LOGGER.severe("Unrecognised option for staleness");
             System.exit(1);
         }
         Configuration.staleness = staleness;
@@ -83,7 +130,7 @@ public class Configuration {
             return;
         }
         if (!kernel.equals("exhaustive") && !kernel.equals("optimised") && !kernel.equals("test")) {
-            LOGGER.log(Level.SEVERE, "Unrecognised option for kernel");
+            LOGGER.severe("Unrecognised option for kernel");
             System.exit(1);
         }
         Configuration.kernel = kernel;
@@ -98,7 +145,7 @@ public class Configuration {
             return;
         }
         if (!verbose.equals("true") && !verbose.equals("false")) {
-            LOGGER.log(Level.SEVERE, "Unrecognised option for verbose");
+            LOGGER.severe("Unrecognised option for verbose");
             System.exit(1);
         }
         Configuration.verbose = verbose.equals("true");
@@ -108,4 +155,18 @@ public class Configuration {
         return verbose;
     }
 
+    public static String getUUID() {
+        return "level:" + Configuration.getLevel()
+                + ",staleness:" + Configuration.getStaleness()
+                + ",kernel:" + Configuration.getKernel();
+    }
+
+    public static JsonObject toJSONObject() {
+        JsonObject configuration = new JsonObject();
+        configuration.addProperty("level", Configuration.getLevel());
+        configuration.addProperty("staleness", Configuration.getStaleness());
+        configuration.addProperty("kernel", Configuration.getKernel());
+        return configuration;
+    }
+
 }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java
index b4e039c..60c0f04 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java
@@ -10,11 +10,8 @@ import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
 import br.ufrgs.inf.prosoft.tfcache.metadata.Method;
 import br.ufrgs.inf.prosoft.trace.Trace;
 import java.util.List;
-import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  *
@@ -28,45 +25,19 @@ public class Main {
         System.setProperty("java.util.logging.SimpleFormatter.format", "[%1$tF %1$tT+%1$tL] [%4$-7s] [TFCache] %5$s %n");
 
         if (args.length < 1) {
-            System.err.println("--trace=<TracePath> [--level=<method|input>] [--output=<outputPath>] [--changeability=<allow|deny>] [--staleness=<ignore|shrink>] [--kernel=<exhaustive|optimised>|test] [--verbose=<false|true>]");
+            System.err.println("--trace=<TracePath> [--store=<storePath>] [--level=<method|input>] [--output=<outputPath>] [--changeability=<allow|deny>] [--staleness=<ignore|shrink>] [--kernel=<exhaustive|optimised>|test] [--verbose=<false|true>]");
             System.exit(1);
         }
 
-        Map<String, String> arguments = Stream.of(args).map(arg -> {
-            arg = arg.replaceFirst("--", "");
-            int indexOf = arg.indexOf("=");
-            if (indexOf == -1) {
-                return new String[]{arg, ""};
-            }
-            return new String[]{arg.substring(0, indexOf), arg.substring(indexOf + 1)};
-        }).collect(Collectors.toMap(array -> {
-            return array[0];
-        }, array -> {
-            return array[1];
-        }));
-
-        String tracePath = arguments.get("trace");
-        if (tracePath == null || tracePath.isBlank()) {
-            System.err.println("<TracePath> is required");
-            System.exit(1);
-        }
-
-        Configuration.setLevel(arguments.get("level"));
-        Configuration.setOutput(arguments.get("output"));
-        if (Configuration.getLevel().equals("input") && Configuration.getOutput() == null) {
-            LOGGER.log(Level.SEVERE, "Output is required for input-level recommendations");
-            System.exit(1);
-        }
-        Configuration.setChangeability(arguments.get("changeability"));
-        Configuration.setStaleness(arguments.get("staleness"));
-        Configuration.setKernel(arguments.get("kernel"));
-        Configuration.setVerbose(arguments.get("verbose"));
+        Configuration.process(args);
 
         LOGGER.log(Level.INFO, "Reading traces");
-        List<Trace> traces = TraceReader.parseFile(tracePath);
+        List<Trace> traces = TraceReader.parseFile(Configuration.getInput());
         LOGGER.log(Level.INFO, "Grouping {0} traces by methods", traces.size());
         List<Method> methods = TraceReader.groupByMethods(traces);
+        StorageManager.load();
         TFCache tfCache = new TFCache(methods);
         tfCache.recommend();
+        StorageManager.update();
     }
 }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Method.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Method.java
index 49384f3..91bcc9f 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Method.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Method.java
@@ -7,6 +7,7 @@ package br.ufrgs.inf.prosoft.tfcache.metadata;
 
 import br.ufrgs.inf.prosoft.tfcache.Metrics;
 import br.ufrgs.inf.prosoft.tfcache.Simulator;
+import br.ufrgs.inf.prosoft.tfcache.StorageManager;
 import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
 import java.math.BigDecimal;
 import java.util.ArrayList;
@@ -53,22 +54,15 @@ public class Method {
     }
 
     public Metrics getBestMetrics() {
-        if (this.bestMetrics == null) {
-            throw new RuntimeException("hitsPerTimeInCache must be calculated");
-        }
         return this.bestMetrics;
     }
 
-    public Long getTtl() {
-        return getBestMetrics().getTtl();
-    }
-
-    public Long getSavedTime() {
-        return getBestMetrics().getSavedTime();
-    }
-
-    public BigDecimal getSavedTimePerTimeInCache() {
-        return getBestMetrics().getSavedTimePerTimeInCache();
+    public BigDecimal getEstimatedSavedTimePerTimeInCache() {
+        if (getBestMetrics() != null) {
+            return getBestMetrics().getSavedTimePerTimeInCache();
+        }
+        GroupOfOccurrences max = groupsOfOccurrences().max((group1, group2) -> group1.getSavedTimePerTimeInCache().compareTo(group2.getSavedTimePerTimeInCache())).get();
+        return max.getSavedTimePerTimeInCache();
     }
 
     public Stream<Occurrence> occurrences() {
@@ -185,7 +179,7 @@ public class Method {
 
     public void recommendTTL() {
         if (this.bestMetrics != null) {
-            LOGGER.log(Level.WARNING, "HitsPerTimeInCache already calculated");
+            LOGGER.log(Level.WARNING, "SavedTimePerTimeInCache already calculated");
         }
         if (this.occurrences == null) {
             throw new RuntimeException("groupByInputs called. Cannot proceed");
@@ -207,19 +201,15 @@ public class Method {
             System.out.println("=== " + getName() + " ===");
         }
         groupsOfOccurrences().parallel().forEach(GroupOfOccurrences::calculateHitsPerTimeInCache);
-        if (this.bestMetrics == null) {
-            long savedTime = groupsOfOccurrences().map(GroupOfOccurrences::getSavedTime).reduce(Long::sum).get();
-            long hits = groupsOfOccurrences().map(GroupOfOccurrences::getHits).reduce(Long::sum).get();
-            long stales = groupsOfOccurrences().map(GroupOfOccurrences::getStales).reduce(Long::sum).get();
-            GroupOfOccurrences max = groupsOfOccurrences().max((group1, group2) -> Long.compare(group1.getSavedTime(), group2.getSavedTime())).get();
-            long ttl = max.getTtl();
-            BigDecimal hitsPerTimeInCache = max.getSavedTimePerTimeInCache();
-            this.bestMetrics = new Metrics(ttl, hits, stales, savedTime, hitsPerTimeInCache);
+        String uuid = Configuration.getUUID().replace("level:input", "level:method");
+        Metrics metrics = StorageManager.get(uuid, this.occurrences);
+        if (this.bestMetrics == null && metrics != null) {
+            this.bestMetrics = metrics;
         }
     }
 
     public void removeNotRecommededInputs() {
-        if (this.groupsOfOccurrences == null || this.bestMetrics == null) {
+        if (this.groupsOfOccurrences == null) {
             throw new RuntimeException("Recommendations not called");
         }
         int initialCountofOccurrences = this.groupsOfOccurrences.size();
@@ -234,7 +224,7 @@ public class Method {
         if (this.groupsOfOccurrences == null) {
             throw new RuntimeException("groupsOfOccurrences is null");
         }
-        this.groupsOfOccurrences.sort((group1, group2) -> Long.compare(group2.getSavedTime(), group1.getSavedTime()));
+        this.groupsOfOccurrences.sort((group1, group2) -> group2.getSavedTimePerTimeInCache().compareTo(group1.getSavedTimePerTimeInCache()));
     }
 
     @Override
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
index 4f34d9f..4439e6a 100644
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
@@ -22,6 +22,7 @@ public class Metrics implements Comparable<Metrics> {
 
     private long ttl;
     private long hits;
+    private long timeInCache;
     private long stales;
     private long savedTime;
     private BigDecimal savedTimePerTimeInCache;
@@ -30,12 +31,19 @@ public class Metrics implements Comparable<Metrics> {
         this.savedTimePerTimeInCache = new BigDecimal(BigInteger.ZERO);
     }
 
-    public Metrics(long ttl, long hits, long stales, long savedTime, BigDecimal hitsPerTimeInCache) {
+    public Metrics(long ttl, long hits, long timeInCache, long stales, long savedTime) {
+        if (ttl < 0 || hits < 0 || timeInCache < 0 || stales < 0 || savedTime < 0) {
+            throw new RuntimeException("Metrics cannot be under zero");
+        }
+        if (timeInCache == 0) {
+            throw new RuntimeException("timeInCache should not be zero");
+        }
         this.ttl = ttl;
         this.hits = hits;
+        this.timeInCache = timeInCache;
         this.stales = stales;
         this.savedTime = savedTime;
-        this.savedTimePerTimeInCache = hitsPerTimeInCache;
+        this.savedTimePerTimeInCache = new BigDecimal(this.savedTime).divide(new BigDecimal(this.timeInCache), MathContext.DECIMAL128);
     }
 
     public long getTtl() {
@@ -46,6 +54,10 @@ public class Metrics implements Comparable<Metrics> {
         return this.hits;
     }
 
+    public long getTimeInCache() {
+        return timeInCache;
+    }
+
     public long getStales() {
         return this.stales;
     }
@@ -58,6 +70,10 @@ public class Metrics implements Comparable<Metrics> {
         return this.savedTimePerTimeInCache;
     }
 
+    public synchronized void keepBestMetrics(Metrics metrics) {
+        keepBestMetrics(metrics.ttl, metrics.hits, metrics.timeInCache, metrics.stales, metrics.savedTime);
+    }
+
     public synchronized void keepBestMetrics(long ttl, long hits, long timeInCache, long stales, long savedTime) {
         if (ttl < 0 || hits < 0 || timeInCache < 0 || stales < 0 || savedTime < 0) {
             throw new RuntimeException("Metrics cannot be under zero. TTL: " + ttl
@@ -73,9 +89,10 @@ public class Metrics implements Comparable<Metrics> {
             throw new RuntimeException("timeInCache should not be zero");
         }
         BigDecimal savedTimePerTimeInCache = new BigDecimal(savedTime).divide(new BigDecimal(timeInCache), MathContext.DECIMAL128);
-        if (this.ttl == 0 || compareTo(ttl, hits, stales, savedTime, savedTimePerTimeInCache) == -1) {
+        if (this.ttl == 0 || compareTo(ttl, hits, timeInCache, stales, savedTime, savedTimePerTimeInCache) == -1) {
             this.ttl = ttl;
             this.hits = hits;
+            this.timeInCache = timeInCache;
             this.stales = stales;
             this.savedTime = savedTime;
             this.savedTimePerTimeInCache = savedTimePerTimeInCache;
@@ -101,14 +118,14 @@ public class Metrics implements Comparable<Metrics> {
 
     @Override
     public int compareTo(Metrics other) {
-        return compareTo(other.ttl, other.hits, other.stales, other.savedTime, other.savedTimePerTimeInCache);
+        return compareTo(other.ttl, other.hits, other.timeInCache, other.stales, other.savedTime, other.savedTimePerTimeInCache);
     }
 
-    private int compareTo(long ttl, long hits, long stales, long savedTime, BigDecimal savedTimePerTimeInCache) {
-        if (this.savedTimePerTimeInCache.compareTo(savedTimePerTimeInCache) == 1) {
+    private int compareTo(long ttl, long hits, long timeInCache, long stales, long savedTime, BigDecimal savedTimePerTimeInCache) {
+        if (getSavedTimePerTimeInCache().compareTo(savedTimePerTimeInCache) == 1) {
             return 1;
         }
-        if (this.savedTimePerTimeInCache.compareTo(savedTimePerTimeInCache) == -1) {
+        if (getSavedTimePerTimeInCache().compareTo(savedTimePerTimeInCache) == -1) {
             return -1;
         }
         if (this.stales < stales) {
@@ -135,6 +152,12 @@ public class Metrics implements Comparable<Metrics> {
         if (this.ttl > ttl) {
             return -1;
         }
+        if (this.timeInCache < timeInCache) {
+            return 1;
+        }
+        if (this.timeInCache > timeInCache) {
+            return -1;
+        }
         return 0;
     }
 
@@ -156,10 +179,11 @@ public class Metrics implements Comparable<Metrics> {
     @Override
     public String toString() {
         return "TTL: " + this.ttl
-                + " STpTiC: " + this.savedTimePerTimeInCache
+                + " STpTiC: " + getSavedTimePerTimeInCache()
                 + " Stales: " + this.stales
                 + " Hits: " + this.hits
-                + " SavedTime: " + this.savedTime;
+                + " SavedTime: " + this.savedTime
+                + " TimeInCache: " + this.timeInCache;
     }
 
 }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
index ed926f7..814d241 100644
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
@@ -46,17 +46,21 @@ public class Simulator {
     }
 
     public static void simulate(List<Occurrence> occurrences, Metrics metrics) {
-        switch (Configuration.getKernel()) {
-            case "exhaustive":
-                simulate(occurrences, generateAllTTLs(occurrences), metrics);
-                break;
-            case "optimised":
-                simulate(occurrences, generateTTLsOfInterest(occurrences), metrics);
-                break;
-            default:
-                testKernels(occurrences);
-                break;
-        }
+        Metrics computeIfAbsent = StorageManager.computeIfAbsent(occurrences, () -> {
+            switch (Configuration.getKernel()) {
+                case "exhaustive":
+                    simulate(occurrences, generateAllTTLs(occurrences), metrics);
+                    break;
+                case "optimised":
+                    simulate(occurrences, generateTTLsOfInterest(occurrences), metrics);
+                    break;
+                default:
+                    testKernels(occurrences);
+                    break;
+            }
+            return metrics;
+        });
+        metrics.keepBestMetrics(computeIfAbsent);
     }
 
     public static void simulate(List<Occurrence> occurrences, Stream<Long> ttls, Metrics metrics) {
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/StorageManager.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/StorageManager.java
new file mode 100644
index 0000000..6fb32a5
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/StorageManager.java
@@ -0,0 +1,138 @@
+package br.ufrgs.inf.prosoft.tfcache;
+
+import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
+import br.ufrgs.inf.prosoft.tfcache.metadata.Occurrence;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.logging.Logger;
+
+public class StorageManager {
+
+    private static final Logger LOGGER = Logger.getLogger(StorageManager.class.getName());
+    private static JsonObject STORAGE = null;
+    private static String UUID = null;
+
+    public static Metrics get(String uuid, List<Occurrence> occurrences) {
+        if (occurrences == null || occurrences.isEmpty() || occurrences.size() < 2) {
+            LOGGER.severe("Wrong list of occurrences provided");
+            System.exit(1);
+        }
+        if (Configuration.getStore() == null) {
+            return null;
+        }
+        if (STORAGE == null) {
+            load();
+        }
+        if (STORAGE.get(uuid) == null) {
+            return null;
+        }
+        String batchUUID = getBatchUUID(occurrences);
+        JsonElement storedMetrics = STORAGE.get(uuid).getAsJsonObject().get("batches").getAsJsonObject().get(batchUUID);
+        Gson gson = new Gson();
+        return gson.fromJson(storedMetrics, Metrics.class);
+    }
+
+    public static Metrics computeIfAbsent(List<Occurrence> occurrences, Supplier<Metrics> supplier) {
+        if (occurrences == null || occurrences.isEmpty() || occurrences.size() < 2) {
+            LOGGER.severe("Wrong list of occurrences provided");
+            System.exit(1);
+        }
+        if (Configuration.getStore() == null) {
+            return supplier.get();
+        }
+        if (STORAGE == null) {
+            load();
+        }
+        String batchUUID = getBatchUUID(occurrences);
+        JsonElement storedMetrics = STORAGE.get(UUID).getAsJsonObject().get("batches").getAsJsonObject().get(batchUUID);
+        Gson gson = new Gson();
+        if (storedMetrics != null) {
+            return gson.fromJson(storedMetrics, Metrics.class);
+        }
+        Metrics metrics = supplier.get();
+        JsonElement jsonMetrics = gson.toJsonTree(metrics);
+        STORAGE.get(UUID).getAsJsonObject().get("batches").getAsJsonObject().add(batchUUID, jsonMetrics);
+        return metrics;
+    }
+
+    private static String getBatchUUID(List<Occurrence> occurrences) {
+        StringBuilder uuid = new StringBuilder();
+        Iterator<Occurrence> iterator = occurrences.iterator();
+        while (iterator.hasNext()) {
+            Occurrence occurrence = iterator.next();
+            uuid.append(occurrence.getStartTime()).append(":").append(occurrence.getEndTime());
+            if (iterator.hasNext()) {
+                uuid.append(",");
+            }
+        }
+        return uuid.toString();
+    }
+
+    private static JsonObject loadStorage() {
+        FileReader fileReader = null;
+        try {
+            fileReader = new FileReader(Configuration.getStore());
+        } catch (FileNotFoundException ex) {
+            Gson gson = new GsonBuilder().setPrettyPrinting().create();
+            try (Writer writer = new FileWriter(Configuration.getStore(), true)) {
+                gson.toJson(new JsonObject(), writer);
+            } catch (IOException ex1) {
+            }
+            try {
+                fileReader = new FileReader(Configuration.getStore());
+            } catch (FileNotFoundException ex1) {
+            }
+        }
+        JsonParser jsonParser = new JsonParser();
+        JsonObject storage = jsonParser.parse(fileReader).getAsJsonObject();
+        return storage;
+    }
+
+    public static void load() {
+        if (Configuration.getStore() == null) {
+            return;
+        }
+        STORAGE = loadStorage();
+        UUID = Configuration.getUUID();
+        if (STORAGE.get(UUID) == null) {
+            LOGGER.info("Configurations not stored yet");
+            STORAGE.add(UUID, new JsonObject());
+            STORAGE.get(UUID).getAsJsonObject().add("configuration", Configuration.toJSONObject());
+            STORAGE.get(UUID).getAsJsonObject().add("batches", new JsonObject());
+        }
+    }
+
+    public static void update() {
+        if (Configuration.getStore() == null) {
+            return;
+        }
+        JsonObject storage = loadStorage();
+        if (storage.get(UUID) == null) {
+            storage.add(UUID, STORAGE.get(UUID));
+        } else {
+            STORAGE.get(UUID).getAsJsonObject().get("batches").getAsJsonObject().entrySet().forEach(entry -> {
+                String computedBatchUUID = entry.getKey();
+                if (storage.get(UUID).getAsJsonObject().get("batches").getAsJsonObject().get(computedBatchUUID) == null) {
+                    storage.get(UUID).getAsJsonObject().get("batches").getAsJsonObject().add(computedBatchUUID, entry.getValue());
+                }
+            });
+        }
+        try (Writer writer = new FileWriter(Configuration.getStore())) {
+            Gson gson = new GsonBuilder().setPrettyPrinting().create();
+            gson.toJson(storage, writer);
+        } catch (IOException ex1) {
+        }
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java
index a74a2e6..9aa850d 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java
@@ -44,17 +44,17 @@ public class TFCache {
         LOGGER.log(Level.INFO, "Recommending TTL to {0} methods", this.methods.size());
         this.methods.stream().parallel().forEach(Method::recommendTTL);
         LOGGER.log(Level.INFO, "Removing not recommended from {0}", this.methods.size());
-        this.methods.removeIf(method -> method.getSavedTime() == 0);
+        this.methods.removeIf(method -> method.getBestMetrics().getSavedTime() == 0);
         LOGGER.log(Level.INFO, "Ranking {0} methods according saved time", this.methods.size());
-        this.methods.sort((method1, method2) -> Long.compare(method2.getSavedTime(), method1.getSavedTime()));
+        this.methods.sort((method1, method2) -> method2.getBestMetrics().getSavedTimePerTimeInCache().compareTo(method1.getBestMetrics().getSavedTimePerTimeInCache()));
         LOGGER.log(Level.INFO, "Printing recommendations for {0} methods", this.methods.size());
         this.methods.forEach(method -> {
             System.out.println(method.getName()
                     + " Occurrences " + method.getOccurrencesSize()
                     + " Inputs " + method.groupsOfOccurrences().count()
-                    + " TTL " + method.getTtl()
-                    + " STpTiC " + method.getSavedTimePerTimeInCache()
-                    + " Saves " + method.getSavedTime()
+                    + " TTL " + method.getBestMetrics().getTtl()
+                    + " STpTiC " + method.getBestMetrics().getSavedTimePerTimeInCache()
+                    + " Saves " + method.getBestMetrics().getSavedTime()
                     + " Hits " + method.getBestMetrics().getHits()
                     + " Stales " + method.getBestMetrics().getStales());
         });
@@ -77,17 +77,21 @@ public class TFCache {
         this.methods.removeIf(method -> method.groupsOfOccurrences().count() < 1);
         LOGGER.log(Level.INFO, "Ranking {0} methods and inputs according saved time", this.methods.size());
         this.methods.forEach(Method::rankRecommendations);
-        this.methods.sort((method1, method2) -> Long.compare(method2.getSavedTime(), method1.getSavedTime()));
+        this.methods.sort((method1, method2) -> method2.getEstimatedSavedTimePerTimeInCache().compareTo(method1.getEstimatedSavedTimePerTimeInCache()));
         LOGGER.log(Level.INFO, "Printing recommendations for {0} methods", this.methods.size());
         this.methods.forEach(method -> {
-            System.out.println(method.getName()
-                    + " Occurrences " + method.getOccurrencesSize()
-                    + " Inputs " + method.groupsOfOccurrences().count()
-                    + " TTL " + method.getTtl()
-                    + " STpTiC " + method.getSavedTimePerTimeInCache()
-                    + " Saves " + method.getSavedTime()
-                    + " Hits " + method.getBestMetrics().getHits()
-                    + " Stales " + method.getBestMetrics().getStales());
+            if (method.getBestMetrics() != null) {
+                System.out.println(method.getName()
+                        + " Occurrences " + method.getOccurrencesSize()
+                        + " Inputs " + method.groupsOfOccurrences().count()
+                        + " TTL " + method.getBestMetrics().getTtl()
+                        + " STpTiC " + method.getBestMetrics().getSavedTimePerTimeInCache()
+                        + " Saves " + method.getBestMetrics().getSavedTime()
+                        + " Hits " + method.getBestMetrics().getHits()
+                        + " Stales " + method.getBestMetrics().getStales());
+            } else {
+                System.out.println(method.getName());
+            }
             if (Configuration.getVerbose()) {
                 method.groupsOfOccurrences().forEach(group -> {
                     System.out.println("\t" + group.getParameters().hashCode()