service-capacity-modeling 0.3.73__py3-none-any.whl → 0.3.79__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.

Files changed (40) hide show
  1. service_capacity_modeling/capacity_planner.py +46 -40
  2. service_capacity_modeling/hardware/__init__.py +11 -7
  3. service_capacity_modeling/hardware/profiles/shapes/aws/auto_i3en.json +172 -0
  4. service_capacity_modeling/hardware/profiles/shapes/aws/auto_i4i.json +220 -0
  5. service_capacity_modeling/hardware/profiles/shapes/aws/manual_instances.json +0 -184
  6. service_capacity_modeling/interface.py +48 -22
  7. service_capacity_modeling/models/__init__.py +21 -2
  8. service_capacity_modeling/models/common.py +268 -190
  9. service_capacity_modeling/models/headroom_strategy.py +2 -1
  10. service_capacity_modeling/models/org/netflix/__init__.py +4 -1
  11. service_capacity_modeling/models/org/netflix/aurora.py +12 -7
  12. service_capacity_modeling/models/org/netflix/cassandra.py +39 -24
  13. service_capacity_modeling/models/org/netflix/counter.py +44 -20
  14. service_capacity_modeling/models/org/netflix/crdb.py +7 -4
  15. service_capacity_modeling/models/org/netflix/ddb.py +9 -5
  16. service_capacity_modeling/models/org/netflix/elasticsearch.py +8 -6
  17. service_capacity_modeling/models/org/netflix/entity.py +5 -3
  18. service_capacity_modeling/models/org/netflix/evcache.py +21 -25
  19. service_capacity_modeling/models/org/netflix/graphkv.py +5 -3
  20. service_capacity_modeling/models/org/netflix/iso_date_math.py +12 -9
  21. service_capacity_modeling/models/org/netflix/kafka.py +13 -7
  22. service_capacity_modeling/models/org/netflix/key_value.py +4 -2
  23. service_capacity_modeling/models/org/netflix/postgres.py +4 -2
  24. service_capacity_modeling/models/org/netflix/rds.py +10 -5
  25. service_capacity_modeling/models/org/netflix/stateless_java.py +4 -2
  26. service_capacity_modeling/models/org/netflix/time_series.py +4 -2
  27. service_capacity_modeling/models/org/netflix/time_series_config.py +3 -3
  28. service_capacity_modeling/models/org/netflix/wal.py +4 -2
  29. service_capacity_modeling/models/org/netflix/zookeeper.py +5 -3
  30. service_capacity_modeling/stats.py +14 -11
  31. service_capacity_modeling/tools/auto_shape.py +10 -6
  32. service_capacity_modeling/tools/fetch_pricing.py +13 -6
  33. service_capacity_modeling/tools/generate_missing.py +4 -3
  34. service_capacity_modeling/tools/instance_families.py +18 -7
  35. {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.79.dist-info}/METADATA +9 -5
  36. {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.79.dist-info}/RECORD +40 -38
  37. {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.79.dist-info}/WHEEL +0 -0
  38. {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.79.dist-info}/entry_points.txt +0 -0
  39. {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.79.dist-info}/licenses/LICENSE +0 -0
  40. {service_capacity_modeling-0.3.73.dist-info → service_capacity_modeling-0.3.79.dist-info}/top_level.txt +0 -0
@@ -55,190 +55,6 @@
55
55
  "block_size_kib": 4, "single_tenant": true
56
56
  }
57
57
  },
