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)