Get the FREE Ultimate OpenClaw Setup Guide →

ax-energy

npx machina-cli add skill Kasempiternal/axiom-v2/ax-energy --openclaw
Files (1)
SKILL.md
15.7 KB

Energy Optimization

Quick Patterns

Power Profiler Workflow (5 min)

1. Connect iPhone wirelessly to Xcode (wireless debugging)
2. Xcode > Product > Profile (Cmd+I)
3. Select Blank template
4. Click "+" > Add "Power Profiler" instrument
5. Optional: Add "CPU Profiler" for correlation
6. Click Record, use app normally for 2-3 minutes
7. Click Stop
8. Expand Power Profiler track, examine per-app lanes:
   - CPU Power Impact: Computation, timers, parsing
   - GPU Power Impact: Animations, blur, Metal
   - Display Power Impact: Brightness, content type
   - Network Power Impact: Requests, downloads, polling

Why wireless: When device is charging via cable, power metrics show 0.

Timer Tolerance (1 min fix)

// BAD: No tolerance, prevents system batching
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in self.updateUI() }

// GOOD: 10% tolerance for system batching
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in self.updateUI() }
timer.tolerance = 0.1

// BETTER: Combine Timer (auto-cleanup)
Timer.publish(every: 1.0, tolerance: 0.1, on: .main, in: .default)
    .autoconnect()
    .sink { [weak self] _ in self?.refresh() }
    .store(in: &cancellables)

// BEST: Event-driven, no timer
NotificationCenter.default.publisher(for: .dataDidUpdate)
    .sink { [weak self] _ in self?.updateUI() }
    .store(in: &cancellables)

Push vs Poll (100x efficiency)

// BAD: Polling every 5 seconds (radio active 100% of time)
Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
    self?.fetchLatestData()
}

// GOOD: Server pushes when data changes
func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    Task {
        do {
            let hasNewData = try await fetchLatestData()
            completionHandler(hasNewData ? .newData : .noData)
        } catch { completionHandler(.failed) }
    }
}

Location Accuracy Reduction

// BAD: Best accuracy drains GPS constantly
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()

// GOOD: Appropriate accuracy + distance filter
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.distanceFilter = 100
locationManager.startMonitoringSignificantLocationChanges()

// BEST: iOS 26+ async with stationary detection
for try await update in CLLocationUpdate.liveUpdates() {
    if update.stationary { break }  // System pauses automatically
    handleLocation(update.location)
}

Lazy Loading (eliminates CPU spikes)

// BAD: Creates ALL views upfront (CPU spike from 1 to 21)
VStack { ForEach(videos) { VideoCardView(video: $0) } }

// GOOD: Creates only visible views (CPU impact 4.3)
LazyVStack { ForEach(videos) { VideoCardView(video: $0) } }

Background Task Completion

// BAD: Long-running, never ends task
backgroundTask = application.beginBackgroundTask { }
performLongOperation()

// GOOD: End immediately when done
backgroundTask = application.beginBackgroundTask(withName: "Save State") { [weak self] in
    self?.saveProgress()
    if let task = self?.backgroundTask { application.endBackgroundTask(task) }
    self?.backgroundTask = .invalid
}
saveEssentialState()
application.endBackgroundTask(backgroundTask)
backgroundTask = .invalid

Decision Tree

Identify Dominant Subsystem

User reports energy issue?
|
|-- Run Power Profiler (15 min)
|   |
|   |-- CPU Power Impact dominant?
|   |   |-- Continuous? -> Timer leak, polling, tight loop
|   |   |-- Spikes during actions? -> Eager loading, repeated parsing
|   |   +-- High background CPU? -> Location, BGTasks, audio session
|   |
|   |-- Network Power Impact dominant?
|   |   |-- Many small requests? -> Batch into fewer large requests
|   |   |-- Regular intervals? -> Polling pattern, convert to push
|   |   +-- Large foreground downloads? -> Use discretionary background URLSession
|   |
|   |-- GPU Power Impact dominant?
|   |   |-- Continuous animations? -> Stop when view not visible
|   |   |-- Blur effects? -> Remove or use solid colors
|   |   +-- High frame rate? -> Audit secondary frame rates
|   |
|   |-- Display Power Impact dominant?
|   |   +-- Light backgrounds on OLED? -> Dark Mode (up to 70% savings)
|   |
|   +-- Location (shown in CPU + location icon)?
|       |-- Continuous updates? -> Significant-change monitoring
|       |-- High accuracy? -> Reduce to hundredMeters
|       +-- Background location? -> Evaluate if truly needed

