service-capacity-modeling 0.3.105__tar.gz → 0.3.107__tar.gz
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.
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/PKG-INFO +1 -1
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/capacity_planner.py +262 -10
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/interface.py +48 -1
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/__init__.py +46 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/common.py +40 -8
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/aurora.py +6 -1
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/cassandra.py +80 -34
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/evcache.py +87 -26
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/kafka.py +39 -5
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/key_value.py +44 -2
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/stateless_java.py +53 -11
- service_capacity_modeling-0.3.107/service_capacity_modeling/models/plan_comparison.py +523 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling.egg-info/PKG-INFO +1 -1
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling.egg-info/SOURCES.txt +2 -0
- service_capacity_modeling-0.3.107/tests/test_plan_comparison.py +557 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/LICENSE +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/README.md +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/__init__.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/enum_utils.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/__init__.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/__init__.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/pricing/aws/3yr-reserved_ec2.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/pricing/aws/3yr-reserved_rds.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/pricing/aws/3yr-reserved_zz-overrides.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/profiles.txt +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c5.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c5a.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c5d.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c5n.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c6a.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c6i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c6id.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c7a.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c7i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_c8i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_db_r6g.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_db_r6i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_db_r7g.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_db_r7i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_i3en.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_i4i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_i7i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m4.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m5.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m5n.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m6a.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m6i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m6id.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m6idn.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m6in.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m7a.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m7i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_m8i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r4.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r5.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r5n.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r6a.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r6i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r6id.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r6idn.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r6in.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r7a.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r7i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/auto_r8i.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/manual_drives.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/manual_instances.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/hardware/profiles/shapes/aws/manual_services.json +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/headroom_strategy.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/__init__.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/__init__.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/control.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/counter.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/crdb.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/ddb.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/elasticsearch.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/entity.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/graphkv.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/iso_date_math.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/postgres.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/rds.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/time_series.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/time_series_config.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/wal.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/org/netflix/zookeeper.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/models/utils.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/stats.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/tools/__init__.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/tools/auto_shape.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/tools/capture_baseline_costs.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/tools/data/__init__.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/tools/fetch_pricing.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/tools/generate_missing.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling/tools/instance_families.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling.egg-info/dependency_links.txt +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling.egg-info/entry_points.txt +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling.egg-info/requires.txt +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/service_capacity_modeling.egg-info/top_level.txt +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/setup.cfg +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/setup.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_arguments.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_buffers.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_common.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_desire_merge.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_enum_utils.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_generate_scenarios.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_hardware.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_hardware_shapes.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_headroom_strategy.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_io2.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_model_dump.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_reproducible.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_simulation.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_utils.py +0 -0
- {service_capacity_modeling-0.3.105 → service_capacity_modeling-0.3.107}/tests/test_working_set.py +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
# pylint: disable=too-many-lines
|
|
2
3
|
import functools
|
|
3
4
|
import logging
|
|
4
5
|
import math
|
|
@@ -23,6 +24,9 @@ from service_capacity_modeling.interface import CapacityPlan
|
|
|
23
24
|
from service_capacity_modeling.interface import CapacityRegretParameters
|
|
24
25
|
from service_capacity_modeling.interface import CapacityRequirement
|
|
25
26
|
from service_capacity_modeling.interface import certain_float
|
|
27
|
+
from service_capacity_modeling.interface import ClusterCapacity
|
|
28
|
+
from service_capacity_modeling.interface import Clusters
|
|
29
|
+
from service_capacity_modeling.interface import CurrentClusterCapacity
|
|
26
30
|
from service_capacity_modeling.interface import DataShape
|
|
27
31
|
from service_capacity_modeling.interface import Drive
|
|
28
32
|
from service_capacity_modeling.interface import Hardware
|
|
@@ -33,10 +37,15 @@ from service_capacity_modeling.interface import Lifecycle
|
|
|
33
37
|
from service_capacity_modeling.interface import PlanExplanation
|
|
34
38
|
from service_capacity_modeling.interface import Platform
|
|
35
39
|
from service_capacity_modeling.interface import QueryPattern
|
|
40
|
+
from service_capacity_modeling.interface import RegionClusterCapacity
|
|
36
41
|
from service_capacity_modeling.interface import RegionContext
|
|
37
42
|
from service_capacity_modeling.interface import Requirements
|
|
43
|
+
from service_capacity_modeling.interface import ServiceCapacity
|
|
38
44
|
from service_capacity_modeling.interface import UncertainCapacityPlan
|
|
45
|
+
from service_capacity_modeling.interface import ZoneClusterCapacity
|
|
39
46
|
from service_capacity_modeling.models import CapacityModel
|
|
47
|
+
from service_capacity_modeling.models import CostAwareModel
|
|
48
|
+
from service_capacity_modeling.models.common import get_disk_size_gib
|
|
40
49
|
from service_capacity_modeling.models.common import merge_plan
|
|
41
50
|
from service_capacity_modeling.models.org import netflix
|
|
42
51
|
from service_capacity_modeling.models.utils import reduce_by_family
|
|
@@ -211,6 +220,78 @@ def _set_instance_objects(
|
|
|
211
220
|
)
|
|
212
221
|
|
|
213
222
|
|
|
223
|
+
def _extract_cluster_plan(
|
|
224
|
+
clusters: Sequence[CurrentClusterCapacity],
|
|
225
|
+
hardware: Hardware,
|
|
226
|
+
is_zonal: bool,
|
|
227
|
+
) -> Tuple[List[ClusterCapacity], List[CapacityRequirement]]:
|
|
228
|
+
"""Extract CapacityPlan components from current deployment.
|
|
229
|
+
|
|
230
|
+
Takes what's currently deployed and builds the ClusterCapacity and
|
|
231
|
+
CapacityRequirement objects needed for a CapacityPlan.
|
|
232
|
+
|
|
233
|
+
Drives are priced using hardware.price_drive() to get catalog pricing.
|
|
234
|
+
Cluster annual_cost is computed automatically from instance + drives.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
clusters: Current cluster capacities (must have cluster_type set)
|
|
238
|
+
hardware: Hardware catalog for the region (used to price drives)
|
|
239
|
+
is_zonal: True for ZoneClusterCapacity, False for RegionClusterCapacity
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Tuple of (capacities, requirements) for building CapacityPlan
|
|
243
|
+
|
|
244
|
+
Raises:
|
|
245
|
+
ValueError: If any cluster is missing cluster_type
|
|
246
|
+
"""
|
|
247
|
+
capacities: List[ClusterCapacity] = []
|
|
248
|
+
requirements: List[CapacityRequirement] = []
|
|
249
|
+
|
|
250
|
+
for current in clusters:
|
|
251
|
+
if current.cluster_type is None:
|
|
252
|
+
raise ValueError(
|
|
253
|
+
f"cluster_type is required for baseline extraction. "
|
|
254
|
+
f"Cluster '{current.cluster_instance_name}' is missing cluster_type."
|
|
255
|
+
)
|
|
256
|
+
cluster_type = current.cluster_type
|
|
257
|
+
instance = current.cluster_instance
|
|
258
|
+
if instance is None:
|
|
259
|
+
raise ValueError(
|
|
260
|
+
f"cluster_instance not resolved for '{current.cluster_instance_name}'"
|
|
261
|
+
)
|
|
262
|
+
count = int(current.cluster_instance_count.mid)
|
|
263
|
+
|
|
264
|
+
# Price the drive from hardware catalog (gets annual_cost_per_gib etc.)
|
|
265
|
+
attached_drives = []
|
|
266
|
+
if current.cluster_drive is not None:
|
|
267
|
+
attached_drives.append(hardware.price_drive(current.cluster_drive))
|
|
268
|
+
|
|
269
|
+
disk_gib = get_disk_size_gib(current.cluster_drive, instance)
|
|
270
|
+
|
|
271
|
+
capacity_cls = ZoneClusterCapacity if is_zonal else RegionClusterCapacity
|
|
272
|
+
capacities.append(
|
|
273
|
+
capacity_cls(
|
|
274
|
+
cluster_type=cluster_type,
|
|
275
|
+
count=count,
|
|
276
|
+
instance=instance,
|
|
277
|
+
attached_drives=attached_drives,
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
requirements.append(
|
|
282
|
+
CapacityRequirement(
|
|
283
|
+
requirement_type=cluster_type,
|
|
284
|
+
reference_shape=instance,
|
|
285
|
+
cpu_cores=certain_float(instance.cpu * count),
|
|
286
|
+
mem_gib=certain_float(instance.ram_gib * count),
|
|
287
|
+
network_mbps=certain_float(instance.net_mbps * count),
|
|
288
|
+
disk_gib=certain_float(disk_gib * count),
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
return capacities, requirements
|
|
293
|
+
|
|
294
|
+
|
|
214
295
|
def _allow_instance(
|
|
215
296
|
instance: Instance,
|
|
216
297
|
allowed_names: Sequence[str],
|
|
@@ -340,6 +421,7 @@ class CapacityPlanner:
|
|
|
340
421
|
) -> None:
|
|
341
422
|
self._shapes: HardwareShapes = shapes
|
|
342
423
|
self._models: Dict[str, CapacityModel] = {}
|
|
424
|
+
self._cluster_types: Dict[str, str] = {} # cluster_type -> model_name
|
|
343
425
|
|
|
344
426
|
self._default_num_simulations = default_num_simulations
|
|
345
427
|
self._default_num_results = default_num_results
|
|
@@ -351,6 +433,25 @@ class CapacityPlanner:
|
|
|
351
433
|
self.register_model(name, model)
|
|
352
434
|
|
|
353
435
|
def register_model(self, name: str, capacity_model: CapacityModel) -> None:
|
|
436
|
+
if isinstance(capacity_model, CostAwareModel):
|
|
437
|
+
# Validate required attributes
|
|
438
|
+
sn = getattr(capacity_model, "service_name", None)
|
|
439
|
+
ct = getattr(capacity_model, "cluster_type", None)
|
|
440
|
+
if not sn or not ct:
|
|
441
|
+
raise ValueError(
|
|
442
|
+
f"CostAwareModel '{name}' must define service_name and "
|
|
443
|
+
f"cluster_type (got service_name={sn!r}, cluster_type={ct!r})"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Duplicate cluster_type would cause double-counting
|
|
447
|
+
if ct in self._cluster_types:
|
|
448
|
+
raise ValueError(
|
|
449
|
+
f"Duplicate cluster_type '{ct}': '{name}' "
|
|
450
|
+
f"conflicts with '{self._cluster_types[ct]}'. Must be unique "
|
|
451
|
+
f"to avoid double-counting costs."
|
|
452
|
+
)
|
|
453
|
+
self._cluster_types[ct] = name
|
|
454
|
+
|
|
354
455
|
self._models[name] = capacity_model
|
|
355
456
|
|
|
356
457
|
@property
|
|
@@ -364,6 +465,23 @@ class CapacityPlanner:
|
|
|
364
465
|
def instance(self, name: str, region: Optional[str] = None) -> Instance:
|
|
365
466
|
return self.hardware_shapes.instance(name, region=region)
|
|
366
467
|
|
|
468
|
+
def _prepare_context(
|
|
469
|
+
self,
|
|
470
|
+
region: str,
|
|
471
|
+
num_regions: int,
|
|
472
|
+
) -> Tuple[Hardware, RegionContext]:
|
|
473
|
+
"""Prepare hardware and region context for capacity planning.
|
|
474
|
+
|
|
475
|
+
Loads hardware catalog for region and creates RegionContext.
|
|
476
|
+
"""
|
|
477
|
+
hardware = self._shapes.region(region)
|
|
478
|
+
context = RegionContext(
|
|
479
|
+
zones_in_region=hardware.zones_in_region,
|
|
480
|
+
services={n: s.model_copy(deep=True) for n, s in hardware.services.items()},
|
|
481
|
+
num_regions=num_regions,
|
|
482
|
+
)
|
|
483
|
+
return hardware, context
|
|
484
|
+
|
|
367
485
|
def _plan_percentiles( # pylint: disable=too-many-positional-arguments
|
|
368
486
|
self,
|
|
369
487
|
model_name: str,
|
|
@@ -587,6 +705,146 @@ class CapacityPlanner:
|
|
|
587
705
|
:num_results
|
|
588
706
|
]
|
|
589
707
|
|
|
708
|
+
def _get_model_costs(
|
|
709
|
+
self,
|
|
710
|
+
*,
|
|
711
|
+
model_name: str,
|
|
712
|
+
context: RegionContext,
|
|
713
|
+
desires: CapacityDesires,
|
|
714
|
+
zonal_clusters: Sequence[ClusterCapacity],
|
|
715
|
+
regional_clusters: Sequence[ClusterCapacity],
|
|
716
|
+
extra_model_arguments: Dict[str, Any],
|
|
717
|
+
) -> Tuple[Dict[str, float], List[ServiceCapacity]]:
|
|
718
|
+
"""Get total costs for a model and any models it composes with."""
|
|
719
|
+
costs: Dict[str, float] = {}
|
|
720
|
+
services: List[ServiceCapacity] = []
|
|
721
|
+
|
|
722
|
+
for sub_model_name, sub_desires in self._sub_models(
|
|
723
|
+
model_name, desires, extra_model_arguments
|
|
724
|
+
):
|
|
725
|
+
sub_model = self._models[sub_model_name]
|
|
726
|
+
if not isinstance(sub_model, CostAwareModel):
|
|
727
|
+
raise TypeError(
|
|
728
|
+
f"Sub-model '{sub_model_name}' does not implement CostAwareModel. "
|
|
729
|
+
f"All models in the composition tree must implement cost methods."
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
model_costs = sub_model.cluster_costs(
|
|
733
|
+
service_type=sub_model.service_name,
|
|
734
|
+
zonal_clusters=zonal_clusters,
|
|
735
|
+
regional_clusters=regional_clusters,
|
|
736
|
+
)
|
|
737
|
+
costs.update(model_costs)
|
|
738
|
+
|
|
739
|
+
model_services = sub_model.service_costs(
|
|
740
|
+
service_type=sub_model.service_name,
|
|
741
|
+
context=context,
|
|
742
|
+
desires=sub_desires,
|
|
743
|
+
extra_model_arguments=extra_model_arguments,
|
|
744
|
+
)
|
|
745
|
+
for svc in model_services:
|
|
746
|
+
costs[svc.service_type] = svc.annual_cost
|
|
747
|
+
services.extend(model_services)
|
|
748
|
+
|
|
749
|
+
return costs, services
|
|
750
|
+
|
|
751
|
+
def extract_baseline_plan( # pylint: disable=too-many-positional-arguments
|
|
752
|
+
self,
|
|
753
|
+
model_name: str,
|
|
754
|
+
region: str,
|
|
755
|
+
desires: CapacityDesires,
|
|
756
|
+
num_regions: int = 3,
|
|
757
|
+
extra_model_arguments: Optional[Dict[str, Any]] = None,
|
|
758
|
+
) -> CapacityPlan:
|
|
759
|
+
"""Extract baseline plan from current clusters using model cost methods.
|
|
760
|
+
|
|
761
|
+
This converts the current deployment (from desires.current_clusters) into
|
|
762
|
+
a CapacityPlan that can be compared against recommendations. Uses model-
|
|
763
|
+
specific cost methods for accurate pricing.
|
|
764
|
+
|
|
765
|
+
Note: Only works for models with CostAwareModel mixin (EVCache, Kafka,
|
|
766
|
+
Cassandra, Key-Value). Other models will raise AttributeError.
|
|
767
|
+
|
|
768
|
+
Supports composite models (like Key-Value) that have both zonal and
|
|
769
|
+
regional clusters - each model's cluster_costs filters by cluster_type.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
model_name: Registered model name (e.g., "org.netflix.cassandra")
|
|
773
|
+
region: AWS region for pricing
|
|
774
|
+
desires: CapacityDesires with current_clusters populated
|
|
775
|
+
num_regions: For cross-region cost calculation (default: 3)
|
|
776
|
+
extra_model_arguments: Model-specific arguments (e.g., copies_per_region)
|
|
777
|
+
|
|
778
|
+
Returns:
|
|
779
|
+
CapacityPlan with costs from model.cluster_costs() and model.service_costs()
|
|
780
|
+
|
|
781
|
+
Raises:
|
|
782
|
+
ValueError: If model_name not found or current_clusters invalid
|
|
783
|
+
AttributeError: If model doesn't have CostAwareModel mixin
|
|
784
|
+
"""
|
|
785
|
+
extra_model_arguments = extra_model_arguments or {}
|
|
786
|
+
if model_name not in self._models:
|
|
787
|
+
raise ValueError(
|
|
788
|
+
f"model_name={model_name} does not exist. "
|
|
789
|
+
f"Try {sorted(list(self._models.keys()))}"
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
model = self._models[model_name]
|
|
793
|
+
if not isinstance(model, CostAwareModel):
|
|
794
|
+
raise TypeError(f"Model '{model_name}' must implement CostAwareModel mixin")
|
|
795
|
+
|
|
796
|
+
if desires.current_clusters is None:
|
|
797
|
+
raise ValueError(
|
|
798
|
+
"Cannot extract baseline: desires.current_clusters is None. "
|
|
799
|
+
"This function requires an existing deployment to compare against."
|
|
800
|
+
)
|
|
801
|
+
if not desires.current_clusters.zonal and not desires.current_clusters.regional:
|
|
802
|
+
raise ValueError(
|
|
803
|
+
"Cannot extract baseline: desires.current_clusters has no zonal "
|
|
804
|
+
"or regional clusters defined."
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
hardware, context = self._prepare_context(region, num_regions)
|
|
808
|
+
_set_instance_objects(
|
|
809
|
+
desires, hardware
|
|
810
|
+
) # Resolve instance refs in current_clusters
|
|
811
|
+
|
|
812
|
+
zonal_capacities: List[ClusterCapacity] = []
|
|
813
|
+
zonal_requirements: List[CapacityRequirement] = []
|
|
814
|
+
regional_capacities: List[ClusterCapacity] = []
|
|
815
|
+
regional_requirements: List[CapacityRequirement] = []
|
|
816
|
+
|
|
817
|
+
if desires.current_clusters.zonal:
|
|
818
|
+
zonal_capacities, zonal_requirements = _extract_cluster_plan(
|
|
819
|
+
desires.current_clusters.zonal, hardware, is_zonal=True
|
|
820
|
+
)
|
|
821
|
+
if desires.current_clusters.regional:
|
|
822
|
+
regional_capacities, regional_requirements = _extract_cluster_plan(
|
|
823
|
+
desires.current_clusters.regional, hardware, is_zonal=False
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
costs, services = self._get_model_costs(
|
|
827
|
+
model_name=model_name,
|
|
828
|
+
context=context,
|
|
829
|
+
desires=desires,
|
|
830
|
+
zonal_clusters=zonal_capacities,
|
|
831
|
+
regional_clusters=regional_capacities,
|
|
832
|
+
extra_model_arguments=extra_model_arguments,
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
return CapacityPlan(
|
|
836
|
+
requirements=Requirements(
|
|
837
|
+
zonal=zonal_requirements,
|
|
838
|
+
regional=regional_requirements,
|
|
839
|
+
),
|
|
840
|
+
candidate_clusters=Clusters(
|
|
841
|
+
annual_costs=costs,
|
|
842
|
+
zonal=zonal_capacities,
|
|
843
|
+
regional=regional_capacities,
|
|
844
|
+
services=services,
|
|
845
|
+
),
|
|
846
|
+
)
|
|
847
|
+
|
|
590
848
|
# Calculates the minimum cpu, memory, and network requirements based on desires.
|
|
591
849
|
def _per_instance_requirements(self, desires: CapacityDesires) -> Tuple[int, float]:
|
|
592
850
|
# Applications often set fixed reservations of heap or OS memory
|
|
@@ -636,13 +894,10 @@ class CapacityPlanner:
|
|
|
636
894
|
instance_families = instance_families or []
|
|
637
895
|
drives = drives or []
|
|
638
896
|
|
|
639
|
-
hardware = self.
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
services={n: s.model_copy(deep=True) for n, s in hardware.services.items()},
|
|
644
|
-
num_regions=num_regions,
|
|
645
|
-
)
|
|
897
|
+
hardware, context = self._prepare_context(region, num_regions)
|
|
898
|
+
_set_instance_objects(
|
|
899
|
+
desires, hardware
|
|
900
|
+
) # Resolve instance refs if current_clusters exists
|
|
646
901
|
|
|
647
902
|
allowed_platforms: Set[Platform] = set(model.allowed_platforms())
|
|
648
903
|
allowed_drives: Set[str] = set(drives or [])
|
|
@@ -654,9 +909,6 @@ class CapacityPlanner:
|
|
|
654
909
|
if len(allowed_drives) == 0:
|
|
655
910
|
allowed_drives.update(hardware.drives.keys())
|
|
656
911
|
|
|
657
|
-
# Set current instance object if exists
|
|
658
|
-
_set_instance_objects(desires, hardware)
|
|
659
|
-
|
|
660
912
|
# We should not even bother with shapes that don't meet the minimums
|
|
661
913
|
(
|
|
662
914
|
per_instance_cores,
|
|
@@ -565,6 +565,30 @@ class Hardware(ExcludeUnsetModel):
|
|
|
565
565
|
"""Managed services available (e.g. service name -> Service with
|
|
566
566
|
params, cost, etc.)"""
|
|
567
567
|
|
|
568
|
+
def price_drive(self, drive: Drive) -> Drive:
|
|
569
|
+
"""Hydrate a drive with pricing information from the hardware catalog.
|
|
570
|
+
|
|
571
|
+
User-provided drives (e.g., from CurrentClusterCapacity.cluster_drive)
|
|
572
|
+
contain size/IOPS but lack pricing. This method looks up pricing by
|
|
573
|
+
drive name and returns a properly priced Drive instance.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
drive: Drive with name, size_gib, read_io_per_s, write_io_per_s
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
Drive with catalog pricing and input size/IO values
|
|
580
|
+
|
|
581
|
+
Raises:
|
|
582
|
+
ValueError: If drive.name not in hardware catalog
|
|
583
|
+
"""
|
|
584
|
+
if drive.name not in self.drives:
|
|
585
|
+
raise ValueError(f"Cannot price drive '{drive.name}'")
|
|
586
|
+
priced = self.drives[drive.name].model_copy()
|
|
587
|
+
priced.size_gib = drive.size_gib
|
|
588
|
+
priced.read_io_per_s = drive.read_io_per_s
|
|
589
|
+
priced.write_io_per_s = drive.write_io_per_s
|
|
590
|
+
return priced
|
|
591
|
+
|
|
568
592
|
|
|
569
593
|
class GlobalHardware(ExcludeUnsetModel):
|
|
570
594
|
"""Represents all possible hardware shapes in all regions
|
|
@@ -821,6 +845,10 @@ class CurrentClusterCapacity(ExcludeUnsetModel):
|
|
|
821
845
|
cluster_instance: Optional[Instance] = None
|
|
822
846
|
cluster_drive: Optional[Drive] = None
|
|
823
847
|
cluster_instance_count: Interval
|
|
848
|
+
# Optional: if not set, extract_baseline_plan
|
|
849
|
+
# Required metadata for identifying which model
|
|
850
|
+
# this capacity belongs to
|
|
851
|
+
cluster_type: Optional[str] = None
|
|
824
852
|
# The distribution cpu utilization in the cluster.
|
|
825
853
|
cpu_utilization: Interval = certain_float(0.0)
|
|
826
854
|
# The per node distribution of memory used in gib.
|
|
@@ -1122,11 +1150,30 @@ class ClusterCapacity(ExcludeUnsetModel):
|
|
|
1122
1150
|
count: int
|
|
1123
1151
|
instance: Instance
|
|
1124
1152
|
attached_drives: Sequence[Drive] = ()
|
|
1125
|
-
annual_cost: float
|
|
1126
1153
|
# When provisioning services we might need to signal they
|
|
1127
1154
|
# should have certain configuration, for example flags that
|
|
1128
1155
|
# affect durability shut off
|
|
1129
1156
|
cluster_params: Dict[str, Any] = {}
|
|
1157
|
+
# Override for models with non-standard cost calculation (e.g., Aurora
|
|
1158
|
+
# has shared storage so drive cost isn't multiplied by count)
|
|
1159
|
+
annual_cost_override: Optional[float] = None
|
|
1160
|
+
|
|
1161
|
+
@computed_field(return_type=float) # type: ignore
|
|
1162
|
+
@property
|
|
1163
|
+
def annual_cost(self) -> float:
|
|
1164
|
+
"""Compute annual cost from instance and attached drives.
|
|
1165
|
+
|
|
1166
|
+
Standard formula: count * instance.annual_cost + sum(drive.annual_cost * count)
|
|
1167
|
+
|
|
1168
|
+
Models with different cost structures (e.g., Aurora with shared storage)
|
|
1169
|
+
can set annual_cost_override to bypass this calculation.
|
|
1170
|
+
"""
|
|
1171
|
+
if self.annual_cost_override is not None:
|
|
1172
|
+
return self.annual_cost_override
|
|
1173
|
+
cost = self.count * self.instance.annual_cost
|
|
1174
|
+
for drive in self.attached_drives:
|
|
1175
|
+
cost += drive.annual_cost * self.count
|
|
1176
|
+
return cost
|
|
1130
1177
|
|
|
1131
1178
|
|
|
1132
1179
|
class ServiceCapacity(ExcludeUnsetModel):
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
from typing import Callable
|
|
3
3
|
from typing import Dict
|
|
4
|
+
from typing import List
|
|
4
5
|
from typing import Optional
|
|
6
|
+
from typing import Sequence
|
|
5
7
|
from typing import Tuple
|
|
6
8
|
|
|
7
9
|
from service_capacity_modeling.interface import AccessConsistency
|
|
@@ -18,7 +20,9 @@ from service_capacity_modeling.interface import GlobalConsistency
|
|
|
18
20
|
from service_capacity_modeling.interface import Instance
|
|
19
21
|
from service_capacity_modeling.interface import Platform
|
|
20
22
|
from service_capacity_modeling.interface import QueryPattern
|
|
23
|
+
from service_capacity_modeling.interface import ClusterCapacity
|
|
21
24
|
from service_capacity_modeling.interface import RegionContext
|
|
25
|
+
from service_capacity_modeling.interface import ServiceCapacity
|
|
22
26
|
|
|
23
27
|
__all__ = [
|
|
24
28
|
"AccessConsistency",
|
|
@@ -37,6 +41,7 @@ __all__ = [
|
|
|
37
41
|
"QueryPattern",
|
|
38
42
|
"RegionContext",
|
|
39
43
|
"CapacityModel",
|
|
44
|
+
"CostAwareModel",
|
|
40
45
|
]
|
|
41
46
|
|
|
42
47
|
__common_regrets__ = frozenset(("spend", "disk", "mem"))
|
|
@@ -87,6 +92,47 @@ def _disk_regret( # noqa: C901
|
|
|
87
92
|
return regret
|
|
88
93
|
|
|
89
94
|
|
|
95
|
+
class CostAwareModel:
|
|
96
|
+
"""Mixin for models that implement cost calculation methods.
|
|
97
|
+
|
|
98
|
+
Models using this mixin MUST define:
|
|
99
|
+
service_name: str # prefix for cost keys (e.g., "cassandra")
|
|
100
|
+
cluster_type: str # filters which clusters this model costs
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
class MyModel(CapacityModel, CostAwareModel):
|
|
104
|
+
service_name = "myservice"
|
|
105
|
+
cluster_type = "myservice"
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
# Subclasses MUST override these (validated in register_model)
|
|
109
|
+
service_name: str
|
|
110
|
+
cluster_type: str
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def service_costs(
|
|
114
|
+
service_type: str,
|
|
115
|
+
context: RegionContext,
|
|
116
|
+
desires: CapacityDesires,
|
|
117
|
+
extra_model_arguments: Dict[str, Any],
|
|
118
|
+
) -> List[ServiceCapacity]:
|
|
119
|
+
"""Calculate additional service costs (network, backup, etc)."""
|
|
120
|
+
raise NotImplementedError(
|
|
121
|
+
f"service_costs() must be implemented by {service_type} model"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def cluster_costs(
|
|
126
|
+
service_type: str,
|
|
127
|
+
zonal_clusters: Sequence["ClusterCapacity"] = (),
|
|
128
|
+
regional_clusters: Sequence["ClusterCapacity"] = (),
|
|
129
|
+
) -> Dict[str, float]:
|
|
130
|
+
"""Calculate cluster infrastructure costs (instances, drives)."""
|
|
131
|
+
raise NotImplementedError(
|
|
132
|
+
f"cluster_costs() must be implemented by {service_type} model"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
90
136
|
class CapacityModel:
|
|
91
137
|
"""Stateless interface for defining a capacity model
|
|
92
138
|
|
|
@@ -8,6 +8,7 @@ from typing import Callable
|
|
|
8
8
|
from typing import Dict
|
|
9
9
|
from typing import List
|
|
10
10
|
from typing import Optional
|
|
11
|
+
from typing import Sequence
|
|
11
12
|
from typing import Set
|
|
12
13
|
from typing import Tuple
|
|
13
14
|
|
|
@@ -25,6 +26,7 @@ from service_capacity_modeling.interface import CapacityPlan
|
|
|
25
26
|
from service_capacity_modeling.interface import CapacityRequirement
|
|
26
27
|
from service_capacity_modeling.interface import certain_float
|
|
27
28
|
from service_capacity_modeling.interface import certain_int
|
|
29
|
+
from service_capacity_modeling.interface import ClusterCapacity
|
|
28
30
|
from service_capacity_modeling.interface import Clusters
|
|
29
31
|
from service_capacity_modeling.interface import CurrentClusterCapacity
|
|
30
32
|
from service_capacity_modeling.interface import CurrentClusters
|
|
@@ -48,6 +50,31 @@ logger = logging.getLogger(__name__)
|
|
|
48
50
|
SECONDS_IN_YEAR = 31556926
|
|
49
51
|
|
|
50
52
|
|
|
53
|
+
def cluster_infra_cost(
|
|
54
|
+
service_type: str,
|
|
55
|
+
zonal_clusters: Sequence[ClusterCapacity],
|
|
56
|
+
regional_clusters: Sequence[ClusterCapacity],
|
|
57
|
+
cluster_type: Optional[str] = None,
|
|
58
|
+
) -> Dict[str, float]:
|
|
59
|
+
"""Sum cluster annual_costs, optionally filtering by cluster_type."""
|
|
60
|
+
if cluster_type is not None:
|
|
61
|
+
zonal_clusters = [c for c in zonal_clusters if c.cluster_type == cluster_type]
|
|
62
|
+
regional_clusters = [
|
|
63
|
+
c for c in regional_clusters if c.cluster_type == cluster_type
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
costs: Dict[str, float] = {}
|
|
67
|
+
if zonal_clusters:
|
|
68
|
+
costs[f"{service_type}.zonal-clusters"] = sum(
|
|
69
|
+
c.annual_cost for c in zonal_clusters
|
|
70
|
+
)
|
|
71
|
+
if regional_clusters:
|
|
72
|
+
costs[f"{service_type}.regional-clusters"] = sum(
|
|
73
|
+
c.annual_cost for c in regional_clusters
|
|
74
|
+
)
|
|
75
|
+
return costs
|
|
76
|
+
|
|
77
|
+
|
|
51
78
|
# In square root staffing we have to take into account the QOS parameter
|
|
52
79
|
# Which is related to the probability that a user queues. On low tier clusters
|
|
53
80
|
# (aka critical clusters) we want a lower probability of queueing
|
|
@@ -92,6 +119,17 @@ def _sqrt_staffed_cores(rps: float, latency_s: float, qos: float) -> int:
|
|
|
92
119
|
return math.ceil((rps * latency_s) + qos * math.sqrt(rps * latency_s))
|
|
93
120
|
|
|
94
121
|
|
|
122
|
+
def get_disk_size_gib(
|
|
123
|
+
cluster_drive: Optional[Drive],
|
|
124
|
+
instance: Instance,
|
|
125
|
+
) -> float:
|
|
126
|
+
if cluster_drive is not None:
|
|
127
|
+
return cluster_drive.size_gib or 0.0
|
|
128
|
+
if instance.drive is not None:
|
|
129
|
+
return instance.drive.size_gib or 0.0
|
|
130
|
+
return 0.0
|
|
131
|
+
|
|
132
|
+
|
|
95
133
|
def get_effective_disk_per_node_gib(
|
|
96
134
|
instance: Instance,
|
|
97
135
|
drive: Drive,
|
|
@@ -910,14 +948,8 @@ class RequirementFromCurrentCapacity(BaseModel):
|
|
|
910
948
|
self.current_capacity.disk_utilization_gib.mid
|
|
911
949
|
* self.current_capacity.cluster_instance_count.mid
|
|
912
950
|
)
|
|
913
|
-
current_node_disk_gib =
|
|
914
|
-
self.
|
|
915
|
-
if self.current_instance.drive is not None
|
|
916
|
-
else (
|
|
917
|
-
self.current_capacity.cluster_drive.size_gib
|
|
918
|
-
if self.current_capacity.cluster_drive is not None
|
|
919
|
-
else 0
|
|
920
|
-
)
|
|
951
|
+
current_node_disk_gib = get_disk_size_gib(
|
|
952
|
+
self.current_capacity.cluster_drive, self.current_instance
|
|
921
953
|
)
|
|
922
954
|
|
|
923
955
|
zonal_disk_allocated = float(
|
|
@@ -178,6 +178,10 @@ def _compute_aurora_region( # pylint: disable=too-many-positional-arguments
|
|
|
178
178
|
drive.annual_cost_per_read_io[0][1],
|
|
179
179
|
drive.annual_cost_per_write_io[0][1],
|
|
180
180
|
)
|
|
181
|
+
|
|
182
|
+
# TODO (homatthew): Should instance.annual_cost be multiplied by instance_count?
|
|
183
|
+
# Aurora has shared storage, so storage cost is correct (not multiplied).
|
|
184
|
+
# But compute cost should arguably be instance_count * instance.annual_cost.
|
|
181
185
|
total_annual_cost = instance.annual_cost + attached_drive.annual_cost + io_cost
|
|
182
186
|
|
|
183
187
|
logger.debug(
|
|
@@ -202,7 +206,8 @@ def _compute_aurora_region( # pylint: disable=too-many-positional-arguments
|
|
|
202
206
|
count=instance_count,
|
|
203
207
|
instance=instance,
|
|
204
208
|
attached_drives=attached_drives,
|
|
205
|
-
|
|
209
|
+
# Aurora's cost model differs from standard (shared storage), so override
|
|
210
|
+
annual_cost_override=total_annual_cost,
|
|
206
211
|
cluster_params={"instance_cost": instance.annual_cost},
|
|
207
212
|
)
|
|
208
213
|
|