@@ -39,21 +39,13 @@ func PrivilegedMetrics(ctx context.Context) []domain.Metric {
3939 out = mergeByName (out , linuxPrivileged (ctx ))
4040 // Windows privileged sources (WMI thermal zones); no-op off Windows.
4141 out = mergeByName (out , windowsPrivileged (ctx ))
42- // gpuSeparate marks the GPU as its own power rail (a discrete card, or the GB10
43- // superchip where the Blackwell GPU is metered apart from the Grace CPU). Only
44- // nvidia-smi reports such a rail here; an integrated GPU (Apple, AMD APU) is
45- // already inside the CPU package, so its power must not be added to the total.
46- gpuSeparate := false
4742 if nv , present , err := runNvidiaSMI (ctx ); present {
4843 if err != nil {
4944 // nvidia-smi is installed but broke (e.g. driver/library mismatch after
5045 // an un-rebooted driver upgrade) — surface why instead of blanking GPU.
5146 out = mergeByName (out , nvidiaErrorMetrics (nv ))
5247 } else {
5348 ms := parseNvidiaSMI (nv )
54- if hasName (ms , "power.gpu" ) {
55- gpuSeparate = true
56- }
5749 // Unified-memory NVIDIA (GB10 Grace-Blackwell) has no aggregate VRAM
5850 // counter — nvidia-smi memory.* reads [N/A]. Derive gpu.vram from the
5951 // per-process compute-apps memory over the system RAM total.
@@ -73,38 +65,30 @@ func PrivilegedMetrics(ctx context.Context) []domain.Metric {
7365 // only add names not already set, so an NVIDIA or Apple reading is never
7466 // shadowed, and a non-AMD host contributes nothing.
7567 out = mergeByName (out , amdGPU (ctx ))
76- out = withTotalPower (out , runtime . GOOS , gpuSeparate )
68+ out = withTotalPower (out )
7769 out = ensureNPUUtil (out )
7870 if ! hasOK (out ) {
7971 return []domain.Metric {
80- {Name : "power.pkg " , Status : domain .StatusUnavailable , Detail : "no power source" },
72+ {Name : "power.cpu " , Status : domain .StatusUnavailable , Detail : "no power source" },
8173 {Name : "gpu.util" , Status : domain .StatusUnavailable , Detail : "no gpu source" },
8274 }
8375 }
8476 return out
8577}
8678
87- // withTotalPower appends a source-aware power.total so the panel can headline
88- // real whole-machine draw instead of just the CPU package. It must never
89- // double-count: Apple's power.pkg is SMC PSTR (already whole-system), and an
90- // integrated GPU is already inside the CPU package — only a separate GPU rail
91- // (discrete NVIDIA, or the GB10 Grace+Blackwell split) is added.
92- func withTotalPower (ms []domain.Metric , goos string , gpuSeparate bool ) []domain.Metric {
93- pkg , hasPkg := okGauge (ms , "power.pkg" )
94- gpu , hasGPU := okGauge (ms , "power.gpu" )
95- npu , _ := okGauge (ms , "power.npu" )
96- var total float64
97- switch {
98- case goos == "darwin" :
99- total = pkg // SMC PSTR is already whole-system
100- case gpuSeparate :
101- total = pkg + gpu + npu // CPU package + a discrete/superchip GPU rail
102- default :
103- total = pkg // integrated GPU is already counted inside the package
104- if ! hasPkg && hasGPU {
105- total = gpu
106- }
79+ // withTotalPower appends the whole-machine power.total so the panel can headline
80+ // real draw. On Apple it is already provided directly (SMC PSTR is whole-system),
81+ // so this leaves it untouched. Everywhere else power.cpu (the RAPL package) and
82+ // power.gpu are physically separate rails — the same split btop/top show — so the
83+ // total is their sum plus any NPU rail.
84+ func withTotalPower (ms []domain.Metric ) []domain.Metric {
85+ if hasName (ms , "power.total" ) {
86+ return ms
10787 }
88+ cpu , _ := okGauge (ms , "power.cpu" )
89+ gpu , _ := okGauge (ms , "power.gpu" )
90+ npu , _ := okGauge (ms , "power.npu" )
91+ total := cpu + gpu + npu
10892 if total <= 0 {
10993 return ms
11094 }
@@ -138,11 +122,11 @@ func powerMetric(name string, w float64) domain.Metric {
138122
139123// assembleApplePower builds the macOS power/util metrics from the available
140124// sources. Per-domain CPU/GPU/ANE power and GPU utilisation come from the
141- // IOReport energy counters when present. Package power has a strict precedence:
142- // SMC PSTR ("System Total Power") first, then powermetrics, then the IOReport
143- // per-domain sum as a last resort. The ordering matters: on Apple Silicon
144- // Pro/Max chips IOReport reports 0 for CPU/ANE and only a sub-watt GPU figure,
145- // so the energy-sum is a phantom — it must never shadow a real SMC or
125+ // IOReport energy counters when present. The whole-system total has a strict
126+ // precedence: SMC PSTR ("System Total Power") first, then powermetrics, then the
127+ // IOReport per-domain sum as a last resort. The ordering matters: on Apple
128+ // Silicon Pro/Max chips IOReport reports 0 for CPU/ANE and only a sub-watt GPU
129+ // figure, so the energy-sum is a phantom — it must never shadow a real SMC or
146130// powermetrics reading. pm is the parsed powermetrics output (may be nil).
147131func assembleApplePower (cpu , gpu , ane , gpuUtil float64 , ioOK bool , smcPkg float64 , smcOK bool , pm []domain.Metric ) []domain.Metric {
148132 var out []domain.Metric
@@ -160,16 +144,16 @@ func assembleApplePower(cpu, gpu, ane, gpuUtil float64, ioOK bool, smcPkg float6
160144 out = append (out , domain.Metric {Name : "gpu.util" , Unit : "percent" , Status : domain .StatusOK , Gauge : gpuUtil })
161145 }
162146 }
163- // SMC PSTR is the authoritative whole-system figure and needs no root.
147+ // SMC PSTR is the authoritative whole-system total and needs no root.
164148 if smcOK && smcPkg > 0 {
165- out = append (out , powerMetric ("power.pkg " , smcPkg ))
149+ out = append (out , powerMetric ("power.total " , smcPkg ))
166150 }
167- // powermetrics fills any name not already set (package when SMC is absent,
151+ // powermetrics fills any name not already set (the total when SMC is absent,
168152 // plus CPU/GPU/util gaps).
169153 out = mergeByName (out , pm )
170154 // Last resort: the IOReport per-domain sum, only when nothing better gave a
171- // package figure.
172- if ! hasName (out , "power.pkg " ) {
155+ // whole-system figure.
156+ if ! hasName (out , "power.total " ) {
173157 sum := 0.0
174158 if ioOK {
175159 for _ , w := range []float64 {cpu , gpu , ane } {
@@ -179,7 +163,7 @@ func assembleApplePower(cpu, gpu, ane, gpuUtil float64, ioOK bool, smcPkg float6
179163 }
180164 }
181165 if sum > 0 {
182- out = append (out , powerMetric ("power.pkg " , sum ))
166+ out = append (out , powerMetric ("power.total " , sum ))
183167 }
184168 }
185169 // Apple Silicon is unified memory — there is no discrete VRAM to read. Report
0 commit comments