@@ -28,10 +28,13 @@ func (p *CPUProvider) Meta() SensorMeta {
2828 Category : "system" ,
2929 Platforms : []string {"linux" },
3030 Fields : []FieldDef {
31+ {Name : "Name" , JSONName : "name" , TSName : "name" , Type : FieldTypeString , Unit : "" , Description : "CPU model name" },
3132 {Name : "Load" , JSONName : "load" , TSName : "load" , Type : FieldTypeNumber , Unit : "%" , Description : "CPU load percentage" },
3233 {Name : "Temperature" , JSONName : "temperature" , TSName : "temperature" , Type : FieldTypeOptionalNumber , Unit : "°C" , Description : "CPU temperature" },
3334 {Name : "Frequency" , JSONName : "frequency" , TSName : "frequency" , Type : FieldTypeOptionalNumber , Unit : "MHz" , Description : "CPU frequency" },
3435 {Name : "Cores" , JSONName : "cores" , TSName : "cores" , Type : FieldTypeNumber , Unit : "" , Description : "Number of CPU cores" },
36+ {Name : "Power" , JSONName : "power" , TSName : "power" , Type : FieldTypeOptionalNumber , Unit : "W" , Description : "CPU package power" },
37+ {Name : "Voltage" , JSONName : "voltage" , TSName : "voltage" , Type : FieldTypeOptionalNumber , Unit : "V" , Description : "CPU core voltage" },
3538 },
3639 }
3740}
@@ -46,6 +49,9 @@ func (p *CPUProvider) Available() bool {
4649func (p * CPUProvider ) Collect (state * CollectorState ) map [string ]interface {} {
4750 result := make (map [string ]interface {})
4851
52+ // Get CPU name
53+ result ["name" ] = p .collectName ()
54+
4955 // Get CPU load
5056 load := p .collectLoad (state )
5157 result ["load" ] = load
@@ -63,6 +69,16 @@ func (p *CPUProvider) Collect(state *CollectorState) map[string]interface{} {
6369 // Get core count
6470 result ["cores" ] = runtime .NumCPU ()
6571
72+ // Get power (from RAPL or hwmon)
73+ if power := p .collectPower (); power != nil {
74+ result ["power" ] = * power
75+ }
76+
77+ // Get voltage (from hwmon)
78+ if voltage := p .collectVoltage (); voltage != nil {
79+ result ["voltage" ] = * voltage
80+ }
81+
6682 return result
6783}
6884
@@ -217,3 +233,110 @@ func (p *CPUProvider) collectFrequency() *float64 {
217233
218234 return nil
219235}
236+
237+ func (p * CPUProvider ) collectName () string {
238+ file , err := os .Open ("/proc/cpuinfo" )
239+ if err != nil {
240+ return "Unknown CPU"
241+ }
242+ defer file .Close ()
243+
244+ scanner := bufio .NewScanner (file )
245+ for scanner .Scan () {
246+ line := scanner .Text ()
247+ if strings .HasPrefix (line , "model name" ) {
248+ parts := strings .SplitN (line , ":" , 2 )
249+ if len (parts ) >= 2 {
250+ return strings .TrimSpace (parts [1 ])
251+ }
252+ }
253+ }
254+
255+ return "Unknown CPU"
256+ }
257+
258+ func (p * CPUProvider ) collectPower () * float64 {
259+ // Try RAPL (Running Average Power Limit) for Intel/AMD
260+ raplPaths , _ := filepath .Glob ("/sys/class/powercap/intel-rapl:0/energy_uj" )
261+ for _ , raplPath := range raplPaths {
262+ // RAPL gives cumulative energy, we'd need to track delta
263+ // For now, try hwmon power sensors instead
264+ _ = raplPath
265+ }
266+
267+ // Try hwmon power sensors (zenpower, k10temp with power)
268+ hwmonPaths , _ := filepath .Glob ("/sys/class/hwmon/hwmon*/name" )
269+ for _ , namePath := range hwmonPaths {
270+ nameBytes , err := os .ReadFile (namePath )
271+ if err != nil {
272+ continue
273+ }
274+ name := strings .TrimSpace (string (nameBytes ))
275+
276+ // Look for CPU power sensors
277+ if strings .Contains (name , "zenpower" ) || strings .Contains (name , "k10temp" ) {
278+ dir := filepath .Dir (namePath )
279+ powerFiles , _ := filepath .Glob (filepath .Join (dir , "power*_input" ))
280+ for _ , powerFile := range powerFiles {
281+ data , err := os .ReadFile (powerFile )
282+ if err != nil {
283+ continue
284+ }
285+ microW , err := strconv .ParseInt (strings .TrimSpace (string (data )), 10 , 64 )
286+ if err != nil {
287+ continue
288+ }
289+ power := float64 (microW ) / 1000000.0
290+ return & power
291+ }
292+ }
293+ }
294+
295+ return nil
296+ }
297+
298+ func (p * CPUProvider ) collectVoltage () * float64 {
299+ // Try hwmon voltage sensors
300+ hwmonPaths , _ := filepath .Glob ("/sys/class/hwmon/hwmon*/name" )
301+ for _ , namePath := range hwmonPaths {
302+ nameBytes , err := os .ReadFile (namePath )
303+ if err != nil {
304+ continue
305+ }
306+ name := strings .TrimSpace (string (nameBytes ))
307+
308+ // Look for CPU voltage sensors (zenpower, nct6687, etc.)
309+ if strings .Contains (name , "zenpower" ) || strings .Contains (name , "nct6" ) {
310+ dir := filepath .Dir (namePath )
311+
312+ // Try labeled voltage inputs first
313+ inLabels , _ := filepath .Glob (filepath .Join (dir , "in*_label" ))
314+ for _ , labelPath := range inLabels {
315+ labelBytes , err := os .ReadFile (labelPath )
316+ if err != nil {
317+ continue
318+ }
319+ label := strings .TrimSpace (string (labelBytes ))
320+
321+ // Look for CPU core voltage labels
322+ if strings .Contains (strings .ToLower (label ), "vcore" ) ||
323+ strings .Contains (strings .ToLower (label ), "cpu" ) ||
324+ strings .Contains (strings .ToLower (label ), "svi2_core" ) {
325+ inputPath := strings .Replace (labelPath , "_label" , "_input" , 1 )
326+ data , err := os .ReadFile (inputPath )
327+ if err != nil {
328+ continue
329+ }
330+ milliV , err := strconv .ParseInt (strings .TrimSpace (string (data )), 10 , 64 )
331+ if err != nil {
332+ continue
333+ }
334+ voltage := float64 (milliV ) / 1000.0
335+ return & voltage
336+ }
337+ }
338+ }
339+ }
340+
341+ return nil
342+ }
0 commit comments