tfcache

added pareto frontier. replaced best metrics for euclidean

1/6/2021 6:19:14 AM

Details

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 0c689ed..0c64f2d 100755
--- 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,6 +1,9 @@
 package br.ufrgs.inf.prosoft.tfcache.configuration;
 
 import com.google.gson.JsonObject;
+
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -16,6 +19,7 @@ public class Configuration {
     private static String changeability = "allow";
     private static String staleness = "ignore";
     private static String kernel = "exhaustive";
+    private static ArrayList<Double> preferences = new ArrayList<>(List.of(1D, 0D));
     private static boolean verbose = false;
 
     public static void process(String[] args) {
@@ -35,6 +39,11 @@ public class Configuration {
         setStaleness(arguments.get("staleness"));
         setKernel(arguments.get("kernel"));
         setVerbose(arguments.get("verbose"));
+        setPreferences(arguments.get("preferences"));
+    }
+
+    public static String getInput() {
+        return input;
     }
 
     public static void setInput(String input) {
@@ -45,8 +54,8 @@ public class Configuration {
         Configuration.input = input;
     }
 
-    public static String getInput() {
-        return input;
+    public static String getStore() {
+        return store;
     }
 
     public static void setStore(String store) {
@@ -61,8 +70,8 @@ public class Configuration {
         Configuration.store = store;
     }
 
-    public static String getStore() {
-        return store;
+    public static String getLevel() {
+        return level;
     }
 
     public static void setLevel(String level) {
@@ -77,8 +86,8 @@ public class Configuration {
         Configuration.level = level;
     }
 
-    public static String getLevel() {
-        return level;
+    public static String getOutput() {
+        return output;
     }
 
     public static void setOutput(String output) {
@@ -88,8 +97,8 @@ public class Configuration {
         Configuration.output = output;
     }
 
-    public static String getOutput() {
-        return output;
+    public static String getChangeability() {
+        return changeability;
     }
 
     public static void setChangeability(String changeability) {
@@ -104,8 +113,8 @@ public class Configuration {
         Configuration.changeability = changeability;
     }
 
-    public static String getChangeability() {
-        return changeability;
+    public static String getStaleness() {
+        return staleness;
     }
 
     public static void setStaleness(String staleness) {
@@ -120,8 +129,8 @@ public class Configuration {
         Configuration.staleness = staleness;
     }
 
-    public static String getStaleness() {
-        return staleness;
+    public static String getKernel() {
+        return kernel;
     }
 
     public static void setKernel(String kernel) {
@@ -136,8 +145,8 @@ public class Configuration {
         Configuration.kernel = kernel;
     }
 
-    public static String getKernel() {
-        return kernel;
+    public static boolean getVerbose() {
+        return verbose;
     }
 
     public static void setVerbose(String verbose) {
@@ -151,8 +160,23 @@ public class Configuration {
         Configuration.verbose = verbose.equals("true");
     }
 
-    public static boolean getVerbose() {
-        return verbose;
+    public static List<Double> getPreferences() {
+        return preferences;
+    }
+
+    public static void setPreferences(String preferences) {
+        if (preferences == null) {
+            LOGGER.info("using default preferences");
+            return;
+        }
+        String[] split = preferences.split(",");
+        if (split.length != 2) {
+            LOGGER.severe("Wrong input for preferences");
+            System.exit(1);
+        }
+        Configuration.preferences.clear();
+        Configuration.preferences.add(Double.valueOf(split[0]));
+        Configuration.preferences.add(Double.valueOf(split[1]));
     }
 
     public static String getUUID() {
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 60c0f04..b385b41 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java
@@ -9,12 +9,12 @@ import br.ufrgs.inf.prosoft.tfcache.adapter.TraceReader;
 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.logging.Level;
 import java.util.logging.Logger;
 
 /**
- *
  * @author romulo
  */
 public class Main {
@@ -25,7 +25,15 @@ 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> [--store=<storePath>] [--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] " +
+                    "[--preferences=(savedTime)0..1,0..1(idleTime)] " +
+                    "[--verbose=<false|true>]");
             System.exit(1);
         }
 
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/GroupOfOccurrences.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/GroupOfOccurrences.java
index 25ba662..65d1d7d 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/GroupOfOccurrences.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/GroupOfOccurrences.java
@@ -6,8 +6,12 @@
 package br.ufrgs.inf.prosoft.tfcache.metadata;
 
 import br.ufrgs.inf.prosoft.tfcache.Metrics;
+import br.ufrgs.inf.prosoft.tfcache.Pareto;
 import br.ufrgs.inf.prosoft.tfcache.Simulator;
+import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
+
 import java.math.BigDecimal;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
@@ -26,7 +30,7 @@ public class GroupOfOccurrences {
     private final List<Occurrence> occurrences;
     private final String parameters;
 
-    private Metrics bestMetrics;
+    private Pareto pareto;
 
     public GroupOfOccurrences(String parameters, List<Occurrence> occurrences) {
         this.parameters = parameters;
@@ -42,10 +46,10 @@ public class GroupOfOccurrences {
     }
 
     public Metrics getBestMetrics() {
-        if (this.bestMetrics == null) {
-            throw new RuntimeException("hitsPerTimeInCache must be calculated");
+        if (this.pareto == null) {
+            throw new RuntimeException("simulation must be executed");
         }
-        return this.bestMetrics;
+        return this.pareto.getBestMetrics();
     }
 
     public long getTtl() {
@@ -60,7 +64,7 @@ public class GroupOfOccurrences {
         return getBestMetrics().getStales();
     }
 
-    public long getSavedTime() {
+    public double getSavedTime() {
         return getBestMetrics().getSavedTime();
     }
 
@@ -84,16 +88,20 @@ public class GroupOfOccurrences {
         return false;
     }
 
-    public void calculateHitsPerTimeInCache() {
-        if (this.bestMetrics != null) {
-            LOGGER.log(Level.WARNING, "HitsPerTimeInCache already calculated");
+    public void calculateMetrics() {
+        if (this.pareto != null) {
+            LOGGER.log(Level.WARNING, "metrics already calculated");
         }
         if (this.occurrences.size() < 2) {
             throw new RuntimeException("Not reusable input");
         }
-        this.occurrences.sort((occurrence1, occurrence2) -> Long.compare(occurrence1.getStartTime(), occurrence2.getStartTime()));
-        this.bestMetrics = new Metrics();
-        Simulator.simulate(this.occurrences, this.bestMetrics);
+        this.occurrences.sort(Comparator.comparingLong(Occurrence::getStartTime));
+        this.pareto = new Pareto();
+        Simulator.simulate(this.occurrences, this.pareto);
+        if (Configuration.getVerbose()) {
+            this.pareto.values().forEach(it ->
+                    System.out.println(Configuration.getInput() + "," + it.getTtl() + "," + it.getSavedTime() + "," + it.getHits() + "," + it.getTimeInCache()));
+        }
     }
 
 }
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 f30b16e..84fd1aa 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
@@ -6,6 +6,7 @@
 package br.ufrgs.inf.prosoft.tfcache.metadata;
 
 import br.ufrgs.inf.prosoft.tfcache.Metrics;
+import br.ufrgs.inf.prosoft.tfcache.Pareto;
 import br.ufrgs.inf.prosoft.tfcache.Simulator;
 import br.ufrgs.inf.prosoft.tfcache.StorageManager;
 import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
@@ -27,12 +28,12 @@ public class Method {
     private static final Logger LOGGER = Logger.getLogger(Method.class.getName());
 
     private final String name;
-    private List<Occurrence> occurrences;
+    private final List<Occurrence> occurrences;
     private List<GroupOfOccurrences> groupsOfOccurrences;
 
     private Integer countChangeableGroups;
 
-    private Metrics bestMetrics;
+    private Pareto pareto;
 
     public Method(String name, List<Occurrence> occurrences) {
         this.name = name;
@@ -50,7 +51,7 @@ public class Method {
             throw new RuntimeException("Occurrences cannot be null");
         }
         Map<String, List<Occurrence>> inputHasOccurrences = new ConcurrentHashMap<>();
-        occurrences.stream().forEach(occurrence -> {
+        occurrences.forEach(occurrence -> {
             String parameters = occurrence.getParametersSerialised();
 
             inputHasOccurrences.compute(parameters, (key, oldValue) -> {
@@ -69,21 +70,21 @@ public class Method {
     }
 
     public Metrics getBestMetrics() {
-        return this.bestMetrics;
+        return this.pareto.getBestMetrics();
     }
 
-    public long getEstimatedSavedTime() {
+    public double getEstimatedSavedTime() {
         if (getBestMetrics() != null) {
             return getBestMetrics().getSavedTime();
         }
-        return groupsOfOccurrences().map(group -> group.getSavedTime()).reduce(Long::sum).orElse(0L);
+        return groupsOfOccurrences().map(GroupOfOccurrences::getSavedTime).reduce(Double::sum).orElse(0D);
     }
 
     public BigDecimal getEstimatedSavedTimePerTimeInCache() {
         if (getBestMetrics() != null) {
             return getBestMetrics().getSavedTimePerTimeInCache();
         }
-        GroupOfOccurrences max = groupsOfOccurrences().max((group1, group2) -> group1.getSavedTimePerTimeInCache().compareTo(group2.getSavedTimePerTimeInCache())).get();
+        GroupOfOccurrences max = groupsOfOccurrences().max(Comparator.comparing(GroupOfOccurrences::getSavedTimePerTimeInCache)).orElseThrow();
         return max.getSavedTimePerTimeInCache();
     }
 
@@ -99,7 +100,7 @@ public class Method {
             if (this.groupsOfOccurrences == null) {
                 throw new RuntimeException("groupsOfOccurrences is null");
             }
-            return this.groupsOfOccurrences.stream().map(group -> group.getOccurrencesSize()).reduce(Integer::sum).orElse(0);
+            return this.groupsOfOccurrences.stream().map(GroupOfOccurrences::getOccurrencesSize).reduce(Integer::sum).orElse(0);
         }
         return this.occurrences.size();
     }
@@ -181,18 +182,23 @@ public class Method {
     }
 
     public void recommendTTL() {
-        if (this.bestMetrics != null) {
-            LOGGER.log(Level.WARNING, "SavedTimePerTimeInCache already calculated");
+        if (this.pareto != null) {
+            LOGGER.log(Level.WARNING, "metrics already calculated");
         }
         if (this.occurrences == null) {
             throw new RuntimeException("groupByInputs called. Cannot proceed");
         }
-        this.bestMetrics = new Metrics();
+        this.pareto = new Pareto();
         if (this.occurrences.size() < 2) {
             return;
         }
         this.occurrences.sort(Comparator.comparingLong(Occurrence::getStartTime));
-        Simulator.simulate(this.occurrences, this.bestMetrics);
+        Simulator.simulate(this.occurrences, this.pareto);
+        if (Configuration.getVerbose()) {
+            String application = Configuration.getInput().split(",")[0];
+            this.pareto.values().forEach(it ->
+                    System.out.println(application + "," + name + "," + it.getTtl() + "," + it.getSavedTime() + "," + it.getHits() + "," + it.getTimeInCache()));
+        }
     }
 
     public void recommendTTLPerInput() {
@@ -202,16 +208,19 @@ public class Method {
         }
         if (Configuration.getVerbose()) {
             System.out.println("=== " + getName() + " ===");
+            Configuration.setInput(Configuration.getInput().split(",")[0] + "," + getName() + ",");
+            groupsOfOccurrences().forEach(GroupOfOccurrences::calculateMetrics);
+        } else {
+            groupsOfOccurrences().parallel().forEach(GroupOfOccurrences::calculateMetrics);
         }
-        groupsOfOccurrences().parallel().forEach(GroupOfOccurrences::calculateHitsPerTimeInCache);
         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;
+        Pareto pareto = StorageManager.get(uuid, this.occurrences);
+        if (this.pareto == null && pareto != null) {
+            this.pareto = pareto;
         }
     }
 
-    public void removeNotRecommededInputs() {
+    public void removeNotRecommendedInputs() {
         if (this.groupsOfOccurrences == null) {
             throw new RuntimeException("Recommendations not called");
         }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
old mode 100644
new mode 100755
index 4439e6a..db1120b
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
@@ -5,17 +5,17 @@
  */
 package br.ufrgs.inf.prosoft.tfcache;
 
+import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
+import br.ufrgs.inf.prosoft.tfcache.metadata.Occurrence;
+
 import java.math.BigDecimal;
-import java.math.BigInteger;
 import java.math.MathContext;
-import java.util.Collection;
-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;
 
 /**
- *
  * @author romulo
  */
 public class Metrics implements Comparable<Metrics> {
@@ -23,27 +23,60 @@ public class Metrics implements Comparable<Metrics> {
     private long ttl;
     private long hits;
     private long timeInCache;
+    private long computationTime;
     private long stales;
-    private long savedTime;
-    private BigDecimal savedTimePerTimeInCache;
+    private double savedTime;
+
+    private transient String uuid;
+    private transient Double idleTime;
+
+    protected Metrics() {
+        Logger.getLogger(Metrics.class.getName()).log(Level.FINEST, "returning empty metrics");
+    }
 
-    public Metrics() {
-        this.savedTimePerTimeInCache = new BigDecimal(BigInteger.ZERO);
+    public Metrics(long ttl, long hits, long timeInCache, long computationTime, long stales, double savedTime, String uuid) {
+        this(ttl, hits, timeInCache, computationTime, stales, savedTime);
+        this.uuid = uuid;
     }
 
-    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");
+    public Metrics(long ttl, long hits, long timeInCache, long computationTime, long stales, double savedTime) {
+        if (ttl < 0 || hits < 0 || timeInCache < 0 || computationTime < 0 || stales < 0 || savedTime < 0) {
+            throw new RuntimeException("Metrics cannot be under zero."
+                    + " TTL: " + ttl
+                    + " hits: " + hits
+                    + " timeInCache: " + timeInCache
+                    + " computationTime: " + computationTime
+                    + " stales: " + stales
+                    + " savedTime: " + savedTime);
         }
-        if (timeInCache == 0) {
-            throw new RuntimeException("timeInCache should not be zero");
+        if (ttl == 0 || computationTime == 0 || timeInCache == 0) {
+            throw new RuntimeException("Metrics cannot be zero."
+                    + " TTL: " + ttl
+                    + " timeInCache: " + timeInCache
+                    + " computationTime: " + computationTime);
         }
         this.ttl = ttl;
         this.hits = hits;
         this.timeInCache = timeInCache;
+        this.computationTime = computationTime;
         this.stales = stales;
         this.savedTime = savedTime;
-        this.savedTimePerTimeInCache = new BigDecimal(this.savedTime).divide(new BigDecimal(this.timeInCache), MathContext.DECIMAL128);
+    }
+
+    public static String getUUID(Stream<Occurrence> occurrences) {
+        return occurrences.map(it -> it.getStartTime() + ":" + it.getEndTime()).collect(Collectors.joining(","));
+    }
+
+    private static double calculateEuclideanDistance(double x1, double y1, double x2, double y2) {
+        return Math.sqrt(Math.pow(x1 - x2, 2) - Math.pow(y1, y2));
+    }
+
+    public double calculateEuclideanDistance(double objectiveSavedTime, double objectiveIdleTime) {
+        return calculateEuclideanDistance(getSavedTime(), getIdleTime(), objectiveSavedTime, objectiveIdleTime);
+    }
+
+    public String getUUID() {
+        return uuid;
     }
 
     public long getTtl() {
@@ -58,107 +91,70 @@ public class Metrics implements Comparable<Metrics> {
         return timeInCache;
     }
 
+    public double getIdleTime() {
+        if (this.idleTime == null) {
+            this.idleTime = (double) timeInCache - hits;
+        }
+        return this.idleTime;
+    }
+
+    public long getComputationTime() {
+        return computationTime;
+    }
+
     public long getStales() {
         return this.stales;
     }
 
-    public long getSavedTime() {
+    public double getSavedTime() {
         return this.savedTime;
     }
 
     public BigDecimal getSavedTimePerTimeInCache() {
-        return this.savedTimePerTimeInCache;
+        if (this.timeInCache + this.computationTime == 0) {
+            return BigDecimal.ZERO;
+        }
+        return new BigDecimal(this.savedTime)
+                .divide(new BigDecimal(this.timeInCache + this.computationTime), MathContext.DECIMAL128);
     }
 
-    public synchronized void keepBestMetrics(Metrics metrics) {
-        keepBestMetrics(metrics.ttl, metrics.hits, metrics.timeInCache, metrics.stales, metrics.savedTime);
+    public Double getDifference() {
+        return Configuration.getPreferences().get(0) * getSavedTime() - Configuration.getPreferences().get(1) * getIdleTime();
     }
 
-    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
-                    + " hits: " + hits
-                    + " timeInCache: " + timeInCache
-                    + " stales: " + stales
-                    + " savedTime: " + savedTime);
+    public Metrics getNormalised(double savedTime, double idleTime) {
+        if (savedTime < 0 || savedTime > 1) {
+            throw new RuntimeException("wrong savedTime");
         }
-        if (ttl == 0 || hits == 0 || savedTime == 0) {
-            return;
-        }
-        if (timeInCache == 0) {
-            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, timeInCache, stales, savedTime, savedTimePerTimeInCache) == -1) {
-            this.ttl = ttl;
-            this.hits = hits;
-            this.timeInCache = timeInCache;
-            this.stales = stales;
-            this.savedTime = savedTime;
-            this.savedTimePerTimeInCache = savedTimePerTimeInCache;
+        if (idleTime < 0 || idleTime > 1) {
+            throw new RuntimeException("wrong idleTime");
         }
-    }
-
-    public static void removeDominatedMetrics(Collection<Metrics> allMetrics) {
-        Map<Long, List<Metrics>> groupBySavedTime = allMetrics.stream().collect(Collectors.groupingBy(Metrics::getSavedTime));
-        groupBySavedTime.remove(0L);
-        groupBySavedTime.forEach((savedTime, value) -> {
-            Metrics max = value.stream().max(Metrics::compareTo).get();
-            value.removeIf(metric -> metric.getSavedTimePerTimeInCache().compareTo(max.getSavedTimePerTimeInCache()) == -1);
-        });
-
-        List<Metrics> localMaxima = groupBySavedTime.entrySet().stream()
-                .map(entry -> entry.getValue().stream())
-                .reduce(Stream::concat)
-                .orElse(Stream.empty())
-                .collect(Collectors.toList());
-
-        allMetrics.removeIf(metrics -> !localMaxima.contains(metrics));
+        Metrics metrics = new Metrics(ttl,
+                hits,
+                timeInCache,
+                computationTime,
+                stales,
+                savedTime,
+                uuid);
+        metrics.idleTime = idleTime;
+        return metrics;
     }
 
     @Override
     public int compareTo(Metrics other) {
-        return compareTo(other.ttl, other.hits, other.timeInCache, other.stales, other.savedTime, other.savedTimePerTimeInCache);
-    }
-
-    private int compareTo(long ttl, long hits, long timeInCache, long stales, long savedTime, BigDecimal savedTimePerTimeInCache) {
-        if (getSavedTimePerTimeInCache().compareTo(savedTimePerTimeInCache) == 1) {
-            return 1;
-        }
-        if (getSavedTimePerTimeInCache().compareTo(savedTimePerTimeInCache) == -1) {
-            return -1;
-        }
-        if (this.stales < stales) {
-            return 1;
+        if (getTtl() == other.getTtl()) {
+            return 0;
         }
-        if (this.stales > stales) {
-            return -1;
-        }
-        if (this.savedTime > savedTime) {
-            return 1;
-        }
-        if (this.savedTime < savedTime) {
-            return -1;
-        }
-        if (this.hits > hits) {
-            return 1;
-        }
-        if (this.hits < hits) {
-            return -1;
-        }
-        if (this.ttl < ttl) {
-            return 1;
-        }
-        if (this.ttl > ttl) {
-            return -1;
+        if (getSavedTime() == other.getSavedTime() && getIdleTime() == other.getIdleTime()) {
+            throw new RuntimeException("different ttls leading to same metrics");
         }
-        if (this.timeInCache < timeInCache) {
+        if (getSavedTime() >= other.getSavedTime() && getIdleTime() <= other.getIdleTime()) {
             return 1;
         }
-        if (this.timeInCache > timeInCache) {
+        if (getSavedTime() <= other.getSavedTime() && getIdleTime() >= other.getIdleTime()) {
             return -1;
         }
-        return 0;
+        throw new RuntimeException("comparing pareto metrics");
     }
 
     @Override
@@ -183,7 +179,8 @@ public class Metrics implements Comparable<Metrics> {
                 + " Stales: " + this.stales
                 + " Hits: " + this.hits
                 + " SavedTime: " + this.savedTime
-                + " TimeInCache: " + this.timeInCache;
+                + " TimeInCache: " + this.timeInCache
+                + " ComputationTime: " + this.computationTime;
     }
 
 }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Pareto.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Pareto.java
new file mode 100644
index 0000000..065a9a7
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Pareto.java
@@ -0,0 +1,89 @@
+package br.ufrgs.inf.prosoft.tfcache;
+
+import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class Pareto {
+
+    private final Map<Double, Metrics> savedTimeHasMetrics;
+
+    public Pareto() {
+        savedTimeHasMetrics = new ConcurrentHashMap<>();
+    }
+
+    public Pareto(List<Metrics> metrics) {
+        this();
+        metrics.forEach(this::addIfPareto);
+    }
+
+    public static void removeDominatedMetrics(Collection<Metrics> allMetrics) {
+        Map<Double, List<Metrics>> groupBySavedTime = allMetrics.stream().collect(Collectors.groupingBy(Metrics::getSavedTime));
+        groupBySavedTime.remove(0L);
+        groupBySavedTime.forEach((savedTime, metrics) -> {
+            double minIdleTime = metrics.stream().mapToDouble(Metrics::getIdleTime).min().orElse(0);
+            metrics.removeIf(metric -> metric.getIdleTime() > minIdleTime);
+        });
+
+        List<Metrics> localMaxima = groupBySavedTime.values().stream()
+                .map(Collection::stream)
+                .reduce(Stream::concat)
+                .orElse(Stream.empty())
+                .collect(Collectors.toList());
+
+        allMetrics.removeIf(metrics -> !localMaxima.contains(metrics));
+    }
+
+    public synchronized void addIfPareto(Metrics metrics) {
+        if (metrics.getSavedTime() == 0) {
+            return;
+        }
+        savedTimeHasMetrics.merge(metrics.getSavedTime(), metrics, (existing, incoming) -> existing.getIdleTime() < incoming.getIdleTime() ? existing : incoming);
+    }
+
+    public Collection<Metrics> values() {
+        return savedTimeHasMetrics.values();
+    }
+
+    public Set<Long> getTtls() {
+        return values().stream().map(Metrics::getTtl).collect(Collectors.toSet());
+    }
+
+    public Stream<Metrics> getNormalised() {
+        double minSavedTime = values().stream().mapToDouble(Metrics::getSavedTime).min().orElseThrow();
+        double maxSavedTime = values().stream().mapToDouble(Metrics::getSavedTime).max().orElseThrow();
+        double distanceSavedTime = maxSavedTime - minSavedTime;
+        double minIdleTime = values().stream().mapToDouble(Metrics::getIdleTime).min().orElseThrow();
+        double maxIdleTime = values().stream().mapToDouble(Metrics::getIdleTime).max().orElseThrow();
+        double distanceIdleTime = maxIdleTime - minIdleTime;
+
+        return values().stream().map(it -> {
+            double normalisedSavedTime = (it.getSavedTime() - minSavedTime) / distanceSavedTime;
+            double normalisedIdleTime = (it.getIdleTime() - minIdleTime) / distanceIdleTime;
+            return it.getNormalised(normalisedSavedTime, normalisedIdleTime);
+        });
+    }
+
+    public Metrics getBestMetrics() {
+        return getBestMetrics(Configuration.getPreferences().get(0), Configuration.getPreferences().get(1));
+    }
+
+    private Metrics getBestMetrics(double percentageObjectiveSavedTime, double percentageObjectiveIdleTime) {
+        if (percentageObjectiveSavedTime < 0 || percentageObjectiveSavedTime > 1) {
+            throw new RuntimeException("invalid objective saved time");
+        }
+        if (percentageObjectiveIdleTime < 0 || percentageObjectiveIdleTime > 1) {
+            throw new RuntimeException("invalid objective idle time");
+        }
+        if (savedTimeHasMetrics.isEmpty()) {
+            return new Metrics();
+        }
+        double minIdleTime = values().stream().mapToDouble(Metrics::getIdleTime).min().orElseThrow() * percentageObjectiveIdleTime;
+        double maxSavedTime = values().stream().mapToDouble(Metrics::getSavedTime).max().orElseThrow() * percentageObjectiveSavedTime;
+        return values().stream().min(Comparator.comparingDouble(it -> it.calculateEuclideanDistance(maxSavedTime, minIdleTime))).orElseThrow();
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
old mode 100644
new mode 100755
index 814d241..a17aae1
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
@@ -8,86 +8,72 @@ package br.ufrgs.inf.prosoft.tfcache;
 import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;
 import br.ufrgs.inf.prosoft.tfcache.metadata.Method;
 import br.ufrgs.inf.prosoft.tfcache.metadata.Occurrence;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
+
+import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.LongStream;
 import java.util.stream.Stream;
 
 /**
- *
  * @author romulo
  */
 public class Simulator {
 
-    private final Metrics metrics;
+    private final Pareto pareto;
 
     public Simulator() {
-        this.metrics = new Metrics();
-    }
-
-    public Metrics getMetrics() {
-        return metrics;
-    }
-
-    public void simulate(List<Occurrence> occurrences) {
-        simulate(occurrences, this.metrics);
+        this.pareto = new Pareto();
     }
 
-    public void simulate(Stream<Occurrence> occurrences, long ttl) {
-        simulate(occurrences, ttl, this.metrics);
-    }
-
-    public static void simulate(List<Occurrence> occurrences, Metrics metrics) {
-        Metrics computeIfAbsent = StorageManager.computeIfAbsent(occurrences, () -> {
+    public static void simulate(List<Occurrence> occurrences, Pareto pareto) {
+        Pareto computeIfAbsent = StorageManager.computeIfAbsent(occurrences, () -> {
             switch (Configuration.getKernel()) {
                 case "exhaustive":
-                    simulate(occurrences, generateAllTTLs(occurrences), metrics);
+                    simulate(occurrences, generateAllTTLs(occurrences), pareto);
                     break;
                 case "optimised":
-                    simulate(occurrences, generateTTLsOfInterest(occurrences), metrics);
+                    simulate(occurrences, generateTTLsOfInterest(occurrences), pareto);
                     break;
                 default:
                     testKernels(occurrences);
                     break;
             }
-            return metrics;
+            return pareto;
         });
-        metrics.keepBestMetrics(computeIfAbsent);
+        computeIfAbsent.values().forEach(pareto::addIfPareto);
     }
 
-    public static void simulate(List<Occurrence> occurrences, Stream<Long> ttls, Metrics metrics) {
-        ttls.forEach(actualTTL -> {
-            simulate(occurrences.stream(), actualTTL, metrics);
-        });
+    public static void simulate(List<Occurrence> occurrences, Stream<Long> ttls, Pareto pareto) {
+        ttls.forEach(actualTTL -> simulate(occurrences.stream(), actualTTL, pareto));
     }
 
-    public static void simulate(Stream<Occurrence> occurrences, long ttl, Metrics metrics) {
+    public static void simulate(Stream<Occurrence> occurrences, long ttl, Pareto pareto) {
         Map<String, Long> inputHasCachedTime = new HashMap<>();
         Map<String, Object> inputHasOutput = new HashMap<>();
 
-        long simulationSavedTime = 0;
+        long blindedSavedTime = 0;
         long realSavedTime = 0;
         long hits = 0;
+        long computationTime = 0;
         long timeInCache = 0;
         long stales = 0;
 
         Iterator<Occurrence> iterator = occurrences.iterator();
         while (iterator.hasNext()) {
             Occurrence occurrence = iterator.next();
-            long adjustedStartTime = occurrence.getStartTime() - simulationSavedTime;
-            long adjustedEndTime = occurrence.getEndTime() - simulationSavedTime;
+            long adjustedStartTime = occurrence.getStartTime() - blindedSavedTime;
+            long adjustedEndTime = occurrence.getEndTime() - blindedSavedTime;
+            if (occurrence.getExecutionTime() < 0) {
+                throw new RuntimeException("executionTime cannot be under zero");
+            }
+            if (adjustedEndTime < adjustedStartTime) {
+                throw new RuntimeException("adjustedEndTime should not be lesser than adjustedStartTime");
+            }
+            if (inputHasCachedTime.containsKey(occurrence.getParametersSerialised()) &&
+                    adjustedStartTime - inputHasCachedTime.get(occurrence.getParametersSerialised()) > ttl) {
+                inputHasCachedTime.remove(occurrence.getParametersSerialised());
+            }
             if (inputHasCachedTime.containsKey(occurrence.getParametersSerialised())) {
-                if (adjustedStartTime - inputHasCachedTime.get(occurrence.getParametersSerialised()) > ttl) {
-                    inputHasCachedTime.remove(occurrence.getParametersSerialised());
-                }
                 if (Configuration.getStaleness().equals("shrink")) {
                     if (Objects.deepEquals(inputHasOutput.get(occurrence.getParametersSerialised()), occurrence.getReturnValue())) {
                         realSavedTime += occurrence.getExecutionTime();
@@ -95,21 +81,19 @@ public class Simulator {
                         stales++;
                     }
                 }
-                simulationSavedTime += occurrence.getExecutionTime();
+                blindedSavedTime += occurrence.getExecutionTime();
                 hits++;
             } else {
                 inputHasCachedTime.put(occurrence.getParametersSerialised(), adjustedEndTime);
                 if (Configuration.getStaleness().equals("shrink")) {
                     inputHasOutput.put(occurrence.getParametersSerialised(), occurrence.getReturnValue());
                 }
+                computationTime += occurrence.getExecutionTime();
                 timeInCache += ttl;
             }
         }
-        if (Configuration.getStaleness().equals("shrink")) {
-            metrics.keepBestMetrics(ttl, hits, timeInCache, stales, realSavedTime);
-        } else {
-            metrics.keepBestMetrics(ttl, hits, timeInCache, stales, simulationSavedTime);
-        }
+        Metrics metrics = new Metrics(ttl, hits, timeInCache, computationTime, stales, Configuration.getStaleness().equals("shrink") ? realSavedTime : blindedSavedTime);
+        pareto.addIfPareto(metrics);
     }
 
     public static Stream<Long> generateTTLsOfInterest(Stream<Occurrence> occurrences) {
@@ -118,8 +102,8 @@ public class Simulator {
 
     public static Stream<Long> generateTTLsOfInterest(List<Occurrence> occurrences) {
         List<Long> windows = new ArrayList<>();
-        for (int i = 1; i < occurrences.size(); i++) {
-            long window = occurrences.get(i).getStartTime() - occurrences.get(i - 1).getEndTime();
+        for (int hits = 1; hits < occurrences.size(); hits++) {
+            long window = occurrences.get(hits).getStartTime() - occurrences.get(hits - 1).getEndTime();
             if (window > 0) {
                 windows.add(window);
             }
@@ -151,38 +135,36 @@ public class Simulator {
     }
 
     private static void testKernels(List<Occurrence> occurrences) {
-        Metrics optimisedMetrics = new Metrics();
+        Pareto optimisedPareto = new Pareto();
+        Pareto exhaustivePareto = new Pareto();
 
         Map<String, List<Occurrence>> inputHasOccurrences = Method.groupByInput(occurrences);
         Set<Long> ttlsOfInterest = inputHasOccurrences.values().stream()
                 .map(Simulator::generateTTLsOfInterest)
                 .reduce(Stream::concat)
-                .get()
+                .orElse(Stream.empty())
                 .collect(Collectors.toSet());
-        simulate(occurrences, ttlsOfInterest.stream(), optimisedMetrics);
 
-        List<Metrics> exhaustiveMetrics = Collections.synchronizedList(new ArrayList<>());
-        generateAllTTLs(occurrences).forEach(actualTTL -> {
-            Metrics partialMetrics = new Metrics();
-            exhaustiveMetrics.add(partialMetrics);
-            simulate(occurrences.stream(), actualTTL, partialMetrics);
-        });
+        simulate(occurrences, ttlsOfInterest.stream(), optimisedPareto);
+        simulate(occurrences, generateAllTTLs(occurrences), exhaustivePareto);
 
-        Metrics.removeDominatedMetrics(exhaustiveMetrics);
-        List<Long> missingTTLs = exhaustiveMetrics.stream().map(metrics -> metrics.getTtl())
+        List<Long> missingTTLs = exhaustivePareto.values().stream().map(Metrics::getTtl)
                 .filter(ttl -> !ttlsOfInterest.contains(ttl))
                 .sorted()
                 .collect(Collectors.toList());
         if (!missingTTLs.isEmpty()) {
+            System.out.println("=== " + Configuration.getInput() + " ===");
             System.out.println("\tMissing ttls: " + missingTTLs);
         }
 
-        Metrics maxExhaustiveMetrics = exhaustiveMetrics.stream().max(Metrics::compareTo).orElse(new Metrics());
-        if (maxExhaustiveMetrics.getTtl() != optimisedMetrics.getTtl()) {
+        Metrics maxExhaustiveMetrics = exhaustivePareto.getBestMetrics();
+        Metrics maxOptimisedMetrics = optimisedPareto.getBestMetrics();
+        if (maxExhaustiveMetrics.getTtl() != maxOptimisedMetrics.getTtl()) {
+            System.out.println("=== " + Configuration.getInput() + " ===");
             System.out.println("\tDIFFERENT BEST METRICS");
-            System.out.println("\tOptimised: " + optimisedMetrics);
+            System.out.println("\tOptimised: " + maxOptimisedMetrics);
             System.out.println("\tExhaustive: " + maxExhaustiveMetrics);
-            switch (maxExhaustiveMetrics.compareTo(optimisedMetrics)) {
+            switch (maxExhaustiveMetrics.compareTo(maxOptimisedMetrics)) {
                 case -1:
                     System.out.println("\tOptimised won");
                     break;
@@ -196,4 +178,16 @@ public class Simulator {
         }
     }
 
+    public Pareto getPareto() {
+        return pareto;
+    }
+
+    public void simulate(List<Occurrence> occurrences) {
+        simulate(occurrences, this.pareto);
+    }
+
+    public void simulate(Stream<Occurrence> occurrences, long ttl) {
+        simulate(occurrences, ttl, this.pareto);
+    }
+
 }
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/StorageManager.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/StorageManager.java
index 9df471f..3d3ab5d 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/StorageManager.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/StorageManager.java
@@ -5,7 +5,7 @@ import br.ufrgs.inf.prosoft.tfcache.metadata.Occurrence;
 import com.google.gson.*;
 
 import java.io.*;
-import java.util.Iterator;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Supplier;
 import java.util.logging.Logger;
@@ -16,60 +16,62 @@ public class StorageManager {
     private static JsonObject STORAGE = null;
     private static String UUID = null;
 
-    public static Metrics get(String uuid, List<Occurrence> occurrences) {
+    public static void put(String uuid, List<Occurrence> occurrences, Pareto pareto) {
         if (occurrences == null || occurrences.isEmpty() || occurrences.size() < 2) {
             LOGGER.severe("Wrong list of occurrences provided");
             System.exit(1);
         }
         if (Configuration.getStore() == null) {
-            return null;
+            return;
         }
         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);
+        String batchUUID = Metrics.getUUID(occurrences.stream());
+        STORAGE.getAsJsonObject(uuid).getAsJsonObject("batches").add(batchUUID, new JsonArray());
+        JsonArray jsonPareto = STORAGE.getAsJsonObject(uuid).getAsJsonObject("batches").getAsJsonArray(batchUUID);
         Gson gson = new Gson();
-        return gson.fromJson(storedMetrics, Metrics.class);
+        pareto.values().stream().map(gson::toJsonTree).forEach(jsonPareto::add);
     }
 
-    public static Metrics computeIfAbsent(List<Occurrence> occurrences, Supplier<Metrics> supplier) {
+    public static Pareto 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 supplier.get();
+            return null;
         }
         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);
+        if (STORAGE.get(uuid) == null) {
+            return null;
+        }
+        String batchUUID = Metrics.getUUID(occurrences.stream());
+        JsonArray jsonPareto = STORAGE.getAsJsonObject(uuid).getAsJsonObject("batches").getAsJsonArray(batchUUID);
+        if (jsonPareto == null) {
+            return null;
         }
-        Metrics metrics = supplier.get();
-        JsonElement jsonMetrics = gson.toJsonTree(metrics);
-        STORAGE.get(UUID).getAsJsonObject().get("batches").getAsJsonObject().add(batchUUID, jsonMetrics);
-        return metrics;
+        Gson gson = new Gson();
+        List<Metrics> paretoMetrics = new ArrayList<>();
+        jsonPareto.forEach(jsonElement -> {
+                    Metrics metrics = gson.fromJson(jsonElement, Metrics.class);
+                    paretoMetrics.add(metrics);
+                }
+        );
+        return new Pareto(paretoMetrics);
     }
 
-    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(",");
-            }
+    public static Pareto computeIfAbsent(List<Occurrence> occurrences, Supplier<Pareto> supplier) {
+        Pareto pareto = get(UUID, occurrences);
+        if (pareto != null) {
+            return pareto;
         }
-        return uuid.toString();
+
+        pareto = supplier.get();
+        put(UUID, occurrences, pareto);
+        return pareto;
     }
 
     private static JsonObject loadStorage() {
@@ -80,16 +82,16 @@ public class StorageManager {
             Gson gson = new GsonBuilder().setPrettyPrinting().create();
             try (Writer writer = new FileWriter(Configuration.getStore(), true)) {
                 gson.toJson(new JsonObject(), writer);
-            } catch (IOException ex1) {
+            } catch (IOException ignored) {
             }
             try {
                 fileReader = new FileReader(Configuration.getStore());
-            } catch (FileNotFoundException ex1) {
+            } catch (FileNotFoundException ignored) {
             }
         }
         JsonParser jsonParser = new JsonParser();
-        JsonObject storage = jsonParser.parse(fileReader).getAsJsonObject();
-        return storage;
+        assert fileReader != null;
+        return jsonParser.parse(fileReader).getAsJsonObject();
     }
 
     public static void load() {
@@ -124,7 +126,7 @@ public class StorageManager {
         try (Writer writer = new FileWriter(Configuration.getStore())) {
             Gson gson = new GsonBuilder().setPrettyPrinting().create();
             gson.toJson(storage, writer);
-        } catch (IOException ex1) {
+        } catch (IOException ignored) {
         }
     }
 
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 9aa850d..19873f7 100755
--- a/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java
@@ -10,15 +10,16 @@ import br.ufrgs.inf.prosoft.tfcache.metadata.Method;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonObject;
+
 import java.io.FileWriter;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.Stream;
 
 /**
- *
  * @author romulo
  */
 public class TFCache {
@@ -42,22 +43,25 @@ public class TFCache {
         LOGGER.log(Level.INFO, "Removing not reusable from {0} methods", this.methods.size());
         this.methods.removeIf(Method::isNotReusable);
         LOGGER.log(Level.INFO, "Recommending TTL to {0} methods", this.methods.size());
-        this.methods.stream().parallel().forEach(Method::recommendTTL);
+        if (Configuration.getVerbose()) {
+            this.methods.forEach(Method::recommendTTL);
+        } else {
+            this.methods.stream().parallel().forEach(Method::recommendTTL);
+        }
         LOGGER.log(Level.INFO, "Removing not recommended from {0}", this.methods.size());
         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) -> method2.getBestMetrics().getSavedTimePerTimeInCache().compareTo(method1.getBestMetrics().getSavedTimePerTimeInCache()));
+        this.methods.sort((method1, method2) -> Double.compare(method2.getBestMetrics().getSavedTime(), method1.getBestMetrics().getSavedTime()));
         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.getBestMetrics().getTtl()
-                    + " STpTiC " + method.getBestMetrics().getSavedTimePerTimeInCache()
-                    + " Saves " + method.getBestMetrics().getSavedTime()
-                    + " Hits " + method.getBestMetrics().getHits()
-                    + " Stales " + method.getBestMetrics().getStales());
-        });
+        this.methods.forEach(method -> System.out.println(method.getName()
+                + " Occurrences " + method.getOccurrencesSize()
+                + " Inputs " + method.groupsOfOccurrences().count()
+                + " TTL " + method.getBestMetrics().getTtl()
+                + " Saves " + method.getBestMetrics().getSavedTime()
+                + " Hits " + method.getBestMetrics().getHits()
+                + " Computation " + method.getBestMetrics().getComputationTime()
+                + " TimeInCache " + method.getBestMetrics().getTimeInCache()
+                + " Stales " + method.getBestMetrics().getStales()));
     }
 
     private void recommendCacheableInputs() {
@@ -70,14 +74,18 @@ public class TFCache {
         LOGGER.log(Level.INFO, "Removing not reusable methods from {0} methods", this.methods.size());
         this.methods.removeIf(method -> method.groupsOfOccurrences().count() < 1);
         LOGGER.log(Level.INFO, "Recommending TTL to {0} methods", this.methods.size());
-        this.methods.stream().parallel().forEach(Method::recommendTTLPerInput);
+        if (Configuration.getVerbose()) {
+            this.methods.forEach(Method::recommendTTLPerInput);
+        } else {
+            this.methods.stream().parallel().forEach(Method::recommendTTLPerInput);
+        }
         LOGGER.log(Level.INFO, "Removing not recommended inputs from {0}", this.methods.size());
-        this.methods.forEach(Method::removeNotRecommededInputs);
+        this.methods.forEach(Method::removeNotRecommendedInputs);
         LOGGER.log(Level.INFO, "Removing not recommended methods from {0}", this.methods.size());
         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) -> method2.getEstimatedSavedTimePerTimeInCache().compareTo(method1.getEstimatedSavedTimePerTimeInCache()));
+        this.methods.sort((method1, method2) -> Double.compare(method2.getEstimatedSavedTime(), method1.getEstimatedSavedTime()));
         LOGGER.log(Level.INFO, "Printing recommendations for {0} methods", this.methods.size());
         this.methods.forEach(method -> {
             if (method.getBestMetrics() != null) {
@@ -85,32 +93,30 @@ public class TFCache {
                         + " Occurrences " + method.getOccurrencesSize()
                         + " Inputs " + method.groupsOfOccurrences().count()
                         + " TTL " + method.getBestMetrics().getTtl()
-                        + " STpTiC " + method.getBestMetrics().getSavedTimePerTimeInCache()
                         + " Saves " + method.getBestMetrics().getSavedTime()
                         + " Hits " + method.getBestMetrics().getHits()
+                        + " Computation " + method.getBestMetrics().getComputationTime()
+                        + " TimeInCache " + method.getBestMetrics().getTimeInCache()
                         + " Stales " + method.getBestMetrics().getStales());
             } else {
                 System.out.println(method.getName());
             }
             if (Configuration.getVerbose()) {
-                method.groupsOfOccurrences().forEach(group -> {
-                    System.out.println("\t" + group.getParameters().hashCode()
-                            + " Occurrences " + group.getOccurrencesSize()
-                            + " -> TTL " + group.getTtl()
-                            + " STpTiC " + group.getSavedTimePerTimeInCache()
-                            + " Saves " + group.getSavedTime()
-                            + " Hits " + group.getBestMetrics().getHits()
-                            + " Stales " + group.getBestMetrics().getHits());
-                });
+                method.groupsOfOccurrences().forEach(group -> System.out.println("\t" + group.getParameters().hashCode()
+                        + " Occurrences " + group.getOccurrencesSize()
+                        + " -> TTL " + group.getTtl()
+                        + " Saves " + group.getSavedTime()
+                        + " Hits " + group.getBestMetrics().getHits()
+                        + " Computation " + group.getBestMetrics().getComputationTime()
+                        + " TimeInCache " + group.getBestMetrics().getTimeInCache()
+                        + " Stales " + group.getBestMetrics().getHits()));
             }
         });
         try (FileWriter fileWriter = new FileWriter(Configuration.getOutput())) {
             JsonObject jsonCacheableParameters = new JsonObject();
             this.methods.forEach(method -> {
                 JsonObject cacheableParameters = new JsonObject();
-                method.groupsOfOccurrences().forEach(group -> {
-                    cacheableParameters.addProperty(group.getParameters(), group.getTtl());
-                });
+                method.groupsOfOccurrences().forEach(group -> cacheableParameters.addProperty(group.getParameters(), group.getTtl()));
                 jsonCacheableParameters.add(method.getName(), cacheableParameters);
             });
             Gson gson = new GsonBuilder().setPrettyPrinting().create();
@@ -121,6 +127,9 @@ public class TFCache {
     }
 
     public void recommend() {
+        if (Configuration.getVerbose()) {
+            Configuration.setInput(Arrays.stream(Configuration.getInput().split("/")).reduce((a, b) -> b).orElse(""));
+        }
         if (Configuration.getLevel().equals("method")) {
             recommendCacheableMethods();
         } else {