# Performance Tests - Storm Logger

## Quick Overview for Gemini

Performance tests ensure Storm Logger meets speed, memory, and scalability requirements. These tests benchmark critical operations and identify performance regressions.

## Test Structure

```
performance/
├── test_benchmarks.py  # Core performance benchmarks
└── (future tests)      # Stress tests, load tests
```

## Performance Testing Strategy

### What We Measure
1. **Latency**: Time per operation
2. **Throughput**: Operations per second
3. **Memory Usage**: Heap allocation and leaks
4. **Scalability**: Performance under load
5. **Resource Efficiency**: CPU and I/O usage

### Key Metrics
- Basic logging: < 0.1ms per statement
- With formatting: < 0.2ms
- With sanitization: < 0.3ms
- Business metrics: < 0.05ms overhead
- Memory per logger: < 1MB

## Core Benchmarks

### 1. Basic Logging Performance
```python
def test_basic_logging_speed(benchmark):
    """Benchmark basic logging operation."""
    logger = get_storm_logger("benchmark")
    
    def log_message():
        logger.info("Test message with some data", 
                   user_id=123, action="test")
    
    # Run benchmark
    result = benchmark(log_message)
    
    # Assert performance requirements
    assert result.stats['mean'] < 0.0001  # < 0.1ms
    assert result.stats['stddev'] < 0.00005  # Low variance
```

### 2. Throughput Testing
```python
def test_logging_throughput(benchmark):
    """Measure messages per second."""
    logger = get_storm_logger("throughput")
    
    def log_burst():
        for i in range(1000):
            logger.info(f"Message {i}")
    
    result = benchmark(log_burst)
    
    # Calculate throughput
    messages_per_second = 1000 / result.stats['mean']
    assert messages_per_second > 10000  # >10k msgs/sec
```

### 3. Memory Efficiency
```python
def test_memory_usage():
    """Test memory consumption."""
    import tracemalloc
    tracemalloc.start()
    
    # Create logger and log messages
    logger = get_storm_logger("memory")
    snapshot1 = tracemalloc.take_snapshot()
    
    for i in range(10000):
        logger.info(f"Memory test {i}")
    
    snapshot2 = tracemalloc.take_snapshot()
    
    # Analyze memory growth
    stats = snapshot2.compare_to(snapshot1, 'lineno')
    total_growth = sum(stat.size_diff for stat in stats)
    
    # Should not grow more than 10MB for 10k messages
    assert total_growth < 10 * 1024 * 1024
```

## Component-Specific Benchmarks

### 1. Formatter Performance
```python
@pytest.mark.parametrize("formatter_class,expected_time", [
    (MessageFormatter, 0.00005),
    (JsonFormatter, 0.00008),
    (ColoredFormatter, 0.00010),
])
def test_formatter_performance(benchmark, formatter_class, expected_time):
    """Benchmark different formatters."""
    formatter = formatter_class()
    record = create_log_record("Test message")
    
    result = benchmark(formatter.format, record)
    assert result.stats['mean'] < expected_time
```

### 2. Sanitizer Performance
```python
def test_sanitization_overhead(benchmark):
    """Measure PII sanitization impact."""
    sanitizer = LogSanitizer(level="high")
    sensitive_data = "User SSN: 123-45-6789, CC: 4111-1111-1111-1111"
    
    result = benchmark(sanitizer.sanitize, sensitive_data)
    
    # Sanitization should add < 0.2ms
    assert result.stats['mean'] < 0.0002
```

### 3. Business Metrics Collection
```python
def test_metrics_collection_overhead(benchmark):
    """Test business metrics performance."""
    logger = get_storm_logger("metrics")
    
    def track_event():
        logger.track_business_event("purchase", {
            "amount": 99.99,
            "currency": "USD",
            "items": 3
        })
    
    result = benchmark(track_event)
    
    # Metrics should add minimal overhead
    assert result.stats['mean'] < 0.00005  # < 0.05ms
```

## Scalability Tests

### 1. Concurrent Logging
```python
def test_concurrent_performance():
    """Test performance with multiple threads."""
    import concurrent.futures
    logger = get_storm_logger("concurrent")
    
    def worker(worker_id):
        for i in range(1000):
            logger.info(f"Worker {worker_id} message {i}")
    
    start = time.time()
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        futures = [executor.submit(worker, i) for i in range(10)]
        concurrent.futures.wait(futures)
    
    duration = time.time() - start
    
    # 10 threads × 1000 messages in < 2 seconds
    assert duration < 2.0
```

### 2. Large Message Handling
```python
@pytest.mark.parametrize("size_kb,max_time", [
    (1, 0.001),      # 1KB message
    (10, 0.005),     # 10KB message
    (100, 0.020),    # 100KB message
    (1000, 0.100),   # 1MB message
])
def test_large_message_performance(benchmark, size_kb, max_time):
    """Test performance with large messages."""
    logger = get_storm_logger("large_msg")
    large_data = "x" * (size_kb * 1024)
    
    def log_large():
        logger.info("Large message", data=large_data)
    
    result = benchmark(log_large)
    assert result.stats['mean'] < max_time
```

### 3. Handler Chain Performance
```python
def test_multiple_handlers_performance(benchmark, tmp_path):
    """Test performance with multiple handlers."""
    config = StormLoggerConfig(
        handlers={
            "console": {"class": "ConsoleHandler"},
            "file": {"class": "FileHandler", "filename": tmp_path / "test.log"},
            "memory": {"class": "MemoryHandler", "capacity": 1000},
            "null": {"class": "NullHandler"}
        }
    )
    logger = StormLogger(config)
    
    result = benchmark(logger.info, "Multi-handler test")
    
    # Should still be fast with 4 handlers
    assert result.stats['mean'] < 0.0002  # < 0.2ms
```