58
- "i4i.large": {
59
- "name": "i4i.large",
60
- "cpu": 2,
61
- "cpu_ghz": 3.5,
62
- "cpu_ipc_scale": 1.15,
63
- "ram_gib": 15.48,
64
- "net_mbps": 781,
65
- "drive": {
66
- "name": "ephem", "size_gib": 436,
67
- "read_io_latency_ms": {
68
- "minimum_value":0.05,
69
- "low":0.10, "mid":0.125, "high":0.17,
70
- "maximum_value":2.05, "confidence":0.9
71
- },
72
- "read_io_per_s": 50000, "write_io_per_s": 27500,
73
- "block_size_kib": 4, "single_tenant": false
74
- }
75
- },
76
- "i4i.xlarge": {
77
- "name": "i4i.xlarge",
78
- "cpu": 4,
79
- "cpu_ghz": 3.5,
80
- "cpu_ipc_scale": 1.15,
81
- "ram_gib": 30.955,
82
- "net_mbps": 1875,
83
- "drive": {
84
- "name": "ephem", "size_gib": 873,
85
- "read_io_latency_ms": {
86
- "minimum_value": 0.05,
87
- "low": 0.10, "mid": 0.125, "high": 0.17,
88
- "maximum_value": 2.05, "confidence": 0.9
89
- },
90
- "read_io_per_s": 100000, "write_io_per_s": 55000,
91
- "block_size_kib": 4, "single_tenant": false
92
- }
93
- },
94
- "i4i.2xlarge": {
95
- "name": "i4i.2xlarge",
96
- "cpu": 8,
97
- "cpu_ghz": 3.5,
98
- "cpu_ipc_scale": 1.15,
99
- "ram_gib": 61.91,
100
- "net_mbps": 4687.5 ,
101
- "drive": {
102
- "name": "ephem", "size_gib": 1746,
103
- "read_io_latency_ms": {
104
- "minimum_value": 0.05,
105
- "low": 0.10, "mid": 0.125, "high": 0.17,
106
- "maximum_value": 2.05, "confidence": 0.9
107
- },
108
- "read_io_per_s": 200000, "write_io_per_s": 110000,
109
- "block_size_kib": 4, "single_tenant": true
110
- }
111
- },
112
- "i4i.4xlarge": {
113
- "name": "i4i.4xlarge",
114
- "cpu": 16,
115
- "cpu_ghz": 3.5,
116
- "cpu_ipc_scale": 1.15,
117
- "ram_gib": 123.82,
118
- "net_mbps": 9375,
119
- "drive": {
120
- "name": "ephem", "size_gib": 3492,
121
- "read_io_latency_ms": {
122
- "minimum_value":0.05,
123
- "low":0.10, "mid":0.125, "high":0.17,
124
- "maximum_value":2.05, "confidence":0.9
125
- },
126
- "read_io_per_s": 400000, "write_io_per_s": 220000,
127
- "block_size_kib": 4, "single_tenant": true
128
- }
129
- },
130
- "i4i.8xlarge": {
131
- "name": "i4i.8xlarge",
132
- "cpu": 32,
133
- "cpu_ghz": 3.5,
134
- "cpu_ipc_scale": 1.15,
135
- "ram_gib": 247.76,
136
- "net_mbps": 18750,
137
- "drive": {
138
- "name": "ephem", "size_gib": 6984,
139
- "read_io_latency_ms": {
140
- "minimum_value":0.05,
141
- "low":0.10, "mid":0.125, "high":0.17,
142
- "maximum_value":2.05, "confidence":0.9
143
- },
144
- "read_io_per_s": 800000, "write_io_per_s": 440000,
145
- "block_size_kib": 4, "single_tenant": true
146
- }
147
- },
148
- "i4i.16xlarge": {
149
- "name": "i4i.16xlarge",
150
- "cpu": 64,
151
- "cpu_ghz": 3.5,
152
- "cpu_ipc_scale": 1.15,
153
- "ram_gib": 495.82,
154
- "net_mbps": 35000,
155
- "drive": {
156
- "name": "ephem", "size_gib": 13968,
157
- "read_io_latency_ms": {
158
- "minimum_value":0.05,
159
- "low":0.10, "mid":0.125, "high":0.17,
160
- "maximum_value":2.05, "confidence":0.9
161
- },
162
- "read_io_per_s": 16000000, "write_io_per_s": 880000,
163
- "block_size_kib": 4, "single_tenant": true
164
- }
165
- },
166
-
167
- "i3en.large": {
168
- "name": "i3en.large",
169
- "cpu": 2,
170
- "cpu_ghz": 3.1,
171
- "ram_gib": 15.8,
172
- "net_mbps": 4000,
173
- "drive": {
174
- "name": "ephem", "size_gib": 1150,
175
- "read_io_latency_ms": {
176
- "minimum_value":0.07,
177
- "low":0.08, "mid":0.12, "high":0.20,
178
- "maximum_value":2, "confidence":0.9
179
- },
180
- "read_io_per_s": 42500, "write_io_per_s": 32500,
181
- "block_size_kib": 4, "single_tenant": false
182
- }
183
- },
184
- "i3en.xlarge": {
185
- "name": "i3en.xlarge",
186
- "cpu": 4,
187
- "cpu_ghz": 3.1,
188
- "ram_gib": 31.7,
189
- "net_mbps": 4000,
190
- "drive": {
191
- "name": "ephem", "size_gib": 2300,
192
- "read_io_latency_ms": {
193
- "minimum_value":0.07,
194
- "low":0.08, "mid":0.12, "high":0.20,
195
- "maximum_value":2, "confidence":0.9
196
- },
197
- "read_io_per_s": 85000, "write_io_per_s": 65000,
198
- "block_size_kib": 4, "single_tenant": false
199
- }
200
- },
201
- "i3en.2xlarge": {
202
- "name": "i3en.2xlarge",
203
- "cpu": 8,
204
- "cpu_ghz": 3.1,
205
- "ram_gib": 63.62,
206
- "net_mbps": 8000,
207
- "drive": {
208
- "name": "ephem", "size_gib": 4600,
209
- "read_io_latency_ms": {
210
- "minimum_value": 0.07,
211
- "low":0.08, "mid":0.12, "high": 0.20,
212
- "maximum_value":2, "confidence":0.9
213
- },
214
- "read_io_per_s": 170000, "write_io_per_s": 130000,
215
- "block_size_kib": 4, "single_tenant": false
216
- }
217
- },
218
- "i3en.3xlarge": {
219
- "name": "i3en.3xlarge",
220
- "cpu": 12,
221
- "cpu_ghz": 3.1,
222
- "ram_gib": 95.54,
223
- "net_mbps": 12000,
224
- "drive": {"name": "ephem", "size_gib": 6819,
225
- "read_io_latency_ms": {"minimum_value":0.07, "low":0.08, "mid":0.12, "high":0.16, "maximum_value":2, "confidence":0.9},
226
- "read_io_per_s": 250000, "write_io_per_s": 200000,
227
- "block_size_kib": 4, "single_tenant": true
228
- }
229
- },
230
- "i3en.6xlarge": {
231
- "name": "i3en.6xlarge",
232
- "cpu": 24,
233
- "cpu_ghz": 3.1,
234
- "ram_gib": 186.62,
235
- "net_mbps": 24000,
236
- "drive": {"name": "ephem", "size_gib": 14000,
237
- "read_io_latency_ms": {"minimum_value":0.07, "low":0.08, "mid":0.12, "high":0.16, "maximum_value":2, "confidence":0.9},
238
- "read_io_per_s": 500000, "write_io_per_s": 400000,
239
- "block_size_kib": 4, "single_tenant": true
240
- }
241
- },
242
58
  "m5d.large": {
243
59
  "name": "m5d.large",
244
60
  "cpu": 2,
@@ -27,12 +27,12 @@ MEGABIT_IN_BYTES = (1000 * 1000) / 8
27
27
 
28
28
 
29
29
  class ExcludeUnsetModel(BaseModel):
30
- def model_dump(self, *args, **kwargs):
30
+ def model_dump(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
31
31
  if "exclude_unset" not in kwargs:
32
32
  kwargs["exclude_unset"] = True
33
33
  return super().model_dump(*args, **kwargs)
34
34
 
35
- def model_dump_json(self, *args, **kwargs):
35
+ def model_dump_json(self, *args: Any, **kwargs: Any) -> str:
36
36
  if "exclude_unset" not in kwargs:
37
37
  kwargs["exclude_unset"] = True
38
38
  return super().model_dump_json(*args, **kwargs)
@@ -44,10 +44,10 @@ class ExcludeUnsetModel(BaseModel):
44
44
 
45
45
 
46
46
  class IntervalModel(str, Enum):
47
- def __str__(self):
47
+ def __str__(self) -> str:
48
48
  return str(self.value)
49
49
 
50
- def __repr__(self):
50
+ def __repr__(self) -> str:
51
51
  return f"D({self.value})"
52
52
 
53
53
  gamma = "gamma"
@@ -71,11 +71,11 @@ class Interval(ExcludeUnsetModel):
71
71
  model_config = ConfigDict(frozen=True, protected_namespaces=())
72
72
 
73
73
  @property
74
- def can_simulate(self):
74
+ def can_simulate(self) -> bool:
75
75
  return self.confidence <= 0.99 and self.allow_simulate
76
76
 
77
77
  @property
78
- def minimum(self):
78
+ def minimum(self) -> float:
79
79
  if self.minimum_value is None:
80
80
  if self.confidence == 1.0:
81
81
  return self.low * 0.999
@@ -84,17 +84,19 @@ class Interval(ExcludeUnsetModel):
84
84
  return self.minimum_value
85
85
 
86
86
  @property
87
- def maximum(self):
87
+ def maximum(self) -> float:
88
88
  if self.maximum_value is None:
89
89
  if self.confidence == 1.0:
90
90
  return self.high * 1.001
91
91
  return self.high * 2
92
92
  return self.maximum_value
93
93
 
94
- def __hash__(self):
94
+ def __hash__(self) -> int:
95
95
  return hash((type(self),) + tuple(self.__dict__.values()))
96
96
 
97
- def __eq__(self, other):
97
+ def __eq__(self, other: object) -> bool:
98
+ if not isinstance(other, Interval):
99
+ return False
98
100
  return self.__hash__() == other.__hash__()
99
101
 
100
102
  def scale(self, factor: float) -> Interval:
@@ -264,14 +266,14 @@ class Drive(ExcludeUnsetModel):
264
266
  return max(self.block_size_kib, self.group_size_kib)
265
267
 
266
268
  @property
267
- def max_size_gib(self):
269
+ def max_size_gib(self) -> float:
268
270
  if self.max_scale_size_gib != 0:
269
271
  return self.max_scale_size_gib
270
272
  else:
271
273
  return self.size_gib
272
274
 
273
275
  @property
274
- def max_io_per_s(self):
276
+ def max_io_per_s(self) -> int:
275
277
  if self.max_scale_io_per_s != 0:
276
278
  return self.max_scale_io_per_s
277
279
  else:
@@ -279,7 +281,7 @@ class Drive(ExcludeUnsetModel):
279
281
 
280
282
  @computed_field(return_type=float) # type: ignore
281
283
  @property
282
- def annual_cost(self):
284
+ def annual_cost(self) -> float:
283
285
  size = self.size_gib or 0
284
286
  r_ios = self.read_io_per_s or 0
285
287
  w_ios = self.write_io_per_s or 0
@@ -382,15 +384,15 @@ class Instance(ExcludeUnsetModel):
382
384
  family_separator: str = "."
383
385
 
384
386
  @property
385
- def family(self):
387
+ def family(self) -> str:
386
388
  return self.name.rsplit(self.family_separator, 1)[0]
387
389
 
388
390
  @property
389
- def size(self):
391
+ def size(self) -> str:
390
392
  return self.name.rsplit(self.family_separator, 1)[1]
391
393
 
392
394
  @property
393
- def cores(self):
395
+ def cores(self) -> int:
394
396
  if self.cpu_cores is not None:
395
397
  return self.cpu_cores
396
398
  return self.cpu // 2
@@ -456,7 +458,7 @@ class Service(ExcludeUnsetModel):
456
458
  low=1, mid=10, high=50, confidence=0.9
457
459
  )
458
460
 
459
- def annual_cost_gib(self, data_gib: float = 0):
461
+ def annual_cost_gib(self, data_gib: float = 0) -> float:
460
462
  if isinstance(self.annual_cost_per_gib, float):
461
463
  return self.annual_cost_per_gib * data_gib
462
464
  else:
@@ -779,23 +781,48 @@ class BufferComponent(str, Enum):
779
781
  compute = "compute"
780
782
  # [Data Shape] a.k.a. "Dataset" related buffers, e.g. Disk and Memory
781
783
  storage = "storage"
782
-
783
784
  # Resource specific component
784
785
  cpu = "cpu"
785
786
  network = "network"
786
787
  disk = "disk"
787
788
  memory = "memory"
788
789
 
790
+ @staticmethod
791
+ def is_generic(component: str) -> bool:
792
+ return component in {BufferComponent.compute, BufferComponent.storage}
793
+
794
+ @staticmethod
795
+ def is_specific(component: str) -> bool:
796
+ return not BufferComponent.is_generic(component)
797
+
789
798
 
790
799
  class BufferIntent(str, Enum):
791
800
  # Most buffers show "desired" buffer, this is the default
792
801
  desired = "desired"
793
802
  # ratio on top of existing buffers to ensure exists. Generally combined
794
803
  # with a different desired buffer to ensure we don't just scale needlessly
804
+ # This means we can scale up or down as as long as we meet the desired buffer.
795
805
  scale = "scale"
796
- # Ignore model preferences, just preserve existing buffers
806
+
807
+ # DEPRECATED: Use scale_up/scale_down instead
808
+ # Ignores model preferences, just preserve existing buffers
809
+ # We rarely actually want to do this since it can cause severe over provisioning
797
810
  preserve = "preserve"
798
811
 
812
+ # Scale up if necessary to meet the desired buffer.
813
+ # If the existing resource is over-provisioned, do not reduce the requirement.
814
+ # If under-provisioned, the requirement can be increased to meet the desired buffer.
815
+ # Example: need 20 cores but have 10 → scale up to 20 cores.
816
+ # Example 2: need 20 cores but have 40 → do not scale down and require at
817
+ # least 40 cores
818
+ scale_up = "scale_up"
819
+ # Scale down if necessary to meet the desired buffer.
820
+ # If the existing resource is under-provisioned, do not increase the requirement.
821
+ # If over-provisioned, the requirement can be decreased to meet the desired buffer.
822
+ # Example: need 20 cores but have 10 → maintain buffer and do not scale up.
823
+ # Example 2: need 20 cores but have 40 → scale down to 20 cores.
824
+ scale_down = "scale_down"
825
+
799
826
 
800
827
  class Buffer(ExcludeUnsetModel):
801
828
  # The value of the buffer expressed as a ratio over "normal" load e.g. 1.5x
@@ -819,7 +846,6 @@ class Buffers(ExcludeUnsetModel):
819
846
  "compute": Buffer(ratio: 1.5),
820
847
  }
