Method.java

248 lines | 9.476 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.tfcache.metadata;

import br.ufrgs.inf.prosoft.tfcache.Metrics;
import br.ufrgs.inf.prosoft.tfcache.Pareto;
import br.ufrgs.inf.prosoft.tfcache.Simulator;
import br.ufrgs.inf.prosoft.tfcache.StorageManager;
import br.ufrgs.inf.prosoft.tfcache.configuration.Configuration;

import java.math.BigDecimal;
import java.util.*;
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.Stream;

/**
 * @author romulo
 */
public class Method {

    private static final Logger LOGGER = Logger.getLogger(Method.class.getName());

    private final String name;
    private final List<Occurrence> occurrences;
    private List<GroupOfOccurrences> groupsOfOccurrences;

    private Integer countChangeableGroups;

    private Pareto pareto;

    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 static Map<String, List<Occurrence>> groupByInput(List<Occurrence> occurrences) {
        if (occurrences == null) {
            throw new RuntimeException("Occurrences cannot be null");
        }
        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;
            });
        });
        return inputHasOccurrences;
    }

    public String getName() {
        return name;
    }

    public Metrics getBestMetrics() {
        return this.pareto.getBestMetrics();
    }

    public double getEstimatedSavedTime() {
        if (getBestMetrics() != null) {
            return getBestMetrics().getSavedTime();
        }
        return groupsOfOccurrences().map(GroupOfOccurrences::getSavedTime).reduce(Double::sum).orElse(0D);
    }

    public BigDecimal getEstimatedSavedTimePerTimeInCache() {
        if (getBestMetrics() != null) {
            return getBestMetrics().getSavedTimePerTimeInCache();
        }
        GroupOfOccurrences max = groupsOfOccurrences().max(Comparator.comparing(GroupOfOccurrences::getSavedTimePerTimeInCache)).orElseThrow();
        return max.getSavedTimePerTimeInCache();
    }

    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(GroupOfOccurrences::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");
        }
        this.groupsOfOccurrences = groupByInput(this.occurrences).entrySet().stream()
                .map(entry -> new GroupOfOccurrences(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
    }

    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);
        int removedOccurrences = initialCountofOccurrences - this.groupsOfOccurrences.size();
        if (removedOccurrences > 0) {
            LOGGER.log(Level.INFO, "\tRemoved {0} of {1} inputs from method {2}", new Object[]{removedOccurrences, 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.pareto != null) {
            LOGGER.log(Level.WARNING, "metrics already calculated");
        }
        if (this.occurrences == null) {
            throw new RuntimeException("groupByInputs called. Cannot proceed");
        }
        this.pareto = new Pareto();
        if (this.occurrences.size() < 2) {
            return;
        }
        this.occurrences.sort(Comparator.comparingLong(Occurrence::getStartTime));
        Simulator.simulate(this.occurrences, this.pareto);
        if (Configuration.getVerbose()) {
            String application = Configuration.getInput().split(",")[0];
            this.pareto.values().forEach(it ->
                    System.out.println(application + "," + name + "," + it.getTtl() + "," + it.getSavedTime() + "," + it.getHits() + "," + it.getTimeInCache()));
        }
    }

    public void recommendTTLPerInput() {
        if (this.groupsOfOccurrences == null) {
            groupByInput();
            removeSingleOccurrences();
        }
        if (Configuration.getVerbose()) {
            System.out.println("=== " + getName() + " ===");
            Configuration.setInput(Configuration.getInput().split(",")[0] + "," + getName() + ",");
            groupsOfOccurrences().forEach(GroupOfOccurrences::calculateMetrics);
        } else {
            groupsOfOccurrences().parallel().forEach(GroupOfOccurrences::calculateMetrics);
        }
        String uuid = Configuration.getUUID().replace("level:input", "level:method");
        Pareto pareto = StorageManager.get(uuid, this.occurrences);
        if (this.pareto == null && pareto != null) {
            this.pareto = pareto;
        }
    }

    public void removeNotRecommendedInputs() {
        if (this.groupsOfOccurrences == null) {
            throw new RuntimeException("Recommendations not called");
        }
        int initialCountofOccurrences = this.groupsOfOccurrences.size();
        this.groupsOfOccurrences.removeIf(groupOfOccurrences -> groupOfOccurrences.getSavedTime() == 0);
        int removedOccurrences = initialCountofOccurrences - this.groupsOfOccurrences.size();
        if (removedOccurrences > 0) {
            LOGGER.log(Level.INFO, "\tRemoved {0} of {1} inputs from method {2}", new Object[]{removedOccurrences, initialCountofOccurrences, this.name});
        }
    }

    public void rankRecommendations() {
        if (this.groupsOfOccurrences == null) {
            throw new RuntimeException("groupsOfOccurrences is null");
        }
        this.groupsOfOccurrences.sort((group1, group2) -> group2.getSavedTimePerTimeInCache().compareTo(group1.getSavedTimePerTimeInCache()));
    }

    @Override
    public String toString() {
        return this.name;
    }

}