/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 gobblin.util.request_allocation; import java.util.Arrays; import java.util.List; import java.util.Map; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.Singular; /** * Represents a pool of available resources for a {@link RequestAllocator}. The resources pool is essentially a vector * of doubles where each dimension represents a resource. A set of resource requests exceeds the availability of the * pool if the vector sum of those requests is larger than the vector of resources in the pool along any dimension. */ @Getter(value = AccessLevel.PROTECTED) public class ResourcePool { public static final double DEFAULT_DIMENSION_TOLERANCE = 1.2; private final ImmutableMap<String, Integer> dimensionIndex; private final double[] softBound; private final double[] hardBound; private final double[] defaultResourceUse; /** * @param maxResources Maximum resource availability along each dimension. Each entry in this map is a dimension. Note * this is considered a soft bound (e.g. max resources may be exceeded by a tolerance). * @param tolerances The hard limit on resources availability along each dimension is set to maxResource * tolerance. * The default tolerance is {@link #DEFAULT_DIMENSION_TOLERANCE}. It is recommended to always have a * tolerance >1, as some {@link RequestAllocator}s will do unnecessary work if the soft and hard * bounds are too close to each other. * @param defaultRequirements Specifies the default usage of the resources along each dimension when creating a * {@link ResourceRequirement}. Default is 0. */ @Builder protected ResourcePool(@Singular Map<String, Double> maxResources, @Singular Map<String, Double> tolerances, @Singular Map<String, Double> defaultRequirements) { ImmutableMap.Builder<String, Integer> indexBuilder = ImmutableMap.builder(); this.softBound = new double[maxResources.size()]; int currentIndex = 0; for (Map.Entry<String, Double> resource : maxResources.entrySet()) { indexBuilder.put(resource.getKey(), currentIndex); this.softBound[currentIndex] = resource.getValue(); currentIndex++; } this.dimensionIndex = indexBuilder.build(); this.hardBound = this.softBound.clone(); for (int i = 0; i < this.hardBound.length; i++) { this.hardBound[i] *= DEFAULT_DIMENSION_TOLERANCE; } this.defaultResourceUse = new double[this.softBound.length]; for (Map.Entry<String, Integer> idxEntry : this.dimensionIndex.entrySet()) { if (tolerances.containsKey(idxEntry.getKey())) { this.hardBound[idxEntry.getValue()] = this.softBound[idxEntry.getValue()] * Math.max(1.0, tolerances.get(idxEntry.getKey())); } if (defaultRequirements.containsKey(idxEntry.getKey())) { this.defaultResourceUse[idxEntry.getValue()] = defaultRequirements.get(idxEntry.getKey()); } } } private ResourcePool(double[] softBound, double[] hardBound, double[] defaultResourceUse, ImmutableMap<String, Integer> dimensionIndex) { this.softBound = softBound; this.hardBound = hardBound; this.defaultResourceUse = defaultResourceUse; this.dimensionIndex = dimensionIndex; } protected ResourcePool(ResourcePool other) { this.softBound = other.getSoftBound(); this.hardBound = other.getHardBound(); this.defaultResourceUse = other.getDefaultResourceUse(); this.dimensionIndex = other.getDimensionIndex(); } @Override public String toString() { StringBuilder builder = new StringBuilder(ResourcePool.class.getSimpleName()).append(": {"); builder.append("softBound").append(": ").append(vectorToString(this.softBound)); builder.append(", "); builder.append("hardBound").append(": ").append(vectorToString(this.hardBound)); builder.append("}"); return builder.toString(); } /** * Stringify a {@link ResourceRequirement} with the appropriate dimension labels. */ public String stringifyRequirement(ResourceRequirement requirement) { return vectorToString(requirement.getResourceVector()); } private String vectorToString(double[] vector) { List<String> tokens = Lists.newArrayListWithCapacity(this.dimensionIndex.size()); for (Map.Entry<String, Integer> dimension : dimensionIndex.entrySet()) { tokens.add(dimension.getKey() + ": " + vector[dimension.getValue()]); } return Arrays.toString(tokens.toArray()); } /** * @return true if input {@link ResourceRequirement} exceeds the soft bound long any dimension. If the parameter * orEqual is true, then matching along any dimension will also return true. */ public boolean exceedsSoftBound(ResourceRequirement requirement, boolean orEqual) { return VectorAlgebra.exceedsVector(this.softBound, requirement.getResourceVector(), orEqual); } /** * @return true if input {@link ResourceRequirement} exceeds the hard bound long any dimension. If the parameter * orEqual is true, then matching along any dimension will also return true. */ public boolean exceedsHardBound(ResourceRequirement requirement, boolean orEqual) { return VectorAlgebra.exceedsVector(this.hardBound, requirement.getResourceVector(), orEqual); } /** * Use to create a {@link ResourceRequirement} compatible with this {@link ResourcePool}. */ public ResourceRequirement.Builder getResourceRequirementBuilder() { return new ResourceRequirement.Builder(this); } /** * @return a new {@link ResourcePool} which is a copy of this {@link ResourcePool} except its resource vector has been * reduced by the input {@link ResourceRequirement}. */ protected ResourcePool contractPool(ResourceRequirement requirement) { return new ResourcePool(VectorAlgebra.addVector(this.softBound, requirement.getResourceVector(), -1., null), VectorAlgebra.addVector(this.hardBound, requirement.getResourceVector(), -1., null), this.defaultResourceUse, this.dimensionIndex); } /** * Get the dimensionality of the embedded resource vector. */ int getNumDimensions() { return this.dimensionIndex.size(); } double[] getDefaultResourceUse(double[] reuse) { if (reuse != null && this.defaultResourceUse.length == reuse.length) { System.arraycopy(this.defaultResourceUse, 0, reuse, 0, this.defaultResourceUse.length); return reuse; } return this.defaultResourceUse.clone(); } }