Symptom: App at Top of Battery Settings

App at top of Battery Settings?
|
|-- Step 1: Run Power Profiler (15 min)
|   |-- CPU high? -> Check timers, polling, eager loading
|   |-- Network high? -> Check request frequency, batching
|   |-- GPU high? -> Check animations, blur effects
|   +-- Display high? -> Check Dark Mode support
|
+-- Step 2: Check background section
    |-- High background time?
    |   |-- Location icon visible? -> Continuous location
    |   |-- Audio active? -> Session not deactivated
    |   +-- BGTasks running long? -> Not completing promptly
    +-- Background time appropriate? -> Issue is foreground

Symptom: Device Gets Hot

Device gets hot?
|
|-- During specific action?
|   |-- Video/camera? -> Check encoding efficiency, release session
|   |-- Scroll/animation? -> GPU effects, frame rate too high
|   +-- Data processing? -> Cache parsed results, move to background
|
|-- During normal use? -> Run Power Profiler
|   |-- CPU high continuously -> Timer, polling, tight loop
|   |-- GPU high continuously -> Animation leak
|   +-- Network high continuously -> Polling pattern
|
+-- Only in background?
    |-- Location continuous -> Reduce accuracy, stop when done
    |-- Audio session active -> Deactivate when not playing
    +-- BGTask too long -> Complete faster, requiresExternalPower

Symptom: Background Battery Drain

High background battery?
|
|-- Check Info.plist background modes
|   |-- "location" enabled? -> Need it? Use significant-change, lowest accuracy
|   |-- "audio" enabled? -> Not playing? Deactivate session
|   |-- "fetch" enabled? -> earliestBeginDate reasonable?
|   +-- "remote-notification" -> Check handler efficiency
|
|-- Check BGTaskScheduler
|   |-- Refresh too frequent? -> Increase earliestBeginDate
|   |-- Processing without power? -> Add requiresExternalPower = true
|   +-- Not completing? -> Always call setTaskCompleted
|
+-- Check beginBackgroundTask
    |-- endBackgroundTask called promptly?
    +-- Multiple overlapping tasks? -> Track IDs, end each

Symptom: High Cellular Energy

High drain on cellular?
|
|-- URLSession configuration
|   |-- allowsExpensiveNetworkAccess = true? -> Set false for non-urgent
|   |-- isDiscretionary = false? -> Set true for background downloads
|   +-- waitsForConnectivity = false? -> Set true to avoid retries
|
|-- Request patterns
|   |-- Many small requests? -> Batch
|   |-- Polling? -> Push notifications
|   +-- Large foreground downloads? -> Background URLSession
|
+-- Low Data Mode
    +-- Check allowsConstrainedNetworkAccess, isLowDataModeEnabled

Anti-Patterns

Polling instead of push. Polling every 5 seconds keeps radio active 100% of the time. Push notifications activate radio only when data changes. Polling uses 100x more energy.

Best accuracy for non-navigation. kCLLocationAccuracyBest uses GPS + WiFi + cellular triangulation. 95% of apps only need kCLLocationAccuracyHundredMeters.

Animations running when not visible. GPU continues rendering offscreen. Stop in viewWillDisappear, resume in viewWillAppear.

Timer without tolerance. Forces exact wake schedule, prevents system batching. Set tolerance to at least 10% of interval.

VStack with large ForEach. Creates ALL views upfront. Use LazyVStack for on-demand creation.

Repeated parsing in frequently-called methods. JSON parsed on every location update or timer fire. Cache with lazy var.

Audio session never deactivated. Hardware stays powered. Call setActive(false, options: .notifyOthersOnDeactivation) when playback stops.

beginBackgroundTask without prompt end. Running until expiration wastes energy. Call endBackgroundTask immediately when work completes.

Ship now, optimize later. Battery drain is immediately visible. Users see your app at top of Battery Settings on day one. A 15-minute Power Profiler session before launch catches major issues.


Deep Patterns

Background Execution (EMRCA)

