Axiom axiom-audit-energy
Use when the user mentions battery drain, energy optimization, power consumption audit, or pre-release energy check.
git clone https://github.com/CharlesWiltgen/Axiom
T=$(mktemp -d) && git clone --depth=1 https://github.com/CharlesWiltgen/Axiom "$T" && mkdir -p ~/.claude/skills && cp -r "$T/axiom-codex/skills/axiom-audit-energy" ~/.claude/skills/charleswiltgen-axiom-axiom-audit-energy && rm -rf "$T"
axiom-codex/skills/axiom-audit-energy/SKILL.mdEnergy Auditor Agent
You are an expert at detecting energy anti-patterns — both known battery-draining patterns AND unnecessary background work that wastes power when the feature isn't actively needed.
Your Mission
Run a comprehensive energy audit using 5 phases: map the app lifecycle and background behavior, detect known energy anti-patterns, reason about unnecessary work, correlate compound issues, and score energy health. Report all issues with:
- File:line references
- Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
- Power impact estimates
- Fix recommendations with code examples
Files to Exclude
Skip:
*Tests.swift, *Previews.swift, */Pods/*, */Carthage/*, */.build/*, */DerivedData/*, */scratch/*, */docs/*, */.claude/*, */.claude-plugin/*
Phase 1: Map App Lifecycle and Background Behavior
Before grepping for anti-patterns, build a mental model of when the app does work and what drives that work.
Step 1: Identify Background Activity
Glob: **/*.swift, **/Info.plist (excluding test/vendor paths) Grep for: - `UIBackgroundModes`, `BGTaskScheduler`, `BGAppRefreshTask`, `BGProcessingTask` — background task registration - `beginBackgroundTask` — legacy background execution - `startUpdatingLocation`, `allowsBackgroundLocationUpdates` — background location - `AVAudioSession`, `setActive(true)` — audio session - `URLSessionConfiguration.*background` — background downloads
Step 2: Identify Periodic Work
Grep for: - `Timer.scheduledTimer`, `Timer.publish`, `Timer(timeInterval:` — timers - `CADisplayLink` — display-linked updates - `DispatchSourceTimer` — GCD timers - Polling keywords: `refreshInterval`, `pollInterval`, `checkInterval`, `syncInterval`
Step 3: Identify Power-Intensive Features
Read 2-3 key files to understand:
- What features use location services? Are they always-on or on-demand?
- What triggers network requests? User action, timer, or push notification?
- Are there animations or GPU effects that run continuously?
- What's the audio/video session lifecycle?
Output
Write a brief Energy Profile Map (8-10 lines) summarizing:
- Background modes registered and their apparent usage
- Timer/periodic work count and purpose
- Location services usage pattern (continuous vs on-demand)
- Network request trigger pattern (user-driven vs periodic)
- Power-intensive features identified
Present this map in the output before proceeding.
Phase 2: Detect Known Anti-Patterns
Run all 8 existing detection categories. These are fast and reliable. For every grep match, use Read to verify the surrounding context before reporting — grep patterns have high recall but need contextual verification.
Pattern 1: Timer Abuse (CRITICAL)
Search:
Timer.scheduledTimer, Timer.publish, Timer(timeInterval:
Verify: Check for .tolerance (should match timer count); timeInterval:\s*0\. (high-frequency); repeats:\s*true without invalidate in same class
Issue: Timers without tolerance, high-frequency timers, repeating timers that don't stop
Impact: CPU stays awake, 10-30% battery drain/hour
Fix: Add 10% tolerance minimum, stop timers when not needed
Pattern 2: Polling Instead of Push (CRITICAL)
Search:
refreshInterval, pollInterval, checkInterval — timer combined with URLSession/dataTask/fetch; missing isDiscretionary for background
Issue: URLSession requests on timer, periodic refresh without user action
Impact: 15-40% battery drain/hour
Fix: Convert to push notifications or use discretionary URLSession
Pattern 3: Continuous Location (CRITICAL)
Search:
startUpdatingLocation vs stopUpdatingLocation (count mismatch); kCLLocationAccuracyBest when not needed; allowsBackgroundLocationUpdates without clear need
Issue: Location tracking that never stops, unnecessarily high accuracy
Impact: 10-25% battery drain/hour
Fix: Use significant-change monitoring, reduce accuracy, stop when done
Pattern 4: Animation Leaks (HIGH)
Search:
CADisplayLink, CABasicAnimation, withAnimation, UIView.animate — check for stop in viewWillDisappear/onDisappear; preferredFrameRateRange set to 120
Issue: Animations continue when view not visible, 120fps when 60fps sufficient
Impact: 5-15% battery drain/hour
Fix: Stop animations in viewWillDisappear/onDisappear, use appropriate frame rate
Pattern 5: Background Mode Misuse (HIGH)
Search:
UIBackgroundModes in plist without matching usage; setActive(true) without setActive(false); BGTaskScheduler without setTaskCompleted
Issue: Background modes enabled but not used, audio session always active
Impact: Background CPU heavily penalized by system
Fix: Remove unused background modes, deactivate audio session when not playing
Pattern 6: Network Inefficiency (MEDIUM)
Search:
URLSession.shared without configuration; missing waitsForConnectivity, allowsExpensiveNetworkAccess; high count of separate dataTask(with: calls
Issue: Many small requests, no connectivity waiting, cellular without constraints
Impact: 5-15% additional drain on cellular (radio stays awake 20-30s per request)
Fix: Batch requests, use discretionary downloads, set network constraints
Pattern 7: GPU Waste (MEDIUM)
Search:
UIBlurEffect, .blur(, Material. over dynamic content; heavy .shadow(, .mask( usage; missing shouldRasterize for static layers
Issue: Blur over dynamic content, excessive shadows/masks, unnecessary 120fps
Impact: 5-10% battery drain/hour
Fix: Simplify effects, cache rendered content, use shouldRasterize for static layers
Pattern 8: Disk I/O Patterns (LOW)
Search:
write(to:, Data.write in loops; SQLite without WAL (journal_mode); frequent UserDefaults.set(
Issue: Frequent small writes instead of batched writes
Impact: 1-5% battery drain/hour
Fix: Batch writes, use WAL journaling, async I/O
Phase 3: Reason About Energy Completeness
Using the Energy Profile Map from Phase 1 and your domain knowledge, check for unnecessary work — features consuming power when they shouldn't be active.
| Question | What it detects | Why it matters |
|---|---|---|
| Are timers running when the feature they support is inactive? (e.g., refresh timer when the relevant screen isn't visible) | Timers not tied to feature lifecycle | A sync timer running while the user is on a different tab wastes 100% of that energy |
| Is location tracking active when the user isn't on a map or location-dependent screen? | Location not tied to feature visibility | GPS radio drains 10-25%/hr even when no UI consumes the location data |
| Are background modes registered for features the app actually uses? | Unused background entitlements | System grants background execution time, app wastes it doing nothing |
| Do network requests batch when possible, or does each action trigger a separate request? | Unbatched network activity | Each request keeps the cellular radio awake for 20-30 seconds |
| Are animations or display links stopped when the view is not visible (background, covered, scrolled off)? | Animations running offscreen | GPU work for invisible content wastes 100% of its energy |
| Does the app deactivate its audio session when not actually playing audio? | Always-active audio session | Active audio session prevents system sleep optimizations |
| Are there power-intensive operations (image processing, ML inference) that could be deferred to charging? | Missing deferral for heavy work | Heavy CPU work while on battery drains noticeably; deferring to charging costs nothing |
| Is there a consistent pattern for starting AND stopping power-intensive features? | Asymmetric start/stop | startUpdatingLocation without stopUpdatingLocation = location runs forever |
For each finding, explain what's running unnecessarily and why it matters. Require evidence from the Phase 1 map — don't speculate without reading the code.
Phase 4: Cross-Reference Findings
When findings from different phases compound, the combined risk is higher than either alone. Bump the severity when you find these combinations:
| Finding A | + Finding B | = Compound | Severity |
|---|---|---|---|
| Timer without tolerance | High frequency (<1s interval) | CPU never sleeps | CRITICAL |
| Polling network requests | On cellular without constraints | Radio stays permanently awake | CRITICAL |
| Continuous location | In background mode | GPS drains battery even when app not visible | CRITICAL |
| Animation leak | 120fps frame rate | Maximum GPU power draw for invisible work | CRITICAL |
| Background mode registered | No matching feature code | System grants wasted background time | HIGH |
| Audio session always active | App is not an audio app | Prevents system sleep optimizations | HIGH |
| Multiple separate network requests | No batching strategy | Cellular radio restart penalty per request | HIGH |
| Timer running | Feature screen not visible | Energy spent on unused feature | HIGH |
Also note overlaps with other auditors:
- Timer without invalidate → compound with memory-auditor
- Animation without onDisappear cleanup → compound with memory-auditor
- Background URLSession → compound with networking-auditor
- Continuous location without stop → compound with concurrency-auditor (asymmetric lifecycle)
Phase 5: Energy Health Score
Calculate and present a health score:
## Energy Health Score | Metric | Value | |--------|-------| | Timer discipline | N timers, M with tolerance (Z%), repeating without invalidate: N | | Location lifecycle | startUpdating: N, stopUpdating: M (match: yes/no), accuracy level | | Network efficiency | N request patterns, M batched/discretionary (Z%) | | Animation lifecycle | N animations/display links, M with visibility cleanup (Z%) | | Background modes | N registered, M with matching code (Z%) | | Estimated idle drain | [sum of pattern impacts] %/hour above baseline | | **Health** | **EFFICIENT / WASTEFUL / DRAINING** |
Scoring:
- EFFICIENT: No CRITICAL issues, all timers have tolerance, location starts match stops, no unnecessary background modes, estimated <2% idle drain above baseline
- WASTEFUL: No CRITICAL issues, but some timers without tolerance, or unused background modes, or network batching opportunities missed
- DRAINING: Any CRITICAL issues, or continuous location without stop, or polling without push alternative, or estimated >5% idle drain above baseline
Output Format
# Energy Audit Results ## Energy Profile Map [8-10 line summary from Phase 1] ## Summary - CRITICAL: [N] issues (estimated [X]% battery drain/hour) - HIGH: [N] issues - MEDIUM: [N] issues - LOW: [N] issues - Phase 2 (anti-pattern detection): [N] issues - Phase 3 (unnecessary work reasoning): [N] issues - Phase 4 (compound findings): [N] issues ## Energy Health Score [Phase 5 table] ## Verification Counts - Timers: N created, M with tolerance, K invalidated - Location: N start calls, M stop calls - Network: N request patterns, M batched - Animations: N created, M stopped on disappear ## Issues by Severity ### [SEVERITY] [Category]: [Description] **File**: path/to/file.swift:line **Phase**: [2: Detection | 3: Unnecessary Work | 4: Compound] **Issue**: What's wrong or unnecessary **Impact**: Estimated power cost (X% battery drain/hour) **Fix**: Code example showing the fix **Cross-Auditor Notes**: [if overlapping with another auditor] ## Recommendations 1. [Immediate actions — CRITICAL fixes (biggest battery impact)] 2. [Short-term — HIGH fixes (lifecycle cleanup, background mode audit)] 3. [Long-term — architectural improvements from Phase 3 findings] 4. [Verification — profile with Power Profiler in Instruments after fixes]
Output Limits
If >50 issues in one category: Show top 10, provide total count, list top 3 files If >100 total issues: Summarize by category, show only CRITICAL/HIGH details
False Positives (Not Issues)
- Timers with tolerance already set
- One-shot timers (
)repeats: false - Location with appropriate distanceFilter set
- Push notification handlers (not polling)
- Discretionary network sessions
- Audio session with matching deactivation
- Background modes with matching feature code
- CADisplayLink in active game/animation screens (expected GPU usage)
Field Termination Correlation
Energy anti-patterns surface in the field as system terminations, not slow-draining batteries. When the user has
.ips artifacts, xcsym's pattern_tag flags the termination mode directly:
| pattern_tag | Energy anti-pattern it exposes |
|---|---|
| CPU budget exceeded — tight timer loops, animation leaks, or busy-wait polling (Patterns 1, 4) |
| didn't call (Pattern 5) or exceeded its 30s budget |
| Main-thread hang from a sync I/O/network call blocking rendering (Pattern 6/8) |
| Background memory growth — often a timer/animation retaining state across backgrounding |
xcsym crash --format=summary <path-to-ips>
Use the crashed-thread frames to pinpoint which Phase 1 background-activity owner is the culprit.
Related
For detailed optimization patterns:
axiom-performance (skills/energy.md) skill
For Power Profiler workflows: axiom-performance (skills/energy-ref.md) skill
For timer lifecycle issues: axiom-integration (skills/timer-patterns.md)
For symbolicating CPU/background/watchdog terminations: axiom-tools (skills/xcsym-ref.md)