Skip to content

Commit 67cdcc4

Browse files
eddyz87anakryiko
authored andcommitted
veristat: Memory accounting for bpf programs
This commit adds a new field mem_peak / "Peak memory (MiB)" field to a set of gathered statistics. The field is intended as an estimate for peak verifier memory consumption for processing of a given program. Mechanically stat is collected as follows: - At the beginning of handle_verif_mode() a new cgroup is created and veristat process is moved into this cgroup. - At each program load: - bpf_object__load() is split into bpf_object__prepare() and bpf_object__load() to avoid accounting for memory allocated for maps; - before bpf_object__load(): - a write to "memory.peak" file of the new cgroup is used to reset cgroup statistics; - updated value is read from "memory.peak" file and stashed; - after bpf_object__load() "memory.peak" is read again and difference between new and stashed values is used as a metric. If any of the above steps fails veristat proceeds w/o collecting mem_peak information for a program, reporting mem_peak as -1. While memcg provides data in bytes (converted from pages), veristat converts it to megabytes to avoid jitter when comparing results of different executions. The change has no measurable impact on veristat running time. A correlation between "Peak states" and "Peak memory" fields provides a sanity check for gathered statistics, e.g. a sample of data for sched_ext programs: Program Peak states Peak memory (MiB) ------------------------ ----------- ----------------- lavd_select_cpu 2153 44 lavd_enqueue 1982 41 lavd_dispatch 3480 28 layered_dispatch 1417 17 layered_enqueue 760 11 lavd_cpu_offline 349 6 lavd_cpu_online 349 6 lavd_init 394 6 rusty_init 350 5 layered_select_cpu 391 4 ... rusty_stopping 134 1 arena_topology_node_init 170 0 Signed-off-by: Eduard Zingerman <[email protected]> Signed-off-by: Andrii Nakryiko <[email protected]> Link: https://lore.kernel.org/bpf/[email protected]
1 parent 43736ec commit 67cdcc4

File tree

2 files changed

+246
-7
lines changed

2 files changed

+246
-7
lines changed

tools/testing/selftests/bpf/Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,11 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \
841841
$(call msg,BINARY,,$@)
842842
$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@
843843

844+
# This works around GCC warning about snprintf truncating strings like:
845+
#
846+
# char a[PATH_MAX], b[PATH_MAX];
847+
# snprintf(a, "%s/foo", b); // triggers -Wformat-truncation
848+
$(OUTPUT)/veristat.o: CFLAGS += -Wno-format-truncation
844849
$(OUTPUT)/veristat.o: $(BPFOBJ)
845850
$(OUTPUT)/veristat: $(OUTPUT)/veristat.o
846851
$(call msg,BINARY,,$@)

tools/testing/selftests/bpf/veristat.c

Lines changed: 241 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ enum stat_id {
4949
STACK,
5050
PROG_TYPE,
5151
ATTACH_TYPE,
52+
MEMORY_PEAK,
5253

5354
FILE_NAME,
5455
PROG_NAME,
@@ -208,6 +209,9 @@ static struct env {
208209
int top_src_lines;
209210
struct var_preset *presets;
210211
int npresets;
212+
char orig_cgroup[PATH_MAX];
213+
char stat_cgroup[PATH_MAX];
214+
int memory_peak_fd;
211215
} env;
212216

213217
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
@@ -219,6 +223,22 @@ static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va
219223
return vfprintf(stderr, format, args);
220224
}
221225

226+
#define log_errno(fmt, ...) log_errno_aux(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
227+
228+
__printf(3, 4)
229+
static int log_errno_aux(const char *file, int line, const char *fmt, ...)
230+
{
231+
int err = -errno;
232+
va_list ap;
233+
234+
va_start(ap, fmt);
235+
fprintf(stderr, "%s:%d: ", file, line);
236+
vfprintf(stderr, fmt, ap);
237+
fprintf(stderr, " failed with error '%s'.\n", strerror(errno));
238+
va_end(ap);
239+
return err;
240+
}
241+
222242
#ifndef VERISTAT_VERSION
223243
#define VERISTAT_VERSION "<kernel>"
224244
#endif
@@ -734,13 +754,13 @@ static int append_file_from_file(const char *path)
734754
}
735755

