Skip to content

Understanding Reports

smattymatty edited this page Aug 25, 2025 · 1 revision

📊 Understanding Performance Reports

Learn to read, interpret, and act on Mercury's performance reports. Every metric tells a story about your application's health.

Report Philosophy

Mercury reports are designed to be:

  • Progressive: Simple overview → Detailed analysis
  • Actionable: Every issue includes a solution
  • Educational: Learn while you analyze
  • Adaptive: Different details per profile

Report Anatomy

The Overview Dashboard

Every Mercury report starts with a dashboard:

🎨 MERCURY PERFORMANCE DASHBOARD - UserAPITests
╭─────────────────────────────────────────────────────────────╮
│ 🚀 Overall Status: NEEDS IMPROVEMENT                          │
│ 🎓 Overall Grade: C (65/100)                                 │
│ 📊 Tests Executed: 12                                        │
│ ⏱️  Avg Response Time: 105.6ms                                │
│ 🧠 Avg Memory Usage: 45.7MB                                  │
│ 🗃️  Total Queries: 567 (47.25 avg)                          │
│ 🚨 N+1 Issues: 3/12 tests affected                           │
╰─────────────────────────────────────────────────────────────╯

Let's break down each element:

🚀 Overall Status

Status levels indicate urgency:

  • ✅ EXCELLENT: Everything is optimized
  • ✅ GOOD: Minor improvements possible
  • ⚠️ NEEDS IMPROVEMENT: Notable issues to address
  • ❌ CRITICAL: Serious performance problems

🎓 Performance Grades

Mercury uses an academic grading system:

Grade Score Meaning Action Required
S 100 Perfect None - celebrate! 🎉
A+ 95-99 Excellent Minor tweaks only
A 90-94 Very Good Polish for perfection
B 80-89 Good Some optimization needed
C 70-79 Acceptable Clear room for improvement
D 60-69 Poor Significant issues
F <60 Failing Critical problems

How Grades Are Calculated

# Grade components and weights
Response Time: 40%
Query Count: 30%  
Memory Usage: 20%
Cache Performance: 10%

# Example calculation
Response: 85/100 × 0.4 = 34
Queries: 70/100 × 0.3 = 21
Memory: 90/100 × 0.2 = 18
Cache: 60/100 × 0.1 = 6
─────────────────────────
Total: 79/100 = Grade C

Core Metrics Explained

⏱️ Response Time

What it measures: Time from request to response

Response Time: 156ms
├─ Database: 89ms (57%)
├─ Python Logic: 45ms (29%)
├─ Template Rendering: 12ms (8%)
└─ Other: 10ms (6%)

Thresholds:

  • Excellent: <50ms
  • Good: 50-100ms
  • Acceptable: 100-200ms
  • Slow: 200-500ms
  • Critical: >500ms

Common Causes of Slow Response:

  1. Unoptimized queries (most common)
  2. Complex computations in views
  3. External API calls without caching
  4. Large data serialization

🗃️ Database Queries

What it measures: Number of database queries per request

Query Analysis:
Total Queries: 89
├─ SELECT: 87
├─ INSERT: 1
├─ UPDATE: 1
└─ DELETE: 0

Duplicate Queries: 43 (48%)
N+1 Detected: Yes

Thresholds:

  • Excellent: 1-5 queries
  • Good: 6-10 queries
  • Acceptable: 11-20 queries
  • High: 21-50 queries
  • Critical: >50 queries

Reading Query Patterns:

# Good pattern (2 queries)
SELECT users...
SELECT profiles WHERE user_id IN (1,2,3...)

# Bad pattern (N+1, 101 queries)
SELECT users...
SELECT profile WHERE user_id = 1
SELECT profile WHERE user_id = 2
... (98 more)

🧠 Memory Usage

What it measures: RAM consumed during request

Memory Breakdown:
Total: 45.7MB
├─ Django ORM: 28.3MB (62%)
├─ Python Objects: 12.1MB (26%)
├─ Templates: 3.2MB (7%)
└─ Other: 2.1MB (5%)

Peak Usage: 67.8MB
Baseline: 23.4MB
Overhead: 44.4MB

Thresholds:

  • Excellent: <10MB overhead
  • Good: 10-25MB
  • Acceptable: 25-50MB
  • High: 50-100MB
  • Critical: >100MB

Common Memory Issues:

  1. Loading entire querysets into memory
  2. Large serialized responses
  3. Unbounded caches
  4. Memory leaks in views

💾 Cache Performance

What it measures: Cache hit/miss ratio

