Files
magnus919_agent-skills/qa-methodology/references/performance-testing.md
T
usernameandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 5bad13af56 feat(qa-methodology): refresh 5 traditional QA support references
- test-data-management: add time-travel testing (freezegun/timecop),
  data masking (static vs dynamic), GDPR/right-to-erasure, PII rules
- performance-testing: add tool landscape (k6/Locust/Gatling/JMeter),
  SLO-based threshold design, baseline-then-regress, CI cadence,
  gate-governance link to quality-gates-and-metrics
- security-testing: update to OWASP Top 10:2025 (A01-A10), add
  supply-chain/SBOM, STRIDE threat modeling, SAST/DAST/SCA landscape
- ci-failure-triage: add exit-code taxonomy (1/2/126/127/137/139/143),
  git bisect run, flake-vs-failure rerun-once protocol, numbered
  5-step diagnostic procedure
- test-debugging: add CI-vs-local divergence checklist (7 causes),
  test ordering/shared state (pytest-randomly), mock path binding

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-01 17:41:52 -04:00

139 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Performance Testing
## Types
| Type | Question Answered | Duration | Frequency |
|------|-------------------|----------|-----------|
| Load test | Does it handle expected traffic? | 515 min | Nightly / pre-release |
| Stress test | Where does it break? | Ramp until failure | On-demand / architectural changes |
| Soak test | Does it degrade over time (leaks)? | 424 hours | Weekly |
| Spike test | Does it survive sudden bursts? | Seconds to minutes | Pre-launch / event prep |
| Benchmark | What's the raw throughput/latency? | 30s2 min | On PR (smoke) |
## Tool Landscape
| Tool | Language | Strengths | Weaknesses | Best For |
|------|----------|-----------|------------|----------|
| **k6** | JavaScript (Go runtime) | CI-native, thresholds as code, low resource overhead, Grafana ecosystem | No GUI scripting; HTTP/gRPC/WebSocket/browser (experimental) | CI-integrated load testing, developer-owned perf |
| **Locust** | Python | Distributed by design, real-user behavior modeling, event-driven | Higher memory per VU than k6; Python GIL limits single-node throughput | Complex user flows, distributed load generation |
| **Gatling** | Scala/Java/Kotlin | Highest throughput per node, detailed HTML reports, protocol breadth | Steep learning curve; commercial features paywalled | High-throughput enterprise, protocol-heavy (JMS, JDBC) |
| **JMeter** | Java (GUI + CLI) | Widest protocol support, plugin ecosystem, 20+ years of community | Heavy resource usage, brittle test plans, XML config | Legacy protocol support, non-developer QA teams |
### Selection Decision
| Signal | Choose |
|--------|--------|
| CI/CD integration is the primary driver | k6 |
| Team is Python-centric, needs distributed load | Locust |
| Need maximum VUs per machine, detailed reporting | Gatling |
| Must test JMS, JDBC, FTP, or other non-HTTP protocols | JMeter |
| Browser-level rendering performance matters | k6 (browser module) or Lighthouse CI |
## SLO-Based Threshold Design
Performance thresholds must derive from product requirements, not arbitrary round numbers.
### Deriving Thresholds from SLOs
1. **Start with the product SLO.** Example: "95% of API requests complete in < 300ms" → p95 < 300ms.
2. **Set the test threshold at the SLO.** The load test asserts `p(95)<300`.
3. **Add a warning threshold below the SLO** for early signal: `p(95)<250` (advisory, does not fail CI).
4. **Set error-rate threshold from availability SLO.** If availability SLO is 99.9%, error threshold is `rate<0.001`.
### Baseline-Then-Regress Pattern
Never evaluate a single run in isolation. Establish a baseline first:
```
1. Run the load test on a known-good commit (e.g., main HEAD).
2. Store p50, p95, p99, throughput, error rate as the baseline.
3. On subsequent runs, compare against the 7-day rolling baseline.
4. Alert if p95 regresses > 20% vs baseline.
5. Fail CI (blocking gate) if p95 exceeds the absolute SLO.
```
| Comparison | Action |
|-----------|--------|
| p95 within 10% of baseline | Pass — no action |
| p95 regressed 1020% | Advisory warning — investigate if trend continues |
| p95 regressed > 20% vs baseline | Alert — likely regression; investigate before merge |
| p95 exceeds absolute SLO | Block merge — SLO breach |
### Worked Example (k6 thresholds)
```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 20 }, // ramp up
{ duration: '1m', target: 20 }, // steady state
{ duration: '10s', target: 0 }, // ramp down
],
thresholds: {
http_req_duration: ['p(95)<300', 'p(99)<800'], // from product SLO
http_req_failed: ['rate<0.001'], // from availability SLO
},
};
export default function () {
const res = http.get('https://staging.example.com/api/items');
check(res, {
'status 200': (r) => r.status === 200,
'response time < 200ms': (r) => r.timings.duration < 200,
});
sleep(1);
}
```
## Key Metrics
| Metric | Definition | Target Guidance |
|--------|-----------|-----------------|
| p50 latency | Median response time | User-perceived "normal" |
| p95 latency | 95th percentile | SLO boundary for most APIs |
| p99 latency | 99th percentile | Tail latency — catches GC pauses, cold starts |
| Throughput | Requests/sec sustained | Compare against capacity plan |
| Error rate | 5xx / total | < 0.1% under load |
| Saturation | CPU/memory/connections at peak | < 80% = headroom |
## CI Cadence
| Trigger | Test Type | Duration | Gate |
|---------|-----------|----------|------|
| PR (hot-path changes only) | Smoke benchmark (10 VUs, 30s) | < 2 min | Advisory — catches 10× regressions |
| Nightly (staging) | Full load test (expected traffic profile) | 1015 min | Blocking for release branch |
| Weekly | Soak test (constant load, 48h) | 48 hours | Alert on degradation trend |
| Pre-release | Stress test + spike | 30 min | Blocking — must not regress vs last release |
**Not on every PR.** Full load tests on every PR waste CI resources and produce noisy results. Smoke benchmarks on PRs catch gross regressions; nightly runs catch subtle ones.
## Interpreting Results
| Symptom | Likely Cause | Next Step |
|---------|-------------|-----------|
| Latency climbs linearly with VUs | Single-threaded bottleneck or lock contention | Profile CPU, check for global locks |
| Latency flat then sudden cliff | Resource exhaustion (connections, memory, FDs) | Check pool sizes, `ulimit`, OOM killer |
| Throughput plateaus early | Downstream dependency is the bottleneck | Test the dependency in isolation |
| Errors only at high concurrency | Race condition or timeout misconfiguration | Check connection pool, retry storms |
| Memory grows during soak | Leak — unclosed connections, unbounded cache | Heap dump at intervals, diff allocations |
## Gate Governance
Performance thresholds feed into quality gate decisions — they are not standalone pass/fail numbers. A perf regression that exceeds the SLO is a **blocking gate** (prevents merge/deploy); a regression within the warning band is an **advisory gate** (flags for review). For the full gate design framework (blocking vs advisory, pipeline stages, anti-patterns), see [quality-gates-and-metrics.md](./quality-gates-and-metrics.md).
## Gotchas
- **Thresholds without baselines are meaningless.** A p95 of 280ms tells you nothing without knowing whether last week's was 120ms. Always baseline first.
- **Testing against shared staging environments** produces noisy results from other teams' load. Use dedicated perf environments or time-boxed windows.
- **Ignoring tail latency.** p50 can look healthy while p99 is 10× the SLO. Always assert on percentiles, not averages.
- **Running the full suite on every PR** wastes CI minutes and teaches teams to ignore perf results. Tier by trigger.
## Related
- Gate governance (blocking vs advisory, pipeline placement): [quality-gates-and-metrics.md](./quality-gates-and-metrics.md)
- Test data for load tests (volume seeding): [test-data-management.md](./test-data-management.md)
*Sources: k6 documentation (grafana.com/k6), Locust docs (locust.io), Gatling docs (gatling.io), JMeter docs (jmeter.apache.org), Google SRE Workbook (O'Reilly, 2018), DORA State of DevOps Reports.*