3333import com .cloud .host .Host ;
3434import com .cloud .host .HostLoad ;
3535import com .cloud .host .HostScoringWeights ;
36- import com .cloud .host .HostLoadService ;
3736import com .cloud .offering .ServiceOffering ;
3837import com .cloud .org .Cluster ;
3938import com .cloud .utils .Ternary ;
5352 * This blends four figures per host - CPU and memory allocated, CPU and memory in use - and
5453 * balances the result. The weights are the same host.weighted.* settings initial placement uses, on
5554 * purpose: if the two weighted them differently they would disagree about which host is the better
56- * one, and rebalancing could move VMs off hosts that placement had just chosen. Imbalance keeps the same meaning as the other algorithms: the standard
57- * deviation of the per-host figure over its mean, so drs.imbalance still means what it did.
55+ * one, and rebalancing could move VMs off hosts that placement had just chosen. Imbalance keeps the same shape as the other algorithms - the standard
56+ * deviation of the per-host figure over its mean - but it is computed over a blend rather than over
57+ * one metric, so drs.imbalance is not calibrated the same way and is worth re-checking after
58+ * switching.
59+ *
60+ * drs.metric, drs.metric.type and drs.metric.use.ratio choose and shape the single metric the other
61+ * algorithms balance. They do not apply here and are ignored.
5862 */
5963public class Weighted extends AdapterBase implements ClusterDrsAlgorithm , Configurable {
6064
6165 private static final Logger LOGGER = LogManager .getLogger (Weighted .class );
6266
63- @ Inject
64- private HostLoadService hostLoadService ;
67+ public static final ConfigKey <Double > StorageMotionCost = new ConfigKey <>(ConfigKey .CATEGORY_ADVANCED ,
68+ Double .class , "drs.weighted.storage.motion.cost" , "0.02" ,
69+ "How much a migration must improve the cluster's imbalance to be worth also moving the VM's "
70+ + "storage. Migrations that do not need storage moved only have to improve it at all." ,
71+ true , ConfigKey .Scope .Cluster );
6572
6673 @ Inject
6774 private ClusterDetailsDao clusterDetailsDao ;
6875
76+ /**
77+ * Everything that is constant for one plan. getMetrics is called for every candidate VM and
78+ * host - up to hundreds of thousands of times for a large cluster - so nothing in that path may
79+ * hit the database or re-read settings.
80+ */
81+ private static final class PlanContext {
82+ private final float cpuOvercommit ;
83+ private final float memoryOvercommit ;
84+ private final double cpuAllocatedWeight ;
85+ private final double memoryAllocatedWeight ;
86+ private final double cpuUsedWeight ;
87+ private final double memoryUsedWeight ;
88+ private final Map <Long , HostLoad > hostLoadMap ;
89+ private final boolean everyHostMeasured ;
90+
91+ private PlanContext (float cpuOvercommit , float memoryOvercommit , double cpuAllocatedWeight ,
92+ double memoryAllocatedWeight , double cpuUsedWeight , double memoryUsedWeight ,
93+ Map <Long , HostLoad > hostLoadMap , boolean everyHostMeasured ) {
94+ this .everyHostMeasured = everyHostMeasured ;
95+ this .cpuOvercommit = cpuOvercommit ;
96+ this .memoryOvercommit = memoryOvercommit ;
97+ this .cpuAllocatedWeight = cpuAllocatedWeight ;
98+ this .memoryAllocatedWeight = memoryAllocatedWeight ;
99+ this .cpuUsedWeight = cpuUsedWeight ;
100+ this .memoryUsedWeight = memoryUsedWeight ;
101+ this .hostLoadMap = hostLoadMap ;
102+ }
103+
104+ private HostLoad loadOf (long hostId ) {
105+ HostLoad load = hostLoadMap .get (hostId );
106+ return load == null ? HostLoad .UNKNOWN : load ;
107+ }
108+ }
109+
110+ private final ThreadLocal <PlanContext > context = new ThreadLocal <>();
111+
112+ @ Override
113+ public void prepare (Cluster cluster , Map <Long , Ternary <Long , Long , Long >> hostCpuMap ,
114+ Map <Long , Ternary <Long , Long , Long >> hostMemoryMap , Map <Long , HostLoad > hostLoadMap ) {
115+ long clusterId = cluster .getId ();
116+ context .set (new PlanContext (
117+ overcommitRatio (clusterId , VmDetailConstants .CPU_OVER_COMMIT_RATIO ),
118+ overcommitRatio (clusterId , VmDetailConstants .MEMORY_OVER_COMMIT_RATIO ),
119+ weight (HostScoringWeights .CpuAllocatedWeight , clusterId ),
120+ weight (HostScoringWeights .MemoryAllocatedWeight , clusterId ),
121+ weight (HostScoringWeights .CpuUsedWeight , clusterId ),
122+ weight (HostScoringWeights .MemoryUsedWeight , clusterId ),
123+ hostLoadMap == null ? new HashMap <>() : hostLoadMap ,
124+ hostLoadMap != null && !hostLoadMap .isEmpty ()
125+ && hostLoadMap .values ().stream ().allMatch (HostLoad ::isUsable )));
126+ }
127+
128+ /**
129+ * Falls back to reading everything when prepare has not been called, so the algorithm still
130+ * works for a caller that does not know about it.
131+ */
132+ private PlanContext contextFor (Cluster cluster ) {
133+ PlanContext prepared = context .get ();
134+ if (prepared != null ) {
135+ return prepared ;
136+ }
137+ prepare (cluster , null , null , null );
138+ return context .get ();
139+ }
140+
69141 @ Override
70142 public String getName () {
71143 return "weighted" ;
@@ -81,12 +153,13 @@ public boolean needsDrs(Cluster cluster, List<Ternary<Long, Long, Long>> cpuList
81153 cpuMap .put ((long ) -(i + 1 ), cpuList .get (i ));
82154 memoryMap .put ((long ) -(i + 1 ), memoryList .get (i ));
83155 }
84- return needsDrs (cluster , cpuMap , memoryMap );
156+ return needsDrs (cluster , cpuMap , memoryMap , new HashMap <>() );
85157 }
86158
87159 @ Override
88160 public boolean needsDrs (Cluster cluster , Map <Long , Ternary <Long , Long , Long >> hostCpuMap ,
89- Map <Long , Ternary <Long , Long , Long >> hostMemoryMap ) throws ConfigurationException {
161+ Map <Long , Ternary <Long , Long , Long >> hostMemoryMap , Map <Long , HostLoad > hostLoadMap )
162+ throws ConfigurationException {
90163 double threshold = 1.0 - ClusterDrsService .ClusterDrsImbalanceThreshold .valueIn (cluster .getId ());
91164 double imbalance = imbalanceOf (blendByHost (cluster , hostCpuMap , hostMemoryMap ).values ());
92165 boolean needed = imbalance > threshold ;
@@ -111,11 +184,12 @@ public Ternary<Double, Double, Double> getMetrics(Cluster cluster, VirtualMachin
111184
112185 double after = imbalanceOf (blendByHost (cluster , cpuAfter , memoryAfter ).values ());
113186
187+ // the caller migrates when benefit > cost, so expressing both in units of imbalance makes
188+ // that comparison mean "is this worth what it costs". A migration that has to move storage
189+ // has to earn more than one that does not.
114190 double improvement = before - after ;
115- // moving a VM costs something and buys nothing unless the cluster ends up more even, so a
116- // migration is only worth making when it measurably helps
117- double cost = Boolean .TRUE .equals (requiresStorageMotion ) ? 1.0 : 0.0 ;
118- double benefit = improvement > 0 ? 1.0 + improvement : 0.0 ;
191+ double cost = Boolean .TRUE .equals (requiresStorageMotion ) ? weight (StorageMotionCost , cluster .getId ()) : 0.0 ;
192+ double benefit = improvement ;
119193
120194 LOGGER .trace ("Cluster {} imbalance {} -> {} moving {} to {}" , cluster , before , after , vm , destHost );
121195 return new Ternary <>(improvement , cost , benefit );
@@ -126,13 +200,7 @@ public Ternary<Double, Double, Double> getMetrics(Cluster cluster, VirtualMachin
126200 */
127201 protected Map <Long , Double > blendByHost (Cluster cluster , Map <Long , Ternary <Long , Long , Long >> hostCpuMap ,
128202 Map <Long , Ternary <Long , Long , Long >> hostMemoryMap ) {
129- float cpuOvercommit = overcommitRatio (cluster .getId (), VmDetailConstants .CPU_OVER_COMMIT_RATIO );
130- float memoryOvercommit = overcommitRatio (cluster .getId (), VmDetailConstants .MEMORY_OVER_COMMIT_RATIO );
131-
132- double cpuAllocatedWeight = weight (HostScoringWeights .CpuAllocatedWeight , cluster .getId ());
133- double memoryAllocatedWeight = weight (HostScoringWeights .MemoryAllocatedWeight , cluster .getId ());
134- double cpuUsedWeight = weight (HostScoringWeights .CpuUsedWeight , cluster .getId ());
135- double memoryUsedWeight = weight (HostScoringWeights .MemoryUsedWeight , cluster .getId ());
203+ PlanContext ctx = contextFor (cluster );
136204
137205 Map <Long , Double > blended = new HashMap <>();
138206 for (Map .Entry <Long , Ternary <Long , Long , Long >> entry : hostCpuMap .entrySet ()) {
@@ -141,20 +209,24 @@ protected Map<Long, Double> blendByHost(Cluster cluster, Map<Long, Ternary<Long,
141209 if (memory == null ) {
142210 continue ;
143211 }
144- double cpuAllocated = fractionOf (entry .getValue (), cpuOvercommit );
145- double memoryAllocated = fractionOf (memory , memoryOvercommit );
212+ double cpuAllocated = fractionOf (entry .getValue (), ctx . cpuOvercommit );
213+ double memoryAllocated = fractionOf (memory , ctx . memoryOvercommit );
146214
147- HostLoad load = hostLoadService == null ? HostLoad .UNKNOWN : hostLoadService .getLoad (hostId );
148- double usedCpuWeight = load .isUsable () ? cpuUsedWeight : 0 ;
149- double usedMemoryWeight = load .isUsable () ? memoryUsedWeight : 0 ;
215+ // utilisation is only used when every host has it. Imbalance compares hosts against
216+ // each other, so mixing hosts measured on utilisation with hosts measured on allocation
217+ // alone would report a difference that is an artefact of the monitoring, not the load -
218+ // and would evacuate whichever host stopped reporting.
219+ HostLoad load = ctx .loadOf (hostId );
220+ double usedCpuWeight = ctx .everyHostMeasured ? ctx .cpuUsedWeight : 0 ;
221+ double usedMemoryWeight = ctx .everyHostMeasured ? ctx .memoryUsedWeight : 0 ;
150222
151- double sum = cpuAllocatedWeight + memoryAllocatedWeight + usedCpuWeight + usedMemoryWeight ;
223+ double sum = ctx . cpuAllocatedWeight + ctx . memoryAllocatedWeight + usedCpuWeight + usedMemoryWeight ;
152224 if (sum <= 0 ) {
153225 blended .put (hostId , 0.0 );
154226 continue ;
155227 }
156- blended .put (hostId , (cpuAllocatedWeight * cpuAllocated
157- + memoryAllocatedWeight * memoryAllocated
228+ blended .put (hostId , (ctx . cpuAllocatedWeight * cpuAllocated
229+ + ctx . memoryAllocatedWeight * memoryAllocated
158230 + usedCpuWeight * load .getCpuUtilisation ()
159231 + usedMemoryWeight * load .getMemoryUtilisation ()) / sum );
160232 }
@@ -181,11 +253,14 @@ private Map<Long, Ternary<Long, Long, Long>> withVmMoved(Map<Long, Ternary<Long,
181253 * Used over what the host can hand out, which is its real total scaled by the overcommit ratio.
182254 */
183255 private double fractionOf (Ternary <Long , Long , Long > capacity , float overcommit ) {
184- double allocatable = (capacity .third () - capacity .second ()) * (double ) overcommit ;
256+ // overcommit scales the host's total; reserved is then taken off that, which is how
257+ // CapacityManager computes free capacity everywhere else. Multiplying reserved by the ratio
258+ // instead would make a host look fuller the more capacity it merely has reserved.
259+ double allocatable = capacity .third () * (double ) overcommit - capacity .second ();
185260 if (allocatable <= 0 ) {
186261 return 0 ;
187262 }
188- return clamp ( capacity .first () / allocatable ) ;
263+ return capacity .first () / allocatable ;
189264 }
190265
191266 protected float overcommitRatio (long clusterId , String key ) {
@@ -239,7 +314,7 @@ public String getConfigComponentName() {
239314
240315 @ Override
241316 public ConfigKey <?>[] getConfigKeys () {
242- // the four host.weighted.* weights are shared with initial placement, which registers them
317+ // the four host.weighted.* weights are shared with initial placement and registered there
243318 return new ConfigKey <?>[] {StorageMotionCost };
244319 }
245320}
0 commit comments