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