Cache Statistics:
Hit Rate: 73%
├─ Database Cache: 45/50 (90%)
├─ Template Cache: 12/20 (60%)
├─ API Cache: 8/15 (53%)
└─ Static Files: 15/15 (100%)

Missed Opportunities: 12
Suggested Cache Keys: 
- user_list_page_1
- product_details_5

Target Hit Rates:

  • Excellent: >90%
  • Good: 80-90%
  • Acceptable: 70-80%
  • Poor: 50-70%
  • Critical: <50%

Issue Detection and Explanations

N+1 Query Detection

Mercury identifies N+1 patterns:

🚨 N+1 QUERY PATTERN DETECTED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Location: UserSerializer.to_representation()
Pattern: Accessing user.profile in loop

Query Pattern Found:
1. SELECT * FROM users (1 query)
2. SELECT * FROM profiles WHERE user_id = ? (N queries)

Impact: 101 queries for 100 users

Solution:
queryset = User.objects.select_related('profile')

Expected Improvement: 
101 queries → 1 query (99% reduction)

Slow Query Identification

🐌 SLOW QUERY DETECTED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Query: SELECT * FROM orders WHERE status = 'pending' 
       AND created_at > '2024-01-01'
       
Execution Time: 342ms
Rows Examined: 45,678
Rows Returned: 234

Missing Index: orders.status, orders.created_at

Solution:
class Meta:
    indexes = [
        models.Index(fields=['status', 'created_at'])
    ]

Expected Improvement:
342ms → ~15ms (95% faster)

Memory Spike Detection

📈 MEMORY SPIKE DETECTED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Location: ProductListView.get_queryset()
Spike: 23MB → 89MB (+66MB)

Cause: Loading all products into memory
products = list(Product.objects.all())

Solution:
Use pagination or iterator():
products = Product.objects.iterator(chunk_size=100)

Expected Improvement:
66MB overhead → ~5MB (92% reduction)

Profile-Specific Reports

🎓 Student Profile Reports

Educational and detailed:

📚 PERFORMANCE REPORT - EDUCATIONAL MODE
════════════════════════════════════════════════════════

Test: test_user_list_api
────────────────────────────────────────────────────────
Grade: D (65/100)

What happened:
✗ Response took 234ms (should be under 100ms)
✗ Made 89 queries (should be under 10)
✓ Memory usage OK at 23MB

Why this matters:
• Slow pages frustrate users
• Many queries = database overload
• This will get worse with more data

How to fix (step by step):
1. Open views.py line 45
2. Find: users = User.objects.all()
3. Change to: users = User.objects.select_related('profile')
4. Run test again to verify

Learn more:
mercury-test --learn n1-queries

You're learning! Every fix makes you better! 💪

💼 Expert Profile Reports

Concise and technical:

PERFORMANCE METRICS
═══════════════════════════════════════════════════════
test_user_list      234ms  89q  23MB  D  N+1@L45
test_user_detail     45ms   3q  12MB  B  OK
test_user_create     67ms   5q  18MB  B  OK
test_user_search    456ms 234q  45MB  F  N+1@L78,L92

Critical: 2 N+1 patterns
Action: select_related on L45, prefetch_related on L78

🤖 Agent Profile Reports

Structured JSON:

{
  "summary": {
    "grade": "D",
    "score": 65,
    "status": "needs_improvement"
  },
  "metrics": {
    "response_time_ms": 234,
    "query_count": 89,
    "memory_mb": 23,
    "cache_hit_ratio": 0.73
  },
  "issues": [
    {
      "type": "n_plus_one",
      "severity": "high",
      "location": "views.py:45",
      "impact": {
        "queries_excess": 84,
        "time_excess_ms": 134
      },
      "fix": {
        "code": "User.objects.select_related('profile')",
        "auto_applicable": false,
        "requires_review": true
      }
    }
  ],
  "recommendations": [
    "Add database indexes",
    "Implement query caching",
    "Use pagination"
  ]
}

Understanding Severity Levels

🔴 Critical (Immediate Action)

  • Response time >1 second
  • 100+ queries per request
  • Memory usage >200MB
  • Complete cache misses

🟠 High (Fix Soon)

  • Response time 500ms-1s
  • 50-100 queries
  • Memory usage 100-200MB
  • Cache hit rate <50%

🟡 Medium (Plan to Fix)

  • Response time 200-500ms
  • 20-50 queries
  • Memory usage 50-100MB
  • Cache hit rate 50-70%

🟢 Low (Nice to Have)

  • Response time 100-200ms
  • 10-20 queries
  • Memory usage 25-50MB
  • Cache hit rate 70-80%

Comparative Reports

Baseline Comparison

📊 PERFORMANCE COMPARISON
════════════════════════════════════════════════════════
              Baseline → Current    Change   Status
