Created
December 26, 2025 06:48
-
-
Save GenoZhou/a6ba51fba9296889b6d6ca9ea39fe9e6 to your computer and use it in GitHub Desktop.
Mac Power Monitor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Mac Power Monitor - Real-time power consumption dashboard for macOS | |
| Features: | |
| - Dynamic power detection (supports any charger wattage) | |
| - Hardware telemetry: CPU/GPU/ANE (measured), Memory/Storage/Network/Screen (estimated) | |
| - Application power analysis with proportional allocation | |
| - Smart battery status detection (optimized charging, insufficient power warnings) | |
| - Cross-validation using multiple data sources (ioreg, powermetrics, system_profiler) | |
| Usage: sudo python3 main.py | |
| Requirements: macOS, Python 3.x, sudo privileges | |
| Compatibility: Intel Macs and Apple Silicon (M1/M2/M3) | |
| """ | |
| import os | |
| import subprocess | |
| import time | |
| import re | |
| def get_output(command): | |
| try: | |
| return subprocess.check_output(command, shell=True, timeout=5).decode('utf-8') | |
| except: | |
| return "" | |
| def is_apple_silicon(): | |
| cpu_info = get_output("sysctl -n machdep.cpu.brand_string") | |
| return "Apple" in cpu_info | |
| def parse_power_data(): | |
| # 1. Get Charger Information (Hub Input) | |
| charger_raw = get_output("system_profiler SPPowerDataType") | |
| wattage_match = re.search(r"Wattage \(W\): (\d+)", charger_raw) | |
| in_watts = wattage_match.group(1) if wattage_match else "0" | |
| # 2. Get Battery Drain/Charge Rate (Net System Balance) | |
| battery_raw = get_output("ioreg -rw0 -c AppleSmartBattery") | |
| amps = re.search(r"\"Amperage\" = (-?\d+)", battery_raw) | |
| volts = re.search(r"\"Voltage\" = (\d+)", battery_raw) | |
| live_net_watts = 0.0 | |
| if amps and volts: | |
| try: | |
| amp_value = int(amps.group(1)) | |
| volt_value = int(volts.group(1)) | |
| # Handle signed integer overflow: if value is very large (close to 2^64), | |
| # it's likely a negative value represented as unsigned int64 | |
| # Convert to signed int64 if value > 2^63 | |
| if amp_value > 2**63: | |
| amp_value = amp_value - 2**64 | |
| # Validate values are in reasonable range | |
| # Amperage: -10000 to 10000 mA (typical range for MacBook) | |
| # Voltage: 10000 to 20000 mV (typical range for MacBook battery) | |
| if -10000 <= amp_value <= 10000 and 10000 <= volt_value <= 20000: | |
| # (mA * mV) / 1,000,000 = Watts | |
| live_net_watts = (amp_value * volt_value) / 1000000.0 | |
| else: | |
| # Values out of range, set to 0 | |
| live_net_watts = 0.0 | |
| except (ValueError, OverflowError): | |
| # If conversion fails, set to 0 | |
| live_net_watts = 0.0 | |
| # 2a. Get Battery Status Information | |
| is_charging_match = re.search(r"\"IsCharging\" = (Yes|No)", battery_raw) | |
| fully_charged_match = re.search(r"\"FullyCharged\" = (Yes|No)", battery_raw) | |
| external_connected_match = re.search(r"\"ExternalConnected\" = (Yes|No)", battery_raw) | |
| max_capacity_match = re.search(r"\"MaxCapacity\" = (\d+)", battery_raw) | |
| current_capacity_match = re.search(r"\"CurrentCapacity\" = (\d+)", battery_raw) | |
| # Extract PowerTelemetryData (more accurate system power) | |
| system_power_in_match = re.search(r"\"SystemPowerIn\"\s*=\s*(\d+)", battery_raw) | |
| system_power_in = 0.0 | |
| if system_power_in_match: | |
| try: | |
| system_power_in = int(system_power_in_match.group(1)) | |
| # SystemPowerIn is in watts, but verify (if > 1000, might be mW) | |
| if system_power_in > 1000: | |
| system_power_in = system_power_in / 1000.0 | |
| except (ValueError, OverflowError): | |
| pass | |
| # Extract AdapterDetails (more accurate adapter power) | |
| adapter_watts_match = re.search(r"\"AdapterDetails\".*?\"Watts\"\s*=\s*(\d+)", battery_raw) | |
| adapter_watts = 0 | |
| if adapter_watts_match: | |
| try: | |
| adapter_watts = int(adapter_watts_match.group(1)) | |
| except ValueError: | |
| pass | |
| # Fallback to system_profiler value if AdapterDetails not found | |
| if adapter_watts == 0: | |
| adapter_watts = int(in_watts) if in_watts.isdigit() else 0 | |
| # Determine if battery is fully charged | |
| battery_full = False | |
| if fully_charged_match and fully_charged_match.group(1) == "Yes": | |
| battery_full = True | |
| elif max_capacity_match and current_capacity_match: | |
| try: | |
| max_cap = int(max_capacity_match.group(1)) | |
| curr_cap = int(current_capacity_match.group(1)) | |
| if curr_cap >= max_cap * 0.99: # 99%以上认为充满 | |
| battery_full = True | |
| except (ValueError, ZeroDivisionError): | |
| pass | |
| # Calculate battery percentage | |
| battery_percent = 0 | |
| if max_capacity_match and current_capacity_match: | |
| try: | |
| max_cap = int(max_capacity_match.group(1)) | |
| curr_cap = int(current_capacity_match.group(1)) | |
| if max_cap > 0: | |
| battery_percent = int((curr_cap / max_cap) * 100) | |
| except (ValueError, ZeroDivisionError): | |
| pass | |
| # 3. Get Hardware Breakdown (CPU/GPU/System) | |
| # Apple Silicon uses different samplers than Intel | |
| apple_silicon = is_apple_silicon() | |
| sampler = "apple_gpu,cpu_power" if apple_silicon else "cpu_power,gpu_power" | |
| # Add ANE sampler for Apple Silicon if available | |
| if apple_silicon: | |
| sampler += ",ane" | |
| pm_raw = get_output(f"sudo powermetrics -n 1 -i 1 --samplers {sampler}") | |
| # Regex for both Intel and Apple Silicon patterns | |
| cpu_match = re.search(r"(?:CPU|Combined) Power: (\d+) mW", pm_raw) | |
| gpu_match = re.search(r"(?:GPU|GPU) Power: (\d+) mW", pm_raw) | |
| ane_match = re.search(r"ANE Power: (\d+) mW", pm_raw) | |
| # Extract Combined Power (CPU + GPU + ANE) - more accurate hardware total | |
| combined_power_match = re.search(r"Combined Power \(CPU \+ GPU \+ ANE\):\s+(\d+)\s+mW", pm_raw) | |
| hardware_combined_power = 0.0 | |
| if combined_power_match: | |
| try: | |
| hardware_combined_power = int(combined_power_match.group(1)) / 1000.0 | |
| except ValueError: | |
| pass | |
| cpu_w = int(cpu_match.group(1))/1000.0 if cpu_match else 0.0 | |
| gpu_w = int(gpu_match.group(1))/1000.0 if gpu_match else 0.0 | |
| ane_w = int(ane_match.group(1))/1000.0 if ane_match else 0.0 | |
| # Use Combined Power if available, otherwise sum components | |
| if hardware_combined_power > 0: | |
| # Adjust individual components proportionally if Combined Power is available | |
| component_sum = cpu_w + gpu_w + ane_w | |
| if component_sum > 0: | |
| scale_factor = hardware_combined_power / component_sum | |
| cpu_w = cpu_w * scale_factor | |
| gpu_w = gpu_w * scale_factor | |
| ane_w = ane_w * scale_factor | |
| # 4. Get Memory Power Estimate (ESTIMATED) | |
| # Formula: Memory Power = Base Power (0.5W) + Usage-based Power (0-1.5W) | |
| # Calculation: Based on active and wired pages from vm_stat | |
| # Note: This is an estimation. Actual memory power varies by: | |
| # - RAM capacity and type (DDR4/DDR5) | |
| # - Memory frequency | |
| # - Activity level | |
| vm_stat_raw = get_output("vm_stat") | |
| pages_free = re.search(r"Pages free:\s+(\d+)", vm_stat_raw) | |
| pages_active = re.search(r"Pages active:\s+(\d+)", vm_stat_raw) | |
| pages_inactive = re.search(r"Pages inactive:\s+(\d+)", vm_stat_raw) | |
| pages_wired = re.search(r"Pages wired down:\s+(\d+)", vm_stat_raw) | |
| memory_w = 0.5 # Base power (idle) | |
| memory_calc_note = "Base: 0.5W" | |
| if pages_active and pages_wired: | |
| try: | |
| active_pages = int(pages_active.group(1).replace('.', '')) | |
| wired_pages = int(pages_wired.group(1).replace('.', '')) | |
| total_used = active_pages + wired_pages | |
| # Estimation: 0.5W base + usage-based (max 1.5W additional) | |
| # Assuming ~8GB RAM, each page is 4KB | |
| # Normalization: 2M pages ≈ 8GB RAM | |
| memory_usage_ratio = min(1.0, total_used / 2000000.0) | |
| usage_power = memory_usage_ratio * 1.5 | |
| memory_w = 0.5 + usage_power | |
| memory_calc_note = f"Base: 0.5W + Usage: {usage_power:.2f}W (ratio: {memory_usage_ratio:.2f})" | |
| except: | |
| pass | |
| # 5. Get Storage/SSD Power Estimate (ESTIMATED) | |
| # Formula: SSD Power = Idle Power (0.5W) or Active Power (1.5W) | |
| # Calculation: Based on disk activity from iostat | |
| # Note: This is a rough estimation. Actual SSD power varies by: | |
| # - SSD type (SATA/NVMe) | |
| # - Read/write operations | |
| # - Drive capacity and technology | |
| disk_w = 0.5 # Base idle power | |
| disk_calc_note = "Idle: 0.5W" | |
| try: | |
| iostat_raw = get_output("iostat -d 1 1 2>/dev/null") | |
| # Check for disk activity indicators | |
| if iostat_raw and ('%util' in iostat_raw or 'KB/t' in iostat_raw): | |
| # If there's any activity, estimate moderate activity power | |
| disk_w = 1.5 | |
| disk_calc_note = "Active: 1.5W (estimated)" | |
| except: | |
| pass | |
| # 6. Get Network Power Estimate (ESTIMATED) | |
| # Formula: Network Power = WiFi Power (1.5W if active) + Ethernet Power (0.5W if active) | |
| # Calculation: Based on network interface status | |
| # Note: This is an estimation. Actual network power varies by: | |
| # - WiFi/Ethernet chip type | |
| # - Data transfer rate | |
| # - Signal strength (WiFi) | |
| network_w = 0.0 | |
| network_calc_note = "Inactive: 0W" | |
| # Check WiFi status | |
| wifi_raw = get_output("networksetup -getairportpower en0 2>/dev/null || networksetup -getairportpower en1 2>/dev/null") | |
| wifi_active = False | |
| if wifi_raw and "On" in wifi_raw: | |
| network_w += 1.5 # WiFi active power | |
| wifi_active = True | |
| # Check Ethernet status | |
| ethernet_active = False | |
| ethernet_raw = get_output("ifconfig | grep -E '^en[0-9]' | grep -v 'inet' | head -1") | |
| if ethernet_raw: | |
| # Check if ethernet interface is up | |
| ifconfig_raw = get_output("ifconfig | grep -A 1 '^en[0-9]' | grep 'status: active'") | |
| if ifconfig_raw: | |
| network_w += 0.5 # Ethernet active power | |
| ethernet_active = True | |
| if wifi_active and ethernet_active: | |
| network_calc_note = "WiFi: 1.5W + Ethernet: 0.5W" | |
| elif wifi_active: | |
| network_calc_note = "WiFi: 1.5W" | |
| elif ethernet_active: | |
| network_calc_note = "Ethernet: 0.5W" | |
| # 7. Calculate Screen/Display Power (ESTIMATED - residual calculation) | |
| # Formula: Screen Power = Total System Power - (CPU + GPU + ANE + Memory + Disk + Network) | |
| # Note: This is calculated as the residual power after accounting for other components. | |
| # Actual screen power varies by: | |
| # - Display brightness | |
| # - Screen size and resolution | |
| # - Display technology (LCD/OLED) | |
| total_accounted = cpu_w + gpu_w + ane_w + memory_w + disk_w + network_w | |
| # Calculate total system power using more reasonable method | |
| hub_val_int = int(in_watts) if in_watts.isdigit() else 0 | |
| if hub_val_int > 0: | |
| # Has external power source | |
| if live_net_watts > 0: | |
| # Charging: total power = input power - charging power | |
| total_system_watts = hub_val_int - live_net_watts | |
| else: | |
| # Discharging: total power = input power + discharge power | |
| total_system_watts = hub_val_int + abs(live_net_watts) | |
| else: | |
| # No external power: use battery discharge power or fallback | |
| if live_net_watts != 0: | |
| total_system_watts = abs(live_net_watts) | |
| else: | |
| # Fallback: estimate based on hardware components | |
| total_system_watts = cpu_w + gpu_w + ane_w + memory_w + disk_w + network_w + 10.0 | |
| # Sanity check: ensure total system power is in reasonable range (5W to 200W) | |
| total_system_watts = max(5.0, min(200.0, total_system_watts)) | |
| screen_w = max(0.0, total_system_watts - total_accounted) | |
| # 8. Get Top 5 Apps with calculated Estimated Wattage | |
| app_data = [] | |
| top_raw = get_output("top -l 1 -n 5 -o power -stats command,power") | |
| lines = top_raw.strip().split('\n') | |
| for line in lines: | |
| if line and not any(x in line for x in ['COMMAND', 'Processes', 'Load']): | |
| parts = line.split() | |
| if len(parts) >= 2: | |
| name = parts[0][:15] | |
| score_str = parts[-1].replace(',', '') | |
| try: | |
| score = float(score_str) | |
| # Improved conversion: consider system load and actual hardware power | |
| # Base conversion: 100 units ≈ 1W, but adjust based on actual CPU+GPU power | |
| hardware_total = cpu_w + gpu_w | |
| # If hardware is active, scale conversion factor | |
| if hardware_total > 0: | |
| # More accurate: scale based on actual hardware utilization | |
| conversion_factor = max(80.0, min(120.0, 100.0 * (hardware_total / max(1.0, abs(live_net_watts))))) | |
| else: | |
| conversion_factor = 100.0 | |
| est_w = score / conversion_factor | |
| app_data.append((name, score, est_w)) | |
| except ValueError: | |
| continue | |
| # 9. Proportional allocation of hardware power to apps | |
| total_energy_impact = sum(score for _, score, _ in app_data) | |
| hardware_total = cpu_w + gpu_w | |
| # Update app_data with allocated power | |
| updated_app_data = [] | |
| for name, score, est_w in app_data: | |
| if total_energy_impact > 0 and hardware_total > 0: | |
| # Allocate hardware power proportionally | |
| proportion = score / total_energy_impact | |
| allocated_w = hardware_total * proportion | |
| updated_app_data.append((name, score, est_w, allocated_w)) | |
| else: | |
| # Fallback to original conversion | |
| updated_app_data.append((name, score, est_w, est_w)) | |
| app_data = updated_app_data | |
| return { | |
| "in_watts": in_watts, | |
| "live_net_watts": live_net_watts, | |
| "cpu_w": cpu_w, | |
| "gpu_w": gpu_w, | |
| "ane_w": ane_w, | |
| "memory_w": memory_w, | |
| "disk_w": disk_w, | |
| "network_w": network_w, | |
| "screen_w": screen_w, | |
| "apps": app_data, | |
| "is_as": apple_silicon, | |
| "battery_full": battery_full, | |
| "battery_percent": battery_percent, | |
| "external_connected": external_connected_match.group(1) == "Yes" if external_connected_match else False, | |
| "is_charging": is_charging_match.group(1) == "Yes" if is_charging_match else False, | |
| "system_power_in": system_power_in, | |
| "adapter_watts": adapter_watts, | |
| "hardware_combined_power": hardware_combined_power, | |
| "memory_calc_note": memory_calc_note, | |
| "disk_calc_note": disk_calc_note, | |
| "network_calc_note": network_calc_note, | |
| "total_accounted": total_accounted | |
| } | |
| def main(): | |
| print("Starting Universal Mac Power Dashboard...") | |
| print("(Sudo password required for hardware telemetry)") | |
| try: | |
| while True: | |
| data = parse_power_data() | |
| os.system('clear') | |
| arch_label = "APPLE SILICON" if data['is_as'] else "INTEL i9" | |
| # Header | |
| print("┌────────────────────────────────────────────────┐") | |
| print(f"│ {arch_label} POWER DASHBOARD │") | |
| print("└────────────────────────────────────────────────┘") | |
| # Section 1: Supply | |
| # Use adapter_watts if available (more accurate), otherwise fallback to in_watts | |
| adapter_watts = data.get('adapter_watts', 0) | |
| hub_val = adapter_watts if adapter_watts > 0 else int(data['in_watts']) | |
| system_power_in = data.get('system_power_in', 0) | |
| print(f"\n[ POWER SUPPLY ]") | |
| print(f" Input Source: {hub_val}W") | |
| if system_power_in > 0: | |
| print(f" System Power: {system_power_in:.1f}W") | |
| # Display battery percentage if available | |
| battery_percent = data.get('battery_percent', 0) | |
| if battery_percent > 0: | |
| if battery_percent >= 99: | |
| battery_display = "FULL (100%)" | |
| else: | |
| battery_display = f"{battery_percent}%" | |
| print(f" Battery Level: {battery_display}") | |
| # Improved status logic with cross-validation | |
| external_connected = data.get('external_connected', False) | |
| is_charging = data.get('is_charging', False) | |
| battery_full = data.get('battery_full', False) | |
| live_net_watts = data.get('live_net_watts', 0) | |
| # Determine if actually using battery | |
| actually_using_battery = False | |
| if external_connected: | |
| # Has external power | |
| if system_power_in > 0 and hub_val > 0: | |
| # Compare system power with adapter power | |
| # If system power significantly exceeds adapter power, likely using battery | |
| if system_power_in > hub_val * 1.3: # 30% tolerance | |
| if abs(live_net_watts) > 1.0: # More than 1W from battery | |
| actually_using_battery = True | |
| elif abs(live_net_watts) > 1.0: # Fallback: use Amperage if > 1W | |
| actually_using_battery = True | |
| else: | |
| # No external power, definitely using battery | |
| actually_using_battery = True | |
| # Determine status display | |
| if external_connected: | |
| if battery_full: | |
| # Battery full and has external power | |
| if abs(live_net_watts) < 1.0: | |
| status = "🔋 FULLY CHARGED (Charging Paused)" | |
| else: | |
| battery_draw = abs(live_net_watts) | |
| status = f"⚠️ FULLY CHARGED, INSUFFICIENT POWER ({hub_val}W input, {battery_draw:.1f}W from battery)" | |
| elif is_charging: | |
| # Charging | |
| status = "🔋 CHARGING" | |
| elif not is_charging and battery_percent > 80: | |
| # Not charging but battery > 80% - likely paused charging (optimized charging) | |
| if abs(live_net_watts) < 1.0: | |
| status = "⏸️ CHARGING PAUSED (Optimized)" | |
| elif actually_using_battery: | |
| battery_draw = abs(live_net_watts) | |
| status = f"⚠️ INSUFFICIENT POWER ({hub_val}W input, {battery_draw:.1f}W from battery)" | |
| else: | |
| status = "⚖️ BALANCED" | |
| elif actually_using_battery: | |
| # Using battery due to insufficient power | |
| battery_draw = abs(live_net_watts) | |
| status = f"⚠️ INSUFFICIENT POWER ({hub_val}W input, {battery_draw:.1f}W from battery)" | |
| else: | |
| # Balanced or small measurement error | |
| status = "⚖️ BALANCED" | |
| else: | |
| # No external power | |
| if battery_full: | |
| status = "🔋 FULLY CHARGED (On Battery)" | |
| elif live_net_watts < 0: | |
| status = "🔻 DISCHARGING" | |
| else: | |
| status = "⚖️ BALANCED" | |
| print(f" Supply Status: {status}") | |
| # Section 2: Real-time Balance | |
| print(f"\n[ TOTAL SYSTEM CONSUMPTION ]") | |
| # Use SystemPowerIn if available (more accurate), otherwise calculate | |
| if system_power_in > 0: | |
| total_usage = system_power_in | |
| elif hub_val > 0: | |
| # Has external power: system consumption = input power - charging power | |
| if data['live_net_watts'] > 0: | |
| total_usage = hub_val - data['live_net_watts'] | |
| else: | |
| total_usage = hub_val + abs(data['live_net_watts']) | |
| # Sanity check | |
| if total_usage < 0 or total_usage > 200: | |
| total_usage = hub_val * 0.8 # Fallback: assume 80% efficiency | |
| total_usage = max(0, total_usage) | |
| else: | |
| # No external power: use battery discharge | |
| total_usage = abs(data['live_net_watts']) if data['live_net_watts'] < 0 else 0.0 | |
| if total_usage > 200: | |
| total_usage = 0.0 | |
| print(f" Total Usage: {total_usage:.2f}W") | |
| # Energy state display | |
| if external_connected: | |
| if is_charging: | |
| print(f" Energy State: 🔋 CHARGING (+{data['live_net_watts']:.1f}W)") | |
| elif actually_using_battery: | |
| battery_draw = abs(data['live_net_watts']) | |
| print(f" Energy State: ⚠️ USING BATTERY ({battery_draw:.1f}W from battery)") | |
| else: | |
| print(f" Energy State: ⚖️ BALANCED (Adapter Power Sufficient)") | |
| else: | |
| if data['live_net_watts'] < 0: | |
| print(f" Energy State: 🔻 DISCHARGING ({abs(data['live_net_watts']):.1f}W)") | |
| else: | |
| print(f" Energy State: ⚖️ BALANCED") | |
| # Section 3: Hardware Breakdown | |
| print(f"\n[ HARDWARE TELEMETRY ]") | |
| print(f" Processor (CPU): {data['cpu_w']:.2f}W (measured)") | |
| print(f" Graphics (GPU): {data['gpu_w']:.2f}W (measured)") | |
| if data['is_as'] and data['ane_w'] > 0: | |
| print(f" Neural Engine (ANE): {data['ane_w']:.2f}W (measured)") | |
| # Format calculation notes more concisely | |
| memory_note = data.get('memory_calc_note', '') | |
| if 'Usage:' in memory_note: | |
| # Extract usage value | |
| usage_match = re.search(r'Usage: ([\d.]+)W', memory_note) | |
| if usage_match: | |
| memory_note = f"est: 0.5W+{usage_match.group(1)}W" | |
| else: | |
| memory_note = "est" | |
| else: | |
| memory_note = "est" | |
| print(f" Memory (RAM): {data['memory_w']:.2f}W ({memory_note})") | |
| # Format disk note | |
| disk_note = "est: active" if 'Active' in data.get('disk_calc_note', '') else "est: idle" | |
| print(f" Storage (SSD): {data['disk_w']:.2f}W ({disk_note})") | |
| # Format network note | |
| network_calc = data.get('network_calc_note', '') | |
| if 'WiFi' in network_calc and 'Ethernet' in network_calc: | |
| network_note = "est: WiFi+Eth" | |
| elif 'WiFi' in network_calc: | |
| network_note = "est: WiFi" | |
| elif 'Ethernet' in network_calc: | |
| network_note = "est: Eth" | |
| else: | |
| network_note = "est" | |
| print(f" Network: {data['network_w']:.2f}W ({network_note})") | |
| print(f" Display (Screen): {data['screen_w']:.2f}W (est: residual)") | |
| # Show component sum vs system total | |
| total_accounted = data.get('total_accounted', 0) + data['screen_w'] | |
| system_total = data.get('system_power_in', 0) if data.get('system_power_in', 0) > 0 else total_usage | |
| if system_total > 0: | |
| difference = system_total - total_accounted | |
| difference_pct = (difference / system_total * 100) if system_total > 0 else 0 | |
| print(f"\n Component Sum: {total_accounted:.2f}W") | |
| print(f" System Total: {system_total:.2f}W") | |
| if abs(difference) > 0.5: # Only show if significant difference | |
| print(f" Difference: {difference:+.2f}W ({difference_pct:+.1f}%)") | |
| print(f" Note: Difference may include unmeasured components, measurement") | |
| print(f" errors, or system overhead (power conversion, cooling, etc.)") | |
| # Section 4: App Breakdown | |
| print(f"\n[ TOP APPS BY WATTAGE (EST.) ]") | |
| print(f" {'APP NAME':<18} {'ENERGY IMPACT':<15} {'EST. WATTS':<12} {'ALLOCATED W'}") | |
| print(f" ─────────────────────────────────────────────────────────────") | |
| for app_item in data['apps']: | |
| if len(app_item) == 4: | |
| name, score, est_w, allocated_w = app_item | |
| print(f" {name:<18} {score:<15.1f} {est_w:<12.2f} {allocated_w:.2f}W") | |
| else: | |
| # Fallback for old format | |
| name, score, est_w = app_item | |
| print(f" {name:<18} {score:<15.1f} {est_w:<12.2f} {est_w:.2f}W") | |
| print("\n" + "─"*50) | |
| print(" Press Ctrl+C to Exit") | |
| time.sleep(3) | |
| except KeyboardInterrupt: | |
| print("\nExiting...") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment