[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`
* **group** `memory`
* **channel** `available, total, used, availablePercent, usedPercent`
* **channel** `available, total, used, availablePercent, usedPercent, usedHeapPercent, availableHeap`
* **group** `swap`
* **channel** `available, total, used, availablePercent, usedPercent`
* **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)
* **channel** `name, remainingCapacity, remainingTime`
* **group** `cpu`
* **channel** `name, description, load1, load5, load15, uptime`
* **channel** `name, description, load, load1, load5, load15, uptime, threads`
* **group** `sensors`
* **channel** `cpuTemp, cpuVoltage, fanSpeed`
* **group** `network` (deviceIndex)
@ -104,12 +104,14 @@ The binding introduces the following channels:
| 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 |
| load5 | Load for the last 5 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 |
| 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 |
| used | Used size in MB | Number | High | False |
| total | Total size in MB | Number | Low | False |
@ -148,6 +150,9 @@ It has the following options:
- **Medium**
- **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).
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.
@ -190,6 +195,7 @@ Number Network_PacketsReceived "Packets received" <returnpipe> { chann
/* CPU information*/
String CPU_Name "Name" <none> { channel="systeminfo:computer:work:cpu#name" }
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_Load5 "Load (5 min)" <none> { channel="systeminfo:computer:work:cpu#load5" }
Number CPU_Load15 "Load (15 min)" <none> { channel="systeminfo:computer:work:cpu#load15" }

View File

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

View File

@ -4,9 +4,9 @@
<feature name="openhab-binding-systeminfo" description="System Info Binding" version="${project.version}">
<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-platform/5.9.0</bundle>
<bundle dependency="true">mvn:com.github.oshi/oshi-core/5.8.2</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.12.1</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>
</feature>
</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.SysteminfoInterface;
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.unit.Units;
import org.openhab.core.thing.Channel;
@ -294,8 +294,8 @@ public class SysteminfoHandler extends BaseThingHandler {
state = new QuantityType<>(Runtime.getRuntime().freeMemory(), Units.BYTE);
break;
case CHANNEL_MEMORY_USED_HEAP_PERCENT:
state = new DecimalType((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
* 100 / Runtime.getRuntime().maxMemory());
state = new QuantityType<>((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
* 100 / Runtime.getRuntime().maxMemory(), Units.PERCENT);
break;
case CHANNEL_DISPLAY_INFORMATION:
state = systeminfo.getDisplayInformation(deviceIndex);
@ -318,6 +318,10 @@ public class SysteminfoHandler extends BaseThingHandler {
case CHANNEL_SENSORS_FAN_SPEED:
state = systeminfo.getSensorsFanSpeed(deviceIndex);
break;
case CHANNEL_CPU_LOAD:
PercentType cpuLoad = systeminfo.getSystemCpuLoad();
state = (cpuLoad != null) ? new QuantityType<>(cpuLoad, Units.PERCENT) : null;
break;
case CHANNEL_CPU_LOAD_1:
state = systeminfo.getCpuLoad1();
break;
@ -427,7 +431,8 @@ public class SysteminfoHandler extends BaseThingHandler {
state = systeminfo.getNetworkPacketsSent(deviceIndex);
break;
case CHANNEL_PROCESS_LOAD:
state = systeminfo.getProcessCpuUsage(deviceIndex);
PercentType processLoad = systeminfo.getProcessCpuUsage(deviceIndex);
state = (processLoad != null) ? new QuantityType<>(processLoad, Units.PERCENT) : null;
break;
case CHANNEL_PROCESS_MEMORY:
state = systeminfo.getProcessMemoryUsage(deviceIndex);

View File

@ -14,11 +14,14 @@ package org.openhab.binding.systeminfo.internal.model;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
@ -74,6 +77,12 @@ public class OSHISysteminfo implements SysteminfoInterface {
private @NonNullByDefault({}) List<PowerSource> powerSources;
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;
/**
@ -338,7 +347,7 @@ public class OSHISysteminfo implements SysteminfoInterface {
@Override
public @Nullable DecimalType getSensorsFanSpeed(int index) throws DeviceNotFoundException {
int[] fanSpeeds = sensors.getFanSpeeds();
int speed = 0;// 0 means unable to measure speed
int speed = 0; // 0 means unable to measure speed
if (index < fanSpeeds.length) {
speed = fanSpeeds[index];
}
@ -485,6 +494,14 @@ public class OSHISysteminfo implements SysteminfoInterface {
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}
*
@ -518,10 +535,10 @@ public class OSHISysteminfo implements SysteminfoInterface {
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
int index;
switch (timeInMunutes) {
switch (timeInMinutes) {
case 1:
index = 0;
break;
@ -603,12 +620,14 @@ public class OSHISysteminfo implements SysteminfoInterface {
}
@Override
public @Nullable DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException {
public @Nullable PercentType getProcessCpuUsage(int pid) throws DeviceNotFoundException {
if (pid > 0) {
OSProcess process = getProcess(pid);
double cpuUsageRaw = (process.getKernelTime() + process.getUserTime()) / process.getUpTime();
BigDecimal cpuUsage = getPercentsValue(cpuUsageRaw);
return new DecimalType(cpuUsage);
PercentType load = (processTicks.containsKey(pid))
? new PercentType(getPercentsValue(process.getProcessCpuLoadBetweenTicks(processTicks.get(pid))))
: null;
processTicks.put(pid, process);
return load;
} else {
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.Nullable;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
/**
@ -71,6 +72,13 @@ public interface SysteminfoInterface {
*/
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.
*
@ -411,7 +419,7 @@ public interface SysteminfoInterface {
* @return - percentage value /0-100/
* @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

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.ip.label = IP Address
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.description = Load as a number of processes for the last 1,5 or 15 minutes
channel-type.systeminfo.load_process.label = Load

View File

@ -107,6 +107,7 @@
<channels>
<channel id="name" typeId="name"/>
<channel id="description" typeId="description"/>
<channel id="load" typeId="cpuLoad"/>
<channel id="load1" typeId="loadAverage"/>
<channel id="load5" typeId="loadAverage"/>
<channel id="load15" typeId="loadAverage"/>
@ -288,13 +289,21 @@
</channel-type>
<channel-type id="load_process">
<item-type>Number</item-type>
<item-type>Number:Dimensionless</item-type>
<label>Load</label>
<description>Load in percent</description>
<state readOnly="true" pattern="%.1f %%"/>
<config-description-ref uri="channel-type:systeminfo:highpriority_process"/>
</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">
<item-type>Number</item-type>
<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)',\
tech.units.indriya;version='[2.1.2,2.1.3)',\
uom-lib-common;version='[2.1.0,2.1.1)',\
com.sun.jna;version='[5.9.0,5.9.1)',\
com.sun.jna.platform;version='[5.9.0,5.9.1)',\
com.sun.jna;version='[5.12.1,5.12.2)',\
com.sun.jna.platform;version='[5.12.1,5.12.2)',\
si-units;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)',\

View File

@ -23,17 +23,17 @@
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.9.0</version>
<version>5.12.1</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.9.0</version>
<version>5.12.1</version>
</dependency>
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>5.8.2</version>
<version>6.2.2</version>
<exclusions>
<exclusion>
<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.StringItem;
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.test.java.JavaOSGiTest;
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);
}
@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
public void assertChannelCpuLoad1IsUpdated() {
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
int pid = 0;
DecimalType mockedProcessLoad = new DecimalType(3);
PercentType mockedProcessLoad = new PercentType(3);
when(mockedSystemInfo.getProcessCpuUsage(pid)).thenReturn(mockedProcessLoad);
initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);