────────────────────────────────────────────────────────
Response:     145ms → 89ms         -38.6%   ✅ Improved
Queries:      67 → 45              -32.8%   ✅ Improved
Memory:       34MB → 38MB          +11.7%   ⚠️ Increased
Cache:        65% → 82%            +26.1%   ✅ Improved

Overall: Performance IMPROVED by 27%

Biggest wins:
• UserListView: 78% faster
• ProductSearch: 65% fewer queries

New issues:
• OrderHistoryView: Memory increased 45%

Trend Analysis

📈 PERFORMANCE TRENDS (Last 7 Days)
════════════════════════════════════════════════════════

Response Time:
Mon ████████████████ 156ms
Tue ███████████████  145ms  
Wed █████████████    134ms
Thu ████████████     128ms
Fri ██████████       112ms
Sat █████████        98ms  ← Best
Sun ██████████       105ms

Pattern: Steady improvement
Recommendation: Current optimizations working

Taking Action on Reports

Priority Matrix

Use this matrix to decide what to fix first:

         High Impact
              ↑
    ┌─────────┬─────────┐
    │    1    │    2    │
    │ Critical│Important│
    │  & Easy │ & Easy  │
    ├─────────┼─────────┤
    │    3    │    4    │
    │Critical │Important│
    │  & Hard │ & Hard  │
    └─────────┴─────────┘
    Easy ←────────→ Hard

Fix Order:

  1. High impact, easy fixes (N+1 queries)
  2. High impact, moderate difficulty (indexes)
  3. Lower impact, easy fixes (caching)
  4. Complex architectural changes (last)

Creating Action Items

Transform report issues into tasks:

# From report
"N+1 in UserSerializer, 89 queries"

# To action item
"""
Task: Fix N+1 in UserSerializer
Priority: High
Estimate: 15 minutes
Steps:
1. Open serializers.py
2. Update queryset in ViewSet
3. Add select_related('profile')
4. Run tests to verify
Expected: 89 queries → 2 queries
"""

Tracking Improvements

Document your fixes:

## Performance Improvements Log

### 2024-01-15
- **Issue**: N+1 in UserListView
- **Fix**: Added select_related
- **Result**: 89 queries → 2 queries
- **Impact**: 234ms → 45ms (80% faster)

### 2024-01-16  
- **Issue**: Missing index on orders.status
- **Fix**: Added database index
- **Result**: 342ms → 15ms query time
- **Impact**: Overall 50% speed improvement

Common Report Patterns

The "Death by Thousand Cuts"

No single critical issue, but many small ones:
- 15 queries (not terrible)
- 150ms response (not terrible)
- 35MB memory (not terrible)
- 60% cache hits (not terrible)

Combined: Grade D performance

Fix: Address all small issues systematically

The "One Bad Apple"

Everything excellent except one critical issue:
- ✅ 3 queries
- ❌ 2.5 second response time
- ✅ 15MB memory
- ✅ 95% cache hits

Cause: External API call without timeout

Fix: Add timeout and async processing

The "Hidden N+1"

Seems OK at first:
- 12 queries (acceptable)
- 89ms response (good)

But with more data:
- 120 queries (10x users)
- 890ms response (10x slower)

Classic N+1 scaling problem

Report Customization

Configure Report Detail

# mercury_config.toml
[reporting]
detail_level = "high"  # low, medium, high
show_sql_queries = true
show_stack_traces = false
show_suggestions = true
group_by = "test_class"  # test_class, endpoint, severity

Custom Report Formats

# HTML report for sharing
mercury-test --format=html > report.html

# CSV for spreadsheet analysis
mercury-test --format=csv > metrics.csv

# Markdown for documentation
mercury-test --format=markdown > PERFORMANCE.md

Focus Reports

# Just show failures
mercury-test --report-failures-only

# Show worst performers
mercury-test --show-worst 5

# Filter by metric
mercury-test --report-filter="queries>50"

Learning from Reports

Pattern Recognition

Look for patterns across tests:

  • Same issue in multiple places?
  • Issues clustered in one app?
  • Certain operations always slow?

Building Intuition

Over time, you'll recognize issues instantly:

  • "45 queries for 10 items = N+1"
  • "500ms for simple list = missing index"
  • "Memory spike = loading full queryset"

Sharing Knowledge

Share interesting reports with your team:

  • "Look at this N+1 I found!"
  • "Check out this optimization"
  • "Here's a pattern to avoid"

Next Steps


Remember: Reports are not judgments, they're opportunities to improve.

Django Mercury Wiki

🏠 Overview

Clone this wiki locally