tfcache

first commit. added tf cache with hitspertimeincache and sorting

4/17/2020 4:23:32 AM

Details

.gitignore 34(+34 -0)

diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..4a482b8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+# ignore eclipse project files
+.project
+.classpath
+
+# ignore Intellij Idea project files
+.idea
+*.iml
+
+# Netbeans
+/nbproject/
+/nbactions.xml
+
+# Netbeans deploy
+/build/
+
+# Maven deploy
+/target/
+
+# Ant deploy
+/dist/
+
+# Class files
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*

pom.xml 59(+59 -0)

diff --git a/pom.xml b/pom.xml
new file mode 100755
index 0000000..b0a537d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>br.ufrgs.inf.prosoft.tfcache</groupId>
+    <artifactId>TFCache</artifactId>
+    <version>1.0</version>
+    <packaging>jar</packaging>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>br.ufrgs.inf.prosoft.trace</groupId>
+            <artifactId>Trace</artifactId>
+            <version>1.0</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>
+                                ${project.build.directory}/lib
+                            </outputDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>3.1.0</version>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <addClasspath>true</addClasspath>
+                            <classpathPrefix>lib</classpathPrefix> 
+                            <mainClass>br.ufrgs.inf.prosoft.tfcache.Main</mainClass>
+                        </manifest>
+                        <manifestEntries>
+                            <Class-Path>lib/</Class-Path>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/adapter/TraceReader.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/adapter/TraceReader.java
new file mode 100755
index 0000000..c106366
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/adapter/TraceReader.java
@@ -0,0 +1,67 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package br.ufrgs.inf.prosoft.tfcache.adapter;
+
+import br.ufrgs.inf.prosoft.tfcache.metadata.Method;
+import br.ufrgs.inf.prosoft.tfcache.metadata.Occurrence;
+import br.ufrgs.inf.prosoft.tfcache.metadata.OccurrenceConcrete;
+import br.ufrgs.inf.prosoft.tfcache.metadata.OccurrenceReference;
+import br.ufrgs.inf.prosoft.trace.Parameter;
+import br.ufrgs.inf.prosoft.trace.Trace;
+import br.ufrgs.inf.prosoft.trace.TraceReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * @author romulo
+ */
+public class TraceReader extends br.ufrgs.inf.prosoft.trace.reader.TraceReader {
+
+    private static final Logger LOGGER = Logger.getLogger(TraceReader.class.getName());
+
+    public static List<Method> groupByMethods(List<Trace> traces) {
+        Map<String, List<Occurrence>> methodHasOccurrences = new HashMap<>();
+        while (!traces.isEmpty()) {
+            Trace trace = traces.remove(0);
+            try {
+                Occurrence occurrence;
+                if (trace instanceof TraceReference) {
+                    TraceReference traceReference = (TraceReference) trace;
+                    occurrence = new OccurrenceReference(traceReference.getIndex(), traceReference.getInstance(), trace.getStartTime(), trace.getEndTime(), trace.getUserSession());
+                } else {
+                    occurrence = new OccurrenceConcrete(trace.getInstance(),
+                            trace.getParameters().stream()
+                                    .map(Parameter::getData)
+                                    .collect(Collectors.toList()).toArray(),
+                            trace.getReturn().getData(),
+                            trace.getStartTime(),
+                            trace.getEndTime(),
+                            trace.getUserSession()
+                    );
+                }
+                try {
+                    methodHasOccurrences.get(trace.getName()).add(occurrence);
+                } catch (Exception ex) {
+                    List<Occurrence> occurrences = new ArrayList<>();
+                    occurrences.add(occurrence);
+                    methodHasOccurrences.put(trace.getName(), occurrences);
+                }
+            } catch (Exception e) {
+                LOGGER.log(Level.INFO, "Trace discarted: {0}", trace);
+            }
+        }
+        return methodHasOccurrences.entrySet().stream()
+                .map(entry -> new Method(entry.getKey(), entry.getValue()))
+                .collect(Collectors.toList());
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java
new file mode 100755
index 0000000..ab6e6fd
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Main.java
@@ -0,0 +1,77 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package br.ufrgs.inf.prosoft.tfcache;
+
+import br.ufrgs.inf.prosoft.tfcache.adapter.TraceReader;
+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;
+
+/**
+ *
+ * @author romulo
+ */
+public class Main {
+
+    private static final Logger LOGGER = Logger.getLogger(Main.class.getName());
+
+    public static void main(String[] args) {
+        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>");
+            System.err.println("--trace=<TracePath> --level=<method|input> [--output=<outputPath>] [--changeability=<true|false>]");
+            System.err.println("--trace=<TracePath> --level=input --output=<outputPath>");
+            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) {
+            System.err.println("<TracePath> is required");
+            System.exit(1);
+        }
+        String level = arguments.get("level");
+        if (level == null) {
+            level = "method";
+            LOGGER.log(Level.INFO, "Using default level: {0}", level);
+        }
+        String changeability = arguments.get("changeability");
+        if (changeability == null) {
+            changeability = "false";
+            LOGGER.log(Level.INFO, "Using default changeability: {0}", changeability);
+        }
+        String output = arguments.get("output");
+        if (level.equals("input") && output == null) {
+            System.err.println("outputPath is required for input-level recommendations");
+            System.exit(1);
+        }
+
+        LOGGER.log(Level.INFO, "Reading traces");
+        List<Trace> traces = TraceReader.parseFile(tracePath);
+        LOGGER.log(Level.INFO, "Grouping {0} traces by methods", traces.size());
+        List<Method> methods = TraceReader.groupByMethods(traces);
+        TFCache tfCache = new TFCache(methods);
+        tfCache.recommend(changeability.equals("true"), output);
+    }
+}
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
new file mode 100755
index 0000000..4bc3a88
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/GroupOfOccurrences.java
@@ -0,0 +1,93 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package br.ufrgs.inf.prosoft.tfcache.metadata;
+
+import br.ufrgs.inf.prosoft.tfcache.Metrics;
+import br.ufrgs.inf.prosoft.tfcache.Simulator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+/**
+ *
+ * @author romulo
+ */
+public class GroupOfOccurrences {
+
+    private static final Logger LOGGER = Logger.getLogger(GroupOfOccurrences.class.getName());
+
+    private final List<Occurrence> occurrences;
+    private final String parameters;
+
+    private Metrics bestMetrics;
+
+    public GroupOfOccurrences(String parameters, List<Occurrence> occurrences) {
+        this.parameters = parameters;
+        this.occurrences = occurrences;
+    }
+
+    public String getParameters() {
+        return this.parameters;
+    }
+
+    public int getOccurrencesSize() {
+        return this.occurrences.size();
+    }
+
+    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 Double getHitsPerTimeInCache() {
+        return getBestMetrics().getHitsPerTimeInCache();
+    }
+
+    public Stream<Occurrence> occurrences() {
+        return this.occurrences.stream();
+    }
+
+    public boolean isChangeable() {
+        Iterator<Occurrence> iterator = this.occurrences.iterator();
+        Occurrence first = iterator.next();
+        while (iterator.hasNext()) {
+            Occurrence next = iterator.next();
+            if (!Objects.deepEquals(first.getReturnValue(), next.getReturnValue())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void calculateHitsPerTimeInCache() {
+        if (this.bestMetrics != null) {
+            LOGGER.log(Level.WARNING, "HitsPerTimeInCache already calculated");
+        }
+        if (this.occurrences.size() < 2) {
+            throw new RuntimeException("Not reusable input");
+        }
+        long maxTTL = this.occurrences.get(this.occurrences.size() - 1).getStartTime() - this.occurrences.get(0).getStartTime();
+        this.bestMetrics = new Metrics();
+        LongStream.range(1, maxTTL).parallel().forEach(actualTTL -> {
+            Simulator.simulate(occurrences(), actualTTL, this.bestMetrics);
+        });
+    }
+
+}
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
new file mode 100755
index 0000000..1c5c3d8
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Method.java
@@ -0,0 +1,224 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package br.ufrgs.inf.prosoft.tfcache.metadata;
+
+import br.ufrgs.inf.prosoft.tfcache.Metrics;
+import br.ufrgs.inf.prosoft.tfcache.Simulator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+/**
+ *
+ * @author romulo
+ */
+public class Method {
+
+    private static final Logger LOGGER = Logger.getLogger(Method.class.getName());
+    private static final boolean TRACER_VERBOSE = System.getenv("TRACER_VERBOSE") != null && System.getenv("TRACER_VERBOSE").equals("true");
+
+    private final String name;
+    private List<Occurrence> occurrences;
+    private List<GroupOfOccurrences> groupsOfOccurrences;
+
+    private Integer countChangeableGroups;
+
+    private Metrics bestMetrics;
+
+    public Method(String name, List<Occurrence> occurrences) {
+        this.name = name;
+        if (occurrences == null) {
+            throw new RuntimeException("Occurrences is null");
+        }
+        if (occurrences.isEmpty()) {
+            throw new RuntimeException("Occurrences is empty");
+        }
+        this.occurrences = occurrences;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    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 Double getHitsPerTimeInCache() {
+        return getBestMetrics().getHitsPerTimeInCache();
+    }
+
+    public Stream<Occurrence> occurrences() {
+        if (this.occurrences == null) {
+            throw new RuntimeException("Occurrences already consumed");
+        }
+        return this.occurrences.stream();
+    }
+
+    public int getOccurrencesSize() {
+        if (this.occurrences == null) {
+            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.occurrences.size();
+    }
+
+    public Stream<GroupOfOccurrences> groupsOfOccurrences() {
+        if (this.groupsOfOccurrences == null) {
+            groupByInput();
+        }
+        return this.groupsOfOccurrences.stream();
+    }
+
+    private void groupByInput() {
+        if (this.occurrences == null) {
+            throw new RuntimeException("Occurrences already consumed");
+        }
+        Map<String, List<Occurrence>> inputHasOccurrences = new ConcurrentHashMap<>();
+        this.occurrences.stream().forEach(occurrence -> {
+            String parameters = occurrence.getParametersSerialised();
+
+            inputHasOccurrences.compute(parameters, (key, oldValue) -> {
+                if (oldValue == null) {
+                    oldValue = new ArrayList<>();
+                }
+                oldValue.add(occurrence);
+                return oldValue;
+            });
+        });
+        this.groupsOfOccurrences = inputHasOccurrences.entrySet().stream()
+                .map(entry -> new GroupOfOccurrences(entry.getKey(), entry.getValue()))
+                .collect(Collectors.toList());
+        this.occurrences = null;
+    }
+
+    public void removeChangeableInputs() {
+        if (this.countChangeableGroups != null) {
+            throw new RuntimeException("Changeable already filtered");
+        }
+        if (this.groupsOfOccurrences == null) {
+            groupByInput();
+        }
+        int initialCountOfInputs = this.groupsOfOccurrences.size();
+        this.groupsOfOccurrences.removeIf(GroupOfOccurrences::isChangeable);
+        this.countChangeableGroups = initialCountOfInputs - this.groupsOfOccurrences.size();
+        if (this.countChangeableGroups > 0) {
+            LOGGER.log(Level.INFO, "\tRemoved {0} of {1} changeable inputs from method {2}", new Object[]{this.countChangeableGroups, initialCountOfInputs, this.name});
+        }
+    }
+
+    public boolean isChangeable() {
+        if (this.countChangeableGroups != null) {
+            return this.countChangeableGroups > 0;
+        }
+        if (this.groupsOfOccurrences != null) {
+            return this.groupsOfOccurrences.stream().anyMatch(GroupOfOccurrences::isChangeable);
+        }
+        Map<String, Object> inputHasOutput = new HashMap<>();
+        for (Occurrence occurrence : this.occurrences) {
+            String parameters = occurrence.getParametersSerialised();
+            if (!inputHasOutput.containsKey(parameters)) {
+                inputHasOutput.put(parameters, occurrence.getReturnValue());
+            } else if (!Objects.deepEquals(inputHasOutput.get(parameters), occurrence.getReturnValue())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void removeSingleOccurrences() {
+        if (this.groupsOfOccurrences == null) {
+            groupByInput();
+        }
+        int initialCountofOccurrences = this.groupsOfOccurrences.size();
+        this.groupsOfOccurrences.removeIf(groupOfOccurrences -> groupOfOccurrences.getOccurrencesSize() < 2);
+        LOGGER.log(Level.INFO, "\tKept {0} of {1} inputs from method {2}", new Object[]{this.groupsOfOccurrences.size(), initialCountofOccurrences, this.name});
+    }
+
+    public boolean isReusable() {
+        if (this.groupsOfOccurrences != null) {
+            return this.groupsOfOccurrences.stream().anyMatch(groupOfOccurrences -> groupOfOccurrences.getOccurrencesSize() > 1);
+        }
+        Map<String, Long> inputHasFrequency = occurrences()
+                .map(Occurrence::getParametersSerialised)
+                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
+        return inputHasFrequency.values().stream().anyMatch(frequency -> frequency > 1);
+
+    }
+
+    public boolean isNotReusable() {
+        return !isReusable();
+    }
+
+    public void recommendTTL() {
+        if (this.bestMetrics != null) {
+            LOGGER.log(Level.WARNING, "HitsPerTimeInCache already calculated");
+        }
+        if (this.occurrences == null) {
+            throw new RuntimeException("groupByInputs called. Cannot proceed");
+        }
+        if (this.occurrences.size() < 2) {
+            throw new RuntimeException("Not reusable method");
+        }
+        long maxTTL = this.occurrences.get(this.occurrences.size() - 1).getStartTime() - this.occurrences.get(0).getStartTime();
+
+        this.bestMetrics = new Metrics();
+        LongStream.range(1, maxTTL).parallel().forEach(actualTTL -> {
+            Simulator.simulate(occurrences(), actualTTL, this.bestMetrics);
+        });
+    }
+
+    public void recommendTTLPerInput() {
+        if (this.bestMetrics != null) {
+            throw new RuntimeException("ttl already computed");
+        }
+        if (this.groupsOfOccurrences == null) {
+            groupByInput();
+            removeSingleOccurrences();
+        }
+        groupsOfOccurrences().forEach(GroupOfOccurrences::calculateHitsPerTimeInCache);
+        long savedTime = groupsOfOccurrences().map(GroupOfOccurrences::getSavedTime).reduce(Long::sum).get();
+        GroupOfOccurrences max = groupsOfOccurrences().max((group1, group2) -> Long.compare(group1.getSavedTime(), group2.getSavedTime())).get();
+        long ttl = max.getTtl();
+        double hitsPerTimeInCache = max.getHitsPerTimeInCache();
+        this.bestMetrics = new Metrics(ttl, savedTime, hitsPerTimeInCache);
+    }
+
+    public void rankRecommendations() {
+        if (this.groupsOfOccurrences == null) {
+            throw new RuntimeException("groupsOfOccurrences is null");
+        }
+        this.groupsOfOccurrences.sort((occurrence1, occurrence2) -> Long.compare(occurrence2.getSavedTime(), occurrence1.getSavedTime()));
+    }
+
+    @Override
+    public String toString() {
+        return this.name;
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Occurrence.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Occurrence.java
new file mode 100755
index 0000000..65e80df
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/Occurrence.java
@@ -0,0 +1,48 @@
+package br.ufrgs.inf.prosoft.tfcache.metadata;
+
+import com.google.gson.Gson;
+
+public abstract class Occurrence {
+
+    private final String instance;
+    private final long startTime;
+    private final long endTime;
+    private final String userId;
+
+    public Occurrence(String instance, long startTime, long endTime, String userId) {
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.userId = userId;
+        this.instance = instance;
+    }
+
+    public String getInstance() {
+        return instance;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+
+    public long getExecutionTime() {
+        return endTime - startTime;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public abstract Object[] getParameters();
+
+    public abstract Object getReturnValue();
+
+    public String getParametersSerialised() {
+        Gson gson = new Gson();
+        return gson.toJson(getParameters());
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/OccurrenceConcrete.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/OccurrenceConcrete.java
new file mode 100755
index 0000000..a73eae9
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/OccurrenceConcrete.java
@@ -0,0 +1,42 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package br.ufrgs.inf.prosoft.tfcache.metadata;
+
+/**
+ *
+ * @author romulo
+ */
+public class OccurrenceConcrete extends Occurrence {
+
+    private final Object[] parameters;
+    private final Object returnValue;
+    private String parametersSerialiased;
+
+    public OccurrenceConcrete(String instance, Object[] parameters, Object returnedValue, long startTime, long endTime, String userId) {
+        super(instance, startTime, endTime, userId);
+        this.parameters = parameters;
+        this.returnValue = returnedValue;
+    }
+
+    @Override
+    public Object[] getParameters() {
+        return this.parameters;
+    }
+
+    @Override
+    public Object getReturnValue() {
+        return this.returnValue;
+    }
+
+    @Override
+    public String getParametersSerialised() {
+        if (this.parametersSerialiased == null) {
+            this.parametersSerialiased = super.getParametersSerialised();
+        }
+        return this.parametersSerialiased;
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/OccurrenceReference.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/OccurrenceReference.java
new file mode 100755
index 0000000..317b35a
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/metadata/OccurrenceReference.java
@@ -0,0 +1,41 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package br.ufrgs.inf.prosoft.tfcache.metadata;
+
+import br.ufrgs.inf.prosoft.trace.Parameter;
+import br.ufrgs.inf.prosoft.trace.Return;
+import br.ufrgs.inf.prosoft.trace.reader.Traces;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * @author romulo
+ */
+public class OccurrenceReference extends Occurrence {
+
+    private final int index;
+
+    public OccurrenceReference(int index, String instance, long startTime, long endTime, String userId) {
+        super(instance, startTime, endTime, userId);
+        this.index = index;
+    }
+
+    @Override
+    public Object[] getParameters() {
+        List<Parameter> parameters = Traces.getTraceParameter(this.index);
+        return parameters.stream()
+                .map(Parameter::getData)
+                .collect(Collectors.toList())
+                .toArray();
+    }
+
+    @Override
+    public Object getReturnValue() {
+        Return returnValue = Traces.getTraceReturn(this.index);
+        return returnValue == null ? null : returnValue.getData();
+    }
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
new file mode 100644
index 0000000..7671a73
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Metrics.java
@@ -0,0 +1,77 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package br.ufrgs.inf.prosoft.tfcache;
+
+/**
+ *
+ * @author romulo
+ */
+public class Metrics implements Comparable<Metrics> {
+
+    private long ttl;
+    private long savedTime;
+    private double hitsPerTimeInCache;
+
+    public Metrics() {
+    }
+
+    public Metrics(long ttl, long savedTime, double hitsPerTimeInCache) {
+        this.ttl = ttl;
+        this.savedTime = savedTime;
+        this.hitsPerTimeInCache = hitsPerTimeInCache;
+    }
+
+    public long getTtl() {
+        return this.ttl;
+    }
+
+    public long getSavedTime() {
+        return this.savedTime;
+    }
+
+    public double getHitsPerTimeInCache() {
+        return this.hitsPerTimeInCache;
+    }
+
+    public synchronized void keepBestMetrics(long ttl, long hits, long timeInCache, long savedTime) {
+        if (hits == 0) {
+            return;
+        }
+        double hitsPerTimeInCache = (double) (hits) / timeInCache;
+        if (this.ttl == 0
+                || this.hitsPerTimeInCache < hitsPerTimeInCache
+                || (this.hitsPerTimeInCache == hitsPerTimeInCache && this.savedTime < savedTime)
+                || (this.hitsPerTimeInCache == hitsPerTimeInCache && this.savedTime == savedTime && this.ttl > ttl)) {
+            this.ttl = ttl;
+            this.savedTime = savedTime;
+            this.hitsPerTimeInCache = hitsPerTimeInCache;
+        }
+    }
+
+    @Override
+    public int compareTo(Metrics other) {
+        if (this.hitsPerTimeInCache > other.hitsPerTimeInCache) {
+            return 1;
+        }
+        if (this.hitsPerTimeInCache < other.hitsPerTimeInCache) {
+            return -1;
+        }
+        if (this.savedTime > other.savedTime) {
+            return 1;
+        }
+        if (this.savedTime < other.savedTime) {
+            return -1;
+        }
+        if (this.ttl < other.ttl) {
+            return 1;
+        }
+        if (this.ttl > other.ttl) {
+            return -1;
+        }
+        return 0;
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
new file mode 100644
index 0000000..a573181
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/Simulator.java
@@ -0,0 +1,60 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package br.ufrgs.inf.prosoft.tfcache;
+
+import br.ufrgs.inf.prosoft.tfcache.metadata.Occurrence;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.stream.Stream;
+
+/**
+ *
+ * @author romulo
+ */
+public class Simulator {
+
+    private final Metrics metrics;
+
+    public Simulator() {
+        this.metrics = new Metrics();
+    }
+
+    public Metrics getMetrics() {
+        return metrics;
+    }
+
+    public void simulate(Stream<Occurrence> occurrences, long ttl) {
+        simulate(occurrences, ttl, this.metrics);
+    }
+
+    public static void simulate(Stream<Occurrence> occurrences, long ttl, Metrics metrics) {
+        Map<String, Long> inputHasCachedTime = new HashMap<>();
+
+        long actualSavedTime = 0;
+        long actualHits = 0;
+        long actualTimeInCache = 0;
+
+        Iterator<Occurrence> iterator = occurrences.iterator();
+        while (iterator.hasNext()) {
+            Occurrence occurrence = iterator.next();
+            long adjustedStartTime = occurrence.getStartTime() - actualSavedTime;
+            long adjustedEndTime = occurrence.getEndTime() - actualSavedTime;
+            if (inputHasCachedTime.containsKey(occurrence.getParametersSerialised()) && adjustedStartTime - inputHasCachedTime.get(occurrence.getParametersSerialised()) > ttl) {
+                inputHasCachedTime.remove(occurrence.getParametersSerialised());
+            }
+            if (inputHasCachedTime.containsKey(occurrence.getParametersSerialised())) {
+                actualSavedTime += occurrence.getExecutionTime();
+                actualHits++;
+            } else {
+                inputHasCachedTime.put(occurrence.getParametersSerialised(), adjustedEndTime);
+                actualTimeInCache += ttl;
+            }
+        }
+        metrics.keepBestMetrics(ttl, actualHits, actualTimeInCache, actualSavedTime);
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java b/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java
new file mode 100755
index 0000000..22342c6
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/tfcache/TFCache.java
@@ -0,0 +1,110 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package br.ufrgs.inf.prosoft.tfcache;
+
+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.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+
+/**
+ *
+ * @author romulo
+ */
+public class TFCache {
+
+    private static final Logger LOGGER = Logger.getLogger(TFCache.class.getName());
+    private final List<Method> methods;
+
+    public TFCache(List<Method> methods) {
+        this.methods = methods;
+    }
+
+    public Stream<Method> methods() {
+        return this.methods.stream();
+    }
+
+    private void recommendCacheableMethods(boolean changeable) {
+        if (!changeable) {
+            LOGGER.log(Level.INFO, "Removing changeable from {0} methods", this.methods.size());
+            this.methods.removeIf(Method::isChangeable);
+        }
+        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.forEach(Method::recommendTTL);
+        LOGGER.log(Level.INFO, "Ranking {0} methods according saved time", this.methods.size());
+        this.methods.sort((method1, method2) -> Long.compare(method2.getSavedTime(), method1.getSavedTime()));
+        LOGGER.log(Level.INFO, "Printing recommendations for {0} methods", this.methods.size());
+        this.methods.forEach(method -> {
+            System.out.println(method.getName() + " -> TTL " + method.getBestMetrics().getTtl() + " saves " + method.getSavedTime());
+        });
+    }
+
+    private void recommendCacheableInputs(boolean changeable, String outputPath) {
+        if (!changeable) {
+            LOGGER.log(Level.INFO, "Removing changeable inputs from {0} methods", this.methods.size());
+            this.methods.forEach(Method::removeChangeableInputs);
+        }
+        LOGGER.log(Level.INFO, "Removing not reusable inputs from {0} methods", this.methods.size());
+        this.methods.forEach(Method::removeSingleOccurrences);
+        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.forEach(Method::recommendTTLPerInput);
+        LOGGER.log(Level.INFO, "Ranking {0} methods according saved time", this.methods.size());
+        this.methods.forEach(Method::rankRecommendations);
+        this.methods.sort((method1, method2) -> Long.compare(method2.getSavedTime(), method1.getSavedTime()));
+        LOGGER.log(Level.INFO, "Printing recommendations for {0} methods", this.methods.size());
+        this.methods.forEach(method -> {
+            System.out.println(method.getName() + " TTL " + method.getTtl() + " saves " + method.getSavedTime());
+            method.groupsOfOccurrences().forEach(occurrence -> {
+                System.out.println("\t" + occurrence.getParameters().hashCode() + " -> TTL " + occurrence.getTtl() + " saves " + occurrence.getSavedTime());
+            });
+        });
+        try (FileWriter fileWriter = new FileWriter(outputPath)) {
+            JsonObject jsonCacheableParameters = new JsonObject();
+            this.methods.forEach(method -> {
+                JsonObject cacheableParameters = new JsonObject();
+                method.groupsOfOccurrences().forEach(group -> {
+                    cacheableParameters.addProperty(group.getParameters(), group.getTtl());
+                });
+                jsonCacheableParameters.add(method.getName(), cacheableParameters);
+            });
+            Gson gson = new GsonBuilder().setPrettyPrinting().create();
+            gson.toJson(jsonCacheableParameters, fileWriter);
+        } catch (IOException ex) {
+            LOGGER.log(Level.SEVERE, "invalid <outputPath>");
+        }
+    }
+
+    public void recommend() {
+        recommend(false);
+    }
+
+    public void recommend(String outputPath) {
+        recommend(false, outputPath);
+    }
+
+    public void recommend(boolean changeable) {
+        recommendCacheableMethods(changeable);
+    }
+
+    public void recommend(boolean changeable, String outputPath) {
+        if (outputPath == null) {
+            recommend(changeable);
+        } else {
+            recommendCacheableInputs(changeable, outputPath);
+        }
+    }
+
+}