cloudcostwise 0.1.2__tar.gz → 0.1.3__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.
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/PKG-INFO +3 -1
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/pyproject.toml +3 -1
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise/__init__.py +1 -1
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise/mcp_server.py +10 -10
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise.egg-info/PKG-INFO +3 -1
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/advisory_types.py +12 -10
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/cloudwatch_metrics_service.py +15 -9
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/data_providers/base.py +31 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/data_providers/missing_data.py +7 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/data_providers/models.py +14 -2
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/data_providers/offline.py +466 -87
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/data_providers/online.py +291 -22
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/open/database.py +254 -50
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/open/network.py +9 -1
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/open/optimizer.py +92 -23
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/open/storage.py +29 -1
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/metric_window.py +53 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/models.py +6 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/waste_detection_service.py +6 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/LICENSE.md +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/README.md +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/setup.cfg +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise/cli.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise/render.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise/runtime.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise/scan.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise.egg-info/SOURCES.txt +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise.egg-info/dependency_links.txt +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise.egg-info/entry_points.txt +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise.egg-info/requires.txt +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudcostwise.egg-info/top_level.txt +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/__init__.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/aws_ip_ranges.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/aws_ip_ranges_snapshot.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/aws_pricing_service.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/billed_cost.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/cache.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/circuit_breaker.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/config.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/cpu_sizing.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/credential_cache.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/data_providers/__init__.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/__init__.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/extended_support.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/open/__init__.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/open/commitment_cache.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/open/compute.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/open/management.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/open/savings.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/detectors/open/security.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/metrics.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/open_core_manifest.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/open_service.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/ports.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/py.typed +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/savings_cache_service.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/tier_routing.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/tests/test_cli.py +0 -0
- {cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/tests/test_mcp_server.py +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudcostwise
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Find AWS waste from your terminal or your AI assistant. Runs locally with your own read-only credentials; nothing leaves your machine.
|
|
5
5
|
Author: CloudWise
|
|
6
6
|
License: FSL-1.1-ALv2
|
|
7
7
|
Project-URL: Homepage, https://cloudcostwise.io/cli
|
|
8
|
+
Project-URL: Repository, https://github.com/cloudwise-app/cloudcostwise
|
|
9
|
+
Project-URL: Issues, https://github.com/cloudwise-app/cloudcostwise/issues
|
|
8
10
|
Requires-Python: >=3.11
|
|
9
11
|
Description-Content-Type: text/markdown
|
|
10
12
|
License-File: LICENSE.md
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cloudcostwise"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "Find AWS waste from your terminal or your AI assistant. Runs locally with your own read-only credentials; nothing leaves your machine."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -20,6 +20,8 @@ cloudcostwise = "cloudcostwise.cli:main"
|
|
|
20
20
|
|
|
21
21
|
[project.urls]
|
|
22
22
|
Homepage = "https://cloudcostwise.io/cli"
|
|
23
|
+
Repository = "https://github.com/cloudwise-app/cloudcostwise"
|
|
24
|
+
Issues = "https://github.com/cloudwise-app/cloudcostwise/issues"
|
|
23
25
|
|
|
24
26
|
[tool.setuptools.packages.find]
|
|
25
27
|
where = ["src"]
|
|
@@ -62,16 +62,16 @@ def build_server() -> MCPServer:
|
|
|
62
62
|
session = _Session()
|
|
63
63
|
server = MCPServer(name="cloudcostwise", version=__version__, instructions=INSTRUCTIONS)
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
# Same one-line decorator shape as the other three tools: directories that
|
|
66
|
+
# list tools by reading the source (mcprush) missed this one when its
|
|
67
|
+
# decorator opened on its own line.
|
|
68
|
+
@server.tool(annotations=READ_ONLY,
|
|
69
|
+
description="Scan the user's AWS account for waste with their local credentials (read-only; "
|
|
70
|
+
"any non-read AWS call is refused). Takes 30-60 s per region; up to 4 regions run "
|
|
71
|
+
"at once. COST: the RI/Savings Plans checks call Cost Explorer, billed by AWS at "
|
|
72
|
+
"$0.01 per request (about $0.13 a scan); tell the user before running, and pass "
|
|
73
|
+
"include_cost_explorer=false to skip them. regions: comma-separated, or 'all'; "
|
|
74
|
+
"default us-east-1 plus the profile's region.")
|
|
75
75
|
async def scan(ctx: Context, profile: Optional[str] = None, regions: Optional[str] = None,
|
|
76
76
|
include_cost_explorer: bool = True) -> str:
|
|
77
77
|
try:
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudcostwise
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Find AWS waste from your terminal or your AI assistant. Runs locally with your own read-only credentials; nothing leaves your machine.
|
|
5
5
|
Author: CloudWise
|
|
6
6
|
License: FSL-1.1-ALv2
|
|
7
7
|
Project-URL: Homepage, https://cloudcostwise.io/cli
|
|
8
|
+
Project-URL: Repository, https://github.com/cloudwise-app/cloudcostwise
|
|
9
|
+
Project-URL: Issues, https://github.com/cloudwise-app/cloudcostwise/issues
|
|
8
10
|
Requires-Python: >=3.11
|
|
9
11
|
Description-Content-Type: text/markdown
|
|
10
12
|
License-File: LICENSE.md
|
|
@@ -25,6 +25,7 @@ UNVALIDATED = "unvalidated"
|
|
|
25
25
|
|
|
26
26
|
# No path to L2 within budget.
|
|
27
27
|
ADVISORY_WASTE_TYPES: frozenset[str] = frozenset({
|
|
28
|
+
"appsync_idle_cache",
|
|
28
29
|
"aurora_io_optimization_opportunity",
|
|
29
30
|
"aurora_serverless_opportunity",
|
|
30
31
|
"convertible_ri_exchange_opportunity",
|
|
@@ -55,6 +56,7 @@ ADVISORY_WASTE_TYPES: frozenset[str] = frozenset({
|
|
|
55
56
|
"ri_opportunity_opensearch",
|
|
56
57
|
"ri_opportunity_rds",
|
|
57
58
|
"ri_opportunity_redshift",
|
|
59
|
+
"s3_high_request_and_transfer_cost",
|
|
58
60
|
"s3_no_default_encryption",
|
|
59
61
|
"savings_plan_coverage_gap",
|
|
60
62
|
"sp_opportunity_compute",
|
|
@@ -80,18 +82,22 @@ TRUSTED_WASTE_TYPES: frozenset[str] = frozenset({
|
|
|
80
82
|
"dynamodb_no_deletion_protection",
|
|
81
83
|
"ecr_no_lifecycle_policy",
|
|
82
84
|
"ecs_container_insights_waste",
|
|
85
|
+
"ecs_no_autoscaling",
|
|
83
86
|
"eip_on_stopped_instance",
|
|
84
87
|
"elasticache_engine_migration",
|
|
85
88
|
"elasticache_extended_support_cost",
|
|
89
|
+
"elasticache_replication_waste",
|
|
86
90
|
"empty_log_group",
|
|
87
91
|
"excessive_retention_log_group",
|
|
88
92
|
"failed_glue_job_retry",
|
|
89
93
|
"glue_job_missing_timeout",
|
|
90
94
|
"gp2_migration",
|
|
95
|
+
"idle_beanstalk",
|
|
91
96
|
"idle_documentdb",
|
|
92
97
|
"idle_dynamodb",
|
|
93
98
|
"idle_ecs_service",
|
|
94
99
|
"idle_efs",
|
|
100
|
+
"idle_fsx",
|
|
95
101
|
"idle_load_balancer",
|
|
96
102
|
"idle_state_machine",
|
|
97
103
|
"kinesis_firehose_idle",
|
|
@@ -99,6 +105,8 @@ TRUSTED_WASTE_TYPES: frozenset[str] = frozenset({
|
|
|
99
105
|
"lambda_excessive_timeout",
|
|
100
106
|
"lambda_old_runtime",
|
|
101
107
|
"lambda_provisioned_concurrency_idle",
|
|
108
|
+
"lightsail_idle_database",
|
|
109
|
+
"lightsail_idle_load_balancer",
|
|
102
110
|
"lightsail_unattached_disk",
|
|
103
111
|
"lightsail_unattached_static_ip",
|
|
104
112
|
"low_traffic_alb",
|
|
@@ -108,11 +116,13 @@ TRUSTED_WASTE_TYPES: frozenset[str] = frozenset({
|
|
|
108
116
|
"no_retention_log_group",
|
|
109
117
|
"old_ecr_images",
|
|
110
118
|
"old_glue_job",
|
|
119
|
+
"opensearch_no_encryption_at_rest",
|
|
111
120
|
"orphaned_dns_record",
|
|
112
121
|
"orphaned_ebs_snapshot",
|
|
113
122
|
"over_provisioned_dynamodb",
|
|
114
123
|
"over_provisioned_lambda",
|
|
115
124
|
"oversized_glue_job",
|
|
125
|
+
"oversized_sagemaker_endpoint",
|
|
116
126
|
"previous_gen_sagemaker_instance",
|
|
117
127
|
"rds_no_deletion_protection",
|
|
118
128
|
"rds_publicly_accessible",
|
|
@@ -140,7 +150,6 @@ TRUSTED_WASTE_TYPES: frozenset[str] = frozenset({
|
|
|
140
150
|
|
|
141
151
|
# L0/L1 with a path to L2.
|
|
142
152
|
UNVALIDATED_WASTE_TYPES: frozenset[str] = frozenset({
|
|
143
|
-
"appsync_idle_cache",
|
|
144
153
|
"aurora_extended_support_cost",
|
|
145
154
|
"aurora_to_rds_downgrade_opportunity",
|
|
146
155
|
"backup_copy_policy_overreach",
|
|
@@ -151,20 +160,16 @@ UNVALIDATED_WASTE_TYPES: frozenset[str] = frozenset({
|
|
|
151
160
|
"beanstalk_unnecessary_alb",
|
|
152
161
|
"cloudtrail_s3_no_lifecycle",
|
|
153
162
|
"disabled_global_accelerator",
|
|
154
|
-
"ecs_no_autoscaling",
|
|
155
163
|
"eks_extended_support_cost",
|
|
156
164
|
"elasticache_data_tiering_opportunity",
|
|
157
|
-
"elasticache_replication_waste",
|
|
158
165
|
"elasticache_serverless_optimization",
|
|
159
166
|
"emr_missing_auto_termination",
|
|
160
167
|
"emr_previous_gen_instances",
|
|
161
168
|
"emr_spot_opportunity",
|
|
162
169
|
"fsx_throughput_overprovisioned",
|
|
163
|
-
"idle_beanstalk",
|
|
164
170
|
"idle_ec2",
|
|
165
171
|
"idle_elasticache",
|
|
166
172
|
"idle_emr_cluster",
|
|
167
|
-
"idle_fsx",
|
|
168
173
|
"idle_glue_crawler",
|
|
169
174
|
"idle_kinesis_stream",
|
|
170
175
|
"idle_lightsail",
|
|
@@ -179,8 +184,6 @@ UNVALIDATED_WASTE_TYPES: frozenset[str] = frozenset({
|
|
|
179
184
|
"incomplete_multipart",
|
|
180
185
|
"kinesis_enhanced_fan_out_waste",
|
|
181
186
|
"kinesis_extended_retention_waste",
|
|
182
|
-
"lightsail_idle_database",
|
|
183
|
-
"lightsail_idle_load_balancer",
|
|
184
187
|
"lightsail_old_snapshot",
|
|
185
188
|
"neptune_old_snapshot",
|
|
186
189
|
"neptune_serverless_opportunity",
|
|
@@ -192,7 +195,6 @@ UNVALIDATED_WASTE_TYPES: frozenset[str] = frozenset({
|
|
|
192
195
|
"old_rds_snapshot",
|
|
193
196
|
"opensearch_ebs_overprovisioned",
|
|
194
197
|
"opensearch_extended_support_cost",
|
|
195
|
-
"opensearch_no_encryption_at_rest",
|
|
196
198
|
"over_provisioned_iops",
|
|
197
199
|
"over_provisioned_kinesis",
|
|
198
200
|
"overprovisioned_documentdb",
|
|
@@ -208,10 +210,8 @@ UNVALIDATED_WASTE_TYPES: frozenset[str] = frozenset({
|
|
|
208
210
|
"oversized_neptune",
|
|
209
211
|
"oversized_opensearch",
|
|
210
212
|
"oversized_rds_optimizer",
|
|
211
|
-
"oversized_sagemaker_endpoint",
|
|
212
213
|
"oversized_workspace",
|
|
213
214
|
"rds_extended_support_cost",
|
|
214
|
-
"s3_high_request_and_transfer_cost",
|
|
215
215
|
"s3_rapid_growth",
|
|
216
216
|
"stopped_sagemaker_notebook_storage",
|
|
217
217
|
"unused_api_gateway",
|
|
@@ -224,6 +224,7 @@ UNVALIDATED_WASTE_TYPES: frozenset[str] = frozenset({
|
|
|
224
224
|
|
|
225
225
|
# Why each advisory type is advisory (ledger `advisory_reason`).
|
|
226
226
|
ADVISORY_REASONS: Dict[str, str] = {
|
|
227
|
+
"appsync_idle_cache": "over_budget",
|
|
227
228
|
"aurora_io_optimization_opportunity": "over_budget",
|
|
228
229
|
"aurora_serverless_opportunity": "over_budget",
|
|
229
230
|
"convertible_ri_exchange_opportunity": "commitment",
|
|
@@ -254,6 +255,7 @@ ADVISORY_REASONS: Dict[str, str] = {
|
|
|
254
255
|
"ri_opportunity_opensearch": "commitment",
|
|
255
256
|
"ri_opportunity_rds": "commitment",
|
|
256
257
|
"ri_opportunity_redshift": "commitment",
|
|
258
|
+
"s3_high_request_and_transfer_cost": "over_budget",
|
|
257
259
|
"s3_no_default_encryption": "retired_config",
|
|
258
260
|
"savings_plan_coverage_gap": "commitment",
|
|
259
261
|
"sp_opportunity_compute": "commitment",
|
{cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/cloudwatch_metrics_service.py
RENAMED
|
@@ -164,22 +164,27 @@ class MSKMetrics:
|
|
|
164
164
|
messages_in_per_sec: float
|
|
165
165
|
bytes_in_per_sec: float
|
|
166
166
|
bytes_out_per_sec: float
|
|
167
|
-
#
|
|
168
|
-
# traffic summed across brokers.
|
|
167
|
+
# Both online (CLO-549) and offline (CLO-560): the busiest broker's mean
|
|
168
|
+
# CpuUser + CpuSystem, and traffic summed across brokers.
|
|
169
169
|
cpu_user: float
|
|
170
170
|
period_days: int
|
|
171
171
|
is_idle: bool = False
|
|
172
|
-
# CLO-457: hourly CpuUser datapoints behind
|
|
173
|
-
#
|
|
174
|
-
#
|
|
172
|
+
# CLO-457/CLO-560: hourly CpuUser+CpuSystem datapoints behind
|
|
173
|
+
# ``cpu_user`` (the fewest of any broker). 0 means CPU was not measured
|
|
174
|
+
# on at least one broker (``cpu_user`` is then a placeholder 0.0, not 0%
|
|
175
|
+
# CPU). None is unused today — both providers count it.
|
|
175
176
|
cpu_datapoints: Optional[int] = None
|
|
176
177
|
|
|
177
178
|
|
|
178
179
|
@dataclass
|
|
179
180
|
class NeptuneMetrics:
|
|
180
181
|
"""Neptune cluster metrics."""
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
# CLO-584: float for parity with NeptuneMetricsData (data_providers/models.py) --
|
|
183
|
+
# this class and get_neptune_metrics() below are not on the live
|
|
184
|
+
# detector path (no caller in the repo), but kept consistent rather than
|
|
185
|
+
# left truncating.
|
|
186
|
+
gremlin_requests: float
|
|
187
|
+
sparql_requests: float
|
|
183
188
|
loader_requests: int
|
|
184
189
|
avg_cpu: float
|
|
185
190
|
max_cpu: float
|
|
@@ -1779,8 +1784,9 @@ class CloudWatchMetricsService:
|
|
|
1779
1784
|
)
|
|
1780
1785
|
)
|
|
1781
1786
|
|
|
1782
|
-
|
|
1783
|
-
|
|
1787
|
+
# CLO-584: kept as floats (see NeptuneMetrics above).
|
|
1788
|
+
gremlin_requests = sum(dp.get('Sum', 0) for dp in gremlin_response.get('Datapoints', []))
|
|
1789
|
+
sparql_requests = sum(dp.get('Sum', 0) for dp in sparql_response.get('Datapoints', []))
|
|
1784
1790
|
|
|
1785
1791
|
cpu_datapoints = cpu_response.get('Datapoints', [])
|
|
1786
1792
|
avg_cpu = sum(dp.get('Average', 0) for dp in cpu_datapoints) / len(cpu_datapoints) if cpu_datapoints else 0
|
|
@@ -776,6 +776,26 @@ class WasteDataProvider(ABC):
|
|
|
776
776
|
both names; datapoints from before the consumer existed are excluded."""
|
|
777
777
|
pass
|
|
778
778
|
|
|
779
|
+
@abstractmethod
|
|
780
|
+
async def get_kinesis_consumer_subscribed(
|
|
781
|
+
self, stream_name: str, consumer_name: str,
|
|
782
|
+
) -> Optional[bool]:
|
|
783
|
+
"""Whether an enhanced fan-out consumer has subscribed in the past
|
|
784
|
+
two weeks (CLO-589 follow-up, PR #1654 review HIGH 1).
|
|
785
|
+
|
|
786
|
+
AWS publishes SubscribeToShard.Success (StreamName, ConsumerName) at
|
|
787
|
+
least once every 5 minutes while a subscription lives, and the
|
|
788
|
+
SubscribeToShardEvent.* series only while one is active; a consumer
|
|
789
|
+
that never subscribes publishes no series at all. CloudWatch
|
|
790
|
+
ListMetrics reports the metrics that had data in the past two weeks.
|
|
791
|
+
So: True when a successful ListMetrics answer holds any series for
|
|
792
|
+
the (StreamName, ConsumerName) pair; False only when a complete,
|
|
793
|
+
successful answer holds none; None (MISSING, noted) when it could not
|
|
794
|
+
be read. Never an empty GetMetricStatistics series read as idle.
|
|
795
|
+
|
|
796
|
+
Both providers implement it (repo rule: every get_* is abstract)."""
|
|
797
|
+
pass
|
|
798
|
+
|
|
779
799
|
@abstractmethod
|
|
780
800
|
async def get_firehose_delivery_streams(self) -> List['KinesisFirehoseData']:
|
|
781
801
|
"""Get all Kinesis Data Firehose delivery streams."""
|
|
@@ -1011,6 +1031,17 @@ class WasteDataProvider(ABC):
|
|
|
1011
1031
|
"""Get copy job summaries for the specified lookback period."""
|
|
1012
1032
|
pass
|
|
1013
1033
|
|
|
1034
|
+
async def backup_vault_exists(self, vault_arn: str) -> Optional[bool]:
|
|
1035
|
+
"""Whether the backup vault ``vault_arn`` exists now (CLO-589).
|
|
1036
|
+
|
|
1037
|
+
True / False only on a read that could see the vault; None when it
|
|
1038
|
+
could not be judged (a cross-account vault, a failed read, a
|
|
1039
|
+
provider that cannot look): MISSING, never "exists". ListCopyJobs
|
|
1040
|
+
keeps a deleted vault's COMPLETED jobs for its whole window, so
|
|
1041
|
+
backup_copy_policy_overreach needs this before naming a vault. The
|
|
1042
|
+
default cannot look."""
|
|
1043
|
+
return None
|
|
1044
|
+
|
|
1014
1045
|
# =========================================================================
|
|
1015
1046
|
# DocumentDB
|
|
1016
1047
|
# =========================================================================
|
{cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/data_providers/missing_data.py
RENAMED
|
@@ -23,6 +23,13 @@ logger = logging.getLogger(__name__)
|
|
|
23
23
|
# Example resource ids kept in a note; the count covers the rest.
|
|
24
24
|
_MAX_EXAMPLES = 3
|
|
25
25
|
|
|
26
|
+
# CLO-589 follow-up: the one note kinesis_enhanced_fan_out_waste's withheld
|
|
27
|
+
# verdicts share (both providers, the base default and the detector), kept
|
|
28
|
+
# apart from the stream-level 'kinesis' note so their wording never mixes.
|
|
29
|
+
KINESIS_EFO_NOTE_SERVICE = 'kinesis enhanced fan-out'
|
|
30
|
+
KINESIS_EFO_NOTE_VERDICT = 'never-subscribed consumer'
|
|
31
|
+
KINESIS_EFO_NOTE_EVIDENCE = 'SubscribeToShard series (ListMetrics)'
|
|
32
|
+
|
|
26
33
|
|
|
27
34
|
class MissingDataNotesMixin:
|
|
28
35
|
"""Provider mixin. Needs ``self.data_warnings`` (a list), ``self._region``
|
{cloudcostwise-0.1.2 → cloudcostwise-0.1.3}/src/cloudwise_scan_core/data_providers/models.py
RENAMED
|
@@ -583,6 +583,15 @@ class ElastiCacheMetricsData:
|
|
|
583
583
|
# applies to the CurrConnections coverage check — otherwise a shorter
|
|
584
584
|
# export could never pass a coverage gate sized to the full window.
|
|
585
585
|
cpu_window_days: Optional[int] = None
|
|
586
|
+
# CLO-572: DatabaseMemoryUsagePercentage datapoints behind
|
|
587
|
+
# database_memory_usage_pct, the same convention as cpu_datapoints: None
|
|
588
|
+
# means not reported, 0 means a read with no data (MISSING, never read
|
|
589
|
+
# as 0% used). oversized_elasticache's memory gate needs 75% coverage
|
|
590
|
+
# of memory_window_days (cpu_window_days's sibling: None means use
|
|
591
|
+
# period_days, the offline provider clamps it to the export's own
|
|
592
|
+
# shorter window).
|
|
593
|
+
memory_datapoints: Optional[int] = None
|
|
594
|
+
memory_window_days: Optional[int] = None
|
|
586
595
|
|
|
587
596
|
|
|
588
597
|
@dataclass
|
|
@@ -1422,8 +1431,11 @@ class NeptuneSnapshotData:
|
|
|
1422
1431
|
class NeptuneMetricsData:
|
|
1423
1432
|
"""Normalized Neptune cluster CloudWatch metrics."""
|
|
1424
1433
|
cluster_id: str
|
|
1425
|
-
|
|
1426
|
-
|
|
1434
|
+
# CLO-584: floats, not int -- a lightly used cluster's per-second-rate
|
|
1435
|
+
# Sum over the window can be well under 1, and an int() truncation made
|
|
1436
|
+
# that indistinguishable from true zero traffic.
|
|
1437
|
+
gremlin_requests: float = 0.0
|
|
1438
|
+
sparql_requests: float = 0.0
|
|
1427
1439
|
avg_cpu: float = 0.0
|
|
1428
1440
|
max_cpu: float = 0.0 # window maximum of hourly Maximums; reported, not gated by oversized_neptune
|
|
1429
1441
|
# CLO-480: the shared CPU rule's inputs (cloudwise_scan_core.cpu_sizing)
|