APLCache.java

133 lines | 6.667 kB Blame History Raw Download
/*
 * 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.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   SavedTimePerTimeInCache: {0}", Thresholds.getAverageSavedTimePerTimeInCache());
        LOGGER.log(Level.INFO, "\tStdDv     SavedTimePerTimeInCache: {0}", Thresholds.getStdDevSavedTimePerTimeInCache());
        LOGGER.log(Level.INFO, "\tThreshold SavedTimePerTimeInCache: {0}", Thresholds.savedTimePerTimeInCacheThreshold(kStdDev));
    }

    private void filterCacheableInputs(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);
        this.methods.removeIf(method -> method.groupsOfOccurrences().count() == 0);
    }

    private void removeSingleOccurrences() {
        int initialMethodsSize = this.methods.size();
        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() == 0);
        int removedMethods = initialMethodsSize - this.methods.size();
        if (removedMethods > 0) {
            LOGGER.log(Level.INFO, "Removed {0} of {1} not reusable methods", new Object[]{removedMethods, initialMethodsSize});
        }
    }

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

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

        LOGGER.log(Level.INFO, "{0} cacheable methods detected", this.methods.size());
        this.methods.sort((method1, method2) -> method2.getEstimatedSavedTimePerTimeInCache().compareTo(method1.getEstimatedSavedTimePerTimeInCache()));

        this.methods.forEach(method -> {
            if (method.getBestMetrics() != null) {
                System.out.println(method.getName()
                        + " Occurrences " + method.getOccurrencesSize()
                        + " Inputs " + method.groupsOfOccurrences().count()
                        + " TTL " + method.getBestMetrics().getTtl()
                        + " STpTiC " + method.getBestMetrics().getSavedTimePerTimeInCache()
                        + " Saves " + method.getBestMetrics().getSavedTime()
                        + " Hits " + method.getBestMetrics().getHits()
                        + " Stales " + method.getBestMetrics().getStales());
            } else {
                System.out.println(method.getName()
                        + " Occurrences " + method.getOccurrencesSize()
                        + " 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.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);
    }

}