stemedb/cmd/load-test/report.go
jordan 157dbbb9eb feat: Complete Aphoria Phase 8-9 + UAT suite (90/90 tests passing)
## Phase 8: Enterprise Extractor Improvements 
- 14 security extractors (TLS, JWT, SQL injection, XSS, etc.)
- 10 framework-specific extractors (Spring, Django, Rails, etc.)
- Config file security detection (YAML, TOML)

## Phase 9: Autonomous Extractor Generation 
- Shadow mode executor with TP/FP tracking
- Graduation pipeline with confidence thresholds
- Auto-rollback on regression detection
- Cross-project pattern syncing

## UAT Suite Complete (14 scripts, 90 tests)
- test-core-detection.sh (6 tests)
- test-declarative-extractors.sh (5 tests)
- test-domain-frameworks.sh (5 tests)
- test-domain-unreal.sh (3 tests)
- test-llm-extraction.sh (6 tests)
- test-eval-harness.sh (5 tests)
- test-cross-language.sh (3 tests)
- test-precommit-performance.sh (4 tests)
- test-output-formats.sh (8 tests)
- test-drift-detection.sh (6 tests)
- test-exit-codes.sh (12 tests)
+ 3 more scripts

## Other Changes
- Updated roadmap to mark Phase 8-9 complete
- Added .gitignore entries for build artifacts
- Updated pre-commit: 800 line limit, exclude tests/data/cmd

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 22:50:55 -07:00

300 lines
8.6 KiB
Go

