APLCache.java

124 lines | 6.139 kB Blame History Raw Download
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.text.MessageFormat;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

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.info(MessageFormat.format("Calculating metrics for {0} methods...", this.methods.size()));
    this.methods.stream().parallel().forEach(Method::calculateMetrics);
  }

  private void calculateThresholds(int kStdDev) {
    LOGGER.info(MessageFormat.format("Calculating thresholds for {0} methods with {1} stdDev...", this.methods.size(), kStdDev));

    Thresholds.reset();
    Thresholds.population = getPopulation();
    this.methods.forEach(Method::calculateThresholds);

    LOGGER.info(MessageFormat.format("\tAverage   ExecutionTime: {0}", Thresholds.getAverageExecutionTime()));
    LOGGER.info(MessageFormat.format("\tStdDv     ExecutionTime: {0}", Thresholds.getStdDevExecutionTime()));
    LOGGER.info(MessageFormat.format("\tThreshold ExecutionTime: {0}", Thresholds.expensivenessThreshold(kStdDev)));
    LOGGER.info(MessageFormat.format("\tAverage   HitRatio:      {0}", Thresholds.getAverageHitRatio()));
    LOGGER.info(MessageFormat.format("\tStdDv     HitRatio:      {0}", Thresholds.getStdDevHitRatio()));
    LOGGER.info(MessageFormat.format("\tThreshold HitRatio:      {0}", Thresholds.hitThreshold(kStdDev)));
    LOGGER.info(MessageFormat.format("\tAverage   MissRatio:     {0}", Thresholds.getAverageMissRatio()));
    LOGGER.info(MessageFormat.format("\tStdDv     MissRatio:     {0}", Thresholds.getStdDevMissRatio()));
    LOGGER.info(MessageFormat.format("\tThreshold MissRatio:     {0}", Thresholds.missThreshold(kStdDev)));
    LOGGER.info(MessageFormat.format("\tAverage   Shareability:  {0}", Thresholds.getAverageShareability()));
    LOGGER.info(MessageFormat.format("\tStdDv     Shareability:  {0}", Thresholds.getStdDevShareability()));
    LOGGER.info(MessageFormat.format("\tThreshold Shareability:  {0}", Thresholds.shareabilityThreshold(kStdDev)));
    LOGGER.info(MessageFormat.format("\tAverage   Distance:      {0}", Thresholds.getAverageDistance()));
    LOGGER.info(MessageFormat.format("\tStdDv     Distance:      {0}", Thresholds.getStdDevDistance()));
    LOGGER.info(MessageFormat.format("\tThreshold Distance:      {0}", Thresholds.distanceThreshold(kStdDev)));
  }

  private void filterCacheableInputs(int kStdDev) {
    LOGGER.info(MessageFormat.format("Filtering inputs of {0} methods under {1} stdDev threshold...", this.methods.size(), kStdDev));
    CacheabilityMetrics.K_STANDARD_DEVIATION = kStdDev;
    this.methods.forEach(Method::filterCacheableInputs);
    this.methods.removeIf(method -> method.groupsOfOccurrences().count() == 0);
  }

  private void removeSingleOccurrences() {
    int initialMethodsSize = this.methods.size();
    LOGGER.info(MessageFormat.format("Removing not reusable inputs from {0} methods", this.methods.size()));
    this.methods.forEach(Method::removeSingleOccurrences);
    LOGGER.info(MessageFormat.format("Removing not reusable methods from {0} methods", this.methods.size()));
    this.methods.removeIf(method -> method.groupsOfOccurrences().count() == 0);
    int removedMethods = initialMethodsSize - this.methods.size();
    if (removedMethods > 0) LOGGER.info(MessageFormat.format("Removed {0} of {1} not reusable methods", removedMethods, initialMethodsSize));
  }

  public void recommend(String outputPath) {
    recommend(0, outputPath);
  }

  public void recommend(int kStdDev, String outputPath) {
    LOGGER.info(MessageFormat.format("Recommending TTL per method for {0} methods", this.methods.size()));
    removeSingleOccurrences();
    calculateMetrics();
    calculateThresholds(kStdDev);
    filterCacheableInputs(kStdDev);

    LOGGER.info(MessageFormat.format("{0} cacheable methods detected", this.methods.size()));
    this.methods.sort((method1, method2) -> Double.compare(method2.getEstimatedSavedTime(), method1.getEstimatedSavedTime()));

    this.methods.forEach(method -> {
      if (method.getBestTFMetrics() != null) {
        System.out.println(method.getName()
          + " Occurrences " + method.occurrences().count()
          + " Inputs " + method.groupsOfOccurrences().count()
          + " TTL " + method.getBestTFMetrics().getTtl()
          + " Saves " + method.getBestTFMetrics().getSavedTime()
          + " Hits " + method.getBestTFMetrics().getHits()
          + " Computation " + method.getBestTFMetrics().getComputationTime()
          + " TimeInCache " + method.getBestTFMetrics().getTimeInCache()
          + " Stales " + method.getBestTFMetrics().getStales());
      } else {
        System.out.println(method.getName()
          + " Occurrences " + method.occurrences().count()
          + " EstimatedSavedTime " + method.getEstimatedSavedTime()
          + " Inputs " + method.groupsOfOccurrences().count());
      }
    });
    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.getMetrics().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(it -> it.occurrences().count()).reduce(Long::sum).orElse(0L);
  }

}