Skip to content

Commit a349ab9

Browse files
authored
[grid] Add GreedySlotSelector as a built-in slot-selector option (#15897)
Signed-off-by: Viet Nguyen Duc <[email protected]>
1 parent b73da5e commit a349ab9

File tree

2 files changed

+425
-0
lines changed

2 files changed

+425
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.grid.distributor.selector;
19+
20+
import static com.google.common.collect.ImmutableSet.toImmutableSet;
21+
import static org.openqa.selenium.grid.data.Availability.UP;
22+
23+
import java.util.Comparator;
24+
import java.util.Set;
25+
import org.openqa.selenium.Capabilities;
26+
import org.openqa.selenium.grid.config.Config;
27+
import org.openqa.selenium.grid.data.NodeStatus;
28+
import org.openqa.selenium.grid.data.SemanticVersionComparator;
29+
import org.openqa.selenium.grid.data.Slot;
30+
import org.openqa.selenium.grid.data.SlotId;
31+
import org.openqa.selenium.grid.data.SlotMatcher;
32+
33+
/**
34+
* A greedy slot selector that aims to maximize node utilization by minimizing the number of
35+
* partially filled nodes. The algorithm works as follows: 1. Sort nodes by their utilization load
36+
* (descending). 2. Among nodes with the same utilization, prefer those with fewer total slots. 3.
37+
* Then sort by the last session created (oldest first). This approach helps to: - Fill up nodes
38+
* that are already partially utilized - Minimize the number of nodes that are partially filled -
39+
* Distribute load evenly across nodes
40+
*/
41+
public class GreedySlotSelector implements SlotSelector {
42+
43+
public static SlotSelector create(Config config) {
44+
return new GreedySlotSelector();
45+
}
46+
47+
@Override
48+
public Set<SlotId> selectSlot(
49+
Capabilities capabilities, Set<NodeStatus> nodes, SlotMatcher slotMatcher) {
50+
return nodes.stream()
51+
.filter(node -> node.hasCapacity(capabilities, slotMatcher) && node.getAvailability() == UP)
52+
.sorted(
53+
// First and foremost, sort by utilization ratio (descending)
54+
// This ensures we ALWAYS try to fill nodes that are already partially utilized first
55+
Comparator.comparingDouble(NodeStatus::getLoad)
56+
.reversed()
57+
// Then sort by total number of slots (ascending)
58+
// Among nodes with same utilization, prefer those with fewer total slots
59+
.thenComparingLong(node -> node.getSlots().size())
60+
// Then last session created (oldest first)
61+
.thenComparingLong(NodeStatus::getLastSessionCreated)
62+
// Then sort by stereotype browserVersion (descending order)
63+
.thenComparing(
64+
Comparator.comparing(
65+
NodeStatus::getBrowserVersion, new SemanticVersionComparator().reversed())))
66+
.flatMap(
67+
node ->
68+
node.getSlots().stream()
69+
.filter(slot -> slot.getSession() == null)
70+
.filter(slot -> slot.isSupporting(capabilities, slotMatcher))
71+
.map(Slot::getId))
72+
.collect(toImmutableSet());
73+
}
74+
}

0 commit comments

Comments
 (0)