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 }