[systeminfo] Add CPU load channel, update dependencies (#13292)

* Add CPU load channel, update dependencies
* use PercentType, correct process CPU load
* Add and fix test

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
This commit is contained in:
Mark Herwege 2022-09-11 11:06:52 +02:00 committed by GitHub
parent 55e27c8c12
commit 909d390581
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 97 additions and 29 deletions

View File

@ -65,7 +65,7 @@ In the list below, you can find, how are channel group and channels id`s related
**thing** `computer` **thing** `computer`
* **group** `memory` * **group** `memory`
* **channel** `available, total, used, availablePercent, usedPercent` * **channel** `available, total, used, availablePercent, usedPercent, usedHeapPercent, availableHeap`
* **group** `swap` * **group** `swap`
* **channel** `available, total, used, availablePercent, usedPercent` * **channel** `available, total, used, availablePercent, usedPercent`
* **group** `storage` (deviceIndex) * **group** `storage` (deviceIndex)
@ -77,7 +77,7 @@ In the list below, you can find, how are channel group and channels id`s related
* **group** `battery` (deviceIndex) * **group** `battery` (deviceIndex)
* **channel** `name, remainingCapacity, remainingTime` * **channel** `name, remainingCapacity, remainingTime`
* **group** `cpu` * **group** `cpu`
* **channel** `name, description, load1, load5, load15, uptime` * **channel** `name, description, load, load1, load5, load15, uptime, threads`
* **group** `sensors` * **group** `sensors`
* **channel** `cpuTemp, cpuVoltage, fanSpeed` * **channel** `cpuTemp, cpuVoltage, fanSpeed`
* **group** `network` (deviceIndex) * **group** `network` (deviceIndex)
@ -104,12 +104,14 @@ The binding introduces the following channels:
| Channel ID | Channel Description | Supported item type | Default priority | Advanced | | Channel ID | Channel Description | Supported item type | Default priority | Advanced |
|--------------------|------------------------------------------------------------------|---------------------|------------------|----------| |--------------------|------------------------------------------------------------------|---------------------|------------------|----------|
| load | CPU Load (total or by process) in % | Number:Dimensionless| High | False |
| load1 | Load for the last 1 minute | Number | Medium | True | | load1 | Load for the last 1 minute | Number | Medium | True |
| load5 | Load for the last 5 minutes | Number | Medium | True | | load5 | Load for the last 5 minutes | Number | Medium | True |
| load15 | Load for the last 15 minutes | Number | Medium | True | | load15 | Load for the last 15 minutes | Number | Medium | True |
| threads | Number of threads currently running | Number | Medium | True | | threads | Number of threads currently running or for the process | Number | Medium | True |
| path | The full path of the process | String | Low | False |
| uptime | System uptime (time after start) in minutes | Number | Medium | True | | uptime | System uptime (time after start) in minutes | Number | Medium | True |
| name | Name of the device | String | Low | False | | name | Name of the device or process | String | Low | False |
| available | Available size in MB | Number | High | False | | available | Available size in MB | Number | High | False |
| used | Used size in MB | Number | High | False | | used | Used size in MB | Number | High | False |
| total | Total size in MB | Number | Low | False | | total | Total size in MB | Number | Low | False |
@ -148,6 +150,9 @@ It has the following options:
- **Medium** - **Medium**
- **Low** - **Low**
The ''load'' channel will update total or by process CPU load at the frequency defined by the priority update interval, by default high priority, every second.
The value corresponds to the average CPU load over the interval.
Channels from group ''process'' have additional configuration parameter - PID (Process identifier). Channels from group ''process'' have additional configuration parameter - PID (Process identifier).
This parameter is used as 'deviceIndex' and defines which process is tracked from the channel. This parameter is used as 'deviceIndex' and defines which process is tracked from the channel.
This makes the channels from this groups very flexible - they can change its PID dynamically. This makes the channels from this groups very flexible - they can change its PID dynamically.
@ -190,6 +195,7 @@ Number Network_PacketsReceived "Packets received" <returnpipe> { chann
/* CPU information*/ /* CPU information*/
String CPU_Name "Name" <none> { channel="systeminfo:computer:work:cpu#name" } String CPU_Name "Name" <none> { channel="systeminfo:computer:work:cpu#name" }
String CPU_Description "Description" <none> { channel="systeminfo:computer:work:cpu#description" } String CPU_Description "Description" <none> { channel="systeminfo:computer:work:cpu#description" }
Number CPU_Load "CPU Load" <none> { channel="systeminfo:computer:work:cpu#load" }
Number CPU_Load1 "Load (1 min)" <none> { channel="systeminfo:computer:work:cpu#load1" } Number CPU_Load1 "Load (1 min)" <none> { channel="systeminfo:computer:work:cpu#load1" }
Number CPU_Load5 "Load (5 min)" <none> { channel="systeminfo:computer:work:cpu#load5" } Number CPU_Load5 "Load (5 min)" <none> { channel="systeminfo:computer:work:cpu#load5" }
Number CPU_Load15 "Load (15 min)" <none> { channel="systeminfo:computer:work:cpu#load15" } Number CPU_Load15 "Load (15 min)" <none> { channel="systeminfo:computer:work:cpu#load15" }

View File

@ -22,20 +22,26 @@
<dependency> <dependency>
<groupId>net.java.dev.jna</groupId> <groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId> <artifactId>jna-platform</artifactId>
<version>5.9.0</version> <version>5.12.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.java.dev.jna</groupId> <groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId> <artifactId>jna</artifactId>
<version>5.9.0</version> <version>5.12.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.oshi</groupId> <groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId> <artifactId>oshi-core</artifactId>
<version>5.8.2</version> <version>6.2.2</version>
<scope>compile</scope> <scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -4,9 +4,9 @@
<feature name="openhab-binding-systeminfo" description="System Info Binding" version="${project.version}"> <feature name="openhab-binding-systeminfo" description="System Info Binding" version="${project.version}">
<feature>openhab-runtime-base</feature> <feature>openhab-runtime-base</feature>
<bundle dependency="true">mvn:net.java.dev.jna/jna/5.9.0</bundle> <bundle dependency="true">mvn:net.java.dev.jna/jna/5.12.1</bundle>
<bundle dependency="true">mvn:net.java.dev.jna/jna-platform/5.9.0</bundle> <bundle dependency="true">mvn:net.java.dev.jna/jna-platform/5.12.1</bundle>
<bundle dependency="true">mvn:com.github.oshi/oshi-core/5.8.2</bundle> <bundle dependency="true">mvn:com.github.oshi/oshi-core/6.2.2</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.systeminfo/${project.version}</bundle> <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.systeminfo/${project.version}</bundle>
</feature> </feature>
</features> </features>

View File

@ -28,7 +28,7 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.systeminfo.internal.model.DeviceNotFoundException; import org.openhab.binding.systeminfo.internal.model.DeviceNotFoundException;
import org.openhab.binding.systeminfo.internal.model.SysteminfoInterface; import org.openhab.binding.systeminfo.internal.model.SysteminfoInterface;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units; import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Channel; import org.openhab.core.thing.Channel;
@ -294,8 +294,8 @@ public class SysteminfoHandler extends BaseThingHandler {
state = new QuantityType<>(Runtime.getRuntime().freeMemory(), Units.BYTE); state = new QuantityType<>(Runtime.getRuntime().freeMemory(), Units.BYTE);
break; break;
case CHANNEL_MEMORY_USED_HEAP_PERCENT: case CHANNEL_MEMORY_USED_HEAP_PERCENT:
state = new DecimalType((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) state = new QuantityType<>((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
* 100 / Runtime.getRuntime().maxMemory()); * 100 / Runtime.getRuntime().maxMemory(), Units.PERCENT);
break; break;
case CHANNEL_DISPLAY_INFORMATION: case CHANNEL_DISPLAY_INFORMATION:
state = systeminfo.getDisplayInformation(deviceIndex); state = systeminfo.getDisplayInformation(deviceIndex);
@ -318,6 +318,10 @@ public class SysteminfoHandler extends BaseThingHandler {
case CHANNEL_SENSORS_FAN_SPEED: case CHANNEL_SENSORS_FAN_SPEED:
state = systeminfo.getSensorsFanSpeed(deviceIndex); state = systeminfo.getSensorsFanSpeed(deviceIndex);
break; break;
case CHANNEL_CPU_LOAD:
PercentType cpuLoad = systeminfo.getSystemCpuLoad();
state = (cpuLoad != null) ? new QuantityType<>(cpuLoad, Units.PERCENT) : null;
break;
case CHANNEL_CPU_LOAD_1: case CHANNEL_CPU_LOAD_1:
state = systeminfo.getCpuLoad1(); state = systeminfo.getCpuLoad1();
break; break;
@ -427,7 +431,8 @@ public class SysteminfoHandler extends BaseThingHandler {
state = systeminfo.getNetworkPacketsSent(deviceIndex); state = systeminfo.getNetworkPacketsSent(deviceIndex);
break; break;
case CHANNEL_PROCESS_LOAD: case CHANNEL_PROCESS_LOAD:
state = systeminfo.getProcessCpuUsage(deviceIndex); PercentType processLoad = systeminfo.getProcessCpuUsage(deviceIndex);
state = (processLoad != null) ? new QuantityType<>(processLoad, Units.PERCENT) : null;
break; break;
case CHANNEL_PROCESS_MEMORY: case CHANNEL_PROCESS_MEMORY:
state = systeminfo.getProcessMemoryUsage(deviceIndex); state = systeminfo.getProcessMemoryUsage(deviceIndex);

View File

@ -14,11 +14,14 @@ package org.openhab.binding.systeminfo.internal.model;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -74,6 +77,12 @@ public class OSHISysteminfo implements SysteminfoInterface {
private @NonNullByDefault({}) List<PowerSource> powerSources; private @NonNullByDefault({}) List<PowerSource> powerSources;
private @NonNullByDefault({}) List<HWDiskStore> drives; private @NonNullByDefault({}) List<HWDiskStore> drives;
// Array containing cpu tick info to calculate CPU load, according to oshi doc:
// 8 long values representing time spent in User, Nice, System, Idle, IOwait, IRQ, SoftIRQ, and Steal states
private long[] ticks = new long[8];
// Map containing previous process state to calculate load by process
private Map<Integer, OSProcess> processTicks = new HashMap<>();
public static final int PRECISION_AFTER_DECIMAL_SIGN = 1; public static final int PRECISION_AFTER_DECIMAL_SIGN = 1;
/** /**
@ -485,6 +494,14 @@ public class OSHISysteminfo implements SysteminfoInterface {
return timeInMinutes; return timeInMinutes;
} }
@Override
public @Nullable PercentType getSystemCpuLoad() {
PercentType load = (ticks[0] > 0) ? new PercentType(getPercentsValue(cpu.getSystemCpuLoadBetweenTicks(ticks)))
: null;
ticks = cpu.getSystemCpuLoadTicks();
return load;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
@ -518,10 +535,10 @@ public class OSHISysteminfo implements SysteminfoInterface {
return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad); return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
} }
private BigDecimal getAvarageCpuLoad(int timeInMunutes) { private BigDecimal getAvarageCpuLoad(int timeInMinutes) {
// This parameter is specified in OSHI Javadoc // This parameter is specified in OSHI Javadoc
int index; int index;
switch (timeInMunutes) { switch (timeInMinutes) {
case 1: case 1:
index = 0; index = 0;
break; break;
@ -603,12 +620,14 @@ public class OSHISysteminfo implements SysteminfoInterface {
} }
@Override @Override
public @Nullable DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException { public @Nullable PercentType getProcessCpuUsage(int pid) throws DeviceNotFoundException {
if (pid > 0) { if (pid > 0) {
OSProcess process = getProcess(pid); OSProcess process = getProcess(pid);
double cpuUsageRaw = (process.getKernelTime() + process.getUserTime()) / process.getUpTime(); PercentType load = (processTicks.containsKey(pid))
BigDecimal cpuUsage = getPercentsValue(cpuUsageRaw); ? new PercentType(getPercentsValue(process.getProcessCpuLoadBetweenTicks(processTicks.get(pid))))
return new DecimalType(cpuUsage); : null;
processTicks.put(pid, process);
return load;
} else { } else {
return null; return null;
} }

View File

@ -15,6 +15,7 @@ package org.openhab.binding.systeminfo.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
/** /**
@ -71,6 +72,13 @@ public interface SysteminfoInterface {
*/ */
public DecimalType getCpuPhysicalCores(); public DecimalType getCpuPhysicalCores();
/**
* Returns the system cpu load.
*
* @return the system cpu load between 0 and 1 or null, if no information is available
*/
public @Nullable PercentType getSystemCpuLoad();
/** /**
* Returns the system load average for the last minute. * Returns the system load average for the last minute.
* *
@ -411,7 +419,7 @@ public interface SysteminfoInterface {
* @return - percentage value /0-100/ * @return - percentage value /0-100/
* @throws DeviceNotFoundException - thrown if process with this PID can not be found * @throws DeviceNotFoundException - thrown if process with this PID can not be found
*/ */
public @Nullable DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException; public @Nullable PercentType getProcessCpuUsage(int pid) throws DeviceNotFoundException;
/** /**
* Returns the size of RAM memory only usage of the process * Returns the size of RAM memory only usage of the process

View File

@ -62,6 +62,8 @@ channel-type.systeminfo.information.label = Display Information
channel-type.systeminfo.information.description = Product, manufacturer, SN, width and height of the display in cm channel-type.systeminfo.information.description = Product, manufacturer, SN, width and height of the display in cm
channel-type.systeminfo.ip.label = IP Address channel-type.systeminfo.ip.label = IP Address
channel-type.systeminfo.ip.description = Host IP address of the network channel-type.systeminfo.ip.description = Host IP address of the network
channel-type.systeminfo.cpuLoad.label = CPU Load
channel-type.systeminfo.cpuLoad.description = CPU load in percent
channel-type.systeminfo.loadAverage.label = Load Average channel-type.systeminfo.loadAverage.label = Load Average
channel-type.systeminfo.loadAverage.description = Load as a number of processes for the last 1,5 or 15 minutes channel-type.systeminfo.loadAverage.description = Load as a number of processes for the last 1,5 or 15 minutes
channel-type.systeminfo.load_process.label = Load channel-type.systeminfo.load_process.label = Load

View File

@ -107,6 +107,7 @@
<channels> <channels>
<channel id="name" typeId="name"/> <channel id="name" typeId="name"/>
<channel id="description" typeId="description"/> <channel id="description" typeId="description"/>
<channel id="load" typeId="cpuLoad"/>
<channel id="load1" typeId="loadAverage"/> <channel id="load1" typeId="loadAverage"/>
<channel id="load5" typeId="loadAverage"/> <channel id="load5" typeId="loadAverage"/>
<channel id="load15" typeId="loadAverage"/> <channel id="load15" typeId="loadAverage"/>
@ -288,13 +289,21 @@
</channel-type> </channel-type>
<channel-type id="load_process"> <channel-type id="load_process">
<item-type>Number</item-type> <item-type>Number:Dimensionless</item-type>
<label>Load</label> <label>Load</label>
<description>Load in percent</description> <description>Load in percent</description>
<state readOnly="true" pattern="%.1f %%"/> <state readOnly="true" pattern="%.1f %%"/>
<config-description-ref uri="channel-type:systeminfo:highpriority_process"/> <config-description-ref uri="channel-type:systeminfo:highpriority_process"/>
</channel-type> </channel-type>
<channel-type id="cpuLoad">
<item-type>Number:Dimensionless</item-type>
<label>CPU Load</label>
<description>CPU load in percent</description>
<state readOnly="true" pattern="%.1f %%"/>
<config-description-ref uri="channel-type:systeminfo:highpriority"/>
</channel-type>
<channel-type id="loadAverage" advanced="true"> <channel-type id="loadAverage" advanced="true">
<item-type>Number</item-type> <item-type>Number</item-type>
<label>Load Average</label> <label>Load Average</label>

View File

@ -34,8 +34,8 @@ Fragment-Host: org.openhab.binding.systeminfo
org.jsr-305;version='[3.0.2,3.0.3)',\ org.jsr-305;version='[3.0.2,3.0.3)',\
tech.units.indriya;version='[2.1.2,2.1.3)',\ tech.units.indriya;version='[2.1.2,2.1.3)',\
uom-lib-common;version='[2.1.0,2.1.1)',\ uom-lib-common;version='[2.1.0,2.1.1)',\
com.sun.jna;version='[5.9.0,5.9.1)',\ com.sun.jna;version='[5.12.1,5.12.2)',\
com.sun.jna.platform;version='[5.9.0,5.9.1)',\ com.sun.jna.platform;version='[5.12.1,5.12.2)',\
si-units;version='[2.1.0,2.1.1)',\ si-units;version='[2.1.0,2.1.1)',\
si.uom.si-quantity;version='[2.1.0,2.1.1)',\ si.uom.si-quantity;version='[2.1.0,2.1.1)',\
junit-jupiter-api;version='[5.8.1,5.8.2)',\ junit-jupiter-api;version='[5.8.1,5.8.2)',\

View File

@ -23,17 +23,17 @@
<dependency> <dependency>
<groupId>net.java.dev.jna</groupId> <groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId> <artifactId>jna-platform</artifactId>
<version>5.9.0</version> <version>5.12.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.java.dev.jna</groupId> <groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId> <artifactId>jna</artifactId>
<version>5.9.0</version> <version>5.12.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.oshi</groupId> <groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId> <artifactId>oshi-core</artifactId>
<version>5.8.2</version> <version>6.2.2</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>

View File

@ -45,6 +45,7 @@ import org.openhab.core.items.ItemRegistry;
import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.StringItem; import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.test.java.JavaOSGiTest; import org.openhab.core.test.java.JavaOSGiTest;
import org.openhab.core.test.storage.VolatileStorageService; import org.openhab.core.test.storage.VolatileStorageService;
@ -346,6 +347,18 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, UnDefType.UNDEF); assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, UnDefType.UNDEF);
} }
@Test
public void assertChannelCpuLoadIsUpdated() {
String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD;
String acceptedItemType = "Number";
PercentType mockedCpuLoadValue = new PercentType(9);
when(mockedSystemInfo.getSystemCpuLoad()).thenReturn(mockedCpuLoadValue);
initializeThingWithChannel(channnelID, acceptedItemType);
assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoadValue);
}
@Test @Test
public void assertChannelCpuLoad1IsUpdated() { public void assertChannelCpuLoad1IsUpdated() {
String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD_1; String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD_1;
@ -1007,7 +1020,7 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
// The pid of the System idle process in Windows // The pid of the System idle process in Windows
int pid = 0; int pid = 0;
DecimalType mockedProcessLoad = new DecimalType(3); PercentType mockedProcessLoad = new PercentType(3);
when(mockedSystemInfo.getProcessCpuUsage(pid)).thenReturn(mockedProcessLoad); when(mockedSystemInfo.getProcessCpuUsage(pid)).thenReturn(mockedProcessLoad);
initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid); initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);