tfcache
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);
+ }
+ }
+
+}