package main
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
// Targets defines the pass/fail thresholds.
var Targets = struct {
BaselineP50 time.Duration
BaselineP99 time.Duration
BaselineMaxLat time.Duration
BaselineErrRate float64
SustainedP99 time.Duration
SustainedErrRate float64
DegradationPct float64
}{
BaselineP50: 50 * time.Millisecond,
BaselineP99: 200 * time.Millisecond,
BaselineMaxLat: 1 * time.Second,
BaselineErrRate: 0.0,
SustainedP99: 200 * time.Millisecond,
SustainedErrRate: 0.1,
DegradationPct: 100.0, // <2x = <100% degradation
}
// TestResults holds all scenario results.
type TestResults struct {
BaselineMetrics *Metrics
SustainedMetrics *Metrics
ConcurrentMetrics *Metrics
APIURL string
Duration time.Duration
TargetRPS int
ReaderCount int
MeterDisabled bool
}
// GenerateReport creates a markdown report from test results.
func GenerateReport(results TestResults, outputDir string) (string, error) {
now := time.Now()
filename := fmt.Sprintf("%s-load-test.md", now.Format("2006-01-02"))
outputPath := filepath.Join(outputDir, filename)
var sb strings.Builder
// Header
sb.WriteString("# UAT Report: Load Testing Results\n\n")
sb.WriteString(fmt.Sprintf("**Date:** %s\n", now.Format("2006-01-02")))
sb.WriteString("**Phase/Feature:** P4.1 Load Testing\n")
sb.WriteString("**Tester:** load-test CLI\n")
// Calculate overall status
overallPass := true
if results.BaselineMetrics != nil {
if results.BaselineMetrics.WriteP99() > Targets.BaselineP99 ||
results.BaselineMetrics.WriteErrorRate() > Targets.BaselineErrRate {
overallPass = false
}
}
if results.SustainedMetrics != nil {
if results.SustainedMetrics.WriteP99() > Targets.SustainedP99 ||
results.SustainedMetrics.WriteErrorRate() > Targets.SustainedErrRate {
overallPass = false
}
}
if results.ConcurrentMetrics != nil {
if results.ConcurrentMetrics.DegradationPct() > Targets.DegradationPct {
overallPass = false
}
}
if overallPass {
sb.WriteString("**Status:** PASS\n\n")
} else {
sb.WriteString("**Status:** FAIL\n\n")
}
// Environment
sb.WriteString("## Environment\n\n")
sb.WriteString(fmt.Sprintf("- Go version: %s\n", runtime.Version()))
sb.WriteString(fmt.Sprintf("- OS: %s/%s\n", runtime.GOOS, runtime.GOARCH))
sb.WriteString(fmt.Sprintf("- StemeDB API: %s\n", results.APIURL))
if results.MeterDisabled {
sb.WriteString("- Meter: Disabled (`STEMEDB_METER_ENABLED=false`)\n")
} else {
sb.WriteString("- Meter: Enabled (may affect sustained test)\n")
}
sb.WriteString("\n")
// Test Results
sb.WriteString("## Test Results\n\n")
// Scenario A: Baseline
if results.BaselineMetrics != nil {
sb.WriteString(writeBaselineSection(results.BaselineMetrics))
}
// Scenario B: Sustained
if results.SustainedMetrics != nil {
sb.WriteString(writeSustainedSection(results.SustainedMetrics, results.Duration, results.TargetRPS))
}
// Scenario C: Concurrent
if results.ConcurrentMetrics != nil {
sb.WriteString(writeConcurrentSection(results.ConcurrentMetrics))
}
// Recommendations
sb.WriteString(writeRecommendations(results))
// Write file
if err := os.MkdirAll(outputDir, 0755); err != nil {
return "", fmt.Errorf("failed to create output directory: %w", err)
}
if err := os.WriteFile(outputPath, []byte(sb.String()), 0644); err != nil {
return "", fmt.Errorf("failed to write report: %w", err)
}
return outputPath, nil
}
func writeBaselineSection(m *Metrics) string {
var sb strings.Builder
sb.WriteString("### Scenario A: Baseline Latency (10K Assertions)\n\n")
sb.WriteString("| Metric | Target | Actual | Status |\n")
sb.WriteString("|--------|--------|--------|--------|\n")
// Total count
sb.WriteString(fmt.Sprintf("| Total | 10,000 | %d | %s |\n",
m.WriteCount(),
passFailEmoji(m.WriteCount() >= 10000)))
// P50 latency
p50Pass := m.WriteP50() <= Targets.BaselineP50
sb.WriteString(fmt.Sprintf("| p50 latency | <%s | %s | %s |\n",
Targets.BaselineP50,
m.WriteP50(),
passFailEmoji(p50Pass)))
// P99 latency
p99Pass := m.WriteP99() <= Targets.BaselineP99
sb.WriteString(fmt.Sprintf("| p99 latency | <%s | %s | %s |\n",
Targets.BaselineP99,
m.WriteP99(),
passFailEmoji(p99Pass)))
// Max latency
maxPass := m.WriteMax() <= Targets.BaselineMaxLat
sb.WriteString(fmt.Sprintf("| Max latency | <%s | %s | %s |\n",
Targets.BaselineMaxLat,
m.WriteMax(),
passFailEmoji(maxPass)))
// Error rate
errPass := m.WriteErrorRate() <= Targets.BaselineErrRate
sb.WriteString(fmt.Sprintf("| Error rate | 0%% | %.2f%% | %s |\n",
m.WriteErrorRate(),
passFailEmoji(errPass)))
sb.WriteString("\n")
return sb.String()
}
func writeSustainedSection(m *Metrics, duration time.Duration, targetRPS int) string {
var sb strings.Builder
sb.WriteString("### Scenario B: Sustained Writes\n\n")
sb.WriteString("| Metric | Target | Actual | Status |\n")
sb.WriteString("|--------|--------|--------|--------|\n")
// Duration
sb.WriteString(fmt.Sprintf("| Duration | %s | %s | PASS |\n",
duration.Round(time.Second),
m.Duration().Round(time.Second)))
// Throughput
actualRPS := m.WriteThroughput()
rpsPass := actualRPS >= float64(targetRPS)*0.9 // Allow 10% tolerance
sb.WriteString(fmt.Sprintf("| Avg throughput | %d/sec | %.0f/sec | %s |\n",
targetRPS,
actualRPS,
passFailEmoji(rpsPass)))
// P99 latency
p99Pass := m.WriteP99() <= Targets.SustainedP99
sb.WriteString(fmt.Sprintf("| p99 latency | <%s | %s | %s |\n",
Targets.SustainedP99,
m.WriteP99(),
passFailEmoji(p99Pass)))
// Error rate
errPass := m.WriteErrorRate() <= Targets.SustainedErrRate
sb.WriteString(fmt.Sprintf("| Error rate | <%.1f%% | %.3f%% | %s |\n",
Targets.SustainedErrRate,
m.WriteErrorRate(),
passFailEmoji(errPass)))
// Total writes
sb.WriteString(fmt.Sprintf("| Total writes | - | %d | - |\n", m.WriteCount()))
sb.WriteString("\n")
return sb.String()
}
func writeConcurrentSection(m *Metrics) string {
var sb strings.Builder
sb.WriteString("### Scenario C: Concurrent Readers\n\n")
sb.WriteString("| Metric | Target | Actual | Status |\n")
sb.WriteString("|--------|--------|--------|--------|\n")
// Reader count
sb.WriteString(fmt.Sprintf("| Reader count | 100 | %d | PASS |\n", m.ReaderCount()))
// Baseline P99
sb.WriteString(fmt.Sprintf("| Baseline p99 | - | %s | - |\n", m.BaselineP99()))
// Under-load P99
degradationPass := m.DegradationPct() <= Targets.DegradationPct
sb.WriteString(fmt.Sprintf("| Under-load p99 | <2x baseline | %s | %s |\n",
m.LoadedP99(),
passFailEmoji(degradationPass)))
// Degradation percentage
sb.WriteString(fmt.Sprintf("| Degradation | <100%% | %.1f%% | %s |\n",
m.DegradationPct(),
passFailEmoji(degradationPass)))
sb.WriteString("\n")
return sb.String()
}
func writeRecommendations(results TestResults) string {
var sb strings.Builder
var recommendations []string
sb.WriteString("## Recommendations\n\n")
// Check baseline
if results.BaselineMetrics != nil {
if results.BaselineMetrics.WriteP99() > Targets.BaselineP99 {
recommendations = append(recommendations,
"Baseline p99 latency exceeds target. Consider profiling write path for bottlenecks.")
}
if results.BaselineMetrics.WriteErrorRate() > 0 {
recommendations = append(recommendations,
"Baseline test had errors. Investigate server logs for root cause.")
}
}
// Check sustained
if results.SustainedMetrics != nil {
if results.SustainedMetrics.WriteP99() > Targets.SustainedP99 {
recommendations = append(recommendations,
"Sustained write p99 latency exceeds target. Consider scaling or optimizing WAL.")
}
if results.SustainedMetrics.WriteErrorRate() > Targets.SustainedErrRate {
recommendations = append(recommendations,
"Sustained write error rate too high. Check for resource exhaustion or rate limiting.")
}
if !results.MeterDisabled {
recommendations = append(recommendations,
"Meter was enabled during sustained test. Re-run with STEMEDB_METER_ENABLED=false for accurate results.")
}
}
// Check concurrent
if results.ConcurrentMetrics != nil {
if results.ConcurrentMetrics.DegradationPct() > Targets.DegradationPct {
recommendations = append(recommendations,
"Read latency degradation under load exceeds 2x. Consider read replica or caching layer.")
}
}
if len(recommendations) == 0 {
sb.WriteString("All tests passed. System is ready for production load.\n\n")
} else {
for _, rec := range recommendations {
sb.WriteString(fmt.Sprintf("- %s\n", rec))
}
sb.WriteString("\n")
}
return sb.String()
}
func passFailEmoji(pass bool) string {
if pass {
return "PASS"
}
return "FAIL"
}