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