736756
static const struct stat_specs default_csv_output_spec = {
737-
.spec_cnt = 14,
757+
.spec_cnt = 15,
738758
.ids = {
739759
FILE_NAME, PROG_NAME, VERDICT, DURATION,
740760
TOTAL_INSNS, TOTAL_STATES, PEAK_STATES,
741761
MAX_STATES_PER_INSN, MARK_READ_MAX_LEN,
742762
SIZE, JITED_SIZE, PROG_TYPE, ATTACH_TYPE,
743-
STACK,
763+
STACK, MEMORY_PEAK,
744764
},
745765
};
746766

@@ -781,6 +801,7 @@ static struct stat_def {
781801
[STACK] = {"Stack depth", {"stack_depth", "stack"}, },
782802
[PROG_TYPE] = { "Program type", {"prog_type"}, },
783803
[ATTACH_TYPE] = { "Attach type", {"attach_type", }, },
804+
[MEMORY_PEAK] = { "Peak memory (MiB)", {"mem_peak", }, },
784805
};
785806

786807
static bool parse_stat_id_var(const char *name, size_t len, int *id,
@@ -1279,16 +1300,214 @@ static int max_verifier_log_size(void)
12791300
return log_size;
12801301
}
12811302

1303+
static bool output_stat_enabled(int id)
1304+
{
1305+
int i;
1306+
1307+
for (i = 0; i < env.output_spec.spec_cnt; i++)
1308+
if (env.output_spec.ids[i] == id)
1309+
return true;
1310+
return false;
1311+
}
1312+
1313+
__printf(2, 3)
1314+
static int write_one_line(const char *file, const char *fmt, ...)
1315+
{
1316+
int err, saved_errno;
1317+
va_list ap;
1318+
FILE *f;
1319+
1320+
f = fopen(file, "w");
1321+
if (!f)
1322+
return -1;
1323+
1324+
va_start(ap, fmt);
1325+
errno = 0;
1326+
err = vfprintf(f, fmt, ap);
1327+
saved_errno = errno;
1328+
va_end(ap);
1329+
fclose(f);
1330+
errno = saved_errno;
1331+
return err < 0 ? -1 : 0;
1332+
}
1333+
1334+
__scanf(3, 4)
1335+
static int scanf_one_line(const char *file, int fields_expected, const char *fmt, ...)
1336+
{
1337+
int res = 0, saved_errno = 0;
1338+
char *line = NULL;
1339+
size_t line_len;
1340+
va_list ap;
1341+
FILE *f;
1342+
1343+
f = fopen(file, "r");
1344+
if (!f)
1345+
return -1;
1346+
1347+
va_start(ap, fmt);
1348+
while (getline(&line, &line_len, f) > 0) {
1349+
res = vsscanf(line, fmt, ap);
1350+
if (res == fields_expected)
1351+
goto out;
1352+
}
1353+
if (ferror(f)) {
1354+
saved_errno = errno;
1355+
res = -1;
1356+
}
1357+
1358+
out:
1359+
va_end(ap);
1360+
free(line);
1361+
fclose(f);
1362+
errno = saved_errno;
1363+
return res;
1364+
}
1365+
1366+
static void destroy_stat_cgroup(void)
1367+
{
1368+
char buf[PATH_MAX];
1369+
int err;
1370+
1371+
close(env.memory_peak_fd);
1372+
1373+
if (env.orig_cgroup[0]) {
1374+
snprintf(buf, sizeof(buf), "%s/cgroup.procs", env.orig_cgroup);
1375+
err = write_one_line(buf, "%d\n", getpid());
1376+
if (err < 0)
1377+
log_errno("moving self to original cgroup %s\n", env.orig_cgroup);
1378+
}
1379+
1380+
if (env.stat_cgroup[0]) {
1381+
err = rmdir(env.stat_cgroup);
1382+
if (err < 0)
1383+
log_errno("deletion of cgroup %s", env.stat_cgroup);
1384+
}
1385+
1386+
env.memory_peak_fd = -1;
1387+
env.orig_cgroup[0] = 0;
1388+
env.stat_cgroup[0] = 0;
1389+
}
1390+
1391+
/*
1392+
* Creates a cgroup at /sys/fs/cgroup/veristat-accounting-<pid>,
1393+
* moves current process to this cgroup.
1394+
*/
1395+
static void create_stat_cgroup(void)
1396+
{
1397+
char cgroup_fs_mount[4096];
1398+
char buf[4096];
1399+
int err;
1400+
1401+
env.memory_peak_fd = -1;
1402+
1403+
if (!output_stat_enabled(MEMORY_PEAK))
1404+
return;
1405+
1406+
err = scanf_one_line("/proc/self/mounts", 2, "%*s %4095s cgroup2 %s",
1407+
cgroup_fs_mount, buf);
1408+
if (err != 2) {
1409+
if (err < 0)
1410+
log_errno("reading /proc/self/mounts");
1411+
else if (!env.quiet)
1412+
fprintf(stderr, "Can't find cgroupfs v2 mount point.\n");
1413+
goto err_out;
1414+
}
1415+
1416+
/* cgroup-v2.rst promises the line "0::<group>" for cgroups v2 */
1417+
err = scanf_one_line("/proc/self/cgroup", 1, "0::%4095s", buf);
1418+
if (err != 1) {
1419+
if (err < 0)
1420+
log_errno("reading /proc/self/cgroup");
1421+
else if (!env.quiet)
1422+
fprintf(stderr, "Can't infer veristat process cgroup.");
1423+
goto err_out;
1424+
}
1425+
1426+
snprintf(env.orig_cgroup, sizeof(env.orig_cgroup), "%s/%s", cgroup_fs_mount, buf);
1427+
1428+
snprintf(buf, sizeof(buf), "%s/veristat-accounting-%d", cgroup_fs_mount, getpid());
1429+
err = mkdir(buf, 0777);
1430+
if (err < 0) {
1431+
log_errno("creation of cgroup %s", buf);
1432+
goto err_out;
1433+
}
1434+
strcpy(env.stat_cgroup, buf);
1435+
1436+
snprintf(buf, sizeof(buf), "%s/cgroup.procs", env.stat_cgroup);
1437+
err = write_one_line(buf, "%d\n", getpid());
1438+
if (err < 0) {
1439+
log_errno("entering cgroup %s", buf);
1440+
goto err_out;
1441+
}
1442+
1443+
snprintf(buf, sizeof(buf), "%s/memory.peak", env.stat_cgroup);
1444+
env.memory_peak_fd = open(buf, O_RDWR | O_APPEND);
1445+
if (env.memory_peak_fd < 0) {
1446+
log_errno("opening %s", buf);
1447+
goto err_out;
1448+
}
1449+
1450+
return;
1451+
1452+
err_out:
1453+
if (!env.quiet)
1454+
fprintf(stderr, "Memory usage metric unavailable.\n");
1455+
destroy_stat_cgroup();
1456+
}
1457+
1458+
/* Current value of /sys/fs/cgroup/veristat-accounting-<pid>/memory.peak */
1459+
static long cgroup_memory_peak(void)
1460+
{
1461+
long err, memory_peak;
1462+
char buf[32];
1463+
1464+
if (env.memory_peak_fd < 0)
1465+
return -1;
1466+
1467+
err = pread(env.memory_peak_fd, buf, sizeof(buf) - 1, 0);
1468+
if (err <= 0) {
1469+
log_errno("pread(%s/memory.peak)", env.stat_cgroup);
1470+
return -1;
1471+
}
1472+
1473+
buf[err] = 0;
1474+
errno = 0;
1475+
memory_peak = strtoll(buf, NULL, 10);
1476+
if (errno) {
1477+
log_errno("%s/memory.peak:strtoll(%s)", env.stat_cgroup, buf);
1478+
return -1;
1479+
}
1480+
1481+
return memory_peak;
1482+
}
1483+
1484+
static int reset_stat_cgroup(void)
1485+
{
1486+
char buf[] = "r\n";
1487+
int err;
1488+
1489+
if (env.memory_peak_fd < 0)
1490+
return -1;
1491+
1492+
err = pwrite(env.memory_peak_fd, buf, sizeof(buf), 0);
1493+
if (err <= 0) {
1494+
log_errno("pwrite(%s/memory.peak)", env.stat_cgroup);
1495+
return -1;
1496+
}
1497+
return 0;
1498+
}
1499+
12821500
static int process_prog(const char *filename, struct bpf_object *obj, struct bpf_program *prog)
12831501
{
12841502
const char *base_filename = basename(strdupa(filename));
12851503
const char *prog_name = bpf_program__name(prog);
1504+
long mem_peak_a, mem_peak_b, mem_peak = -1;
12861505
char *buf;
12871506
int buf_sz, log_level;
12881507
struct verif_stats *stats;
12891508
struct bpf_prog_info info;
12901509
__u32 info_len = sizeof(info);
1291-
int err = 0;
1510+
int err = 0, cgroup_err;
12921511
void *tmp;
12931512
int fd;
12941513

@@ -1333,7 +1552,15 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
13331552
if (env.force_reg_invariants)
13341553
bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_REG_INVARIANTS);
13351554

