From 6124f487c6861ed02677478844ece82c3e6f545c Mon Sep 17 00:00:00 2001 From: Tom Hromatka Date: Thu, 31 Jul 2025 15:34:38 -0600 Subject: [PATCH 1/9] adaptived: utils: Add auto-detection support for cgroup floats Add auto-detection support for cgroup floats when invoking adaptived_cgroup_get_value() with a type of ADAPTIVED_CGVAL_DETECT. Signed-off-by: Tom Hromatka --- adaptived/src/utils/cgroup_utils.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/adaptived/src/utils/cgroup_utils.c b/adaptived/src/utils/cgroup_utils.c index b6583dc..38f1ff6 100644 --- a/adaptived/src/utils/cgroup_utils.c +++ b/adaptived/src/utils/cgroup_utils.c @@ -456,14 +456,23 @@ API int adaptived_cgroup_get_value(const char * const setting, adaptived_dbg("setting from %s is not a long long.\n", setting); } + ret = adaptived_cgroup_get_float(setting, &value->value.float_value); + if (ret == 0) { + value->type = ADAPTIVED_CGVAL_FLOAT; + return ret; + } else { + adaptived_dbg("setting from %s is not a float: %d\n", setting, ret); + } + ret = adaptived_cgroup_get_str(setting, &value->value.str_value); if (ret == 0) { value->type = ADAPTIVED_CGVAL_STR; - adaptived_dbg("setting from %s is a string: %s\n", setting, value->value.str_value); return ret; } else { adaptived_dbg("setting from %s is not a string: %d\n", setting, ret); } + + adaptived_err("Failed to detect setting type for %s\n", setting); break; default: adaptived_err("Invalid cgroup value type: %d\n", value->type); From 41b0249eff75807bcd0163291915e328b8a76a0d Mon Sep 17 00:00:00 2001 From: Tom Hromatka Date: Thu, 31 Jul 2025 15:36:24 -0600 Subject: [PATCH 2/9] adaptived: shared-data: Add suport for ADAPTIVED_SDATA_CGROUP_SETTING_VALUE Add support for a shared data struct that contains the cgroup name, a setting name, and the value associated with that setting. Signed-off-by: Tom Hromatka --- adaptived/include/adaptived.h | 11 ++++- adaptived/src/adaptived-internal.h | 10 +++++ adaptived/src/shared_data.c | 64 ++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/adaptived/include/adaptived.h b/adaptived/include/adaptived.h index d2afb4f..9cd9d06 100644 --- a/adaptived/include/adaptived.h +++ b/adaptived/include/adaptived.h @@ -70,8 +70,9 @@ enum adaptived_sdata_type { */ ADAPTIVED_SDATA_CUSTOM = 0, ADAPTIVED_SDATA_STR, - ADAPTIVED_SDATA_CGROUP, - ADAPTIVED_SDATA_NAME_VALUE, + ADAPTIVED_SDATA_CGROUP, /* struct adaptived_cgroup_value */ + ADAPTIVED_SDATA_NAME_VALUE, /* struct adaptived_name_and_value */ + ADAPTIVED_SDATA_CGROUP_SETTING_VALUE, /* struct adaptived_cgroup_setting_and_value */ ADAPTIVED_SDATA_CNT }; @@ -117,6 +118,12 @@ struct adaptived_name_and_value { struct adaptived_cgroup_value *value; }; +struct adaptived_cgroup_setting_and_value { + char *cgroup_name; + char *setting; + struct adaptived_cgroup_value *value; +}; + struct adaptived_rule_stats { int cause_cnt; int effect_cnt; diff --git a/adaptived/src/adaptived-internal.h b/adaptived/src/adaptived-internal.h index 2f681f4..954aabd 100644 --- a/adaptived/src/adaptived-internal.h +++ b/adaptived/src/adaptived-internal.h @@ -148,6 +148,16 @@ int _sort_pid_list(const void *p1, const void *p2); struct adaptived_rule *rule_init(const char * const name); void rule_destroy(struct adaptived_rule ** rule); +/* + * shared_data.c functions + */ + +int write_sdata_cgroup_setting_value(struct adaptived_cause * const cse, + const char * const cgroup_name, + const char * const setting, + const struct adaptived_cgroup_value * const value, + uint32_t flags); + /* * mem_utils defines */ diff --git a/adaptived/src/shared_data.c b/adaptived/src/shared_data.c index c20bc09..94e7808 100644 --- a/adaptived/src/shared_data.c +++ b/adaptived/src/shared_data.c @@ -26,12 +26,66 @@ #include #include +#include #include #include "adaptived-internal.h" #include "shared_data.h" #include "cause.h" +int write_sdata_cgroup_setting_value(struct adaptived_cause * const cse, + const char * const cgroup_name, + const char * const setting, + const struct adaptived_cgroup_value * const value, + uint32_t flags) +{ + struct adaptived_cgroup_setting_and_value *sdata = NULL; + int ret; + + if (!cse || !cgroup_name || !setting || !value) + return -EINVAL; + + sdata = malloc(sizeof(struct adaptived_cgroup_setting_and_value)); + if (!sdata) + return -ENOMEM; + memset(sdata, 0, sizeof(struct adaptived_cgroup_setting_and_value)); + + sdata->cgroup_name = strdup(cgroup_name); + if (!sdata->cgroup_name) { + ret = -ENOMEM; + goto error; + } + + sdata->setting = strdup(setting); + if (!sdata->setting) { + ret = -ENOMEM; + goto error; + } + + sdata->value = malloc(sizeof(struct adaptived_cgroup_value)); + if (!sdata->value) { + ret = -ENOMEM; + goto error; + } + + memcpy(sdata->value, value, sizeof(struct adaptived_cgroup_value)); + + ret = adaptived_write_shared_data(cse, ADAPTIVED_SDATA_CGROUP_SETTING_VALUE, + sdata, NULL, flags); + + return ret; + +error: + if (sdata->cgroup_name) + free(sdata->cgroup_name); + if (sdata->setting) + free(sdata->setting); + if (sdata->value) + free(sdata->value); + + return ret; +} + /* * Method for a cause to share data with effect(s) in the same rule. * @@ -223,6 +277,16 @@ API void free_shared_data(struct adaptived_cause * const cse, bool force_delete) adaptived_free_cgroup_value(name_value->value); free(cur->data); break; + case ADAPTIVED_SDATA_CGROUP_SETTING_VALUE: + struct adaptived_cgroup_setting_and_value *cgsv; + + cgsv = (struct adaptived_cgroup_setting_and_value *)cur->data; + + free(cgsv->cgroup_name); + free(cgsv->setting); + adaptived_free_cgroup_value(cgsv->value); + free(cur->data); + break; default: free(cur->data); break; From 5007eec53f5e660ec1b924ac0379883b68caf522 Mon Sep 17 00:00:00 2001 From: Tom Hromatka Date: Thu, 31 Jul 2025 15:38:28 -0600 Subject: [PATCH 3/9] adaptived: causes: Add a cause that can gather cgroup data Add a cause that can gather cgroup data and publish it to the shared data mechanism. Downstream effects (print, rest calls, etc.) can then utilize this data. Signed-off-by: Tom Hromatka --- adaptived/src/Makefile.am | 1 + adaptived/src/cause.c | 2 + adaptived/src/cause.h | 5 + adaptived/src/causes/cgroup_data.c | 299 +++++++++++++++++++++++++++++ 4 files changed, 307 insertions(+) create mode 100644 adaptived/src/causes/cgroup_data.c diff --git a/adaptived/src/Makefile.am b/adaptived/src/Makefile.am index c7e391c..bdd20cc 100644 --- a/adaptived/src/Makefile.am +++ b/adaptived/src/Makefile.am @@ -6,6 +6,7 @@ SUBDIRS = ${DIST_SUBDIRS} SOURCES = \ adaptived-internal.h \ causes/always.c \ + causes/cgroup_data.c \ causes/cgroup_setting.c \ causes/days_of_the_week.c \ causes/meminfo.c \ diff --git a/adaptived/src/cause.c b/adaptived/src/cause.c index 644bb5e..6dbc56b 100644 --- a/adaptived/src/cause.c +++ b/adaptived/src/cause.c @@ -55,6 +55,7 @@ const char * const cause_names[] = { "memory.stat", "top", "cgroup_memory_setting", + "cgroup_data", }; static_assert(ARRAY_SIZE(cause_names) == CAUSE_CNT, "cause_names[] must be same length as CAUSE_CNT"); @@ -81,6 +82,7 @@ const struct adaptived_cause_functions cause_fns[] = { {memorystat_init, memorystat_main, memorystat_exit}, {top_init, top_main, top_exit}, {cgset_memory_init, cgset_memory_main, cgset_exit}, + {cgroup_data_init, cgroup_data_main, cgroup_data_exit}, }; static_assert(ARRAY_SIZE(cause_fns) == CAUSE_CNT, "cause_fns[] must be same length as CAUSE_CNT"); diff --git a/adaptived/src/cause.h b/adaptived/src/cause.h index 413ee6b..d535913 100644 --- a/adaptived/src/cause.h +++ b/adaptived/src/cause.h @@ -63,6 +63,7 @@ enum cause_enum { MEMORYSTAT, TOP, CGROUP_MEMORY_SETTING, + CGROUP_DATA, CAUSE_CNT }; @@ -144,4 +145,8 @@ int top_init(struct adaptived_cause * const cse, struct json_object *args_obj, i int top_main(struct adaptived_cause * const cse, int time_since_last_run); void top_exit(struct adaptived_cause * const cse); +int cgroup_data_init(struct adaptived_cause * const cse, struct json_object *args_obj, int interval); +int cgroup_data_main(struct adaptived_cause * const cse, int time_since_last_run); +void cgroup_data_exit(struct adaptived_cause * const cse); + #endif /* __ADAPTIVED_CAUSE_H */ diff --git a/adaptived/src/causes/cgroup_data.c b/adaptived/src/causes/cgroup_data.c new file mode 100644 index 0000000..9d3a69b --- /dev/null +++ b/adaptived/src/causes/cgroup_data.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2024-2025, Oracle and/or its affiliates. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/** + * Cause that will gather cgroup data into a shared data structure + * + */ + +#include +#include +#include +#include + +#include +#include + +#include "adaptived-internal.h" +#include "shared_data.h" +#include "defines.h" + +static const char * const slash_path = "/"; + +struct cgroup_data_opts { + char *cgroup_path; + size_t cgroup_path_len; /* calculated from the original path provided */ + char **settings; + int settings_cnt; /* calculated from the length of the settings json array */ + int max_depth; /* optional */ + bool rel_paths; /* optional */ +}; + +static void free_opts(struct cgroup_data_opts * const opts) +{ + int i; + + if (!opts) + return; + + if (opts->cgroup_path) + free(opts->cgroup_path); + + for (i = 0; i < opts->settings_cnt; i++) { + if (opts->settings[i]) + free(opts->settings[i]); + } + + free(opts); +} + +int cgroup_data_init(struct adaptived_cause * const cse, struct json_object *args_obj, + int interval) +{ + struct json_object *settings_obj, *setting_obj; + const char *cgroup_path, *setting; + struct cgroup_data_opts *opts; + json_bool exists; + int i, ret = 0; + + opts = malloc(sizeof(struct cgroup_data_opts)); + if (!opts) { + ret = -ENOMEM; + goto error; + } + + memset(opts, 0, sizeof(struct cgroup_data_opts)); + + ret = adaptived_parse_string(args_obj, "cgroup", &cgroup_path); + if (ret) { + adaptived_err("Failed to parse the cgroup path: %d\n", ret); + goto error; + } + + /* +1 to add a trailing "/", +1 to add a trailing "*", and +1 for the NULL charactoer */ + opts->cgroup_path = malloc(sizeof(char) * (strlen(cgroup_path) + 3)); + if (!opts->cgroup_path) { + ret = -ENOMEM; + goto error; + } + + strcpy(opts->cgroup_path, cgroup_path); + opts->cgroup_path[strlen(cgroup_path)] = '\0'; + + /* Remove all trailing "/" characters */ + i = strlen(opts->cgroup_path) - 1; + while (opts->cgroup_path[i] == '/') { + opts->cgroup_path[i] = '\0'; + i--; + } + + opts->cgroup_path_len = strlen(opts->cgroup_path); + + /* + * Add a trailing '/' and '*' to the path. This instructs the path walk code to + * not list the starting directory. + */ + opts->cgroup_path[strlen(opts->cgroup_path)] = '/'; + opts->cgroup_path[strlen(opts->cgroup_path)] = '*'; + + exists = json_object_object_get_ex(args_obj, "settings", &settings_obj); + if (!exists || !settings_obj) { + ret = -EINVAL; + goto error; + } + + opts->settings_cnt = json_object_array_length(settings_obj); + + opts->settings = malloc(sizeof(char *) * opts->settings_cnt); + if (!opts->settings) { + ret = -ENOMEM; + goto error; + } + memset(opts->settings, 0, sizeof(char *) * opts->settings_cnt); + + for (i = 0; i < opts->settings_cnt; i++) { + setting_obj = json_object_array_get_idx(settings_obj, i); + if (!setting_obj) { + ret = -EINVAL; + goto error; + } + + ret = adaptived_parse_string(setting_obj, "setting", &setting); + if (ret) { + adaptived_err("Failed to parse the setting: %d\n", ret); + goto error; + } + + opts->settings[i] = malloc(sizeof(char) * (strlen(setting) + 1)); + if (!opts->settings[i]) { + ret = -ENOMEM; + goto error; + } + + strcpy(opts->settings[i], setting); + opts->settings[i][strlen(setting)] = '\0'; + } + + ret = adaptived_parse_int(args_obj, "max_depth", &opts->max_depth); + if (ret == -ENOENT) { + /* If unspecified, only enumerate the direct children */ + opts->max_depth = 0; + ret = 0; + } else if (ret) { + goto error; + } + + ret = adaptived_parse_bool(args_obj, "rel_paths", &opts->rel_paths); + if (ret == -ENOENT) { + opts->rel_paths = true; + ret = 0; + } else if (ret) { + adaptived_err("Failed to parse rel_paths arg: %d", ret); + goto error; + } + + ret = adaptived_cause_set_data(cse, (void *)opts); + if (ret) + goto error; + + return ret; + +error: + free_opts(opts); + return ret; +} + +static int read_settings(struct adaptived_cause * const cse, struct cgroup_data_opts * const opts, + const char * const cg_path) +{ + char *setting_path = NULL; + struct adaptived_cgroup_value val; + size_t setting_path_len; + const char *sdata_path; + int ret, i; + + for (i = 0; i < opts->settings_cnt; i++) { + /* Add 1 for the middle "/" and 1 for the null character */ + setting_path_len = sizeof(char) * (strlen(cg_path) + strlen(opts->settings[i]) + 2); + + setting_path = malloc(setting_path_len); + if (!setting_path) { + ret = -ENOMEM; + goto error; + } + + sprintf(setting_path, "%s/%s", cg_path, opts->settings[i]); + + val.type = ADAPTIVED_CGVAL_DETECT; + ret = adaptived_cgroup_get_value(setting_path, &val); + if (ret == -ENOENT) { + /* + * This cgroup doesn't have the requested file. It's likely that + * the particular controller isn't enabled for this cgroup, and + * thus this isn't a fatal error. Continue on + */ + free(setting_path); + setting_path = NULL; + ret = 0; + continue; + } else if (ret) { + goto error; + } + + if (opts->rel_paths) { + if (strlen(cg_path) <= opts->cgroup_path_len + 1) + /* + * Since we currently don't publish cgroup data for the root + * directory, this line should be impossible to hit, but I'm + * leaving it in out of an abundance of caution. + */ + sdata_path = slash_path; + else + sdata_path = &cg_path[opts->cgroup_path_len + 1]; + } else { + sdata_path = cg_path; + } + + ret = write_sdata_cgroup_setting_value(cse, sdata_path, opts->settings[i], &val, + ADAPTIVED_SDATAF_PERSIST); + if (ret) + goto error; + + free(setting_path); + setting_path = NULL; + } + +error: + if (setting_path) + free(setting_path); + + return ret; +} + +int cgroup_data_main(struct adaptived_cause * const cse, int time_since_last_run) +{ + struct cgroup_data_opts *opts = (struct cgroup_data_opts *)adaptived_cause_get_data(cse); + struct adaptived_path_walk_handle *handle = NULL; + char *cur_path = NULL; + int ret; + + free_shared_data(cse, true); + + ret = adaptived_path_walk_start(opts->cgroup_path, &handle, + ADAPTIVED_PATH_WALK_LIST_DIRS, opts->max_depth); + if (ret) + goto error; + + do { + ret = adaptived_path_walk_next(&handle, &cur_path); + if (ret) + goto error; + if (!cur_path) + /* We've reached the end of the tree */ + break; + + ret = read_settings(cse, opts, cur_path); + if (ret) + goto error; + + free(cur_path); + } while (true); + +error: + adaptived_path_walk_end(&handle); + if (cur_path) + free(cur_path); + + if (ret == 0) + /* Unless there is an error, this cause always triggers */ + return 1; + + return ret; +} + +void cgroup_data_exit(struct adaptived_cause * const cse) +{ + struct cgroup_data_opts *opts = (struct cgroup_data_opts *)adaptived_cause_get_data(cse); + + free_shared_data(cse, true); + free_opts(opts); +} From b90e0596efb4c9c37af6a41a22fa030d3fec4f9f Mon Sep 17 00:00:00 2001 From: Tom Hromatka Date: Thu, 31 Jul 2025 15:41:32 -0600 Subject: [PATCH 4/9] adaptived: effects: Add support for printing shared data Add support to the "print" effect for printing shared data to the screen. This effect is useful for debugging the internal state of adaptived. Signed-off-by: Tom Hromatka --- .../doc/internal/list-of-built-in-effects.md | 2 +- adaptived/src/effects/print.c | 148 +++++++++++++++++- 2 files changed, 148 insertions(+), 2 deletions(-) diff --git a/adaptived/doc/internal/list-of-built-in-effects.md b/adaptived/doc/internal/list-of-built-in-effects.md index a8fe1db..e0e6090 100644 --- a/adaptived/doc/internal/list-of-built-in-effects.md +++ b/adaptived/doc/internal/list-of-built-in-effects.md @@ -12,7 +12,7 @@ Parameters that accept long long or float also support some human-readable forma | [kill_cgroup_by_psi](../../src/effects/kill_cgroup_by_psi.c) | Walk a cgroup tree, and kill the processes in the cgroup with the highest PSI utilization |
  • "cgroup" (string) - full path to the cgroup to be killed. See [path rules](path-rules.md) for more details. Use the "\*" wildcard to ensure the tree is walked.
  • "type" (string) - which PSI type to evaluate, "cpu", "memory", or "io"
  • "measurement" (string) - which measurement to compare, e.g. some-avg10, full-avg60, etc. some-total and full-total are not supported
  • "signal" (int - optional) - signal to send to the processes being killed. Default - SIGKILL
  • "max_depth" (int - optional) - maximum depth to traverse in the cgroup hierarchy. Default - unlimited
| [ftest 024](../../tests/ftests/024-effect-kill_cgroup_by_psi.json) | | | [kill_processes](../../src/effects/kill_processes.c) | Kill processes that match the specified process name(s) |
  • "proc_names" (array)
    • "name" (string) - process name (as found in /proc/{pid}/stat)
  • "signal" (int - optional) - signal to send to the processes being killed. Currently only supports integers. Default - 9 (i.e. SIGKILL)
  • "count" (int - optional) - number of processes to kill each time this cause is run. If specified, the processes consuming the most memory will be killed first. Default - all matching processes
  • "field" (string - optional) - field in /proc/pid/stat to sort on. Currently supports "vsize" or "rss". Default - "rss".
| [ftest 067](../../tests/ftests/067-effect-kill_processes.json)
[ftest 068](../../tests/ftests/068-effect-kill_processes_rss.json) | | | [logger](../../src/effects/logger.c) | Given an array of files, write their contents to "logfile" |
  • "logfile" (string) - Output file to store the log data
  • "max_file_size" (int - optional) - Maximum amount of data that will be copied from each source file. Defaults to 32kB if not specified
  • "files" (array)
    • "file" (string) - file to copy
  • "separator_prefix" (string - optional) - If specified, this string will be written each time this effect triggers
  • "date_format" (string - optional) - If specified, the date will be written in the specified format each time the effect triggers
  • "utc" (boolean - optional) - If specified, the date will be recorded in UTC time. Otherwise, the machine's localtime() will be used
  • "separator_postfix" (string - optional) - If specified, this string will be written each time this effect triggers
  • "file_separator" (string - optional) -If specified, this string will be written between each file being logged
| [ftest 043](../../tests/ftests/043-effect-logger-no-separators.json)
[ftest 044](../../tests/ftests/044-effect-logger-date-format.json) | | -| [print](../../src/effects/print.c) | Print a message to a file |
  • "message" (string - optional) - message to output
  • "file" (string) - file to write to. Currently only supports "stdout" or "stderr"
| [Jimmy Buffett Example](../examples/jimmy-buffett-config.json) | | +| [print](../../src/effects/print.c) | Print a message to a file |
  • "message" (string - optional) - message to output
  • "file" (string) - file to write to. Supports "stderr", "stdout", or any arbitrary path and filename
  • "shared_data" (boolean - optional) - If specified, this effect will print the data that has been shared by the causes in this rule. Default - false
| [Jimmy Buffett Example](../examples/jimmy-buffett-config.json) | | | [print_schedstat](../../src/effects/print_schedstat.c) | Print schedstat to a file |
  • "file" (string) - file to write to. Currently only supports "stdout" or "stderr"
| [ftest 054](../../tests/ftests/054-effect-print_schedstat.json) | | | [sd_bus_setting](../../src/effects/sd_bus_setting.c) | Operate on sd_bus properties |
  • "target" (string) - cgroup slice name or scope name
  • "setting" (string) - sd_bus property name (e.g. MemoryMax)
  • "value" (string, long long, or double) - value to write to the property. If the operator is set to add or subtract, this value will be added/subtracted from the current value of the property
  • "operator" (string) - add, subtract, or set
  • "limit" (string, long long, or double - optional) - if provided, this effect will use the value as an upper or lower limit when the operator is set to add or subtract, respectfully
  • "validate" (boolean - optional) - if true, the setting effect will read from the property to ensure the value was properly set
  • "runtime" (boolean - optional) - if true, make changes only temporarily, so that they are lost on the next reboot.
| [ftest 1000](../../tests/ftests/1000-sudo-effect-sd_bus_setting_set_int.json)
[ftest 1001](../../tests/ftests/1001-sudo-effect-sd_bus_setting_add_int.json)
[ftest 1002](../../tests/ftests/1002-sudo-effect-sd_bus_setting_sub_int.json)
[ftest 1003](../../tests/ftests/1003-sudo-effect-sd_bus_setting-CPUQuota.json)
[ftest 1004](../../tests/ftests/1004-sudo-effect-sd_bus_setting_add_int_infinity.json)
[ftest 1005](../../tests/ftests/1005-sudo-effect-sd_bus_setting_sub_infinity.json)
[ftest 1006](../../tests/ftests/1006-sudo-effect-sd_bus_setting_set_int_scope.json)
[ftest 1007](../../tests/ftests/1007-sudo-effect-sd_bus_setting_set_str.json) | | | [setting](../../src/effects/cgroup_setting.c) | Write to a setting file |
  • "setting" (string) - full path to the setting
  • "value" (string, long long, or double) - value to write to the setting file. If the operator is set to add or subtract, this value will be added/subtracted from the current value of setting
  • "operator" (string) - add, subtract, or set
  • "limit" (string, long long, or double - optional) - if provided, this effect will use the value as an upper or lower limit when the operator is set to add or subtract, respectfully
  • "validate" (boolean - optional) - if true, the setting effect will read from the setting file to ensure the value was properly set
| [ftest 055](../../tests/ftests/055-effect-setting_set_int.json)
[ftest 056](../../tests/ftests/056-effect-setting_add_int.json)
[ftest 057](../../tests/ftests/057-effect-setting_sub_int.json) | Shares a code base with the cgroup effect code | diff --git a/adaptived/src/effects/print.c b/adaptived/src/effects/print.c index 2daa50f..16d7c4a 100644 --- a/adaptived/src/effects/print.c +++ b/adaptived/src/effects/print.c @@ -44,8 +44,10 @@ enum file_enum { struct print_opts { FILE *file; + bool close_file; /* internal flag to tell us to close the file on exit */ const struct adaptived_cause *cse; char *msg; + bool shared_data; }; int print_init(struct adaptived_effect * const eff, struct json_object *args_obj, @@ -86,10 +88,27 @@ int print_init(struct adaptived_effect * const eff, struct json_object *args_obj if (strncmp(file_str, "stderr", strlen("stderr")) == 0) { opts->file = stderr; + opts->close_file = false; } else if (strncmp(file_str, "stdout", strlen("stdout")) == 0) { opts->file = stdout; + opts->close_file = false; } else { - ret = -EINVAL; + opts->file = fopen(file_str, "w"); + if (!opts->file) { + adaptived_err("Failed to fopen %s\n", file_str); + ret = -EINVAL; + goto error; + } + + opts->close_file = true; + } + + ret = adaptived_parse_bool(args_obj, "shared_data", &opts->shared_data); + if (ret == -ENOENT) { + opts->shared_data = false; + ret = 0; + } else if (ret) { + adaptived_err("Failed to parse shared_data arg: %d\n", ret); goto error; } @@ -111,6 +130,128 @@ int print_init(struct adaptived_effect * const eff, struct json_object *args_obj return ret; } +static void print_custom(const struct print_opts * const opts, void *data) +{ + fprintf(opts->file, "Cause %s shared custom data 0x%p\n", opts->cse->name, data); +} + +static void print_str(const struct print_opts * const opts, char *data) +{ + fprintf(opts->file, "Cause %s shared string \"%s\"\n", opts->cse->name, data); +} + +static void print_cgroup(const struct print_opts * const opts, struct adaptived_cgroup_value *data) +{ + switch(data->type) { + case ADAPTIVED_CGVAL_STR: + fprintf(opts->file, "Cause %s shared string value \"%s\"\n", opts->cse->name, + data->value.str_value); + break; + case ADAPTIVED_CGVAL_LONG_LONG: + fprintf(opts->file, "Cause %s shared long long value \"%lld\"\n", opts->cse->name, + data->value.ll_value); + break; + case ADAPTIVED_CGVAL_FLOAT: + fprintf(opts->file, "Cause %s shared float value \"%f\"\n", opts->cse->name, + data->value.float_value); + break; + default: + adaptived_err("Unsupported cgroup type: %d\n", data->type); + } +} + +static void print_name_value(const struct print_opts * const opts, + struct adaptived_name_and_value *data) +{ + switch(data->value->type) { + case ADAPTIVED_CGVAL_STR: + fprintf(opts->file, "Cause %s shared name \"%s\" and string value \"%s\"\n", + opts->cse->name, data->name, data->value->value.str_value); + break; + case ADAPTIVED_CGVAL_LONG_LONG: + fprintf(opts->file, "Cause %s shared name \"%s\" and long long value \"%lld\"\n", + opts->cse->name, data->name, data->value->value.ll_value); + break; + case ADAPTIVED_CGVAL_FLOAT: + fprintf(opts->file, "Cause %s shared name \"%s\" and float value \"%f\"\n", + opts->cse->name, data->name, data->value->value.float_value); + break; + default: + adaptived_err("Unsupported cgroup type: %d\n", data->value->type); + } +} + +static void print_cgroup_setting_and_value(const struct print_opts * const opts, + struct adaptived_cgroup_setting_and_value *data) +{ + switch(data->value->type) { + case ADAPTIVED_CGVAL_STR: + fprintf(opts->file, + "Cause \"%s\" shared\n\tcgroup \"%s\"\n\tsetting \"%s\"\n\tstring value \"%s\"\n", + opts->cse->name, data->cgroup_name, data->setting, + data->value->value.str_value); + break; + case ADAPTIVED_CGVAL_LONG_LONG: + fprintf(opts->file, + "Cause \"%s\" shared\n\tcgroup \"%s\"\n\tsetting \"%s\"\n\tlong long value \"%lld\"\n", + opts->cse->name, data->cgroup_name, data->setting, + data->value->value.ll_value); + break; + case ADAPTIVED_CGVAL_FLOAT: + fprintf(opts->file, + "Cause \"%s\" shared\n\tcgroup \"%s\"\n\tsetting \"%s\"\n\tfloat value \"%f\"\n", + opts->cse->name, data->cgroup_name, data->setting, + data->value->value.float_value); + break; + default: + adaptived_err("Unsupported cgroup type: %d\n", data->value->type); + } +} + +static void print_shared_data(const struct print_opts * const opts) +{ + const struct adaptived_cause *cse = opts->cse; + enum adaptived_sdata_type type; + void *read_data; + int ret, cnt, i; + uint32_t flags; + + while (cse) { + cnt = adaptived_get_shared_data_cnt(cse); + + for (i = 0; i < cnt; i++) { + ret = adaptived_get_shared_data(cse, i, &type, &read_data, &flags); + if (ret) { + adaptived_err("Failed to get shared data: %d\n", ret); + cnt--; + continue; + } + + switch(type) { + case ADAPTIVED_SDATA_CUSTOM: + print_custom(opts, read_data); + break; + case ADAPTIVED_SDATA_STR: + print_str(opts, read_data); + break; + case ADAPTIVED_SDATA_CGROUP: + print_cgroup(opts, read_data); + break; + case ADAPTIVED_SDATA_NAME_VALUE: + print_name_value(opts, read_data); + break; + case ADAPTIVED_SDATA_CGROUP_SETTING_VALUE: + print_cgroup_setting_and_value(opts, read_data); + break; + default: + adaptived_err("Unsupported shared data type: %d\n", type); + } + } + + cse = cse->next; + } +} + int print_main(struct adaptived_effect * const eff) { struct print_opts *opts = (struct print_opts *)eff->data; @@ -129,6 +270,9 @@ int print_main(struct adaptived_effect * const eff) } } + if (opts->shared_data) + print_shared_data(opts); + return 0; } @@ -136,6 +280,8 @@ void print_exit(struct adaptived_effect * const eff) { struct print_opts *opts = (struct print_opts *)eff->data; + if (opts->close_file) + fclose(opts->file); if (opts->msg) free(opts->msg); free(opts); From 2b2176b6ba6a47841c40daaadd40275e2a4e6495 Mon Sep 17 00:00:00 2001 From: Tom Hromatka Date: Tue, 12 Aug 2025 07:05:30 -0600 Subject: [PATCH 5/9] adaptived: ftests: Add compare_files_unsorted() Add a file comparison function, compare_files_unsorted(), that will compare two files even if the data is out of order. Note that the comparison is quite slow as the second file is repeatedly walked. Signed-off-by: Tom Hromatka --- adaptived/tests/ftests/ftests.c | 89 +++++++++++++++++++++++++++++++++ adaptived/tests/ftests/ftests.h | 1 + 2 files changed, 90 insertions(+) diff --git a/adaptived/tests/ftests/ftests.c b/adaptived/tests/ftests/ftests.c index c9ee428..3ded094 100644 --- a/adaptived/tests/ftests/ftests.c +++ b/adaptived/tests/ftests/ftests.c @@ -128,6 +128,95 @@ int compare_files(const char * const file1, const char * const file2) return ret; } +#define MAX_LINES 4096 +int compare_files_unsorted(const char * const file1, const char * const file2) +{ + char *f1_line = NULL, *f2_line = NULL; + int f1_line_cnt = 0, f2_line_cnt = 0; + bool used[MAX_LINES] = { false }; + FILE *f1 = NULL, *f2 = NULL; + size_t f1_sz, f2_sz; + bool found_line; + ssize_t read; + int ret, i; + + f1 = fopen(file1, "r"); + if (!f1) { + ret = -errno; + goto err; + } + + f2 = fopen(file2, "r"); + if (!f2) { + ret = -errno; + goto err; + } + + + while ((read = getline(&f1_line, &f1_sz, f1)) != -1) { + f1_line_cnt++; + rewind(f2); + i = 0; + + while ((read = getline(&f2_line, &f2_sz, f2)) != -1) { + if (i < MAX_LINES && used[i] == true) { + i++; + continue; + } + + if (f1_sz != f2_sz) { + i++; + continue; + } + + if (strncmp(f1_line, f2_line, f1_sz) == 0) { + found_line = true; + if (i < MAX_LINES) + used[i] = true; + break; + } + + i++; + } + + if (!found_line) { + ret = -ENOSTR; + goto err; + } + } + + for (i = 0; i < MAX_LINES && i < f1_line_cnt; i++) { + if (!used[i]) { + adaptived_err("Line %d in %s was unused\n", i, file2); + ret = -ENODATA; + goto err; + } + } + + rewind(f2); + while ((read = getline(&f2_line, &f2_sz, f2)) != -1) + f2_line_cnt++; + + if (f1_line_cnt != f2_line_cnt) { + ret = -ENODATA; + goto err; + } + + ret = 0; + +err: + if (f1_line) + free(f1_line); + if (f2_line) + free(f2_line); + if (f1) + fclose(f1); + if (f2) + fclose(f2); + + return ret; +} + int verify_int_file(const char * const filename, int expected_value) { char buf[1024] = { '\0' }; diff --git a/adaptived/tests/ftests/ftests.h b/adaptived/tests/ftests/ftests.h index 387518f..eef4bae 100644 --- a/adaptived/tests/ftests/ftests.h +++ b/adaptived/tests/ftests/ftests.h @@ -58,6 +58,7 @@ void delete_file(const char * const filename); void delete_files(const char * const files[], int files_cnt); void write_file(const char * const filename, const char * const contents); int compare_files(const char * const file1, const char * const file2); +int compare_files_unsorted(const char * const file1, const char * const file2); int verify_int_file(const char * const filename, int expected_value); int verify_ll_file(const char * const filename, long long expected_value); int verify_char_file(const char * const filename, const char * const expected_contents); From 1bc25259383511db6e9fc5913ebaa06b1e6fc578 Mon Sep 17 00:00:00 2001 From: Tom Hromatka Date: Fri, 1 Aug 2025 13:08:38 -0600 Subject: [PATCH 6/9] adaptived: ftests: Add a test for the cgroup_data cause Add a functional test for the cgroup_data cause. Note that this test operates on a fictional cgroup hierarchy that is temporarily built under the ftests directory. $ ./test071 ./ $ echo $? 0 Signed-off-by: Tom Hromatka --- adaptived/tests/ftests/.gitignore | 1 + .../tests/ftests/071-cause-cgroup_data.c | 188 ++++++++++++++++++ .../ftests/071-cause-cgroup_data.expected | 38 ++++ .../tests/ftests/071-cause-cgroup_data.json | 35 ++++ adaptived/tests/ftests/Makefile.am | 6 +- 5 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 adaptived/tests/ftests/071-cause-cgroup_data.c create mode 100644 adaptived/tests/ftests/071-cause-cgroup_data.expected create mode 100644 adaptived/tests/ftests/071-cause-cgroup_data.json diff --git a/adaptived/tests/ftests/.gitignore b/adaptived/tests/ftests/.gitignore index 1afedd6..c13319f 100644 --- a/adaptived/tests/ftests/.gitignore +++ b/adaptived/tests/ftests/.gitignore @@ -1,5 +1,6 @@ __pycache__/ *.log +*.out *.pressure *.trs test[0-9]* diff --git a/adaptived/tests/ftests/071-cause-cgroup_data.c b/adaptived/tests/ftests/071-cause-cgroup_data.c new file mode 100644 index 0000000..efa9637 --- /dev/null +++ b/adaptived/tests/ftests/071-cause-cgroup_data.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2024-2025, Oracle and/or its affiliates. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/** + * Test to exercise the cgroup_data cause + * + * Note that this test creates a fake cgroup hierarchy directly in the + * tests/ftests directory and operates on it. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ftests.h" + +#define EXPECTED_RET -ETIME + +static const char * const cgroup_dirs[] = { + "./test071cgroup", + "./test071cgroup/child1", + "./test071cgroup/child1/grandchild11/", + "./test071cgroup/child2", + "./test071cgroup/child3" +}; +static const int cgroup_dirs_cnt = ARRAY_SIZE(cgroup_dirs); + +static const char * const cgroup_files[] = { + "./test071cgroup/memory.current", + "./test071cgroup/cpu.uclamp.min", + "./test071cgroup/cpuset.cpus.effective", + "./test071cgroup/cpu.max", + + "./test071cgroup/child1/memory.current", + "./test071cgroup/child1/cpu.uclamp.min", + "./test071cgroup/child1/cpuset.cpus.effective", + "./test071cgroup/child1/cpu.max", + + "./test071cgroup/child1/grandchild11/memory.current", + "./test071cgroup/child1/grandchild11/cpu.uclamp.min", + "./test071cgroup/child1/grandchild11/cpuset.cpus.effective", + "./test071cgroup/child1/grandchild11/cpu.max", + + "./test071cgroup/child2/memory.current", + "./test071cgroup/child2/cpu.uclamp.min", + "./test071cgroup/child2/cpuset.cpus.effective", + "./test071cgroup/child2/cpu.max", + + "./test071cgroup/child3/memory.current", + "./test071cgroup/child3/cpu.uclamp.min", + "./test071cgroup/child3/cpuset.cpus.effective", + "./test071cgroup/child3/cpu.max", +}; +static const int cgroup_files_cnt = ARRAY_SIZE(cgroup_files); + +/* + * Write the file contents as strings since that's how adaptived will read out + * the data. adaptived will then try to determine the correct data type. + */ +static const char * const file_contents[] = { + "4096000", + "1.234", + "1-2,4-7,10,12", + "max 100000", + + "8192000", + "3.456", + "1-20", + "10000 100000", + + "1024000", + "0.000", + "1,3,5,7,9,11", + "250000 1000000", + + "0", + "9.876", + "5-8", + "max 1000000", + + "12345678", + "4.680", + "8,10", + "500000 1000000", +}; +static_assert(ARRAY_SIZE(file_contents) == ARRAY_SIZE(cgroup_files), + "file_contents should be the same size as cgroup_files_cnt"); + + +static void write_cgroup_files(void) +{ + int i; + + for (i = 0; i < cgroup_files_cnt; i++) + write_file(cgroup_files[i], file_contents[i]); +} + +int main(int argc, char *argv[]) +{ + char config_path[FILENAME_MAX], expected_file[FILENAME_MAX]; + struct adaptived_ctx *ctx = NULL; + int ret; + + snprintf(config_path, FILENAME_MAX - 1, "%s/071-cause-cgroup_data.json", + argv[1]); + config_path[FILENAME_MAX - 1] = '\0'; + + ctx = adaptived_init(config_path); + if (!ctx) + return AUTOMAKE_HARD_ERROR; + + ret = create_dirs(cgroup_dirs, cgroup_dirs_cnt); + if (ret) + goto err; + + write_cgroup_files(); + + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_MAX_LOOPS, 1); + if (ret) + goto err; + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_INTERVAL, 1234); + if (ret) + goto err; + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_SKIP_SLEEP, 1); + if (ret) + goto err; + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_LOG_LEVEL, LOG_EMERG); + if (ret) + goto err; + + ret = adaptived_loop(ctx, true); + if (ret != EXPECTED_RET) { + adaptived_err("Test 071 returned: %d, expected: %d\n", ret, EXPECTED_RET); + goto err; + } + + adaptived_release(&ctx); + ctx = NULL; + + snprintf(expected_file, FILENAME_MAX - 1, "%s/071-cause-cgroup_data.expected", argv[1]); + expected_file[FILENAME_MAX - 1] = '\0'; + + ret = compare_files_unsorted("071-cause-cgroup_data.out", expected_file); + if (ret) + goto err; + + delete_file("071-cause-cgroup_data.out"); + delete_files(cgroup_files, cgroup_files_cnt); + delete_dirs(cgroup_dirs, cgroup_dirs_cnt); + + return AUTOMAKE_PASSED; + +err: + delete_file("071-cause-cgroup_data.out"); + delete_files(cgroup_files, cgroup_files_cnt); + delete_dirs(cgroup_dirs, cgroup_dirs_cnt); + if (ctx) + adaptived_release(&ctx); + + return AUTOMAKE_HARD_ERROR; +} diff --git a/adaptived/tests/ftests/071-cause-cgroup_data.expected b/adaptived/tests/ftests/071-cause-cgroup_data.expected new file mode 100644 index 0000000..a754e2c --- /dev/null +++ b/adaptived/tests/ftests/071-cause-cgroup_data.expected @@ -0,0 +1,38 @@ +Print effect triggered by: + cgroup_data +Cause "cgroup_data" shared + cgroup "child1" + setting "memory.current" + long long value "8192000" +Cause "cgroup_data" shared + cgroup "child1" + setting "cpu.uclamp.min" + float value "3.456000" +Cause "cgroup_data" shared + cgroup "child1" + setting "cpuset.cpus.effective" + string value "1-20" +Cause "cgroup_data" shared + cgroup "child2" + setting "memory.current" + long long value "0" +Cause "cgroup_data" shared + cgroup "child2" + setting "cpu.uclamp.min" + float value "9.876000" +Cause "cgroup_data" shared + cgroup "child2" + setting "cpuset.cpus.effective" + string value "5-8" +Cause "cgroup_data" shared + cgroup "child3" + setting "memory.current" + long long value "12345678" +Cause "cgroup_data" shared + cgroup "child3" + setting "cpu.uclamp.min" + float value "4.680000" +Cause "cgroup_data" shared + cgroup "child3" + setting "cpuset.cpus.effective" + string value "8,10" diff --git a/adaptived/tests/ftests/071-cause-cgroup_data.json b/adaptived/tests/ftests/071-cause-cgroup_data.json new file mode 100644 index 0000000..61f8600 --- /dev/null +++ b/adaptived/tests/ftests/071-cause-cgroup_data.json @@ -0,0 +1,35 @@ +{ + "rules": [ + { + "name": "Gather cgroup data and print it out", + "causes": [ + { + "name": "cgroup_data", + "args": { + "cgroup": "./test071cgroup///", + "settings": [ + { + "setting": "memory.current" + }, + { + "setting": "cpu.uclamp.min" + }, + { + "setting": "cpuset.cpus.effective" + } + ] + } + } + ], + "effects": [ + { + "name": "print", + "args": { + "file": "071-cause-cgroup_data.out", + "shared_data": true + } + } + ] + } + ] +} diff --git a/adaptived/tests/ftests/Makefile.am b/adaptived/tests/ftests/Makefile.am index 075a259..46ceacc 100644 --- a/adaptived/tests/ftests/Makefile.am +++ b/adaptived/tests/ftests/Makefile.am @@ -110,6 +110,7 @@ test067_SOURCES = 067-effect-kill_processes.c ftests.c test068_SOURCES = 068-effect-kill_processes_rss.c ftests.c test069_SOURCES = 069-effect-signal.c ftests.c test070_SOURCES = 070-rule-multiple_rules.c ftests.c +test071_SOURCES = 071-cause-cgroup_data.c ftests.c sudo1000_SOURCES = 1000-sudo-effect-sd_bus_setting_set_int.c ftests.c sudo1001_SOURCES = 1001-sudo-effect-sd_bus_setting_add_int.c ftests.c @@ -187,6 +188,7 @@ check_PROGRAMS = \ test068 \ test069 \ test070 \ + test071 \ sudo1000 \ sudo1001 \ sudo1002 \ @@ -282,7 +284,9 @@ EXTRA_DIST_CFGS = \ 067-effect-kill_processes.json \ 068-effect-kill_processes_rss.json \ 069-effect-signal.json \ - 070-rule-multiple_rules.json + 070-rule-multiple_rules.json \ + 071-cause-cgroup_data.json \ + 071-cause-cgroup_data.expected EXTRA_DIST_H_FILES = \ ftests.h From e57bdd471de05fe4caa20d66ae47414746643c42 Mon Sep 17 00:00:00 2001 From: Tom Hromatka Date: Mon, 4 Aug 2025 14:59:38 -0600 Subject: [PATCH 7/9] adaptived: ftests: Add a C token parser Add a utility function, parse_token_file(), that can parse a file, search for a given token of the format "<< TOKEN >>", and then output the replaced token to the specified file. This can be useful for inserting timestamps, absolute paths, etc. into JSON files. Signed-off-by: Tom Hromatka --- adaptived/tests/ftests/ftests.c | 124 ++++++++++++++++++++++++++++++++ adaptived/tests/ftests/ftests.h | 1 + 2 files changed, 125 insertions(+) diff --git a/adaptived/tests/ftests/ftests.c b/adaptived/tests/ftests/ftests.c index 3ded094..77efd90 100644 --- a/adaptived/tests/ftests/ftests.c +++ b/adaptived/tests/ftests/ftests.c @@ -757,3 +757,127 @@ int start_unit(const char *unit_name, const char *cmd_to_run) return ret; } + +static int replace_token(FILE * const out_file, const char * const line, ssize_t line_len) +{ + char *start, *end, *token_str, *op = NULL; + ssize_t op_len, start_len, end_len; + char cwd[FILENAME_MAX]; + int ret = 0; + + start = strstr(line, "<< "); + end = strstr(line, " >>"); + + /* + * Subtract 2 for the <<, subtract 1 for the first whitespace padding, Add 1 for the + * NULL terminator + */ + op_len = end - start - 2 - 1 + 1; + + op = malloc(op_len * sizeof(char)); + if (!op) + return -ENOMEM; + + snprintf(op, op_len, "%s", &start[3]); + op[op_len - 1] = '\0'; + + if (strcmp(op, "pwd") == 0) { + getcwd(cwd, sizeof(cwd)); + token_str = cwd; + } else { + /* unsupported operation */ + ret = -EINVAL; + goto err; + } + + start_len = start - line; + + if (start_len) + (void)fwrite(line, sizeof(char), start_len, out_file); + + (void)fwrite(token_str, sizeof(char), strlen(token_str), out_file); + + /* + * We can't do strlen(line) because it may contain extra characters + * from a previous getline(). + * + * The first "-3" is the length of the start token, and the second + * "-3" is the length of the end token + */ + end_len = line_len - start_len - op_len - 3 - 3; + + if (end_len) + (void)fwrite(&end[3], sizeof(char), end_len, out_file); + + (void)fwrite("\n", sizeof(char), strlen("\n"), out_file); + +err: + if (op) + free(op); + + return ret; +} + +/* + * Parse the specified token file, e.g. test001.json.token, and output the + * parsed contents to the filename minus the ".token" suffix, e.g. + * test001.json + * + * It is the responsibility of the caller to delete the output file + */ +int parse_token_file(const char * const token_file, const char * const out_file) +{ + FILE *in = NULL, *out = NULL; + ssize_t chars_rd, chars_wr; + char *line = NULL; + size_t line_sz; + int ret; + + if (!token_file) + return -EINVAL; + + if (!strstr(token_file, ".token")) + return -EINVAL; + + in = fopen(token_file, "r"); + if (!in) { + adaptived_err("Failed to open: %s: \n", token_file, errno); + ret = -errno; + goto err; + } + + out = fopen(out_file, "w"); + if (!out) { + adaptived_err("Failed to open: %s: %d\n", out_file, errno); + ret = -errno; + goto err; + } + + while ((chars_rd = getline(&line, &line_sz, in)) != -1) { + if (strstr(line, "<<") && strstr(line, ">>")) { + ret = replace_token(out, line, chars_rd); + if (ret) + goto err; + } else { + chars_wr = fwrite(line, sizeof(char), chars_rd, out); + if (chars_wr != chars_rd) { + adaptived_err("Failed to write \"%s\" to %s\n", line, out_file); + ret = -EIO; + goto err; + } + } + } + + ret = 0; + +err: + if (in) + fclose(in); + if (out) + fclose(out); + + if (line) + free(line); + + return ret; +} diff --git a/adaptived/tests/ftests/ftests.h b/adaptived/tests/ftests/ftests.h index eef4bae..22fa99a 100644 --- a/adaptived/tests/ftests/ftests.h +++ b/adaptived/tests/ftests/ftests.h @@ -85,5 +85,6 @@ int get_cgroup_version(int * const version); int build_cgroup_path(const char * const controller, const char * const cgrp, char ** path); int build_systemd_cgroup_path(const char * const cgrp, char ** path); int build_systemd_memory_max_file(const char * const cgrp_path, char **file_path); +int parse_token_file(const char * const token_file, const char * const out_file); #endif /* __ADAPTIVED_FTESTS_H */ From 1ec3870396c4ce4b9a17c1c3d11d9f72eacc704d Mon Sep 17 00:00:00 2001 From: Tom Hromatka Date: Mon, 4 Aug 2025 15:01:43 -0600 Subject: [PATCH 8/9] adaptived: ftests: Add an absolute path test for the cgroup_data cause Add a functional test for the cgroup_data cause. Note that this test operates on a fictional cgroup hierarchy that is temporarily built under the ftests directory. Also note that the test produces output paths that are absolute paths. $ ./test072 ./ $ echo $? 0 Signed-off-by: Tom Hromatka --- .../tests/ftests/072-cause-cgroup_data2.c | 273 ++++++++++++++++++ .../072-cause-cgroup_data2.expected.token | 42 +++ .../ftests/072-cause-cgroup_data2.json.token | 37 +++ adaptived/tests/ftests/Makefile.am | 6 +- 4 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 adaptived/tests/ftests/072-cause-cgroup_data2.c create mode 100644 adaptived/tests/ftests/072-cause-cgroup_data2.expected.token create mode 100644 adaptived/tests/ftests/072-cause-cgroup_data2.json.token diff --git a/adaptived/tests/ftests/072-cause-cgroup_data2.c b/adaptived/tests/ftests/072-cause-cgroup_data2.c new file mode 100644 index 0000000..46da095 --- /dev/null +++ b/adaptived/tests/ftests/072-cause-cgroup_data2.c @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2024-2025, Oracle and/or its affiliates. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/** + * Test to exercise the cgroup_data cause + * + * Note that this test creates a fake cgroup hierarchy directly in the + * tests/ftests directory and operates on it. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ftests.h" + +#define EXPECTED_RET -ETIME + +static const char * const cgroup_dirs[] = { + "./test072cgroup", + "./test072cgroup/child1", + "./test072cgroup/child1/grandchild11/", + "./test072cgroup/child2", + "./test072cgroup/child3" +}; +static const int cgroup_dirs_cnt = ARRAY_SIZE(cgroup_dirs); + +static const char * const cgroup_files[] = { + "./test072cgroup/memory.current", + "./test072cgroup/cpu.uclamp.min", + "./test072cgroup/cpuset.cpus.effective", + "./test072cgroup/cpu.max", + + "./test072cgroup/child1/memory.current", + "./test072cgroup/child1/cpu.uclamp.min", + /* Intentionally omit cpuset.cpus.effective from the child1 cgroup */ + "./test072cgroup/child1/cpu.max", + + "./test072cgroup/child1/grandchild11/memory.current", + "./test072cgroup/child1/grandchild11/cpu.uclamp.min", + "./test072cgroup/child1/grandchild11/cpuset.cpus.effective", + /* Intentionally omit cpu.max from grandchild11 */ + + "./test072cgroup/child2/memory.current", + "./test072cgroup/child2/cpu.uclamp.min", + "./test072cgroup/child2/cpuset.cpus.effective", + "./test072cgroup/child2/cpu.max", + + /* Intentionally omit memory.current from child3 */ + "./test072cgroup/child3/cpu.uclamp.min", + "./test072cgroup/child3/cpuset.cpus.effective", + "./test072cgroup/child3/cpu.max", +}; +static const int cgroup_files_cnt = ARRAY_SIZE(cgroup_files); + +/* + * Write the file contents as strings since that's how adaptived will read out + * the data. adaptived will then try to determine the correct data type. + */ +static const char * const file_contents[] = { + "4096000", + "1.234", + "1-2,4-7,10,12", + "max 100000", + + "8192000", + "3.456", + /* file omitted */ + "10000 100000", + + "1024000", + "0.000", + "1,3,5,7,9,11", + /* file omitted */ + + "0", + "9.876", + "5-8", + "max 1000000", + + /* file omitted */ + "4.680", + "8,10", + "500000 1000000", +}; +static_assert(ARRAY_SIZE(file_contents) == ARRAY_SIZE(cgroup_files), + "file_contents should be the same size as cgroup_files_cnt"); + +const char * const config_token_file = "072-cause-cgroup_data2.json.token"; +const char * const config_file = "072-cause-cgroup_data2.json"; +const char * const expected_token_file = "072-cause-cgroup_data2.expected.token"; +const char * const expected_file = "072-cause-cgroup_data2.expected"; +const char * const out_file = "072-cause-cgroup_data2.out"; + +static void write_cgroup_files(void) +{ + int i; + + for (i = 0; i < cgroup_files_cnt; i++) + write_file(cgroup_files[i], file_contents[i]); +} + +int main(int argc, char *argv[]) +{ + char *ftests_path, *config_token_path = NULL, *config_path = NULL, *out_path = NULL, + *expected_token_path = NULL, *expected_path = NULL; + struct adaptived_ctx *ctx = NULL; + ssize_t ftests_path_len, cwd_len; + char cwd[FILENAME_MAX]; + int ret; + + ftests_path = argv[1]; + ftests_path_len = strlen(ftests_path); + + getcwd(cwd, sizeof(cwd)); + cwd_len = strlen(cwd); + + config_token_path = calloc(sizeof(char), + (ftests_path_len + strlen(config_token_file) + 2)); + if (!config_token_path) + goto err; + sprintf(config_token_path, "%s/%s", ftests_path, config_token_file); + + config_path = calloc(sizeof(char), + (cwd_len + strlen(config_file) + 2)); + if (!config_path) + goto err; + sprintf(config_path, "%s/%s", cwd, config_file); + + out_path = calloc(sizeof(char), (cwd_len + strlen(out_file) + 2)); + if (!out_path) + goto err; + sprintf(out_path, "%s/%s", cwd, out_file); + + expected_token_path = calloc(sizeof(char), + (ftests_path_len + strlen(expected_token_file) + 2)); + if (!expected_token_path) + goto err; + sprintf(expected_token_path, "%s/%s", ftests_path, expected_token_file); + + expected_path = calloc(sizeof(char), + (cwd_len + strlen(expected_file) + 2)); + if (!expected_path) + goto err; + sprintf(expected_path, "%s/%s", cwd, expected_file); + + ret = parse_token_file(config_token_path, config_path); + if (ret) + goto err; + + ret = parse_token_file(expected_token_path, expected_path); + if (ret) + goto err; + + ctx = adaptived_init(config_path); + if (!ctx) + return AUTOMAKE_HARD_ERROR; + + ret = create_dirs(cgroup_dirs, cgroup_dirs_cnt); + if (ret) + goto err; + + write_cgroup_files(); + + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_MAX_LOOPS, 1); + if (ret) + goto err; + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_SKIP_SLEEP, 1); + if (ret) + goto err; + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_LOG_LEVEL, LOG_EMERG); + if (ret) + goto err; + + ret = adaptived_loop(ctx, true); + if (ret != EXPECTED_RET) { + adaptived_err("Test 072 returned: %d, expected: %d\n", ret, EXPECTED_RET); + goto err; + } + + adaptived_release(&ctx); + ctx = NULL; + + ret = compare_files_unsorted(out_path, expected_path); + if (ret) + goto err; + + if (config_token_path) + free(config_token_path); + + if (expected_token_path) + free(expected_token_path); + + if (out_path) { + delete_file(out_path); + free(out_path); + } + + if (config_path) { + delete_file(config_path); + free(config_path); + } + + if (expected_path) { + delete_file(expected_path); + free(expected_path); + } + + delete_files(cgroup_files, cgroup_files_cnt); + delete_dirs(cgroup_dirs, cgroup_dirs_cnt); + + if (ctx) + adaptived_release(&ctx); + + return AUTOMAKE_PASSED; + +err: + if (config_token_path) + free(config_token_path); + + if (expected_token_path) + free(expected_token_path); + + if (out_path) { + delete_file(out_path); + free(out_path); + } + + if (config_path) { + delete_file(config_path); + free(config_path); + } + + if (expected_path) { + delete_file(expected_path); + free(expected_path); + } + + delete_files(cgroup_files, cgroup_files_cnt); + delete_dirs(cgroup_dirs, cgroup_dirs_cnt); + + if (ctx) + adaptived_release(&ctx); + + return AUTOMAKE_HARD_ERROR; +} diff --git a/adaptived/tests/ftests/072-cause-cgroup_data2.expected.token b/adaptived/tests/ftests/072-cause-cgroup_data2.expected.token new file mode 100644 index 0000000..81b7d81 --- /dev/null +++ b/adaptived/tests/ftests/072-cause-cgroup_data2.expected.token @@ -0,0 +1,42 @@ +Print effect triggered by: + cgroup_data +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child1" + setting "memory.current" + long long value "8192000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child1" + setting "cpu.uclamp.min" + float value "3.456000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child1/grandchild11" + setting "memory.current" + long long value "1024000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child1/grandchild11" + setting "cpu.uclamp.min" + float value "0.000000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child1/grandchild11" + setting "cpuset.cpus.effective" + string value "1,3,5,7,9,11" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child2" + setting "memory.current" + long long value "0" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child2" + setting "cpu.uclamp.min" + float value "9.876000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child2" + setting "cpuset.cpus.effective" + string value "5-8" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child3" + setting "cpu.uclamp.min" + float value "4.680000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child3" + setting "cpuset.cpus.effective" + string value "8,10" diff --git a/adaptived/tests/ftests/072-cause-cgroup_data2.json.token b/adaptived/tests/ftests/072-cause-cgroup_data2.json.token new file mode 100644 index 0000000..ae343f6 --- /dev/null +++ b/adaptived/tests/ftests/072-cause-cgroup_data2.json.token @@ -0,0 +1,37 @@ +{ + "rules": [ + { + "name": "Gather cgroup data and print it out", + "causes": [ + { + "name": "cgroup_data", + "args": { + "cgroup": "<< pwd >>/test072cgroup", + "rel_paths": false, + "max_depth": 10, + "settings": [ + { + "setting": "memory.current" + }, + { + "setting": "cpu.uclamp.min" + }, + { + "setting": "cpuset.cpus.effective" + } + ] + } + } + ], + "effects": [ + { + "name": "print", + "args": { + "file": "072-cause-cgroup_data2.out", + "shared_data": true + } + } + ] + } + ] +} diff --git a/adaptived/tests/ftests/Makefile.am b/adaptived/tests/ftests/Makefile.am index 46ceacc..8516ba5 100644 --- a/adaptived/tests/ftests/Makefile.am +++ b/adaptived/tests/ftests/Makefile.am @@ -111,6 +111,7 @@ test068_SOURCES = 068-effect-kill_processes_rss.c ftests.c test069_SOURCES = 069-effect-signal.c ftests.c test070_SOURCES = 070-rule-multiple_rules.c ftests.c test071_SOURCES = 071-cause-cgroup_data.c ftests.c +test072_SOURCES = 072-cause-cgroup_data2.c ftests.c sudo1000_SOURCES = 1000-sudo-effect-sd_bus_setting_set_int.c ftests.c sudo1001_SOURCES = 1001-sudo-effect-sd_bus_setting_add_int.c ftests.c @@ -189,6 +190,7 @@ check_PROGRAMS = \ test069 \ test070 \ test071 \ + test072 \ sudo1000 \ sudo1001 \ sudo1002 \ @@ -286,7 +288,9 @@ EXTRA_DIST_CFGS = \ 069-effect-signal.json \ 070-rule-multiple_rules.json \ 071-cause-cgroup_data.json \ - 071-cause-cgroup_data.expected + 071-cause-cgroup_data.expected \ + 072-cause-cgroup_data2.json.token \ + 072-cause-cgroup_data2.expected.token EXTRA_DIST_H_FILES = \ ftests.h From 91c5676ddc0723424371482ed22efe935eb81fa1 Mon Sep 17 00:00:00 2001 From: Tom Hromatka Date: Thu, 31 Jul 2025 15:47:29 -0600 Subject: [PATCH 9/9] adaptived: doc: Add documentation for the cgroup_data cause Signed-off-by: Tom Hromatka --- adaptived/doc/internal/list-of-built-in-causes.md | 1 + adaptived/doc/internal/list-of-built-in-effects.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/adaptived/doc/internal/list-of-built-in-causes.md b/adaptived/doc/internal/list-of-built-in-causes.md index 7daaea8..cf678c0 100644 --- a/adaptived/doc/internal/list-of-built-in-causes.md +++ b/adaptived/doc/internal/list-of-built-in-causes.md @@ -5,6 +5,7 @@ Parameters that accept long long or float also support some human-readable forma | Cause | Trigger | Schema | Examples | Notes | | ----- | ------- | ------ | -------- | ----- | | [always](../../src/causes/always.c) | Will trigger every single time it's run. Likely only useful for testing and debugging | | [ftest 021](../../tests/ftests/021-effect-cgroup_setting_sub_int.json) | | +| [cgroup_data](../../src/causes/cgroup_data.c) | Always triggers but can be used in conjunction with other causes to limit its trigger rate. Gathers a cgroup hierarchy's settings and values. Data is shared with effects in the same rule using the shared_data mechanism |
  • "cgroup" (string) - full path to the cgroup directory
  • "settings" (array)
    • "setting" (string) - Cgroup setting to read and save in shared_data
  • "max_depth" (int - optional) - maximum depth to traverse in the cgroup hierarchy. Default - the first-level children of `cgroup_path`
  • "rel_paths" (boolean - optional) - If true, the cgroup name stored in the shared data will be a relative path. Default - true
| [ftest 071](../../tests/ftests/071-cause-cgroup_data.json)
[ftest 072](../../tests/ftests/072-cause-cgroup_data2.json.token) | | | [cgroup_setting](../../src/causes/cgroup_setting.c) | Will trigger when a cgroup setting exceeds the specified threshold. (Note - will work on any file that contains a float or long long) |
  • "setting" (string) - full path to the cgroup setting
  • "threshold" (long long or float)
  • "operator" (string) - currently greaterthan, lessthan, or equal
| [ftest 029](../../tests/ftests/029-cause-cgroup_setting_ll_gt.json)
[ftest 030](../../tests/ftests/030-cause-cgroup_setting_ll_lt.json)
[ftest 031](../../tests/ftests/031-cause-cgroup_setting_float_gt.json)
[ftest 032](../../tests/ftests/032-cause-cgroup_setting_float_lt.json) | | | [days_of_the_week](../../src/causes/days_of_the_week.c) | Will trigger when today matches one of the specified day(s) (Monday, Tuesday, etc.) in the config file |
  • "days" (array)
    • "day" (string)
| [Jimmy Buffett Example](../examples/jimmy-buffett-config.json)
[ftest 004](../../tests/ftests/004-register_plugin_effect.json) | | | [meminfo](../../src/causes/meminfo.c) | Will trigger when a field in /proc/meminfo exceeds the specified threshold |
  • "meminfo_file" (string - optional) - path to the meminfo file. Useful for testing.
  • "field" (string) - field in the meminfo file to operate on, e.g. AnonPages
  • "threshold" (long long) - threshold in bytes
  • "operator" (string) - currently greaterthan, lessthan, or equal
| [ftest 048](../../tests/ftests/048-cause-meminfo_gt.json)
[ftest 049](../../tests/ftests/049-cause-meminfo_lt.json)
[ftest 050](../../tests/ftests/050-cause-meminfo_eq.json) | | diff --git a/adaptived/doc/internal/list-of-built-in-effects.md b/adaptived/doc/internal/list-of-built-in-effects.md index e0e6090..4de0855 100644 --- a/adaptived/doc/internal/list-of-built-in-effects.md +++ b/adaptived/doc/internal/list-of-built-in-effects.md @@ -12,7 +12,7 @@ Parameters that accept long long or float also support some human-readable forma | [kill_cgroup_by_psi](../../src/effects/kill_cgroup_by_psi.c) | Walk a cgroup tree, and kill the processes in the cgroup with the highest PSI utilization |
  • "cgroup" (string) - full path to the cgroup to be killed. See [path rules](path-rules.md) for more details. Use the "\*" wildcard to ensure the tree is walked.
  • "type" (string) - which PSI type to evaluate, "cpu", "memory", or "io"
  • "measurement" (string) - which measurement to compare, e.g. some-avg10, full-avg60, etc. some-total and full-total are not supported
  • "signal" (int - optional) - signal to send to the processes being killed. Default - SIGKILL
  • "max_depth" (int - optional) - maximum depth to traverse in the cgroup hierarchy. Default - unlimited
| [ftest 024](../../tests/ftests/024-effect-kill_cgroup_by_psi.json) | | | [kill_processes](../../src/effects/kill_processes.c) | Kill processes that match the specified process name(s) |
  • "proc_names" (array)
    • "name" (string) - process name (as found in /proc/{pid}/stat)
  • "signal" (int - optional) - signal to send to the processes being killed. Currently only supports integers. Default - 9 (i.e. SIGKILL)
  • "count" (int - optional) - number of processes to kill each time this cause is run. If specified, the processes consuming the most memory will be killed first. Default - all matching processes
  • "field" (string - optional) - field in /proc/pid/stat to sort on. Currently supports "vsize" or "rss". Default - "rss".
| [ftest 067](../../tests/ftests/067-effect-kill_processes.json)
[ftest 068](../../tests/ftests/068-effect-kill_processes_rss.json) | | | [logger](../../src/effects/logger.c) | Given an array of files, write their contents to "logfile" |
  • "logfile" (string) - Output file to store the log data
  • "max_file_size" (int - optional) - Maximum amount of data that will be copied from each source file. Defaults to 32kB if not specified
  • "files" (array)
    • "file" (string) - file to copy
  • "separator_prefix" (string - optional) - If specified, this string will be written each time this effect triggers
  • "date_format" (string - optional) - If specified, the date will be written in the specified format each time the effect triggers
  • "utc" (boolean - optional) - If specified, the date will be recorded in UTC time. Otherwise, the machine's localtime() will be used
  • "separator_postfix" (string - optional) - If specified, this string will be written each time this effect triggers
  • "file_separator" (string - optional) -If specified, this string will be written between each file being logged
| [ftest 043](../../tests/ftests/043-effect-logger-no-separators.json)
[ftest 044](../../tests/ftests/044-effect-logger-date-format.json) | | -| [print](../../src/effects/print.c) | Print a message to a file |
  • "message" (string - optional) - message to output
  • "file" (string) - file to write to. Supports "stderr", "stdout", or any arbitrary path and filename
  • "shared_data" (boolean - optional) - If specified, this effect will print the data that has been shared by the causes in this rule. Default - false
| [Jimmy Buffett Example](../examples/jimmy-buffett-config.json) | | +| [print](../../src/effects/print.c) | Print a message to a file |
  • "message" (string - optional) - message to output
  • "file" (string) - file to write to. Supports "stderr", "stdout", or any arbitrary path and filename
  • "shared_data" (boolean - optional) - If specified, this effect will print the data that has been shared by the causes in this rule. Default - false
| [Jimmy Buffett Example](../examples/jimmy-buffett-config.json)
[ftest 071](../../tests/ftests/071-cause-cgroup_data.json)
[ftest 072](../../tests/ftests/072-cause-cgroup_data2.json.token) | | | [print_schedstat](../../src/effects/print_schedstat.c) | Print schedstat to a file |
  • "file" (string) - file to write to. Currently only supports "stdout" or "stderr"
| [ftest 054](../../tests/ftests/054-effect-print_schedstat.json) | | | [sd_bus_setting](../../src/effects/sd_bus_setting.c) | Operate on sd_bus properties |
  • "target" (string) - cgroup slice name or scope name
  • "setting" (string) - sd_bus property name (e.g. MemoryMax)
  • "value" (string, long long, or double) - value to write to the property. If the operator is set to add or subtract, this value will be added/subtracted from the current value of the property
  • "operator" (string) - add, subtract, or set
  • "limit" (string, long long, or double - optional) - if provided, this effect will use the value as an upper or lower limit when the operator is set to add or subtract, respectfully
  • "validate" (boolean - optional) - if true, the setting effect will read from the property to ensure the value was properly set
  • "runtime" (boolean - optional) - if true, make changes only temporarily, so that they are lost on the next reboot.
| [ftest 1000](../../tests/ftests/1000-sudo-effect-sd_bus_setting_set_int.json)
[ftest 1001](../../tests/ftests/1001-sudo-effect-sd_bus_setting_add_int.json)
[ftest 1002](../../tests/ftests/1002-sudo-effect-sd_bus_setting_sub_int.json)
[ftest 1003](../../tests/ftests/1003-sudo-effect-sd_bus_setting-CPUQuota.json)
[ftest 1004](../../tests/ftests/1004-sudo-effect-sd_bus_setting_add_int_infinity.json)
[ftest 1005](../../tests/ftests/1005-sudo-effect-sd_bus_setting_sub_infinity.json)
[ftest 1006](../../tests/ftests/1006-sudo-effect-sd_bus_setting_set_int_scope.json)
[ftest 1007](../../tests/ftests/1007-sudo-effect-sd_bus_setting_set_str.json) | | | [setting](../../src/effects/cgroup_setting.c) | Write to a setting file |
  • "setting" (string) - full path to the setting
  • "value" (string, long long, or double) - value to write to the setting file. If the operator is set to add or subtract, this value will be added/subtracted from the current value of setting
  • "operator" (string) - add, subtract, or set
  • "limit" (string, long long, or double - optional) - if provided, this effect will use the value as an upper or lower limit when the operator is set to add or subtract, respectfully
  • "validate" (boolean - optional) - if true, the setting effect will read from the setting file to ensure the value was properly set
| [ftest 055](../../tests/ftests/055-effect-setting_set_int.json)
[ftest 056](../../tests/ftests/056-effect-setting_add_int.json)
[ftest 057](../../tests/ftests/057-effect-setting_sub_int.json) | Shares a code base with the cgroup effect code |