1616
1717namespace facebook ::rebalancer {
1818
19+ namespace {
20+ // Keeps only the scope items that compare better than the moving object's
21+ // current one on a chosen dimension. See ScopeItemList.filter.
22+ class ObjectDimensionBasedFilter {
23+ public:
24+ ObjectDimensionBasedFilter (
25+ const interface::ObjectDimensionBasedFilter& filter,
26+ const entities::ObjectScalarDimension& dimension,
27+ entities::ScopeId scopeId,
28+ entities::ObjectId object,
29+ std::optional<entities::ScopeItemId> currentScopeItem)
30+ : filter_{filter},
31+ dimension_{dimension},
32+ scopeId_{scopeId},
33+ object_{object} {
34+ if (currentScopeItem.has_value ()) {
35+ currentValue_ = valueAt (*currentScopeItem);
36+ }
37+ }
38+
39+ bool includes (entities::ScopeItemId scopeItemId) const {
40+ // An object outside the scope has no current value to beat, so keep all.
41+ if (!currentValue_.has_value ()) {
42+ return true ;
43+ }
44+ const auto candidate = valueAt (scopeItemId);
45+ if (candidate == *currentValue_) {
46+ return *filter_.includeEqual ();
47+ }
48+ return *filter_.preferLower () ? candidate < *currentValue_
49+ : candidate > *currentValue_;
50+ }
51+
52+ private:
53+ double valueAt (entities::ScopeItemId scopeItemId) const {
54+ return dimension_.getValueSafe (
55+ object_,
56+ entities::ScopeScopeItemPair{
57+ .scopeId = scopeId_, .scopeItemId = scopeItemId});
58+ }
59+
60+ const interface::ObjectDimensionBasedFilter& filter_;
61+ const entities::ObjectScalarDimension& dimension_;
62+ entities::ScopeId scopeId_;
63+ entities::ObjectId object_;
64+ std::optional<double > currentValue_;
65+ };
66+ } // namespace
67+
1968DestinationsToExploreGenerator::DestinationsToExploreGenerator (
2069 const PackerSet<entities::ContainerId>& nonAcceptingContainers,
2170 const entities::Universe& universe)
@@ -36,7 +85,8 @@ DestinationsToExploreGenerator::getAcceptingDestinations(
3685 // in the given scope
3786 interface::ScopeItemList scopeItemList;
3887 scopeItemList.scopeName () = scopeName;
39- return getAcceptingContainersList (scopeItemList);
88+ return getAcceptingContainersList (
89+ scopeItemList, hotContainerId, /* hotObject=*/ std::nullopt );
4090 }
4191
4292 return ReferenceList<const std::vector<entities::ContainerId>>{
@@ -46,14 +96,16 @@ DestinationsToExploreGenerator::getAcceptingDestinations(
4696ReferenceList<const std::vector<entities::ContainerId>>
4797DestinationsToExploreGenerator::getAcceptingDestinations (
4898 const interface::MoveToScopeItemsSpec& moveToScopeItems,
99+ const entities::ContainerId hotContainer,
49100 const entities::ObjectId hotObject) {
50101 auto & objectToScopeItems = *moveToScopeItems.objectToScopeItems ();
51102 auto & hotObjectName = universe_.getEntityName (hotObject);
52103
53104 // if an object is specified in objectToScopeItems, use that.
54105 auto scopeItemListPtr = folly::get_ptr (objectToScopeItems, hotObjectName);
55106 if (scopeItemListPtr) {
56- return getAcceptingContainersList (*scopeItemListPtr);
107+ return getAcceptingContainersList (
108+ *scopeItemListPtr, hotContainer, hotObject);
57109 }
58110
59111 // if a group is specified in groupToScopeItems and hotObject belongs to that
@@ -81,43 +133,88 @@ DestinationsToExploreGenerator::getAcceptingDestinations(
81133 groupToScopeItem,
82134 universe_.getEntityName (onlyGroupId),
83135 defaultScopeItems);
84- return getAcceptingContainersList (scopeItemList);
136+ return getAcceptingContainersList (scopeItemList, hotContainer, hotObject );
85137 }
86138 }
87139
88140 // use defaultScopeItems if there is no specialization for hotObject
89- return getAcceptingContainersList (defaultScopeItems);
141+ return getAcceptingContainersList (defaultScopeItems, hotContainer, hotObject );
90142}
91143
92144ReferenceList<const std::vector<entities::ContainerId>>
93145DestinationsToExploreGenerator::getAcceptingDestinations (
94- const interface::MoveToScopeItemsSpec& moveToScopeItems) {
146+ const interface::MoveToScopeItemsSpec& moveToScopeItems,
147+ const entities::ContainerId hotContainer) {
95148 if (!moveToScopeItems.objectToScopeItems ()->empty ()) {
96149 throw std::runtime_error (
97150 " this function requires that objectToScopeItems is empty" );
98151 }
99- return getAcceptingContainersList (*moveToScopeItems.defaultScopeItems ());
152+ return getAcceptingContainersList (
153+ *moveToScopeItems.defaultScopeItems (),
154+ hotContainer,
155+ /* hotObject=*/ std::nullopt );
100156}
101157
102158ReferenceList<const std::vector<entities::ContainerId>>
103159DestinationsToExploreGenerator::getAcceptingContainersList (
104- const interface::ScopeItemList& scopeItemList) {
105- ReferenceList<const std::vector<entities::ContainerId>> destinations;
160+ const interface::ScopeItemList& scopeItemList,
161+ const entities::ContainerId hotContainer,
162+ std::optional<entities::ObjectId> hotObject) {
106163 auto & scopeName = *scopeItemList.scopeName ();
107164 auto scopeId = universe_.getScopeId (scopeName);
108165 auto & scope = universe_.getScope (scopeId);
109- if (scopeItemList.scopeItems ().has_value ()) {
110- auto & scopeItemNames = scopeItemList.scopeItems ().value ();
111- for (auto & scopeItemName : scopeItemNames) {
112- auto scopeItemId = universe_.getScopeItemId (scopeId, scopeItemName);
166+
167+ std::optional<ObjectDimensionBasedFilter> objectDimensionFilter;
168+ if (scopeItemList.filter ().has_value ()) {
169+ if (!hotObject.has_value ()) {
170+ throw std::runtime_error (
171+ " ScopeItemList.filter requires the moving object, but this move type does not provide one" );
172+ }
173+ const auto & filter = *scopeItemList.filter ();
174+ switch (filter.getType ()) {
175+ case interface::ScopeItemFilter::Type::objectDimensionBased: {
176+ const auto & filterSpec = filter.get_objectDimensionBased ();
177+ const auto & dimension = universe_.getObjects ()
178+ .getDimension (universe_.getDimensionId (
179+ *filterSpec.dimensionName ()))
180+ .only ();
181+ if (!dimension.isDynamic () || dimension.getScopeId () != scopeId)
182+ [[unlikely]] {
183+ throw std::runtime_error (
184+ fmt::format (
185+ " ObjectDimensionBasedFilter dimension '{}' is expected to be dynamic on scope '{}'" ,
186+ *filterSpec.dimensionName (),
187+ scopeName));
188+ }
189+ const auto hotContainerScopeItem = scope.getScopeItemId (hotContainer);
190+ objectDimensionFilter.emplace (
191+ filterSpec, dimension, scopeId, *hotObject, hotContainerScopeItem);
192+ break ;
193+ }
194+ case interface::ScopeItemFilter::Type::__EMPTY__:
195+ throw std::runtime_error (" ScopeItemFilter is not set" );
196+ }
197+ }
198+
199+ ReferenceList<const std::vector<entities::ContainerId>> destinations;
200+ const auto addToDestinations = [&](entities::ScopeItemId scopeItemId) {
201+ if (!objectDimensionFilter.has_value () ||
202+ objectDimensionFilter->includes (scopeItemId)) {
113203 destinations.emplace_back (getAcceptingContainers (scopeItemId, scope));
114204 }
205+ };
206+
207+ if (scopeItemList.scopeItems ().has_value ()) {
208+ const auto & scopeItemNames = scopeItemList.scopeItems ().value ();
209+ for (const auto & scopeItemName : scopeItemNames) {
210+ addToDestinations (universe_.getScopeItemId (scopeId, scopeItemName));
211+ }
115212 } else {
116213 // if scopeItems are not explicitly listed, all scopeItems in the specified
117214 // scopeName are taken
118215 auto & scopeItemIds = scope.getScopeItemIds ();
119216 for (auto scopeItemId : scopeItemIds) {
120- destinations. emplace_back ( getAcceptingContainers ( scopeItemId, scope) );
217+ addToDestinations ( scopeItemId);
121218 }
122219 }
123220 return destinations;
0 commit comments