
Validating Performance Budgets
- 58 installs
- 2.6k repo stars
- Updated August 5, 2026
- jeremylongshore/claude-code-plugins-plus-skills
Validates page load times, bundle sizes and API response times against defined performance budgets.
About
Checks current performance metrics against predefined budgets to catch regressions before production. A developer uses it when enforcing performance thresholds in CI or reviews.
- Validates load times, bundle sizes and API response times
- Uses Lighthouse and webpack to detect regressions early
Validating Performance Budgets by the numbers
- 58 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,174 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill validating-performance-budgetsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 58 |
|---|---|
| repo stars | ★ 2.6k |
| Last updated | August 5, 2026 |
| Repository | jeremylongshore/claude-code-plugins-plus-skills ↗ |
What it does
Validates page load times, bundle sizes and API response times against defined performance budgets.
Files
Performance Budget Validator
Validate page load times, bundle sizes, and API response times against predefined performance budgets to catch regressions before they reach production.
Overview
This skill allows Claude to automatically validate your application's performance against predefined budgets. It helps identify performance regressions and ensures your application maintains optimal performance characteristics.
How It Works
1. Analyze Performance Metrics: Claude analyzes current performance metrics, such as page load times, bundle sizes, and API response times. 2. Validate Against Budget: The plugin validates these metrics against predefined performance budget thresholds. 3. Report Violations: If any metrics exceed the defined budget, the skill reports violations and provides details on the exceeded thresholds.
When to Use This Skill
This skill activates when you need to:
- Validate performance against predefined budgets.
- Identify performance regressions in your application.
- Integrate performance budget validation into your CI/CD pipeline.
Examples
Example 1: Preventing Performance Regressions
User request: "Validate performance budget for the homepage."
The skill will:
1. Analyze the homepage's performance metrics (load time, bundle size). 2. Compare these metrics against the defined budget. 3. Report any violations, such as exceeding the load time budget.
Example 2: Integrating with CI/CD
User request: "Run performance budget validation as part of the build process."
The skill will:
1. Execute the performance budget validation command. 2. Check all defined performance metrics against their budgets. 3. Report any violations that would cause the build to fail.
Best Practices
- Budget Definition: Define realistic and achievable performance budgets based on current application performance and user expectations.
- Metric Selection: Choose relevant performance metrics that directly impact user experience, such as page load times and API response times.
- CI/CD Integration: Integrate performance budget validation into your CI/CD pipeline to automatically detect and prevent performance regressions.
Integration
This skill can be integrated with other plugins that provide performance metrics, such as website speed test tools or API monitoring services. It can also be used in conjunction with alerting plugins to notify developers of performance budget violations.
Prerequisites
- Performance budget definitions in ${CLAUDE_SKILL_DIR}/performance-budgets.json
- Access to performance testing tools (Lighthouse, WebPageTest)
- Build output directory for bundle analysis
- Historical performance metrics for comparison
Instructions
1. Load performance budget configuration 2. Collect current performance metrics (load time, bundle size, API latency) 3. Compare metrics against defined budget thresholds 4. Identify budget violations and severity 5. Generate detailed violation report 6. Provide remediation recommendations
Output
- Performance budget validation report
- List of metrics exceeding budget thresholds
- Comparison with previous measurements
- Detailed breakdown by metric category
- Actionable recommendations for fixes
Error Handling
If budget validation fails:
- Verify budget configuration file exists
- Check performance testing tool availability
- Validate metric collection permissions
- Ensure network access to test endpoints
- Review budget threshold definitions
Resources
- Performance budget best practices
- Lighthouse performance scoring guide
- Bundle size optimization techniques
- CI/CD integration patterns for performance testing
Assets
Bundled resources for performance-budget-validator skill
References
Bundled resources for performance-budget-validator skill
#!/usr/bin/env python3
"""
Performance Budget Creation Script
Assists in creating or updating performance budget configuration files.
Supports interactive mode, template-based creation, and baseline generation
from existing metrics.
Usage:
create_budget.py --interactive
create_budget.py --template default --output budget.json
create_budget.py --baseline metrics.json --margin 10 --output budget.json
"""
import argparse
import json
import sys
from pathlib import Path
from typing import Dict, Any, Optional
from datetime import datetime
class BudgetCreator:
"""Creates and manages performance budget configurations."""
TEMPLATES = {
"default": {
"name": "Default Performance Budget",
"version": "1.0.0",
"budgets": {
"page_load_time": 3000,
"first_contentful_paint": 1500,
"largest_contentful_paint": 2500,
"time_to_interactive": 3500,
"bundle_size": 200000,
"js_bundle_size": 150000,
"css_bundle_size": 50000,
},
"description": "Default performance budgets for web applications",
},
"strict": {
"name": "Strict Performance Budget",
"version": "1.0.0",
"budgets": {
"page_load_time": 2000,
"first_contentful_paint": 800,
"largest_contentful_paint": 1500,
"time_to_interactive": 2500,
"bundle_size": 100000,
"js_bundle_size": 75000,
"css_bundle_size": 25000,
},
"description": "Strict performance budgets for high-performance applications",
},
"mobile": {
"name": "Mobile Performance Budget",
"version": "1.0.0",
"budgets": {
"page_load_time": 5000,
"first_contentful_paint": 2500,
"largest_contentful_paint": 4000,
"time_to_interactive": 5500,
"bundle_size": 150000,
"js_bundle_size": 100000,
"css_bundle_size": 50000,
},
"description": "Performance budgets optimized for mobile devices",
},
"api": {
"name": "API Performance Budget",
"version": "1.0.0",
"budgets": {
"api_response_time": 500,
"p95_response_time": 2000,
"p99_response_time": 5000,
"error_rate": 0.5,
},
"description": "Performance budgets for API services",
},
}
def __init__(self):
"""Initialize budget creator."""
self.budget = {}
def create_interactive(self) -> Dict[str, Any]:
"""Create budget interactively."""
print("\n" + "=" * 60)
print("Performance Budget Creator - Interactive Mode")
print("=" * 60)
# Basic information
name = input("\nBudget name: ").strip() or "Performance Budget"
description = input("Description (optional): ").strip()
version = input("Version (default: 1.0.0): ").strip() or "1.0.0"
self.budget = {
"name": name,
"version": version,
"created": datetime.now().isoformat(),
"description": description,
"budgets": {},
}
# Add metrics
print("\n" + "-" * 60)
print("Add Performance Metrics")
print("-" * 60)
print("\nAvailable metrics:")
metrics_info = {
"1": ("page_load_time", "ms", 3000),
"2": ("first_contentful_paint", "ms", 1500),
"3": ("largest_contentful_paint", "ms", 2500),
"4": ("time_to_interactive", "ms", 3500),
"5": ("bundle_size", "KB", 200),
"6": ("js_bundle_size", "KB", 150),
"7": ("css_bundle_size", "KB", 50),
"8": ("api_response_time", "ms", 500),
"9": ("memory_usage", "MB", 100),
"10": ("custom metric", None, None),
}
for key, (metric_name, unit, default) in metrics_info.items():
if default:
print(f" {key}. {metric_name} ({unit}) - default: {default}")
else:
print(f" {key}. {metric_name}")
while True:
choice = input("\nSelect metrics (comma-separated, or 'done' to finish): ").strip()
if choice.lower() == "done":
break
for selection in choice.split(","):
selection = selection.strip()
if selection in metrics_info:
metric_name, unit, default = metrics_info[selection]
if metric_name == "custom metric":
custom_name = input(" Custom metric name: ").strip()
custom_budget = input(" Budget value: ").strip()
input(" Unit (ms/KB/etc): ").strip()
if custom_name and custom_budget:
try:
self.budget["budgets"][custom_name] = float(custom_budget)
print(f" ✓ Added {custom_name}")
except ValueError:
print(" ✗ Invalid budget value")
else:
budget_value = input(f" Budget for {metric_name} (default: {default}): ").strip()
try:
if budget_value:
self.budget["budgets"][metric_name] = float(budget_value)
else:
self.budget["budgets"][metric_name] = default
print(f" ✓ Added {metric_name}")
except ValueError:
print(" ✗ Invalid budget value, skipping")
return self.budget
def create_from_template(self, template_name: str) -> Dict[str, Any]:
"""Create budget from template."""
if template_name not in self.TEMPLATES:
raise ValueError(f"Unknown template: {template_name}")
template = self.TEMPLATES[template_name].copy()
template["created"] = datetime.now().isoformat()
return template
def create_from_baseline(
self, metrics: Dict[str, Any], margin_percent: float = 10.0, percentile: float = 1.0
) -> Dict[str, Any]:
"""
Create budget from baseline metrics with margin.
Args:
metrics: Current metrics dictionary
margin_percent: Safety margin percentage (default: 10%)
percentile: Percentile multiplier (e.g., 1.0 for p100, 0.95 for p95)
Returns:
Budget configuration
"""
self.budget = {
"name": "Generated Performance Budget",
"version": "1.0.0",
"created": datetime.now().isoformat(),
"description": f"Auto-generated from baseline with {margin_percent}% margin",
"baseline_metrics": metrics.copy(),
"generation_params": {"margin_percent": margin_percent, "percentile": percentile},
"budgets": {},
}
# Convert metrics to budgets with margin
for metric_name, metric_value in metrics.items():
if isinstance(metric_value, (int, float)):
# Apply percentile
adjusted_value = metric_value * percentile
# Add margin
budget_value = adjusted_value * (1 + margin_percent / 100)
self.budget["budgets"][metric_name] = round(budget_value, 2)
return self.budget
def create_tiered(
self, metrics: Dict[str, Any], warning_margin: float = 5.0, critical_margin: float = 15.0
) -> Dict[str, Any]:
"""
Create tiered budget with warning and critical thresholds.
Args:
metrics: Baseline metrics
warning_margin: Margin for warning threshold
critical_margin: Margin for critical threshold
Returns:
Tiered budget configuration
"""
self.budget = {
"name": "Tiered Performance Budget",
"version": "1.0.0",
"created": datetime.now().isoformat(),
"description": f"Tiered thresholds: warning at {warning_margin}%, critical at {critical_margin}%",
"budgets": {},
}
for metric_name, metric_value in metrics.items():
if isinstance(metric_value, (int, float)):
self.budget["budgets"][metric_name] = {
"baseline": metric_value,
"warning": round(metric_value * (1 + warning_margin / 100), 2),
"critical": round(metric_value * (1 + critical_margin / 100), 2),
}
return self.budget
def add_metric(
self, metric_name: str, budget_value: float, unit: Optional[str] = None, description: Optional[str] = None
) -> None:
"""Add a metric to the budget."""
if not self.budget:
self.budget = {
"name": "Performance Budget",
"version": "1.0.0",
"created": datetime.now().isoformat(),
"budgets": {},
}
if isinstance(budget_value, dict):
# Support threshold-based budgets
self.budget["budgets"][metric_name] = budget_value
else:
self.budget["budgets"][metric_name] = float(budget_value)
def save(self, output_path: str) -> None:
"""Save budget to file."""
if not self.budget:
raise ValueError("No budget to save")
output = Path(output_path)
output.parent.mkdir(parents=True, exist_ok=True)
with open(output, "w") as f:
json.dump(self.budget, f, indent=2)
print(f"Budget saved to: {output_path}")
@staticmethod
def list_templates() -> None:
"""List available templates."""
print("\nAvailable Templates:")
print("-" * 60)
for name, template in BudgetCreator.TEMPLATES.items():
print(f"\n{name}:")
print(f" Description: {template['description']}")
print(f" Metrics: {', '.join(template['budgets'].keys())}")
def get_budget(self) -> Dict[str, Any]:
"""Get the current budget."""
return self.budget
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Create or update performance budget configurations",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
create_budget.py --interactive
create_budget.py --template default --output budget.json
create_budget.py --template strict --output budget.json
create_budget.py --baseline metrics.json --margin 15 --output budget.json
create_budget.py --baseline metrics.json --tiered --output budget.json
create_budget.py --list-templates
""",
)
parser.add_argument("-i", "--interactive", action="store_true", help="Interactive budget creation")
parser.add_argument(
"-t", "--template", type=str, choices=list(BudgetCreator.TEMPLATES.keys()), help="Use a predefined template"
)
parser.add_argument("-b", "--baseline", type=str, help="Generate from baseline metrics file")
parser.add_argument(
"-m", "--margin", type=float, default=10.0, help="Safety margin percentage for baseline (default: 10)"
)
parser.add_argument("--tiered", action="store_true", help="Generate tiered budget (baseline, warning, critical)")
parser.add_argument("-o", "--output", type=str, help="Output budget file")
parser.add_argument("--list-templates", action="store_true", help="List available templates")
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
args = parser.parse_args()
try:
creator = BudgetCreator()
# List templates
if args.list_templates:
creator.list_templates()
return 0
# Create budget
if args.interactive:
budget = creator.create_interactive()
elif args.baseline:
baseline_path = Path(args.baseline)
if not baseline_path.exists():
print(f"Error: Baseline file not found: {args.baseline}", file=sys.stderr)
return 1
with open(baseline_path, "r") as f:
metrics = json.load(f)
if args.tiered:
budget = creator.create_tiered(metrics)
else:
budget = creator.create_from_baseline(metrics, args.margin)
elif args.template:
budget = creator.create_from_template(args.template)
else:
parser.error("One of --interactive, --template, or --baseline is required")
# Output
if args.output:
creator.save(args.output)
else:
print(json.dumps(budget, indent=2))
if args.verbose:
print(f"\nBudget created with {len(budget.get('budgets', {}))} metrics")
return 0
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
Scripts
Bundled resources for performance-budget-validator skill
- [x] validate_budget.py: Script to execute the performance budget validation logic, potentially integrating with Lighthouse or other performance testing tools.
- [x] create_budget.py: Script to assist in creating or updating the performance budget configuration file.
- [x] report_violation.py: Script to generate reports or send alerts when performance budgets are violated.
#!/usr/bin/env python3
"""
Performance Budget Violation Reporter Script
Generates reports and sends alerts when performance budgets are violated.
Supports multiple output formats and integrations with monitoring systems.
Usage:
report_violation.py --report validation_report.json
report_violation.py --report validation_report.json --output violations.html
report_violation.py --report validation_report.json --slack webhook_url
report_violation.py --report validation_report.json --email team@example.com
"""
import argparse
import json
import sys
from pathlib import Path
from typing import Dict, Any
from datetime import datetime
class ViolationReporter:
"""Reports performance budget violations."""
def __init__(self):
"""Initialize violation reporter."""
self.violations = []
self.warnings = []
self.passed = []
def parse_report(self, report: Dict[str, Any]) -> None:
"""Parse validation report."""
self.violations = report.get("violations", [])
self.warnings = report.get("warnings", [])
self.passed = report.get("passed_checks", [])
def generate_text_report(self) -> str:
"""Generate text format report."""
lines = []
lines.append("=" * 70)
lines.append("PERFORMANCE BUDGET VIOLATION REPORT")
lines.append("=" * 70)
lines.append(f"Generated: {datetime.now().isoformat()}")
lines.append("")
# Summary
lines.append("SUMMARY")
lines.append("-" * 70)
lines.append(f"Total Violations: {len(self.violations)}")
lines.append(f"Warnings: {len(self.warnings)}")
lines.append(f"Passed Checks: {len(self.passed)}")
lines.append("")
# Critical violations
critical = [v for v in self.violations if v.get("severity") == "critical"]
if critical:
lines.append("CRITICAL VIOLATIONS")
lines.append("-" * 70)
for violation in critical:
lines.append(f"\nMetric: {violation.get('metric')}")
lines.append(f" Budget: {violation.get('budget')} {violation.get('unit', '')}")
lines.append(f" Actual: {violation.get('actual')} {violation.get('unit', '')}")
lines.append(f" Over: {violation.get('percentage_over', 'N/A')}%")
if violation.get("difference"):
lines.append(f" Excess: {violation.get('difference')} {violation.get('unit', '')}")
lines.append("")
# Other violations
other_violations = [v for v in self.violations if v.get("severity") != "critical"]
if other_violations:
lines.append("VIOLATIONS")
lines.append("-" * 70)
for violation in other_violations:
lines.append(f"\nMetric: {violation.get('metric')}")
lines.append(f" Budget: {violation.get('budget')} {violation.get('unit', '')}")
lines.append(f" Actual: {violation.get('actual')} {violation.get('unit', '')}")
lines.append("")
# Warnings
if self.warnings:
lines.append("WARNINGS")
lines.append("-" * 70)
for warning in self.warnings:
lines.append(f"- {warning.get('message', str(warning))}")
lines.append("")
# Recommendations
lines.append("RECOMMENDATIONS")
lines.append("-" * 70)
if len(self.violations) > 0:
lines.append("1. Address critical violations immediately")
lines.append("2. Implement performance optimizations")
lines.append("3. Review deployment process for performance regressions")
else:
lines.append("✓ All performance metrics within budget")
lines.append("=" * 70)
return "\n".join(lines)
def generate_html_report(self) -> str:
"""Generate HTML format report."""
html = []
html.append("<!DOCTYPE html>")
html.append("<html>")
html.append("<head>")
html.append("<meta charset='utf-8'>")
html.append("<title>Performance Budget Violation Report</title>")
html.append("<style>")
html.append("""
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
.container { max-width: 1000px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }
h2 { color: #555; margin-top: 30px; }
.summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin: 20px 0; }
.summary-item { background: #f9f9f9; padding: 15px; border-left: 4px solid #007bff; }
.summary-item.violations { border-left-color: #dc3545; }
.summary-item.warnings { border-left-color: #ffc107; }
.summary-item.passed { border-left-color: #28a745; }
.summary-item h3 { margin: 0 0 5px 0; font-size: 14px; color: #666; }
.summary-item .value { font-size: 24px; font-weight: bold; }
table { width: 100%; border-collapse: collapse; margin: 15px 0; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
th { background: #007bff; color: white; font-weight: bold; }
tr:hover { background: #f5f5f5; }
.critical { color: #dc3545; font-weight: bold; }
.warning { color: #ffc107; }
.passed { color: #28a745; }
.metric { font-family: monospace; background: #f5f5f5; padding: 2px 6px; border-radius: 3px; }
.percentage { font-weight: bold; }
.recommendations { background: #e7f3ff; border-left: 4px solid #007bff; padding: 15px; margin-top: 20px; }
.recommendations ul { margin: 10px 0; padding-left: 20px; }
.recommendations li { margin: 8px 0; }
.footer { text-align: center; margin-top: 30px; color: #999; font-size: 12px; }
""")
html.append("</style>")
html.append("</head>")
html.append("<body>")
html.append("<div class='container'>")
# Header
html.append("<h1>Performance Budget Violation Report</h1>")
html.append(f"<p>Generated: {datetime.now().isoformat()}</p>")
# Summary
html.append("<div class='summary'>")
html.append(
f"<div class='summary-item violations'><h3>Violations</h3><div class='value'>{len(self.violations)}</div></div>"
)
html.append(
f"<div class='summary-item warnings'><h3>Warnings</h3><div class='value'>{len(self.warnings)}</div></div>"
)
html.append(
f"<div class='summary-item passed'><h3>Passed</h3><div class='value'>{len(self.passed)}</div></div>"
)
html.append(
f"<div class='summary-item'><h3>Total</h3><div class='value'>{len(self.violations) + len(self.warnings) + len(self.passed)}</div></div>"
)
html.append("</div>")
# Critical violations
critical = [v for v in self.violations if v.get("severity") == "critical"]
if critical:
html.append("<h2>Critical Violations</h2>")
html.append("<table>")
html.append("<tr><th>Metric</th><th>Budget</th><th>Actual</th><th>Over</th><th>Unit</th></tr>")
for violation in critical:
html.append("<tr class='critical'>")
html.append(f"<td class='metric'>{violation.get('metric')}</td>")
html.append(f"<td>{violation.get('budget')}</td>")
html.append(f"<td>{violation.get('actual')}</td>")
html.append(f"<td class='percentage'>{violation.get('percentage_over', 'N/A')}%</td>")
html.append(f"<td>{violation.get('unit', '')}</td>")
html.append("</tr>")
html.append("</table>")
# Other violations
other_violations = [v for v in self.violations if v.get("severity") != "critical"]
if other_violations:
html.append("<h2>Violations</h2>")
html.append("<table>")
html.append("<tr><th>Metric</th><th>Budget</th><th>Actual</th><th>Severity</th><th>Unit</th></tr>")
for violation in other_violations:
html.append("<tr>")
html.append(f"<td class='metric'>{violation.get('metric')}</td>")
html.append(f"<td>{violation.get('budget')}</td>")
html.append(f"<td>{violation.get('actual')}</td>")
html.append(f"<td>{violation.get('severity', 'unknown')}</td>")
html.append(f"<td>{violation.get('unit', '')}</td>")
html.append("</tr>")
html.append("</table>")
# Warnings
if self.warnings:
html.append("<h2>Warnings</h2>")
html.append("<table>")
html.append("<tr><th>Metric</th><th>Message</th></tr>")
for warning in self.warnings:
html.append("<tr class='warning'>")
html.append(f"<td class='metric'>{warning.get('metric', 'N/A')}</td>")
html.append(f"<td>{warning.get('message', str(warning))}</td>")
html.append("</tr>")
html.append("</table>")
# Passed checks (summary)
if self.passed:
html.append(f"<h2>Passed Checks ({len(self.passed)})</h2>")
html.append(f"<p class='passed'>✓ {len(self.passed)} metrics are within budget</p>")
# Recommendations
html.append("<div class='recommendations'>")
html.append("<h2>Recommendations</h2>")
html.append("<ul>")
if len(self.violations) > 0:
html.append("<li>Address critical violations immediately</li>")
html.append("<li>Implement performance optimizations for over-budget metrics</li>")
html.append("<li>Review recent changes that may have caused regressions</li>")
html.append("<li>Consider increasing budget if performance targets have changed</li>")
else:
html.append("<li>✓ All performance metrics are within budget</li>")
html.append("</ul>")
html.append("</div>")
# Footer
html.append("<div class='footer'>")
html.append(f"<p>Report generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>")
html.append("</div>")
html.append("</div>")
html.append("</body>")
html.append("</html>")
return "\n".join(html)
def generate_json_report(self) -> Dict[str, Any]:
"""Generate JSON format report."""
return {
"timestamp": datetime.now().isoformat(),
"violations": self.violations,
"warnings": self.warnings,
"passed_checks": self.passed,
"summary": {
"total_violations": len(self.violations),
"critical_violations": len([v for v in self.violations if v.get("severity") == "critical"]),
"warnings": len(self.warnings),
"passed": len(self.passed),
},
}
def generate_slack_message(self) -> Dict[str, Any]:
"""Generate Slack message payload."""
critical_count = len([v for v in self.violations if v.get("severity") == "critical"])
violation_count = len(self.violations)
color = "#28a745" if violation_count == 0 else "#ffc107" if critical_count == 0 else "#dc3545"
status = "✓ PASSED" if violation_count == 0 else f"⚠ {violation_count} VIOLATION(S)"
fields = []
# Summary
fields.append({"title": "Violations", "value": str(len(self.violations)), "short": True})
fields.append({"title": "Critical", "value": str(critical_count), "short": True})
fields.append({"title": "Warnings", "value": str(len(self.warnings)), "short": True})
fields.append({"title": "Passed", "value": str(len(self.passed)), "short": True})
# Top violations
if critical_count > 0:
top_violations = sorted(self.violations, key=lambda x: x.get("percentage_over", 0), reverse=True)[:3]
violations_text = "\n".join(
[
f"• {v.get('metric')}: {v.get('percentage_over', 'N/A')}% over ({v.get('actual')}/{v.get('budget')} {v.get('unit', '')})"
for v in top_violations
]
)
fields.append({"title": "Top Violations", "value": violations_text, "short": False})
return {
"attachments": [
{
"color": color,
"title": f"Performance Budget Report - {status}",
"text": f"Performance budget validation at {datetime.now().isoformat()}",
"fields": fields,
"footer": "Performance Budget Validator",
"ts": int(datetime.now().timestamp()),
}
]
}
def send_slack(self, webhook_url: str) -> bool:
"""Send report to Slack."""
try:
import urllib.request
import json as json_module
message = self.generate_slack_message()
data = json_module.dumps(message).encode("utf-8")
req = urllib.request.Request(webhook_url, data=data, method="POST")
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req) as response:
return response.status == 200
except Exception as e:
print(f"Error sending to Slack: {e}", file=sys.stderr)
return False
def generate_csv_report(self) -> str:
"""Generate CSV format report."""
lines = []
lines.append("Type,Metric,Budget,Actual,Difference,Percentage,Unit,Severity")
for violation in self.violations:
lines.append(
f"Violation,{violation.get('metric')},{violation.get('budget')},{violation.get('actual')},{violation.get('difference', '')},{violation.get('percentage_over', '')},{violation.get('unit', '')},{violation.get('severity', '')}"
)
for warning in self.warnings:
lines.append(
f"Warning,{warning.get('metric', '')},{warning.get('budget', '')},{warning.get('actual', '')},{warning.get('difference', '')},{warning.get('percentage_over', '')},{warning.get('unit', '')},{warning.get('severity', '')}"
)
for passed in self.passed:
lines.append(
f"Passed,{passed.get('metric')},{passed.get('budget')},{passed.get('actual')},0,0%,{passed.get('unit', '')},passed"
)
return "\n".join(lines)
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Generate reports for performance budget violations",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
report_violation.py --report validation.json --output report.html
report_violation.py --report validation.json --format text
report_violation.py --report validation.json --format csv --output violations.csv
report_violation.py --report validation.json --slack https://hooks.slack.com/...
""",
)
parser.add_argument("-r", "--report", type=str, required=True, help="Path to validation report JSON file")
parser.add_argument(
"-f",
"--format",
type=str,
choices=["text", "html", "json", "csv"],
default="text",
help="Report format (default: text)",
)
parser.add_argument("-o", "--output", type=str, help="Output file (stdout if not specified)")
parser.add_argument("-s", "--slack", type=str, help="Slack webhook URL for sending report")
parser.add_argument("-e", "--email", type=str, help="Email address to send report to")
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
args = parser.parse_args()
try:
# Load validation report
report_path = Path(args.report)
if not report_path.exists():
print(f"Error: Report file not found: {args.report}", file=sys.stderr)
return 1
with open(report_path, "r") as f:
report_data = json.load(f)
# Create reporter
reporter = ViolationReporter()
reporter.parse_report(report_data)
# Generate report
if args.format == "text":
output_text = reporter.generate_text_report()
elif args.format == "html":
output_text = reporter.generate_html_report()
elif args.format == "json":
output_text = json.dumps(reporter.generate_json_report(), indent=2)
elif args.format == "csv":
output_text = reporter.generate_csv_report()
else:
output_text = reporter.generate_text_report()
# Output
if args.output:
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w") as f:
f.write(output_text)
print(f"Report written to: {args.output}")
else:
print(output_text)
# Send to Slack if specified
if args.slack:
if reporter.send_slack(args.slack):
print("Report sent to Slack successfully")
else:
print("Failed to send report to Slack", file=sys.stderr)
return 1
return 0
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in report: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env python3
"""
Performance Budget Validator Script
Executes performance budget validation logic by comparing actual metrics
against configured budgets. Supports multiple metric types and integration
with build processes.
Usage:
validate_budget.py --budget budget.json --metrics metrics.json
validate_budget.py --budget budget.json --lighthouse lighthouse.json --output report.json
validate_budget.py --config config.json --fail-on-violation
"""
import argparse
import json
import sys
from pathlib import Path
from typing import Dict, List, Any
from datetime import datetime
class PerformanceBudgetValidator:
"""Validates performance metrics against defined budgets."""
METRIC_UNITS = {
"page_load_time": "ms",
"first_contentful_paint": "ms",
"largest_contentful_paint": "ms",
"time_to_interactive": "ms",
"bundle_size": "KB",
"js_bundle_size": "KB",
"css_bundle_size": "KB",
"image_size": "KB",
"api_response_time": "ms",
"lighthouse_score": "score (0-100)",
"memory_usage": "MB",
"cpu_usage": "%",
}
def __init__(self):
"""Initialize budget validator."""
self.violations = []
self.warnings = []
self.passed_checks = []
def validate_metrics(self, budget: Dict[str, Any], metrics: Dict[str, Any]) -> Dict[str, Any]:
"""
Validate performance metrics against budget.
Args:
budget: Budget configuration dictionary
metrics: Actual performance metrics dictionary
Returns:
Validation report dictionary
"""
self.violations = []
self.warnings = []
self.passed_checks = []
if not budget:
return {"valid": False, "error": "Empty budget configuration", "timestamp": datetime.now().isoformat()}
if not metrics:
return {"valid": False, "error": "No metrics provided", "timestamp": datetime.now().isoformat()}
# Validate each budget constraint
budget_items = budget.get("budgets", [])
if not budget_items:
budget_items = budget # Support flat structure
for item_name, budget_value in (
budget_items.items() if isinstance(budget_items, dict) else enumerate(budget_items)
):
if isinstance(budget_items, dict):
# Named budget
self._validate_metric(item_name, budget_value, metrics)
else:
# List of budgets
self._validate_budget_item(budget_value, metrics)
return self._generate_report(budget, metrics)
def _validate_metric(self, metric_name: str, budget_value: Any, metrics: Dict[str, Any]) -> None:
"""Validate a single metric against budget."""
actual_value = metrics.get(metric_name)
if actual_value is None:
self.warnings.append(
{
"severity": "warning",
"metric": metric_name,
"issue": f"Metric '{metric_name}' not found in metrics",
"budget": budget_value,
}
)
return
# Handle budget as dict with thresholds
if isinstance(budget_value, dict):
self._validate_threshold_budget(metric_name, budget_value, actual_value)
else:
# Simple comparison
if actual_value > budget_value:
self.violations.append(
{
"severity": "violation",
"metric": metric_name,
"budget": budget_value,
"actual": actual_value,
"difference": actual_value - budget_value,
"percentage_over": round((actual_value - budget_value) / budget_value * 100, 2),
"unit": self.METRIC_UNITS.get(metric_name, "units"),
"message": f"{metric_name}: {actual_value} exceeds budget of {budget_value}",
}
)
else:
self.passed_checks.append(
{
"metric": metric_name,
"budget": budget_value,
"actual": actual_value,
"margin": budget_value - actual_value,
"unit": self.METRIC_UNITS.get(metric_name, "units"),
}
)
def _validate_threshold_budget(self, metric_name: str, budget_config: Dict[str, Any], actual_value: float) -> None:
"""Validate metric against threshold-based budget."""
critical = budget_config.get("critical")
warning = budget_config.get("warning")
if critical is not None and actual_value > critical:
self.violations.append(
{
"severity": "critical",
"metric": metric_name,
"budget_critical": critical,
"actual": actual_value,
"difference": actual_value - critical,
"percentage_over": round((actual_value - critical) / critical * 100, 2),
"unit": self.METRIC_UNITS.get(metric_name, "units"),
"message": f"{metric_name}: {actual_value} CRITICAL (exceeds {critical})",
}
)
elif warning is not None and actual_value > warning:
self.warnings.append(
{
"severity": "warning",
"metric": metric_name,
"budget_warning": warning,
"actual": actual_value,
"difference": actual_value - warning,
"percentage_over": round((actual_value - warning) / warning * 100, 2),
"unit": self.METRIC_UNITS.get(metric_name, "units"),
"message": f"{metric_name}: {actual_value} WARNING (exceeds {warning})",
}
)
else:
self.passed_checks.append(
{
"metric": metric_name,
"budget_critical": critical,
"budget_warning": warning,
"actual": actual_value,
"unit": self.METRIC_UNITS.get(metric_name, "units"),
}
)
def _validate_budget_item(self, item: Dict[str, Any], metrics: Dict[str, Any]) -> None:
"""Validate a budget item from list format."""
metric_name = item.get("name") or item.get("metric")
budget_value = item.get("budget") or item.get("threshold")
item_type = item.get("type", "metric")
if not metric_name or budget_value is None:
return
actual_value = metrics.get(metric_name)
if actual_value is None:
self.warnings.append(
{"severity": "warning", "metric": metric_name, "issue": f"Metric not found: {metric_name}"}
)
return
# Handle different comparison types
comparison = item.get("comparison", ">")
if comparison == ">":
exceeds = actual_value > budget_value
elif comparison == "<":
exceeds = actual_value < budget_value
elif comparison == ">=":
exceeds = actual_value >= budget_value
elif comparison == "<=":
exceeds = actual_value <= budget_value
else:
exceeds = actual_value > budget_value
if exceeds:
difference = abs(actual_value - budget_value)
if budget_value != 0:
percentage = round(difference / budget_value * 100, 2)
else:
percentage = 0
self.violations.append(
{
"severity": item.get("severity", "violation"),
"metric": metric_name,
"type": item_type,
"comparison": comparison,
"budget": budget_value,
"actual": actual_value,
"difference": difference,
"percentage_difference": percentage,
"unit": item.get("unit", self.METRIC_UNITS.get(metric_name, "units")),
"message": f"{metric_name}: {actual_value} {comparison} {budget_value}",
}
)
else:
self.passed_checks.append(
{
"metric": metric_name,
"budget": budget_value,
"actual": actual_value,
"unit": item.get("unit", self.METRIC_UNITS.get(metric_name, "units")),
}
)
def _generate_report(self, budget: Dict[str, Any], metrics: Dict[str, Any]) -> Dict[str, Any]:
"""Generate validation report."""
total_checks = len(self.violations) + len(self.warnings) + len(self.passed_checks)
critical_violations = len([v for v in self.violations if v.get("severity") == "critical"])
violation_count = len(self.violations)
return {
"timestamp": datetime.now().isoformat(),
"valid": violation_count == 0,
"summary": {
"total_checks": total_checks,
"passed": len(self.passed_checks),
"warnings": len(self.warnings),
"violations": violation_count,
"critical_violations": critical_violations,
"pass_rate": round(len(self.passed_checks) / total_checks * 100, 2) if total_checks > 0 else 100,
},
"budget_name": budget.get("name", "Performance Budget"),
"budget_version": budget.get("version", "1.0.0"),
"passed_checks": self.passed_checks,
"warnings": self.warnings,
"violations": self.violations,
"recommendations": self._generate_recommendations(),
}
def _generate_recommendations(self) -> List[str]:
"""Generate improvement recommendations."""
recommendations = []
if len(self.violations) > 0:
recommendations.append(f"Fix {len(self.violations)} performance budget violation(s)")
# Categorize violations
by_metric = {}
for violation in self.violations:
metric = violation.get("metric", "unknown")
if metric not in by_metric:
by_metric[metric] = 0
by_metric[metric] += 1
for metric, count in sorted(by_metric.items(), key=lambda x: x[1], reverse=True):
if count >= 2:
recommendations.append(f"Focus on reducing {metric} ({count} violations)")
if any("bundle_size" in v.get("metric", "") for v in self.violations):
recommendations.append("Consider code splitting or lazy loading to reduce bundle size")
if any("paint" in v.get("metric", "") for v in self.violations):
recommendations.append("Optimize critical rendering path and defer non-critical resources")
if any("api_response_time" in v.get("metric", "") for v in self.violations):
recommendations.append("Review API performance, caching strategies, and database queries")
if not recommendations:
recommendations.append("Performance metrics are within budget")
return recommendations
def generate_summary(self) -> str:
"""Generate a text summary of the validation."""
lines = []
lines.append("=" * 60)
lines.append("Performance Budget Validation Report")
lines.append("=" * 60)
if len(self.violations) == 0 and len(self.warnings) == 0:
lines.append("✓ All performance metrics within budget")
else:
if len(self.violations) > 0:
lines.append(f"\n✗ {len(self.violations)} Budget Violations:")
for violation in self.violations:
metric = violation.get("metric", "unknown")
actual = violation.get("actual", "N/A")
budget = violation.get("budget", "N/A")
unit = violation.get("unit", "")
pct = violation.get("percentage_over", 0)
lines.append(f" - {metric}: {actual}{unit} (budget: {budget}{unit}, {pct}% over)")
if len(self.warnings) > 0:
lines.append(f"\n⚠ {len(self.warnings)} Warnings:")
for warning in self.warnings:
lines.append(f" - {warning.get('message', str(warning))}")
lines.append(f"\n✓ {len(self.passed_checks)} Metrics Passed")
lines.append("=" * 60)
return "\n".join(lines)
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Validate performance metrics against defined budgets",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
validate_budget.py --budget budget.json --metrics metrics.json
validate_budget.py --budget budget.json --metrics metrics.json --output report.json
validate_budget.py --budget budget.json --metrics metrics.json --fail-on-violation
validate_budget.py --config config.json
""",
)
parser.add_argument("-b", "--budget", type=str, help="Path to budget configuration JSON file")
parser.add_argument("-m", "--metrics", type=str, help="Path to metrics JSON file")
parser.add_argument("-c", "--config", type=str, help="Path to combined config file (budget + metrics)")
parser.add_argument("-o", "--output", type=str, help="Output file for JSON report")
parser.add_argument("-s", "--summary", action="store_true", help="Print text summary to console")
parser.add_argument(
"-f", "--fail-on-violation", action="store_true", help="Exit with error code 1 if violations found"
)
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
args = parser.parse_args()
try:
# Load configuration
if args.config:
config_path = Path(args.config)
if not config_path.exists():
print(f"Error: Config file not found: {args.config}", file=sys.stderr)
return 1
with open(config_path, "r") as f:
config = json.load(f)
budget = config.get("budget", config)
metrics = config.get("metrics", {})
else:
if not args.budget or not args.metrics:
parser.error("Either --config or both --budget and --metrics are required")
budget_path = Path(args.budget)
metrics_path = Path(args.metrics)
if not budget_path.exists():
print(f"Error: Budget file not found: {args.budget}", file=sys.stderr)
return 1
if not metrics_path.exists():
print(f"Error: Metrics file not found: {args.metrics}", file=sys.stderr)
return 1
with open(budget_path, "r") as f:
budget = json.load(f)
with open(metrics_path, "r") as f:
metrics = json.load(f)
# Validate metrics
validator = PerformanceBudgetValidator()
report = validator.validate_metrics(budget, metrics)
# Output report
if args.output:
output_path = Path(args.output)
with open(output_path, "w") as f:
json.dump(report, f, indent=2)
print(f"Report written to: {args.output}")
# Print summary if requested
if args.summary or not args.output:
print(validator.generate_summary())
# JSON output if no output file
if not args.output and not args.summary:
print(json.dumps(report, indent=2))
# Return appropriate exit code
if not report.get("valid") and args.fail_on_violation:
return 1
return 0
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())