pkg.re/essentialkaos/ek@v12.36.0+incompatible/system/sensors/sensors_linux.go (about)

     1  // +build linux
     2  
     3  // Package sensors provide methods for collecting sensors information
     4  package sensors
     5  
     6  // ////////////////////////////////////////////////////////////////////////////////// //
     7  //                                                                                    //
     8  //                         Copyright (c) 2021 ESSENTIAL KAOS                          //
     9  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
    10  //                                                                                    //
    11  // ////////////////////////////////////////////////////////////////////////////////// //
    12  
    13  import (
    14  	"fmt"
    15  	"io/ioutil"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"pkg.re/essentialkaos/ek.v12/fsutil"
    20  	"pkg.re/essentialkaos/ek.v12/sortutil"
    21  )
    22  
    23  // ////////////////////////////////////////////////////////////////////////////////// //
    24  
    25  // Device contains info from different device sensors
    26  type Device struct {
    27  	Name        string
    28  	TempSensors []TempSensor
    29  }
    30  
    31  // TempSensor contains info from temperature sensor
    32  type TempSensor struct {
    33  	Name string
    34  	Cur  float64
    35  	Min  float64
    36  	Max  float64
    37  	Crit float64
    38  }
    39  
    40  // ////////////////////////////////////////////////////////////////////////////////// //
    41  
    42  var hwmonDir = "/sys/class/hwmon"
    43  
    44  // ////////////////////////////////////////////////////////////////////////////////// //
    45  
    46  // Collect collects sensors information
    47  // https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
    48  func Collect() ([]*Device, error) {
    49  	if !fsutil.CheckPerms("DR", hwmonDir) {
    50  		return nil, fmt.Errorf("Can't read sensors information")
    51  	}
    52  
    53  	var result []*Device
    54  
    55  	deviceDir := fsutil.List(hwmonDir, false)
    56  
    57  	sortutil.StringsNatural(deviceDir)
    58  
    59  	for _, deviceDir := range deviceDir {
    60  		if !hasSensorsData(hwmonDir + "/" + deviceDir) {
    61  			continue
    62  		}
    63  
    64  		device, err := collectDeviceInfo(hwmonDir + "/" + deviceDir)
    65  
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  
    70  		if device != nil {
    71  			result = append(result, device)
    72  		}
    73  	}
    74  
    75  	return result, nil
    76  }
    77  
    78  // ////////////////////////////////////////////////////////////////////////////////// //
    79  
    80  // Temperature returns min, max and average temperature
    81  func (d *Device) Temperature() (float64, float64, float64) {
    82  	if len(d.TempSensors) == 0 {
    83  		return 0.0, 0.0, 0.0
    84  	}
    85  
    86  	min, max, tot := 100.0, 0.0, 0.0
    87  
    88  	for _, v := range d.TempSensors {
    89  		if v.Cur < min {
    90  			min = v.Cur
    91  		}
    92  
    93  		if v.Cur > max {
    94  			max = v.Cur
    95  		}
    96  
    97  		tot += v.Cur
    98  	}
    99  
   100  	return min, max, tot / float64(len(d.TempSensors))
   101  }
   102  
   103  // String formats sensor data as a string
   104  func (s TempSensor) String() string {
   105  	return fmt.Sprintf(
   106  		"{Name:%s Cur:+%g°C Min:+%g°C Max:+%g°C Crit:+%g°C}",
   107  		s.Name, s.Cur, s.Min, s.Max, s.Crit,
   108  	)
   109  }
   110  
   111  // ////////////////////////////////////////////////////////////////////////////////// //
   112  
   113  func hasSensorsData(dir string) bool {
   114  	switch {
   115  	case hasTempSensorsData(dir):
   116  		return true
   117  	}
   118  
   119  	return false
   120  }
   121  
   122  func hasTempSensorsData(dir string) bool {
   123  	return fsutil.IsExist(dir + "/temp1_input")
   124  }
   125  
   126  func collectDeviceInfo(dir string) (*Device, error) {
   127  	var err error
   128  
   129  	device := &Device{}
   130  
   131  	device.Name, err = readSensorLabel(dir + "/name")
   132  
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	if hasTempSensorsData(dir) {
   138  		device.TempSensors, err = collectTempSensorsInfo(dir)
   139  
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  	}
   144  
   145  	return device, nil
   146  }
   147  
   148  func collectTempSensorsInfo(dir string) ([]TempSensor, error) {
   149  	var result []TempSensor
   150  
   151  	for i := 1; i < 128; i++ {
   152  		filePrefix := dir + "/temp" + strconv.Itoa(i)
   153  
   154  		if !fsutil.IsExist(filePrefix + "_input") {
   155  			break
   156  		}
   157  
   158  		sensor, err := readTempSensors(filePrefix)
   159  
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  
   164  		result = append(result, sensor)
   165  	}
   166  
   167  	return result, nil
   168  }
   169  
   170  func readTempSensors(filePrefix string) (TempSensor, error) {
   171  	var err error
   172  
   173  	sensor := TempSensor{}
   174  
   175  	if fsutil.IsExist(filePrefix + "_label") {
   176  		sensor.Name, err = readSensorLabel(filePrefix + "_label")
   177  
   178  		if err != nil {
   179  			return TempSensor{}, err
   180  		}
   181  	}
   182  
   183  	sensor.Cur, err = readTempSensorValue(filePrefix + "_input")
   184  
   185  	if err != nil {
   186  		return TempSensor{}, err
   187  	}
   188  
   189  	sensor.Min, err = readTempSensorValue(filePrefix + "_min")
   190  
   191  	if err != nil {
   192  		return TempSensor{}, err
   193  	}
   194  
   195  	sensor.Max, err = readTempSensorValue(filePrefix + "_max")
   196  
   197  	if err != nil {
   198  		return TempSensor{}, err
   199  	}
   200  
   201  	sensor.Crit, err = readTempSensorValue(filePrefix + "_crit")
   202  
   203  	if err != nil {
   204  		return TempSensor{}, err
   205  	}
   206  
   207  	return sensor, nil
   208  }
   209  
   210  func readSensorLabel(file string) (string, error) {
   211  	data, err := ioutil.ReadFile(file)
   212  
   213  	if err != nil {
   214  		return "", fmt.Errorf("Can't read data from %s: %w", file, err)
   215  	}
   216  
   217  	return strings.Trim(string(data), "\r\n"), nil
   218  }
   219  
   220  func readTempSensorValue(file string) (float64, error) {
   221  	if !fsutil.IsExist(file) {
   222  		return 0.0, nil
   223  	}
   224  
   225  	data, err := ioutil.ReadFile(file)
   226  
   227  	if err != nil {
   228  		return 0.0, fmt.Errorf("Can't read sensor data from %s: %w", file, err)
   229  	}
   230  
   231  	value, err := strconv.ParseFloat(strings.Trim(string(data), "\r\n"), 64)
   232  
   233  	if err != nil {
   234  		return 0.0, fmt.Errorf("Can't parse sensor data from %s: %w", file, err)
   235  	}
   236  
   237  	return value / 1000.0, nil
   238  }