Skip to content

Commit 912acbb

Browse files
committed
Refactor ClusterDRSAlgortihm to improve performance
1 parent c6c5689 commit 912acbb

File tree

8 files changed

+315
-193
lines changed

8 files changed

+315
-193
lines changed

api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithm.java

Lines changed: 109 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import com.cloud.host.Host;
2323
import com.cloud.offering.ServiceOffering;
2424
import com.cloud.org.Cluster;
25-
import com.cloud.utils.Pair;
2625
import com.cloud.utils.Ternary;
2726
import com.cloud.utils.component.Adapter;
2827
import com.cloud.vm.VirtualMachine;
@@ -39,6 +38,10 @@
3938
import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetricUseRatio;
4039

4140
public 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
}

plugins/drs/cluster/balanced/src/main/java/org/apache/cloudstack/cluster/Balanced.java

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,35 +68,26 @@ public String getName() {
6868
return "balanced";
6969
}
7070

71-
@Override
72-
public Ternary<Double, Double, Double> getMetrics(Cluster cluster, VirtualMachine vm,
73-
ServiceOffering serviceOffering, Host destHost,
74-
Map<Long, Ternary<Long, Long, Long>> hostCpuMap, Map<Long, Ternary<Long, Long, Long>> hostMemoryMap,
75-
Boolean requiresStorageMotion) throws ConfigurationException {
76-
Double preImbalance = ClusterDrsAlgorithm.getClusterImbalance(cluster.getId(), new ArrayList<>(hostCpuMap.values()), new ArrayList<>(hostMemoryMap.values()), null);
77-
return getMetrics(cluster, vm, serviceOffering, destHost, hostCpuMap, hostMemoryMap, requiresStorageMotion, preImbalance);
78-
}
7971

8072
@Override
8173
public Ternary<Double, Double, Double> getMetrics(Cluster cluster, VirtualMachine vm,
8274
ServiceOffering serviceOffering, Host destHost,
8375
Map<Long, Ternary<Long, Long, Long>> hostCpuMap, Map<Long, Ternary<Long, Long, Long>> hostMemoryMap,
84-
Boolean requiresStorageMotion, Double preImbalance) throws ConfigurationException {
76+
Boolean requiresStorageMotion, Double preImbalance,
77+
double[] baseMetricsArray, Map<Long, Integer> hostIdToIndexMap) throws ConfigurationException {
8578
// Use provided pre-imbalance if available, otherwise calculate it
8679
if (preImbalance == null) {
8780
preImbalance = ClusterDrsAlgorithm.getClusterImbalance(cluster.getId(), new ArrayList<>(hostCpuMap.values()), new ArrayList<>(hostMemoryMap.values()), null);
8881
}
89-
Double postImbalance = getImbalancePostMigration(serviceOffering, vm, destHost, hostCpuMap, hostMemoryMap);
9082

91-
logger.trace("Cluster {} pre-imbalance: {} post-imbalance: {} Algorithm: {} VM: {} srcHost: {} destHost: {}",
83+
// Use optimized post-imbalance calculation that adjusts only affected hosts
84+
Double postImbalance = getImbalancePostMigration(vm, destHost,
85+
cluster.getId(), ClusterDrsAlgorithm.getVmMetric(serviceOffering, cluster.getId()),
86+
baseMetricsArray, hostIdToIndexMap, hostCpuMap, hostMemoryMap);
87+
88+
logger.trace("Cluster {} pre-imbalance: {} post-imbalance: {} Algorithm: {} VM: {} srcHost ID: {} destHost: {} (optimized)",
9289
cluster, preImbalance, postImbalance, getName(), vm, vm.getHostId(), destHost);
9390

94-
// This needs more research to determine the cost and benefit of a migration
95-
// TODO: Cost should be a factor of the VM size and the host capacity
96-
// TODO: Benefit should be a factor of the VM size and the host capacity and the number of VMs on the host
97-
final double improvement = preImbalance - postImbalance;
98-
final double cost = 0.0;
99-
final double benefit = 1.0;
100-
return new Ternary<>(improvement, cost, benefit);
91+
return calculateMetricsFromImbalances(preImbalance, postImbalance);
10192
}
10293
}

0 commit comments

Comments
 (0)