1
2
3
4
5 package oshi.hardware.platform.linux;
6
7 import java.io.File;
8 import java.io.FileFilter;
9 import java.io.IOException;
10 import java.nio.file.Paths;
11 import java.util.ArrayList;
12 import java.util.HashMap;
13 import java.util.List;
14 import java.util.Locale;
15 import java.util.Map;
16 import java.util.function.ToIntFunction;
17 import java.util.regex.Pattern;
18 import java.util.stream.Collectors;
19 import java.util.stream.Stream;
20
21 import oshi.annotation.concurrent.ThreadSafe;
22 import oshi.hardware.common.AbstractSensors;
23 import oshi.util.ExecutingCommand;
24 import oshi.util.FileUtil;
25 import oshi.util.GlobalConfig;
26 import oshi.util.ParseUtil;
27 import oshi.util.platform.linux.SysPath;
28
29
30
31
32 @ThreadSafe
33 final class LinuxSensors extends AbstractSensors {
34
35
36
37
38
39
40
41
42
43
44
45 public static final String OSHI_HWMON_NAME_PRIORITY = "oshi.os.linux.sensors.hwmon.names";
46 public static final String OSHI_THERMAL_ZONE_TYPE_PRIORITY = "oshi.os.linux.sensors.cpuTemperature.types";
47
48 private static final List<String> HWMON_NAME_PRIORITY = Stream.of(GlobalConfig
49 .get(OSHI_HWMON_NAME_PRIORITY, "coretemp,k10temp,zenpower,k8temp,via-cputemp,acpitz").split(","))
50 .filter((s) -> !s.isEmpty()).collect(Collectors.toList());
51 private static final List<String> THERMAL_ZONE_TYPE_PRIORITY = Stream
52 .of(GlobalConfig.get(OSHI_THERMAL_ZONE_TYPE_PRIORITY, "cpu-thermal,x86_pkg_temp").split(","))
53 .filter((s) -> !s.isEmpty()).collect(Collectors.toList());
54
55 private static final String TYPE = "type";
56 private static final String NAME = "/name";
57
58 private static final String TEMP = "temp";
59 private static final String FAN = "fan";
60 private static final String VOLTAGE = "in";
61
62 private static final String INPUT_SUFFIX = "_input";
63 private static final Pattern TEMP_INPUT_PATTERN = Pattern.compile("^" + TEMP + "\\d+" + INPUT_SUFFIX + "$");
64
65
66 private static final String HWMON = "hwmon";
67 private static final String HWMON_PATH = SysPath.HWMON + HWMON;
68
69 private static final String THERMAL_ZONE = "thermal_zone";
70 private static final String THERMAL_ZONE_PATH = SysPath.THERMAL + THERMAL_ZONE;
71
72
73 private static final boolean IS_PI = queryCpuTemperatureFromVcGenCmd() > 0;
74
75
76 private final Map<String, String> sensorsMap = new HashMap<>();
77
78
79
80
81
82
83 LinuxSensors() {
84 if (!IS_PI) {
85 populateSensorsMapFromHwmon();
86
87 if (!this.sensorsMap.containsKey(TEMP)) {
88 populateSensorsMapFromThermalZone();
89 }
90 }
91 }
92
93
94
95
96 private void populateSensorsMapFromHwmon() {
97 String selectedTempPath = null;
98 int selectedPriority = Integer.MAX_VALUE;
99
100 int i = 0;
101 while (Paths.get(HWMON_PATH + i).toFile().isDirectory()) {
102 String path = HWMON_PATH + i;
103
104
105 String sensorName = FileUtil.getStringFromFile(path + NAME).trim();
106
107
108 File dir = new File(path);
109 File[] tempInputs = dir.listFiles((d, name) -> TEMP_INPUT_PATTERN.matcher(name).matches());
110
111 if (tempInputs != null && tempInputs.length > 0) {
112 int priority = HWMON_NAME_PRIORITY.indexOf(sensorName);
113 if (priority >= 0 && priority < selectedPriority) {
114
115 for (File tempInput : tempInputs) {
116 long temp = FileUtil.getLongFromFile(tempInput.getPath());
117 if (temp > 0) {
118 selectedPriority = priority;
119 selectedTempPath = path;
120 break;
121 }
122 }
123 }
124 }
125
126
127 for (String sensor : new String[] { FAN, VOLTAGE }) {
128 final String sensorPrefix = sensor;
129
130 getSensorFilesFromPath(path, sensor, f -> {
131 try {
132 return f.getName().startsWith(sensorPrefix) && f.getName().endsWith(INPUT_SUFFIX)
133 && FileUtil.getIntFromFile(f.getCanonicalPath()) > 0;
134 } catch (IOException e) {
135 return false;
136 }
137 });
138 }
139
140 i++;
141 }
142
143 if (selectedTempPath != null) {
144 this.sensorsMap.put(TEMP, selectedTempPath + "/temp");
145 }
146 }
147
148
149
150
151 private void populateSensorsMapFromThermalZone() {
152 getSensorFilesFromPath(THERMAL_ZONE_PATH, TEMP, f -> f.getName().equals(TYPE) || f.getName().equals(TEMP),
153 files -> Stream.of(files).filter(f -> TYPE.equals(f.getName())).findFirst().map(File::getPath)
154 .map(FileUtil::getStringFromFile).map(THERMAL_ZONE_TYPE_PRIORITY::indexOf)
155 .filter((index) -> index >= 0).orElse(THERMAL_ZONE_TYPE_PRIORITY.size()));
156 }
157
158
159
160
161
162
163
164
165 private void getSensorFilesFromPath(String sensorPath, String sensor, FileFilter sensorFileFilter) {
166 getSensorFilesFromPath(sensorPath, sensor, sensorFileFilter, (files) -> 0);
167 }
168
169
170
171
172
173
174
175
176
177 private void getSensorFilesFromPath(String sensorPath, String sensor, FileFilter sensorFileFilter,
178 ToIntFunction<File[]> prioritizer) {
179 String selectedPath = null;
180 int selectedPriority = Integer.MAX_VALUE;
181
182 int i = 0;
183 while (Paths.get(sensorPath + i).toFile().isDirectory()) {
184 String path = sensorPath + i;
185 File dir = new File(path);
186 File[] matchingFiles = dir.listFiles(sensorFileFilter);
187
188 if (matchingFiles != null && matchingFiles.length > 0) {
189 int priority = prioritizer.applyAsInt(matchingFiles);
190
191 if (priority < selectedPriority) {
192 selectedPriority = priority;
193 selectedPath = path;
194 }
195 }
196 i++;
197 }
198
199 if (selectedPath != null) {
200 this.sensorsMap.put(sensor, String.format(Locale.ROOT, "%s/%s", selectedPath, sensor));
201 }
202 }
203
204 @Override
205 public double queryCpuTemperature() {
206 if (IS_PI) {
207 return queryCpuTemperatureFromVcGenCmd();
208 }
209 String tempStr = this.sensorsMap.get(TEMP);
210 if (tempStr != null) {
211 long millidegrees = 0;
212 if (tempStr.contains(HWMON)) {
213
214 millidegrees = FileUtil.getLongFromFile(String.format(Locale.ROOT, "%s1%s", tempStr, INPUT_SUFFIX));
215
216 if (millidegrees > 0) {
217 return millidegrees / 1000d;
218 }
219
220
221 long sum = 0;
222 int count = 0;
223 for (int i = 2; i <= 6; i++) {
224 millidegrees = FileUtil
225 .getLongFromFile(String.format(Locale.ROOT, "%s%d%s", tempStr, i, INPUT_SUFFIX));
226 if (millidegrees > 0) {
227 sum += millidegrees;
228 count++;
229 }
230 }
231 if (count > 0) {
232 return sum / (count * 1000d);
233 }
234 } else if (tempStr.contains(THERMAL_ZONE)) {
235
236 millidegrees = FileUtil.getLongFromFile(tempStr);
237
238 if (millidegrees > 0) {
239 return millidegrees / 1000d;
240 }
241 }
242 }
243 return 0d;
244 }
245
246
247
248
249
250
251 private static double queryCpuTemperatureFromVcGenCmd() {
252 String tempStr = ExecutingCommand.getFirstAnswer("vcgencmd measure_temp");
253
254 if (tempStr.startsWith("temp=")) {
255 return ParseUtil.parseDoubleOrDefault(tempStr.replaceAll("[^\\d|\\.]+", ""), 0d);
256 }
257 return 0d;
258 }
259
260 @Override
261 public int[] queryFanSpeeds() {
262 if (!IS_PI) {
263 String fanStr = this.sensorsMap.get(FAN);
264 if (fanStr != null) {
265 List<Integer> speeds = new ArrayList<>();
266 int fan = 1;
267 for (;;) {
268 String fanPath = String.format(Locale.ROOT, "%s%d%s", fanStr, fan, INPUT_SUFFIX);
269 if (!new File(fanPath).exists()) {
270
271 break;
272 }
273
274 speeds.add(FileUtil.getIntFromFile(fanPath));
275
276 fan++;
277 }
278 int[] fanSpeeds = new int[speeds.size()];
279 for (int i = 0; i < speeds.size(); i++) {
280 fanSpeeds[i] = speeds.get(i);
281 }
282 return fanSpeeds;
283 }
284 }
285 return new int[0];
286 }
287
288 @Override
289 public double queryCpuVoltage() {
290 if (IS_PI) {
291 return queryCpuVoltageFromVcGenCmd();
292 }
293 String voltageStr = this.sensorsMap.get(VOLTAGE);
294 if (voltageStr != null) {
295
296 return FileUtil.getIntFromFile(String.format(Locale.ROOT, "%s1%s", voltageStr, INPUT_SUFFIX)) / 1000d;
297 }
298 return 0d;
299 }
300
301
302
303
304
305
306 private static double queryCpuVoltageFromVcGenCmd() {
307
308 String voltageStr = ExecutingCommand.getFirstAnswer("vcgencmd measure_volts core");
309
310 if (voltageStr.startsWith("volt=")) {
311 return ParseUtil.parseDoubleOrDefault(voltageStr.replaceAll("[^\\d|\\.]+", ""), 0d);
312 }
313 return 0d;
314 }
315 }