Principles from WWDC25-227:

  • Efficient: Lightweight, purpose-driven tasks
  • Minimal: Keep background work to minimum
  • Resilient: Save incremental progress, handle expiration
  • Courteous: Honor user preferences and system conditions
  • Adaptive: Work with system priorities, don't fight constraints

BGProcessingTask (Long Operations)

func scheduleBackgroundProcessing() {
    let request = BGProcessingTaskRequest(identifier: "com.app.maintenance")
    request.requiresNetworkConnectivity = true
    request.requiresExternalPower = true  // Only when charging
    try? BGTaskScheduler.shared.submit(request)
}

BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.app.maintenance", using: nil) { task in
    self.handleMaintenance(task: task as! BGProcessingTask)
}

iOS 26+ BGContinuedProcessingTask

Continue user-initiated tasks with progress UI:

let request = BGContinuedProcessingTaskRequest(
    identifier: "com.app.export",
    title: "Exporting Photos",
    subtitle: "0 of 100 photos"
)
try? BGTaskScheduler.shared.submit(request)

BGTaskScheduler.shared.register("com.app.export") { task in
    let continuedTask = task as! BGContinuedProcessingTask
    continuedTask.progress.totalUnitCount = 100

    var shouldContinue = true
    continuedTask.expirationHandler = { shouldContinue = false }

    for i in 0..<100 {
        guard shouldContinue else { break }
        performExportStep(i)
        continuedTask.progress.completedUnitCount = Int64(i + 1)
    }
    continuedTask.setTaskCompleted(success: shouldContinue)
}

Frame Rate Auditing

Secondary animations at higher frame rates than needed waste GPU power. Up to 20% battery savings by aligning frame rates.

