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 | | [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 a8fe1db..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. 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)
[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 |
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/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/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/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);
+}
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);
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;
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);
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/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 075a259..8516ba5 100644
--- a/adaptived/tests/ftests/Makefile.am
+++ b/adaptived/tests/ftests/Makefile.am
@@ -110,6 +110,8 @@ 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
+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
@@ -187,6 +189,8 @@ check_PROGRAMS = \
test068 \
test069 \
test070 \
+ test071 \
+ test072 \
sudo1000 \
sudo1001 \
sudo1002 \
@@ -282,7 +286,11 @@ 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 \
+ 072-cause-cgroup_data2.json.token \
+ 072-cause-cgroup_data2.expected.token
EXTRA_DIST_H_FILES = \
ftests.h
diff --git a/adaptived/tests/ftests/ftests.c b/adaptived/tests/ftests/ftests.c
index c9ee428..77efd90 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' };
@@ -668,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 387518f..22fa99a 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);
@@ -84,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 */