2222import com .cloud .host .Host ;
2323import com .cloud .offering .ServiceOffering ;
2424import com .cloud .org .Cluster ;
25- import com .cloud .utils .Pair ;
2625import com .cloud .utils .Ternary ;
2726import com .cloud .utils .component .Adapter ;
2827import com .cloud .vm .VirtualMachine ;
3938import static org .apache .cloudstack .cluster .ClusterDrsService .ClusterDrsMetricUseRatio ;
4039
4140public interface ClusterDrsAlgorithm extends Adapter {
41+ // Reusable stateless calculator objects (thread-safe) - created once, reused for all calculations
42+ // Apache Commons Math Mean and StandardDeviation are stateless and thread-safe
43+ Mean MEAN_CALCULATOR = new Mean ();
44+ StandardDeviation STDDEV_CALCULATOR = new StandardDeviation (false );
4245
4346 /**
4447 * Determines whether a DRS operation is needed for a given cluster and host-VM
@@ -59,111 +62,118 @@ public interface ClusterDrsAlgorithm extends Adapter {
5962 boolean needsDrs (Cluster cluster , List <Ternary <Long , Long , Long >> cpuList ,
6063 List <Ternary <Long , Long , Long >> memoryList ) throws ConfigurationException ;
6164
62-
6365 /**
64- * Determines the metrics for a given virtual machine and destination host in a DRS cluster.
65- *
66- * @param clusterId
67- * the ID of the cluster to check
68- * @param vm
69- * the virtual machine to check
70- * @param serviceOffering
71- * the service offering for the virtual machine
72- * @param destHost
73- * the destination host for the virtual machine
74- * @param hostCpuMap
75- * a map of host IDs to the Ternary of used, reserved and total CPU on each host
76- * @param hostMemoryMap
77- * a map of host IDs to the Ternary of used, reserved and total memory on each host
78- * @param requiresStorageMotion
79- * whether storage motion is required for the virtual machine
66+ * Calculates the metrics (improvement, cost, benefit) for migrating a VM to a destination host. Improvement is
67+ * calculated based on the change in cluster imbalance before and after the migration.
8068 *
69+ * @param cluster the cluster to check
70+ * @param vm the virtual machine to check
71+ * @param serviceOffering the service offering for the virtual machine
72+ * @param destHost the destination host for the virtual machine
73+ * @param hostCpuMap a map of host IDs to the Ternary of used, reserved and total CPU on each host
74+ * @param hostMemoryMap a map of host IDs to the Ternary of used, reserved and total memory on each host
75+ * @param requiresStorageMotion whether storage motion is required for the virtual machine
76+ * @param preImbalance the pre-calculated cluster imbalance before migration (null to calculate it)
77+ * @param baseMetricsArray pre-calculated array of all host metrics before migration
78+ * @param hostIdToIndexMap mapping from host ID to index in the metrics array
8179 * @return a ternary containing improvement, cost, benefit
8280 */
8381 Ternary <Double , Double , Double > getMetrics (Cluster cluster , VirtualMachine vm , ServiceOffering serviceOffering ,
8482 Host destHost , Map <Long , Ternary <Long , Long , Long >> hostCpuMap ,
8583 Map <Long , Ternary <Long , Long , Long >> hostMemoryMap ,
86- Boolean requiresStorageMotion ) throws ConfigurationException ;
84+ Boolean requiresStorageMotion , Double preImbalance ,
85+ double [] baseMetricsArray , Map <Long , Integer > hostIdToIndexMap ) throws ConfigurationException ;
8786
8887 /**
89- * Determines the metrics for a given virtual machine and destination host in a DRS cluster.
90- * This overloaded version accepts a pre-calculated pre-imbalance value to avoid recalculating
91- * it for every VM-host combination within the same iteration.
92- *
93- * @param cluster
94- * the cluster to check
95- * @param vm
96- * the virtual machine to check
97- * @param serviceOffering
98- * the service offering for the virtual machine
99- * @param destHost
100- * the destination host for the virtual machine
101- * @param hostCpuMap
102- * a map of host IDs to the Ternary of used, reserved and total CPU on each host
103- * @param hostMemoryMap
104- * a map of host IDs to the Ternary of used, reserved and total memory on each host
105- * @param requiresStorageMotion
106- * whether storage motion is required for the virtual machine
107- * @param preImbalance
108- * the pre-calculated cluster imbalance before migration (null to calculate it)
88+ * Calculates the cluster imbalance after migrating a VM to a destination host.
10989 *
110- * @return a ternary containing improvement, cost, benefit
90+ * @param vm the virtual machine being migrated
91+ * @param destHost the destination host for the virtual machine
92+ * @param clusterId the cluster ID
93+ * @param vmMetric the VM's resource consumption metric
94+ * @param baseMetricsArray pre-calculated array of all host metrics before migration
95+ * @param hostIdToIndexMap mapping from host ID to index in the metrics array
96+ * @return the cluster imbalance after migration
11197 */
112- default Ternary <Double , Double , Double > getMetrics (Cluster cluster , VirtualMachine vm , ServiceOffering serviceOffering ,
113- Host destHost , Map <Long , Ternary <Long , Long , Long >> hostCpuMap ,
114- Map <Long , Ternary <Long , Long , Long >> hostMemoryMap ,
115- Boolean requiresStorageMotion , Double preImbalance ) throws ConfigurationException {
116- // Default implementation delegates to the original method for backward compatibility
117- return getMetrics (cluster , vm , serviceOffering , destHost , hostCpuMap , hostMemoryMap , requiresStorageMotion );
98+ default Double getImbalancePostMigration (VirtualMachine vm ,
99+ Host destHost , Long clusterId , long vmMetric , double [] baseMetricsArray ,
100+ Map <Long , Integer > hostIdToIndexMap , Map <Long , Ternary <Long , Long , Long >> hostCpuMap ,
101+ Map <Long , Ternary <Long , Long , Long >> hostMemoryMap ) {
102+ // Create a copy of the base array and adjust only the two affected hosts
103+ double [] adjustedMetrics = new double [baseMetricsArray .length ];
104+ System .arraycopy (baseMetricsArray , 0 , adjustedMetrics , 0 , baseMetricsArray .length );
105+
106+ long destHostId = destHost .getId ();
107+ long vmHostId = vm .getHostId ();
108+
109+ // Adjust source host (remove VM resources)
110+ Integer sourceIndex = hostIdToIndexMap .get (vmHostId );
111+ if (sourceIndex != null && sourceIndex < adjustedMetrics .length ) {
112+ Map <Long , Ternary <Long , Long , Long >> sourceMetricsMap = getClusterDrsMetric (clusterId ).equals ("cpu" ) ? hostCpuMap : hostMemoryMap ;
113+ Ternary <Long , Long , Long > sourceMetrics = sourceMetricsMap .get (vmHostId );
114+ if (sourceMetrics != null ) {
115+ adjustedMetrics [sourceIndex ] = getMetricValuePostMigration (clusterId , sourceMetrics , vmMetric , vmHostId , destHostId , vmHostId );
116+ }
117+ }
118+
119+ // Adjust destination host (add VM resources)
120+ Integer destIndex = hostIdToIndexMap .get (destHostId );
121+ if (destIndex != null && destIndex < adjustedMetrics .length ) {
122+ Map <Long , Ternary <Long , Long , Long >> destMetricsMap = getClusterDrsMetric (clusterId ).equals ("cpu" ) ? hostCpuMap : hostMemoryMap ;
123+ Ternary <Long , Long , Long > destMetrics = destMetricsMap .get (destHostId );
124+ if (destMetrics != null ) {
125+ adjustedMetrics [destIndex ] = getMetricValuePostMigration (clusterId , destMetrics , vmMetric , destHostId , destHostId , vmHostId );
126+ }
127+ }
128+
129+ return calculateImbalance (adjustedMetrics );
118130 }
119131
120132 /**
121- * Calculates the imbalance of the cluster after a virtual machine migration.
122- *
123- * @param serviceOffering
124- * the service offering for the virtual machine
125- * @param vm
126- * the virtual machine being migrated
127- * @param destHost
128- * the destination host for the virtual machine
129- * @param hostCpuMap
130- * a map of host IDs to the Ternary of used, reserved and total CPU on each host
131- * @param hostMemoryMap
132- * a map of host IDs to the Ternary of used, reserved and total memory on each host
133+ * Calculate imbalance from a double array. Imbalance is defined as standard deviation divided by mean.
133134 *
134- * @return a pair containing the CPU and memory imbalance of the cluster after the migration
135+ * Uses reusable stateless calculator objects to avoid object creation overhead.
136+ * @param values array of metric values
137+ * @return calculated imbalance
135138 */
136- default Double getImbalancePostMigration (ServiceOffering serviceOffering , VirtualMachine vm ,
137- Host destHost , Map <Long , Ternary <Long , Long , Long >> hostCpuMap ,
138- Map <Long , Ternary <Long , Long , Long >> hostMemoryMap ) throws ConfigurationException {
139- Pair <Long , Map <Long , Ternary <Long , Long , Long >>> pair = getHostMetricsMapAndType (destHost .getClusterId (), serviceOffering , hostCpuMap , hostMemoryMap );
140- long vmMetric = pair .first ();
141- Map <Long , Ternary <Long , Long , Long >> hostMetricsMap = pair .second ();
142-
143- List <Double > list = new ArrayList <>();
144- for (Long hostId : hostMetricsMap .keySet ()) {
145- list .add (getMetricValuePostMigration (destHost .getClusterId (), hostMetricsMap .get (hostId ), vmMetric , hostId , destHost .getId (), vm .getHostId ()));
139+ private static double calculateImbalance (double [] values ) {
140+ if (values == null || values .length == 0 ) {
141+ return 0.0 ;
146142 }
147- return getImbalance (list );
143+ // Reuse static final calculator objects (thread-safe, stateless)
144+ double mean = MEAN_CALCULATOR .evaluate (values );
145+ if (mean == 0.0 ) {
146+ return 0.0 ; // Avoid division by zero
147+ }
148+ double stdDev = STDDEV_CALCULATOR .evaluate (values , mean );
149+ return stdDev / mean ;
148150 }
149151
150- private Pair <Long , Map <Long , Ternary <Long , Long , Long >>> getHostMetricsMapAndType (Long clusterId ,
151- ServiceOffering serviceOffering , Map <Long , Ternary <Long , Long , Long >> hostCpuMap ,
152- Map <Long , Ternary <Long , Long , Long >> hostMemoryMap ) throws ConfigurationException {
152+ /**
153+ * Helper method to get VM metric based on cluster configuration.
154+ */
155+ static long getVmMetric (ServiceOffering serviceOffering , Long clusterId ) throws ConfigurationException {
153156 String metric = getClusterDrsMetric (clusterId );
154- Pair <Long , Map <Long , Ternary <Long , Long , Long >>> pair ;
155157 switch (metric ) {
156158 case "cpu" :
157- pair = new Pair <>((long ) serviceOffering .getCpu () * serviceOffering .getSpeed (), hostCpuMap );
158- break ;
159+ return (long ) serviceOffering .getCpu () * serviceOffering .getSpeed ();
159160 case "memory" :
160- pair = new Pair <>(serviceOffering .getRamSize () * 1024L * 1024L , hostMemoryMap );
161- break ;
161+ return serviceOffering .getRamSize () * 1024L * 1024L ;
162162 default :
163163 throw new ConfigurationException (
164164 String .format ("Invalid metric: %s for cluster: %d" , metric , clusterId ));
165165 }
166- return pair ;
166+ }
167+
168+ /**
169+ * Helper method to calculate metrics from pre and post imbalance values.
170+ * Subclasses can override this to implement different improvement calculations.
171+ */
172+ default Ternary <Double , Double , Double > calculateMetricsFromImbalances (Double preImbalance , Double postImbalance ) {
173+ final double improvement = preImbalance - postImbalance ;
174+ final double cost = 0.0 ;
175+ final double benefit = 1.0 ;
176+ return new Ternary <>(improvement , cost , benefit );
167177 }
168178
169179 private Double getMetricValuePostMigration (Long clusterId , Ternary <Long , Long , Long > metrics , long vmMetric ,
@@ -183,9 +193,26 @@ private Double getMetricValuePostMigration(Long clusterId, Ternary<Long, Long, L
183193 }
184194
185195 private static Double getImbalance (List <Double > metricList ) {
186- Double clusterMeanMetric = getClusterMeanMetric (metricList );
187- Double clusterStandardDeviation = getClusterStandardDeviation (metricList , clusterMeanMetric );
188- return clusterStandardDeviation / clusterMeanMetric ;
196+ if (metricList == null || metricList .isEmpty ()) {
197+ return 0.0 ;
198+ }
199+ // Convert List<Double> to double[] once, avoiding repeated conversions
200+ double [] values = new double [metricList .size ()];
201+ int index = 0 ;
202+ for (Double value : metricList ) {
203+ if (value != null ) {
204+ values [index ++] = value ;
205+ }
206+ }
207+
208+ // Trim array if some values were null
209+ if (index < values .length ) {
210+ double [] trimmed = new double [index ];
211+ System .arraycopy (values , 0 , trimmed , 0 , index );
212+ values = trimmed ;
213+ }
214+
215+ return calculateImbalance (values );
189216 }
190217
191218 static String getClusterDrsMetric (long clusterId ) {
@@ -213,36 +240,6 @@ static Double getMetricValue(long clusterId, long used, long free, long total, F
213240 return null ;
214241 }
215242
216- /**
217- * Mean is the average of a collection or set of metrics. In context of a DRS
218- * cluster, the cluster metrics defined as the average metrics value for some
219- * metric (such as CPU, memory etc.) for every resource such as host.
220- * Cluster Mean Metric, mavg = (∑mi) / N, where mi is a measurable metric for a
221- * resource ‘i’ in a cluster with total N number of resources.
222- */
223- static Double getClusterMeanMetric (List <Double > metricList ) {
224- return new Mean ().evaluate (metricList .stream ().mapToDouble (i -> i ).toArray ());
225- }
226-
227- /**
228- * Standard deviation is defined as the square root of the absolute squared sum
229- * of difference of a metric from its mean for every resource divided by the
230- * total number of resources. In context of the DRS, the cluster standard
231- * deviation is the standard deviation based on a metric of resources in a
232- * cluster such as for the allocation or utilisation CPU/memory metric of hosts
233- * in a cluster.
234- * Cluster Standard Deviation, σc = sqrt((∑∣mi−mavg∣^2) / N), where mavg is the
235- * mean metric value and mi is a measurable metric for some resource ‘i’ in the
236- * cluster with total N number of resources.
237- */
238- static Double getClusterStandardDeviation (List <Double > metricList , Double mean ) {
239- if (mean != null ) {
240- return new StandardDeviation (false ).evaluate (metricList .stream ().mapToDouble (i -> i ).toArray (), mean );
241- } else {
242- return new StandardDeviation (false ).evaluate (metricList .stream ().mapToDouble (i -> i ).toArray ());
243- }
244- }
245-
246243 static boolean getDrsMetricUseRatio (long clusterId ) {
247244 return ClusterDrsMetricUseRatio .valueIn (clusterId );
248245 }
0 commit comments