1336-
err = bpf_object__load(obj);
1555+
err = bpf_object__prepare(obj);
1556+
if (!err) {
1557+
cgroup_err = reset_stat_cgroup();
1558+
mem_peak_a = cgroup_memory_peak();
1559+
err = bpf_object__load(obj);
1560+
mem_peak_b = cgroup_memory_peak();
1561+
if (!cgroup_err && mem_peak_a >= 0 && mem_peak_b >= 0)
1562+
mem_peak = mem_peak_b - mem_peak_a;
1563+
}
13371564
env.progs_processed++;
13381565

13391566
stats->file_name = strdup(base_filename);
@@ -1342,6 +1569,7 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf
13421569
stats->stats[SIZE] = bpf_program__insn_cnt(prog);
13431570
stats->stats[PROG_TYPE] = bpf_program__type(prog);
13441571
stats->stats[ATTACH_TYPE] = bpf_program__expected_attach_type(prog);
1572+
stats->stats[MEMORY_PEAK] = mem_peak < 0 ? -1 : mem_peak / (1024 * 1024);
13451573

13461574
memset(&info, 0, info_len);
13471575
fd = bpf_program__fd(prog);
@@ -1825,6 +2053,7 @@ static int cmp_stat(const struct verif_stats *s1, const struct verif_stats *s2,
18252053
case TOTAL_STATES:
18262054
case PEAK_STATES:
18272055
case MAX_STATES_PER_INSN:
2056+
case MEMORY_PEAK:
18282057
case MARK_READ_MAX_LEN: {
18292058
long v1 = s1->stats[id];
18302059
long v2 = s2->stats[id];
@@ -2054,6 +2283,7 @@ static void prepare_value(const struct verif_stats *s, enum stat_id id,
20542283
case STACK:
20552284
case SIZE:
20562285
case JITED_SIZE:
2286+
case MEMORY_PEAK:
20572287
*val = s ? s->stats[id] : 0;
20582288
break;
20592289
default:
@@ -2140,6 +2370,7 @@ static int parse_stat_value(const char *str, enum stat_id id, struct verif_stats
21402370
case MARK_READ_MAX_LEN:
21412371
case SIZE:
21422372
case JITED_SIZE:
2373+
case MEMORY_PEAK:
21432374
case STACK: {
21442375
long val;
21452376
int err, n;
@@ -2777,27 +3008,30 @@ static void output_prog_stats(void)
27773008

27783009
static int handle_verif_mode(void)
27793010
{
2780-
int i, err;
3011+
int i, err = 0;
27813012

27823013
if (env.filename_cnt == 0) {
27833014
fprintf(stderr, "Please provide path to BPF object file!\n\n");
27843015
argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat");
27853016
return -EINVAL;
27863017
}
27873018

3019+
create_stat_cgroup();
27883020
for (i = 0; i < env.filename_cnt; i++) {
27893021
err = process_obj(env.filenames[i]);
27903022
if (err) {
27913023
fprintf(stderr, "Failed to process '%s': %d\n", env.filenames[i], err);
2792-
return err;
3024+
goto out;
27933025
}
27943026
}
27953027

27963028
qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats);
27973029

27983030
output_prog_stats();
27993031

2800-
return 0;
3032+
out:
3033+
destroy_stat_cgroup();
3034+
return err;
28013035
}
28023036

28033037
static int handle_replay_mode(void)

0 commit comments

Comments
 (0)