aplcachetf

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 75(+75 -0)

diff --git a/pom.xml b/pom.xml
new file mode 100755
index 0000000..0c0f4d9
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,75 @@
+<?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>
+    <name>APLCacheTF</name>
+    <groupId>br.ufrgs.inf.prosoft.aplcachetf</groupId>
+    <artifactId>APLCacheTF</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>
+        <dependency>
+            <groupId>br.ufrgs.inf.prosoft.cache</groupId>
+            <artifactId>Cache</artifactId>
+            <version>1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>br.ufrgs.inf.prosoft.tfcache</groupId>
+            <artifactId>TFCache</artifactId>
+            <version>1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>br.ufrgs.inf.prosoft.aplcache</groupId>
+            <artifactId>APLCache</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.aplcachetf.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/aplcachetf/adapter/TraceReader.java b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/adapter/TraceReader.java
new file mode 100644
index 0000000..d1f63d4
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/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.aplcachetf.adapter;
+
+import br.ufrgs.inf.prosoft.aplcachetf.extension.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/aplcachetf/extension/APLCache.java b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/APLCache.java
new file mode 100644
index 0000000..cf68fd3
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/APLCache.java
@@ -0,0 +1,112 @@
+/*
+ * 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.aplcachetf.extension;
+
+import br.ufrgs.inf.prosoft.aplcachetf.extension.metadata.Method;
+import br.ufrgs.inf.prosoft.aplcachetf.extension.metrics.CacheabilityMetrics;
+import br.ufrgs.inf.prosoft.aplcachetf.extension.metrics.Thresholds;
+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.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author root
+ */
+public class APLCache {
+
+    private static final Logger LOGGER = Logger.getLogger(APLCache.class.getName());
+
+    private final List<Method> methods;
+
+    public APLCache(List<Method> methods) {
+        this.methods = methods;
+    }
+
+    private void calculateMetrics() {
+        LOGGER.log(Level.INFO, "Calculating metrics for {0} methods...", this.methods.size());
+        this.methods.stream().parallel().forEach(Method::calculateMetrics);
+    }
+
+    private void calculateThresholds(int kStdDev) {
+        LOGGER.log(Level.INFO, "Calculating thresholds for {0} methods with {1} stdDev...", new Object[]{this.methods.size(), kStdDev});
+
+        Thresholds.reset();
+        Thresholds.population = getPopulation();
+        this.methods.stream().forEach(Method::calculateThresholds);
+
+        LOGGER.log(Level.INFO, "\tAverage   ExecutionTime:      {0}", Thresholds.getAverageExecutionTime());
+        LOGGER.log(Level.INFO, "\tStdDv     ExecutionTime:      {0}", Thresholds.getStdDevExecutionTimeRatio());
+        LOGGER.log(Level.INFO, "\tThreshold ExecutionTime:      {0}", Thresholds.expensivenessThreshold(kStdDev));
+        LOGGER.log(Level.INFO, "\tAverage   HitRatio:           {0}", Thresholds.getAverageHitRatio());
+        LOGGER.log(Level.INFO, "\tStdDv     HitRatio:           {0}", Thresholds.getStdDevHitRatio());
+        LOGGER.log(Level.INFO, "\tThreshold HitRatio:           {0}", Thresholds.hitThreshold(kStdDev));
+        LOGGER.log(Level.INFO, "\tAverage   MissRatio:          {0}", Thresholds.getAverageMissRatio());
+        LOGGER.log(Level.INFO, "\tStdDv     MissRatio:          {0}", Thresholds.getStdDevMissRatio());
+        LOGGER.log(Level.INFO, "\tThreshold MissRatio:          {0}", Thresholds.missThreshold(kStdDev));
+        LOGGER.log(Level.INFO, "\tAverage   Shareability:       {0}", Thresholds.getAverageShareability());
+        LOGGER.log(Level.INFO, "\tStdDv     Shareability:       {0}", Thresholds.getStdDevShareability());
+        LOGGER.log(Level.INFO, "\tThreshold Shareability:       {0}", Thresholds.shareabilityThreshold(kStdDev));
+        LOGGER.log(Level.INFO, "\tAverage   HitsPerTimeInCache: {0}", Thresholds.getAverageHitsPerTimeInCache());
+        LOGGER.log(Level.INFO, "\tStdDv     HitsPerTimeInCache: {0}", Thresholds.getStdDevHitsPerTimeInCache());
+        LOGGER.log(Level.INFO, "\tThreshold HitsPerTimeInCache: {0}", Thresholds.hitsPerTimeInCacheThreshold(kStdDev));
+    }
+
+    private void filterUncacheableInputs(int kStdDev) {
+        LOGGER.log(Level.INFO, "Filtering inputs of {0} methods under {1} stdDev threshold...", new Object[]{this.methods.size(), kStdDev});
+        CacheabilityMetrics.K_STANDARD_DEVIATION = kStdDev;
+        this.methods.forEach(Method::filterCacheableInputs);
+
+        int initialMethodsSize = this.methods.size();
+        this.methods.removeIf(method -> method.groupsOfOccurrences().count() == 0);
+        int removedMethods = initialMethodsSize - this.methods.size();
+        if (removedMethods > 0) {
+            LOGGER.log(Level.INFO, "Removed {0} of {1} methods with no inputs recommemded", new Object[]{removedMethods, initialMethodsSize});
+        }
+    }
+
+    public void recommend(String outputPath) {
+        recommend(0, outputPath);
+    }
+
+    public void recommend(int kStdDev, String outputPath) {
+        calculateMetrics();
+        calculateThresholds(kStdDev);
+        filterUncacheableInputs(kStdDev);
+
+        LOGGER.log(Level.INFO, "{0} cacheable methods detected", this.methods.size());
+        Collections.sort(this.methods, (m1, m2) -> Long.compare(m2.getSavedTime(), m1.getSavedTime()));
+
+        this.methods.forEach(method -> {
+            System.out.println(method.getName() + " Occurrences " + method.getOccurrencesSize() + " Inputs " + method.groupsOfOccurrences().count() + " TTL " + method.getTtl() + " HpTiC " + method.getHitsPerTimeInCache() + " Saves " + method.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>");
+        }
+    }
+
+    private long getPopulation() {
+        return this.methods.stream().map(Method::getOccurrencesSize).reduce(Integer::sum).orElse(0);
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metadata/GroupOfOccurrences.java b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metadata/GroupOfOccurrences.java
new file mode 100644
index 0000000..9719bbd
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metadata/GroupOfOccurrences.java
@@ -0,0 +1,120 @@
+/*
+ * 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.aplcachetf.extension.metadata;
+
+import br.ufrgs.inf.prosoft.aplcachetf.extension.metrics.Thresholds;
+import br.ufrgs.inf.prosoft.aplcachetf.extension.metrics.Metrics;
+import br.ufrgs.inf.prosoft.tfcache.Simulator;
+import br.ufrgs.inf.prosoft.tfcache.metadata.Occurrence;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+
+/**
+ *
+ * @author root
+ */
+public class GroupOfOccurrences {
+
+    private static final Logger LOGGER = Logger.getLogger(GroupOfOccurrences.class.getName());
+
+    private final String parameters;
+    private final List<Occurrence> occurrences;
+    private Metrics bestMetrics;
+
+    public GroupOfOccurrences(String parameters, List<Occurrence> occurrences) {
+        this.parameters = parameters;
+        this.occurrences = occurrences;
+    }
+
+    public String getParameters() {
+        return this.parameters;
+    }
+
+    public Stream<Occurrence> occurrences() {
+        return this.occurrences.stream();
+    }
+
+    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 void calculateHitsPerTimeInCache() {
+        if (this.bestMetrics != null) {
+            LOGGER.log(Level.WARNING, "HitsPerTimeInCache already calculated");
+        }
+        this.bestMetrics = new Metrics();
+        if (this.occurrences.size() < 2) {
+            return;
+        }
+        long maxTTL = this.occurrences.get(this.occurrences.size() - 1).getStartTime() - this.occurrences.get(0).getStartTime();
+        LongStream.range(1, maxTTL).parallel().forEach(actualTTL -> {
+            Simulator.simulate(occurrences(), actualTTL, this.bestMetrics);
+        });
+    }
+
+    protected void calculateMetrics() {
+        calculateHitsPerTimeInCache();
+        if (this.occurrences.size() == 1) {
+            this.bestMetrics.addSameOccurrence(this.occurrences.get(0));
+            return;
+        }
+        for (int i = 0; i < this.occurrences.size(); i++) {
+            Occurrence occurrence = this.occurrences.get(i);
+            Object returnValue = occurrence.getReturnValue();
+            for (int j = i + 1; j < this.occurrences.size(); j++) {
+                Occurrence other = this.occurrences.get(j);
+                if (EqualsBuilder.reflectionEquals(returnValue, other.getReturnValue())) {
+                    this.bestMetrics.addSameOccurrence(occurrence);
+                    continue;
+                }
+                this.bestMetrics.addDifferentReturnOccurrence();
+            }
+        }
+    }
+
+    protected void calculateThresholds() {
+        Thresholds.sumExecutionTime += this.bestMetrics.getSameOccurrencesTotalExecutionTime();
+        Thresholds.executionTimes.add(this.bestMetrics.getSameOccurrencesTotalExecutionTime());
+
+        Thresholds.sumHitRatio += this.bestMetrics.getHitRatio();
+        Thresholds.hitRatios.add(this.bestMetrics.getHitRatio());
+
+        Thresholds.sumMissRatio += this.bestMetrics.getMissRatio();
+        Thresholds.missRatios.add(this.bestMetrics.getMissRatio());
+
+        Thresholds.sumShareability += this.bestMetrics.getShareability();
+        Thresholds.shareabilities.add(this.bestMetrics.getShareability());
+
+        if (this.bestMetrics.getHitsPerTimeInCache() > 0) {
+            Thresholds.sumHitsPerTimeInCache += this.bestMetrics.getHitsPerTimeInCache();
+            Thresholds.hitsPerTimeInCache.add(this.bestMetrics.getHitsPerTimeInCache());
+        }
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metadata/Method.java b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metadata/Method.java
new file mode 100644
index 0000000..6a9af4d
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metadata/Method.java
@@ -0,0 +1,148 @@
+/*
+ * 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.aplcachetf.extension.metadata;
+
+import br.ufrgs.inf.prosoft.aplcachetf.extension.metrics.CacheabilityPatternDecider;
+import br.ufrgs.inf.prosoft.tfcache.Metrics;
+import br.ufrgs.inf.prosoft.tfcache.metadata.Occurrence;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ *
+ * @author root
+ */
+public class Method {
+
+    private static final Logger LOGGER = Logger.getLogger(Method.class.getName());
+
+    private final String name;
+    private List<Occurrence> occurrences;
+    private List<GroupOfOccurrences> groupsOfOccurrences;
+    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 this.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 Stream<GroupOfOccurrences> groupsOfOccurrences() {
+        if (this.groupsOfOccurrences == null) {
+            groupByInput();
+        }
+        return this.groupsOfOccurrences.stream();
+    }
+
+    public int getOccurrencesSize() {
+        if (this.occurrences == null) {
+            if (this.groupsOfOccurrences == null) {
+                throw new RuntimeException("groupsOfOccurrences is null");
+            }
+            return this.groupsOfOccurrences.stream().map(GroupOfOccurrences::getOccurrencesSize).reduce(Integer::sum).orElse(0);
+        }
+        return this.occurrences.size();
+    }
+
+    private void groupByInput() {
+        if (this.occurrences == null) {
+            throw new RuntimeException("Occurrences already consumed");
+        }
+        Map<String, List<Occurrence>> inputHasOccurrences = new ConcurrentHashMap<>();
+        occurrences().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 calculateMetrics() {
+        if (this.groupsOfOccurrences == null) {
+            groupByInput();
+        }
+        this.groupsOfOccurrences.stream().forEach(GroupOfOccurrences::calculateMetrics);
+        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 calculateThresholds() {
+        this.groupsOfOccurrences.stream().forEach(GroupOfOccurrences::calculateThresholds);
+    }
+
+    public void filterCacheableInputs() {
+        int initialInputsSize = this.groupsOfOccurrences.size();
+        this.groupsOfOccurrences.removeIf(CacheabilityPatternDecider::isNotCacheable);
+        int removedInputs = initialInputsSize - this.groupsOfOccurrences.size();
+        if (removedInputs > 0) {
+            LOGGER.log(Level.INFO, "\tRemoved {0} of {1} uncacheable inputs from method {2}", new Object[]{removedInputs, initialInputsSize, this.name});
+        }
+        initialInputsSize = this.groupsOfOccurrences.size();
+        this.groupsOfOccurrences.removeIf(groupOfOccurrence -> groupOfOccurrence.getTtl() == 0);
+        removedInputs = initialInputsSize - this.groupsOfOccurrences.size();
+        if (removedInputs > 0) {
+            LOGGER.log(Level.INFO, "\tRemoved {0} of {1} single-occurrence inputs from method {2}", new Object[]{removedInputs, initialInputsSize, this.name});
+        }
+    }
+
+    @Override
+    public String toString() {
+        return this.name;
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/CacheabilityMetrics.java b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/CacheabilityMetrics.java
new file mode 100644
index 0000000..541d854
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/CacheabilityMetrics.java
@@ -0,0 +1,34 @@
+package br.ufrgs.inf.prosoft.aplcachetf.extension.metrics;
+
+import br.ufrgs.inf.prosoft.aplcachetf.extension.metadata.GroupOfOccurrences;
+import java.util.Optional;
+
+public class CacheabilityMetrics {
+
+    public static int K_STANDARD_DEVIATION = 0;
+
+    public static boolean isStaticData(GroupOfOccurrences groupOfOccurrences) {
+        return groupOfOccurrences.getBestMetrics().getNumberOfDifferentReturnOccurrences() == 0;
+    }
+
+    public static Optional<Boolean> changeMoreThanUsed(GroupOfOccurrences groupOfOccurrences) {
+        //+/- k sds
+        return Optional.of(groupOfOccurrences.getBestMetrics().getMissRatio() > Thresholds.missThreshold(K_STANDARD_DEVIATION));
+    }
+
+    public static Optional<Boolean> usedByManyRequests(GroupOfOccurrences groupOfOccurrences) {
+        return Optional.of(groupOfOccurrences.getBestMetrics().getHitsPerTimeInCache() >= Thresholds.hitsPerTimeInCacheThreshold(K_STANDARD_DEVIATION));
+    }
+
+    public static Optional<Boolean> isUserSpecific(GroupOfOccurrences groupOfOccurrences) {
+        if (groupOfOccurrences.getBestMetrics().getAmountOfIdentifiedSameOccurences() == 0) {
+            return Optional.empty();
+        }
+        //the less shareable, the more user specific
+        return Optional.of(groupOfOccurrences.getBestMetrics().getShareability() < Thresholds.shareabilityThreshold(K_STANDARD_DEVIATION));
+    }
+
+    public static Optional<Boolean> isExpensive(GroupOfOccurrences groupOfOccurrences) {
+        return Optional.of(groupOfOccurrences.getBestMetrics().getSameOccurrencesAverageExecutionTime() >= Thresholds.expensivenessThreshold(K_STANDARD_DEVIATION));
+    }
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/CacheabilityPatternDecider.java b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/CacheabilityPatternDecider.java
new file mode 100644
index 0000000..93fac50
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/CacheabilityPatternDecider.java
@@ -0,0 +1,42 @@
+package br.ufrgs.inf.prosoft.aplcachetf.extension.metrics;
+
+import br.ufrgs.inf.prosoft.aplcachetf.extension.metadata.GroupOfOccurrences;
+import java.util.Optional;
+
+public class CacheabilityPatternDecider {
+
+    /**
+     * Flowchart definition
+     *
+     * @param groupOfOccurrences
+     * @return
+     */
+    public static boolean isCacheable(GroupOfOccurrences groupOfOccurrences) {
+        //Is the data completely static?
+        boolean isStaticData = CacheabilityMetrics.isStaticData(groupOfOccurrences);
+        if (isStaticData) { // staticity yes
+            return true;
+        }
+        // staticity no/not sure
+        Optional<Boolean> changeMoreThanUsed = CacheabilityMetrics.changeMoreThanUsed(groupOfOccurrences);
+        if (changeMoreThanUsed.isPresent() && changeMoreThanUsed.get()) { //changeMoreThanUsed true
+            return false;
+        }
+        //changeMoreThanUsed not/not sure
+        Optional<Boolean> usedByManyRequests = CacheabilityMetrics.usedByManyRequests(groupOfOccurrences);
+        if (usedByManyRequests.isPresent() && !usedByManyRequests.get()) { //useByManyRequests no
+            return false;
+        }
+        Optional<Boolean> isUserSpecific = CacheabilityMetrics.isUserSpecific(groupOfOccurrences);
+        if (isUserSpecific.isPresent() && isUserSpecific.get()) {
+            return true;
+        }
+        Optional<Boolean> isExpensive = CacheabilityMetrics.isExpensive(groupOfOccurrences);
+        return isExpensive.isPresent() ? isExpensive.get() : true;
+    }
+
+    public static boolean isNotCacheable(GroupOfOccurrences groupOfOccurrences) {
+        return !isCacheable(groupOfOccurrences);
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/Metrics.java b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/Metrics.java
new file mode 100644
index 0000000..b486553
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/Metrics.java
@@ -0,0 +1,124 @@
+/*
+ * 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.aplcachetf.extension.metrics;
+
+import br.ufrgs.inf.prosoft.tfcache.metadata.Occurrence;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ *
+ * @author root
+ */
+public class Metrics extends br.ufrgs.inf.prosoft.tfcache.Metrics {
+//exactly same method calls
+
+    private long sameOccurrences;
+
+    //number of same method calls with different return
+    private long differentReturnOccurrences;
+    private long sameOccurrencesExecutionTime;
+
+    private long amountOfIdentifiedSameOccurences;
+    private final Set<String> uniqueUsers;
+
+    public Metrics() {
+        sameOccurrences = 0L;
+        differentReturnOccurrences = 0L;
+        sameOccurrencesExecutionTime = 0L;
+        amountOfIdentifiedSameOccurences = 0L;
+        uniqueUsers = new HashSet<>();
+    }
+
+    public void addSameOccurrence(Occurrence occurrence) {
+        sameOccurrences++;
+        addSameOccurrencesTotalExecutionTime(occurrence.getExecutionTime());
+
+        if (occurrence.getUserId() != null && !occurrence.getUserId().equals("Anonymous")) {
+            uniqueUsers.add(occurrence.getUserId());
+            addIdentifiedSameOccurence();
+        }
+    }
+
+    public void addDifferentReturnOccurrence() {
+        differentReturnOccurrences++;
+    }
+
+    private void addSameOccurrencesTotalExecutionTime(Long executionTime) {
+        sameOccurrencesExecutionTime += executionTime;
+    }
+
+    private void addIdentifiedSameOccurence() {
+        amountOfIdentifiedSameOccurences++;
+    }
+
+    public long getSameOccurrencesTotalExecutionTime() {
+        return sameOccurrencesExecutionTime;
+    }
+
+    public double getSameOccurrencesAverageExecutionTime() {
+        return new BigDecimal(getSameOccurrencesTotalExecutionTime())
+                .divide(new BigDecimal(sameOccurrences), 5, RoundingMode.HALF_UP)
+                .doubleValue();
+    }
+
+    public Long getNumberOfOccurrences() {
+        return sameOccurrences + differentReturnOccurrences;
+    }
+
+    public Long getNumberOfSameOccurrences() {
+        return sameOccurrences;
+    }
+
+    public Long getNumberOfDifferentReturnOccurrences() {
+        return differentReturnOccurrences;
+    }
+
+    //from 0% to 100%
+    public double getHitRatio() {
+        BigDecimal bd = new BigDecimal(getNumberOfSameOccurrences() * 100);
+        return bd.divide(new BigDecimal(getNumberOfOccurrences()), 5, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    //from 0% to 100%
+    public double getMissRatio() {
+        BigDecimal bd = new BigDecimal(getNumberOfDifferentReturnOccurrences() * 100);
+        return bd.divide(new BigDecimal(getNumberOfOccurrences()), 5, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    public double getShareability() {
+        Long amountOfIdentifiedSameOccurences = getAmountOfIdentifiedSameOccurences();
+        if (amountOfIdentifiedSameOccurences == 0) {
+            return 100;
+        }
+        BigDecimal bd = new BigDecimal(getAmountOfUniqueIdentifiedSameOccurences() * 100);
+        return bd.divide(new BigDecimal(amountOfIdentifiedSameOccurences), 5, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    public Long getAmountOfIdentifiedSameOccurences() {
+        return amountOfIdentifiedSameOccurences;
+    }
+
+    public int getAmountOfUniqueIdentifiedSameOccurences() {
+        return uniqueUsers.size();
+    }
+
+    public Long getAmountOfAnonymousSameOccurences() {
+        return sameOccurrences - amountOfIdentifiedSameOccurences;
+    }
+
+    @Override
+    public String toString() {
+        return "{"
+                + "\"hitRatio\":" + getHitRatio() + ","
+                + "\"missRatio\":" + getMissRatio() + ","
+                + "\"shareability\":" + getShareability()
+                + "}";
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/Thresholds.java b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/Thresholds.java
new file mode 100644
index 0000000..895c0d3
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/extension/metrics/Thresholds.java
@@ -0,0 +1,218 @@
+/*
+ * 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.aplcachetf.extension.metrics;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author romulo
+ */
+public class Thresholds {
+
+    public static double sumMissRatio;
+    public static double sumHitRatio;
+    public static double sumExecutionTime;
+    public static double sumShareability;
+    public static double sumHitsPerTimeInCache;
+
+    public static Double averageHitsPerTimeInCache;
+
+    public static Double stdDevHitRatio;
+    public static Double stdDevMissRatio;
+    public static Double stdDevExecutionTimeRatio;
+    public static Double stdDevHitsPerTimeInCache;
+    public static Double stdDevShareability;
+
+    public static long population;
+
+    public static final List<Double> hitRatios = new ArrayList<>();
+    public static final List<Double> missRatios = new ArrayList<>();
+    public static final List<Long> executionTimes = new ArrayList<>();
+    public static final List<Double> shareabilities = new ArrayList<>();
+    public static final List<Double> hitsPerTimeInCache = new ArrayList<>();
+
+    public static void reset() {
+        Thresholds.sumMissRatio = 0;
+        Thresholds.sumHitRatio = 0;
+        Thresholds.sumExecutionTime = 0;
+        Thresholds.sumShareability = 0;
+        Thresholds.sumHitsPerTimeInCache = 0;
+
+        Thresholds.averageHitsPerTimeInCache = null;
+
+        Thresholds.stdDevHitRatio = null;
+        Thresholds.stdDevMissRatio = null;
+        Thresholds.stdDevExecutionTimeRatio = null;
+        Thresholds.stdDevHitsPerTimeInCache = null;
+        Thresholds.stdDevShareability = null;
+
+        Thresholds.population = 0;
+
+        Thresholds.hitRatios.clear();
+        Thresholds.missRatios.clear();
+        Thresholds.executionTimes.clear();
+        Thresholds.shareabilities.clear();
+        Thresholds.hitsPerTimeInCache.clear();
+    }
+
+    /**
+     * General mean hit ratio of all calls
+     *
+     * @return
+     */
+    public static double getAverageHitRatio() {
+        if (population == 0) {
+            return 0;
+        }
+        return new BigDecimal(sumHitRatio).divide(new BigDecimal(population), 5, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    public static double getAverageMissRatio() {
+        if (population == 0) {
+            return 0;
+        }
+        return new BigDecimal(sumMissRatio).divide(new BigDecimal(population), 5, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    public static double getAverageExecutionTime() {
+        if (population == 0) {
+            return 0;
+        }
+        return new BigDecimal(sumExecutionTime).divide(new BigDecimal(population), 5, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    public static double getAverageShareability() {
+        if (population == 0) {
+            return 0;
+        }
+        return new BigDecimal(sumShareability).divide(new BigDecimal(population), 5, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    public static double getStdDevHitRatio() {
+        if (population == 0) {
+            return 0;
+        }
+        if (stdDevHitRatio != null) {
+            return stdDevHitRatio;
+        }
+
+        double mean = getAverageHitRatio();
+        double temp = hitRatios.stream()
+                .map(hitRate -> (hitRate - mean) * (hitRate - mean))
+                .reduce(Double::sum)
+                .orElse(0D);
+        stdDevHitRatio = Math.sqrt(temp / population);
+        return stdDevHitRatio;
+    }
+
+    public static double getStdDevMissRatio() {
+        if (population == 0) {
+            return 0;
+        }
+        if (stdDevMissRatio != null) {
+            return stdDevMissRatio;
+        }
+
+        double mean = getAverageMissRatio();
+        double temp = missRatios.stream().map(missRatio -> (missRatio - mean) * (missRatio - mean))
+                .reduce(Double::sum)
+                .orElse(0D);
+        stdDevMissRatio = Math.sqrt(temp / population);
+        return stdDevMissRatio;
+    }
+
+    public static double getStdDevExecutionTimeRatio() {
+        if (population == 0) {
+            return 0;
+        }
+        if (stdDevExecutionTimeRatio != null) {
+            return stdDevExecutionTimeRatio;
+        }
+
+        double mean = getAverageExecutionTime();
+        double temp = executionTimes.stream()
+                .map(executionTime -> (executionTime - mean) * (executionTime - mean))
+                .reduce(Double::sum)
+                .orElse(0D);
+        stdDevExecutionTimeRatio = Math.sqrt(temp / population);
+        return stdDevExecutionTimeRatio;
+    }
+
+    public static double getStdDevHitsPerTimeInCache() {
+        if (population == 0) {
+            return 0;
+        }
+        if (stdDevHitsPerTimeInCache != null) {
+            return stdDevHitsPerTimeInCache;
+        }
+
+        double mean = getAverageHitsPerTimeInCache();
+        double temp = hitsPerTimeInCache.stream()
+                .map(frequency -> (frequency - mean) * (frequency - mean))
+                .reduce(Double::sum)
+                .orElse(0D);
+        stdDevHitsPerTimeInCache = Math.sqrt(temp / population);
+        return stdDevHitsPerTimeInCache;
+    }
+
+    public static double getStdDevShareability() {
+        if (population == 0) {
+            return 0;
+        }
+        if (stdDevShareability != null) {
+            return stdDevShareability;
+        }
+
+        double mean = getAverageShareability();
+        double temp = shareabilities.stream()
+                .map(shareability -> (shareability - mean) * (shareability - mean))
+                .reduce(Double::sum)
+                .orElse(0D);
+        stdDevShareability = Math.sqrt(temp / population);
+        return stdDevShareability;
+    }
+
+    public static double getAverageHitsPerTimeInCache() {
+        if (hitsPerTimeInCache.isEmpty()) {
+            return 0;
+        }
+        if (averageHitsPerTimeInCache != null) {
+            return averageHitsPerTimeInCache;
+        }
+
+        averageHitsPerTimeInCache = new BigDecimal(sumHitsPerTimeInCache).divide(new BigDecimal(hitsPerTimeInCache.size()), 5, RoundingMode.HALF_UP).doubleValue();
+        return averageHitsPerTimeInCache;
+    }
+
+//getting X% with most hits
+    public static double hitThreshold(int kStdDev) {
+        return getAverageHitRatio() + (kStdDev * getStdDevHitRatio());
+    }
+
+//getting X% with most misses
+    public static double missThreshold(int kStdDev) {
+        return getAverageMissRatio() + (kStdDev * getStdDevMissRatio());
+    }
+
+//getting X% most expensive methods
+    public static double expensivenessThreshold(int kStdDev) {
+        return getAverageExecutionTime() + (kStdDev * getStdDevExecutionTimeRatio());
+    }
+
+    public static double shareabilityThreshold(int kStdDev) {
+        return getAverageShareability() + (kStdDev * getStdDevShareability());
+    }
+
+//getting X% most frenquent
+    public static double hitsPerTimeInCacheThreshold(int kStdDev) {
+        return getAverageHitsPerTimeInCache() + (kStdDev * getStdDevHitsPerTimeInCache());
+    }
+
+}
diff --git a/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/Main.java b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/Main.java
new file mode 100755
index 0000000..3193e94
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/aplcachetf/Main.java
@@ -0,0 +1,97 @@
+/*
+ * 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.aplcachetf;
+
+import br.ufrgs.inf.prosoft.aplcachetf.adapter.TraceReader;
+import br.ufrgs.inf.prosoft.aplcachetf.extension.APLCache;
+import br.ufrgs.inf.prosoft.aplcachetf.extension.metadata.Method;
+import br.ufrgs.inf.prosoft.trace.Trace;
+import br.ufrgs.inf.prosoft.trace.reader.Mode;
+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] [APLCacheTF] %5$s %n");
+
+        if (args.length < 2) {
+            System.err.println("--trace=<TracePath> --output=<OutputPath> [--mode=<complete|hashed|partial>] [--k=<kStandardDeviation>] [--window=<windowSize>] [--shift=<shiftTime>]");
+            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 outputPath = arguments.get("output");
+        if (outputPath == null) {
+            System.err.println("<OutputPath> is required");
+            System.exit(1);
+        }
+        String mode = arguments.get("mode");
+        if (mode == null) {
+            mode = "complete";
+            LOGGER.log(Level.INFO, "Using default mode: {0}", mode);
+        }
+        int k = 0;
+        String kStr = arguments.get("k");
+        if (kStr == null) {
+            LOGGER.log(Level.INFO, "Using default k: {0}", k);
+        } else {
+            try {
+                k = Integer.valueOf(kStr);
+            } catch (NumberFormatException ex) {
+                System.err.println("<k> must be a number");
+                System.exit(1);
+            }
+        }
+
+        Long window = null;
+        try {
+            window = Long.valueOf(arguments.get("window"));
+        } catch (NumberFormatException ex) {
+        }
+        Long shift = null;
+        try {
+            shift = Long.valueOf(arguments.get("shift"));
+        } catch (NumberFormatException ex) {
+        }
+
+        LOGGER.log(Level.INFO, "Reading traces");
+        List<Trace> traces = TraceReader.parseFile(tracePath, Mode.valueOf(mode.toUpperCase()), window, shift);
+        LOGGER.log(Level.INFO, "Grouping {0} traces by methods", traces.size());
+        List<Method> methods = TraceReader.groupByMethods(traces);
+        LOGGER.log(Level.INFO, "grouped traces into {0} methods", methods.size());
+
+        APLCache aplcache = new APLCache(methods);
+        aplcache.recommend(k, outputPath);
+    }
+}