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),