Method.java

173 lines | 7.445 kB Blame History Raw Download
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.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

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;
  private BigDecimal normalisedMinEuclideanDistance;

  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) {
    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 BigDecimal getNormalisedMinEuclideanDistance() {
    if (this.pareto == null) throw new RuntimeException("trying to access pareto before calculating it");
    if (normalisedMinEuclideanDistance == null) normalisedMinEuclideanDistance = this.pareto.getNormalisedMinEuclideanDistance();
    return normalisedMinEuclideanDistance;
  }

  public Metrics getBestMetrics() {
    if (this.pareto == null) throw new RuntimeException("trying to access pareto before calculating it");
    return this.pareto.getBestMetrics();
  }

  public long getEstimatedSavedTime() {
    if (getBestMetrics() != null) return getBestMetrics().getSavedTime();
    return groupsOfOccurrences().map(it -> it.getBestMetrics().getSavedTime()).reduce(Long::sum).orElse(0L);
  }

  public Stream<Occurrence> occurrences() {
    return this.occurrences.stream();
  }

  public Stream<GroupOfOccurrences> groupsOfOccurrences() {
    if (this.groupsOfOccurrences == null) groupByInput();
    return this.groupsOfOccurrences.stream();
  }

  private void groupByInput() {
    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.info(MessageFormat.format("\tRemoved {0} of {1} changeable inputs from method {2}", 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(it -> it.occurrences().count() < 2);
    int removedOccurrences = initialCountOfOccurrences - this.groupsOfOccurrences.size();
    if (removedOccurrences > 0) LOGGER.info(MessageFormat.format("\tRemoved {0} of {1} inputs from method {2}", removedOccurrences, initialCountOfOccurrences, this.name));
  }

  public boolean isReusable() {
    if (this.groupsOfOccurrences != null) return this.groupsOfOccurrences.stream().anyMatch(it -> it.occurrences().count() > 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.warning("metrics already calculated");
    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(it -> it.getBestMetrics().getSavedTime() == 0);
    int removedOccurrences = initialCountOfOccurrences - this.groupsOfOccurrences.size();
    if (removedOccurrences > 0) LOGGER.info(MessageFormat.format("\tRemoved {0} of {1} inputs from method {2}", removedOccurrences, initialCountOfOccurrences, this.name));
  }

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

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

}