diff --git a/data/org.eclipse.bluechi.Node.xml b/data/org.eclipse.bluechi.Node.xml
index ff2e4a3af3..ddeb5e31ec 100644
--- a/data/org.eclipse.bluechi.Node.xml
+++ b/data/org.eclipse.bluechi.Node.xml
@@ -63,6 +63,12 @@
+
+
+
+
+
+
diff --git a/data/org.eclipse.bluechi.internal.Agent.xml b/data/org.eclipse.bluechi.internal.Agent.xml
index a0e1c91449..46796ebb79 100644
--- a/data/org.eclipse.bluechi.internal.Agent.xml
+++ b/data/org.eclipse.bluechi.internal.Agent.xml
@@ -77,6 +77,12 @@
+
+
+
+
+
+
diff --git a/doc/docs/api/description.md b/doc/docs/api/description.md
index 31bcfea01c..da937c2821 100644
--- a/doc/docs/api/description.md
+++ b/doc/docs/api/description.md
@@ -202,6 +202,10 @@ Object path: `/org/eclipse/bluechi/node/$name`
Set the new log level for bluechi-agent by invoking the internal bluechi-agent API.
+ * `GetSystemResources(out u cpu_number, out t cpu_time, out t memory_total, out t memory_used)`
+
+ Returns information about system resources (cpu and memory) on this node.
+
#### Properties
* `Name` - `s`
diff --git a/doc/man/bluechictl.1.md b/doc/man/bluechictl.1.md
index d52022df0c..8cc4ad20f9 100644
--- a/doc/man/bluechictl.1.md
+++ b/doc/man/bluechictl.1.md
@@ -46,6 +46,10 @@ Creates a monitor on the given agent to observe changes in the specified units.
Creates a monitor to observe connection state changes for all nodes.
+### **bluechictl** *system-resources* *agent*
+
+Fetches information about system resources on the specific bluechi-agents.
+
**Example:**
diff --git a/src/agent/agent.c b/src/agent/agent.c
index af3ac3c240..1cb8fe0a63 100644
--- a/src/agent/agent.c
+++ b/src/agent/agent.c
@@ -12,6 +12,7 @@
#include "libbluechi/common/network.h"
#include "libbluechi/common/opt.h"
#include "libbluechi/common/parse-util.h"
+#include "libbluechi/common/procfs-util.h"
#include "libbluechi/common/time-util.h"
#include "libbluechi/log/log.h"
#include "libbluechi/service/shutdown.h"
@@ -1460,6 +1461,49 @@ static int agent_method_set_log_level(
return sd_bus_reply_method_return(m, "");
}
+/*************************************************************************
+ ************** org.eclipse.bluechi.Agent.GetSystemResources *
+ *************************************************************************/
+
+static int agent_method_get_system_resources(
+ UNUSED sd_bus_message *m, UNUSED void *userdata, UNUSED sd_bus_error *ret_error) {
+ _cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
+ uint32_t cpu_number = 0;
+ uint64_t cpu_time = 0;
+ uint64_t memory_total = 0;
+ uint64_t memory_used = 0;
+ long ret = 0;
+
+ int r = sd_bus_message_new_method_return(m, &reply);
+ if (r < 0) {
+ return sd_bus_reply_method_errorf(reply, SD_BUS_ERROR_FAILED, "Internal error");
+ }
+
+ ret = sysconf(_SC_NPROCESSORS_ONLN);
+ if (ret < 0) {
+ return sd_bus_reply_method_errorf(reply, SD_BUS_ERROR_FAILED, "Internal error");
+ }
+ assert(ret > 0);
+ cpu_number = ret;
+
+ r = procfs_cpu_get_usage(&cpu_time);
+ if (r < 0) {
+ return sd_bus_reply_method_errorf(reply, SD_BUS_ERROR_FAILED, "Internal error");
+ }
+
+ r = procfs_memory_get(&memory_total, &memory_used);
+ if (r < 0) {
+ return sd_bus_reply_method_errorf(reply, SD_BUS_ERROR_FAILED, "Internal error");
+ }
+
+ r = sd_bus_message_append(reply, "uttt", cpu_number, cpu_time, memory_total, memory_used);
+ if (r < 0) {
+ return sd_bus_reply_method_errorf(reply, SD_BUS_ERROR_FAILED, "Internal error");
+ }
+
+ return sd_bus_message_send(reply);
+}
+
static const sd_bus_vtable internal_agent_vtable[] = {
SD_BUS_VTABLE_START(0),
@@ -1478,6 +1522,7 @@ static const sd_bus_vtable internal_agent_vtable[] = {
SD_BUS_METHOD("EnableMetrics", "", "", agent_method_enable_metrics, 0),
SD_BUS_METHOD("DisableMetrics", "", "", agent_method_disable_metrics, 0),
SD_BUS_METHOD("SetLogLevel", "s", "", agent_method_set_log_level, 0),
+ SD_BUS_METHOD("GetSystemResources", "", "uttt", agent_method_get_system_resources, 0),
SD_BUS_SIGNAL_WITH_NAMES("JobDone", "us", SD_BUS_PARAM(id) SD_BUS_PARAM(result), 0),
SD_BUS_SIGNAL_WITH_NAMES("JobStateChanged", "us", SD_BUS_PARAM(id) SD_BUS_PARAM(state), 0),
SD_BUS_SIGNAL_WITH_NAMES(
diff --git a/src/bindings/python/bluechi/api.py b/src/bindings/python/bluechi/api.py
index a78becc759..51fe8ea2f7 100644
--- a/src/bindings/python/bluechi/api.py
+++ b/src/bindings/python/bluechi/api.py
@@ -450,6 +450,18 @@ def set_log_level(self, level: str) -> None:
level,
)
+ def get_system_resources(self) -> Tuple[UInt32, UInt64, UInt64, UInt64]:
+ """
+ GetSystemResources:
+ @cpu_number: The cpu number of the node.
+ @cpu_time: The cpu time (nsec) of the node.
+ @memory_total: The total memory size (kB) of the node.
+ @memory_used: The used memory size (kB) of the node.
+
+ Get the system resources of the node.
+ """
+ return self.get_proxy().GetSystemResources()
+
@property
def name(self) -> str:
return self.get_proxy().Name
diff --git a/src/client/client.c b/src/client/client.c
index cb195a528c..e4da7842a0 100644
--- a/src/client/client.c
+++ b/src/client/client.c
@@ -614,6 +614,49 @@ static int method_thaw_unit_on(Client *client, char *node_name, char *unit) {
return 0;
}
+static int method_get_system_resources_on(Client *client, char *node_name) {
+ int r = 0;
+ _cleanup_sd_bus_error_ sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_sd_bus_message_ sd_bus_message *result = NULL;
+ uint32_t cpu_number = 0;
+ uint64_t cpu_time = 0;
+ uint64_t memory_total = 0;
+ uint64_t memory_used = 0;
+
+ r = assemble_object_path_string(NODE_OBJECT_PATH_PREFIX, node_name, &client->object_path);
+ if (r < 0) {
+ return r;
+ }
+
+ r = sd_bus_call_method(
+ client->api_bus,
+ BC_INTERFACE_BASE_NAME,
+ client->object_path,
+ NODE_INTERFACE,
+ "GetSystemResources",
+ &error,
+ &result,
+ "");
+ if (r < 0) {
+ fprintf(stderr, "Failed to issue method call: %s\n", error.message);
+ return r;
+ }
+
+ r = sd_bus_message_read(result, "uttt", &cpu_number, &cpu_time, &memory_total, &memory_used);
+ if (r < 0) {
+ fprintf(stderr, "Failed to read the system resources of the node: %s\n", strerror(-r));
+ return r;
+ }
+
+ printf("CPU(s): %u, CPU time: %lu nsec, Memory: %lu kB / %lu kB\n",
+ cpu_number,
+ cpu_time,
+ memory_used,
+ memory_total);
+
+ return 0;
+}
+
int client_call_manager(Client *client) {
int r = 0;
@@ -750,6 +793,11 @@ int client_call_manager(Client *client) {
} else {
return -EINVAL;
}
+ } else if (streq(client->op, "system-resources")) {
+ if (client->opargc != 1) {
+ return -EINVAL;
+ }
+ return method_get_system_resources_on(client, client->opargv[0]);
} else if (streq(client->op, "version")) {
printf("%s\n", CONFIG_H_BC_VERSION);
} else {
@@ -793,5 +841,7 @@ int print_client_usage(char *argv) {
printf(" usage: monitor node-connection\n");
printf(" - daemon-reload: reload systemd daemon on a specific node\n");
printf(" usage: disable nodename\n");
+ printf(" - system-resources: returns the system resources on a specific nodes\n");
+ printf(" usage: system-resources nodename\n");
return 0;
}
diff --git a/src/libbluechi/common/common.h b/src/libbluechi/common/common.h
index 8804804fc5..65299aa341 100644
--- a/src/libbluechi/common/common.h
+++ b/src/libbluechi/common/common.h
@@ -27,6 +27,12 @@ static inline int steal_fd(int *fdp) {
return fd;
}
+static inline void fclosep(FILE **f) {
+ if (*f) {
+ fclose(*f);
+ }
+}
+
static inline void closep(const int *fd) {
if (*fd >= 0) {
close(*fd);
@@ -60,6 +66,7 @@ static inline void *malloc0_array(size_t base_size, size_t element_size, size_t
#define _cleanup_(x) __attribute__((__cleanup__(x)))
#define _cleanup_free_ _cleanup_(freep)
#define _cleanup_fd_ _cleanup_(closep)
+#define _cleanup_fclose_ _cleanup_(fclosep)
// NOLINTBEGIN(bugprone-macro-parentheses)
#define DEFINE_CLEANUP_FUNC(_type, _freefunc) \
diff --git a/src/libbluechi/common/procfs-util.c b/src/libbluechi/common/procfs-util.c
new file mode 100644
index 0000000000..9ddd01672b
--- /dev/null
+++ b/src/libbluechi/common/procfs-util.c
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include
+
+#include "libbluechi/common/common.h"
+#include "procfs-util.h"
+
+#define MEMINFO_BUF_SIZE 60
+#define STAT_BUF_SIZE 512
+
+#define BASE_DECIMAL 10
+
+#define IDLE_INDEX 3
+#define IOWAIT_INDEX 4
+#define STEAL_INDEX 7
+
+#define NSEC_PER_SEC 1000000000ULL
+
+#define DIV_ROUND_UP(n, d) (((n) + (d) -1) / (d))
+
+static uint64_t calc_gcd64(uint64_t a, uint64_t b) {
+ while (b > 0) {
+ uint64_t t = a % b;
+
+ a = b;
+ b = t;
+ }
+
+ return a;
+}
+
+int procfs_cpu_get_usage(uint64_t *ret) {
+ long ticks_per_second = 0;
+ uint64_t sum = 0, gcd = 0, a = 0, b = 0;
+ _cleanup_fclose_ FILE *f = NULL;
+ char buf[STAT_BUF_SIZE];
+ const char *p = buf;
+ int i = 0;
+
+ assert(ret);
+
+ f = fopen("/proc/stat", "re");
+ if (!f) {
+ return -errno;
+ }
+
+ if (!fgets(buf, sizeof(buf), f) || buf[0] != 'c' /* not "cpu" */) {
+ return -EINVAL;
+ }
+
+ p += strcspn(p, " \t");
+
+ while (p && *p != '\0') {
+ char *endptr = NULL;
+ uint64_t val = UINT64_MAX;
+
+ val = strtoul(p, &endptr, BASE_DECIMAL);
+ if ((errno == ERANGE && val == UINT64_MAX) || (errno != EAGAIN && errno != 0 && val == 0)) {
+ return -errno;
+ }
+
+ if (i != IDLE_INDEX && i != IOWAIT_INDEX && i != STEAL_INDEX) {
+ sum += val;
+ }
+
+ p = ++endptr;
+ i++;
+ }
+
+ ticks_per_second = sysconf(_SC_CLK_TCK);
+ if (ticks_per_second < 0) {
+ return -errno;
+ }
+ assert(ticks_per_second > 0);
+
+ /* Let's reduce this fraction before we apply it to avoid overflows when converting this to μsec */
+ gcd = calc_gcd64(NSEC_PER_SEC, ticks_per_second);
+
+ a = (uint64_t) NSEC_PER_SEC / gcd;
+ b = (uint64_t) ticks_per_second / gcd;
+
+ *ret = DIV_ROUND_UP(sum * a, b);
+ return 0;
+}
+
+int procfs_memory_get(uint64_t *ret_total, uint64_t *ret_used) {
+ uint64_t mem_total = UINT64_MAX, mem_available = UINT64_MAX;
+ _cleanup_fclose_ FILE *f = NULL;
+ char buf[MEMINFO_BUF_SIZE];
+
+ f = fopen("/proc/meminfo", "re");
+ if (!f) {
+ return -errno;
+ }
+
+ while (fgets(buf, sizeof(buf), f) != NULL) {
+ char *c = strchr(buf, ':');
+ if (!c) {
+ continue;
+ }
+ *c = '\0';
+
+ if (streq(buf, "MemTotal")) {
+ mem_total = strtoul(c + 1, NULL, BASE_DECIMAL);
+ if ((errno == ERANGE && mem_total == UINT64_MAX) ||
+ (errno != EAGAIN && errno != 0 && mem_total == 0)) {
+ return -errno;
+ }
+ continue;
+ }
+
+ if (streq(buf, "MemAvailable")) {
+ mem_available = strtoul(c + 1, NULL, BASE_DECIMAL);
+ if ((errno == ERANGE && mem_available == UINT64_MAX) ||
+ (errno != EAGAIN && errno != 0 && mem_available == 0)) {
+ return -errno;
+ }
+ continue;
+ }
+
+ if (mem_total != UINT64_MAX && mem_available != UINT64_MAX) {
+ break;
+ }
+ }
+
+ if (mem_available > mem_total) {
+ return -EINVAL;
+ }
+
+ if (ret_total) {
+ *ret_total = mem_total;
+ }
+
+ if (ret_used) {
+ *ret_used = mem_total - mem_available;
+ }
+
+ return 0;
+}
diff --git a/src/libbluechi/common/procfs-util.h b/src/libbluechi/common/procfs-util.h
new file mode 100644
index 0000000000..17b695846f
--- /dev/null
+++ b/src/libbluechi/common/procfs-util.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include
+
+int procfs_cpu_get_usage(uint64_t *ret);
+int procfs_memory_get(uint64_t *ret_total, uint64_t *ret_used);
diff --git a/src/libbluechi/meson.build b/src/libbluechi/meson.build
index 714a1a2d1f..a21c0e19a1 100644
--- a/src/libbluechi/meson.build
+++ b/src/libbluechi/meson.build
@@ -15,6 +15,8 @@ libbluechi_src = [
'common/opt.h',
'common/parse-util.c',
'common/parse-util.h',
+ 'common/procfs-util.c',
+ 'common/procfs-util.h',
'common/network.h',
'common/network.c',
'common/time-util.c',
diff --git a/src/manager/node.c b/src/manager/node.c
index 03579171c8..41719d204c 100644
--- a/src/manager/node.c
+++ b/src/manager/node.c
@@ -72,6 +72,7 @@ static const sd_bus_vtable node_vtable[] = {
SD_BUS_METHOD("DisableUnitFiles", "asb", "a(sss)", node_method_passthrough_to_agent, 0),
SD_BUS_METHOD("Reload", "", "", node_method_passthrough_to_agent, 0),
SD_BUS_METHOD("SetLogLevel", "s", "", node_method_set_log_level, 0),
+ SD_BUS_METHOD("GetSystemResources", "", "uttt", node_method_passthrough_to_agent, 0),
SD_BUS_PROPERTY("Name", "s", node_property_get_nodename, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Status", "s", node_property_get_status, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("LastSeenTimestamp", "t", node_property_get_last_seen, 0, SD_BUS_VTABLE_PROPERTY_EXPLICIT),