821
848
  )
822
-
823
849
  And then models layer in their buffers, for example if a workload
824
850
  requires 10 CPU cores, but the operator of that workload likes to build in
825
851
  2x buffer for background work (20 cores provisioned), they would express that
@@ -955,7 +981,7 @@ class CapacityRequirement(ExcludeUnsetModel):
955
981
  network_mbps: Interval = certain_int(0)
956
982
  disk_gib: Interval = certain_int(0)
957
983
 
958
- context: Dict = {}
984
+ context: Dict[str, Any] = {}
959
985
 
960
986
 
961
987
  class ClusterCapacity(ExcludeUnsetModel):
@@ -968,7 +994,7 @@ class ClusterCapacity(ExcludeUnsetModel):
968
994
  # When provisioning services we might need to signal they
969
995
  # should have certain configuration, for example flags that
970
996
  # affect durability shut off
971
- cluster_params: Dict = {}
997
+ cluster_params: Dict[str, Any] = {}
972
998
 
973
999
 
974
1000
  class ServiceCapacity(ExcludeUnsetModel):
@@ -979,7 +1005,7 @@ class ServiceCapacity(ExcludeUnsetModel):
979
1005
  regret_cost: bool = False
980
1006
  # Often while provisioning cloud services we need to represent
981
1007
  # parameters to the cloud APIs, use this to inject those from models