## Stress Testing

### 1. Sustained Load
```python
def test_sustained_load():
    """Test performance under sustained load."""
    logger = get_storm_logger("stress")
    
    # Log continuously for 10 seconds
    start = time.time()
    count = 0
    
    while time.time() - start < 10:
        logger.info(f"Stress test {count}")
        count += 1
    
    throughput = count / 10
    print(f"Sustained throughput: {throughput} msgs/sec")
    assert throughput > 5000  # >5k msgs/sec sustained
```

### 2. Memory Stress Test
```python
def test_memory_under_stress():
    """Test memory behavior under stress."""
    import gc
    import psutil
    
    process = psutil.Process()
    initial_memory = process.memory_info().rss / 1024 / 1024  # MB
    
    logger = get_storm_logger("memory_stress")
    
    # Create many loggers
    loggers = []
    for i in range(100):
        loggers.append(get_storm_logger(f"logger_{i}"))
    
    # Log heavily
    for _ in range(100):
        for logger in loggers:
            logger.info("Memory stress test")
    
    # Force cleanup
    loggers.clear()
    gc.collect()
    
    final_memory = process.memory_info().rss / 1024 / 1024  # MB
    memory_growth = final_memory - initial_memory
    
    print(f"Memory growth: {memory_growth:.2f} MB")
    assert memory_growth < 50  # Less than 50MB growth
```

## Performance Profiling

### 1. CPU Profiling
```python
def test_cpu_profile():
    """Profile CPU usage during logging."""
    import cProfile
    import pstats
    
    logger = get_storm_logger("cpu_profile")
    
    profiler = cProfile.Profile()
    profiler.enable()
    
    # Run logging operations
    for i in range(10000):
        logger.info(f"Profile test {i}")
    
    profiler.disable()
    
    # Analyze results
    stats = pstats.Stats(profiler)
    stats.sort_stats('cumulative')
    
    # Verify no obvious bottlenecks
    top_functions = stats.get_print_list([10])[0]
    # Add assertions about expected performance
```

### 2. I/O Performance
```python
def test_io_performance(benchmark, tmp_path):
    """Test file I/O performance."""
    log_file = tmp_path / "io_test.log"
    
    config = StormLoggerConfig(
        handlers={
            "file": {
                "class": "FileHandler",
                "filename": str(log_file),
                "mode": "a"
            }
        }
    )
    logger = StormLogger(config)
    
    def write_log():
        logger.info("I/O test message with some data")
    
    result = benchmark(write_log)
    
    # File I/O should be reasonably fast
    assert result.stats['mean'] < 0.001  # < 1ms
```

## Regression Testing

### 1. Performance History
```python
def test_performance_regression():
    """Compare against baseline performance."""
    # Load baseline results
    baseline = load_baseline_performance()
    
    # Run current benchmarks
    current = run_all_benchmarks()
    
    # Compare results
    for test_name, baseline_time in baseline.items():
        current_time = current[test_name]
        
        # Allow 10% degradation maximum
        assert current_time < baseline_time * 1.1, \
            f"{test_name} regressed: {current_time} vs {baseline_time}"
```

### 2. Version Comparison
```python
@pytest.mark.skipif(not PREVIOUS_VERSION_AVAILABLE, 
                    reason="Previous version not available")
def test_version_performance_comparison():
    """Compare performance with previous version."""
    # Benchmark current version
    current_perf = benchmark_current_version()
    
    # Benchmark previous version
    previous_perf = benchmark_previous_version()
    
    # Current should be at least as fast
    assert current_perf >= previous_perf
```

## Running Performance Tests

```bash
# Run all performance tests
pytest tests/performance/ -v

# Run with benchmark output
pytest tests/performance/ --benchmark-only

# Save benchmark results
pytest tests/performance/ --benchmark-save=baseline

# Compare with baseline
pytest tests/performance/ --benchmark-compare=baseline

# Run specific benchmark
pytest tests/performance/test_benchmarks.py::test_basic_logging_speed

# Generate HTML report
pytest tests/performance/ --benchmark-histogram
```

## Performance Optimization Tips

### 1. Common Bottlenecks
- String formatting (use lazy evaluation)
- File I/O (use buffering)
- Lock contention (minimize critical sections)
- Memory allocation (reuse objects)

### 2. Profiling Commands
```bash
# CPU profiling
python -m cProfile -o profile.stats test_script.py

# Memory profiling
python -m memory_profiler test_script.py

# Line profiling
kernprof -l -v test_script.py
```

### 3. Optimization Checklist
- [ ] Profile before optimizing
- [ ] Measure impact of changes
- [ ] Test under realistic load
- [ ] Consider trade-offs
- [ ] Document optimizations

## Best Practices

### 1. Consistent Environment
- Use same hardware for comparisons
- Control for system load
- Warm up before measuring
- Multiple runs for statistics

### 2. Realistic Scenarios
- Test with production-like data
- Include error cases
- Vary message sizes
- Test concurrent access

### 3. Continuous Monitoring
- Track performance over time
- Set up automated alerts
- Regular benchmark runs
- Version comparisons

## Next Steps

- Add more stress tests
- Implement load testing
- Profile memory allocation
- Add network handler benchmarks
- Create performance dashboard