/* In comparison mode each stat can specify up to four different values: * - A side value; * - B side value; * - absolute diff value; * - relative (percentage) diff value. * * When specifying stat specs in comparison mode, user can use one of the * following variant suffixes to specify which exact variant should be used for * ordering or filtering: * - `_a` for A side value; * - `_b` for B side value; * - `_diff` for absolute diff value; * - `_pct` for relative (percentage) diff value. * * If no variant suffix is provided, then `_b` (control data) is assumed. * * As an example, let's say instructions stat has the following output: * * Insns (A) Insns (B) Insns (DIFF) * --------- --------- -------------- * 21547 20920 -627 (-2.91%) * * Then: * - 21547 is A side value (insns_a); * - 20920 is B side value (insns_b); * - -627 is absolute diff value (insns_diff); * - -2.91% is relative diff value (insns_pct). * * For verdict there is no verdict_pct variant. * For file and program name, _a and _b variants are equivalent and there are * no _diff or _pct variants.
*/ enum stat_variant {
VARIANT_A,
VARIANT_B,
VARIANT_DIFF,
VARIANT_PCT,
};
struct filter *allow_filters; struct filter *deny_filters; int allow_filter_cnt; int deny_filter_cnt;
int files_processed; int files_skipped; int progs_processed; int progs_skipped; int top_src_lines; struct var_preset *presets; int npresets; char orig_cgroup[PATH_MAX]; char stat_cgroup[PATH_MAX]; int memory_peak_fd;
} env;
staticint libbpf_print_fn(enum libbpf_print_level level, constchar *format, va_list args)
{ if (!env.verbose) return 0; if (level == LIBBPF_DEBUG && !env.debug) return 0; return vfprintf(stderr, format, args);
}
staticbool should_process_file_prog(constchar *filename, constchar *prog_name)
{ struct filter *f; int i, allow_cnt = 0;
for (i = 0; i < env.deny_filter_cnt; i++) {
f = &env.deny_filters[i]; if (f->kind != FILTER_NAME) continue;
if (f->any_glob && glob_matches(filename, f->any_glob)) returnfalse; if (f->any_glob && prog_name && glob_matches(prog_name, f->any_glob)) returnfalse; if (f->file_glob && glob_matches(filename, f->file_glob)) returnfalse; if (f->prog_glob && prog_name && glob_matches(prog_name, f->prog_glob)) returnfalse;
}
for (i = 0; i < env.allow_filter_cnt; i++) {
f = &env.allow_filters[i]; if (f->kind != FILTER_NAME) continue;
allow_cnt++; if (f->any_glob) { if (glob_matches(filename, f->any_glob)) returntrue; /* If we don't know program name yet, any_glob filter * has to assume that current BPF object file might be * relevant; we'll check again later on after opening * BPF object file, at which point program name will * be known finally.
*/ if (!prog_name || glob_matches(prog_name, f->any_glob)) returntrue;
} else { if (f->file_glob && !glob_matches(filename, f->file_glob)) continue; if (f->prog_glob && prog_name && !glob_matches(prog_name, f->prog_glob)) continue; returntrue;
}
}
/* if there are no file/prog name allow filters, allow all progs, * unless they are denied earlier explicitly
*/ return allow_cnt == 0;
}
staticstruct { enum operator_kind op_kind; constchar *op_str;
} operators[] = { /* Order of these definitions matter to avoid situations like '<' * matching part of what is actually a '<>' operator. That is, * substrings should go last.
*/
{ OP_EQ, "==" },
{ OP_NEQ, "!=" },
{ OP_NEQ, "<>" },
{ OP_LE, "<=" },
{ OP_LT, "<" },
{ OP_GE, ">=" },
{ OP_GT, ">" },
{ OP_EQ, "=" },
};
/* First, let's check if it's a stats filter of the following form: * <stat><op><value, where: * - <stat> is one of supported numerical stats (verdict is also * considered numerical, failure == 0, success == 1); * - <op> is comparison operator (see `operators` definitions); * - <value> is an integer (or failure/success, or false/true as * special aliases for 0 and 1, respectively). * If the form doesn't match what user provided, we assume file/prog * glob filter.
*/ for (i = 0; i < ARRAY_SIZE(operators); i++) { enum stat_variant var; int id; long val; constchar *end = str; constchar *op_str; bool is_abs;
op_str = operators[i].op_str;
p = strstr(str, op_str); if (!p) continue;
if (!parse_stat_id_var(str, p - str, &id, &var, &is_abs)) {
fprintf(stderr, "Unrecognized stat name in '%s'!\n", str); return -EINVAL;
} if (id >= FILE_NAME) {
fprintf(stderr, "Non-integer stat is specified in '%s'!\n", str); return -EINVAL;
}
/* File/prog filter can be specified either as '<glob>' or * '<file-glob>/<prog-glob>'. In the former case <glob> is applied to * both file and program names. This seems to be way more useful in * practice. If user needs full control, they can use '/<prog-glob>' * form to glob just program name, or '<file-glob>/' to glob only file * name. But usually common <glob> seems to be the most useful and * ergonomic way.
*/
f->kind = FILTER_NAME;
p = strchr(str, '/'); if (!p) {
f->any_glob = strdup(str); if (!f->any_glob) return -ENOMEM;
} else { if (str != p) { /* non-empty file glob */
f->file_glob = strndup(str, p - str); if (!f->file_glob) return -ENOMEM;
} if (strlen(p + 1) > 0) { /* non-empty prog glob */
f->prog_glob = strdup(p + 1); if (!f->prog_glob) {
free(f->file_glob);
f->file_glob = NULL; return -ENOMEM;
}
}
}
/* |<stat>| means we take absolute value of given stat */
*is_abs = false; if (len > 2 && name[0] == '|' && name[len - 1] == '|') {
*is_abs = true;
name += 1;
len -= 2;
}
for (i = 0; i < ARRAY_SIZE(stat_defs); i++) { struct stat_def *def = &stat_defs[i];
size_t alias_len, sfx_len; constchar *alias;
for (j = 0; j < ARRAY_SIZE(stat_defs[i].names); j++) {
alias = def->names[j]; if (!alias) continue;
alias_len = strlen(alias); if (strncmp(name, alias, alias_len) != 0) continue;
if (alias_len == len) { /* If no variant suffix is specified, we * assume control group (just in case we are * in comparison mode. Variant is ignored in * non-comparison mode.
*/
*var = VARIANT_B;
*id = i; returntrue;
}
for (k = 0; k < ARRAY_SIZE(var_sfxs); k++) {
sfx_len = strlen(var_sfxs[k]); if (alias_len + sfx_len != len) continue;
for (pos = strlen(buf) - 1, lines = 0; pos >= 0 && lines < MAX_PARSED_LOG_LINES; lines++) { /* find previous endline or otherwise take the start of log buf */ for (cur = &buf[pos]; cur > buf && cur[0] != '\n'; cur--, pos--) {
} /* next time start from end of previous line (or pos goes to <0) */
pos--; /* if we found endline, point right after endline symbol; * otherwise, stay at the beginning of log buf
*/ if (cur[0] == '\n')
cur++;
staticint guess_prog_type_by_ctx_name(constchar *ctx_name, enum bpf_prog_type *prog_type, enum bpf_attach_type *attach_type)
{ /* We need to guess program type based on its declared context type. * This guess can't be perfect as many different program types might * share the same context type. So we can only hope to reasonably * well guess this and get lucky. * * Just in case, we support both UAPI-side type names and * kernel-internal names.
*/ staticstruct { constchar *uapi_name; constchar *kern_name; enum bpf_prog_type prog_type; enum bpf_attach_type attach_type;
} ctx_map[] = { /* __sk_buff is most ambiguous, we assume TC program */
{ "__sk_buff", "sk_buff", BPF_PROG_TYPE_SCHED_CLS },
{ "bpf_sock", "sock", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND },
{ "bpf_sock_addr", "bpf_sock_addr_kern", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND },
{ "bpf_sock_ops", "bpf_sock_ops_kern", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS },
{ "sk_msg_md", "sk_msg", BPF_PROG_TYPE_SK_MSG, BPF_SK_MSG_VERDICT },
{ "bpf_cgroup_dev_ctx", "bpf_cgroup_dev_ctx", BPF_PROG_TYPE_CGROUP_DEVICE, BPF_CGROUP_DEVICE },
{ "bpf_sysctl", "bpf_sysctl_kern", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL },
{ "bpf_sockopt", "bpf_sockopt_kern", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT },
{ "sk_reuseport_md", "sk_reuseport_kern", BPF_PROG_TYPE_SK_REUSEPORT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE },
{ "bpf_sk_lookup", "bpf_sk_lookup_kern", BPF_PROG_TYPE_SK_LOOKUP, BPF_SK_LOOKUP },
{ "xdp_md", "xdp_buff", BPF_PROG_TYPE_XDP, BPF_XDP }, /* tracing types with no expected attach type */
{ "bpf_user_pt_regs_t", "pt_regs", BPF_PROG_TYPE_KPROBE },
{ "bpf_perf_event_data", "bpf_perf_event_data_kern", BPF_PROG_TYPE_PERF_EVENT }, /* raw_tp programs use u64[] from kernel side, we don't want * to match on that, probably; so NULL for kern-side type
*/
{ "bpf_raw_tracepoint_args", NULL, BPF_PROG_TYPE_RAW_TRACEPOINT },
}; int i;
if (!ctx_name) return -EINVAL;
for (i = 0; i < ARRAY_SIZE(ctx_map); i++) { if (strcmp(ctx_map[i].uapi_name, ctx_name) == 0 ||
(ctx_map[i].kern_name && strcmp(ctx_map[i].kern_name, ctx_name) == 0)) {
*prog_type = ctx_map[i].prog_type;
*attach_type = ctx_map[i].attach_type; return 0;
}
}
return -ESRCH;
}
/* Make sure only target program is referenced from struct_ops map, * otherwise libbpf would automatically set autocreate for all * referenced programs. * See libbpf.c:bpf_object_adjust_struct_ops_autoload.
*/ staticvoid mask_unrelated_struct_ops_progs(struct bpf_object *obj, struct bpf_map *map, struct bpf_program *prog)
{ struct btf *btf = bpf_object__btf(obj); conststruct btf_type *t, *mt; struct btf_member *m; int i, moff;
size_t data_sz, ptr_sz = sizeof(void *); void *data;
t = btf__type_by_id(btf, bpf_map__btf_value_type_id(map)); if (!btf_is_struct(t)) return;
data = bpf_map__initial_value(map, &data_sz); for (i = 0; i < btf_vlen(t); i++) {
m = &btf_members(t)[i];
mt = btf__type_by_id(btf, m->type); if (!btf_is_ptr(mt)) continue;
moff = m->offset / 8; if (moff + ptr_sz > data_sz) continue; if (memcmp(data + moff, &prog, ptr_sz) == 0) continue;
memset(data + moff, 0, ptr_sz);
}
}
/* fix up map size, if necessary */ switch (bpf_map__type(map)) { case BPF_MAP_TYPE_SK_STORAGE: case BPF_MAP_TYPE_TASK_STORAGE: case BPF_MAP_TYPE_INODE_STORAGE: case BPF_MAP_TYPE_CGROUP_STORAGE: case BPF_MAP_TYPE_CGRP_STORAGE: break; case BPF_MAP_TYPE_STRUCT_OPS:
mask_unrelated_struct_ops_progs(obj, map, prog); break; default: if (bpf_map__max_entries(map) == 0)
bpf_map__set_max_entries(map, 1);
}
}
/* SEC(freplace) programs can't be loaded with veristat as is, * but we can try guessing their target program's expected type by * looking at the type of program's first argument and substituting * corresponding program type
*/ if (bpf_program__type(prog) == BPF_PROG_TYPE_EXT) { conststruct btf *btf = bpf_object__btf(obj); constchar *prog_name = bpf_program__name(prog); enum bpf_prog_type prog_type; enum bpf_attach_type attach_type; conststruct btf_type *t; constchar *ctx_name; int id;
if (!btf) goto skip_freplace_fixup;
id = btf__find_by_name_kind(btf, prog_name, BTF_KIND_FUNC);
t = btf__type_by_id(btf, id);
t = btf__type_by_id(btf, t->type); if (!btf_is_func_proto(t) || btf_vlen(t) != 1) goto skip_freplace_fixup;
/* context argument is a pointer to a struct/typedef */
t = btf__type_by_id(btf, btf_params(t)[0].type); while (t && btf_is_mod(t))
t = btf__type_by_id(btf, t->type); if (!t || !btf_is_ptr(t)) goto skip_freplace_fixup;
t = btf__type_by_id(btf, t->type); while (t && btf_is_mod(t))
t = btf__type_by_id(btf, t->type); if (!t) goto skip_freplace_fixup;
if (!env.quiet) {
fprintf(stderr, "Using guessed program type '%s' for %s/%s...\n",
libbpf_bpf_prog_type_str(prog_type),
filename, prog_name);
}
} else { if (!env.quiet) {
fprintf(stderr, "Failed to guess program type for freplace program with context type name '%s' for %s/%s. Consider using canonical type names to help veristat...\n",
ctx_name, filename, prog_name);
}
}
}
skip_freplace_fixup: return;
}
/* * Creates a cgroup at /sys/fs/cgroup/veristat-accounting-<pid>, * moves current process to this cgroup.
*/ staticvoid create_stat_cgroup(void)
{ char cgroup_fs_mount[4096]; char buf[4096]; int err;
if (val[0] == '-' || isdigit(val[0])) { /* must be a number */
errno = 0;
value = strtoll(val, &val_end, 0); if (errno == ERANGE) {
errno = 0;
value = strtoull(val, &val_end, 0);
} if (errno || *val_end != '\0') {
fprintf(stderr, "Failed to parse value '%s'\n", val); return -EINVAL;
}
rvalue->ivalue = value;
rvalue->type = INTEGRAL;
} else { /* if not a number, consider it enum value */
rvalue->svalue = strdup(val); if (!rvalue->svalue) return -ENOMEM;
rvalue->type = ENUMERATOR;
} return 0;
}
if (sscanf(expr, " %[][a-zA-Z0-9_. ] = %s %n", var, val, &n) != 2 || n != strlen(expr)) {
fprintf(stderr, "Failed to parse expression '%s'\n", expr); return -EINVAL;
} /* Remove trailing spaces from var, as scanf may add those */
rtrim(var);
err = parse_rvalue(val, &cur->value); if (err) return err;
cur->full_name = strdup(var); if (!cur->full_name) return -ENOMEM;
err = parse_var_atoms(var, cur); if (err) return err;
f = fopen(filename, "rt"); if (!f) {
err = -errno;
fprintf(stderr, "Failed to open presets in '%s': %s\n", filename, strerror(-err)); return -EINVAL;
}
while (fscanf(f, " %1023[^\n]\n", buf) == 1) { if (buf[0] == '\0' || buf[0] == '#') continue;
err = append_var_preset(&env.presets, &env.npresets, buf); if (err) goto cleanup;
}
cleanup:
fclose(f); return err;
}
staticbool is_signed_type(conststruct btf_type *t)
{ if (btf_is_int(t)) return btf_int_encoding(t) & BTF_INT_SIGNED; if (btf_is_any_enum(t)) return btf_kflag(t); returntrue;
}
staticint enum_value_from_name(conststruct btf *btf, conststruct btf_type *t, constchar *evalue, longlong *retval)
{ if (btf_is_enum(t)) { struct btf_enum *e = btf_enum(t); int i, n = btf_vlen(t);
for (i = 0; i < n; ++i, ++e) { constchar *cur_name = btf__name_by_offset(btf, e->name_off);
if (strcmp(cur_name, evalue) == 0) {
*retval = e->val; return 0;
}
}
} elseif (btf_is_enum64(t)) { struct btf_enum64 *e = btf_enum64(t); int i, n = btf_vlen(t);
for (i = 0; i < n; ++i, ++e) { constchar *cur_name = btf__name_by_offset(btf, e->name_off);
__u64 value = btf_enum64_value(e);
base_type = btf__type_by_id(btf, btf__resolve_type(btf, sinfo->type)); if (!base_type) {
fprintf(stderr, "Failed to resolve type %d\n", sinfo->type); return -EINVAL;
} if (!is_preset_supported(base_type)) {
fprintf(stderr, "Can't set %s. Only ints and enums are supported\n",
preset->full_name); return -EINVAL;
}
if (preset->value.type == ENUMERATOR) { if (btf_is_any_enum(base_type)) { if (enum_value_from_name(btf, base_type, preset->value.svalue, &value)) {
fprintf(stderr, "Failed to find integer value for enum element %s\n",
preset->value.svalue); return -EINVAL;
}
} else {
fprintf(stderr, "Value %s is not supported for type %s\n",
preset->value.svalue,
btf__name_by_offset(btf, base_type->name_off)); return -EINVAL;
}
}
/* Check if value fits into the target variable size */ if (sinfo->size < sizeof(value)) { bool is_signed = is_signed_type(base_type);
__u32 unsigned_bits = sinfo->size * 8 - (is_signed ? 1 : 0); longlong max_val = 1ll << unsigned_bits;
if (value >= max_val || value < -max_val) {
fprintf(stderr, "Variable %s value %lld is out of range [%lld; %lld]\n",
btf__name_by_offset(btf, base_type->name_off), value,
is_signed ? -max_val : 0, max_val - 1); return -EINVAL;
}
}
for (k = 0; k < npresets; ++k) { struct btf_var_secinfo tmp_sinfo;
if (strcmp(var_name, presets[k].atoms[0].name) != 0) continue;
if (presets[k].applied) {
fprintf(stderr, "Variable %s is set more than once",
var_name); return -EINVAL;
}
tmp_sinfo = *sinfo;
err = adjust_var_secinfo(btf, var_type,
&tmp_sinfo, presets + k); if (err) return err;
if (!should_process_file_prog(base_filename, NULL)) { if (env.verbose)
printf("Skipping '%s' due to filters...\n", filename);
env.files_skipped++; return 0;
} if (!is_bpf_obj_file(filename)) { if (env.verbose)
printf("Skipping '%s' as it's not a BPF object file...\n", filename);
env.files_skipped++; return 0;
}
if (!env.quiet && env.out_fmt == RESFMT_TABLE)
printf("Processing '%s'...\n", base_filename);
old_libbpf_print_fn = libbpf_set_print(libbpf_print_fn);
obj = bpf_object__open_file(filename, &opts); if (!obj) { /* if libbpf can't open BPF object file, it could be because * that BPF object file is incomplete and has to be statically * linked into a final BPF object file; instead of bailing * out, report it into stderr, mark it as skipped, and * proceed
*/
fprintf(stderr, "Failed to open '%s': %d\n", filename, -errno);
env.files_skipped++;
err = 0; goto cleanup;
}
switch (id) { case FILE_NAME:
cmp = strcmp(s1->file_name, s2->file_name); break; case PROG_NAME:
cmp = strcmp(s1->prog_name, s2->prog_name); break; case ATTACH_TYPE: case PROG_TYPE: case SIZE: case JITED_SIZE: case STACK: case VERDICT: case DURATION: case TOTAL_INSNS: case TOTAL_STATES: case PEAK_STATES: case MAX_STATES_PER_INSN: case MEMORY_PEAK: case MARK_READ_MAX_LEN: { long v1 = s1->stats[id]; long v2 = s2->stats[id];
for (i = 0; i < env.output_spec.spec_cnt; i++) { int id = env.output_spec.ids[i]; int *max_len = &env.output_spec.lens[i], len; constchar *str = NULL; long val = 0;
prepare_value(s, id, &str, &val);
switch (fmt) { case RESFMT_TABLE_CALCLEN: if (str)
len = snprintf(NULL, 0, "%s", str); else
len = snprintf(NULL, 0, "%ld", val); if (len > *max_len)
*max_len = len; break; case RESFMT_TABLE: if (str)
printf("%s%-*s", i == 0 ? "" : COLUMN_SEP, *max_len, str); else
printf("%s%*ld", i == 0 ? "" : COLUMN_SEP, *max_len, val); if (i == env.output_spec.spec_cnt - 1)
printf("\n"); break; case RESFMT_CSV: if (str)
printf("%s%s", i == 0 ? "" : ",", str); else
printf("%s%ld", i == 0 ? "" : ",", val); if (i == env.output_spec.spec_cnt - 1)
printf("\n"); break;
}
}
st = &(*statsp)[*stat_cntp];
memset(st, 0, sizeof(*st));
*stat_cntp += 1;
}
while ((next = strtok_r(cnt++ ? NULL : input, ",\n", &state))) { if (header) { /* for the first line, set up spec stats */
err = parse_stat(next, specs); if (err) goto cleanup; continue;
}
/* for all other lines, parse values based on spec */ if (col >= specs->spec_cnt) {
fprintf(stderr, "Found extraneous column #%d in row #%d of '%s'\n",
col, *stat_cntp, filename);
err = -EINVAL; goto cleanup;
}
err = parse_stat_value(next, specs->ids[col], st); if (err) goto cleanup;
col++;
}
if (header) {
header = false; continue;
}
if (col < specs->spec_cnt) {
fprintf(stderr, "Not enough columns in row #%d in '%s'\n",
*stat_cntp, filename);
err = -EINVAL; goto cleanup;
}
if (!st->file_name || !st->prog_name) {
fprintf(stderr, "Row #%d in '%s' is missing file and/or program name\n",
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.