Received: by 2002:a05:6a10:9848:0:0:0:0 with SMTP id x8csp4468845pxf; Tue, 16 Mar 2021 14:28:39 -0700 (PDT) X-Google-Smtp-Source: ABdhPJw7qi3efb2fYPhTFZvtOR6vko2hTBGNANO4XYVeNoSKEyRz1vPvw6x5YWd/ew9lnnFYN55m X-Received: by 2002:a05:6402:12cf:: with SMTP id k15mr37464251edx.192.1615930119566; Tue, 16 Mar 2021 14:28:39 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1615930119; cv=none; d=google.com; s=arc-20160816; b=FcR7FNH74KubdVfFKN9uPdm9yjkk3FyzU5ORnkNPOunn+EgGh0cg3kv01rdgzU7a/e jz8ACE3hOCo8OdcNmB3Jz8REPM39dokQvtJodgbdRg3Zmk0O8eXvg5g2xusqD3HYZ8WF FEoLftERpEvvDgIAVrqRYQON7C5I8zG9RGxPIf6I+7x/JowVVjbCONL/bQtvnB3ykwXl 9lcQoFcC72z1VNnnRAv8A0lTPAwS1s0+m9SPstkkQZ1xfUA9U6KadBs6ihfMMbLOx58R lr1GpXwUwv7xrOWLxpGHmaIQOFxC+2T82fRuWu17fztNeC8qlFKJqYN31jmq5P4SuqVy L2jw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:mime-version:content-transfer-encoding :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=0GC72DehGvOLKJDeW6+nSdXcr/T0Bi1qFOQqWm9lWhM=; b=dzLqRLcoFkYpESZg0LAqXV9X0iIWBpVSspOXNsRWlThtwRIHsD/WqiLJMQAz3un02X PqyescrSSioDQVhv8NMa6w5vH6KOzmvOAEEcfPfG/KV3scbKw1JMKBhMXMvZLnqge1OL Qbl4hk0kDap0HmylVtPKizEGeKItmFJmp96ysKh0GvEhYhdxe9p1wIekop9ipQmMIp3U N8E8gavT9w9JXO6QS1BtUhG0tXcjKCh/uwNE0Z9xqlq1qba7HeB0875zIEltX2JID0An 63/8CfVAvBS4rJkKZf8DR2hzPO50uIlSQs9dzHm1SFozEnOlHKD+3GSQSDmeYiX4peyW GthA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@fb.com header.s=facebook header.b=PHbSyKo5; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=fb.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id de1si14243214edb.413.2021.03.16.14.28.17; Tue, 16 Mar 2021 14:28:39 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@fb.com header.s=facebook header.b=PHbSyKo5; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=fb.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232988AbhCPVTC (ORCPT + 99 others); Tue, 16 Mar 2021 17:19:02 -0400 Received: from mx0a-00082601.pphosted.com ([67.231.145.42]:2518 "EHLO mx0a-00082601.pphosted.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232462AbhCPVSt (ORCPT ); Tue, 16 Mar 2021 17:18:49 -0400 Received: from pps.filterd (m0044010.ppops.net [127.0.0.1]) by mx0a-00082601.pphosted.com (8.16.0.43/8.16.0.43) with SMTP id 12GLA2iJ029654 for ; Tue, 16 Mar 2021 14:18:49 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fb.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : content-type : content-transfer-encoding : mime-version; s=facebook; bh=0GC72DehGvOLKJDeW6+nSdXcr/T0Bi1qFOQqWm9lWhM=; b=PHbSyKo5TX6JawwewhigALbYt3Voh/ddSEsO8woI0CwU3f8voDK0VlUzyhgP3T3Q5O97 7JK1h0S/PyHFQtaW9cC+DAYW9PBbF/3WGH9I+RhxonKugNiu/6yKO8Vu0n5kZPZcubb8 gFQRu4pfSSefe5rH7DaVk0n5QzAa/wrmlKI= Received: from mail.thefacebook.com ([163.114.132.120]) by mx0a-00082601.pphosted.com with ESMTP id 379ebu6acp-2 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Tue, 16 Mar 2021 14:18:48 -0700 Received: from intmgw001.38.frc1.facebook.com (2620:10d:c085:108::8) by mail.thefacebook.com (2620:10d:c085:21d::6) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2176.2; Tue, 16 Mar 2021 14:18:48 -0700 Received: by devbig006.ftw2.facebook.com (Postfix, from userid 4523) id D254D62E181B; Tue, 16 Mar 2021 14:18:46 -0700 (PDT) From: Song Liu To: CC: , , , , , Song Liu Subject: [PATCH v2 1/3] perf-stat: introduce bperf, share hardware PMCs with BPF Date: Tue, 16 Mar 2021 14:18:35 -0700 Message-ID: <20210316211837.910506-2-songliubraving@fb.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210316211837.910506-1-songliubraving@fb.com> References: <20210316211837.910506-1-songliubraving@fb.com> X-FB-Internal: Safe Content-Type: text/plain Content-Transfer-Encoding: quoted-printable X-Proofpoint-UnRewURL: 0 URL was un-rewritten MIME-Version: 1.0 X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:6.0.369,18.0.761 definitions=2021-03-16_08:2021-03-16,2021-03-16 signatures=0 X-Proofpoint-Spam-Details: rule=fb_default_notspam policy=fb_default score=0 spamscore=0 lowpriorityscore=0 malwarescore=0 bulkscore=0 impostorscore=0 mlxscore=0 clxscore=1015 priorityscore=1501 mlxlogscore=999 suspectscore=0 phishscore=0 adultscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2009150000 definitions=main-2103160136 X-FB-Internal: deliver Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org perf uses performance monitoring counters (PMCs) to monitor system performance. The PMCs are limited hardware resources. For example, Intel CPUs have 3x fixed PMCs and 4x programmable PMCs per cpu. Modern data center systems use these PMCs in many different ways: system level monitoring, (maybe nested) container level monitoring, per process monitoring, profiling (in sample mode), etc. In some cases, there are more active perf_events than available hardware PMCs. To allow all perf_events to have a chance to run, it is necessary to do expensive time multiplexing of events. On the other hand, many monitoring tools count the common metrics (cycles, instructions). It is a waste to have multiple tools create multiple perf_events of "cycles" and occupy multiple PMCs. bperf tries to reduce such wastes by allowing multiple perf_events of "cycles" or "instructions" (at different scopes) to share PMUs. Instead of having each perf-stat session to read its own perf_events, bperf uses BPF programs to read the perf_events and aggregate readings to BPF maps. Then, the perf-stat session(s) reads the values from these BPF maps. Please refer to the comment before the definition of bperf_ops for the description of bperf architecture. bperf is off by default. To enable it, pass --bpf-counters option to perf-stat. bperf uses a BPF hashmap to share information about BPF programs and maps used by bperf. This map is pinned to bpffs. The default path is /sys/fs/bpf/perf_attr_map. The user could change the path with option --bpf-attr-map. Signed-off-by: Song Liu --- Known limitations: 1. Do not support per cgroup events; 2. Do not support monitoring of BPF program (perf-stat -b); 3. Do not support event groups; 4. Do not support inherit events during fork(). The following commands have been tested: perf stat --bpf-counters -e cycles,ref-cycles -a perf stat --bpf-counters -e cycles,instructions -C 1,3,4 perf stat --bpf-counters -e cycles -p 123 perf stat --bpf-counters -e cycles -t 100,101 perf stat --bpf-counters -e cycles,ref-cycles -- stressapptest ... --- tools/perf/Documentation/perf-stat.txt | 11 + tools/perf/Makefile.perf | 1 + tools/perf/builtin-stat.c | 10 + tools/perf/util/bpf_counter.c | 519 +++++++++++++++++- tools/perf/util/bpf_skel/bperf.h | 14 + tools/perf/util/bpf_skel/bperf_follower.bpf.c | 69 +++ tools/perf/util/bpf_skel/bperf_leader.bpf.c | 46 ++ tools/perf/util/bpf_skel/bperf_u.h | 14 + tools/perf/util/evsel.h | 20 +- tools/perf/util/target.h | 4 +- 10 files changed, 701 insertions(+), 7 deletions(-) create mode 100644 tools/perf/util/bpf_skel/bperf.h create mode 100644 tools/perf/util/bpf_skel/bperf_follower.bpf.c create mode 100644 tools/perf/util/bpf_skel/bperf_leader.bpf.c create mode 100644 tools/perf/util/bpf_skel/bperf_u.h diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentat= ion/perf-stat.txt index 08a1714494f87..d2e7656b5ef81 100644 --- a/tools/perf/Documentation/perf-stat.txt +++ b/tools/perf/Documentation/perf-stat.txt @@ -93,6 +93,17 @@ report:: =20 1.102235068 seconds time elapsed =20 +--bpf-counters:: + Use BPF programs to aggregate readings from perf_events. This + allows multiple perf-stat sessions that are counting the same metric (cyc= les, + instructions, etc.) to share hardware counters. + +--bpf-attr-map:: + With option "--bpf-counters", different perf-stat sessions share + information about shared BPF programs and maps via a pinned hashmap. + Use "--bpf-attr-map" to specify the path of this pinned hashmap. + The default path is /sys/fs/bpf/perf_attr_map. + ifdef::HAVE_LIBPFM[] --pfm-events events:: Select a PMU event using libpfm4 syntax (see http://perfmon2.sf.net) diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf index f6e609673de2b..ca9aa08e85a1f 100644 --- a/tools/perf/Makefile.perf +++ b/tools/perf/Makefile.perf @@ -1007,6 +1007,7 @@ python-clean: SKEL_OUT :=3D $(abspath $(OUTPUT)util/bpf_skel) SKEL_TMP_OUT :=3D $(abspath $(SKEL_OUT)/.tmp) SKELETONS :=3D $(SKEL_OUT)/bpf_prog_profiler.skel.h +SKELETONS +=3D $(SKEL_OUT)/bperf_leader.skel.h $(SKEL_OUT)/bperf_follower.= skel.h =20 ifdef BUILD_BPF_SKEL BPFTOOL :=3D $(SKEL_TMP_OUT)/bootstrap/bpftool diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 2e2e4a8345ea2..92696373da994 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -792,6 +792,12 @@ static int __run_perf_stat(int argc, const char **argv= , int run_idx) } =20 evlist__for_each_cpu (evsel_list, i, cpu) { + /* + * bperf calls evsel__open_per_cpu() in bperf__load(), so + * no need to call it again here. + */ + if (target.use_bpf) + break; affinity__set(&affinity, cpu); =20 evlist__for_each_entry(evsel_list, counter) { @@ -1146,6 +1152,10 @@ static struct option stat_options[] =3D { #ifdef HAVE_BPF_SKEL OPT_STRING('b', "bpf-prog", &target.bpf_str, "bpf-prog-id", "stat events on existing bpf program id"), + OPT_BOOLEAN(0, "bpf-counters", &target.use_bpf, + "use bpf program to count events"), + OPT_STRING(0, "bpf-attr-map", &target.attr_map, "attr-map-path", + "path to perf_event_attr map"), #endif OPT_BOOLEAN('a', "all-cpus", &target.system_wide, "system-wide collection from all CPUs"), diff --git a/tools/perf/util/bpf_counter.c b/tools/perf/util/bpf_counter.c index 04f89120b3232..81d1df3c4ec0e 100644 --- a/tools/perf/util/bpf_counter.c +++ b/tools/perf/util/bpf_counter.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -12,14 +13,45 @@ #include #include #include +#include =20 #include "bpf_counter.h" #include "counts.h" #include "debug.h" #include "evsel.h" +#include "evlist.h" #include "target.h" +#include "cpumap.h" +#include "thread_map.h" =20 #include "bpf_skel/bpf_prog_profiler.skel.h" +#include "bpf_skel/bperf_u.h" +#include "bpf_skel/bperf_leader.skel.h" +#include "bpf_skel/bperf_follower.skel.h" + +/* + * bperf uses a hashmap, the attr_map, to track all the leader programs. + * The hashmap is pinned in bpffs. flock() on this file is used to ensure + * no concurrent access to the attr_map. The key of attr_map is struct + * perf_event_attr, and the value is struct perf_event_attr_map_entry. + * + * struct perf_event_attr_map_entry contains two __u32 IDs, bpf_link of the + * leader prog, and the diff_map. Each perf-stat session holds a reference + * to the bpf_link to make sure the leader prog is attached to sched_switch + * tracepoint. + * + * Since the hashmap only contains IDs of the bpf_link and diff_map, it + * does not hold any references to the leader program. Once all perf-stat + * sessions of these events exit, the leader prog, its maps, and the + * perf_events will be freed. + */ +struct perf_event_attr_map_entry { + __u32 link_id; + __u32 diff_map_id; +}; + +#define DEFAULT_ATTR_MAP_PATH "fs/bpf/perf_attr_map" +#define ATTR_MAP_SIZE 16 =20 static inline void *u64_to_ptr(__u64 ptr) { @@ -274,17 +306,494 @@ struct bpf_counter_ops bpf_program_profiler_ops =3D { .install_pe =3D bpf_program_profiler__install_pe, }; =20 +static __u32 bpf_link_get_id(int fd) +{ + struct bpf_link_info link_info =3D {0}; + __u32 link_info_len =3D sizeof(link_info); + + bpf_obj_get_info_by_fd(fd, &link_info, &link_info_len); + return link_info.id; +} + +static __u32 bpf_link_get_prog_id(int fd) +{ + struct bpf_link_info link_info =3D {0}; + __u32 link_info_len =3D sizeof(link_info); + + bpf_obj_get_info_by_fd(fd, &link_info, &link_info_len); + return link_info.prog_id; +} + +static __u32 bpf_map_get_id(int fd) +{ + struct bpf_map_info map_info =3D {0}; + __u32 map_info_len =3D sizeof(map_info); + + bpf_obj_get_info_by_fd(fd, &map_info, &map_info_len); + return map_info.id; +} + +static int bperf_lock_attr_map(struct target *target) +{ + char path[PATH_MAX]; + int map_fd, err; + + if (target->attr_map) { + scnprintf(path, PATH_MAX, "%s", target->attr_map); + } else { + scnprintf(path, PATH_MAX, "%s/%s", sysfs__mountpoint(), + DEFAULT_ATTR_MAP_PATH); + } + + if (access(path, F_OK)) { + map_fd =3D bpf_create_map(BPF_MAP_TYPE_HASH, + sizeof(struct perf_event_attr), + sizeof(struct perf_event_attr_map_entry), + ATTR_MAP_SIZE, 0); + if (map_fd < 0) + return -1; + + err =3D bpf_obj_pin(map_fd, path); + if (err) { + /* someone pinned the map in parallel? */ + close(map_fd); + map_fd =3D bpf_obj_get(path); + if (map_fd < 0) + return -1; + } + } else { + map_fd =3D bpf_obj_get(path); + if (map_fd < 0) + return -1; + } + + err =3D flock(map_fd, LOCK_EX); + if (err) { + close(map_fd); + return -1; + } + return map_fd; +} + +/* trigger the leader program on a cpu */ +static int bperf_trigger_reading(int prog_fd, int cpu) +{ + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts, + .ctx_in =3D NULL, + .ctx_size_in =3D 0, + .flags =3D BPF_F_TEST_RUN_ON_CPU, + .cpu =3D cpu, + .retval =3D 0, + ); + + return bpf_prog_test_run_opts(prog_fd, &opts); +} + +static int bperf_check_target(struct evsel *evsel, + struct target *target, + enum bperf_filter_type *filter_type, + __u32 *filter_entry_cnt) +{ + if (evsel->leader->core.nr_members > 1) { + pr_err("bpf managed perf events do not yet support groups.\n"); + return -1; + } + + /* determine filter type based on target */ + if (target->system_wide) { + *filter_type =3D BPERF_FILTER_GLOBAL; + *filter_entry_cnt =3D 1; + } else if (target->cpu_list) { + *filter_type =3D BPERF_FILTER_CPU; + *filter_entry_cnt =3D perf_cpu_map__nr(evsel__cpus(evsel)); + } else if (target->tid) { + *filter_type =3D BPERF_FILTER_PID; + *filter_entry_cnt =3D perf_thread_map__nr(evsel->core.threads); + } else if (target->pid || evsel->evlist->workload.pid !=3D -1) { + *filter_type =3D BPERF_FILTER_TGID; + *filter_entry_cnt =3D perf_thread_map__nr(evsel->core.threads); + } else { + pr_err("bpf managed perf events do not yet support these targets.\n"); + return -1; + } + + return 0; +} + +static struct perf_cpu_map *all_cpu_map; + +static int bperf_reload_leader_program(struct evsel *evsel, int attr_map_f= d, + struct perf_event_attr_map_entry *entry) +{ + struct bperf_leader_bpf *skel =3D bperf_leader_bpf__open(); + int link_fd, diff_map_fd, err; + struct bpf_link *link =3D NULL; + + if (!skel) { + pr_err("Failed to open leader skeleton\n"); + return -1; + } + + bpf_map__resize(skel->maps.events, libbpf_num_possible_cpus()); + err =3D bperf_leader_bpf__load(skel); + if (err) { + pr_err("Failed to load leader skeleton\n"); + goto out; + } + + err =3D -1; + link =3D bpf_program__attach(skel->progs.on_switch); + if (!link) { + pr_err("Failed to attach leader program\n"); + goto out; + } + + link_fd =3D bpf_link__fd(link); + diff_map_fd =3D bpf_map__fd(skel->maps.diff_readings); + entry->link_id =3D bpf_link_get_id(link_fd); + entry->diff_map_id =3D bpf_map_get_id(diff_map_fd); + err =3D bpf_map_update_elem(attr_map_fd, &evsel->core.attr, entry, BPF_AN= Y); + assert(err =3D=3D 0); + + evsel->bperf_leader_link_fd =3D bpf_link_get_fd_by_id(entry->link_id); + assert(evsel->bperf_leader_link_fd >=3D 0); + + /* + * save leader_skel for install_pe, which is called within + * following evsel__open_per_cpu call + */ + evsel->leader_skel =3D skel; + evsel__open_per_cpu(evsel, all_cpu_map, -1); + +out: + bperf_leader_bpf__destroy(skel); + bpf_link__destroy(link); + return err; +} + +static int bperf__load(struct evsel *evsel, struct target *target) +{ + struct perf_event_attr_map_entry entry =3D {0xffffffff, 0xffffffff}; + int attr_map_fd, diff_map_fd =3D -1, err; + enum bperf_filter_type filter_type; + __u32 filter_entry_cnt, i; + + if (bperf_check_target(evsel, target, &filter_type, &filter_entry_cnt)) + return -1; + + if (!all_cpu_map) { + all_cpu_map =3D perf_cpu_map__new(NULL); + if (!all_cpu_map) + return -1; + } + + evsel->bperf_leader_prog_fd =3D -1; + evsel->bperf_leader_link_fd =3D -1; + + /* + * Step 1: hold a fd on the leader program and the bpf_link, if + * the program is not already gone, reload the program. + * Use flock() to ensure exclusive access to the perf_event_attr + * map. + */ + attr_map_fd =3D bperf_lock_attr_map(target); + if (attr_map_fd < 0) { + pr_err("Failed to lock perf_event_attr map\n"); + return -1; + } + + err =3D bpf_map_lookup_elem(attr_map_fd, &evsel->core.attr, &entry); + if (err) { + err =3D bpf_map_update_elem(attr_map_fd, &evsel->core.attr, &entry, BPF_= ANY); + if (err) + goto out; + } + + evsel->bperf_leader_link_fd =3D bpf_link_get_fd_by_id(entry.link_id); + if (evsel->bperf_leader_link_fd < 0 && + bperf_reload_leader_program(evsel, attr_map_fd, &entry)) + goto out; + + /* + * The bpf_link holds reference to the leader program, and the + * leader program holds reference to the maps. Therefore, if + * link_id is valid, diff_map_id should also be valid. + */ + evsel->bperf_leader_prog_fd =3D bpf_prog_get_fd_by_id( + bpf_link_get_prog_id(evsel->bperf_leader_link_fd)); + assert(evsel->bperf_leader_prog_fd >=3D 0); + + diff_map_fd =3D bpf_map_get_fd_by_id(entry.diff_map_id); + assert(diff_map_fd >=3D 0); + + /* + * bperf uses BPF_PROG_TEST_RUN to get accurate reading. Check + * whether the kernel support it + */ + err =3D bperf_trigger_reading(evsel->bperf_leader_prog_fd, 0); + if (err) { + pr_err("The kernel does not support test_run for raw_tp BPF programs.\n" + "Therefore, --use-bpf might show inaccurate readings\n"); + goto out; + } + + /* Step 2: load the follower skeleton */ + evsel->follower_skel =3D bperf_follower_bpf__open(); + if (!evsel->follower_skel) { + pr_err("Failed to open follower skeleton\n"); + goto out; + } + + /* attach fexit program to the leader program */ + bpf_program__set_attach_target(evsel->follower_skel->progs.fexit_XXX, + evsel->bperf_leader_prog_fd, "on_switch"); + + /* connect to leader diff_reading map */ + bpf_map__reuse_fd(evsel->follower_skel->maps.diff_readings, diff_map_fd); + + /* set up reading map */ + bpf_map__set_max_entries(evsel->follower_skel->maps.accum_readings, + filter_entry_cnt); + /* set up follower filter based on target */ + bpf_map__set_max_entries(evsel->follower_skel->maps.filter, + filter_entry_cnt); + err =3D bperf_follower_bpf__load(evsel->follower_skel); + if (err) { + pr_err("Failed to load follower skeleton\n"); + bperf_follower_bpf__destroy(evsel->follower_skel); + evsel->follower_skel =3D NULL; + goto out; + } + + for (i =3D 0; i < filter_entry_cnt; i++) { + int filter_map_fd; + __u32 key; + + if (filter_type =3D=3D BPERF_FILTER_PID || + filter_type =3D=3D BPERF_FILTER_TGID) + key =3D evsel->core.threads->map[i].pid; + else if (filter_type =3D=3D BPERF_FILTER_CPU) + key =3D evsel->core.cpus->map[i]; + else + break; + + filter_map_fd =3D bpf_map__fd(evsel->follower_skel->maps.filter); + bpf_map_update_elem(filter_map_fd, &key, &i, BPF_ANY); + } + + evsel->follower_skel->bss->type =3D filter_type; + + err =3D bperf_follower_bpf__attach(evsel->follower_skel); + +out: + if (err && evsel->bperf_leader_link_fd >=3D 0) + close(evsel->bperf_leader_link_fd); + if (err && evsel->bperf_leader_prog_fd >=3D 0) + close(evsel->bperf_leader_prog_fd); + if (diff_map_fd >=3D 0) + close(diff_map_fd); + + flock(attr_map_fd, LOCK_UN); + close(attr_map_fd); + + return err; +} + +static int bperf__install_pe(struct evsel *evsel, int cpu, int fd) +{ + struct bperf_leader_bpf *skel =3D evsel->leader_skel; + + return bpf_map_update_elem(bpf_map__fd(skel->maps.events), + &cpu, &fd, BPF_ANY); +} + +/* + * trigger the leader prog on each cpu, so the accum_reading map could get + * the latest readings. + */ +static int bperf_sync_counters(struct evsel *evsel) +{ + int num_cpu, i, cpu; + + num_cpu =3D all_cpu_map->nr; + for (i =3D 0; i < num_cpu; i++) { + cpu =3D all_cpu_map->map[i]; + bperf_trigger_reading(evsel->bperf_leader_prog_fd, cpu); + } + return 0; +} + +static int bperf__enable(struct evsel *evsel) +{ + evsel->follower_skel->bss->enabled =3D 1; + return 0; +} + +static int bperf__read(struct evsel *evsel) +{ + struct bperf_follower_bpf *skel =3D evsel->follower_skel; + __u32 num_cpu_bpf =3D cpu__max_cpu(); + struct bpf_perf_event_value values[num_cpu_bpf]; + int reading_map_fd, err =3D 0; + __u32 i, j, num_cpu; + + bperf_sync_counters(evsel); + reading_map_fd =3D bpf_map__fd(skel->maps.accum_readings); + + for (i =3D 0; i < bpf_map__max_entries(skel->maps.accum_readings); i++) { + __u32 cpu; + + err =3D bpf_map_lookup_elem(reading_map_fd, &i, values); + if (err) + goto out; + switch (evsel->follower_skel->bss->type) { + case BPERF_FILTER_GLOBAL: + assert(i =3D=3D 0); + + num_cpu =3D all_cpu_map->nr; + for (j =3D 0; j < num_cpu; j++) { + cpu =3D all_cpu_map->map[j]; + perf_counts(evsel->counts, cpu, 0)->val =3D values[cpu].counter; + perf_counts(evsel->counts, cpu, 0)->ena =3D values[cpu].enabled; + perf_counts(evsel->counts, cpu, 0)->run =3D values[cpu].running; + } + break; + case BPERF_FILTER_CPU: + cpu =3D evsel->core.cpus->map[i]; + perf_counts(evsel->counts, i, 0)->val =3D values[cpu].counter; + perf_counts(evsel->counts, i, 0)->ena =3D values[cpu].enabled; + perf_counts(evsel->counts, i, 0)->run =3D values[cpu].running; + break; + case BPERF_FILTER_PID: + case BPERF_FILTER_TGID: + perf_counts(evsel->counts, 0, i)->val =3D 0; + perf_counts(evsel->counts, 0, i)->ena =3D 0; + perf_counts(evsel->counts, 0, i)->run =3D 0; + + for (cpu =3D 0; cpu < num_cpu_bpf; cpu++) { + perf_counts(evsel->counts, 0, i)->val +=3D values[cpu].counter; + perf_counts(evsel->counts, 0, i)->ena +=3D values[cpu].enabled; + perf_counts(evsel->counts, 0, i)->run +=3D values[cpu].running; + } + break; + default: + break; + } + } +out: + return err; +} + +static int bperf__destroy(struct evsel *evsel) +{ + bperf_follower_bpf__destroy(evsel->follower_skel); + close(evsel->bperf_leader_prog_fd); + close(evsel->bperf_leader_link_fd); + return 0; +} + +/* + * bperf: share hardware PMCs with BPF + * + * perf uses performance monitoring counters (PMC) to monitor system + * performance. The PMCs are limited hardware resources. For example, + * Intel CPUs have 3x fixed PMCs and 4x programmable PMCs per cpu. + * + * Modern data center systems use these PMCs in many different ways: + * system level monitoring, (maybe nested) container level monitoring, per + * process monitoring, profiling (in sample mode), etc. In some cases, + * there are more active perf_events than available hardware PMCs. To allow + * all perf_events to have a chance to run, it is necessary to do expensive + * time multiplexing of events. + * + * On the other hand, many monitoring tools count the common metrics + * (cycles, instructions). It is a waste to have multiple tools create + * multiple perf_events of "cycles" and occupy multiple PMCs. + * + * bperf tries to reduce such wastes by allowing multiple perf_events of + * "cycles" or "instructions" (at different scopes) to share PMUs. Instead + * of having each perf-stat session to read its own perf_events, bperf uses + * BPF programs to read the perf_events and aggregate readings to BPF maps. + * Then, the perf-stat session(s) reads the values from these BPF maps. + * + * || + * shared progs and maps <- || -> per session progs and maps + * || + * --------------- || + * | perf_events | || + * --------------- fexit || ----------------- + * | --------||----> | follower prog | + * --------------- / || --- ----------------- + * cs -> | leader prog |/ ||/ | | + * --> --------------- /|| -------------- ------------------ + * / | | / || | filter map | | accum_readings | + * / ------------ ------------ || -------------- ------------------ + * | | prev map | | diff map | || | + * | ------------ ------------ || | + * \ || | + * =3D \ =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D | =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + * \ / user space + * \ / + * \ / + * BPF_PROG_TEST_RUN BPF_MAP_LOOKUP_ELEM + * \ / + * \ / + * \------ perf-stat ----------------------/ + * + * The figure above shows the architecture of bperf. Note that the figure + * is divided into 3 regions: shared progs and maps (top left), per session + * progs and maps (top right), and user space (bottom). + * + * The leader prog is triggered on each context switch (cs). The leader + * prog reads perf_events and stores the difference (current_reading - + * previous_reading) to the diff map. For the same metric, e.g. "cycles", + * multiple perf-stat sessions share the same leader prog. + * + * Each perf-stat session creates a follower prog as fexit program to the + * leader prog. It is possible to attach up to BPF_MAX_TRAMP_PROGS (38) + * follower progs to the same leader prog. The follower prog checks current + * task and processor ID to decide whether to add the value from the diff + * map to its accumulated reading map (accum_readings). + * + * Finally, perf-stat user space reads the value from accum_reading map. + * + * Besides context switch, it is also necessary to trigger the leader prog + * before perf-stat reads the value. Otherwise, the accum_reading map may + * not have the latest reading from the perf_events. This is achieved by + * triggering the event via sys_bpf(BPF_PROG_TEST_RUN) to each CPU. + * + * Comment before the definition of struct perf_event_attr_map_entry + * describes how different sessions of perf-stat share information about + * the leader prog. + */ + +struct bpf_counter_ops bperf_ops =3D { + .load =3D bperf__load, + .enable =3D bperf__enable, + .read =3D bperf__read, + .install_pe =3D bperf__install_pe, + .destroy =3D bperf__destroy, +}; + +static inline bool bpf_counter_skip(struct evsel *evsel) +{ + return list_empty(&evsel->bpf_counter_list) && + evsel->follower_skel =3D=3D NULL; +} + int bpf_counter__install_pe(struct evsel *evsel, int cpu, int fd) { - if (list_empty(&evsel->bpf_counter_list)) + if (bpf_counter_skip(evsel)) return 0; return evsel->bpf_counter_ops->install_pe(evsel, cpu, fd); } =20 int bpf_counter__load(struct evsel *evsel, struct target *target) { - if (target__has_bpf(target)) + if (target->bpf_str) evsel->bpf_counter_ops =3D &bpf_program_profiler_ops; + else if (target->use_bpf) + evsel->bpf_counter_ops =3D &bperf_ops; =20 if (evsel->bpf_counter_ops) return evsel->bpf_counter_ops->load(evsel, target); @@ -293,21 +802,21 @@ int bpf_counter__load(struct evsel *evsel, struct tar= get *target) =20 int bpf_counter__enable(struct evsel *evsel) { - if (list_empty(&evsel->bpf_counter_list)) + if (bpf_counter_skip(evsel)) return 0; return evsel->bpf_counter_ops->enable(evsel); } =20 int bpf_counter__read(struct evsel *evsel) { - if (list_empty(&evsel->bpf_counter_list)) + if (bpf_counter_skip(evsel)) return -EAGAIN; return evsel->bpf_counter_ops->read(evsel); } =20 void bpf_counter__destroy(struct evsel *evsel) { - if (list_empty(&evsel->bpf_counter_list)) + if (bpf_counter_skip(evsel)) return; evsel->bpf_counter_ops->destroy(evsel); evsel->bpf_counter_ops =3D NULL; diff --git a/tools/perf/util/bpf_skel/bperf.h b/tools/perf/util/bpf_skel/bp= erf.h new file mode 100644 index 0000000000000..186a5551ddb9d --- /dev/null +++ b/tools/perf/util/bpf_skel/bperf.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +// Copyright (c) 2021 Facebook + +#ifndef __BPERF_STAT_H +#define __BPERF_STAT_H + +typedef struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(struct bpf_perf_event_value)); + __uint(max_entries, 1); +} reading_map; + +#endif /* __BPERF_STAT_H */ diff --git a/tools/perf/util/bpf_skel/bperf_follower.bpf.c b/tools/perf/uti= l/bpf_skel/bperf_follower.bpf.c new file mode 100644 index 0000000000000..b8fa3cb2da230 --- /dev/null +++ b/tools/perf/util/bpf_skel/bperf_follower.bpf.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +// Copyright (c) 2021 Facebook +#include +#include +#include +#include +#include "bperf.h" +#include "bperf_u.h" + +reading_map diff_readings SEC(".maps"); +reading_map accum_readings SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} filter SEC(".maps"); + +enum bperf_filter_type type =3D 0; +int enabled =3D 0; + +SEC("fexit/XXX") +int BPF_PROG(fexit_XXX) +{ + struct bpf_perf_event_value *diff_val, *accum_val; + __u32 filter_key, zero =3D 0; + __u32 *accum_key; + + if (!enabled) + return 0; + + switch (type) { + case BPERF_FILTER_GLOBAL: + accum_key =3D &zero; + goto do_add; + case BPERF_FILTER_CPU: + filter_key =3D bpf_get_smp_processor_id(); + break; + case BPERF_FILTER_PID: + filter_key =3D bpf_get_current_pid_tgid() & 0xffffffff; + break; + case BPERF_FILTER_TGID: + filter_key =3D bpf_get_current_pid_tgid() >> 32; + break; + default: + return 0; + } + + accum_key =3D bpf_map_lookup_elem(&filter, &filter_key); + if (!accum_key) + return 0; + +do_add: + diff_val =3D bpf_map_lookup_elem(&diff_readings, &zero); + if (!diff_val) + return 0; + + accum_val =3D bpf_map_lookup_elem(&accum_readings, accum_key); + if (!accum_val) + return 0; + + accum_val->counter +=3D diff_val->counter; + accum_val->enabled +=3D diff_val->enabled; + accum_val->running +=3D diff_val->running; + + return 0; +} + +char LICENSE[] SEC("license") =3D "Dual BSD/GPL"; diff --git a/tools/perf/util/bpf_skel/bperf_leader.bpf.c b/tools/perf/util/= bpf_skel/bperf_leader.bpf.c new file mode 100644 index 0000000000000..4f70d1459e86c --- /dev/null +++ b/tools/perf/util/bpf_skel/bperf_leader.bpf.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +// Copyright (c) 2021 Facebook +#include +#include +#include +#include +#include "bperf.h" + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(int)); + __uint(map_flags, BPF_F_PRESERVE_ELEMS); +} events SEC(".maps"); + +reading_map prev_readings SEC(".maps"); +reading_map diff_readings SEC(".maps"); + +SEC("raw_tp/sched_switch") +int BPF_PROG(on_switch) +{ + struct bpf_perf_event_value val, *prev_val, *diff_val; + __u32 key =3D bpf_get_smp_processor_id(); + __u32 zero =3D 0; + long err; + + prev_val =3D bpf_map_lookup_elem(&prev_readings, &zero); + if (!prev_val) + return 0; + + diff_val =3D bpf_map_lookup_elem(&diff_readings, &zero); + if (!diff_val) + return 0; + + err =3D bpf_perf_event_read_value(&events, key, &val, sizeof(val)); + if (err) + return 0; + + diff_val->counter =3D val.counter - prev_val->counter; + diff_val->enabled =3D val.enabled - prev_val->enabled; + diff_val->running =3D val.running - prev_val->running; + *prev_val =3D val; + return 0; +} + +char LICENSE[] SEC("license") =3D "Dual BSD/GPL"; diff --git a/tools/perf/util/bpf_skel/bperf_u.h b/tools/perf/util/bpf_skel/= bperf_u.h new file mode 100644 index 0000000000000..1ce0c2c905c11 --- /dev/null +++ b/tools/perf/util/bpf_skel/bperf_u.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +// Copyright (c) 2021 Facebook + +#ifndef __BPERF_STAT_U_H +#define __BPERF_STAT_U_H + +enum bperf_filter_type { + BPERF_FILTER_GLOBAL =3D 1, + BPERF_FILTER_CPU, + BPERF_FILTER_PID, + BPERF_FILTER_TGID, +}; + +#endif /* __BPERF_STAT_U_H */ diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 6026487353dd8..dd4f56f9cfdf5 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -20,6 +20,8 @@ union perf_event; struct bpf_counter_ops; struct target; struct hashmap; +struct bperf_leader_bpf; +struct bperf_follower_bpf; =20 typedef int (evsel__sb_cb_t)(union perf_event *event, void *data); =20 @@ -130,8 +132,24 @@ struct evsel { * See also evsel__has_callchain(). */ __u64 synth_sample_type; - struct list_head bpf_counter_list; + + /* + * bpf_counter_ops serves two use cases: + * 1. perf-stat -b counting events used byBPF programs + * 2. perf-stat --use-bpf use BPF programs to aggregate counts + */ struct bpf_counter_ops *bpf_counter_ops; + + /* for perf-stat -b */ + struct list_head bpf_counter_list; + + /* for perf-stat --use-bpf */ + int bperf_leader_prog_fd; + int bperf_leader_link_fd; + union { + struct bperf_leader_bpf *leader_skel; + struct bperf_follower_bpf *follower_skel; + }; }; =20 struct perf_missing_features { diff --git a/tools/perf/util/target.h b/tools/perf/util/target.h index f132c6c2eef81..1bce3eb28ef25 100644 --- a/tools/perf/util/target.h +++ b/tools/perf/util/target.h @@ -16,6 +16,8 @@ struct target { bool uses_mmap; bool default_per_cpu; bool per_thread; + bool use_bpf; + const char *attr_map; }; =20 enum target_errno { @@ -66,7 +68,7 @@ static inline bool target__has_cpu(struct target *target) =20 static inline bool target__has_bpf(struct target *target) { - return target->bpf_str; + return target->bpf_str || target->use_bpf; } =20 static inline bool target__none(struct target *target) --=20 2.30.2