Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions adaptived/doc/internal/list-of-built-in-causes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | <ul><li>"cgroup" (string) - full path to the cgroup directory</li><li>"settings" (array)<ul><li>"setting" (string) - Cgroup setting to read and save in shared_data</li></ul></li><li>"max_depth" (int - optional) - maximum depth to traverse in the cgroup hierarchy. Default - the first-level children of `cgroup_path`</li><li>"rel_paths" (boolean - optional) - If true, the cgroup name stored in the shared data will be a relative path. Default - true</li></ul> | [ftest 071](../../tests/ftests/071-cause-cgroup_data.json)<br />[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) | <ul><li>"setting" (string) - full path to the cgroup setting</li><li>"threshold" (long long or float)</li><li>"operator" (string) - currently greaterthan, lessthan, or equal</li></ul> | [ftest 029](../../tests/ftests/029-cause-cgroup_setting_ll_gt.json)<br />[ftest 030](../../tests/ftests/030-cause-cgroup_setting_ll_lt.json)<br />[ftest 031](../../tests/ftests/031-cause-cgroup_setting_float_gt.json)<br />[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 | <ul><li>"days" (array)<ul><li>"day" (string)</li></ul></li></ul> | [Jimmy Buffett Example](../examples/jimmy-buffett-config.json)<br />[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 | <ul><li>"meminfo_file" (string - optional) - path to the meminfo file. Useful for testing.</li><li>"field" (string) - field in the meminfo file to operate on, e.g. AnonPages</li><li>"threshold" (long long) - threshold in bytes</li><li>"operator" (string) - currently greaterthan, lessthan, or equal</li></ul> | [ftest 048](../../tests/ftests/048-cause-meminfo_gt.json)<br />[ftest 049](../../tests/ftests/049-cause-meminfo_lt.json)<br />[ftest 050](../../tests/ftests/050-cause-meminfo_eq.json) | |
Expand Down
2 changes: 1 addition & 1 deletion adaptived/doc/internal/list-of-built-in-effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | <ul><li>"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.</li><li>"type" (string) - which PSI type to evaluate, "cpu", "memory", or "io"</li><li>"measurement" (string) - which measurement to compare, e.g. some-avg10, full-avg60, etc. some-total and full-total are not supported</li><li>"signal" (int - optional) - signal to send to the processes being killed. Default - SIGKILL</li><li>"max_depth" (int - optional) - maximum depth to traverse in the cgroup hierarchy. Default - unlimited</li></ul> | [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) | <ul><li>"proc_names" (array)<ul><li>"name" (string) - process name (as found in /proc/{pid}/stat)</li></ul></li><li>"signal" (int - optional) - signal to send to the processes being killed. Currently only supports integers. Default - 9 (i.e. SIGKILL)</li><li>"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</li><li>"field" (string - optional) - field in /proc/pid/stat to sort on. Currently supports "vsize" or "rss". Default - "rss".</li></ul> | [ftest 067](../../tests/ftests/067-effect-kill_processes.json)<br />[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" | <ul><li>"logfile" (string) - Output file to store the log data</li><li>"max_file_size" (int - optional) - Maximum amount of data that will be copied from each source file. Defaults to 32kB if not specified</li><li>"files" (array)<ul><li>"file" (string) - file to copy</li></ul></li><li>"separator_prefix" (string - optional) - If specified, this string will be written each time this effect triggers</li><li>"date_format" (string - optional) - If specified, the date will be written in the specified format each time the effect triggers</li><li>"utc" (boolean - optional) - If specified, the date will be recorded in UTC time. Otherwise, the machine's localtime() will be used</li><li>"separator_postfix" (string - optional) - If specified, this string will be written each time this effect triggers</li><li>"file_separator" (string - optional) -If specified, this string will be written between each file being logged</li></ul> | [ftest 043](../../tests/ftests/043-effect-logger-no-separators.json)<br />[ftest 044](../../tests/ftests/044-effect-logger-date-format.json) | |
| [print](../../src/effects/print.c) | Print a message to a file | <ul><li>"message" (string - optional) - message to output</li><li>"file" (string) - file to write to. Currently only supports "stdout" or "stderr"</li></ul> | [Jimmy Buffett Example](../examples/jimmy-buffett-config.json) | |
| [print](../../src/effects/print.c) | Print a message to a file | <ul><li>"message" (string - optional) - message to output</li><li>"file" (string) - file to write to. Supports "stderr", "stdout", or any arbitrary path and filename</li><li>"shared_data" (boolean - optional) - If specified, this effect will print the data that has been shared by the causes in this rule. Default - false</li></ul> | [Jimmy Buffett Example](../examples/jimmy-buffett-config.json)<br />[ftest 071](../../tests/ftests/071-cause-cgroup_data.json)<br />[ftest 072](../../tests/ftests/072-cause-cgroup_data2.json.token) | |
| [print_schedstat](../../src/effects/print_schedstat.c) | Print schedstat to a file | <ul><li>"file" (string) - file to write to. Currently only supports "stdout" or "stderr"</li></ul> | [ftest 054](../../tests/ftests/054-effect-print_schedstat.json) | |
| [sd_bus_setting](../../src/effects/sd_bus_setting.c) | Operate on sd_bus properties | <ul><li>"target" (string) - cgroup slice name or scope name</li><li>"setting" (string) - sd_bus property name (e.g. MemoryMax)</li><li>"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</li><li>"operator" (string) - add, subtract, or set</li><li>"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</li><li>"validate" (boolean - optional) - if true, the setting effect will read from the property to ensure the value was properly set</li><li>"runtime" (boolean - optional) - if true, make changes only temporarily, so that they are lost on the next reboot.</ul> | [ftest 1000](../../tests/ftests/1000-sudo-effect-sd_bus_setting_set_int.json)<br />[ftest 1001](../../tests/ftests/1001-sudo-effect-sd_bus_setting_add_int.json)<br />[ftest 1002](../../tests/ftests/1002-sudo-effect-sd_bus_setting_sub_int.json)<br />[ftest 1003](../../tests/ftests/1003-sudo-effect-sd_bus_setting-CPUQuota.json)<br />[ftest 1004](../../tests/ftests/1004-sudo-effect-sd_bus_setting_add_int_infinity.json)<br />[ftest 1005](../../tests/ftests/1005-sudo-effect-sd_bus_setting_sub_infinity.json)<br />[ftest 1006](../../tests/ftests/1006-sudo-effect-sd_bus_setting_set_int_scope.json)<br />[ftest 1007](../../tests/ftests/1007-sudo-effect-sd_bus_setting_set_str.json) | |
| [setting](../../src/effects/cgroup_setting.c) | Write to a setting file | <ul><li>"setting" (string) - full path to the setting</li><li>"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</li><li>"operator" (string) - add, subtract, or set</li><li>"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</li><li>"validate" (boolean - optional) - if true, the setting effect will read from the setting file to ensure the value was properly set</li></ul> | [ftest 055](../../tests/ftests/055-effect-setting_set_int.json)<br />[ftest 056](../../tests/ftests/056-effect-setting_add_int.json)<br />[ftest 057](../../tests/ftests/057-effect-setting_sub_int.json) | Shares a code base with the cgroup effect code |
Expand Down
11 changes: 9 additions & 2 deletions adaptived/include/adaptived.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions adaptived/src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
10 changes: 10 additions & 0 deletions adaptived/src/adaptived-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
2 changes: 2 additions & 0 deletions adaptived/src/cause.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand Down
5 changes: 5 additions & 0 deletions adaptived/src/cause.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ enum cause_enum {
MEMORYSTAT,
TOP,
CGROUP_MEMORY_SETTING,
CGROUP_DATA,

CAUSE_CNT
};
Expand Down Expand Up @@ -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 */
299 changes: 299 additions & 0 deletions adaptived/src/causes/cgroup_data.c
Original file line number Diff line number Diff line change
@@ -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 <stdbool.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

#include <adaptived-utils.h>
#include <adaptived.h>

#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);
}
Loading
Loading