982
- service_params: Dict = {}
1008
+ service_params: Dict[str, Any] = {}
983
1009
 
984
1010
 
985
1011
  # For services that are provisioned by zone (e.g. Cassandra, EVCache)
@@ -20,6 +20,25 @@ from service_capacity_modeling.interface import Platform
20
20
  from service_capacity_modeling.interface import QueryPattern
21
21
  from service_capacity_modeling.interface import RegionContext
22
22
 
23
+ __all__ = [
24
+ "AccessConsistency",
25
+ "AccessPattern",
26
+ "CapacityDesires",
27
+ "CapacityPlan",
28
+ "CapacityRegretParameters",
29
+ "certain_float",
30
+ "Consistency",
31
+ "DataShape",
32
+ "Drive",
33
+ "FixedInterval",
34
+ "GlobalConsistency",
35
+ "Instance",
36
+ "Platform",
37
+ "QueryPattern",
38
+ "RegionContext",
39
+ "CapacityModel",
40
+ ]
41
+
23
42
  __common_regrets__ = frozenset(("spend", "disk", "mem"))
24
43
 
25
44
 
@@ -85,7 +104,7 @@ class CapacityModel:
85
104
 
86
105
  """
87
106
 
88
- def __init__(self):
107
+ def __init__(self) -> None:
89
108
  pass
90
109
 
91
110
  @staticmethod
@@ -270,7 +289,7 @@ class CapacityModel:
270
289
  @staticmethod
271
290
  def default_desires(
272
291
  user_desires: CapacityDesires, extra_model_arguments: Dict[str, Any]
273
- ):
292
+ ) -> CapacityDesires:
274
293
  """Optional defaults to apply given a user desires
275
294
 
276
295
  Often users do not know what the on-cpu time of their queries