feat: add unified code quality dashboard with multiple validators
- Add validator_type field to scans and violations (architecture, security, performance) - Create security validator with SEC-xxx rules - Create performance validator with PERF-xxx rules - Add base validator class for shared functionality - Add validate_all.py script to run all validators - Update code quality service with validator type filtering - Add validator type tabs to dashboard UI - Add validator type filter to violations list - Update stats response with per-validator breakdown - Add security and performance rules documentation - Add chat-bubble icons to icon library 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
66
.performance-rules/_main.yaml
Normal file
66
.performance-rules/_main.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
# Performance Rules Configuration
|
||||
# ================================
|
||||
# Performance-focused validation rules for the codebase.
|
||||
# Run with: python scripts/validate_performance.py
|
||||
|
||||
version: "1.0"
|
||||
project: "letzshop-product-import"
|
||||
|
||||
description: |
|
||||
Performance validation rules to detect inefficient patterns and ensure
|
||||
optimal performance across the application.
|
||||
|
||||
principles:
|
||||
- name: "Minimize Database Queries"
|
||||
description: "Reduce N+1 queries and optimize data fetching"
|
||||
- name: "Efficient Data Structures"
|
||||
description: "Use appropriate data structures for the task"
|
||||
- name: "Lazy Loading"
|
||||
description: "Load data only when needed"
|
||||
- name: "Caching Strategy"
|
||||
description: "Cache expensive computations and frequent queries"
|
||||
- name: "Async I/O"
|
||||
description: "Use async for I/O-bound operations"
|
||||
|
||||
includes:
|
||||
- database.yaml
|
||||
- caching.yaml
|
||||
- api.yaml
|
||||
- async.yaml
|
||||
- memory.yaml
|
||||
- frontend.yaml
|
||||
|
||||
severity_levels:
|
||||
error:
|
||||
description: "Critical performance issue that must be fixed"
|
||||
exit_code: 1
|
||||
warning:
|
||||
description: "Performance concern that should be addressed"
|
||||
exit_code: 0
|
||||
info:
|
||||
description: "Performance optimization recommendation"
|
||||
exit_code: 0
|
||||
|
||||
ignore:
|
||||
files:
|
||||
- "**/test_*.py"
|
||||
- "**/tests/**"
|
||||
- "**/*_test.py"
|
||||
- "**/conftest.py"
|
||||
- "**/migrations/**"
|
||||
- "**/.venv/**"
|
||||
- "**/venv/**"
|
||||
- "**/node_modules/**"
|
||||
- "**/site/**"
|
||||
- "**/scripts/**"
|
||||
- "**/__pycache__/**"
|
||||
- "**/*.pyc"
|
||||
patterns:
|
||||
# Allow patterns in test files
|
||||
- file: "**/tests/**"
|
||||
pattern: ".*"
|
||||
reason: "Test files may have different performance requirements"
|
||||
# Allow patterns in scripts
|
||||
- file: "**/scripts/**"
|
||||
pattern: "\\.all\\(\\)"
|
||||
reason: "Scripts may need to process all records"
|
||||
135
.performance-rules/api.yaml
Normal file
135
.performance-rules/api.yaml
Normal file
@@ -0,0 +1,135 @@
|
||||
# API Performance Rules
|
||||
# =====================
|
||||
|
||||
api_rules:
|
||||
- id: "PERF-026"
|
||||
name: "Pagination required for list endpoints"
|
||||
severity: error
|
||||
description: |
|
||||
All list endpoints must support pagination.
|
||||
Unbounded lists cause performance issues:
|
||||
- Memory exhaustion
|
||||
- Slow response times
|
||||
- Database locks
|
||||
file_pattern: "**/api/**/*.py"
|
||||
anti_patterns:
|
||||
- '@router\\.get\\([^)]*\\)\\s*\\n(?:(?!limit|skip|offset|page).)*def\\s+\\w+.*:\\s*\\n(?:(?!limit|skip|offset|page).)*return.*\\.all\\(\\)'
|
||||
required_patterns:
|
||||
- "limit|skip|offset|page"
|
||||
example_bad: |
|
||||
@router.get("/products")
|
||||
def list_products(db: Session):
|
||||
return db.query(Product).all()
|
||||
example_good: |
|
||||
@router.get("/products")
|
||||
def list_products(
|
||||
skip: int = 0,
|
||||
limit: int = Query(default=20, le=100),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
return db.query(Product).offset(skip).limit(limit).all()
|
||||
|
||||
- id: "PERF-027"
|
||||
name: "Reasonable default page sizes"
|
||||
severity: warning
|
||||
description: |
|
||||
Default page sizes should be reasonable:
|
||||
- Default: 20-50 items
|
||||
- Maximum: 100-200 items
|
||||
|
||||
Very large page sizes negate pagination benefits.
|
||||
file_pattern: "**/api/**/*.py"
|
||||
anti_patterns:
|
||||
- 'limit.*=.*Query\\([^)]*default\\s*=\\s*[5-9]\\d{2,}'
|
||||
- 'limit.*=.*Query\\([^)]*le\\s*=\\s*[1-9]\\d{3,}'
|
||||
example_bad: |
|
||||
limit: int = Query(default=500, le=10000)
|
||||
example_good: |
|
||||
limit: int = Query(default=20, ge=1, le=100)
|
||||
|
||||
- id: "PERF-028"
|
||||
name: "Response compression"
|
||||
severity: info
|
||||
description: |
|
||||
Enable response compression for large responses:
|
||||
- GZip or Brotli
|
||||
- Significant bandwidth savings
|
||||
- Faster load times
|
||||
file_pattern: "**/main.py|**/app.py"
|
||||
suggested_patterns:
|
||||
- "GZipMiddleware|BrotliMiddleware|compress"
|
||||
|
||||
- id: "PERF-029"
|
||||
name: "Efficient serialization"
|
||||
severity: info
|
||||
description: |
|
||||
Use Pydantic's response_model for efficient serialization.
|
||||
Avoid manual dict conversion.
|
||||
file_pattern: "**/api/**/*.py"
|
||||
anti_patterns:
|
||||
- 'return\\s+\\{[^}]*for\\s+\\w+\\s+in'
|
||||
- 'return\\s+\\[\\{.*for.*in.*\\]'
|
||||
suggested_patterns:
|
||||
- "response_model"
|
||||
|
||||
- id: "PERF-030"
|
||||
name: "Avoid redundant queries in response"
|
||||
severity: warning
|
||||
description: |
|
||||
Don't trigger lazy-loaded relationships during serialization.
|
||||
Use eager loading or carefully control serialization.
|
||||
file_pattern: "**/api/**/*.py"
|
||||
|
||||
- id: "PERF-031"
|
||||
name: "Streaming for large responses"
|
||||
severity: info
|
||||
description: |
|
||||
Use streaming responses for large data:
|
||||
- File downloads
|
||||
- Large exports (CSV, JSON)
|
||||
- Real-time data feeds
|
||||
file_pattern: "**/api/**/*.py"
|
||||
suggested_patterns:
|
||||
- "StreamingResponse|yield|generator"
|
||||
|
||||
- id: "PERF-032"
|
||||
name: "Conditional requests support"
|
||||
severity: info
|
||||
description: |
|
||||
Support conditional requests to reduce bandwidth:
|
||||
- ETag validation
|
||||
- If-None-Match handling
|
||||
- 304 Not Modified responses
|
||||
file_pattern: "**/api/**/*.py"
|
||||
suggested_patterns:
|
||||
- "ETag|If-None-Match|304"
|
||||
|
||||
- id: "PERF-033"
|
||||
name: "Field selection support"
|
||||
severity: info
|
||||
description: |
|
||||
Allow clients to request only needed fields.
|
||||
Reduces response size and serialization cost.
|
||||
file_pattern: "**/api/**/*.py"
|
||||
suggested_patterns:
|
||||
- "fields|include|exclude|sparse"
|
||||
|
||||
- id: "PERF-034"
|
||||
name: "Avoid deep nesting in responses"
|
||||
severity: info
|
||||
description: |
|
||||
Deeply nested responses are slow to serialize.
|
||||
Consider flattening or using links instead.
|
||||
file_pattern: "**/api/**/*.py"
|
||||
|
||||
- id: "PERF-035"
|
||||
name: "Endpoint response time monitoring"
|
||||
severity: info
|
||||
description: |
|
||||
Monitor API response times:
|
||||
- Set SLA targets
|
||||
- Alert on degradation
|
||||
- Track percentiles (p50, p95, p99)
|
||||
file_pattern: "**/main.py|**/middleware*.py"
|
||||
suggested_patterns:
|
||||
- "prometheus|metrics|timing|latency"
|
||||
142
.performance-rules/async.yaml
Normal file
142
.performance-rules/async.yaml
Normal file
@@ -0,0 +1,142 @@
|
||||
# Async & Concurrency Performance Rules
|
||||
# =====================================
|
||||
|
||||
async_rules:
|
||||
- id: "PERF-036"
|
||||
name: "Async for I/O operations"
|
||||
severity: info
|
||||
description: |
|
||||
Use async for I/O-bound operations:
|
||||
- Database queries (with async driver)
|
||||
- External API calls
|
||||
- File operations
|
||||
- Network requests
|
||||
file_pattern: "**/api/**/*.py|**/service*.py"
|
||||
suggested_patterns:
|
||||
- "async def|await|asyncio"
|
||||
|
||||
- id: "PERF-037"
|
||||
name: "Parallel independent operations"
|
||||
severity: warning
|
||||
description: |
|
||||
Multiple independent async operations should run in parallel.
|
||||
Use asyncio.gather() instead of sequential awaits.
|
||||
file_pattern: "**/*.py"
|
||||
anti_patterns:
|
||||
- 'await\\s+\\w+\\([^)]*\\)\\s*\\n\\s*await\\s+\\w+\\([^)]*\\)\\s*\\n\\s*await\\s+\\w+\\('
|
||||
suggested_patterns:
|
||||
- "asyncio\\.gather|asyncio\\.create_task"
|
||||
example_bad: |
|
||||
user = await get_user(user_id)
|
||||
orders = await get_orders(user_id)
|
||||
preferences = await get_preferences(user_id)
|
||||
example_good: |
|
||||
user, orders, preferences = await asyncio.gather(
|
||||
get_user(user_id),
|
||||
get_orders(user_id),
|
||||
get_preferences(user_id)
|
||||
)
|
||||
|
||||
- id: "PERF-038"
|
||||
name: "Background tasks for slow operations"
|
||||
severity: warning
|
||||
description: |
|
||||
Operations taking > 500ms should run in background:
|
||||
- Email sending
|
||||
- Report generation
|
||||
- External API syncs
|
||||
- File processing
|
||||
file_pattern: "**/api/**/*.py"
|
||||
suggested_patterns:
|
||||
- "BackgroundTasks|background_task|celery|rq|dramatiq"
|
||||
|
||||
- id: "PERF-039"
|
||||
name: "Connection pooling for HTTP clients"
|
||||
severity: warning
|
||||
description: |
|
||||
HTTP clients should reuse connections.
|
||||
Create client once, not per request.
|
||||
file_pattern: "**/*client*.py|**/service*.py"
|
||||
anti_patterns:
|
||||
- 'def\\s+\\w+\\([^)]*\\):\\s*\\n[^}]*requests\\.get\\('
|
||||
- 'httpx\\.get\\('
|
||||
- 'aiohttp\\.request\\('
|
||||
suggested_patterns:
|
||||
- "httpx\\.AsyncClient|aiohttp\\.ClientSession|requests\\.Session"
|
||||
example_bad: |
|
||||
def fetch_data(url):
|
||||
response = requests.get(url) # New connection each time
|
||||
example_good: |
|
||||
# Use a session (connection pool)
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url)
|
||||
|
||||
- id: "PERF-040"
|
||||
name: "Timeout configuration"
|
||||
severity: error
|
||||
description: |
|
||||
All external calls must have timeouts.
|
||||
Without timeouts, requests can hang indefinitely.
|
||||
file_pattern: "**/*client*.py|**/service*.py"
|
||||
context_patterns:
|
||||
- "requests|httpx|aiohttp|urllib"
|
||||
required_patterns:
|
||||
- "timeout"
|
||||
example_bad: |
|
||||
response = requests.get(url)
|
||||
example_good: |
|
||||
response = requests.get(url, timeout=30)
|
||||
|
||||
- id: "PERF-041"
|
||||
name: "Connection pool limits"
|
||||
severity: info
|
||||
description: |
|
||||
Configure appropriate connection pool limits:
|
||||
- max_connections: Total connections
|
||||
- max_keepalive_connections: Idle connections
|
||||
- keepalive_expiry: Time before closing idle
|
||||
file_pattern: "**/*client*.py"
|
||||
suggested_patterns:
|
||||
- "max_connections|pool_connections|pool_maxsize"
|
||||
|
||||
- id: "PERF-042"
|
||||
name: "Retry with backoff"
|
||||
severity: info
|
||||
description: |
|
||||
External calls should retry with exponential backoff.
|
||||
Prevents cascade failures and respects rate limits.
|
||||
file_pattern: "**/*client*.py|**/service*.py"
|
||||
suggested_patterns:
|
||||
- "retry|backoff|tenacity|Retry"
|
||||
|
||||
- id: "PERF-043"
|
||||
name: "Circuit breaker pattern"
|
||||
severity: info
|
||||
description: |
|
||||
Use circuit breaker for unreliable external services.
|
||||
Prevents repeated failures from slowing down the system.
|
||||
file_pattern: "**/*client*.py"
|
||||
suggested_patterns:
|
||||
- "circuit_breaker|CircuitBreaker|pybreaker"
|
||||
|
||||
- id: "PERF-044"
|
||||
name: "Task queues for heavy processing"
|
||||
severity: info
|
||||
description: |
|
||||
Heavy processing should use task queues:
|
||||
- Celery
|
||||
- RQ (Redis Queue)
|
||||
- Dramatiq
|
||||
- Huey
|
||||
file_pattern: "**/tasks/**/*.py"
|
||||
suggested_patterns:
|
||||
- "celery|rq|dramatiq|huey|@task"
|
||||
|
||||
- id: "PERF-045"
|
||||
name: "Worker pool sizing"
|
||||
severity: info
|
||||
description: |
|
||||
Size worker pools appropriately:
|
||||
- CPU-bound: Number of cores
|
||||
- I/O-bound: Higher multiplier (2-4x cores)
|
||||
- Memory-constrained: Based on available RAM
|
||||
125
.performance-rules/caching.yaml
Normal file
125
.performance-rules/caching.yaml
Normal file
@@ -0,0 +1,125 @@
|
||||
# Caching Performance Rules
|
||||
# =========================
|
||||
|
||||
caching_rules:
|
||||
- id: "PERF-016"
|
||||
name: "Cache expensive computations"
|
||||
severity: info
|
||||
description: |
|
||||
Computationally expensive operations should be cached:
|
||||
- Complex aggregations
|
||||
- External API results
|
||||
- Template rendering
|
||||
- Data transformations
|
||||
file_pattern: "**/service*.py"
|
||||
suggested_patterns:
|
||||
- "@cache|@lru_cache|@cached|redis|memcache"
|
||||
|
||||
- id: "PERF-017"
|
||||
name: "Cache key includes tenant context"
|
||||
severity: warning
|
||||
description: |
|
||||
Multi-tenant cache keys must include vendor_id.
|
||||
Otherwise, cached data may leak between tenants.
|
||||
file_pattern: "**/*cache*.py|**/service*.py"
|
||||
context_patterns:
|
||||
- "cache|@cached|redis"
|
||||
required_patterns:
|
||||
- "vendor_id|tenant"
|
||||
example_bad: |
|
||||
@cache.memoize()
|
||||
def get_products():
|
||||
return db.query(Product).all()
|
||||
example_good: |
|
||||
@cache.memoize()
|
||||
def get_products(vendor_id: int):
|
||||
return db.query(Product).filter_by(vendor_id=vendor_id).all()
|
||||
|
||||
- id: "PERF-018"
|
||||
name: "Cache TTL configuration"
|
||||
severity: info
|
||||
description: |
|
||||
Cache entries should have appropriate TTL:
|
||||
- Short TTL (1-5 min): Frequently changing data
|
||||
- Medium TTL (5-60 min): Semi-static data
|
||||
- Long TTL (1+ hour): Reference data
|
||||
file_pattern: "**/*cache*.py"
|
||||
suggested_patterns:
|
||||
- "ttl|expire|timeout"
|
||||
|
||||
- id: "PERF-019"
|
||||
name: "Cache invalidation strategy"
|
||||
severity: warning
|
||||
description: |
|
||||
Define cache invalidation strategy:
|
||||
- Time-based (TTL)
|
||||
- Event-based (on data change)
|
||||
- Manual (admin action)
|
||||
|
||||
Without invalidation, stale data may be served.
|
||||
file_pattern: "**/*cache*.py|**/service*.py"
|
||||
suggested_patterns:
|
||||
- "invalidate|delete|clear|purge"
|
||||
|
||||
- id: "PERF-020"
|
||||
name: "Response caching headers"
|
||||
severity: info
|
||||
description: |
|
||||
API responses can use HTTP caching headers:
|
||||
- Cache-Control for browser/CDN caching
|
||||
- ETag for conditional requests
|
||||
- Last-Modified for validation
|
||||
file_pattern: "**/api/**/*.py"
|
||||
suggested_patterns:
|
||||
- "Cache-Control|ETag|Last-Modified"
|
||||
|
||||
- id: "PERF-021"
|
||||
name: "Query result caching"
|
||||
severity: info
|
||||
description: |
|
||||
Frequently accessed, rarely changed data should be cached:
|
||||
- User preferences
|
||||
- Configuration settings
|
||||
- Static reference data
|
||||
file_pattern: "**/service*.py"
|
||||
|
||||
- id: "PERF-022"
|
||||
name: "Session-level caching"
|
||||
severity: info
|
||||
description: |
|
||||
Use SQLAlchemy's identity map for request-scoped caching.
|
||||
Avoid re-fetching the same entity within a request.
|
||||
file_pattern: "**/service*.py"
|
||||
|
||||
- id: "PERF-023"
|
||||
name: "Distributed cache for scalability"
|
||||
severity: info
|
||||
description: |
|
||||
For multi-instance deployments, use distributed cache:
|
||||
- Redis
|
||||
- Memcached
|
||||
- Database-backed cache
|
||||
|
||||
Local caches don't work across instances.
|
||||
file_pattern: "**/config*.py"
|
||||
suggested_patterns:
|
||||
- "redis|memcache|CACHE_TYPE"
|
||||
|
||||
- id: "PERF-024"
|
||||
name: "Cache warming strategy"
|
||||
severity: info
|
||||
description: |
|
||||
Pre-warm cache for predictable high-traffic patterns:
|
||||
- On application startup
|
||||
- Before marketing campaigns
|
||||
- After cache flush
|
||||
|
||||
- id: "PERF-025"
|
||||
name: "Monitor cache hit rates"
|
||||
severity: info
|
||||
description: |
|
||||
Track cache performance:
|
||||
- Hit rate (should be > 80%)
|
||||
- Miss penalty (time saved)
|
||||
- Memory usage
|
||||
- Eviction rate
|
||||
223
.performance-rules/database.yaml
Normal file
223
.performance-rules/database.yaml
Normal file
@@ -0,0 +1,223 @@
|
||||
# Database Performance Rules
|
||||
# ==========================
|
||||
|
||||
database_rules:
|
||||
- id: "PERF-001"
|
||||
name: "N+1 query detection"
|
||||
severity: warning
|
||||
description: |
|
||||
Accessing relationships in loops causes N+1 queries.
|
||||
For each item in a list, a separate query is executed.
|
||||
|
||||
Solutions:
|
||||
- joinedload(): Eager load with JOIN
|
||||
- selectinload(): Eager load with IN clause
|
||||
- subqueryload(): Eager load with subquery
|
||||
file_pattern: "**/service*.py|**/api/**/*.py"
|
||||
anti_patterns:
|
||||
- 'for\s+\w+\s+in\s+\w+\.all\(\):\s*\n[^}]*\.\w+\.\w+'
|
||||
suggested_patterns:
|
||||
- "joinedload|selectinload|subqueryload"
|
||||
example_bad: |
|
||||
orders = db.query(Order).all()
|
||||
for order in orders:
|
||||
customer_name = order.customer.name # N+1 query!
|
||||
example_good: |
|
||||
orders = db.query(Order).options(
|
||||
joinedload(Order.customer)
|
||||
).all()
|
||||
for order in orders:
|
||||
customer_name = order.customer.name # Already loaded
|
||||
|
||||
- id: "PERF-002"
|
||||
name: "Eager loading for known relationships"
|
||||
severity: info
|
||||
description: |
|
||||
When you always need related data, use eager loading
|
||||
to reduce the number of database round trips.
|
||||
file_pattern: "**/service*.py"
|
||||
suggested_patterns:
|
||||
- "joinedload|selectinload|subqueryload"
|
||||
|
||||
- id: "PERF-003"
|
||||
name: "Query result limiting"
|
||||
severity: warning
|
||||
description: |
|
||||
All list queries should have pagination or limits.
|
||||
Unbounded queries can cause memory issues and slow responses.
|
||||
file_pattern: "**/service*.py|**/api/**/*.py"
|
||||
anti_patterns:
|
||||
- '\\.all\\(\\)(?![^\\n]*limit|[^\\n]*\\[:)'
|
||||
exclude_patterns:
|
||||
- "# noqa: PERF-003"
|
||||
- "# bounded query"
|
||||
- ".filter("
|
||||
suggested_patterns:
|
||||
- "limit|offset|skip|paginate"
|
||||
example_bad: |
|
||||
all_products = db.query(Product).all()
|
||||
example_good: |
|
||||
products = db.query(Product).limit(100).all()
|
||||
# Or with pagination
|
||||
products = db.query(Product).offset(skip).limit(limit).all()
|
||||
|
||||
- id: "PERF-004"
|
||||
name: "Index usage for filtered columns"
|
||||
severity: info
|
||||
description: |
|
||||
Columns frequently used in WHERE clauses should have indexes:
|
||||
- Foreign keys (vendor_id, customer_id)
|
||||
- Status fields
|
||||
- Date fields used for filtering
|
||||
- Boolean flags used for filtering
|
||||
file_pattern: "**/models/database/*.py"
|
||||
suggested_patterns:
|
||||
- "index=True|Index\\("
|
||||
|
||||
- id: "PERF-005"
|
||||
name: "Select only needed columns"
|
||||
severity: info
|
||||
description: |
|
||||
For large tables, select only the columns you need.
|
||||
Use .with_entities() or load_only() to reduce data transfer.
|
||||
file_pattern: "**/service*.py"
|
||||
suggested_patterns:
|
||||
- "with_entities|load_only|defer"
|
||||
example_good: |
|
||||
# Only load id and name columns
|
||||
products = db.query(Product).options(
|
||||
load_only(Product.id, Product.name)
|
||||
).all()
|
||||
|
||||
- id: "PERF-006"
|
||||
name: "Bulk operations for multiple records"
|
||||
severity: warning
|
||||
description: |
|
||||
Use bulk operations instead of individual operations in loops:
|
||||
- bulk_insert_mappings() for inserts
|
||||
- bulk_update_mappings() for updates
|
||||
- add_all() for ORM inserts
|
||||
file_pattern: "**/service*.py"
|
||||
anti_patterns:
|
||||
- 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*db\\.add\\s*\\('
|
||||
- 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*\\.save\\s*\\('
|
||||
suggested_patterns:
|
||||
- "bulk_insert_mappings|bulk_update_mappings|add_all"
|
||||
example_bad: |
|
||||
for item in items:
|
||||
product = Product(**item)
|
||||
db.add(product)
|
||||
example_good: |
|
||||
products = [Product(**item) for item in items]
|
||||
db.add_all(products)
|
||||
|
||||
- id: "PERF-007"
|
||||
name: "Connection pool configuration"
|
||||
severity: info
|
||||
description: |
|
||||
Configure database connection pool for optimal performance:
|
||||
- pool_size: Number of persistent connections
|
||||
- max_overflow: Additional connections allowed
|
||||
- pool_pre_ping: Check connection health
|
||||
- pool_recycle: Recycle connections periodically
|
||||
file_pattern: "**/database.py|**/config*.py"
|
||||
suggested_patterns:
|
||||
- "pool_size|pool_pre_ping|pool_recycle|max_overflow"
|
||||
|
||||
- id: "PERF-008"
|
||||
name: "Use EXISTS for existence checks"
|
||||
severity: info
|
||||
description: |
|
||||
Use EXISTS or .first() is not None instead of count() > 0.
|
||||
EXISTS stops at first match, count() scans all matches.
|
||||
file_pattern: "**/service*.py"
|
||||
anti_patterns:
|
||||
- '\\.count\\(\\)\\s*>\\s*0'
|
||||
- '\\.count\\(\\)\\s*>=\\s*1'
|
||||
- '\\.count\\(\\)\\s*!=\\s*0'
|
||||
suggested_patterns:
|
||||
- "exists\\(\\)|scalar\\(exists"
|
||||
example_bad: |
|
||||
if db.query(Order).filter_by(customer_id=id).count() > 0:
|
||||
example_good: |
|
||||
exists_query = db.query(exists().where(Order.customer_id == id))
|
||||
if db.scalar(exists_query):
|
||||
|
||||
- id: "PERF-009"
|
||||
name: "Batch updates instead of loops"
|
||||
severity: warning
|
||||
description: |
|
||||
Use .update() with filters instead of updating in a loop.
|
||||
One UPDATE statement is faster than N individual updates.
|
||||
file_pattern: "**/service*.py"
|
||||
anti_patterns:
|
||||
- 'for\\s+\\w+\\s+in\\s+\\w+:\\s*\\n[^}]*\\w+\\.\\w+\\s*='
|
||||
suggested_patterns:
|
||||
- "\\.update\\(\\{"
|
||||
example_bad: |
|
||||
for product in products:
|
||||
product.is_active = False
|
||||
db.add(product)
|
||||
example_good: |
|
||||
db.query(Product).filter(
|
||||
Product.id.in_(product_ids)
|
||||
).update({"is_active": False}, synchronize_session=False)
|
||||
|
||||
- id: "PERF-010"
|
||||
name: "Avoid SELECT * patterns"
|
||||
severity: info
|
||||
description: |
|
||||
When you only need specific columns, don't load entire rows.
|
||||
This reduces memory usage and network transfer.
|
||||
file_pattern: "**/service*.py"
|
||||
|
||||
- id: "PERF-011"
|
||||
name: "Use appropriate join strategies"
|
||||
severity: info
|
||||
description: |
|
||||
Choose the right join strategy:
|
||||
- joinedload: Few related items, always needed
|
||||
- selectinload: Many related items, always needed
|
||||
- subqueryload: Complex queries, many related items
|
||||
- lazyload: Rarely accessed relationships
|
||||
file_pattern: "**/service*.py"
|
||||
|
||||
- id: "PERF-012"
|
||||
name: "Transaction scope optimization"
|
||||
severity: warning
|
||||
description: |
|
||||
Keep transactions short and focused:
|
||||
- Don't hold transactions during I/O
|
||||
- Commit after bulk operations
|
||||
- Use read-only transactions when possible
|
||||
file_pattern: "**/service*.py"
|
||||
|
||||
- id: "PERF-013"
|
||||
name: "Query result caching"
|
||||
severity: info
|
||||
description: |
|
||||
Consider caching for:
|
||||
- Frequently accessed, rarely changed data
|
||||
- Configuration tables
|
||||
- Reference data (categories, statuses)
|
||||
file_pattern: "**/service*.py"
|
||||
suggested_patterns:
|
||||
- "@cache|@lru_cache|redis|memcache"
|
||||
|
||||
- id: "PERF-014"
|
||||
name: "Composite indexes for multi-column filters"
|
||||
severity: info
|
||||
description: |
|
||||
Queries filtering on multiple columns benefit from composite indexes.
|
||||
Order columns by selectivity (most selective first).
|
||||
file_pattern: "**/models/database/*.py"
|
||||
suggested_patterns:
|
||||
- "Index\\([^)]*,[^)]*\\)"
|
||||
|
||||
- id: "PERF-015"
|
||||
name: "Avoid correlated subqueries"
|
||||
severity: info
|
||||
description: |
|
||||
Correlated subqueries execute once per row.
|
||||
Use JOINs or window functions instead when possible.
|
||||
file_pattern: "**/service*.py"
|
||||
177
.performance-rules/frontend.yaml
Normal file
177
.performance-rules/frontend.yaml
Normal file
@@ -0,0 +1,177 @@
|
||||
# Frontend Performance Rules
|
||||
# ==========================
|
||||
|
||||
frontend_rules:
|
||||
- id: "PERF-056"
|
||||
name: "Debounce search inputs"
|
||||
severity: warning
|
||||
description: |
|
||||
Search inputs should debounce API calls.
|
||||
Recommended: 300-500ms delay.
|
||||
|
||||
Prevents excessive API calls while user is typing.
|
||||
file_pattern: "**/*.js"
|
||||
context_patterns:
|
||||
- "search|filter|query"
|
||||
anti_patterns:
|
||||
- '@input=".*search.*fetch'
|
||||
- '@keyup=".*search.*fetch'
|
||||
suggested_patterns:
|
||||
- "debounce|setTimeout.*search|\\$watch.*search"
|
||||
example_bad: |
|
||||
<input @input="searchProducts($event.target.value)">
|
||||
example_good: |
|
||||
<input @input="debouncedSearch($event.target.value)">
|
||||
// With: debouncedSearch = debounce(searchProducts, 300)
|
||||
|
||||
- id: "PERF-057"
|
||||
name: "Lazy load off-screen content"
|
||||
severity: info
|
||||
description: |
|
||||
Defer loading of off-screen content:
|
||||
- Modals
|
||||
- Tabs (inactive)
|
||||
- Below-the-fold content
|
||||
- Images
|
||||
file_pattern: "**/*.html"
|
||||
suggested_patterns:
|
||||
- 'loading="lazy"|x-intersect|x-show|x-if'
|
||||
|
||||
- id: "PERF-058"
|
||||
name: "Image optimization"
|
||||
severity: warning
|
||||
description: |
|
||||
Images should be optimized:
|
||||
- Use appropriate formats (WebP, AVIF)
|
||||
- Serve responsive sizes
|
||||
- Lazy load off-screen images
|
||||
- Use CDN for static assets
|
||||
file_pattern: "**/*.html"
|
||||
required_patterns:
|
||||
- 'loading="lazy"|srcset|x-intersect'
|
||||
example_good: |
|
||||
<img src="image.webp" loading="lazy" alt="Product">
|
||||
|
||||
- id: "PERF-059"
|
||||
name: "Minimize Alpine.js watchers"
|
||||
severity: info
|
||||
description: |
|
||||
Excessive $watch calls impact performance.
|
||||
Use computed properties or event handlers instead.
|
||||
file_pattern: "**/*.js"
|
||||
anti_patterns:
|
||||
- '\\$watch\\([^)]+\\).*\\$watch\\([^)]+\\).*\\$watch\\('
|
||||
|
||||
- id: "PERF-060"
|
||||
name: "Virtual scrolling for long lists"
|
||||
severity: info
|
||||
description: |
|
||||
Lists with 100+ items should use virtual scrolling.
|
||||
Only render visible items in the viewport.
|
||||
file_pattern: "**/*.html|**/*.js"
|
||||
suggested_patterns:
|
||||
- "virtual-scroll|x-intersect|IntersectionObserver"
|
||||
|
||||
- id: "PERF-061"
|
||||
name: "Minimize bundle size"
|
||||
severity: info
|
||||
description: |
|
||||
Reduce JavaScript bundle size:
|
||||
- Import only needed modules
|
||||
- Use tree-shaking
|
||||
- Split code by route
|
||||
file_pattern: "**/*.js"
|
||||
|
||||
- id: "PERF-062"
|
||||
name: "Reasonable polling intervals"
|
||||
severity: warning
|
||||
description: |
|
||||
Polling should be >= 10 seconds for non-critical updates.
|
||||
Lower intervals waste bandwidth and server resources.
|
||||
file_pattern: "**/*.js"
|
||||
anti_patterns:
|
||||
- 'setInterval\\s*\\([^,]+,\\s*[1-9]\\d{0,3}\\s*\\)'
|
||||
exclude_patterns:
|
||||
- "# real-time required"
|
||||
example_bad: |
|
||||
setInterval(fetchUpdates, 1000); // Every second
|
||||
example_good: |
|
||||
setInterval(fetchUpdates, 30000); // Every 30 seconds
|
||||
|
||||
- id: "PERF-063"
|
||||
name: "CSS containment"
|
||||
severity: info
|
||||
description: |
|
||||
Use CSS containment for complex layouts.
|
||||
Limits rendering scope for better performance.
|
||||
file_pattern: "**/*.css|**/*.html"
|
||||
suggested_patterns:
|
||||
- "contain:|content-visibility"
|
||||
|
||||
- id: "PERF-064"
|
||||
name: "Avoid layout thrashing"
|
||||
severity: warning
|
||||
description: |
|
||||
Don't interleave DOM reads and writes.
|
||||
Batch reads first, then writes.
|
||||
file_pattern: "**/*.js"
|
||||
anti_patterns:
|
||||
- 'offsetHeight.*style\\.|style\\..*offsetHeight'
|
||||
|
||||
- id: "PERF-065"
|
||||
name: "Use CSS animations over JavaScript"
|
||||
severity: info
|
||||
description: |
|
||||
CSS animations are hardware-accelerated.
|
||||
Use CSS for simple animations, JS for complex ones.
|
||||
file_pattern: "**/*.js"
|
||||
suggested_patterns:
|
||||
- "transition|animation|transform"
|
||||
|
||||
- id: "PERF-066"
|
||||
name: "Preload critical resources"
|
||||
severity: info
|
||||
description: |
|
||||
Preload critical CSS, fonts, and above-the-fold images.
|
||||
Reduces perceived load time.
|
||||
file_pattern: "**/*.html"
|
||||
suggested_patterns:
|
||||
- 'rel="preload"|rel="prefetch"|rel="preconnect"'
|
||||
|
||||
- id: "PERF-067"
|
||||
name: "Defer non-critical JavaScript"
|
||||
severity: info
|
||||
description: |
|
||||
Non-critical JavaScript should be deferred.
|
||||
Allows page rendering to complete first.
|
||||
file_pattern: "**/*.html"
|
||||
suggested_patterns:
|
||||
- 'defer|async'
|
||||
|
||||
- id: "PERF-068"
|
||||
name: "Minimize DOM nodes"
|
||||
severity: info
|
||||
description: |
|
||||
Excessive DOM nodes slow rendering.
|
||||
Target: < 1500 nodes, depth < 32, children < 60
|
||||
file_pattern: "**/*.html"
|
||||
|
||||
- id: "PERF-069"
|
||||
name: "Efficient event handlers"
|
||||
severity: info
|
||||
description: |
|
||||
Use event delegation for repeated elements.
|
||||
Add listener to parent, not each child.
|
||||
file_pattern: "**/*.js"
|
||||
suggested_patterns:
|
||||
- "@click.delegate|event.target.closest"
|
||||
|
||||
- id: "PERF-070"
|
||||
name: "Cache DOM queries"
|
||||
severity: info
|
||||
description: |
|
||||
Store DOM element references instead of re-querying.
|
||||
Each querySelector has performance cost.
|
||||
file_pattern: "**/*.js"
|
||||
anti_patterns:
|
||||
- 'document\\.querySelector\\([^)]+\\).*document\\.querySelector\\('
|
||||
156
.performance-rules/memory.yaml
Normal file
156
.performance-rules/memory.yaml
Normal file
@@ -0,0 +1,156 @@
|
||||
# Memory Management Performance Rules
|
||||
# ====================================
|
||||
|
||||
memory_rules:
|
||||
- id: "PERF-046"
|
||||
name: "Generators for large datasets"
|
||||
severity: warning
|
||||
description: |
|
||||
Use generators/iterators for processing large datasets.
|
||||
Avoids loading everything into memory at once.
|
||||
file_pattern: "**/service*.py"
|
||||
anti_patterns:
|
||||
- '\\.all\\(\\).*for\\s+\\w+\\s+in'
|
||||
suggested_patterns:
|
||||
- "yield|yield_per|iter"
|
||||
example_bad: |
|
||||
products = db.query(Product).all() # Loads all into memory
|
||||
for product in products:
|
||||
process(product)
|
||||
example_good: |
|
||||
for product in db.query(Product).yield_per(100):
|
||||
process(product)
|
||||
|
||||
- id: "PERF-047"
|
||||
name: "Stream large file uploads"
|
||||
severity: warning
|
||||
description: |
|
||||
Large files should be streamed to disk, not held in memory.
|
||||
Use SpooledTemporaryFile or direct disk writing.
|
||||
file_pattern: "**/upload*.py|**/attachment*.py"
|
||||
suggested_patterns:
|
||||
- "SpooledTemporaryFile|chunk|stream"
|
||||
example_bad: |
|
||||
content = await file.read() # Entire file in memory
|
||||
with open(path, 'wb') as f:
|
||||
f.write(content)
|
||||
example_good: |
|
||||
with open(path, 'wb') as f:
|
||||
while chunk := await file.read(8192):
|
||||
f.write(chunk)
|
||||
|
||||
- id: "PERF-048"
|
||||
name: "Chunked processing for imports"
|
||||
severity: warning
|
||||
description: |
|
||||
Bulk imports should process in chunks:
|
||||
- Read in batches
|
||||
- Commit in batches
|
||||
- Report progress periodically
|
||||
file_pattern: "**/import*.py|**/csv*.py"
|
||||
required_patterns:
|
||||
- "chunk|batch|yield"
|
||||
example_bad: |
|
||||
rows = list(csv_reader) # All rows in memory
|
||||
for row in rows:
|
||||
process(row)
|
||||
example_good: |
|
||||
def process_in_chunks(reader, chunk_size=1000):
|
||||
chunk = []
|
||||
for row in reader:
|
||||
chunk.append(row)
|
||||
if len(chunk) >= chunk_size:
|
||||
yield chunk
|
||||
chunk = []
|
||||
if chunk:
|
||||
yield chunk
|
||||
|
||||
- id: "PERF-049"
|
||||
name: "Context managers for resources"
|
||||
severity: error
|
||||
description: |
|
||||
Use context managers for file operations.
|
||||
Ensures resources are properly released.
|
||||
file_pattern: "**/*.py"
|
||||
anti_patterns:
|
||||
- 'f\\s*=\\s*open\\s*\\([^)]+\\)(?!\\s*#.*context)'
|
||||
- '^(?!.*with).*open\\s*\\([^)]+\\)\\s*$'
|
||||
exclude_patterns:
|
||||
- "# noqa: PERF-049"
|
||||
- "with open"
|
||||
example_bad: |
|
||||
f = open('file.txt')
|
||||
content = f.read()
|
||||
f.close() # May not run if exception
|
||||
example_good: |
|
||||
with open('file.txt') as f:
|
||||
content = f.read()
|
||||
|
||||
- id: "PERF-050"
|
||||
name: "Limit in-memory collections"
|
||||
severity: info
|
||||
description: |
|
||||
Avoid building large lists in memory.
|
||||
Use generators, itertools, or database pagination.
|
||||
file_pattern: "**/service*.py"
|
||||
anti_patterns:
|
||||
- '\\[.*for.*in.*\\](?!.*[:10])'
|
||||
|
||||
- id: "PERF-051"
|
||||
name: "String concatenation efficiency"
|
||||
severity: info
|
||||
description: |
|
||||
For many string concatenations, use join() or StringIO.
|
||||
Repeated += creates many intermediate strings.
|
||||
file_pattern: "**/*.py"
|
||||
anti_patterns:
|
||||
- 'for.*:\\s*\\n[^}]*\\+='
|
||||
suggested_patterns:
|
||||
- "\\.join\\(|StringIO"
|
||||
example_bad: |
|
||||
result = ""
|
||||
for item in items:
|
||||
result += str(item)
|
||||
example_good: |
|
||||
result = "".join(str(item) for item in items)
|
||||
|
||||
- id: "PERF-052"
|
||||
name: "Efficient data structures"
|
||||
severity: info
|
||||
description: |
|
||||
Choose appropriate data structures:
|
||||
- set for membership testing
|
||||
- dict for key-value lookup
|
||||
- deque for queue operations
|
||||
- defaultdict for grouping
|
||||
file_pattern: "**/*.py"
|
||||
|
||||
- id: "PERF-053"
|
||||
name: "Object pooling for expensive objects"
|
||||
severity: info
|
||||
description: |
|
||||
Reuse expensive-to-create objects:
|
||||
- Database connections
|
||||
- HTTP clients
|
||||
- Template engines
|
||||
file_pattern: "**/*.py"
|
||||
|
||||
- id: "PERF-054"
|
||||
name: "Weak references for caches"
|
||||
severity: info
|
||||
description: |
|
||||
Use weak references for large object caches.
|
||||
Allows garbage collection when memory is needed.
|
||||
file_pattern: "**/*cache*.py"
|
||||
suggested_patterns:
|
||||
- "WeakValueDictionary|WeakKeyDictionary|weakref"
|
||||
|
||||
- id: "PERF-055"
|
||||
name: "Slots for frequently instantiated classes"
|
||||
severity: info
|
||||
description: |
|
||||
Use __slots__ for classes with many instances.
|
||||
Reduces memory footprint per instance.
|
||||
file_pattern: "**/models/**/*.py"
|
||||
suggested_patterns:
|
||||
- "__slots__"
|
||||
Reference in New Issue
Block a user