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