com.netflix.fenzo.queues.tiered.QueueBucket Maven / Gradle / Ivy
/*
* Copyright 2016 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.netflix.fenzo.queues.tiered;
import com.netflix.fenzo.VMResource;
import com.netflix.fenzo.queues.*;
import com.netflix.fenzo.queues.TaskQueue;
import com.netflix.fenzo.sla.ResAllocs;
import com.netflix.fenzo.sla.ResAllocsUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.BiFunction;
/**
* A queue bucket is a collection of tasks in one bucket. Generally, all tasks in the bucket are associated
* with a single entity for scheduling purposes such as capacity guarantees.
*/
class QueueBucket implements UsageTrackedQueue {
private static final Logger logger = LoggerFactory.getLogger(QueueBucket.class);
private final int tierNumber;
private final String name;
private final ResUsage totals;
private final ResAllocs emptyBucketGuarantees;
private ResAllocs bucketGuarantees;
private ResAllocs effectiveUsage;
private final LinkedHashMap queuedTasks;
private final LinkedHashMap launchedTasks;
// Assigned tasks is a temporary holder for tasks being assigned resources during scheduling
// iteration. These tasks are duplicate entries of tasks in queuedTasks, which cannot be removed from queuedTasks
// collection in order to keep the iterator on queuedTasks consistent throughout the scheduling iteration. Remember
// that scheduler's taskTracker will trigger call into assignTask() during the scheduling iteration.
private final LinkedHashMap assignedTasks;
private Iterator> iterator = null;
private ResAllocs tierResources;
private final BiFunction allocsShareGetter;
private final ResUsage tierUsage;
QueueBucket(int tierNumber, String name, ResUsage tierUsage, BiFunction allocsShareGetter) {
this.tierNumber = tierNumber;
this.name = name;
this.tierUsage = tierUsage;
totals = new ResUsage();
this.emptyBucketGuarantees = ResAllocsUtil.emptyOf(name);
bucketGuarantees = emptyBucketGuarantees;
queuedTasks = new LinkedHashMap<>();
launchedTasks = new LinkedHashMap<>();
assignedTasks = new LinkedHashMap<>();
this.allocsShareGetter = allocsShareGetter == null ?
(integer, s) -> 1.0 :
allocsShareGetter;
}
void setBucketGuarantees(ResAllocs bucketGuarantees) {
this.bucketGuarantees = bucketGuarantees == null ? emptyBucketGuarantees : bucketGuarantees;
updateEffectiveUsage();
}
@Override
public void queueTask(QueuableTask t) throws TaskQueueException {
if (iterator != null)
throw new ConcurrentModificationException("Must reset before queuing tasks");
if (queuedTasks.get(t.getId()) != null)
throw new TaskQueueException("Duplicate task not allowed, task with id " + t.getId());
if (launchedTasks.get(t.getId()) != null)
throw new TaskQueueException("Task already launched, can't queue, id=" + t.getId());
queuedTasks.put(t.getId(), t);
}
@Override
public Assignable nextTaskToLaunch() throws TaskQueueException {
if (iterator == null) {
iterator = queuedTasks.entrySet().iterator();
if (!assignedTasks.isEmpty())
throw new TaskQueueException(assignedTasks.size() + " tasks still assigned but not launched");
}
if (iterator.hasNext())
return Assignable.success(iterator.next().getValue());
return null;
}
@Override
public void assignTask(QueuableTask t) throws TaskQueueException {
if (iterator == null)
throw new TaskQueueException(new IllegalStateException("assign called on task " + t.getId() + " while not iterating over tasks"));
if (queuedTasks.get(t.getId()) == null)
throw new TaskQueueException("Task not in queue for assigning, id=" + t.getId());
if (assignedTasks.get(t.getId()) != null)
throw new TaskQueueException("Task already assigned, id=" + t.getId());
if (launchedTasks.get(t.getId()) != null)
throw new TaskQueueException("Task already launched, id=" + t.getId());
assignedTasks.put(t.getId(), t);
addUsage(t);
}
@Override
public boolean launchTask(QueuableTask t) throws TaskQueueException {
if (iterator != null)
throw new ConcurrentModificationException("Must reset before launching tasks");
if (launchedTasks.get(t.getId()) != null)
throw new TaskQueueException("Task already launched, id=" + t.getId());
queuedTasks.remove(t.getId());
final QueuableTask removed = assignedTasks.remove(t.getId());
launchedTasks.put(t.getId(), t);
if (removed == null) { // queueTask usage only if it was not assigned, happens when initializing tasks that were running previously
addUsage(t);
return true;
}
return false;
}
@Override
public QueuableTask removeTask(String id, QAttributes qAttributes) throws TaskQueueException {
if (iterator != null)
throw new TaskQueueException("Must reset before removing tasks");
QueuableTask removed = queuedTasks.remove(id);
if (removed == null) {
removed = assignedTasks.remove(id);
if (removed == null)
removed = launchedTasks.remove(id);
if (removed != null)
removeUsage(removed);
}
return removed;
}
private void addUsage(QueuableTask t) {
totals.addUsage(t);
updateEffectiveUsage();
}
private void removeUsage(QueuableTask removed) {
totals.remUsage(removed);
updateEffectiveUsage();
}
private void updateEffectiveUsage() {
effectiveUsage = ResAllocsUtil.ceilingOf(totals.getResAllocsWrapper(), bucketGuarantees);
}
@Override
public double getDominantUsageShare() {
// If total tier capacity is not available, use current tier allocation as a base for share computation.
ResAllocs total = tierResources == null ? tierUsage.getResAllocsWrapper() : tierResources;
return totals.getDominantResUsageFrom(total) /
Math.max(TierSla.eps / 10.0, allocsShareGetter.apply(tierNumber, name));
}
public boolean hasGuaranteedCapacityFor(QueuableTask task) {
// Check first if we are already above the limit
if (!ResAllocsUtil.isBounded(totals.getResAllocsWrapper(), bucketGuarantees)) {
return false;
}
// We have some remaining guaranteed resources. Now check if they are enough for our task.
ResAllocs summed = ResAllocsUtil.add(totals.getResAllocsWrapper(), task);
return ResAllocsUtil.isBounded(summed, bucketGuarantees);
}
public ResAllocs getEffectiveUsage() {
return effectiveUsage;
}
@Override
public void reset() {
iterator = null;
}
@Override
public Map> getAllTasks() throws TaskQueueException {
if (iterator != null)
throw new TaskQueueException("Must reset before getting list of tasks");
Map> result = new HashMap<>();
result.put(TaskQueue.TaskState.QUEUED, Collections.unmodifiableCollection(queuedTasks.values()));
result.put(TaskQueue.TaskState.LAUNCHED, Collections.unmodifiableCollection(launchedTasks.values()));
return result;
}
@Override
public void setTotalResources(Map totalResourcesMap) {
this.tierResources = ResAllocsUtil.toResAllocs("tier", totalResourcesMap);
}
public void setTotalResources(ResAllocs tierResources) {
this.tierResources = tierResources;
}
int size() {
return queuedTasks.size() + launchedTasks.size(); // don't queueTask assignedTasks.size(), they are duplicate of queuedTasks
}
int getTierNumber() {
return tierNumber;
}
String getName() {
return name;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy