/** * Copyright 2016 Yahoo 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.yahoo.pulsar.common.policies.data.loadbalancer; import java.util.Set; import com.yahoo.pulsar.common.policies.data.ResourceQuota; public class ResourceUnitRanking implements Comparable<ResourceUnitRanking> { private static final long KBITS_TO_BYTES = 1024 / 8; private static final double PERCENTAGE_DIFFERENCE_THRESHOLD = 5.0; private static double cpuUsageByMsgRate = 0.05; // system resource usage from last LoadReport private SystemResourceUsage systemResourceUsage; // sum of quota allocated to bundles which are already loaded by each broker private ResourceQuota allocatedQuota; // sum of quota allocated to bundles which are assigned and to be loaded by each broker private ResourceQuota preAllocatedQuota; // list of bundles already loaded private Set<String> loadedBundles; // list of bundles assigned and to be loaded private Set<String> preAllocatedBundles; // estimated percentage of resource usage with the already assigned (both loaded and to-be-loaded) bundles private double estimatedLoadPercentage; // estimated number of total messages with the already assigned (both loaded and to-be-loaded) bundles private double estimatedMessageRate; private double allocatedLoadPercentageCPU; private double allocatedLoadPercentageMemory; private double allocatedLoadPercentageBandwidthIn; private double allocatedLoadPercentageBandwidthOut; private double estimatedLoadPercentageCPU; private double estimatedLoadPercentageMemory; private double estimatedLoadPercentageDirectMemory; private double estimatedLoadPercentageBandwidthIn; private double estimatedLoadPercentageBandwidthOut; public ResourceUnitRanking(SystemResourceUsage systemResourceUsage, Set<String> loadedBundles, ResourceQuota allocatedQuota, Set<String> preAllocatedBundles, ResourceQuota preAllocatedQuota) { this.systemResourceUsage = systemResourceUsage; this.loadedBundles = loadedBundles; this.allocatedQuota = allocatedQuota; this.preAllocatedBundles = preAllocatedBundles; this.preAllocatedQuota = preAllocatedQuota; this.estimateLoadPercentage(); } public static void setCpuUsageByMsgRate(double cpuUsageByMsgRate) { ResourceUnitRanking.cpuUsageByMsgRate = cpuUsageByMsgRate; } /** * Estimate the load percentage which is the max percentage of all resource usages */ private void estimateLoadPercentage() { double cpuUsed = this.systemResourceUsage.cpu.usage; double cpuAllocated = cpuUsageByMsgRate * (this.allocatedQuota.getMsgRateIn() + this.allocatedQuota.getMsgRateOut()); double cpuPreAllocated = cpuUsageByMsgRate * (this.preAllocatedQuota.getMsgRateIn() + this.preAllocatedQuota.getMsgRateOut()); this.allocatedLoadPercentageCPU = (this.systemResourceUsage.cpu.limit <= 0) ? 0 : Math.min(100, 100 * cpuAllocated / this.systemResourceUsage.cpu.limit); this.estimatedLoadPercentageCPU = (this.systemResourceUsage.cpu.limit <= 0) ? 0 : Math.min(100, 100 * (Math.max(cpuUsed, cpuAllocated) + cpuPreAllocated) / this.systemResourceUsage.cpu.limit); double memUsed = this.systemResourceUsage.memory.usage; double memAllocated = this.allocatedQuota.getMemory(); double memPreAllocated = this.preAllocatedQuota.getMemory(); this.allocatedLoadPercentageMemory = (this.systemResourceUsage.memory.limit <= 0) ? 0 : Math.min(100, 100 * memAllocated / this.systemResourceUsage.memory.limit); this.estimatedLoadPercentageMemory = (this.systemResourceUsage.memory.limit <= 0) ? 0 : Math.min(100, 100 * (Math.max(memUsed, memAllocated) + memPreAllocated) / this.systemResourceUsage.memory.limit); double bandwidthInUsed = this.systemResourceUsage.bandwidthIn.usage; double bandwidthInAllocated = this.allocatedQuota.getBandwidthIn() / KBITS_TO_BYTES; double bandwidthInPreAllocated = this.preAllocatedQuota.getBandwidthIn() / KBITS_TO_BYTES; this.allocatedLoadPercentageBandwidthIn = (this.systemResourceUsage.bandwidthIn.limit <= 0) ? 0 : Math.min(100, 100 * bandwidthInAllocated / this.systemResourceUsage.bandwidthIn.limit); this.estimatedLoadPercentageBandwidthIn = (this.systemResourceUsage.bandwidthIn.limit <= 0) ? 0 : Math.min(100, 100 * (Math.max(bandwidthInUsed, bandwidthInAllocated) + bandwidthInPreAllocated) / this.systemResourceUsage.bandwidthIn.limit); double bandwidthOutUsed = this.systemResourceUsage.bandwidthOut.usage; double bandwidthOutAllocated = this.allocatedQuota.getBandwidthOut() / KBITS_TO_BYTES; double bandwidthOutPreAllocated = this.preAllocatedQuota.getBandwidthOut() / KBITS_TO_BYTES; this.allocatedLoadPercentageBandwidthOut = (this.systemResourceUsage.bandwidthOut.limit <= 0) ? 0 : Math.min(100, 100 * bandwidthOutAllocated / this.systemResourceUsage.bandwidthOut.limit); this.estimatedLoadPercentageBandwidthOut = (this.systemResourceUsage.bandwidthOut.limit <= 0) ? 0 : Math.min(100, 100 * (Math.max(bandwidthOutUsed, bandwidthOutAllocated) + bandwidthOutPreAllocated) / this.systemResourceUsage.bandwidthOut.limit); double directMemoryUsed = this.systemResourceUsage.directMemory.usage; this.estimatedLoadPercentageDirectMemory = (this.systemResourceUsage.directMemory.limit <= 0) ? 0 : Math.min(100, 100 * directMemoryUsed / this.systemResourceUsage.directMemory.limit); this.estimatedLoadPercentage = Math.max(this.estimatedLoadPercentageCPU, Math.max(this.estimatedLoadPercentageMemory, Math.max(this.estimatedLoadPercentageDirectMemory, Math.max(this.estimatedLoadPercentageBandwidthIn, this.estimatedLoadPercentageBandwidthOut)))); this.estimatedMessageRate = this.allocatedQuota.getMsgRateIn() + this.allocatedQuota.getMsgRateOut() + this.preAllocatedQuota.getMsgRateIn() + this.preAllocatedQuota.getMsgRateOut(); } public int compareTo(ResourceUnitRanking other) { if (Math.abs(this.estimatedLoadPercentage - other.estimatedLoadPercentage) > PERCENTAGE_DIFFERENCE_THRESHOLD) { return Double.compare(this.estimatedLoadPercentage, other.estimatedLoadPercentage); } if (Math.abs(this.estimatedLoadPercentageMemory - other.estimatedLoadPercentageMemory) > PERCENTAGE_DIFFERENCE_THRESHOLD) { return Double.compare(this.estimatedLoadPercentageMemory, other.estimatedLoadPercentageMemory); } if (Math.abs( this.estimatedLoadPercentageCPU - other.estimatedLoadPercentageCPU) > PERCENTAGE_DIFFERENCE_THRESHOLD) { return Double.compare(this.estimatedLoadPercentageCPU, other.estimatedLoadPercentageCPU); } if (Math.abs(this.estimatedLoadPercentageBandwidthIn - other.estimatedLoadPercentageBandwidthIn) > PERCENTAGE_DIFFERENCE_THRESHOLD) { return Double.compare(this.estimatedLoadPercentageBandwidthIn, other.estimatedLoadPercentageBandwidthIn); } if (Math.abs(this.estimatedLoadPercentageBandwidthOut - other.estimatedLoadPercentageBandwidthOut) > PERCENTAGE_DIFFERENCE_THRESHOLD) { return Double.compare(this.estimatedLoadPercentageBandwidthOut, other.estimatedLoadPercentageBandwidthOut); } return Double.compare(this.estimatedLoadPercentage, other.estimatedLoadPercentage); } /** * Compare two loads based on message rate only */ public int compareMessageRateTo(ResourceUnitRanking other) { return Double.compare(this.estimatedMessageRate, other.estimatedMessageRate); } /** * If the ResourceUnit is idle */ public boolean isIdle() { return this.loadedBundles.isEmpty() && this.preAllocatedBundles.isEmpty(); } /** * Check if a ServiceUnit is already loaded by this ResourceUnit */ public boolean isServiceUnitLoaded(String suName) { return this.loadedBundles.contains(suName); } /** * Check if a ServiceUnit is pre-allocated to this ResourceUnit */ public boolean isServiceUnitPreAllocated(String suName) { return this.preAllocatedBundles.contains(suName); } /** * Pre-allocate a ServiceUnit to this ResourceUnit */ public void addPreAllocatedServiceUnit(String suName, ResourceQuota quota) { this.preAllocatedBundles.add(suName); this.preAllocatedQuota.add(quota); estimateLoadPercentage(); } /** * Remove a service unit from the loaded bundle list */ public void removeLoadedServiceUnit(String suName, ResourceQuota quota) { if (this.loadedBundles.remove(suName)) { this.allocatedQuota.substract(quota); estimateLoadPercentage(); } } /** * Get the pre-allocated bundles */ public Set<String> getPreAllocatedBundles() { return this.preAllocatedBundles; } /** * Get the loaded bundles. */ public Set<String> getLoadedBundles() { return loadedBundles; } /** * Get the estimated load percentage */ public double getEstimatedLoadPercentage() { return this.estimatedLoadPercentage; } /** * Get the estimated message rate */ public double getEstimatedMessageRate() { return this.estimatedMessageRate; } /** * Percentage of CPU allocated to bundle's quota */ public double getAllocatedLoadPercentageCPU() { return this.allocatedLoadPercentageCPU; } /** * Percetage of memory allocated to bundle's quota */ public double getAllocatedLoadPercentageMemory() { return this.allocatedLoadPercentageMemory; } /** * Percentage of inbound bandwidth allocated to bundle's quota */ public double getAllocatedLoadPercentageBandwidthIn() { return this.allocatedLoadPercentageBandwidthIn; } /** * Percentage of outbound bandwidth allocated to bundle's quota */ public double getAllocatedLoadPercentageBandwidthOut() { return this.allocatedLoadPercentageBandwidthOut; } /** * Get the load percentage in String, with detail resource usages */ public String getEstimatedLoadPercentageString() { return String.format( "msgrate: %.0f, load: %.1f%% - cpu: %.1f%%, mem: %.1f%%, directMemory: %.1f%%, bandwidthIn: %.1f%%, bandwidthOut: %.1f%%", this.estimatedMessageRate, this.estimatedLoadPercentage, this.estimatedLoadPercentageCPU, this.estimatedLoadPercentageMemory, this.estimatedLoadPercentageDirectMemory, this.estimatedLoadPercentageBandwidthIn, this.estimatedLoadPercentageBandwidthOut); } /** * Estimate the maximum number of namespace bundles ths ResourceUnit is able to handle with all resource. */ public long estimateMaxCapacity(ResourceQuota defaultQuota) { return calculateBrokerMaxCapacity(this.systemResourceUsage, defaultQuota); } /** * Estimate the maximum number namespace bundles a ResourceUnit is able to handle with all resource */ public static long calculateBrokerMaxCapacity(SystemResourceUsage systemResourceUsage, ResourceQuota defaultQuota) { double bandwidthOutLimit = systemResourceUsage.bandwidthOut.limit * KBITS_TO_BYTES; double bandwidthInLimit = systemResourceUsage.bandwidthIn.limit * KBITS_TO_BYTES; long capacity = calculateBrokerCapacity(defaultQuota, systemResourceUsage.cpu.limit, systemResourceUsage.memory.limit, bandwidthOutLimit, bandwidthInLimit); return capacity; } /** * Calculate how many bundles could be handle with the specified resources */ private static long calculateBrokerCapacity(ResourceQuota defaultQuota, double usableCPU, double usableMem, double usableBandwidthOut, double usableBandwidthIn) { // estimate capacity with usable CPU double cpuCapacity = (usableCPU / cpuUsageByMsgRate) / (defaultQuota.getMsgRateIn() + defaultQuota.getMsgRateOut()); // estimate capacity with usable memory double memCapacity = usableMem / defaultQuota.getMemory(); // estimate capacity with usable outbound bandwidth double bandwidthOutCapacity = usableBandwidthOut / defaultQuota.getBandwidthOut(); // estimate capacity with usable inbound bandwidth double bandwidthInCapacity = usableBandwidthIn / defaultQuota.getBandwidthIn(); // the ServiceUnit capacity is determined by the minimum capacity of resources double capacity = Math.min(cpuCapacity, Math.min(memCapacity, Math.min(bandwidthOutCapacity, bandwidthInCapacity))); return (long) Math.max(capacity, 0); } }