Skip to content

Commit f81c544

Browse files
NLB-7299: Cleanedup code to remove print statements
1 parent 6f17d21 commit f81c544

File tree

2 files changed

+45
-146
lines changed

2 files changed

+45
-146
lines changed

content/nginxaas-azure/billing/usage-and-cost-estimator.md

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Before using the cost analysis script:
164164
pip3 install azure-identity azure-mgmt-monitor
165165
```
166166

167-
4. **NGINXaaS for Azure deployment** with monitoring enabled
167+
4. **NGINXaaS for Azure deployment on Standard V3 Plan** with monitoring enabled
168168
5. **Azure AD Tenant ID** (required for authentication)
169169
6. **Monitoring Reader permissions** on your NGINXaaS resource
170170

@@ -214,41 +214,9 @@ python3 nginxaas_cost_analysis.py \
214214
{{< details "View sample output" >}}
215215

216216
```
217-
🌐 Using InteractiveBrowserCredential with tenant: d106871e-7b91-4733-8423-f98586303b68
218-
📈 Processing 72 hours of data...
219-
============================================================
220-
📈 COST ANALYSIS SUMMARY
221-
============================================================
222-
Total Analysis Period: 72 hours
223-
Total Cost: $32.40
224-
225-
🕐 HOURLY COST BREAKDOWN (First 5 hours):
226-
------------------------------------------------------------
227-
Hour 1 - 2025-11-18T00:00:00Z
228-
Fixed: $0.250 | WAF: $0.000 | NCU: $0.160
229-
Ports: $0.000 | Data: $0.000 | Total: $0.410
230-
231-
Hour 2 - 2025-11-18T01:00:00Z
232-
Fixed: $0.250 | WAF: $0.000 | NCU: $0.160
233-
Ports: $0.000 | Data: $0.000 | Total: $0.410
234-
235-
Hour 3 - 2025-11-18T02:00:00Z
236-
Fixed: $0.250 | WAF: $0.000 | NCU: $0.160
237-
Ports: $0.000 | Data: $0.000 | Total: $0.410
238-
239-
Hour 4 - 2025-11-18T03:00:00Z
240-
Fixed: $0.250 | WAF: $0.000 | NCU: $0.160
241-
Ports: $0.000 | Data: $0.000 | Total: $0.410
242-
243-
Hour 5 - 2025-11-18T04:00:00Z
244-
Fixed: $0.250 | WAF: $0.000 | NCU: $0.160
245-
Ports: $0.000 | Data: $0.000 | Total: $0.410
246-
247-
... and 67 more hours
248-
249-
✅ Cost breakdown exported to nginxaas_cost_breakdown.csv
250-
📊 Summary: 72 hours, Total cost: $32.40
251-
✅ Cost analysis completed successfully!
217+
Cost breakdown exported to nginxaas_cost_breakdown.csv
218+
Summary: 96 hours, Total cost: $71.66
219+
Cost analysis completed successfully!
252220
```
253221
254222
{{< /details >}}

static/scripts/nginxaas_cost_analysis.py

Lines changed: 41 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def get_metrics_azure_sdk(client, resource_id, metric_name, start_time, end_time
6969
timespan=f"{start_time}/{end_time}",
7070
interval="PT1M", # 1-minute intervals
7171
metricnames=metric_name,
72-
aggregation="Average,Maximum,Total",
72+
aggregation="Total",
7373
)
7474
return metrics_data
7575

@@ -86,24 +86,17 @@ def aggregate_hourly_data(metrics_data, metric_name):
8686
hourly_aggregates = {}
8787

8888
for point in data_points:
89-
# SDK format - use time_stamp attribute
89+
if point.total is None:
90+
continue
9091
timestamp = point.time_stamp
9192
hour_key = timestamp.replace(minute=0, second=0, microsecond=0)
92-
9393
if hour_key not in hourly_aggregates:
9494
hourly_aggregates[hour_key] = {
9595
'timestamp': hour_key.isoformat().replace('+00:00', 'Z'),
96-
'values': [],
9796
'totals': []
9897
}
99-
100-
if metric_name == "system.interface.total_bytes":
101-
total_val = point.total or 0
102-
hourly_aggregates[hour_key]['totals'].append(total_val)
103-
else:
104-
avg_val = point.average or 0
105-
max_val = point.maximum or 0
106-
hourly_aggregates[hour_key]['values'].append(max_val if max_val > 0 else avg_val)
98+
total_val = point.total
99+
hourly_aggregates[hour_key]['totals'].append(total_val)
107100

108101
result = []
109102
for hour_key in sorted(hourly_aggregates.keys()):
@@ -112,7 +105,7 @@ def aggregate_hourly_data(metrics_data, metric_name):
112105
if metric_name == "system.interface.total_bytes":
113106
final_value = sum(hour_data['totals'])
114107
else:
115-
final_value = max(hour_data['values']) if hour_data['values'] else 0
108+
final_value = max(hour_data['totals']) if hour_data['totals'] else 0
116109

117110
result.append({
118111
'timestamp': hour_data['timestamp'],
@@ -141,59 +134,48 @@ def calculate_cost_breakdown(date_range, resource_id, location, credential, subs
141134
waf_metrics_raw = get_metrics_azure_sdk(client, resource_id, "waf.enabled", start_time, end_time)
142135
ports_metrics_raw = get_metrics_azure_sdk(client, resource_id, "ports.used", start_time, end_time)
143136
ncu_metrics_raw = get_metrics_azure_sdk(client, resource_id, "ncu.provisioned", start_time, end_time)
144-
network_metrics_raw = get_metrics_azure_sdk(client, resource_id, "system.interface.total_bytes", start_time, end_time)
137+
data_processed_metrics_raw = get_metrics_azure_sdk(client, resource_id, "system.interface.total_bytes", start_time, end_time)
145138

146139
waf_hourly = aggregate_hourly_data(waf_metrics_raw, "waf.enabled")
147140
ports_hourly = aggregate_hourly_data(ports_metrics_raw, "ports.used")
148141
ncu_hourly = aggregate_hourly_data(ncu_metrics_raw, "ncu.provisioned")
149-
network_hourly = aggregate_hourly_data(network_metrics_raw, "system.interface.total_bytes")
142+
data_processed_hourly = aggregate_hourly_data(data_processed_metrics_raw, "system.interface.total_bytes")
150143

151144
cost_breakdown = []
152145

153-
if not (waf_hourly and ports_hourly and ncu_hourly and network_hourly):
146+
if not (waf_hourly and ports_hourly and ncu_hourly and data_processed_hourly):
154147
raise Exception("No metric data available for the specified time range")
155148

156-
min_length = min(len(waf_hourly), len(ports_hourly), len(ncu_hourly), len(network_hourly))
157-
print(f"📈 Processing {min_length} hours of data...")
158-
149+
min_length = min(len(waf_hourly), len(ports_hourly), len(ncu_hourly), len(data_processed_hourly))
159150
for i in range(min_length):
160151
timestamp = waf_hourly[i]['timestamp']
161-
waf_enabled = waf_hourly[i]['value'] == 1 # 1 means WAF is enabled
152+
waf_enabled = float(waf_hourly[i]['value']) == 1.0 # 1.0 means WAF is enabled
162153
ports_used = ports_hourly[i]['value']
163154
ncu_provisioned = ncu_hourly[i]['value']
164-
total_bytes = network_hourly[i]['value']
155+
total_bytes = data_processed_hourly[i]['value']
165156

166157
# Convert total bytes to GB
167158
data_processed_gb = total_bytes / (1024 ** 3) if total_bytes else 0
168159

169-
# Check if deployment is active (both NCU and ports > 0 indicates active deployment)
170-
deployment_active = ncu_provisioned > 0 or ports_used > 0
171-
172-
# Cost components - only charge fixed costs if deployment is active
173-
if deployment_active:
174-
fixed_deployment_cost = pricing["fixed"]["NGINX"]
175-
waf_cost = pricing["fixed"]["NGINX + WAF"] - fixed_deployment_cost if waf_enabled else 0
176-
else:
177-
fixed_deployment_cost = 0
178-
waf_cost = 0
179-
160+
base_deployment_cost = pricing["fixed"]["NGINX"]
161+
waf_deployment_cost = pricing["fixed"]["NGINX + WAF"] - base_deployment_cost if waf_enabled else 0
180162
base_ncu_cost = ncu_provisioned * pricing["ncu"]["NGINX"]
181163
waf_ncu_cost = (ncu_provisioned * (pricing["ncu"]["NGINX + WAF"] - pricing["ncu"]["NGINX"])) if waf_enabled else 0
182164

183-
ports_cost = (max(ports_used - 5, 0) * pricing["ncu"]["NGINX"]) # Cost for ports > 5
165+
ports_ncu_cost = (max(ports_used - 5, 0) * 2 * pricing["ncu"]["NGINX"]) # Cost for ports > 5
184166
data_processing_cost = data_processed_gb * pricing["data_processing"]
185167

186168
# Total hourly cost
187-
total_cost = fixed_deployment_cost + waf_cost + base_ncu_cost + waf_ncu_cost + ports_cost + data_processing_cost
169+
total_cost = base_deployment_cost + waf_deployment_cost + base_ncu_cost + waf_ncu_cost + ports_ncu_cost + data_processing_cost
188170

189171
# Append hourly cost breakdown
190172
cost_breakdown.append({
191173
"timestamp": timestamp,
192-
"fixed_deployment_cost": round(fixed_deployment_cost, 6),
193-
"waf_cost": round(waf_cost, 6),
174+
"base_deployment_cost": round(base_deployment_cost, 6),
175+
"waf_deployment_cost": round(waf_deployment_cost, 6),
194176
"base_ncu_cost": round(base_ncu_cost, 6),
195177
"waf_ncu_cost": round(waf_ncu_cost, 6),
196-
"ports_cost": round(ports_cost, 6),
178+
"ports_ncu_cost": round(ports_ncu_cost, 6),
197179
"data_processing_cost": round(data_processing_cost, 6),
198180
"total_cost": round(total_cost, 6)
199181
})
@@ -225,16 +207,13 @@ def parse_arguments():
225207
help='Azure region where NGINX is deployed (e.g., eastus2, westus2)')
226208
parser.add_argument('--date-range', '-d', required=True,
227209
help='Analysis period in ISO format: start/end (e.g., 2025-11-18T00:00:00Z/2025-11-19T23:59:59Z)')
228-
229-
# Required tenant-id argument
230210
parser.add_argument('--tenant-id', '-t', required=True,
231211
help='Azure AD Tenant ID (required for authentication)')
212+
# Optional arguments
232213
parser.add_argument('--subscription-id',
233214
help='Azure Subscription ID (extracted from resource-id if not provided)')
234215
parser.add_argument('--output', '-o', default='nginxaas_cost_breakdown.csv',
235216
help='Output CSV filename (default: nginxaas_cost_breakdown.csv)')
236-
parser.add_argument('--verbose', '-v', action='store_true',
237-
help='Enable verbose debug output')
238217

239218
return parser.parse_args()
240219

@@ -250,36 +229,22 @@ def main():
250229
"date_range": args.date_range,
251230
"tenant_id": args.tenant_id,
252231
"output_file": args.output,
253-
"verbose": args.verbose
254232
}
255233

256234
# Validate required arguments
257235
if not config["resource_id"].startswith("/subscriptions/"):
258-
print("❌ Error: Invalid resource ID format")
259-
print(" Resource ID should start with /subscriptions/")
260-
print(" Example: /subscriptions/xxx/resourceGroups/my-rg/providers/Nginx.NginxPlus/nginxDeployments/my-nginx")
236+
print("Error: Invalid resource ID format.\nResource ID should start with /subscriptions/.\nExample: /subscriptions/xxx/resourceGroups/my-rg/providers/Nginx.NginxPlus/nginxDeployments/my-nginx")
261237
return 1
262-
238+
263239
if not config["date_range"] or "/" not in config["date_range"]:
264-
print("❌ Error: Invalid date range format")
265-
print(" Use format: start/end (e.g., 2025-11-18T00:00:00Z/2025-11-19T23:59:59Z)")
240+
print("Error: Invalid date range format.\nUse format: start/end (e.g., 2025-11-18T00:00:00Z/2025-11-19T23:59:59Z)")
266241
return 1
267242

268-
try:
269-
print("🔧 Initializing NGINX for Azure Cost Analysis...")
270-
print(f"📍 Location: {config['location']}")
271-
print(f"📅 Date Range: {config['date_range']}")
272-
print(f"🔗 Resource: {config['resource_id'].split('/')[-1]}")
273-
print()
274-
275-
# Authentication with Azure
276-
print("🔐 Authenticating with Azure...")
277-
243+
try:
278244
# Use InteractiveBrowserCredential with required tenant_id
279245
credential = InteractiveBrowserCredential(
280246
tenant_id=config["tenant_id"]
281247
)
282-
print(f"🌐 Using InteractiveBrowserCredential with tenant: {config['tenant_id']}")
283248

284249
# Run the cost calculation
285250
result = calculate_cost_breakdown(
@@ -290,73 +255,39 @@ def main():
290255
config["subscription_id"]
291256
)
292257

293-
# Display summary statistics
294-
total_hours = len(result)
295-
total_cost = sum(entry["total_cost"] for entry in result)
296-
avg_hourly_cost = total_cost / total_hours if total_hours > 0 else 0
297-
298-
print("=" * 60)
299-
print("📈 COST ANALYSIS SUMMARY")
300-
print("=" * 60)
301-
print(f"Total Analysis Period: {total_hours} hours")
302-
print(f"Total Cost: ${total_cost:.2f}")
303-
print()
304-
305-
# Display detailed breakdown for first few hours
306-
print("🕐 HOURLY COST BREAKDOWN (First 5 hours):")
307-
print("-" * 60)
308-
for i, entry in enumerate(result[:5]):
309-
print(f"Hour {i+1} - {entry['timestamp']}")
310-
print(f" Fixed: ${entry['fixed_deployment_cost']:.3f} | WAF: ${entry['waf_cost']:.3f} | NCU: ${entry['base_ncu_cost']:.3f}")
311-
print(f" Ports: ${entry['ports_cost']:.3f} | Data: ${entry['data_processing_cost']:.3f} | Total: ${entry['total_cost']:.3f}")
312-
print()
313-
314-
if total_hours > 5:
315-
print(f"... and {total_hours - 5} more hours")
316-
print()
317-
318258
# Export to CSV
319259
export_to_csv(result, config["output_file"])
320260

321-
print("Cost analysis completed successfully!")
261+
print("Cost analysis completed successfully!")
322262
return 0
323263

324264
except Exception as e:
325265
error_message = str(e)
326-
print(f"❌ Error during cost analysis: {error_message}")
327-
266+
print(f"Error during cost analysis: {error_message}")
328267
if "authorization" in error_message.lower() or "403" in error_message:
329-
print("\n🔐 PERMISSIONS ERROR")
330-
print("=" * 25)
331-
print("Your Azure account needs access to read metrics from this NGINX resource.")
332-
print("This typically requires 'Monitoring Reader' or 'Reader' role on the resource.")
268+
print("\nPERMISSIONS ERROR\n" + "=" * 25 + "\nYour Azure account needs access to read metrics from this NGINX resource.\nThis typically requires 'Monitoring Reader' or 'Reader' role on the resource.")
333269
else:
334-
print(f"\n💡 Please check:")
335-
print(" • Your Azure permissions (Monitoring Reader role)")
336-
print(" • That the resource ID is correct")
337-
print(" • That the date range is within the last 30 days")
338-
print(" • Try running 'az login' first for DefaultAzureCredential")
339-
270+
print("\nPlease check:\n - Your Azure permissions (Monitoring Reader role)\n - That the resource ID is correct\n - That the date range is within the last 30 days\n")
340271
return 1
341272

342273
def export_to_csv(cost_breakdown, filename="nginx_cost_breakdown.csv"):
343274
"""Export cost breakdown to CSV file."""
344275
import csv
345276

346277
if not cost_breakdown:
347-
print("⚠️ No data to export")
278+
print("No data to export")
348279
return
349280

350281
try:
351282
with open(filename, 'w', newline='') as csvfile:
352283
fieldnames = cost_breakdown[0].keys()
353284
header_mapping = {
354285
"timestamp": "Timestamp",
355-
"fixed_deployment_cost": "Base Fixed Cost ($USD)",
356-
"waf_cost": "WAF Cost ($USD)",
286+
"base_deployment_cost": "Base Deployment Cost ($USD)",
287+
"waf_deployment_cost": "WAF Deployment Cost ($USD)",
357288
"base_ncu_cost": "Base NCU Cost ($USD)",
358289
"waf_ncu_cost": "WAF NCU Cost ($USD)",
359-
"ports_cost": "Ports Cost ($USD)",
290+
"ports_ncu_cost": "Ports NCU Cost ($USD)",
360291
"data_processing_cost": "Data Processing Cost ($USD)",
361292
"total_cost": "Total Cost ($USD)"
362293
}
@@ -372,11 +303,11 @@ def export_to_csv(cost_breakdown, filename="nginx_cost_breakdown.csv"):
372303

373304
# Calculate and write summary totals
374305
total_hours = len(cost_breakdown)
375-
total_fixed_deployment = sum(entry["fixed_deployment_cost"] for entry in cost_breakdown)
376-
total_waf = sum(entry["waf_cost"] for entry in cost_breakdown)
306+
total_fixed_deployment = sum(entry["base_deployment_cost"] for entry in cost_breakdown)
307+
total_waf = sum(entry["waf_deployment_cost"] for entry in cost_breakdown)
377308
total_base_ncu = sum(entry["base_ncu_cost"] for entry in cost_breakdown)
378309
total_waf_ncu = sum(entry["waf_ncu_cost"] for entry in cost_breakdown)
379-
total_ports = sum(entry["ports_cost"] for entry in cost_breakdown)
310+
total_ports = sum(entry["ports_ncu_cost"] for entry in cost_breakdown)
380311
total_data_processing = sum(entry["data_processing_cost"] for entry in cost_breakdown)
381312
total_cost = sum(entry["total_cost"] for entry in cost_breakdown)
382313

@@ -386,20 +317,20 @@ def export_to_csv(cost_breakdown, filename="nginx_cost_breakdown.csv"):
386317
# Add totals row with dollar signs
387318
totals_row = {
388319
"timestamp": f"TOTALS ({total_hours} hours)",
389-
"fixed_deployment_cost": f"${round(total_fixed_deployment, 4):.4f}",
390-
"waf_cost": f"${round(total_waf, 4):.4f}",
320+
"base_deployment_cost": f"${round(total_fixed_deployment, 4):.4f}",
321+
"waf_deployment_cost": f"${round(total_waf, 4):.4f}",
391322
"base_ncu_cost": f"${round(total_base_ncu, 4):.4f}",
392323
"waf_ncu_cost": f"${round(total_waf_ncu, 4):.4f}",
393-
"ports_cost": f"${round(total_ports, 4):.4f}",
324+
"ports_ncu_cost": f"${round(total_ports, 4):.4f}",
394325
"data_processing_cost": f"${round(total_data_processing, 4):.4f}",
395326
"total_cost": f"${round(total_cost, 2):.2f}"
396327
}
397328
writer.writerow(totals_row)
398329

399-
print(f"Cost breakdown exported to {filename}")
400-
print(f" 📊 Summary: {total_hours} hours, Total cost: ${total_cost:.2f}")
330+
print(f"Cost breakdown exported to {filename}")
331+
print(f"Summary: {total_hours} hours, Total cost: ${total_cost:.2f}")
401332
except Exception as e:
402-
print(f"Error exporting to CSV: {e}")
333+
print(f"Error exporting to CSV: {e}")
403334

404335
if __name__ == "__main__":
405336
sys.exit(main())

0 commit comments

Comments
 (0)