]> git.basschouten.com Git - openhab-addons.git/blob
8fc9f8e68f7d8018a99cfa43056d8d9b0b777c80
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.systeminfo.internal.model;
14
15 import java.math.BigDecimal;
16 import java.math.RoundingMode;
17 import java.util.List;
18
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.core.library.types.DecimalType;
22 import org.openhab.core.library.types.StringType;
23 import org.osgi.service.component.annotations.Component;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import oshi.SystemInfo;
28 import oshi.hardware.CentralProcessor;
29 import oshi.hardware.ComputerSystem;
30 import oshi.hardware.Display;
31 import oshi.hardware.GlobalMemory;
32 import oshi.hardware.HWDiskStore;
33 import oshi.hardware.HardwareAbstractionLayer;
34 import oshi.hardware.NetworkIF;
35 import oshi.hardware.PowerSource;
36 import oshi.hardware.Sensors;
37 import oshi.software.os.OSFileStore;
38 import oshi.software.os.OSProcess;
39 import oshi.software.os.OperatingSystem;
40 import oshi.util.EdidUtil;
41
42 /**
43  * This implementation of {@link SysteminfoInterface} is using the open source library OSHI to provide system
44  * information. OSHI is a free JNA-based (native) Operating System and Hardware Information library for Java.
45  *
46  * @author Svilen Valkanov - Initial contribution
47  * @author Lyubomir Papazov - Move the initialization logic that could potentially take long time to the
48  *         initializeSysteminfo method
49  * @author Christoph Weitkamp - Update to OSHI 3.13.0 - Replaced deprecated method
50  *         CentralProcessor#getSystemSerialNumber()
51  * @author Wouter Born - Update to OSHI 4.0.0 and add null annotations
52  *
53  * @see <a href="https://github.com/oshi/oshi">OSHI GitHub repository</a>
54  */
55 @NonNullByDefault
56 @Component(service = SysteminfoInterface.class)
57 public class OSHISysteminfo implements SysteminfoInterface {
58
59     private final Logger logger = LoggerFactory.getLogger(OSHISysteminfo.class);
60
61     private @NonNullByDefault({}) HardwareAbstractionLayer hal;
62
63     // Dynamic objects (may be queried repeatedly)
64     private @NonNullByDefault({}) GlobalMemory memory;
65     private @NonNullByDefault({}) CentralProcessor cpu;
66     private @NonNullByDefault({}) Sensors sensors;
67
68     // Static objects, should be recreated on each request
69     private @NonNullByDefault({}) ComputerSystem computerSystem;
70     private @NonNullByDefault({}) OperatingSystem operatingSystem;
71     private @NonNullByDefault({}) List<NetworkIF> networks;
72     private @NonNullByDefault({}) List<Display> displays;
73     private @NonNullByDefault({}) List<OSFileStore> fileStores;
74     private @NonNullByDefault({}) List<PowerSource> powerSources;
75     private @NonNullByDefault({}) List<HWDiskStore> drives;
76
77     public static final int PRECISION_AFTER_DECIMAL_SIGN = 1;
78
79     /**
80      * Some of the methods used in this constructor execute native code and require execute permissions
81      *
82      */
83     public OSHISysteminfo() {
84         logger.debug("OSHISysteminfo service is created");
85     }
86
87     @Override
88     public void initializeSysteminfo() {
89         logger.debug("OSHISysteminfo service starts initializing");
90
91         SystemInfo systemInfo = new SystemInfo();
92         hal = systemInfo.getHardware();
93
94         // Doesn't need regular update, they may be queried repeatedly
95         memory = hal.getMemory();
96         cpu = hal.getProcessor();
97         sensors = hal.getSensors();
98
99         computerSystem = hal.getComputerSystem();
100         operatingSystem = systemInfo.getOperatingSystem();
101         networks = hal.getNetworkIFs();
102         displays = hal.getDisplays();
103         fileStores = operatingSystem.getFileSystem().getFileStores();
104         powerSources = hal.getPowerSources();
105         drives = hal.getDiskStores();
106     }
107
108     private <T> T getDevice(List<@Nullable T> devices, int index) throws DeviceNotFoundException {
109         if (devices.size() <= index) {
110             throw new DeviceNotFoundException("Device with index: " + index + " can not be found!");
111         }
112         return (T) devices.get(index);
113     }
114
115     private <T> T getDevice(T @Nullable [] devices, int index) throws DeviceNotFoundException {
116         if (devices == null || devices.length <= index) {
117             throw new DeviceNotFoundException("Device with index: " + index + " can not be found!");
118         }
119         return devices[index];
120     }
121
122     private OSProcess getProcess(int pid) throws DeviceNotFoundException {
123         OSProcess process = operatingSystem.getProcess(pid);
124         if (process == null) {
125             throw new DeviceNotFoundException("Error while getting information for process with PID " + pid);
126         }
127         return process;
128     }
129
130     @Override
131     public StringType getOsFamily() {
132         String osFamily = operatingSystem.getFamily();
133         return new StringType(osFamily);
134     }
135
136     @Override
137     public StringType getOsManufacturer() {
138         String osManufacturer = operatingSystem.getManufacturer();
139         return new StringType(osManufacturer);
140     }
141
142     @Override
143     public StringType getOsVersion() {
144         String osVersion = operatingSystem.getVersionInfo().toString();
145         return new StringType(osVersion);
146     }
147
148     @Override
149     public StringType getCpuName() {
150         String name = cpu.getProcessorIdentifier().getName();
151         return new StringType(name);
152     }
153
154     @Override
155     public StringType getCpuDescription() {
156         String model = cpu.getProcessorIdentifier().getModel();
157         String family = cpu.getProcessorIdentifier().getFamily();
158         String serialNumber = computerSystem.getSerialNumber();
159         String identifier = cpu.getProcessorIdentifier().getIdentifier();
160         String vendor = cpu.getProcessorIdentifier().getVendor();
161         String architecture = cpu.getProcessorIdentifier().isCpu64bit() ? "64 bit" : "32 bit";
162         String descriptionFormatString = "Model: %s %s,family: %s, vendor: %s, sn: %s, identifier: %s ";
163         String description = String.format(descriptionFormatString, model, architecture, family, vendor, serialNumber,
164                 identifier);
165
166         return new StringType(description);
167     }
168
169     @Override
170     public DecimalType getCpuLogicalCores() {
171         int logicalProcessorCount = cpu.getLogicalProcessorCount();
172         return new DecimalType(logicalProcessorCount);
173     }
174
175     @Override
176     public DecimalType getCpuPhysicalCores() {
177         int physicalProcessorCount = cpu.getPhysicalProcessorCount();
178         return new DecimalType(physicalProcessorCount);
179     }
180
181     @Override
182     public DecimalType getMemoryTotal() {
183         long totalMemory = memory.getTotal();
184         totalMemory = getSizeInMB(totalMemory);
185         return new DecimalType(totalMemory);
186     }
187
188     @Override
189     public DecimalType getMemoryAvailable() {
190         long availableMemory = memory.getAvailable();
191         availableMemory = getSizeInMB(availableMemory);
192         return new DecimalType(availableMemory);
193     }
194
195     @Override
196     public DecimalType getMemoryUsed() {
197         long totalMemory = memory.getTotal();
198         long availableMemory = memory.getAvailable();
199         long usedMemory = totalMemory - availableMemory;
200         usedMemory = getSizeInMB(usedMemory);
201         return new DecimalType(usedMemory);
202     }
203
204     @Override
205     public DecimalType getStorageTotal(int index) throws DeviceNotFoundException {
206         OSFileStore fileStore = getDevice(fileStores, index);
207         fileStore.updateAttributes();
208         long totalSpace = fileStore.getTotalSpace();
209         totalSpace = getSizeInMB(totalSpace);
210         return new DecimalType(totalSpace);
211     }
212
213     @Override
214     public DecimalType getStorageAvailable(int index) throws DeviceNotFoundException {
215         OSFileStore fileStore = getDevice(fileStores, index);
216         fileStore.updateAttributes();
217         long freeSpace = fileStore.getUsableSpace();
218         freeSpace = getSizeInMB(freeSpace);
219         return new DecimalType(freeSpace);
220     }
221
222     @Override
223     public DecimalType getStorageUsed(int index) throws DeviceNotFoundException {
224         OSFileStore fileStore = getDevice(fileStores, index);
225         fileStore.updateAttributes();
226         long totalSpace = fileStore.getTotalSpace();
227         long freeSpace = fileStore.getUsableSpace();
228         long usedSpace = totalSpace - freeSpace;
229         usedSpace = getSizeInMB(usedSpace);
230         return new DecimalType(usedSpace);
231     }
232
233     @Override
234     public @Nullable DecimalType getStorageAvailablePercent(int deviceIndex) throws DeviceNotFoundException {
235         OSFileStore fileStore = getDevice(fileStores, deviceIndex);
236         fileStore.updateAttributes();
237         long totalSpace = fileStore.getTotalSpace();
238         long freeSpace = fileStore.getUsableSpace();
239         if (totalSpace > 0) {
240             double freePercentDecimal = (double) freeSpace / (double) totalSpace;
241             BigDecimal freePercent = getPercentsValue(freePercentDecimal);
242             return new DecimalType(freePercent);
243         } else {
244             return null;
245         }
246     }
247
248     @Override
249     public @Nullable DecimalType getStorageUsedPercent(int deviceIndex) throws DeviceNotFoundException {
250         OSFileStore fileStore = getDevice(fileStores, deviceIndex);
251         fileStore.updateAttributes();
252         long totalSpace = fileStore.getTotalSpace();
253         long freeSpace = fileStore.getUsableSpace();
254         long usedSpace = totalSpace - freeSpace;
255         if (totalSpace > 0) {
256             double usedPercentDecimal = (double) usedSpace / (double) totalSpace;
257             BigDecimal usedPercent = getPercentsValue(usedPercentDecimal);
258             return new DecimalType(usedPercent);
259         } else {
260             return null;
261         }
262     }
263
264     @Override
265     public StringType getStorageName(int index) throws DeviceNotFoundException {
266         OSFileStore fileStore = getDevice(fileStores, index);
267         String name = fileStore.getName();
268         return new StringType(name);
269     }
270
271     @Override
272     public StringType getStorageType(int deviceIndex) throws DeviceNotFoundException {
273         OSFileStore fileStore = getDevice(fileStores, deviceIndex);
274         String type = fileStore.getType();
275         return new StringType(type);
276     }
277
278     @Override
279     public StringType getStorageDescription(int index) throws DeviceNotFoundException {
280         OSFileStore fileStore = getDevice(fileStores, index);
281         String description = fileStore.getDescription();
282         return new StringType(description);
283     }
284
285     @Override
286     public StringType getNetworkIp(int index) throws DeviceNotFoundException {
287         NetworkIF netInterface = getDevice(networks, index);
288         netInterface.updateAttributes();
289         String[] ipAddresses = netInterface.getIPv4addr();
290         String ipv4 = getDevice(ipAddresses, 0);
291         return new StringType(ipv4);
292     }
293
294     @Override
295     public StringType getNetworkName(int index) throws DeviceNotFoundException {
296         NetworkIF netInterface = getDevice(networks, index);
297         String name = netInterface.getName();
298         return new StringType(name);
299     }
300
301     @Override
302     public StringType getNetworkDisplayName(int index) throws DeviceNotFoundException {
303         NetworkIF netInterface = getDevice(networks, index);
304         String adapterName = netInterface.getDisplayName();
305         return new StringType(adapterName);
306     }
307
308     @Override
309     public StringType getDisplayInformation(int index) throws DeviceNotFoundException {
310         Display display = getDevice(displays, index);
311
312         byte[] edid = display.getEdid();
313         String manufacturer = EdidUtil.getManufacturerID(edid);
314         String product = EdidUtil.getProductID(edid);
315         String serialNumber = EdidUtil.getSerialNo(edid);
316         int width = EdidUtil.getHcm(edid);
317         int height = EdidUtil.getVcm(edid);
318
319         String edidFormatString = "Product %s, manufacturer %s, SN: %s, Width: %d, Height: %d";
320         String edidInfo = String.format(edidFormatString, product, manufacturer, serialNumber, width, height);
321         return new StringType(edidInfo);
322     }
323
324     @Override
325     public @Nullable DecimalType getSensorsCpuTemperature() {
326         BigDecimal cpuTemp = new BigDecimal(sensors.getCpuTemperature());
327         cpuTemp = cpuTemp.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
328         return cpuTemp.signum() == 1 ? new DecimalType(cpuTemp) : null;
329     }
330
331     @Override
332     public @Nullable DecimalType getSensorsCpuVoltage() {
333         BigDecimal cpuVoltage = new BigDecimal(sensors.getCpuVoltage());
334         cpuVoltage = cpuVoltage.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
335         return cpuVoltage.signum() == 1 ? new DecimalType(cpuVoltage) : null;
336     }
337
338     @Override
339     public @Nullable DecimalType getSensorsFanSpeed(int index) throws DeviceNotFoundException {
340         int[] fanSpeeds = sensors.getFanSpeeds();
341         int speed = 0;// 0 means unable to measure speed
342         if (index < fanSpeeds.length) {
343             speed = fanSpeeds[index];
344         }
345         return speed > 0 ? new DecimalType(speed) : null;
346     }
347
348     @Override
349     public @Nullable DecimalType getBatteryRemainingTime(int index) throws DeviceNotFoundException {
350         PowerSource powerSource = getDevice(powerSources, index);
351         powerSource.updateAttributes();
352         double remainingTimeInSeconds = powerSource.getTimeRemainingEstimated();
353         // The getTimeRemaining() method returns (-1.0) if is calculating or (-2.0) if the time is unlimited.
354         BigDecimal remainingTime = getTimeInMinutes(remainingTimeInSeconds);
355         return remainingTime.signum() == 1 ? new DecimalType(remainingTime) : null;
356     }
357
358     @Override
359     public DecimalType getBatteryRemainingCapacity(int index) throws DeviceNotFoundException {
360         PowerSource powerSource = getDevice(powerSources, index);
361         powerSource.updateAttributes();
362         double remainingCapacity = powerSource.getRemainingCapacityPercent();
363         BigDecimal remainingCapacityPercents = getPercentsValue(remainingCapacity);
364         return new DecimalType(remainingCapacityPercents);
365     }
366
367     @Override
368     public StringType getBatteryName(int index) throws DeviceNotFoundException {
369         PowerSource powerSource = getDevice(powerSources, index);
370         String name = powerSource.getName();
371         return new StringType(name);
372     }
373
374     @Override
375     public @Nullable DecimalType getMemoryAvailablePercent() {
376         long availableMemory = memory.getAvailable();
377         long totalMemory = memory.getTotal();
378         if (totalMemory > 0) {
379             double freePercentDecimal = (double) availableMemory / (double) totalMemory;
380             BigDecimal freePercent = getPercentsValue(freePercentDecimal);
381             return new DecimalType(freePercent);
382         } else {
383             return null;
384         }
385     }
386
387     @Override
388     public @Nullable DecimalType getMemoryUsedPercent() {
389         long availableMemory = memory.getAvailable();
390         long totalMemory = memory.getTotal();
391         long usedMemory = totalMemory - availableMemory;
392         if (totalMemory > 0) {
393             double usedPercentDecimal = (double) usedMemory / (double) totalMemory;
394             BigDecimal usedPercent = getPercentsValue(usedPercentDecimal);
395             return new DecimalType(usedPercent);
396         } else {
397             return null;
398         }
399     }
400
401     @Override
402     public StringType getDriveName(int deviceIndex) throws DeviceNotFoundException {
403         HWDiskStore drive = getDevice(drives, deviceIndex);
404         String name = drive.getName();
405         return new StringType(name);
406     }
407
408     @Override
409     public StringType getDriveModel(int deviceIndex) throws DeviceNotFoundException {
410         HWDiskStore drive = getDevice(drives, deviceIndex);
411         String model = drive.getModel();
412         return new StringType(model);
413     }
414
415     @Override
416     public StringType getDriveSerialNumber(int deviceIndex) throws DeviceNotFoundException {
417         HWDiskStore drive = getDevice(drives, deviceIndex);
418         String serialNumber = drive.getSerial();
419         return new StringType(serialNumber);
420     }
421
422     @Override
423     public @Nullable DecimalType getSwapTotal() {
424         long swapTotal = memory.getVirtualMemory().getSwapTotal();
425         swapTotal = getSizeInMB(swapTotal);
426         return new DecimalType(swapTotal);
427     }
428
429     @Override
430     public @Nullable DecimalType getSwapAvailable() {
431         long swapTotal = memory.getVirtualMemory().getSwapTotal();
432         long swapUsed = memory.getVirtualMemory().getSwapUsed();
433         long swapAvailable = swapTotal - swapUsed;
434         swapAvailable = getSizeInMB(swapAvailable);
435         return new DecimalType(swapAvailable);
436     }
437
438     @Override
439     public @Nullable DecimalType getSwapUsed() {
440         long swapUsed = memory.getVirtualMemory().getSwapUsed();
441         swapUsed = getSizeInMB(swapUsed);
442         return new DecimalType(swapUsed);
443     }
444
445     @Override
446     public @Nullable DecimalType getSwapAvailablePercent() {
447         long swapTotal = memory.getVirtualMemory().getSwapTotal();
448         long swapUsed = memory.getVirtualMemory().getSwapUsed();
449         long swapAvailable = swapTotal - swapUsed;
450         if (swapTotal > 0) {
451             double swapAvailablePercentDecimal = (double) swapAvailable / (double) swapTotal;
452             BigDecimal swapAvailablePercent = getPercentsValue(swapAvailablePercentDecimal);
453             return new DecimalType(swapAvailablePercent);
454         } else {
455             return null;
456         }
457     }
458
459     @Override
460     public @Nullable DecimalType getSwapUsedPercent() {
461         long swapTotal = memory.getVirtualMemory().getSwapTotal();
462         long swapUsed = memory.getVirtualMemory().getSwapUsed();
463         if (swapTotal > 0) {
464             double swapUsedPercentDecimal = (double) swapUsed / (double) swapTotal;
465             BigDecimal swapUsedPercent = getPercentsValue(swapUsedPercentDecimal);
466             return new DecimalType(swapUsedPercent);
467         } else {
468             return null;
469         }
470     }
471
472     private long getSizeInMB(long sizeInBytes) {
473         return Math.round(sizeInBytes / (1024D * 1024));
474     }
475
476     private BigDecimal getPercentsValue(double decimalFraction) {
477         BigDecimal result = new BigDecimal(decimalFraction * 100);
478         result = result.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
479         return result;
480     }
481
482     private BigDecimal getTimeInMinutes(double timeInSeconds) {
483         BigDecimal timeInMinutes = new BigDecimal(timeInSeconds / 60);
484         timeInMinutes = timeInMinutes.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.UP);
485         return timeInMinutes;
486     }
487
488     /**
489      * {@inheritDoc}
490      *
491      * This information is available only on Mac and Linux OS.
492      */
493     @Override
494     public @Nullable DecimalType getCpuLoad1() {
495         BigDecimal avarageCpuLoad = getAvarageCpuLoad(1);
496         return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
497     }
498
499     /**
500      * {@inheritDoc}
501      *
502      * This information is available only on Mac and Linux OS.
503      */
504     @Override
505     public @Nullable DecimalType getCpuLoad5() {
506         BigDecimal avarageCpuLoad = getAvarageCpuLoad(5);
507         return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
508     }
509
510     /**
511      * {@inheritDoc}
512      *
513      * This information is available only on Mac and Linux OS.
514      */
515     @Override
516     public @Nullable DecimalType getCpuLoad15() {
517         BigDecimal avarageCpuLoad = getAvarageCpuLoad(15);
518         return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
519     }
520
521     private BigDecimal getAvarageCpuLoad(int timeInMunutes) {
522         // This parameter is specified in OSHI Javadoc
523         int index;
524         switch (timeInMunutes) {
525             case 1:
526                 index = 0;
527                 break;
528             case 5:
529                 index = 1;
530                 break;
531             case 15:
532                 index = 2;
533                 break;
534             default:
535                 index = 2;
536         }
537         double processorLoads[] = cpu.getSystemLoadAverage(index + 1);
538         BigDecimal result = new BigDecimal(processorLoads[index]);
539         result = result.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
540         return result;
541     }
542
543     @Override
544     public DecimalType getCpuUptime() {
545         long seconds = operatingSystem.getSystemUptime();
546         return new DecimalType(getTimeInMinutes(seconds));
547     }
548
549     @Override
550     public DecimalType getCpuThreads() {
551         int threadCount = operatingSystem.getThreadCount();
552         return new DecimalType(threadCount);
553     }
554
555     @Override
556     public StringType getNetworkMac(int networkIndex) throws DeviceNotFoundException {
557         NetworkIF network = getDevice(networks, networkIndex);
558         String mac = network.getMacaddr();
559         return new StringType(mac);
560     }
561
562     @Override
563     public DecimalType getNetworkPacketsReceived(int networkIndex) throws DeviceNotFoundException {
564         NetworkIF network = getDevice(networks, networkIndex);
565         network.updateAttributes();
566         long packRecv = network.getPacketsRecv();
567         return new DecimalType(packRecv);
568     }
569
570     @Override
571     public DecimalType getNetworkPacketsSent(int networkIndex) throws DeviceNotFoundException {
572         NetworkIF network = getDevice(networks, networkIndex);
573         network.updateAttributes();
574         long packSent = network.getPacketsSent();
575         return new DecimalType(packSent);
576     }
577
578     @Override
579     public DecimalType getNetworkDataSent(int networkIndex) throws DeviceNotFoundException {
580         NetworkIF network = getDevice(networks, networkIndex);
581         network.updateAttributes();
582         long bytesSent = network.getBytesSent();
583         return new DecimalType(getSizeInMB(bytesSent));
584     }
585
586     @Override
587     public DecimalType getNetworkDataReceived(int networkIndex) throws DeviceNotFoundException {
588         NetworkIF network = getDevice(networks, networkIndex);
589         network.updateAttributes();
590         long bytesRecv = network.getBytesRecv();
591         return new DecimalType(getSizeInMB(bytesRecv));
592     }
593
594     @Override
595     public @Nullable StringType getProcessName(int pid) throws DeviceNotFoundException {
596         if (pid > 0) {
597             OSProcess process = getProcess(pid);
598             String name = process.getName();
599             return new StringType(name);
600         } else {
601             return null;
602         }
603     }
604
605     @Override
606     public @Nullable DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException {
607         if (pid > 0) {
608             OSProcess process = getProcess(pid);
609             double cpuUsageRaw = (process.getKernelTime() + process.getUserTime()) / process.getUpTime();
610             BigDecimal cpuUsage = getPercentsValue(cpuUsageRaw);
611             return new DecimalType(cpuUsage);
612         } else {
613             return null;
614         }
615     }
616
617     @Override
618     public @Nullable DecimalType getProcessMemoryUsage(int pid) throws DeviceNotFoundException {
619         if (pid > 0) {
620             OSProcess process = getProcess(pid);
621             long memortInBytes = process.getResidentSetSize();
622             long memoryInMB = getSizeInMB(memortInBytes);
623             return new DecimalType(memoryInMB);
624         } else {
625             return null;
626         }
627     }
628
629     @Override
630     public @Nullable StringType getProcessPath(int pid) throws DeviceNotFoundException {
631         if (pid > 0) {
632             OSProcess process = getProcess(pid);
633             String path = process.getPath();
634             return new StringType(path);
635         } else {
636             return null;
637         }
638     }
639
640     @Override
641     public @Nullable DecimalType getProcessThreads(int pid) throws DeviceNotFoundException {
642         if (pid > 0) {
643             OSProcess process = getProcess(pid);
644             int threadCount = process.getThreadCount();
645             return new DecimalType(threadCount);
646         } else {
647             return null;
648         }
649     }
650 }