Pressure metrics (#43377)

This adds a slightly extended and improved version of the patch we had
internally.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-08-25 14:45:16 +02:00
committed by GitHub
10 changed files with 545 additions and 88 deletions
+48 -26
View File
@@ -6,6 +6,7 @@
#include "extract-word.h"
#include "fd-util.h"
#include "fileio.h"
#include "log.h"
#include "parse-util.h"
#include "process-util.h"
#include "procfs-util.h"
@@ -127,12 +128,36 @@ static uint64_t calc_gcd64(uint64_t a, uint64_t b) {
return a;
}
nsec_t procfs_ticks_to_nsec(uint64_t ticks) {
uint64_t ticks_per_second = sysconf_clock_ticks_cached(),
gcd = calc_gcd64(NSEC_PER_SEC, ticks_per_second);
/* Reduce NSEC_PER_SEC/ticks_per_second first to avoid overflow of ticks * NSEC_PER_SEC, and split
* the multiplication into whole and remainder parts, so that the result stays exact.
*
* Note that in most cases b == 1 and the second term is trivial. */
uint64_t a = NSEC_PER_SEC / gcd,
b = ticks_per_second / gcd;
return ticks / b * a + DIV_ROUND_UP(ticks % b * a, b);
}
int procfs_cpu_get_usage(nsec_t *ret) {
int r;
assert(ret);
ProcfsCpuTicks ticks;
r = procfs_cpu_get_ticks(&ticks);
if (r < 0)
return r;
*ret = procfs_ticks_to_nsec(ticks.user + ticks.nice + ticks.system + ticks.irq + ticks.softirq);
return 0;
}
int procfs_cpu_get_ticks(ProcfsCpuTicks *ret) {
_cleanup_free_ char *first_line = NULL;
unsigned long user_ticks, nice_ticks, system_ticks, irq_ticks, softirq_ticks,
guest_ticks = 0, guest_nice_ticks = 0;
uint64_t sum, gcd, a, b;
const char *p;
int r;
assert(ret);
@@ -141,33 +166,30 @@ int procfs_cpu_get_usage(nsec_t *ret) {
if (r < 0)
return r;
p = first_word(first_line, "cpu");
const char *p = first_word(first_line, "cpu");
if (!p)
return -EINVAL;
if (sscanf(p, "%lu %lu %lu %*u %*u %lu %lu %*u %lu %lu",
&user_ticks,
&nice_ticks,
&system_ticks,
&irq_ticks,
&softirq_ticks,
&guest_ticks,
&guest_nice_ticks) < 5) /* we only insist on the first five fields */
/* All eight fields exist on any kernel we support: iowait was added in 2.5.41, steal in 2.6.11.
* "guest" and "guest_nice" are not returned, the kernel already includes them in "user" and "nice".
* Nevertheless, this file might be synthesized slopilly, so let's not require the last three fields.
* We don't care about them in most cases anyway. */
ProcfsCpuTicks t = {};
r = sscanf(p, "%"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64,
&t.user,
&t.nice,
&t.system,
&t.idle,
&t.iowait,
&t.irq,
&t.softirq,
&t.steal);
if (r < 5)
return -EINVAL;
if (r < 8)
log_once(LOG_INFO, "Short read (%d fields) from /proc/stat, continuing.", r);
uint64_t ticks_per_second = sysconf_clock_ticks_cached();
sum = (uint64_t) user_ticks + (uint64_t) nice_ticks + (uint64_t) system_ticks +
(uint64_t) irq_ticks + (uint64_t) softirq_ticks +
(uint64_t) guest_ticks + (uint64_t) guest_nice_ticks;
/* 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 = NSEC_PER_SEC / gcd;
b = ticks_per_second / gcd;
*ret = DIV_ROUND_UP((nsec_t) sum * (nsec_t) a, (nsec_t) b);
*ret = t;
return 0;
}
+14
View File
@@ -9,8 +9,22 @@ int procfs_get_threads_max(uint64_t *ret);
int procfs_tasks_set_limit(uint64_t limit);
int procfs_tasks_get_current(uint64_t *ret);
nsec_t procfs_ticks_to_nsec(uint64_t ticks);
int procfs_cpu_get_usage(nsec_t *ret);
typedef struct ProcfsCpuTicks {
uint64_t user;
uint64_t nice;
uint64_t system;
uint64_t idle;
uint64_t iowait;
uint64_t irq;
uint64_t softirq;
uint64_t steal;
} ProcfsCpuTicks;
int procfs_cpu_get_ticks(ProcfsCpuTicks *ret);
int procfs_memory_get(uint64_t *ret_total, uint64_t *ret_used);
static inline int procfs_memory_get_used(uint64_t *ret) {
return procfs_memory_get(NULL, ret);
+9 -7
View File
@@ -19,7 +19,7 @@ int read_resource_pressure(const char *path, PressureType type, ResourcePressure
_cleanup_fclose_ FILE *f = NULL;
unsigned field_filled = 0;
ResourcePressure rp = {};
const char *t, *cline;
const char *cline;
char *word;
int r;
@@ -27,12 +27,7 @@ int read_resource_pressure(const char *path, PressureType type, ResourcePressure
assert(IN_SET(type, PRESSURE_TYPE_SOME, PRESSURE_TYPE_FULL));
assert(ret);
if (type == PRESSURE_TYPE_SOME)
t = "some";
else if (type == PRESSURE_TYPE_FULL)
t = "full";
else
return -EINVAL;
const char *t = ASSERT_PTR(pressure_type_to_string(type));
r = fopen_unlocked(path, "re", &f);
if (r < 0)
@@ -131,6 +126,13 @@ static const char* const pressure_resource_table[_PRESSURE_RESOURCE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(pressure_resource, PressureResource);
static const char* const pressure_type_table[_PRESSURE_TYPE_MAX] = {
[PRESSURE_TYPE_SOME] = "some",
[PRESSURE_TYPE_FULL] = "full",
};
DEFINE_STRING_TABLE_LOOKUP(pressure_type, PressureType);
int is_pressure_supported(void) {
static thread_local int cached = -1;
int r;
+3
View File
@@ -7,6 +7,8 @@
typedef enum PressureType {
PRESSURE_TYPE_SOME,
PRESSURE_TYPE_FULL,
_PRESSURE_TYPE_MAX,
_PRESSURE_TYPE_INVALID = -EINVAL,
} PressureType;
typedef enum PressureResource {
@@ -50,6 +52,7 @@ static inline const PressureResourceInfo* pressure_resource_get_info(PressureRes
}
DECLARE_STRING_TABLE_LOOKUP(pressure_resource, PressureResource);
DECLARE_STRING_TABLE_LOOKUP(pressure_type, PressureType);
/* Default parameters for pressure watch logic in sd-event and PID 1 */
#define PRESSURE_DEFAULT_TYPE "some"