service-capacity-modeling 0.3.73__py3-none-any.whl → 0.3.75__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of service-capacity-modeling might be problematic. Click here for more details.
- service_capacity_modeling/hardware/profiles/shapes/aws/auto_i3en.json +172 -0
- service_capacity_modeling/hardware/profiles/shapes/aws/auto_i4i.json +220 -0
- service_capacity_modeling/hardware/profiles/shapes/aws/manual_instances.json +0 -184
- service_capacity_modeling/interface.py +29 -5
- service_capacity_modeling/models/common.py +257 -180
- service_capacity_modeling/models/org/netflix/cassandra.py +14 -9
- service_capacity_modeling/models/org/netflix/evcache.py +10 -18
- service_capacity_modeling/tools/instance_families.py +14 -6
- {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.75.dist-info}/METADATA +1 -1
- {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.75.dist-info}/RECORD +14 -12
- {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.75.dist-info}/WHEEL +0 -0
- {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.75.dist-info}/entry_points.txt +0 -0
- {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.75.dist-info}/licenses/LICENSE +0 -0
- {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.75.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pylint: disable=too-many-lines
|
|
1
2
|
import logging
|
|
2
3
|
import math
|
|
3
4
|
import random
|
|
@@ -6,8 +7,12 @@ from typing import Callable
|
|
|
6
7
|
from typing import Dict
|
|
7
8
|
from typing import List
|
|
8
9
|
from typing import Optional
|
|
10
|
+
from typing import Set
|
|
9
11
|
from typing import Tuple
|
|
10
12
|
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
from pydantic import Field
|
|
15
|
+
|
|
11
16
|
from service_capacity_modeling.hardware import shapes
|
|
12
17
|
from service_capacity_modeling.interface import AVG_ITEM_SIZE_BYTES
|
|
13
18
|
from service_capacity_modeling.interface import Buffer
|
|
@@ -63,6 +68,23 @@ def _QOS(tier: int) -> float:
|
|
|
63
68
|
return 1
|
|
64
69
|
|
|
65
70
|
|
|
71
|
+
def combine_buffer_ratios(left: Optional[float], right: Optional[float]) -> float:
|
|
72
|
+
"""
|
|
73
|
+
Strategy for how two buffers for the same component are combined.
|
|
74
|
+
- Multiply two buffers by multiplying if both are not None
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
if left is None and right is None:
|
|
78
|
+
raise ValueError("Cannot combine buffer ratios when both values are None")
|
|
79
|
+
if left is None:
|
|
80
|
+
assert right is not None # MyPy
|
|
81
|
+
return right
|
|
82
|
+
if right is None:
|
|
83
|
+
assert left is not None # MyPy
|
|
84
|
+
return left
|
|
85
|
+
return left * right
|
|
86
|
+
|
|
87
|
+
|
|
66
88
|
def _sqrt_staffed_cores(rps: float, latency_s: float, qos: float) -> int:
|
|
67
89
|
# Square root staffing
|
|
68
90
|
# s = a + Q*sqrt(a)
|
|
@@ -153,18 +175,31 @@ def normalize_cores(
|
|
|
153
175
|
target_shape: Instance,
|
|
154
176
|
reference_shape: Optional[Instance] = None,
|
|
155
177
|
) -> int:
|
|
156
|
-
"""Calculates equivalent
|
|
178
|
+
"""Calculates equivalent CPU on a target shape relative to a reference
|
|
157
179
|
|
|
158
180
|
Takes into account relative core frequency and IPC factor from the hardware
|
|
159
181
|
description to give a rough estimate of how many equivalent cores you need
|
|
160
182
|
in a target_shape to have the core_count number of cores on the reference_shape
|
|
161
183
|
"""
|
|
184
|
+
# Normalize the core count the same as CPUs
|
|
185
|
+
return _normalize_cpu(
|
|
186
|
+
cpu_count=core_count,
|
|
187
|
+
target_shape=target_shape,
|
|
188
|
+
reference_shape=reference_shape,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _normalize_cpu(
|
|
193
|
+
cpu_count: float,
|
|
194
|
+
target_shape: Instance,
|
|
195
|
+
reference_shape: Optional[Instance] = None,
|
|
196
|
+
) -> int:
|
|
162
197
|
if reference_shape is None:
|
|
163
198
|
reference_shape = default_reference_shape
|
|
164
199
|
|
|
165
200
|
target_speed = target_shape.cpu_ghz * target_shape.cpu_ipc_scale
|
|
166
201
|
reference_speed = reference_shape.cpu_ghz * reference_shape.cpu_ipc_scale
|
|
167
|
-
return max(1, math.ceil(
|
|
202
|
+
return max(1, math.ceil(cpu_count / (target_speed / reference_speed)))
|
|
168
203
|
|
|
169
204
|
|
|
170
205
|
def _reserved_headroom(
|
|
@@ -218,8 +253,6 @@ def cpu_headroom_target(instance: Instance, buffers: Optional[Buffers] = None) -
|
|
|
218
253
|
# When someone asks for the key, return any buffers that
|
|
219
254
|
# influence the component in the value
|
|
220
255
|
_default_buffer_fallbacks: Dict[str, List[str]] = {
|
|
221
|
-
BufferComponent.compute: [BufferComponent.cpu],
|
|
222
|
-
BufferComponent.storage: [BufferComponent.disk],
|
|
223
256
|
BufferComponent.cpu: [BufferComponent.compute],
|
|
224
257
|
BufferComponent.network: [BufferComponent.compute],
|
|
225
258
|
BufferComponent.memory: [BufferComponent.storage],
|
|
@@ -227,6 +260,44 @@ _default_buffer_fallbacks: Dict[str, List[str]] = {
|
|
|
227
260
|
}
|
|
228
261
|
|
|
229
262
|
|
|
263
|
+
def _expand_components(
|
|
264
|
+
components: List[str],
|
|
265
|
+
component_fallbacks: Optional[Dict[str, List[str]]] = None,
|
|
266
|
+
) -> Set[str]:
|
|
267
|
+
"""Expand and dedupe components to include their fallbacks
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
components: List of component names to expand
|
|
271
|
+
component_fallbacks: Optional fallback mapping (uses default if None)
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Set of expanded component names including fallbacks
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
# Semantically it does not make sense to fetch buffers for the generic category
|
|
278
|
+
generic_components = [c for c in components if BufferComponent.is_generic(c)]
|
|
279
|
+
if generic_components:
|
|
280
|
+
all_specific_components = [
|
|
281
|
+
c for c in BufferComponent if BufferComponent.is_specific(c)
|
|
282
|
+
]
|
|
283
|
+
raise ValueError(
|
|
284
|
+
f"Only specific components allowed. Generic components found: "
|
|
285
|
+
f"{', '.join(str(c) for c in generic_components)}. "
|
|
286
|
+
f"Use specific components instead: "
|
|
287
|
+
f"{', '.join(str(c) for c in all_specific_components)}"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if component_fallbacks is None:
|
|
291
|
+
component_fallbacks = _default_buffer_fallbacks
|
|
292
|
+
|
|
293
|
+
expanded_components = set(components)
|
|
294
|
+
for component in components:
|
|
295
|
+
expanded_components = expanded_components | set(
|
|
296
|
+
component_fallbacks.get(component, [])
|
|
297
|
+
)
|
|
298
|
+
return expanded_components
|
|
299
|
+
|
|
300
|
+
|
|
230
301
|
def buffer_for_components(
|
|
231
302
|
buffers: Buffers,
|
|
232
303
|
components: List[str],
|
|
@@ -245,14 +316,7 @@ def buffer_for_components(
|
|
|
245
316
|
components: the components that ultimately matched after applying
|
|
246
317
|
source: All the component buffers that made up the composite ratio
|
|
247
318
|
"""
|
|
248
|
-
|
|
249
|
-
component_fallbacks = _default_buffer_fallbacks
|
|
250
|
-
|
|
251
|
-
unique_components = set(components)
|
|
252
|
-
for component in components:
|
|
253
|
-
unique_components = unique_components | set(
|
|
254
|
-
component_fallbacks.get(component, [])
|
|
255
|
-
)
|
|
319
|
+
expanded_components = _expand_components(components, component_fallbacks)
|
|
256
320
|
|
|
257
321
|
desired = {k: v.model_copy() for k, v in buffers.desired.items()}
|
|
258
322
|
if current_capacity:
|
|
@@ -266,14 +330,14 @@ def buffer_for_components(
|
|
|
266
330
|
ratio = 1.0
|
|
267
331
|
sources = {}
|
|
268
332
|
for name, buffer in desired.items():
|
|
269
|
-
if
|
|
333
|
+
if expanded_components.intersection(buffer.components):
|
|
270
334
|
sources[name] = buffer
|
|
271
|
-
ratio
|
|
335
|
+
ratio = combine_buffer_ratios(ratio, buffer.ratio)
|
|
272
336
|
if not sources:
|
|
273
337
|
ratio = buffers.default.ratio
|
|
274
338
|
|
|
275
339
|
return Buffer(
|
|
276
|
-
ratio=ratio, components=sorted(list(
|
|
340
|
+
ratio=ratio, components=sorted(list(expanded_components)), sources=sources
|
|
277
341
|
)
|
|
278
342
|
|
|
279
343
|
|
|
@@ -483,7 +547,7 @@ def compute_stateful_zone( # pylint: disable=too-many-positional-arguments
|
|
|
483
547
|
# When initially provisioniong we don't want to attach more than
|
|
484
548
|
# 1/3 the maximum volume size in one node (preferring more nodes
|
|
485
549
|
# with smaller volumes)
|
|
486
|
-
max_size = drive.max_size_gib / 3
|
|
550
|
+
max_size = math.ceil(drive.max_size_gib / 3)
|
|
487
551
|
if ebs_gib > max_size > 0:
|
|
488
552
|
ratio = ebs_gib / max_size
|
|
489
553
|
count = max(cluster_size(math.ceil(count * ratio)), min_count)
|
|
@@ -717,180 +781,188 @@ def merge_plan(
|
|
|
717
781
|
)
|
|
718
782
|
|
|
719
783
|
|
|
720
|
-
|
|
721
|
-
scale = 0
|
|
722
|
-
preserve = False
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
784
|
+
class DerivedBuffers(BaseModel):
|
|
785
|
+
scale: float = Field(default=1, gt=0)
|
|
786
|
+
preserve: bool = False
|
|
787
|
+
# When present, this is the maximum ratio of the current usage
|
|
788
|
+
ceiling: Optional[float] = Field(
|
|
789
|
+
default=None,
|
|
790
|
+
gt=0,
|
|
791
|
+
)
|
|
792
|
+
# When present, this is the minimum ratio of the current usage
|
|
793
|
+
floor: Optional[float] = Field(default=None, gt=0)
|
|
794
|
+
|
|
795
|
+
@staticmethod
|
|
796
|
+
def for_components(
|
|
797
|
+
buffer: Dict[str, Buffer],
|
|
798
|
+
components: List[str],
|
|
799
|
+
component_fallbacks: Optional[Dict[str, List[str]]] = None,
|
|
800
|
+
):
|
|
801
|
+
expanded_components = _expand_components(components, component_fallbacks)
|
|
802
|
+
|
|
803
|
+
scale = 1.0
|
|
804
|
+
preserve = False
|
|
805
|
+
ceiling = None
|
|
806
|
+
floor = None
|
|
807
|
+
|
|
808
|
+
for bfr in buffer.values():
|
|
809
|
+
if not expanded_components.intersection(bfr.components):
|
|
810
|
+
continue
|
|
811
|
+
|
|
812
|
+
if bfr.intent in [
|
|
813
|
+
BufferIntent.scale,
|
|
814
|
+
BufferIntent.scale_up,
|
|
815
|
+
BufferIntent.scale_down,
|
|
816
|
+
]:
|
|
817
|
+
scale = combine_buffer_ratios(scale, bfr.ratio)
|
|
818
|
+
if bfr.intent == BufferIntent.scale_up:
|
|
819
|
+
floor = 1 # Create a floor of 1.0x the current usage
|
|
820
|
+
if bfr.intent == BufferIntent.scale_down:
|
|
821
|
+
ceiling = 1 # Create a ceiling of 1.0x the current usage
|
|
731
822
|
if bfr.intent == BufferIntent.preserve:
|
|
732
823
|
preserve = True
|
|
733
824
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
def get_cores_from_current_capacity(
|
|
738
|
-
current_capacity: CurrentClusterCapacity, buffers: Buffers, instance: Instance
|
|
739
|
-
):
|
|
740
|
-
# compute cores required per zone
|
|
741
|
-
cpu_success_buffer = (1 - cpu_headroom_target(instance, buffers)) * 100
|
|
742
|
-
current_cpu_utilization = current_capacity.cpu_utilization.mid
|
|
743
|
-
|
|
744
|
-
if current_capacity.cluster_instance is None:
|
|
745
|
-
cluster_instance = shapes.instance(current_capacity.cluster_instance_name)
|
|
746
|
-
else:
|
|
747
|
-
cluster_instance = current_capacity.cluster_instance
|
|
748
|
-
|
|
749
|
-
current_cores = cluster_instance.cpu * current_capacity.cluster_instance_count.mid
|
|
750
|
-
|
|
751
|
-
scale, preserve = derived_buffer_for_component(buffers.derived, ["compute", "cpu"])
|
|
752
|
-
# Scale and preserve for the same component should not be passed together.
|
|
753
|
-
# If user passes it, then scale will be preferred over preserve.
|
|
754
|
-
if scale > 0:
|
|
755
|
-
# if the new cpu core is less than the current,
|
|
756
|
-
# then take no action and return the current cpu cores
|
|
757
|
-
new_cpu_utilization = current_cpu_utilization * scale
|
|
758
|
-
core_scale_up_factor = max(1.0, new_cpu_utilization / cpu_success_buffer)
|
|
759
|
-
return math.ceil(current_cores * core_scale_up_factor)
|
|
760
|
-
|
|
761
|
-
if preserve:
|
|
762
|
-
return current_cores
|
|
763
|
-
|
|
764
|
-
return int(current_cores * (current_cpu_utilization / cpu_success_buffer))
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
def get_memory_from_current_capacity(
|
|
768
|
-
current_capacity: CurrentClusterCapacity, buffers: Buffers
|
|
769
|
-
):
|
|
770
|
-
# compute memory required per zone
|
|
771
|
-
current_memory_utilization = (
|
|
772
|
-
current_capacity.memory_utilization_gib.mid
|
|
773
|
-
* current_capacity.cluster_instance_count.mid
|
|
774
|
-
)
|
|
775
|
-
|
|
776
|
-
if current_capacity.cluster_instance is None:
|
|
777
|
-
cluster_instance = shapes.instance(current_capacity.cluster_instance_name)
|
|
778
|
-
else:
|
|
779
|
-
cluster_instance = current_capacity.cluster_instance
|
|
780
|
-
|
|
781
|
-
zonal_ram_allocated = (
|
|
782
|
-
cluster_instance.ram_gib * current_capacity.cluster_instance_count.mid
|
|
783
|
-
)
|
|
784
|
-
|
|
785
|
-
# These are the desired buffers
|
|
786
|
-
memory_buffer = buffer_for_components(
|
|
787
|
-
buffers=buffers, components=[BufferComponent.memory]
|
|
788
|
-
)
|
|
789
|
-
|
|
790
|
-
scale, preserve = derived_buffer_for_component(
|
|
791
|
-
buffers.derived, ["memory", "storage"]
|
|
792
|
-
)
|
|
793
|
-
# Scale and preserve for the same component should not be passed together.
|
|
794
|
-
# If user passes it, then scale will be preferred over preserve.
|
|
795
|
-
if scale > 0:
|
|
796
|
-
# if the new required memory is less than the current,
|
|
797
|
-
# then take no action and return the current ram
|
|
798
|
-
return max(
|
|
799
|
-
current_memory_utilization * scale * memory_buffer.ratio,
|
|
800
|
-
zonal_ram_allocated,
|
|
825
|
+
return DerivedBuffers(
|
|
826
|
+
scale=scale, preserve=preserve, ceiling=ceiling, floor=floor
|
|
801
827
|
)
|
|
802
828
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
829
|
+
def calculate_requirement(
|
|
830
|
+
self,
|
|
831
|
+
current_usage: float,
|
|
832
|
+
existing_capacity: float,
|
|
833
|
+
desired_buffer_ratio: float = 1.0,
|
|
834
|
+
) -> float:
|
|
835
|
+
if self.preserve:
|
|
836
|
+
return existing_capacity
|
|
837
|
+
|
|
838
|
+
requirement = self.scale * current_usage * desired_buffer_ratio
|
|
839
|
+
if self.ceiling is not None:
|
|
840
|
+
requirement = min(requirement, self.ceiling * existing_capacity)
|
|
841
|
+
if self.floor is not None:
|
|
842
|
+
requirement = max(requirement, self.floor * existing_capacity)
|
|
843
|
+
|
|
844
|
+
return requirement
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
class RequirementFromCurrentCapacity(BaseModel):
|
|
848
|
+
current_capacity: CurrentClusterCapacity
|
|
849
|
+
buffers: Buffers
|
|
850
|
+
|
|
851
|
+
@property
|
|
852
|
+
def current_instance(self) -> Instance:
|
|
853
|
+
if self.current_capacity.cluster_instance is not None:
|
|
854
|
+
return self.current_capacity.cluster_instance
|
|
855
|
+
return shapes.instance(self.current_capacity.cluster_instance_name)
|
|
856
|
+
|
|
857
|
+
def cpu(self, instance_candidate: Instance) -> int:
|
|
858
|
+
current_cpu_util = self.current_capacity.cpu_utilization.mid / 100
|
|
859
|
+
current_total_cpu = (
|
|
860
|
+
self.current_instance.cpu * self.current_capacity.cluster_instance_count.mid
|
|
861
|
+
)
|
|
831
862
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
)
|
|
835
|
-
# Scale and preserve for the same component should not be passed together.
|
|
836
|
-
# If user passes it, then scale will be preferred over preserve.
|
|
837
|
-
if scale > 0:
|
|
838
|
-
# if the new required network is less than the current,
|
|
839
|
-
# then take no action and return the current bandwidth
|
|
840
|
-
return max(
|
|
841
|
-
current_network_utilization * scale * network_buffer.ratio,
|
|
842
|
-
zonal_network_allocated,
|
|
863
|
+
derived_buffers = DerivedBuffers.for_components(
|
|
864
|
+
self.buffers.derived, [BufferComponent.cpu]
|
|
843
865
|
)
|
|
844
866
|
|
|
845
|
-
|
|
846
|
-
|
|
867
|
+
# The ideal CPU% that accomodates the headroom + desired buffer, sometimes
|
|
868
|
+
# referred to as the "success buffer"
|
|
869
|
+
target_cpu_util = 1 - cpu_headroom_target(instance_candidate, self.buffers)
|
|
870
|
+
# current_util / target_util ratio indicates CPU scaling direction:
|
|
871
|
+
# > 1: scale up, < 1: scale down, = 1: no change needed
|
|
872
|
+
used_cpu = (current_cpu_util / target_cpu_util) * current_total_cpu
|
|
873
|
+
return math.ceil(
|
|
874
|
+
# Desired buffer is omitted because the cpu_headroom already
|
|
875
|
+
# includes it
|
|
876
|
+
derived_buffers.calculate_requirement(
|
|
877
|
+
current_usage=used_cpu,
|
|
878
|
+
existing_capacity=current_total_cpu,
|
|
879
|
+
)
|
|
880
|
+
)
|
|
847
881
|
|
|
848
|
-
|
|
882
|
+
@property
|
|
883
|
+
def mem_gib(self) -> float:
|
|
884
|
+
current_memory_utilization = (
|
|
885
|
+
self.current_capacity.memory_utilization_gib.mid
|
|
886
|
+
* self.current_capacity.cluster_instance_count.mid
|
|
887
|
+
)
|
|
888
|
+
zonal_ram_allocated = (
|
|
889
|
+
self.current_instance.ram_gib
|
|
890
|
+
* self.current_capacity.cluster_instance_count.mid
|
|
891
|
+
)
|
|
849
892
|
|
|
893
|
+
desired_buffer = buffer_for_components(
|
|
894
|
+
buffers=self.buffers, components=[BufferComponent.memory]
|
|
895
|
+
)
|
|
896
|
+
derived_buffer = DerivedBuffers.for_components(
|
|
897
|
+
self.buffers.derived, [BufferComponent.memory]
|
|
898
|
+
)
|
|
850
899
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
current_capacity.disk_utilization_gib.mid
|
|
857
|
-
* current_capacity.cluster_instance_count.mid
|
|
858
|
-
)
|
|
900
|
+
return derived_buffer.calculate_requirement(
|
|
901
|
+
current_usage=current_memory_utilization,
|
|
902
|
+
existing_capacity=zonal_ram_allocated,
|
|
903
|
+
desired_buffer_ratio=desired_buffer.ratio,
|
|
904
|
+
)
|
|
859
905
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
906
|
+
@property
|
|
907
|
+
def disk_gib(self) -> int:
|
|
908
|
+
current_cluster_disk_util_gib = (
|
|
909
|
+
self.current_capacity.disk_utilization_gib.mid
|
|
910
|
+
* self.current_capacity.cluster_instance_count.mid
|
|
911
|
+
)
|
|
912
|
+
current_node_disk_gib = (
|
|
913
|
+
self.current_instance.drive.max_size_gib
|
|
914
|
+
if self.current_instance.drive is not None
|
|
915
|
+
else (
|
|
916
|
+
self.current_capacity.cluster_drive.size_gib
|
|
917
|
+
if self.current_capacity.cluster_drive is not None
|
|
918
|
+
else 0
|
|
919
|
+
)
|
|
920
|
+
)
|
|
864
921
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
922
|
+
zonal_disk_allocated = (
|
|
923
|
+
current_node_disk_gib * self.current_capacity.cluster_instance_count.mid
|
|
924
|
+
)
|
|
925
|
+
# These are the desired buffers
|
|
926
|
+
disk_buffer = buffer_for_components(
|
|
927
|
+
buffers=self.buffers, components=[BufferComponent.disk]
|
|
928
|
+
)
|
|
870
929
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
930
|
+
derived_buffer = DerivedBuffers.for_components(
|
|
931
|
+
self.buffers.derived, [BufferComponent.disk]
|
|
932
|
+
)
|
|
933
|
+
required_disk = derived_buffer.calculate_requirement(
|
|
934
|
+
current_usage=current_cluster_disk_util_gib,
|
|
935
|
+
existing_capacity=zonal_disk_allocated,
|
|
936
|
+
desired_buffer_ratio=disk_buffer.ratio,
|
|
937
|
+
)
|
|
938
|
+
return math.ceil(required_disk)
|
|
874
939
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
940
|
+
@property
|
|
941
|
+
def network_mbps(self) -> int:
|
|
942
|
+
current_network_utilization = (
|
|
943
|
+
self.current_capacity.network_utilization_mbps.mid
|
|
944
|
+
* self.current_capacity.cluster_instance_count.mid
|
|
945
|
+
)
|
|
946
|
+
zonal_network_allocated = (
|
|
947
|
+
self.current_instance.net_mbps
|
|
948
|
+
* self.current_capacity.cluster_instance_count.mid
|
|
949
|
+
)
|
|
879
950
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
return max(
|
|
887
|
-
current_disk_utilization * scale * disk_buffer.ratio, zonal_disk_allocated
|
|
951
|
+
# These are the desired buffers
|
|
952
|
+
network_buffer = buffer_for_components(
|
|
953
|
+
buffers=self.buffers, components=[BufferComponent.network]
|
|
954
|
+
)
|
|
955
|
+
derived_buffer = DerivedBuffers.for_components(
|
|
956
|
+
self.buffers.derived, [BufferComponent.network]
|
|
888
957
|
)
|
|
889
|
-
if preserve:
|
|
890
|
-
# preserve the current disk size for the zone
|
|
891
|
-
return zonal_disk_allocated
|
|
892
958
|
|
|
893
|
-
|
|
959
|
+
return math.ceil(
|
|
960
|
+
derived_buffer.calculate_requirement(
|
|
961
|
+
current_usage=current_network_utilization,
|
|
962
|
+
existing_capacity=zonal_network_allocated,
|
|
963
|
+
desired_buffer_ratio=network_buffer.ratio,
|
|
964
|
+
)
|
|
965
|
+
)
|
|
894
966
|
|
|
895
967
|
|
|
896
968
|
def zonal_requirements_from_current(
|
|
@@ -901,20 +973,25 @@ def zonal_requirements_from_current(
|
|
|
901
973
|
) -> CapacityRequirement:
|
|
902
974
|
if current_cluster is not None and current_cluster.zonal[0] is not None:
|
|
903
975
|
current_capacity: CurrentClusterCapacity = current_cluster.zonal[0]
|
|
904
|
-
|
|
905
|
-
|
|
976
|
+
|
|
977
|
+
# Adjust the CPUs (vCPU + cores) based on generation / instance type
|
|
978
|
+
requirement = RequirementFromCurrentCapacity(
|
|
979
|
+
current_capacity=current_capacity,
|
|
980
|
+
buffers=buffers,
|
|
981
|
+
)
|
|
982
|
+
normalized_cpu = _normalize_cpu(
|
|
983
|
+
requirement.cpu(instance),
|
|
906
984
|
instance,
|
|
907
985
|
reference_shape,
|
|
908
986
|
)
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
needed_memory_gib =
|
|
913
|
-
needed_disk_gib = get_disk_from_current_capacity(current_capacity, buffers)
|
|
987
|
+
|
|
988
|
+
needed_network_mbps = requirement.network_mbps
|
|
989
|
+
needed_disk_gib = requirement.disk_gib
|
|
990
|
+
needed_memory_gib = requirement.mem_gib
|
|
914
991
|
|
|
915
992
|
return CapacityRequirement(
|
|
916
993
|
requirement_type="zonal-capacity",
|
|
917
|
-
cpu_cores=certain_int(
|
|
994
|
+
cpu_cores=certain_int(normalized_cpu),
|
|
918
995
|
mem_gib=certain_float(needed_memory_gib),
|
|
919
996
|
disk_gib=certain_float(needed_disk_gib),
|
|
920
997
|
network_mbps=certain_float(needed_network_mbps),
|
|
@@ -35,7 +35,7 @@ from service_capacity_modeling.interface import ServiceCapacity
|
|
|
35
35
|
from service_capacity_modeling.models import CapacityModel
|
|
36
36
|
from service_capacity_modeling.models.common import buffer_for_components
|
|
37
37
|
from service_capacity_modeling.models.common import compute_stateful_zone
|
|
38
|
-
from service_capacity_modeling.models.common import
|
|
38
|
+
from service_capacity_modeling.models.common import DerivedBuffers
|
|
39
39
|
from service_capacity_modeling.models.common import get_effective_disk_per_node_gib
|
|
40
40
|
from service_capacity_modeling.models.common import network_services
|
|
41
41
|
from service_capacity_modeling.models.common import normalize_cores
|
|
@@ -181,7 +181,9 @@ def _zonal_requirement_for_new_cluster(
|
|
|
181
181
|
)
|
|
182
182
|
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
# pylint: disable=too-many-locals
|
|
185
|
+
# pylint: disable=too-many-positional-arguments
|
|
186
|
+
def _estimate_cassandra_requirement(
|
|
185
187
|
instance: Instance,
|
|
186
188
|
desires: CapacityDesires,
|
|
187
189
|
working_set: float,
|
|
@@ -205,19 +207,22 @@ def _estimate_cassandra_requirement( # pylint: disable=too-many-positional-argu
|
|
|
205
207
|
# If the cluster is already provisioned
|
|
206
208
|
if current_capacity and desires.current_clusters is not None:
|
|
207
209
|
capacity_requirement = zonal_requirements_from_current(
|
|
208
|
-
desires.current_clusters,
|
|
210
|
+
desires.current_clusters,
|
|
211
|
+
desires.buffers,
|
|
212
|
+
instance,
|
|
213
|
+
reference_shape,
|
|
209
214
|
)
|
|
210
|
-
|
|
211
|
-
desires.buffers.derived, [
|
|
215
|
+
disk_derived_buffer = DerivedBuffers.for_components(
|
|
216
|
+
desires.buffers.derived, [BufferComponent.disk]
|
|
212
217
|
)
|
|
213
218
|
disk_used_gib = (
|
|
214
219
|
current_capacity.disk_utilization_gib.mid
|
|
215
220
|
* current_capacity.cluster_instance_count.mid
|
|
216
|
-
*
|
|
217
|
-
)
|
|
218
|
-
_, memory_preserve = derived_buffer_for_component(
|
|
219
|
-
desires.buffers.derived, ["storage", "memory"]
|
|
221
|
+
* disk_derived_buffer.scale
|
|
220
222
|
)
|
|
223
|
+
memory_preserve = DerivedBuffers.for_components(
|
|
224
|
+
desires.buffers.derived, [BufferComponent.memory]
|
|
225
|
+
).preserve
|
|
221
226
|
else:
|
|
222
227
|
# If the cluster is not yet provisioned
|
|
223
228
|
capacity_requirement = _zonal_requirement_for_new_cluster(
|
|
@@ -33,13 +33,10 @@ from service_capacity_modeling.interface import Requirements
|
|
|
33
33
|
from service_capacity_modeling.models import CapacityModel
|
|
34
34
|
from service_capacity_modeling.models.common import buffer_for_components
|
|
35
35
|
from service_capacity_modeling.models.common import compute_stateful_zone
|
|
36
|
-
from service_capacity_modeling.models.common import get_cores_from_current_capacity
|
|
37
|
-
from service_capacity_modeling.models.common import get_disk_from_current_capacity
|
|
38
36
|
from service_capacity_modeling.models.common import get_effective_disk_per_node_gib
|
|
39
|
-
from service_capacity_modeling.models.common import get_memory_from_current_capacity
|
|
40
|
-
from service_capacity_modeling.models.common import get_network_from_current_capacity
|
|
41
37
|
from service_capacity_modeling.models.common import network_services
|
|
42
38
|
from service_capacity_modeling.models.common import normalize_cores
|
|
39
|
+
from service_capacity_modeling.models.common import RequirementFromCurrentCapacity
|
|
43
40
|
from service_capacity_modeling.models.common import simple_network_mbps
|
|
44
41
|
from service_capacity_modeling.models.common import sqrt_staffed_cores
|
|
45
42
|
from service_capacity_modeling.models.common import working_set_from_drive_and_slo
|
|
@@ -104,25 +101,20 @@ def calculate_vitals_for_capacity_planner(
|
|
|
104
101
|
)
|
|
105
102
|
if not current_capacity:
|
|
106
103
|
return needed_cores, needed_network_mbps, needed_memory_gib, needed_disk_gib
|
|
104
|
+
requirements = RequirementFromCurrentCapacity(
|
|
105
|
+
current_capacity=current_capacity,
|
|
106
|
+
buffers=desires.buffers,
|
|
107
|
+
)
|
|
107
108
|
needed_cores = normalize_cores(
|
|
108
|
-
core_count=
|
|
109
|
-
current_capacity, desires.buffers, instance
|
|
110
|
-
),
|
|
109
|
+
core_count=requirements.cpu(instance_candidate=instance),
|
|
111
110
|
target_shape=instance,
|
|
112
111
|
reference_shape=current_capacity.cluster_instance,
|
|
113
112
|
)
|
|
114
|
-
needed_network_mbps =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
needed_memory_gib = get_memory_from_current_capacity(
|
|
118
|
-
current_capacity, desires.buffers
|
|
113
|
+
needed_network_mbps = requirements.network_mbps
|
|
114
|
+
needed_disk_gib = (
|
|
115
|
+
requirements.disk_gib if current_capacity.cluster_drive is not None else 0.0
|
|
119
116
|
)
|
|
120
|
-
|
|
121
|
-
needed_disk_gib = 0.0
|
|
122
|
-
else:
|
|
123
|
-
needed_disk_gib = get_disk_from_current_capacity(
|
|
124
|
-
current_capacity, desires.buffers
|
|
125
|
-
)
|
|
117
|
+
needed_memory_gib = requirements.mem_gib
|
|
126
118
|
return needed_cores, needed_network_mbps, needed_memory_gib, needed_disk_gib
|
|
127
119
|
|
|
128
120
|
|