let displayLink = CADisplayLink(target: self, selector: #selector(updateAnimation))
displayLink.preferredFrameRateRange = CAFrameRateRange(
    minimum: 10, maximum: 30, preferred: 30  // Match primary content
)
displayLink.add(to: .current, forMode: .default)

Low Power Mode Response

if ProcessInfo.processInfo.isLowPowerModeEnabled {
    reduceEnergyUsage()
}

NotificationCenter.default.publisher(for: .NSProcessInfoPowerStateDidChange)
    .sink { [weak self] _ in
        if ProcessInfo.processInfo.isLowPowerModeEnabled {
            self?.reduceEnergyUsage()
        } else {
            self?.restoreNormalOperation()
        }
    }
    .store(in: &cancellables)

func reduceEnergyUsage() {
    // Increase timer intervals
    // Reduce animation frame rates
    // Defer network requests
    // Stop non-critical location updates
}

On-Device Power Profiler (Without Mac)

From WWDC25-226: Capture traces in real-world conditions.

1. Settings > Privacy & Security > Developer Mode > Enable
2. Settings > Developer > Performance Trace > Enable
3. Control Center > Add "Performance Trace" shortcut
4. Swipe down > Tap icon > Start (records up to 10 hours)
5. Share trace via AirDrop to Mac

Production Monitoring with MetricKit

class EnergyMetricsManager: NSObject, MXMetricManagerSubscriber {
    func didReceive(_ payloads: [MXMetricPayload]) {
        for payload in payloads {
            if let cpu = payload.cpuMetrics {
                logMetric("foreground_cpu", value: cpu.cumulativeCPUTime)
            }
            if let location = payload.locationActivityMetrics {
                logMetric("background_location", value: location.cumulativeBackgroundLocationTime)
            }
        }
    }
}

Xcode Organizer > Battery Usage: Foreground vs background energy breakdown, category breakdown, version comparison.

Real-World Examples

Video app eager loading (WWDC25-226): VStack creating all thumbnails upfront caused CPU power impact 21. Changed to LazyVStack, dropped to 4.3.

Location-based suggestions (WWDC25-226): JSON parsed on every location update during commute. Cached with lazy var, eliminated CPU spikes.

Music app audio session: Session never deactivated after stop. Added setActive(false, options: .notifyOthersOnDeactivation), eliminated background drain.


Diagnostics

30-Second Check

  • Device plugged in? (Power metrics show 0)
  • Debug build? (Less optimized than release)
  • Low Power Mode on? (May affect measurements)

5-Minute Check (Power Profiler)

  • Which subsystem is dominant? (CPU/GPU/Network/Display)
  • Sustained or spiky?
  • Foreground or background?

15-Minute Investigation

  • If CPU: Run Time Profiler to identify function
  • If Network: Check request frequency and size
  • If GPU: Check animation frame rates
  • If Background: Check Info.plist modes

Common Quick Fixes

FindingFixTime
Timer without tolerance.tolerance = 0.11 min
VStack with large ForEachChange to LazyVStack1 min
allowsExpensiveNetworkAccess = trueSet false1 min
Missing stopUpdatingLocationAdd stop call2 min
No Dark ModeAdd asset variants30 min
Audio session always activesetActive(false)5 min

Background Drain Patterns

PatternPower Profiler SignatureFix
Continuous locationCPU lane + location iconsignificant-change
Audio session leakCPU lane steadysetActive(false)
Timer not invalidatedCPU spikes at intervalsinvalidate in background
Polling from backgroundNetwork lane at intervalsPush notifications
BGTask too longCPU sustainedFaster completion

Audit Checklists

Timers: Tolerance >= 10%, invalidated when done, using Combine, no polling that could be push, stopped on background.

Network: Requests batched, discretionary for non-urgent, waitsForConnectivity, allowsExpensiveNetworkAccess=false for deferrable, push not poll.

Location: Appropriate accuracy (not Best unless navigation), distanceFilter set, stopped when done, significant-change for background, background justified.

Background: endBackgroundTask called promptly, BGProcessingTask uses requiresExternalPower, background modes limited, audio deactivated, EMRCA followed.

Display/GPU: Dark Mode supported, animations stop when hidden, frame rates appropriate, blur minimized, Metal has frame limiting.

Disk I/O: Writes batched, SQLite WAL mode, no rapid create/delete, SwiftData/Core Data for frequent updates.

Key Energy Savings

OptimizationPotential Savings
Dark Mode on OLEDUp to 70% display power
Frame rate alignmentUp to 20% GPU power
Push vs poll100x network efficiency
Location accuracy reduction50-90% GPS power
Timer toleranceSignificant CPU savings
Lazy loadingEliminates startup CPU spikes

Related

WWDC: 2019-417, 2019-707, 2020-10095, 2022-10083, 2025-226, 2025-227

Docs: /documentation/backgroundtasks, /documentation/corelocation, /documentation/metrickit

Skills: ax-energy-ref, ax-performance, ax-networking

Source

git clone https://github.com/Kasempiternal/axiom-v2/blob/main/axiom-plugin/skills/ax-energy/SKILL.mdView on GitHub

Overview

ax-energy helps diagnose power drains by profiling subsystems (CPU, GPU, Network, Location, Display) and observing background execution patterns. It guides engineers to reduce energy consumption with practical patterns like push-based updates, reduced location accuracy, and lazy loading.

How This Skill Works

Use Xcode's Power Profiler to measure per-app energy impact across subsystems (CPU, GPU, Display, Network). Combine profiling with established patterns (timer tolerance, push vs poll, lazy loading, proper background task handling) and follow the decision tree to identify the dominant energy drain.

When to Use It

  • App drains battery or device gets hot
  • Auditing power consumption in QA or production
  • Investigating CPU/GPU/Network/Location/Display subsystems for energy impact
  • Tuning timers, polling, and background tasks to reduce energy usage
  • Optimizing location accuracy and background updates for efficiency

Quick Start

  1. Step 1: Connect iPhone wirelessly to Xcode and enable Power Profiler
  2. Step 2: In Xcode, Profile with the Power Profiler (and optionally CPU Profiler) and run the app for 2-3 minutes
  3. Step 3: Expand the Power Profiler track and examine per-app lanes: CPU, GPU, Display, and Network Power Impact

Best Practices

  • Run Power Profiler and inspect per-app lanes (CPU, GPU, Display, Network) to identify dominant drains
  • Apply timer tolerance and migrate from fixed timers to event-driven updates
  • Prefer server push over polling to reduce radio activity and energy use
  • Choose appropriate location accuracy and update frequency (e.g., significant-change or async updates)
  • Use lazy loading to minimize CPU spikes and end background tasks promptly when done

Example Use Cases

  • Power Profiler workflow to reveal CPU, GPU, Display, and Network power impact
  • Implementing timer tolerance and replacing fixed timers with publishers
  • Switching from periodic polling to server-push updates for efficiency
  • Reducing location energy use with appropriate accuracy and distance filters
  • Replacing bulk view creation with on-demand rendering (LazyVStack) and timely background completion

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers