package io.vivarium.server.workloadmanagement; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import io.vivarium.persistence.WorkerModel; public class JobAssignments { private Map<WorkerModel, Integer> _workerJobCounts = new HashMap<>(); private Map<WorkerModel, Map<Integer, Integer>> _workerJobPriorityCounts = new HashMap<>(); private Map<WorkerModel, Long> _workerScores = new HashMap<>(); public JobAssignments(Collection<WorkerModel> workers) { for (WorkerModel worker : workers) { _workerJobCounts.put(worker, 0); _workerJobPriorityCounts.put(worker, new HashMap<>()); _workerScores.put(worker, 0L); } } public long getScoreChangeForJob(WorkerModel workerModel, int priority) { Preconditions.checkArgument(_workerJobCounts.containsKey(workerModel)); // Figure out the current job count for this worker int workerJobCount = _workerJobCounts.get(workerModel); int proposedWorkerJobCount = workerJobCount + 1; // Figure out the new score long newScore = determineWorkerScore(workerModel, proposedWorkerJobCount); long throughput = workerModel.getThroughputs()[proposedWorkerJobCount - 1]; long throughputPerJob = throughput / proposedWorkerJobCount; newScore += throughputPerJob * priority; // Determine how the score has changed long scoreChange = newScore - _workerScores.get(workerModel); return scoreChange; } public void addWorkerJob(WorkerModel workerModel, int priority) { Preconditions.checkArgument(_workerJobCounts.containsKey(workerModel)); // Update the job count for this worker int workerJobCount = _workerJobCounts.get(workerModel); workerJobCount++; _workerJobCounts.put(workerModel, workerJobCount); // Update the job count for this worker and priority int workerPriorityJobCount = _workerJobPriorityCounts.get(workerModel).getOrDefault(priority, 0); workerPriorityJobCount++; _workerJobPriorityCounts.get(workerModel).put(priority, workerPriorityJobCount); // Update the score long newScore = determineWorkerScore(workerModel, workerJobCount); _workerScores.put(workerModel, newScore); } private int determineWorkerJobCount(WorkerModel workerModel) { int jobCount = 0; Map<Integer, Integer> jobPriorityCounts = _workerJobPriorityCounts.get(workerModel); for (int priority : jobPriorityCounts.keySet()) { jobCount += jobPriorityCounts.get(priority); } return jobCount; } private long determineWorkerScore(WorkerModel workerModel, int workerJobCount) { if (workerJobCount == 0) { return 0; } long throughput = workerModel.getThroughputs()[workerJobCount - 1]; long throughputPerJob = throughput / workerJobCount; long workerScore = 0; Map<Integer, Integer> jobPriorityCounts = _workerJobPriorityCounts.get(workerModel); for (int priorityLevel : jobPriorityCounts.keySet()) { workerScore += jobPriorityCounts.get(priorityLevel) * throughputPerJob * priorityLevel; } return workerScore; } public long getScore() { long totalScore = 0; for (long workerScore : _workerScores.values()) { totalScore += workerScore; } return totalScore; } public boolean isWorkerFull(WorkerModel workerModel) { int jobCount = _workerJobCounts.get(workerModel); int jobSlots = workerModel.getThroughputs().length; return jobCount == jobSlots; } public Set<WorkerModel> getWorkers() { return Sets.newHashSet(_workerJobCounts.keySet()); } public Map<Integer, Integer> getJobPriorityCounts(WorkerModel workerModel) { Map<Integer, Integer> copyOfJobPriorityCounts = new HashMap<>(_workerJobPriorityCounts.get(workerModel)); return copyOfJobPriorityCounts; } /** * Subtracts one JobAssignments object from another. Both the minuend and subtrahend should apply to the same set of * WorkerModels. The resulting difference is computed by comparing each workers job priority counts and subtracting * each value from each key. If a value would be negative, it is instead zero. * * @param minuend * The JobAssignment object to subtract from. * @param subtrahend * The JobAssignment object to subtract with. * @return The difference between two JobAssignment objects. */ public static JobAssignments subtract(JobAssignments minuend, JobAssignments subtrahend) { // Check that these both the minuend and subtrahend have the same key set of workers. Preconditions.checkArgument(Sets .symmetricDifference(minuend._workerJobCounts.keySet(), subtrahend._workerJobCounts.keySet()) .size() == 0); // Create a new JobAssignments objects with the same worker key set. Set<WorkerModel> workers = minuend._workerJobCounts.keySet(); JobAssignments difference = new JobAssignments(workers); // Update the difference for each worker for (WorkerModel worker : workers) { // Determine all present priority counts from both the minuend and subtrahend Map<Integer, Integer> minuendPriorityCounts = minuend.getJobPriorityCounts(worker); Map<Integer, Integer> subtrahendPriorityCounts = subtrahend.getJobPriorityCounts(worker); Set<Integer> jobPriorities = Sets.union(minuendPriorityCounts.keySet(), subtrahendPriorityCounts.keySet()); // Create an updated workerJobPriorityCount entry for (int priority : jobPriorities) { int minuendPriorityCount = minuendPriorityCounts.getOrDefault(priority, 0); int subtrahendPriorityCount = subtrahendPriorityCounts.getOrDefault(priority, 0); int priorityDifference = Math.max(0, minuendPriorityCount - subtrahendPriorityCount); difference._workerJobPriorityCounts.get(worker).put(priority, priorityDifference); } // Update the total job counts and scores int workerJobCount = difference.determineWorkerJobCount(worker); difference._workerJobCounts.put(worker, workerJobCount); long score = difference.determineWorkerScore(worker, workerJobCount); difference._workerScores.put(worker, score); } return difference; } }