An update for libbpf's hashmap interface from void* -> void* to a polymorphic one, allowing both long and void* keys and values. This simplifies many use cases in libbpf as hashmaps there are mostly integer to integer. Perf copies hashmap implementation from libbpf and has to be updated as well. Changes to libbpf, selftests/bpf and perf are packed as a single commit to avoid compilation issues with any future bisect. Polymorphic interface is acheived by hiding hashmap interface functions behind auxiliary macros that take care of necessary type casts, for example: #define hashmap_cast_ptr(p) \ ({ \ _Static_assert((p) == NULL || sizeof(*(p)) == sizeof(long),\ #p " pointee should be a long-sized integer or a pointer"); \ (long *)(p); \ }) bool hashmap_find(const struct hashmap *map, long key, long *value); #define hashmap__find(map, key, value) \ hashmap_find((map), (long)(key), hashmap_cast_ptr(value)) - hashmap__find macro casts key and value parameters to long and long* respectively - hashmap_cast_ptr ensures that value pointer points to a memory of appropriate size. This hack was suggested by Andrii Nakryiko in [1]. This is a follow up for [2]. [1] https://lore.kernel.org/bpf/CAEf4BzZ8KFneEJxFAaNCCFPGqp20hSpS2aCj76uRk3-qZUH5xg@mail.gmail.com/ [2] https://lore.kernel.org/bpf/af1facf9-7bc8-8a3d-0db4-7b3f333589a2@meta.com/T/#m65b28f1d6d969fcd318b556db6a3ad499a42607d Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Link: https://lore.kernel.org/bpf/20221109142611.879983-2-eddyz87@gmail.com
224 lines
7.2 KiB
C
224 lines
7.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include "util/cputopo.h"
|
|
#include "util/debug.h"
|
|
#include "util/expr.h"
|
|
#include "util/header.h"
|
|
#include "util/smt.h"
|
|
#include "tests.h"
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <linux/zalloc.h>
|
|
|
|
static int test_ids_union(void)
|
|
{
|
|
struct hashmap *ids1, *ids2;
|
|
|
|
/* Empty union. */
|
|
ids1 = ids__new();
|
|
TEST_ASSERT_VAL("ids__new", ids1);
|
|
ids2 = ids__new();
|
|
TEST_ASSERT_VAL("ids__new", ids2);
|
|
|
|
ids1 = ids__union(ids1, ids2);
|
|
TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 0);
|
|
|
|
/* Union {foo, bar} against {}. */
|
|
ids2 = ids__new();
|
|
TEST_ASSERT_VAL("ids__new", ids2);
|
|
|
|
TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids1, strdup("foo")), 0);
|
|
TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids1, strdup("bar")), 0);
|
|
|
|
ids1 = ids__union(ids1, ids2);
|
|
TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 2);
|
|
|
|
/* Union {foo, bar} against {foo}. */
|
|
ids2 = ids__new();
|
|
TEST_ASSERT_VAL("ids__new", ids2);
|
|
TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("foo")), 0);
|
|
|
|
ids1 = ids__union(ids1, ids2);
|
|
TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 2);
|
|
|
|
/* Union {foo, bar} against {bar,baz}. */
|
|
ids2 = ids__new();
|
|
TEST_ASSERT_VAL("ids__new", ids2);
|
|
TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("bar")), 0);
|
|
TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("baz")), 0);
|
|
|
|
ids1 = ids__union(ids1, ids2);
|
|
TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 3);
|
|
|
|
ids__free(ids1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test(struct expr_parse_ctx *ctx, const char *e, double val2)
|
|
{
|
|
double val;
|
|
|
|
if (expr__parse(&val, ctx, e))
|
|
TEST_ASSERT_VAL("parse test failed", 0);
|
|
TEST_ASSERT_VAL("unexpected value", val == val2);
|
|
return 0;
|
|
}
|
|
|
|
static int test__expr(struct test_suite *t __maybe_unused, int subtest __maybe_unused)
|
|
{
|
|
struct expr_id_data *val_ptr;
|
|
const char *p;
|
|
double val, num_cpus, num_cores, num_dies, num_packages;
|
|
int ret;
|
|
struct expr_parse_ctx *ctx;
|
|
bool is_intel = false;
|
|
char buf[128];
|
|
|
|
if (!get_cpuid(buf, sizeof(buf)))
|
|
is_intel = strstr(buf, "Intel") != NULL;
|
|
|
|
TEST_ASSERT_EQUAL("ids_union", test_ids_union(), 0);
|
|
|
|
ctx = expr__ctx_new();
|
|
TEST_ASSERT_VAL("expr__ctx_new", ctx);
|
|
expr__add_id_val(ctx, strdup("FOO"), 1);
|
|
expr__add_id_val(ctx, strdup("BAR"), 2);
|
|
|
|
ret = test(ctx, "1+1", 2);
|
|
ret |= test(ctx, "FOO+BAR", 3);
|
|
ret |= test(ctx, "(BAR/2)%2", 1);
|
|
ret |= test(ctx, "1 - -4", 5);
|
|
ret |= test(ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5);
|
|
ret |= test(ctx, "1-1 | 1", 1);
|
|
ret |= test(ctx, "1-1 & 1", 0);
|
|
ret |= test(ctx, "min(1,2) + 1", 2);
|
|
ret |= test(ctx, "max(1,2) + 1", 3);
|
|
ret |= test(ctx, "1+1 if 3*4 else 0", 2);
|
|
ret |= test(ctx, "100 if 1 else 200 if 1 else 300", 100);
|
|
ret |= test(ctx, "100 if 0 else 200 if 1 else 300", 200);
|
|
ret |= test(ctx, "100 if 1 else 200 if 0 else 300", 100);
|
|
ret |= test(ctx, "100 if 0 else 200 if 0 else 300", 300);
|
|
ret |= test(ctx, "1.1 + 2.1", 3.2);
|
|
ret |= test(ctx, ".1 + 2.", 2.1);
|
|
ret |= test(ctx, "d_ratio(1, 2)", 0.5);
|
|
ret |= test(ctx, "d_ratio(2.5, 0)", 0);
|
|
ret |= test(ctx, "1.1 < 2.2", 1);
|
|
ret |= test(ctx, "2.2 > 1.1", 1);
|
|
ret |= test(ctx, "1.1 < 1.1", 0);
|
|
ret |= test(ctx, "2.2 > 2.2", 0);
|
|
ret |= test(ctx, "2.2 < 1.1", 0);
|
|
ret |= test(ctx, "1.1 > 2.2", 0);
|
|
ret |= test(ctx, "1.1e10 < 1.1e100", 1);
|
|
ret |= test(ctx, "1.1e2 > 1.1e-2", 1);
|
|
|
|
if (ret) {
|
|
expr__ctx_free(ctx);
|
|
return ret;
|
|
}
|
|
|
|
p = "FOO/0";
|
|
ret = expr__parse(&val, ctx, p);
|
|
TEST_ASSERT_VAL("division by zero", ret == -1);
|
|
|
|
p = "BAR/";
|
|
ret = expr__parse(&val, ctx, p);
|
|
TEST_ASSERT_VAL("missing operand", ret == -1);
|
|
|
|
expr__ctx_clear(ctx);
|
|
TEST_ASSERT_VAL("find ids",
|
|
expr__find_ids("FOO + BAR + BAZ + BOZO", "FOO",
|
|
ctx) == 0);
|
|
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 3);
|
|
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BAR", &val_ptr));
|
|
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BAZ", &val_ptr));
|
|
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BOZO", &val_ptr));
|
|
|
|
expr__ctx_clear(ctx);
|
|
ctx->sctx.runtime = 3;
|
|
TEST_ASSERT_VAL("find ids",
|
|
expr__find_ids("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@",
|
|
NULL, ctx) == 0);
|
|
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 2);
|
|
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1,param=3@", &val_ptr));
|
|
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT2,param=3@", &val_ptr));
|
|
|
|
expr__ctx_clear(ctx);
|
|
TEST_ASSERT_VAL("find ids",
|
|
expr__find_ids("dash\\-event1 - dash\\-event2",
|
|
NULL, ctx) == 0);
|
|
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 2);
|
|
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "dash-event1", &val_ptr));
|
|
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "dash-event2", &val_ptr));
|
|
|
|
/* Only EVENT1 or EVENT2 need be measured depending on the value of smt_on. */
|
|
{
|
|
struct cpu_topology *topology = cpu_topology__new();
|
|
bool smton = smt_on(topology);
|
|
bool corewide = core_wide(/*system_wide=*/false,
|
|
/*user_requested_cpus=*/false,
|
|
topology);
|
|
|
|
cpu_topology__delete(topology);
|
|
expr__ctx_clear(ctx);
|
|
TEST_ASSERT_VAL("find ids",
|
|
expr__find_ids("EVENT1 if #smt_on else EVENT2",
|
|
NULL, ctx) == 0);
|
|
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
|
|
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids,
|
|
smton ? "EVENT1" : "EVENT2",
|
|
&val_ptr));
|
|
|
|
expr__ctx_clear(ctx);
|
|
TEST_ASSERT_VAL("find ids",
|
|
expr__find_ids("EVENT1 if #core_wide else EVENT2",
|
|
NULL, ctx) == 0);
|
|
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
|
|
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids,
|
|
corewide ? "EVENT1" : "EVENT2",
|
|
&val_ptr));
|
|
|
|
}
|
|
/* The expression is a constant 1.0 without needing to evaluate EVENT1. */
|
|
expr__ctx_clear(ctx);
|
|
TEST_ASSERT_VAL("find ids",
|
|
expr__find_ids("1.0 if EVENT1 > 100.0 else 1.0",
|
|
NULL, ctx) == 0);
|
|
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0);
|
|
|
|
/* Test toplogy constants appear well ordered. */
|
|
expr__ctx_clear(ctx);
|
|
TEST_ASSERT_VAL("#num_cpus", expr__parse(&num_cpus, ctx, "#num_cpus") == 0);
|
|
TEST_ASSERT_VAL("#num_cores", expr__parse(&num_cores, ctx, "#num_cores") == 0);
|
|
TEST_ASSERT_VAL("#num_cpus >= #num_cores", num_cpus >= num_cores);
|
|
TEST_ASSERT_VAL("#num_dies", expr__parse(&num_dies, ctx, "#num_dies") == 0);
|
|
TEST_ASSERT_VAL("#num_cores >= #num_dies", num_cores >= num_dies);
|
|
TEST_ASSERT_VAL("#num_packages", expr__parse(&num_packages, ctx, "#num_packages") == 0);
|
|
|
|
if (num_dies) // Some platforms do not have CPU die support, for example s390
|
|
TEST_ASSERT_VAL("#num_dies >= #num_packages", num_dies >= num_packages);
|
|
|
|
TEST_ASSERT_VAL("#system_tsc_freq", expr__parse(&val, ctx, "#system_tsc_freq") == 0);
|
|
if (is_intel)
|
|
TEST_ASSERT_VAL("#system_tsc_freq > 0", val > 0);
|
|
else
|
|
TEST_ASSERT_VAL("#system_tsc_freq == 0", fpclassify(val) == FP_ZERO);
|
|
|
|
/*
|
|
* Source count returns the number of events aggregating in a leader
|
|
* event including the leader. Check parsing yields an id.
|
|
*/
|
|
expr__ctx_clear(ctx);
|
|
TEST_ASSERT_VAL("source count",
|
|
expr__find_ids("source_count(EVENT1)",
|
|
NULL, ctx) == 0);
|
|
TEST_ASSERT_VAL("source count", hashmap__size(ctx->ids) == 1);
|
|
TEST_ASSERT_VAL("source count", hashmap__find(ctx->ids, "EVENT1", &val_ptr));
|
|
|
|
expr__ctx_free(ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SUITE("Simple expression parser", expr);
|