Skip to content

Commit

Permalink
[systeminfo] Add CPU load channel, update dependencies (openhab#13292)
Browse files Browse the repository at this point in the history
* 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>
Signed-off-by: Andras Uhrin <andras.uhrin@gmail.com>
  • Loading branch information
mherwege authored and andrasU committed Nov 12, 2022
1 parent 0609245 commit ece6ecd
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 29 deletions.
14 changes: 10 additions & 4 deletions bundles/org.openhab.binding.systeminfo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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 |
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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" }
Expand Down
12 changes: 9 additions & 3 deletions bundles/org.openhab.binding.systeminfo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
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;
Expand Down Expand Up @@ -294,8 +294,8 @@ private State getInfoForChannel(ChannelUID channelUID) {
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);
Expand All @@ -318,6 +318,10 @@ private State getInfoForChannel(ChannelUID channelUID) {
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;
Expand Down Expand Up @@ -427,7 +431,8 @@ private State getInfoForChannel(ChannelUID channelUID) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@

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;
Expand Down Expand Up @@ -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;

/**
Expand Down Expand Up @@ -338,7 +347,7 @@ public StringType getDisplayInformation(int index) throws DeviceNotFoundExceptio
@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];
}
Expand Down Expand Up @@ -485,6 +494,14 @@ private BigDecimal getTimeInMinutes(double timeInSeconds) {
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}
*
Expand Down Expand Up @@ -518,10 +535,10 @@ private BigDecimal getTimeInMinutes(double timeInSeconds) {
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;
Expand Down Expand Up @@ -603,12 +620,14 @@ public DecimalType getNetworkDataReceived(int networkIndex) throws DeviceNotFoun
}

@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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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;

/**
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>
Expand Down Expand Up @@ -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>
Expand Down
4 changes: 2 additions & 2 deletions itests/org.openhab.binding.systeminfo.tests/itest.bndrun
Original file line number Diff line number Diff line change
Expand Up @@ -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)',\
Expand Down
6 changes: 3 additions & 3 deletions itests/org.openhab.binding.systeminfo.tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
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;
Expand Down Expand Up @@ -346,6 +347,18 @@ public void assertStateOfSecondDeviceIsUpdated() {
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;
Expand Down Expand Up @@ -1007,7 +1020,7 @@ public void assertChannelProcessLoadIsUpdatedWithPIDset() throws DeviceNotFoundE
// 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);
Expand Down

0 comments on commit ece6ecd

Please sign in to comment.