File size: 7,975 Bytes
81e8bb6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | #!/usr/bin/env python3
"""
Performance Analysis Script for Atom Backend
Analyzes the codebase for:
1. N+1 query problems
2. Missing database indexes
3. Caching opportunities
4. Slow endpoints
Usage:
python scripts/analyze_performance.py
"""
import os
import sys
import logging
from pathlib import Path
from typing import Dict, List, Set
# Add backend to path
sys.path.insert(0, str(Path(__file__).parent.parent))
logging.basicConfig(level=logging.INFO, format='%(message)s')
logger = logging.getLogger(__name__)
# Results storage
results = {
"n_plus_one_issues": [],
"missing_indexes": [],
"caching_opportunities": [],
"slow_endpoints": [],
}
def analyze_n_plus_one_queries():
"""Analyze code for potential N+1 query problems"""
logger.info("Analyzing for N+1 query patterns...")
issues_found = 0
# Check routes for loop queries
for file in Path("backend/api").glob("*.py"):
try:
content = file.read_text()
# Pattern: loops with db.query inside
if "for " in content and ".query(" in content:
lines = content.split('\n')
for i, line in enumerate(lines):
if "for " in line and ".query(" in content[i:i+5]: # Check nearby lines
results["n_plus_one_issues"].append({
"file": str(file),
"line": i + 1,
"pattern": "Loop with query",
"code": line.strip()
})
issues_found += 1
except Exception as e:
logger.warning(f"Could not analyze {file}: {e}")
logger.info(f"Found {issues_found} potential N+1 query issues")
def analyze_missing_indexes():
"""Analyze models for missing database indexes"""
logger.info("Analyzing for missing database indexes...")
# Read models file
models_file = Path(__file__).parent.parent / "backend" / "core" / "models.py"
if not models_file.exists():
logger.warning(f"models.py not found at {models_file}")
return
content = models_file.read_text()
lines = content.split('\n')
# Find ForeignKey columns without index=True
in_model = False
current_model = None
current_table = None
for i, line in enumerate(lines):
# Check if we're in a class definition
if line.strip().startswith("class "):
in_model = True
parts = line.strip().split()
if len(parts) >= 2:
current_model = parts[1].strip("(")
# Check for __tablename__
if '__tablename__' in line:
parts = line.split('=')
if len(parts) >= 2:
current_table = parts[1].strip().strip('"')
# Check for ForeignKey without index
if 'ForeignKey(' in line and 'index=' not in line:
# This is a foreign key without an index
results["missing_indexes"].append({
"model": current_model,
"table": current_table,
"line": i + 1,
"code": line.strip()
})
logger.info(f"Found {len(results['missing_indexes'])} foreign keys without indexes")
def analyze_caching_opportunities():
"""Analyze code for caching opportunities"""
logger.info("Analyzing for caching opportunities...")
backend_path = Path(__file__).parent.parent / "backend"
# Check for patterns
for file in (backend_path / "core").glob("*.py"):
try:
content = file.read_text()
if "AgentRegistry" in content and ".query(" in content:
results["caching_opportunities"].append({
"type": "agent_registry",
"file": str(file.relative_to(Path(__file__).parent.parent)),
"recommendation": "Cache AgentRegistry queries by agent_id",
"priority": "HIGH"
})
if "User" in content and ".query(" in content:
results["caching_opportunities"].append({
"type": "user_queries",
"file": str(file.relative_to(Path(__file__).parent.parent)),
"recommendation": "Cache User lookups by user_id",
"priority": "MEDIUM"
})
except Exception as e:
logger.warning(f"Could not analyze {file}: {e}")
logger.info(f"Found {len(results['caching_opportunities'])} caching opportunities")
def generate_recommendations():
"""Generate performance improvement recommendations"""
logger.info("\n" + "="*80)
logger.info("PERFORMANCE ANALYSIS RESULTS")
logger.info("="*80 + "\n")
# N+1 Queries
logger.info("π N+1 QUERY ISSUES")
if results["n_plus_one_issues"]:
logger.info(f"Found {len(results['n_plus_one_issues'])} potential issues:")
for issue in results["n_plus_one_issues"][:5]: # Show first 5
logger.info(f" - {issue['file']}:{issue['line']}: {issue['pattern']}")
if len(results["n_plus_one_issues"]) > 5:
logger.info(f" ... and {len(results['n_plus_one_issues']) - 5} more")
else:
logger.info(" No N+1 query issues found")
# Missing Indexes
logger.info("\nπ MISSING DATABASE INDEXES")
if results["missing_indexes"]:
logger.info(f"Found {len(results['missing_indexes'])} foreign keys without indexes:")
for issue in results["missing_indexes"][:5]:
logger.info(f" - {issue['model']}: {issue['code']}")
if len(results["missing_indexes"]) > 5:
logger.info(f" ... and {len(results['missing_indexes']) - 5} more")
logger.info("\n π‘ Recommendation: Add index=True to ForeignKey columns")
logger.info(" Example: user_id = Column(String(36), ForeignKey(\"users.id\"), index=True)")
else:
logger.info(" All foreign keys have indexes")
# Caching Opportunities
logger.info("\nπ CACHING OPPORTUNITIES")
if results["caching_opportunities"]:
logger.info(f"Found {len(results['caching_opportunities'])} caching opportunities:")
for opp in results["caching_opportunities"]:
logger.info(f" - [{opp['priority']}] {opp['type']}: {opp['recommendation']}")
logger.info(f" File: {opp.get('file', 'N/A')}")
logger.info("\n π‘ Recommended cache implementations:")
logger.info(" - Use Redis from core/governance_cache.py for hot data")
logger.info(" - Cache user sessions, agent registry, configuration")
logger.info(" - Set appropriate TTL (5-60 minutes for most data)")
else:
logger.info(" No obvious caching opportunities found")
logger.info("\n" + "="*80)
logger.info("RECOMMENDATIONS")
logger.info("="*80 + "\n")
logger.info("1. Add indexes to foreign keys (see detailed list above)")
logger.info("2. Implement caching for frequently accessed data")
logger.info("3. Use SQLAlchemy's joinedload() for eager loading relationships")
logger.info("4. Add pagination to endpoints that return lists")
logger.info("5. Use selectin/prefetch for optimizing relationship loading")
logger.info("\nSee docs/DATABASE_MODEL_BEST_PRACTICES.md for implementation patterns")
if __name__ == "__main__":
try:
analyze_n_plus_one_queries()
analyze_missing_indexes()
analyze_caching_opportunities()
generate_recommendations()
# Save results to file
import json
with open("scripts/performance_analysis_results.json", "w") as f:
json.dump(results, f, indent=2)
logger.info("\nβ
Analysis complete. Results saved to scripts/performance_analysis_results.json")
except Exception as e:
logger.error(f"Error during analysis: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
|