]> git.basschouten.com Git - openhab-addons.git/blob
7bb4b63badced4a2b99f09813dd953fbc88a23ea
[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
18 import org.apache.commons.lang3.ArrayUtils;
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({}) NetworkIF[] networks;
72     private @NonNullByDefault({}) Display[] displays;
73     private @NonNullByDefault({}) OSFileStore[] fileStores;
74     private @NonNullByDefault({}) PowerSource[] powerSources;
75     private @NonNullByDefault({}) 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 Object getDevice(Object @Nullable [] devices, int index) throws DeviceNotFoundException {
109         if ((devices == null) || (devices.length <= index)) {
110             throw new DeviceNotFoundException("Device with index: " + index + " can not be found!");
111         }
112         return devices[index];
113     }
114
115     private OSProcess getProcess(int pid) throws DeviceNotFoundException {
116         OSProcess process = operatingSystem.getProcess(pid);
117         if (process == null) {
118             throw new DeviceNotFoundException("Error while getting information for process with PID " + pid);
119         }
120         return process;
121     }
122
123     @Override
124     public StringType getOsFamily() {
125         String osFamily = operatingSystem.getFamily();
126         return new StringType(osFamily);
127     }
128
129     @Override
130     public StringType getOsManufacturer() {
131         String osManufacturer = operatingSystem.getManufacturer();
132         return new StringType(osManufacturer);
133     }
134
135     @Override
136     public StringType getOsVersion() {
137         String osVersion = operatingSystem.getVersionInfo().toString();
138         return new StringType(osVersion);
139     }
140
141     @Override
142     public StringType getCpuName() {
143         String name = cpu.getProcessorIdentifier().getName();
144         return new StringType(name);
145     }
146
147     @Override
148     public StringType getCpuDescription() {
149         String model = cpu.getProcessorIdentifier().getModel();
150         String family = cpu.getProcessorIdentifier().getFamily();
151         String serialNumber = computerSystem.getSerialNumber();
152         String identifier = cpu.getProcessorIdentifier().getIdentifier();
153         String vendor = cpu.getProcessorIdentifier().getVendor();
154         String architecture = cpu.getProcessorIdentifier().isCpu64bit() ? "64 bit" : "32 bit";
155         String descriptionFormatString = "Model: %s %s,family: %s, vendor: %s, sn: %s, identifier: %s ";
156         String description = String.format(descriptionFormatString, model, architecture, family, vendor, serialNumber,
157                 identifier);
158
159         return new StringType(description);
160     }
161
162     @Override
163     public DecimalType getCpuLogicalCores() {
164         int logicalProcessorCount = cpu.getLogicalProcessorCount();
165         return new DecimalType(logicalProcessorCount);
166     }
167
168     @Override
169     public DecimalType getCpuPhysicalCores() {
170         int physicalProcessorCount = cpu.getPhysicalProcessorCount();
171         return new DecimalType(physicalProcessorCount);
172     }
173
174     @Override
175     public DecimalType getMemoryTotal() {
176         long totalMemory = memory.getTotal();
177         totalMemory = getSizeInMB(totalMemory);
178         return new DecimalType(totalMemory);
179     }
180
181     @Override
182     public DecimalType getMemoryAvailable() {
183         long availableMemory = memory.getAvailable();
184         availableMemory = getSizeInMB(availableMemory);
185         return new DecimalType(availableMemory);
186     }
187
188     @Override
189     public DecimalType getMemoryUsed() {
190         long totalMemory = memory.getTotal();
191         long availableMemory = memory.getAvailable();
192         long usedMemory = totalMemory - availableMemory;
193         usedMemory = getSizeInMB(usedMemory);
194         return new DecimalType(usedMemory);
195     }
196
197     @Override
198     public DecimalType getStorageTotal(int index) throws DeviceNotFoundException {
199         OSFileStore fileStore = (OSFileStore) getDevice(fileStores, index);
200         fileStore.updateAtrributes();
201         long totalSpace = fileStore.getTotalSpace();
202         totalSpace = getSizeInMB(totalSpace);
203         return new DecimalType(totalSpace);
204     }
205
206     @Override
207     public DecimalType getStorageAvailable(int index) throws DeviceNotFoundException {
208         OSFileStore fileStore = (OSFileStore) getDevice(fileStores, index);
209         fileStore.updateAtrributes();
210         long freeSpace = fileStore.getUsableSpace();
211         freeSpace = getSizeInMB(freeSpace);
212         return new DecimalType(freeSpace);
213     }
214
215     @Override
216     public DecimalType getStorageUsed(int index) throws DeviceNotFoundException {
217         OSFileStore fileStore = (OSFileStore) getDevice(fileStores, index);
218         fileStore.updateAtrributes();
219         long totalSpace = fileStore.getTotalSpace();
220         long freeSpace = fileStore.getUsableSpace();
221         long usedSpace = totalSpace - freeSpace;
222         usedSpace = getSizeInMB(usedSpace);
223         return new DecimalType(usedSpace);
224     }
225
226     @Override
227     public @Nullable DecimalType getStorageAvailablePercent(int deviceIndex) throws DeviceNotFoundException {
228         OSFileStore fileStore = (OSFileStore) getDevice(fileStores, deviceIndex);
229         fileStore.updateAtrributes();
230         long totalSpace = fileStore.getTotalSpace();
231         long freeSpace = fileStore.getUsableSpace();
232         if (totalSpace > 0) {
233             double freePercentDecimal = (double) freeSpace / (double) totalSpace;
234             BigDecimal freePercent = getPercentsValue(freePercentDecimal);
235             return new DecimalType(freePercent);
236         } else {
237             return null;
238         }
239     }
240
241     @Override
242     public @Nullable DecimalType getStorageUsedPercent(int deviceIndex) throws DeviceNotFoundException {
243         OSFileStore fileStore = (OSFileStore) getDevice(fileStores, deviceIndex);
244         fileStore.updateAtrributes();
245         long totalSpace = fileStore.getTotalSpace();
246         long freeSpace = fileStore.getUsableSpace();
247         long usedSpace = totalSpace - freeSpace;
248         if (totalSpace > 0) {
249             double usedPercentDecimal = (double) usedSpace / (double) totalSpace;
250             BigDecimal usedPercent = getPercentsValue(usedPercentDecimal);
251             return new DecimalType(usedPercent);
252         } else {
253             return null;
254         }
255     }
256
257     @Override
258     public StringType getStorageName(int index) throws DeviceNotFoundException {
259         OSFileStore fileStore = (OSFileStore) getDevice(fileStores, index);
260         String name = fileStore.getName();
261         return new StringType(name);
262     }
263
264     @Override
265     public StringType getStorageType(int deviceIndex) throws DeviceNotFoundException {
266         OSFileStore fileStore = (OSFileStore) getDevice(fileStores, deviceIndex);
267         String type = fileStore.getType();
268         return new StringType(type);
269     }
270
271     @Override
272     public StringType getStorageDescription(int index) throws DeviceNotFoundException {
273         OSFileStore fileStore = (OSFileStore) getDevice(fileStores, index);
274         String description = fileStore.getDescription();
275         return new StringType(description);
276     }
277
278     @Override
279     public StringType getNetworkIp(int index) throws DeviceNotFoundException {
280         NetworkIF netInterface = (NetworkIF) getDevice(networks, index);
281         netInterface.updateAttributes();
282         String[] ipAddresses = netInterface.getIPv4addr();
283         String ipv4 = (String) getDevice(ipAddresses, 0);
284         return new StringType(ipv4);
285     }
286
287     @Override
288     public StringType getNetworkName(int index) throws DeviceNotFoundException {
289         NetworkIF netInterface = (NetworkIF) getDevice(networks, index);
290         String name = netInterface.getName();
291         return new StringType(name);
292     }
293
294     @Override
295     public StringType getNetworkDisplayName(int index) throws DeviceNotFoundException {
296         NetworkIF netInterface = (NetworkIF) getDevice(networks, index);
297         String adapterName = netInterface.getDisplayName();
298         return new StringType(adapterName);
299     }
300
301     @Override
302     public StringType getDisplayInformation(int index) throws DeviceNotFoundException {
303         Display display = (Display) getDevice(displays, index);
304
305         byte[] edid = display.getEdid();
306         String manufacturer = EdidUtil.getManufacturerID(edid);
307         String product = EdidUtil.getProductID(edid);
308         String serialNumber = EdidUtil.getSerialNo(edid);
309         int width = EdidUtil.getHcm(edid);
310         int height = EdidUtil.getVcm(edid);
311
312         String edidFormatString = "Product %s, manufacturer %s, SN: %s, Width: %d, Height: %d";
313         String edidInfo = String.format(edidFormatString, product, manufacturer, serialNumber, width, height);
314         return new StringType(edidInfo);
315     }
316
317     @Override
318     public @Nullable DecimalType getSensorsCpuTemperature() {
319         BigDecimal cpuTemp = new BigDecimal(sensors.getCpuTemperature());
320         cpuTemp = cpuTemp.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
321         return cpuTemp.signum() == 1 ? new DecimalType(cpuTemp) : null;
322     }
323
324     @Override
325     public @Nullable DecimalType getSensorsCpuVoltage() {
326         BigDecimal cpuVoltage = new BigDecimal(sensors.getCpuVoltage());
327         cpuVoltage = cpuVoltage.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
328         return cpuVoltage.signum() == 1 ? new DecimalType(cpuVoltage) : null;
329     }
330
331     @Override
332     public @Nullable DecimalType getSensorsFanSpeed(int index) throws DeviceNotFoundException {
333         int[] fanSpeeds = sensors.getFanSpeeds();
334         int speed = (int) getDevice(ArrayUtils.toObject(fanSpeeds), index);
335         return speed > 0 ? new DecimalType(speed) : null;
336     }
337
338     @Override
339     public @Nullable DecimalType getBatteryRemainingTime(int index) throws DeviceNotFoundException {
340         PowerSource powerSource = (PowerSource) getDevice(powerSources, index);
341         powerSource.updateAttributes();
342         double remainingTimeInSeconds = powerSource.getTimeRemainingEstimated();
343         // The getTimeRemaining() method returns (-1.0) if is calculating or (-2.0) if the time is unlimited.
344         BigDecimal remainingTime = getTimeInMinutes(remainingTimeInSeconds);
345         return remainingTime.signum() == 1 ? new DecimalType(remainingTime) : null;
346     }
347
348     @Override
349     public DecimalType getBatteryRemainingCapacity(int index) throws DeviceNotFoundException {
350         PowerSource powerSource = (PowerSource) getDevice(powerSources, index);
351         powerSource.updateAttributes();
352         double remainingCapacity = powerSource.getRemainingCapacityPercent();
353         BigDecimal remainingCapacityPercents = getPercentsValue(remainingCapacity);
354         return new DecimalType(remainingCapacityPercents);
355     }
356
357     @Override
358     public StringType getBatteryName(int index) throws DeviceNotFoundException {
359         PowerSource powerSource = (PowerSource) getDevice(powerSources, index);
360         String name = powerSource.getName();
361         return new StringType(name);
362     }
363
364     @Override
365     public @Nullable DecimalType getMemoryAvailablePercent() {
366         long availableMemory = memory.getAvailable();
367         long totalMemory = memory.getTotal();
368         if (totalMemory > 0) {
369             double freePercentDecimal = (double) availableMemory / (double) totalMemory;
370             BigDecimal freePercent = getPercentsValue(freePercentDecimal);
371             return new DecimalType(freePercent);
372         } else {
373             return null;
374         }
375     }
376
377     @Override
378     public @Nullable DecimalType getMemoryUsedPercent() {
379         long availableMemory = memory.getAvailable();
380         long totalMemory = memory.getTotal();
381         long usedMemory = totalMemory - availableMemory;
382         if (totalMemory > 0) {
383             double usedPercentDecimal = (double) usedMemory / (double) totalMemory;
384             BigDecimal usedPercent = getPercentsValue(usedPercentDecimal);
385             return new DecimalType(usedPercent);
386         } else {
387             return null;
388         }
389     }
390
391     @Override
392     public StringType getDriveName(int deviceIndex) throws DeviceNotFoundException {
393         HWDiskStore drive = (HWDiskStore) getDevice(drives, deviceIndex);
394         String name = drive.getName();
395         return new StringType(name);
396     }
397
398     @Override
399     public StringType getDriveModel(int deviceIndex) throws DeviceNotFoundException {
400         HWDiskStore drive = (HWDiskStore) getDevice(drives, deviceIndex);
401         String model = drive.getModel();
402         return new StringType(model);
403     }
404
405     @Override
406     public StringType getDriveSerialNumber(int deviceIndex) throws DeviceNotFoundException {
407         HWDiskStore drive = (HWDiskStore) getDevice(drives, deviceIndex);
408         String serialNumber = drive.getSerial();
409         return new StringType(serialNumber);
410     }
411
412     @Override
413     public @Nullable DecimalType getSwapTotal() {
414         long swapTotal = memory.getVirtualMemory().getSwapTotal();
415         swapTotal = getSizeInMB(swapTotal);
416         return new DecimalType(swapTotal);
417     }
418
419     @Override
420     public @Nullable DecimalType getSwapAvailable() {
421         long swapTotal = memory.getVirtualMemory().getSwapTotal();
422         long swapUsed = memory.getVirtualMemory().getSwapUsed();
423         long swapAvailable = swapTotal - swapUsed;
424         swapAvailable = getSizeInMB(swapAvailable);
425         return new DecimalType(swapAvailable);
426     }
427
428     @Override
429     public @Nullable DecimalType getSwapUsed() {
430         long swapUsed = memory.getVirtualMemory().getSwapUsed();
431         swapUsed = getSizeInMB(swapUsed);
432         return new DecimalType(swapUsed);
433     }
434
435     @Override
436     public @Nullable DecimalType getSwapAvailablePercent() {
437         long swapTotal = memory.getVirtualMemory().getSwapTotal();
438         long swapUsed = memory.getVirtualMemory().getSwapUsed();
439         long swapAvailable = swapTotal - swapUsed;
440         if (swapTotal > 0) {
441             double swapAvailablePercentDecimal = (double) swapAvailable / (double) swapTotal;
442             BigDecimal swapAvailablePercent = getPercentsValue(swapAvailablePercentDecimal);
443             return new DecimalType(swapAvailablePercent);
444         } else {
445             return null;
446         }
447     }
448
449     @Override
450     public @Nullable DecimalType getSwapUsedPercent() {
451         long swapTotal = memory.getVirtualMemory().getSwapTotal();
452         long swapUsed = memory.getVirtualMemory().getSwapUsed();
453         if (swapTotal > 0) {
454             double swapUsedPercentDecimal = (double) swapUsed / (double) swapTotal;
455             BigDecimal swapUsedPercent = getPercentsValue(swapUsedPercentDecimal);
456             return new DecimalType(swapUsedPercent);
457         } else {
458             return null;
459         }
460     }
461
462     private long getSizeInMB(long sizeInBytes) {
463         return Math.round(sizeInBytes / (1024D * 1024));
464     }
465
466     private BigDecimal getPercentsValue(double decimalFraction) {
467         BigDecimal result = new BigDecimal(decimalFraction * 100);
468         result = result.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
469         return result;
470     }
471
472     private BigDecimal getTimeInMinutes(double timeInSeconds) {
473         BigDecimal timeInMinutes = new BigDecimal(timeInSeconds / 60);
474         timeInMinutes = timeInMinutes.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.UP);
475         return timeInMinutes;
476     }
477
478     /**
479      * {@inheritDoc}
480      *
481      * This information is available only on Mac and Linux OS.
482      */
483     @Override
484     public @Nullable DecimalType getCpuLoad1() {
485         BigDecimal avarageCpuLoad = getAvarageCpuLoad(1);
486         return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
487     }
488
489     /**
490      * {@inheritDoc}
491      *
492      * This information is available only on Mac and Linux OS.
493      */
494     @Override
495     public @Nullable DecimalType getCpuLoad5() {
496         BigDecimal avarageCpuLoad = getAvarageCpuLoad(5);
497         return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
498     }
499
500     /**
501      * {@inheritDoc}
502      *
503      * This information is available only on Mac and Linux OS.
504      */
505     @Override
506     public @Nullable DecimalType getCpuLoad15() {
507         BigDecimal avarageCpuLoad = getAvarageCpuLoad(15);
508         return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
509     }
510
511     private BigDecimal getAvarageCpuLoad(int timeInMunutes) {
512         // This parameter is specified in OSHI Javadoc
513         int index;
514         switch (timeInMunutes) {
515             case 1:
516                 index = 0;
517                 break;
518             case 5:
519                 index = 1;
520                 break;
521             case 15:
522                 index = 2;
523                 break;
524             default:
525                 index = 2;
526         }
527         double processorLoads[] = cpu.getSystemLoadAverage(index + 1);
528         BigDecimal result = new BigDecimal(processorLoads[index]);
529         result = result.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
530         return result;
531     }
532
533     @Override
534     public DecimalType getCpuUptime() {
535         long seconds = operatingSystem.getSystemUptime();
536         return new DecimalType(getTimeInMinutes(seconds));
537     }
538
539     @Override
540     public DecimalType getCpuThreads() {
541         int threadCount = operatingSystem.getThreadCount();
542         return new DecimalType(threadCount);
543     }
544
545     @Override
546     public StringType getNetworkMac(int networkIndex) throws DeviceNotFoundException {
547         NetworkIF network = (NetworkIF) getDevice(networks, networkIndex);
548         String mac = network.getMacaddr();
549         return new StringType(mac);
550     }
551
552     @Override
553     public DecimalType getNetworkPacketsReceived(int networkIndex) throws DeviceNotFoundException {
554         NetworkIF network = (NetworkIF) getDevice(networks, networkIndex);
555         network.updateAttributes();
556         long packRecv = network.getPacketsRecv();
557         return new DecimalType(packRecv);
558     }
559
560     @Override
561     public DecimalType getNetworkPacketsSent(int networkIndex) throws DeviceNotFoundException {
562         NetworkIF network = (NetworkIF) getDevice(networks, networkIndex);
563         network.updateAttributes();
564         long packSent = network.getPacketsSent();
565         return new DecimalType(packSent);
566     }
567
568     @Override
569     public DecimalType getNetworkDataSent(int networkIndex) throws DeviceNotFoundException {
570         NetworkIF network = (NetworkIF) getDevice(networks, networkIndex);
571         network.updateAttributes();
572         long bytesSent = network.getBytesSent();
573         return new DecimalType(getSizeInMB(bytesSent));
574     }
575
576     @Override
577     public DecimalType getNetworkDataReceived(int networkIndex) throws DeviceNotFoundException {
578         NetworkIF network = (NetworkIF) getDevice(networks, networkIndex);
579         network.updateAttributes();
580         long bytesRecv = network.getBytesRecv();
581         return new DecimalType(getSizeInMB(bytesRecv));
582     }
583
584     @Override
585     public @Nullable StringType getProcessName(int pid) throws DeviceNotFoundException {
586         if (pid > 0) {
587             OSProcess process = getProcess(pid);
588             String name = process.getName();
589             return new StringType(name);
590         } else {
591             return null;
592         }
593     }
594
595     @Override
596     public @Nullable DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException {
597         if (pid > 0) {
598             OSProcess process = getProcess(pid);
599             double cpuUsageRaw = (process.getKernelTime() + process.getUserTime()) / process.getUpTime();
600             BigDecimal cpuUsage = getPercentsValue(cpuUsageRaw);
601             return new DecimalType(cpuUsage);
602         } else {
603             return null;
604         }
605     }
606
607     @Override
608     public @Nullable DecimalType getProcessMemoryUsage(int pid) throws DeviceNotFoundException {
609         if (pid > 0) {
610             OSProcess process = getProcess(pid);
611             long memortInBytes = process.getResidentSetSize();
612             long memoryInMB = getSizeInMB(memortInBytes);
613             return new DecimalType(memoryInMB);
614         } else {
615             return null;
616         }
617     }
618
619     @Override
620     public @Nullable StringType getProcessPath(int pid) throws DeviceNotFoundException {
621         if (pid > 0) {
622             OSProcess process = getProcess(pid);
623             String path = process.getPath();
624             return new StringType(path);
625         } else {
626             return null;
627         }
628     }
629
630     @Override
631     public @Nullable DecimalType getProcessThreads(int pid) throws DeviceNotFoundException {
632         if (pid > 0) {
633             OSProcess process = getProcess(pid);
634             int threadCount = process.getThreadCount();
635             return new DecimalType(threadCount);
636         } else {
637             return null;
638         }
639     }
640 }