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