Technical Design: Performance Analysis
Metadata
- Feature: performance-analysis
- Status: APPROVED
- Created: 2026-01-30
1. Overview
1.1 Summary
Add zerg analyze --performance as a comprehensive static performance audit. A new zerg/performance/ submodule (following the zerg/diagnostics/ pattern) houses 11 tool adapters, a factor catalog, stack detection, scoring aggregation, and 4 output formatters. Integration into the existing analyze.py is minimal: one new CheckType enum value, one bridge checker class, and CLI flag additions.
1.2 Goals
- Cover 140 performance factors across 16 categories via static analysis
- Graceful degradation when tools are missing
- Dynamic semgrep rule selection based on project stack
- Rich multi-format output (text, JSON, SARIF, markdown)
1.3 Non-Goals
- Runtime profiling or dynamic analysis
- Advisory checklists for non-static factors
- Adding new pip dependencies
2. Architecture
2.1 High-Level Design
CLI (analyze.py)
└─ PerformanceChecker (bridge)
└─ PerformanceAuditor (aggregator.py)
├─ FactorCatalog (catalog.py) ─── factors.json
├─ StackDetector (stack_detector.py)
├─ ToolRegistry (tool_registry.py)
└─ Adapters[] (adapters/*.py)
├─ SemgrepAdapter
├─ RadonAdapter
├─ LizardAdapter
├─ VultureAdapter
├─ JscpdAdapter
├─ DeptryAdapter
├─ PipdeptreeAdapter
├─ DiveAdapter
├─ HadolintAdapter
├─ TrivyAdapter
└─ ClocAdapter
2.2 Component Breakdown
| Component | Responsibility | Files |
|---|---|---|
| types | Data models for findings, reports, scores | zerg/performance/types.py |
| catalog | Factor loading, filtering, tool-to-factor mapping | zerg/performance/catalog.py |
| stack_detector | Detect languages, frameworks, infra | zerg/performance/stack_detector.py |
| tool_registry | Check tool availability, advisory output | zerg/performance/tool_registry.py |
| adapters | 11 tool adapters + base ABC | zerg/performance/adapters/ |
| aggregator | Orchestrate adapters, score categories | zerg/performance/aggregator.py |
| formatters | Rich, JSON, SARIF, Markdown output | zerg/performance/formatters.py |
2.3 Data Flow
- CLI invokes
PerformanceChecker.check(files) - Bridge creates
PerformanceAuditorand callsrun(files) - Auditor loads factor catalog, detects stack, checks tool availability
- Auditor selects applicable adapters (based on stack + tool availability)
- Adapters run in parallel via ThreadPoolExecutor
- Each adapter returns
list[PerformanceFinding] - Auditor aggregates findings by category, computes scores
- Returns
PerformanceReportto bridge - Bridge converts to
AnalysisResultfor standard flow - CLI uses perf-specific formatters when
check == performance
3. Detailed Design
3.1 Data Models (types.py)
class Severity(Enum):
CRITICAL = "critical" # Weight: 25 points
HIGH = "high" # Weight: 10 points
MEDIUM = "medium" # Weight: 5 points
LOW = "low" # Weight: 2 points
INFO = "info" # Weight: 0 points
@dataclass
class PerformanceFactor:
id: int
category: str
factor: str
description: str
cli_tools: list[str]
security_note: str | None = None
@dataclass
class PerformanceFinding:
factor_id: int
factor_name: str
category: str
severity: Severity
message: str
file: str = ""
line: int = 0
tool: str = ""
rule_id: str = ""
suggestion: str = ""
@dataclass
class ToolStatus:
name: str
available: bool
version: str = ""
factors_covered: int = 0
@dataclass
class CategoryScore:
category: str
score: float | None # None if no tools available
findings: list[PerformanceFinding]
factors_checked: int
factors_total: int
@dataclass
class PerformanceReport:
overall_score: float | None
categories: list[CategoryScore]
tool_statuses: list[ToolStatus]
findings: list[PerformanceFinding]
factors_checked: int
factors_total: int
detected_stack: dict[str, list[str]]
def to_dict(self) -> dict: ...
def top_issues(self, limit: int = 20) -> list[str]: ...
3.2 Adapter Interface (adapters/base.py)
class BaseToolAdapter(ABC):
name: str
tool_name: str
factors_covered: list[int]
@abstractmethod
def run(self, files: list[str], project_path: str, stack: DetectedStack) -> list[PerformanceFinding]: ...
def is_applicable(self, stack: DetectedStack) -> bool:
return True # Override for language/infra-specific adapters
3.3 Semgrep Adapter Registry Selection
# Map detected stack to semgrep config sets
SEMGREP_CONFIGS = {
"python": ["p/python", "p/python-best-practices"],
"javascript": ["p/javascript", "p/nodejs"],
"typescript": ["p/typescript"],
"go": ["p/golang"],
"rust": ["p/rust"],
"docker": ["p/dockerfile"],
}
# Always include: p/performance (if available)
3.4 Scoring Algorithm
Per-category:
penalty = sum(SEVERITY_WEIGHTS[f.severity] for f in category_findings)
max_penalty = factors_checked * 25 # Assume worst case
score = max(0, 100 - (penalty / max(1, factors_checked)) * 10)
Overall:
weighted_sum = sum(cat.score * cat.factors_checked for cat in categories if cat.score is not None)
total_weight = sum(cat.factors_checked for cat in categories if cat.score is not None)
overall = weighted_sum / max(1, total_weight)
4. Key Decisions
4.1 Submodule vs Inline
Decision: New zerg/performance/ submodule
Rationale: 11 adapters + orchestrator + formatters would bloat analyze.py past 2000 lines. Follows established zerg/diagnostics/ pattern.
4.2 Tool Dependencies
Decision: All optional externals, no pip deps Rationale: Users install only what they need. Graceful degradation preserves usability.
4.3 Semgrep Rules
Decision: Dynamic registry pull based on detected stack Rationale: Project-specific rules provide better coverage than shipping static rules.
4.4 Non-Static Factors
Decision: Skip entirely (no advisory checklist) Rationale: User preference. Only report what tools can measure.
4.5 Performance Excluded from --check all
Decision: Require explicit --performance or --check performance Rationale: Performance audit is heavyweight; don't slow down standard quick checks.
5. Implementation Plan
5.1 Phase Summary
| Phase | Level | Tasks | Parallel | Description |
|---|---|---|---|---|
| Foundation | 1 | 4 | Yes | Types, catalog, stack detection, tool registry |
| Adapters | 2 | 4 | Yes | 11 tool adapters grouped by domain |
| Aggregation | 3 | 2 | Yes | Orchestrator + formatters |
| Integration | 4 | 1 | No | CLI modifications to analyze.py |
| Testing | 5 | 2 | Yes | Unit tests with mocked tools |
5.2 Recommended Workers
- Optimal: 4 workers (matches max parallelization at L1 and L2)
6. Testing Strategy
6.1 Unit Tests
- types.py: Serialization, to_dict(), severity ordering
- catalog.py: Load, filter static-only, tool mapping
- stack_detector.py: Detect Python, JS, Go, Rust, Docker, K8s projects
- tool_registry.py: Available/missing tool detection (mock shutil.which)
- adapters: Each adapter with mocked subprocess output → findings
- aggregator: Scoring math with synthetic findings
- formatters: JSON schema, SARIF structure, Rich output, Markdown headings
6.2 Integration
- CLI
--performanceflag invocation (mocked tools) - All 4 output formats produce valid output
- Graceful degradation with zero tools available
7. Risk Assessment
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| Semgrep registry rules change | Medium | Low | Map by category, not exact rule ID |
| Tool output format changes | Low | Medium | Version-pin expected output format per adapter |
| Too many findings overwhelm output | Medium | Low | top_issues() limit, collapsible Rich panels |