diff mbox series

[v2,perf,bpf,11/11] perf, bpf: save information about short living bpf programs

Message ID 20190215000045.2592135-2-songliubraving@fb.com
State Changes Requested
Delegated to: BPF Maintainers
Headers show
Series perf annotation of BPF programs | expand

Commit Message

Song Liu Feb. 15, 2019, midnight UTC
To annotate bpf programs in perf, it is necessary to save information in
bpf_prog_info and btf. For short living bpf program, it is necessary to
save these information before it is unloaded.

This patch saves these information in a separate thread. This thread
creates its own evlist, that only tracks bpf events. This evlists uses
ring buffer with very low watermark for lower latency. When bpf load
events are received, this thread tries to gather information via sys_bpf
and save it in perf_env.

Signed-off-by: Song Liu <songliubraving@fb.com>
---
 tools/perf/builtin-record.c |  13 ++++
 tools/perf/builtin-top.c    |  12 ++++
 tools/perf/util/bpf-event.c | 129 ++++++++++++++++++++++++++++++++++++
 tools/perf/util/bpf-event.h |  22 ++++++
 tools/perf/util/evlist.c    |  20 ++++++
 tools/perf/util/evlist.h    |   2 +
 6 files changed, 198 insertions(+)

Comments

Arnaldo Carvalho de Melo Feb. 15, 2019, 2:41 p.m. UTC | #1
Em Thu, Feb 14, 2019 at 04:00:45PM -0800, Song Liu escreveu:
> To annotate bpf programs in perf, it is necessary to save information in
> bpf_prog_info and btf. For short living bpf program, it is necessary to
> save these information before it is unloaded.
> 
> This patch saves these information in a separate thread. This thread
> creates its own evlist, that only tracks bpf events. This evlists uses
> ring buffer with very low watermark for lower latency. When bpf load
> events are received, this thread tries to gather information via sys_bpf
> and save it in perf_env.
> 
> Signed-off-by: Song Liu <songliubraving@fb.com>
> ---
>  tools/perf/builtin-record.c |  13 ++++
>  tools/perf/builtin-top.c    |  12 ++++
>  tools/perf/util/bpf-event.c | 129 ++++++++++++++++++++++++++++++++++++
>  tools/perf/util/bpf-event.h |  22 ++++++
>  tools/perf/util/evlist.c    |  20 ++++++
>  tools/perf/util/evlist.h    |   2 +
>  6 files changed, 198 insertions(+)
> 
> diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
> index 2355e0a9eda0..46abb44aaaab 100644
> --- a/tools/perf/builtin-record.c
> +++ b/tools/perf/builtin-record.c
> @@ -1106,6 +1106,8 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
>  	struct perf_data *data = &rec->data;
>  	struct perf_session *session;
>  	bool disabled = false, draining = false;
> +	struct bpf_event_poll_args poll_args;
> +	bool bpf_thread_running = false;
>  	int fd;
>  
>  	atexit(record__sig_exit);
> @@ -1206,6 +1208,14 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
>  		goto out_child;
>  	}
>  
> +	if (rec->opts.bpf_event) {
> +		poll_args.env = &session->header.env;
> +		poll_args.target = &rec->opts.target;
> +		poll_args.done = &done;
> +		if (bpf_event__start_polling_thread(&poll_args) == 0)
> +			bpf_thread_running = true;
> +	}
> +
>  	err = record__synthesize(rec, false);
>  	if (err < 0)
>  		goto out_child;
> @@ -1456,6 +1466,9 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
>  
>  out_delete_session:
>  	perf_session__delete(session);
> +
> +	if (bpf_thread_running)
> +		bpf_event__stop_polling_thread(&poll_args);
>  	return status;
>  }
>  
> diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
> index 5271d7211b9c..2586ee081967 100644
> --- a/tools/perf/builtin-top.c
> +++ b/tools/perf/builtin-top.c
> @@ -1524,10 +1524,12 @@ int cmd_top(int argc, const char **argv)
>  			"number of thread to run event synthesize"),
>  	OPT_END()
>  	};
> +	struct bpf_event_poll_args poll_args;
>  	const char * const top_usage[] = {
>  		"perf top [<options>]",
>  		NULL
>  	};
> +	bool bpf_thread_running = false;
>  	int status = hists__init();
>  
>  	if (status < 0)
> @@ -1652,8 +1654,18 @@ int cmd_top(int argc, const char **argv)
>  		signal(SIGWINCH, winch_sig);
>  	}
>  
> +	if (top.record_opts.bpf_event) {
> +		poll_args.env = &perf_env;
> +		poll_args.target = target;
> +		poll_args.done = &done;
> +		if (bpf_event__start_polling_thread(&poll_args) == 0)
> +			bpf_thread_running = true;
> +	}
>  	status = __cmd_top(&top);
>  
> +	if (bpf_thread_running)
> +		bpf_event__stop_polling_thread(&poll_args);
> +
>  out_delete_evlist:
>  	perf_evlist__delete(top.evlist);
>  
> diff --git a/tools/perf/util/bpf-event.c b/tools/perf/util/bpf-event.c
> index 4f347d61ed96..0caf137c515b 100644
> --- a/tools/perf/util/bpf-event.c
> +++ b/tools/perf/util/bpf-event.c
> @@ -8,6 +8,7 @@
>  #include "machine.h"
>  #include "env.h"
>  #include "session.h"
> +#include "evlist.h"
>  
>  #define ptr_to_u64(ptr)    ((__u64)(unsigned long)(ptr))
>  
> @@ -316,3 +317,131 @@ int perf_event__synthesize_bpf_events(struct perf_session *session,
>  	free(event);
>  	return err;
>  }
> +
> +static void perf_env_add_bpf_info(struct perf_env *env, u32 id)
> +{
> +	struct bpf_prog_info_linear *info_linear;
> +	struct bpf_prog_info_node *info_node;
> +	struct btf *btf = NULL;
> +	u64 arrays;
> +	u32 btf_id;
> +	int fd;
> +
> +	fd = bpf_prog_get_fd_by_id(id);
> +	if (fd < 0)
> +		return;
> +
> +	arrays = 1UL << BPF_PROG_INFO_JITED_KSYMS;
> +	arrays |= 1UL << BPF_PROG_INFO_JITED_FUNC_LENS;
> +	arrays |= 1UL << BPF_PROG_INFO_FUNC_INFO;
> +	arrays |= 1UL << BPF_PROG_INFO_PROG_TAGS;
> +	arrays |= 1UL << BPF_PROG_INFO_JITED_INSNS;
> +	arrays |= 1UL << BPF_PROG_INFO_LINE_INFO;
> +	arrays |= 1UL << BPF_PROG_INFO_JITED_LINE_INFO;
> +
> +	info_linear = bpf_program__get_prog_info_linear(fd, arrays);
> +	if (IS_ERR_OR_NULL(info_linear)) {
> +		pr_debug("%s: failed to get BPF program info. aborting\n", __func__);
> +		goto out;
> +	}
> +
> +	btf_id = info_linear->info.btf_id;
> +
> +	info_node = malloc(sizeof(struct bpf_prog_info_node));
> +	if (info_node) {
> +		info_node->info_linear = info_linear;
> +		perf_env__insert_bpf_prog_info(env, info_node);
> +	} else
> +		free(info_linear);
> +
> +	if (btf_id == 0)
> +		goto out;
> +
> +	if (btf__get_from_id(btf_id, &btf)) {
> +		pr_debug("%s: failed to get BTF of id %u, aborting\n",
> +			 __func__, btf_id);
> +		goto out;
> +	}
> +	perf_fetch_btf(env, btf_id, btf);
> +
> +out:
> +	free(btf);
> +	close(fd);
> +}
> +
> +static void *bpf_poll_thread(void *arg)
> +{
> +	struct bpf_event_poll_args *args = arg;
> +	int i;
> +
> +	while (!*(args->done)) {
> +		perf_evlist__poll(args->evlist, 1000);
> +
> +		for (i = 0; i < args->evlist->nr_mmaps; i++) {
> +			struct perf_mmap *map = &args->evlist->mmap[i];
> +			union perf_event *event;
> +
> +			if (perf_mmap__read_init(map))
> +				continue;
> +			while ((event = perf_mmap__read_event(map)) != NULL) {
> +				pr_debug("processing vip event of type %d\n",
> +					 event->header.type);
> +				switch (event->header.type) {
> +				case PERF_RECORD_BPF_EVENT:
> +					if (event->bpf_event.type != PERF_BPF_EVENT_PROG_LOAD)
> +						break;
> +					perf_env_add_bpf_info(args->env, event->bpf_event.id);
> +					break;
> +				default:
> +					break;
> +				}
> +				perf_mmap__consume(map);
> +			}
> +			perf_mmap__read_done(map);
> +		}
> +	}
> +	return NULL;
> +}
> +
> +pthread_t poll_thread;
> +
> +int bpf_event__start_polling_thread(struct bpf_event_poll_args *args)
> +{
> +	struct perf_evsel *counter;
> +
> +	args->evlist = perf_evlist__new();
> +
> +	if (args->evlist == NULL)
> +		return -1;
> +
> +	if (perf_evlist__create_maps(args->evlist, args->target))
		goto out_delete_evlist;
> +
> +	if (perf_evlist__add_bpf_tracker(args->evlist))
		goto out_delete_evlist;
> +
> +	evlist__for_each_entry(args->evlist, counter) {
> +		if (perf_evsel__open(counter, args->evlist->cpus,
> +				     args->evlist->threads) < 0)
			goto out_delete_evlist;
> +	}
> +
> +	if (perf_evlist__mmap(args->evlist, UINT_MAX))
		goto out_delete_evlist;
> +
> +	evlist__for_each_entry(args->evlist, counter) {
> +		if (perf_evsel__enable(counter))
			goto out_delete_evlist;
> +	}
> +
> +	if (pthread_create(&poll_thread, NULL, bpf_poll_thread, args))
		goto out_delete_evlist;	
> +
> +	return 0;
out_delete_evlist:
	perf_evlist__delete(args->evlist);
	args->evlist = NULL;

	return -1;
> +}
> +
> +void bpf_event__stop_polling_thread(struct bpf_event_poll_args *args)
> +{
> +	pthread_join(poll_thread, NULL);
> +	perf_evlist__exit(args->evlist);
> +}
> diff --git a/tools/perf/util/bpf-event.h b/tools/perf/util/bpf-event.h
> index c4f0f1395ea5..61914827c1e3 100644
> --- a/tools/perf/util/bpf-event.h
> +++ b/tools/perf/util/bpf-event.h
> @@ -12,12 +12,17 @@
>  #include <bpf/libbpf.h>
>  #include <linux/btf.h>
>  #include <linux/rbtree.h>
> +#include <pthread.h>
> +#include <api/fd/array.h>
>  #include "event.h"
>  
>  struct machine;
>  union perf_event;
> +struct perf_env;
>  struct perf_sample;
>  struct record_opts;
> +struct evlist;
> +struct target;
>  
>  struct bpf_prog_info_node {
>  	struct bpf_prog_info_linear	*info_linear;
> @@ -31,6 +36,13 @@ struct btf_node {
>  	char		data[];
>  };
>  
> +struct bpf_event_poll_args {
> +	struct perf_env		*env;
> +	struct perf_evlist	*evlist;
> +	struct target		*target;
> +	volatile int		*done;
> +};
> +
>  #ifdef HAVE_LIBBPF_SUPPORT
>  int machine__process_bpf_event(struct machine *machine, union perf_event *event,
>  			       struct perf_sample *sample);
> @@ -39,6 +51,8 @@ int perf_event__synthesize_bpf_events(struct perf_session *session,
>  				      perf_event__handler_t process,
>  				      struct machine *machine,
>  				      struct record_opts *opts);
> +int bpf_event__start_polling_thread(struct bpf_event_poll_args *args);
> +void bpf_event__stop_polling_thread(struct bpf_event_poll_args *args);
>  #else
>  static inline int machine__process_bpf_event(struct machine *machine __maybe_unused,
>  					     union perf_event *event __maybe_unused,
> @@ -54,5 +68,13 @@ static inline int perf_event__synthesize_bpf_events(struct perf_session *session
>  {
>  	return 0;
>  }
> +
> +static inline int bpf_event__start_polling_thread(struct bpf_event_poll_args *args __maybe_unused)
> +{
> +	return 0;
> +}
> +void bpf_event__stop_polling_thread(struct bpf_event_poll_args *args __maybe_unused)
> +{
> +}
>  #endif // HAVE_LIBBPF_SUPPORT
>  #endif
> diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
> index 8c902276d4b4..612c079579ce 100644
> --- a/tools/perf/util/evlist.c
> +++ b/tools/perf/util/evlist.c
> @@ -271,6 +271,26 @@ int perf_evlist__add_dummy(struct perf_evlist *evlist)
>  	return 0;
>  }
>  
> +int perf_evlist__add_bpf_tracker(struct perf_evlist *evlist)
> +{
> +	struct perf_event_attr attr = {
> +		.type	          = PERF_TYPE_SOFTWARE,
> +		.config           = PERF_COUNT_SW_DUMMY,
> +		.watermark        = 1,
> +		.bpf_event        = 1,
> +		.wakeup_watermark = 1,
> +		.size	   = sizeof(attr), /* to capture ABI version */
> +	};
> +	struct perf_evsel *evsel = perf_evsel__new_idx(&attr,
> +						       evlist->nr_entries);
> +
> +	if (evsel == NULL)
> +		return -ENOMEM;
> +
> +	perf_evlist__add(evlist, evsel);

You could use:

	struct perf_evlist *evlist = perf_evlist__new_dummy();
	if (evlist != NULL) {
		struct perf_evsel *evsel == perf_evlist__first(evlist);
		evsel->attr.bpf_event = evsel->attr.watermark = evsel->attr.wakeup_watermark = 1;
		return 0;
	}
	return -1;

Because in this case all you'll have in this evlist is the bpf tracker,
right? The add_bpf_tracker would be handy if we would want to have a
pre-existing evlist with some other events and wanted to add a bpf
tracker, no?

- Arnaldo

> +	return 0;
> +}
> +
>  static int perf_evlist__add_attrs(struct perf_evlist *evlist,
>  				  struct perf_event_attr *attrs, size_t nr_attrs)
>  {
> diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h
> index 868294491194..a2d22715188e 100644
> --- a/tools/perf/util/evlist.h
> +++ b/tools/perf/util/evlist.h
> @@ -84,6 +84,8 @@ int __perf_evlist__add_default_attrs(struct perf_evlist *evlist,
>  
>  int perf_evlist__add_dummy(struct perf_evlist *evlist);
>  
> +int perf_evlist__add_bpf_tracker(struct perf_evlist *evlist);
> +
>  int perf_evlist__add_newtp(struct perf_evlist *evlist,
>  			   const char *sys, const char *name, void *handler);
>  
> -- 
> 2.17.1
Song Liu Feb. 15, 2019, 5:13 p.m. UTC | #2
> On Feb 15, 2019, at 6:41 AM, Arnaldo Carvalho de Melo <acme@redhat.com> wrote:
> 
> Em Thu, Feb 14, 2019 at 04:00:45PM -0800, Song Liu escreveu:
>> To annotate bpf programs in perf, it is necessary to save information in
>> bpf_prog_info and btf. For short living bpf program, it is necessary to
>> save these information before it is unloaded.
>> 
>> This patch saves these information in a separate thread. This thread
>> creates its own evlist, that only tracks bpf events. This evlists uses
>> ring buffer with very low watermark for lower latency. When bpf load
>> events are received, this thread tries to gather information via sys_bpf
>> and save it in perf_env.
>> 
>> Signed-off-by: Song Liu <songliubraving@fb.com>
>> ---
>> tools/perf/builtin-record.c |  13 ++++
>> tools/perf/builtin-top.c    |  12 ++++
>> tools/perf/util/bpf-event.c | 129 ++++++++++++++++++++++++++++++++++++
>> tools/perf/util/bpf-event.h |  22 ++++++
>> tools/perf/util/evlist.c    |  20 ++++++
>> tools/perf/util/evlist.h    |   2 +
>> 6 files changed, 198 insertions(+)
>> 
>> diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
>> index 2355e0a9eda0..46abb44aaaab 100644
>> --- a/tools/perf/builtin-record.c
>> +++ b/tools/perf/builtin-record.c
>> @@ -1106,6 +1106,8 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
>> 	struct perf_data *data = &rec->data;
>> 	struct perf_session *session;
>> 	bool disabled = false, draining = false;
>> +	struct bpf_event_poll_args poll_args;
>> +	bool bpf_thread_running = false;
>> 	int fd;
>> 
>> 	atexit(record__sig_exit);
>> @@ -1206,6 +1208,14 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
>> 		goto out_child;
>> 	}
>> 
>> +	if (rec->opts.bpf_event) {
>> +		poll_args.env = &session->header.env;
>> +		poll_args.target = &rec->opts.target;
>> +		poll_args.done = &done;
>> +		if (bpf_event__start_polling_thread(&poll_args) == 0)
>> +			bpf_thread_running = true;
>> +	}
>> +
>> 	err = record__synthesize(rec, false);
>> 	if (err < 0)
>> 		goto out_child;
>> @@ -1456,6 +1466,9 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
>> 
>> out_delete_session:
>> 	perf_session__delete(session);
>> +
>> +	if (bpf_thread_running)
>> +		bpf_event__stop_polling_thread(&poll_args);
>> 	return status;
>> }
>> 
>> diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
>> index 5271d7211b9c..2586ee081967 100644
>> --- a/tools/perf/builtin-top.c
>> +++ b/tools/perf/builtin-top.c
>> @@ -1524,10 +1524,12 @@ int cmd_top(int argc, const char **argv)
>> 			"number of thread to run event synthesize"),
>> 	OPT_END()
>> 	};
>> +	struct bpf_event_poll_args poll_args;
>> 	const char * const top_usage[] = {
>> 		"perf top [<options>]",
>> 		NULL
>> 	};
>> +	bool bpf_thread_running = false;
>> 	int status = hists__init();
>> 
>> 	if (status < 0)
>> @@ -1652,8 +1654,18 @@ int cmd_top(int argc, const char **argv)
>> 		signal(SIGWINCH, winch_sig);
>> 	}
>> 
>> +	if (top.record_opts.bpf_event) {
>> +		poll_args.env = &perf_env;
>> +		poll_args.target = target;
>> +		poll_args.done = &done;
>> +		if (bpf_event__start_polling_thread(&poll_args) == 0)
>> +			bpf_thread_running = true;
>> +	}
>> 	status = __cmd_top(&top);
>> 
>> +	if (bpf_thread_running)
>> +		bpf_event__stop_polling_thread(&poll_args);
>> +
>> out_delete_evlist:
>> 	perf_evlist__delete(top.evlist);
>> 
>> diff --git a/tools/perf/util/bpf-event.c b/tools/perf/util/bpf-event.c
>> index 4f347d61ed96..0caf137c515b 100644
>> --- a/tools/perf/util/bpf-event.c
>> +++ b/tools/perf/util/bpf-event.c
>> @@ -8,6 +8,7 @@
>> #include "machine.h"
>> #include "env.h"
>> #include "session.h"
>> +#include "evlist.h"
>> 
>> #define ptr_to_u64(ptr)    ((__u64)(unsigned long)(ptr))
>> 
>> @@ -316,3 +317,131 @@ int perf_event__synthesize_bpf_events(struct perf_session *session,
>> 	free(event);
>> 	return err;
>> }
>> +
>> +static void perf_env_add_bpf_info(struct perf_env *env, u32 id)
>> +{
>> +	struct bpf_prog_info_linear *info_linear;
>> +	struct bpf_prog_info_node *info_node;
>> +	struct btf *btf = NULL;
>> +	u64 arrays;
>> +	u32 btf_id;
>> +	int fd;
>> +
>> +	fd = bpf_prog_get_fd_by_id(id);
>> +	if (fd < 0)
>> +		return;
>> +
>> +	arrays = 1UL << BPF_PROG_INFO_JITED_KSYMS;
>> +	arrays |= 1UL << BPF_PROG_INFO_JITED_FUNC_LENS;
>> +	arrays |= 1UL << BPF_PROG_INFO_FUNC_INFO;
>> +	arrays |= 1UL << BPF_PROG_INFO_PROG_TAGS;
>> +	arrays |= 1UL << BPF_PROG_INFO_JITED_INSNS;
>> +	arrays |= 1UL << BPF_PROG_INFO_LINE_INFO;
>> +	arrays |= 1UL << BPF_PROG_INFO_JITED_LINE_INFO;
>> +
>> +	info_linear = bpf_program__get_prog_info_linear(fd, arrays);
>> +	if (IS_ERR_OR_NULL(info_linear)) {
>> +		pr_debug("%s: failed to get BPF program info. aborting\n", __func__);
>> +		goto out;
>> +	}
>> +
>> +	btf_id = info_linear->info.btf_id;
>> +
>> +	info_node = malloc(sizeof(struct bpf_prog_info_node));
>> +	if (info_node) {
>> +		info_node->info_linear = info_linear;
>> +		perf_env__insert_bpf_prog_info(env, info_node);
>> +	} else
>> +		free(info_linear);
>> +
>> +	if (btf_id == 0)
>> +		goto out;
>> +
>> +	if (btf__get_from_id(btf_id, &btf)) {
>> +		pr_debug("%s: failed to get BTF of id %u, aborting\n",
>> +			 __func__, btf_id);
>> +		goto out;
>> +	}
>> +	perf_fetch_btf(env, btf_id, btf);
>> +
>> +out:
>> +	free(btf);
>> +	close(fd);
>> +}
>> +
>> +static void *bpf_poll_thread(void *arg)
>> +{
>> +	struct bpf_event_poll_args *args = arg;
>> +	int i;
>> +
>> +	while (!*(args->done)) {
>> +		perf_evlist__poll(args->evlist, 1000);
>> +
>> +		for (i = 0; i < args->evlist->nr_mmaps; i++) {
>> +			struct perf_mmap *map = &args->evlist->mmap[i];
>> +			union perf_event *event;
>> +
>> +			if (perf_mmap__read_init(map))
>> +				continue;
>> +			while ((event = perf_mmap__read_event(map)) != NULL) {
>> +				pr_debug("processing vip event of type %d\n",
>> +					 event->header.type);
>> +				switch (event->header.type) {
>> +				case PERF_RECORD_BPF_EVENT:
>> +					if (event->bpf_event.type != PERF_BPF_EVENT_PROG_LOAD)
>> +						break;
>> +					perf_env_add_bpf_info(args->env, event->bpf_event.id);
>> +					break;
>> +				default:
>> +					break;
>> +				}
>> +				perf_mmap__consume(map);
>> +			}
>> +			perf_mmap__read_done(map);
>> +		}
>> +	}
>> +	return NULL;
>> +}
>> +
>> +pthread_t poll_thread;
>> +
>> +int bpf_event__start_polling_thread(struct bpf_event_poll_args *args)
>> +{
>> +	struct perf_evsel *counter;
>> +
>> +	args->evlist = perf_evlist__new();
>> +
>> +	if (args->evlist == NULL)
>> +		return -1;
>> +
>> +	if (perf_evlist__create_maps(args->evlist, args->target))
> 		goto out_delete_evlist;
>> +
>> +	if (perf_evlist__add_bpf_tracker(args->evlist))
> 		goto out_delete_evlist;
>> +
>> +	evlist__for_each_entry(args->evlist, counter) {
>> +		if (perf_evsel__open(counter, args->evlist->cpus,
>> +				     args->evlist->threads) < 0)
> 			goto out_delete_evlist;
>> +	}
>> +
>> +	if (perf_evlist__mmap(args->evlist, UINT_MAX))
> 		goto out_delete_evlist;
>> +
>> +	evlist__for_each_entry(args->evlist, counter) {
>> +		if (perf_evsel__enable(counter))
> 			goto out_delete_evlist;
>> +	}
>> +
>> +	if (pthread_create(&poll_thread, NULL, bpf_poll_thread, args))
> 		goto out_delete_evlist;	
>> +
>> +	return 0;
> out_delete_evlist:
> 	perf_evlist__delete(args->evlist);
> 	args->evlist = NULL;
> 
> 	return -1;
>> +}
>> +
>> +void bpf_event__stop_polling_thread(struct bpf_event_poll_args *args)
>> +{
>> +	pthread_join(poll_thread, NULL);
>> +	perf_evlist__exit(args->evlist);
>> +}
>> diff --git a/tools/perf/util/bpf-event.h b/tools/perf/util/bpf-event.h
>> index c4f0f1395ea5..61914827c1e3 100644
>> --- a/tools/perf/util/bpf-event.h
>> +++ b/tools/perf/util/bpf-event.h
>> @@ -12,12 +12,17 @@
>> #include <bpf/libbpf.h>
>> #include <linux/btf.h>
>> #include <linux/rbtree.h>
>> +#include <pthread.h>
>> +#include <api/fd/array.h>
>> #include "event.h"
>> 
>> struct machine;
>> union perf_event;
>> +struct perf_env;
>> struct perf_sample;
>> struct record_opts;
>> +struct evlist;
>> +struct target;
>> 
>> struct bpf_prog_info_node {
>> 	struct bpf_prog_info_linear	*info_linear;
>> @@ -31,6 +36,13 @@ struct btf_node {
>> 	char		data[];
>> };
>> 
>> +struct bpf_event_poll_args {
>> +	struct perf_env		*env;
>> +	struct perf_evlist	*evlist;
>> +	struct target		*target;
>> +	volatile int		*done;
>> +};
>> +
>> #ifdef HAVE_LIBBPF_SUPPORT
>> int machine__process_bpf_event(struct machine *machine, union perf_event *event,
>> 			       struct perf_sample *sample);
>> @@ -39,6 +51,8 @@ int perf_event__synthesize_bpf_events(struct perf_session *session,
>> 				      perf_event__handler_t process,
>> 				      struct machine *machine,
>> 				      struct record_opts *opts);
>> +int bpf_event__start_polling_thread(struct bpf_event_poll_args *args);
>> +void bpf_event__stop_polling_thread(struct bpf_event_poll_args *args);
>> #else
>> static inline int machine__process_bpf_event(struct machine *machine __maybe_unused,
>> 					     union perf_event *event __maybe_unused,
>> @@ -54,5 +68,13 @@ static inline int perf_event__synthesize_bpf_events(struct perf_session *session
>> {
>> 	return 0;
>> }
>> +
>> +static inline int bpf_event__start_polling_thread(struct bpf_event_poll_args *args __maybe_unused)
>> +{
>> +	return 0;
>> +}
>> +void bpf_event__stop_polling_thread(struct bpf_event_poll_args *args __maybe_unused)
>> +{
>> +}
>> #endif // HAVE_LIBBPF_SUPPORT
>> #endif
>> diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
>> index 8c902276d4b4..612c079579ce 100644
>> --- a/tools/perf/util/evlist.c
>> +++ b/tools/perf/util/evlist.c
>> @@ -271,6 +271,26 @@ int perf_evlist__add_dummy(struct perf_evlist *evlist)
>> 	return 0;
>> }
>> 
>> +int perf_evlist__add_bpf_tracker(struct perf_evlist *evlist)
>> +{
>> +	struct perf_event_attr attr = {
>> +		.type	          = PERF_TYPE_SOFTWARE,
>> +		.config           = PERF_COUNT_SW_DUMMY,
>> +		.watermark        = 1,
>> +		.bpf_event        = 1,
>> +		.wakeup_watermark = 1,
>> +		.size	   = sizeof(attr), /* to capture ABI version */
>> +	};
>> +	struct perf_evsel *evsel = perf_evsel__new_idx(&attr,
>> +						       evlist->nr_entries);
>> +
>> +	if (evsel == NULL)
>> +		return -ENOMEM;
>> +
>> +	perf_evlist__add(evlist, evsel);
> 
> You could use:
> 
> 	struct perf_evlist *evlist = perf_evlist__new_dummy();
> 	if (evlist != NULL) {
> 		struct perf_evsel *evsel == perf_evlist__first(evlist);
> 		evsel->attr.bpf_event = evsel->attr.watermark = evsel->attr.wakeup_watermark = 1;
> 		return 0;
> 	}
> 	return -1;

This looks cleaner. Let me fix in next version. 

> 
> Because in this case all you'll have in this evlist is the bpf tracker,
> right? The add_bpf_tracker would be handy if we would want to have a
> pre-existing evlist with some other events and wanted to add a bpf
> tracker, no?

I think all we need is a side-band evlist instead of the main evlist. May
be we should call it side-band evlist, and make it more generic?

Thanks,
Song

> 
> - Arnaldo
> 
>> +	return 0;
>> +}
>> +
>> static int perf_evlist__add_attrs(struct perf_evlist *evlist,
>> 				  struct perf_event_attr *attrs, size_t nr_attrs)
>> {
>> diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h
>> index 868294491194..a2d22715188e 100644
>> --- a/tools/perf/util/evlist.h
>> +++ b/tools/perf/util/evlist.h
>> @@ -84,6 +84,8 @@ int __perf_evlist__add_default_attrs(struct perf_evlist *evlist,
>> 
>> int perf_evlist__add_dummy(struct perf_evlist *evlist);
>> 
>> +int perf_evlist__add_bpf_tracker(struct perf_evlist *evlist);
>> +
>> int perf_evlist__add_newtp(struct perf_evlist *evlist,
>> 			   const char *sys, const char *name, void *handler);
>> 
>> -- 
>> 2.17.1
Arnaldo Carvalho de Melo Feb. 15, 2019, 6:09 p.m. UTC | #3
Em Fri, Feb 15, 2019 at 05:13:01PM +0000, Song Liu escreveu:
> > On Feb 15, 2019, at 6:41 AM, Arnaldo Carvalho de Melo <acme@redhat.com> wrote:
> > Em Thu, Feb 14, 2019 at 04:00:45PM -0800, Song Liu escreveu:
> >> +pthread_t poll_thread;
> >> +
> >> +int bpf_event__start_polling_thread(struct bpf_event_poll_args *args)
> >> +{
> >> +	struct perf_evsel *counter;
> >> +
> >> +	args->evlist = perf_evlist__new();
> >> +
> >> +	if (args->evlist == NULL)
> >> +		return -1;
> >> +
> >> +	if (perf_evlist__create_maps(args->evlist, args->target))
> > 		goto out_delete_evlist;
> >> +
> >> +	if (perf_evlist__add_bpf_tracker(args->evlist))
> > 		goto out_delete_evlist;
> >> +
> >> +	evlist__for_each_entry(args->evlist, counter) {
> >> +		if (perf_evsel__open(counter, args->evlist->cpus,
> >> +				     args->evlist->threads) < 0)
> > 			goto out_delete_evlist;
> >> +	}
> >> +
> >> +	if (perf_evlist__mmap(args->evlist, UINT_MAX))
> > 		goto out_delete_evlist;
> >> +
> >> +	evlist__for_each_entry(args->evlist, counter) {
> >> +		if (perf_evsel__enable(counter))
> > 			goto out_delete_evlist;
> >> +	}
> >> +
> >> +	if (pthread_create(&poll_thread, NULL, bpf_poll_thread, args))
> > 		goto out_delete_evlist;	
> >> +
> >> +	return 0;
> > out_delete_evlist:
> > 	perf_evlist__delete(args->evlist);
> > 	args->evlist = NULL;

Have you seen the error handling suggestion above?

> >> +int perf_evlist__add_bpf_tracker(struct perf_evlist *evlist)
> >> +{
> >> +	struct perf_event_attr attr = {
> >> +		.type	          = PERF_TYPE_SOFTWARE,
> >> +		.config           = PERF_COUNT_SW_DUMMY,
> >> +		.watermark        = 1,
> >> +		.bpf_event        = 1,
> >> +		.wakeup_watermark = 1,
> >> +		.size	   = sizeof(attr), /* to capture ABI version */
> >> +	};
> >> +	struct perf_evsel *evsel = perf_evsel__new_idx(&attr,
> >> +						       evlist->nr_entries);
> >> +
> >> +	if (evsel == NULL)
> >> +		return -ENOMEM;
> >> +
> >> +	perf_evlist__add(evlist, evsel);

> > You could use:
 
> > 	struct perf_evlist *evlist = perf_evlist__new_dummy();
> > 	if (evlist != NULL) {
> > 		struct perf_evsel *evsel == perf_evlist__first(evlist);
> > 		evsel->attr.bpf_event = evsel->attr.watermark = evsel->attr.wakeup_watermark = 1;
> > 		return 0;
> > 	}
> > 	return -1;
 
> This looks cleaner. Let me fix in next version. 

> > Because in this case all you'll have in this evlist is the bpf tracker,
> > right? The add_bpf_tracker would be handy if we would want to have a
> > pre-existing evlist with some other events and wanted to add a bpf
> > tracker, no?

> I think all we need is a side-band evlist instead of the main evlist. May
> be we should call it side-band evlist, and make it more generic?

Sure, you could for instance have something like:

	struct perf_event_attr attr = {
		.watermark        = 1,
		.bpf_event        = 1,
		.wakeup_watermark = 1,
	}
 	struct perf_evlist *evlist = perf_evlist__new_side_band(&attr);


And the other details will be set by it, i.e. the .config

	.type	          = PERF_TYPE_SOFTWARE,
	.config           = PERF_COUNT_SW_DUMMY,
	.size	   = sizeof(attr), /* to capture ABI version */

And the idx arg.

- Arnaldo
Song Liu Feb. 15, 2019, 6:13 p.m. UTC | #4
> On Feb 15, 2019, at 10:09 AM, Arnaldo Carvalho de Melo <acme@redhat.com> wrote:
> 
> Em Fri, Feb 15, 2019 at 05:13:01PM +0000, Song Liu escreveu:
>>> On Feb 15, 2019, at 6:41 AM, Arnaldo Carvalho de Melo <acme@redhat.com> wrote:
>>> Em Thu, Feb 14, 2019 at 04:00:45PM -0800, Song Liu escreveu:
>>>> +pthread_t poll_thread;
>>>> +
>>>> +int bpf_event__start_polling_thread(struct bpf_event_poll_args *args)
>>>> +{
>>>> +	struct perf_evsel *counter;
>>>> +
>>>> +	args->evlist = perf_evlist__new();
>>>> +
>>>> +	if (args->evlist == NULL)
>>>> +		return -1;
>>>> +
>>>> +	if (perf_evlist__create_maps(args->evlist, args->target))
>>> 		goto out_delete_evlist;
>>>> +
>>>> +	if (perf_evlist__add_bpf_tracker(args->evlist))
>>> 		goto out_delete_evlist;
>>>> +
>>>> +	evlist__for_each_entry(args->evlist, counter) {
>>>> +		if (perf_evsel__open(counter, args->evlist->cpus,
>>>> +				     args->evlist->threads) < 0)
>>> 			goto out_delete_evlist;
>>>> +	}
>>>> +
>>>> +	if (perf_evlist__mmap(args->evlist, UINT_MAX))
>>> 		goto out_delete_evlist;
>>>> +
>>>> +	evlist__for_each_entry(args->evlist, counter) {
>>>> +		if (perf_evsel__enable(counter))
>>> 			goto out_delete_evlist;
>>>> +	}
>>>> +
>>>> +	if (pthread_create(&poll_thread, NULL, bpf_poll_thread, args))
>>> 		goto out_delete_evlist;	
>>>> +
>>>> +	return 0;
>>> out_delete_evlist:
>>> 	perf_evlist__delete(args->evlist);
>>> 	args->evlist = NULL;
> 
> Have you seen the error handling suggestion above?

Yes! I will include these changes, as always. :)

> 
>>>> +int perf_evlist__add_bpf_tracker(struct perf_evlist *evlist)
>>>> +{
>>>> +	struct perf_event_attr attr = {
>>>> +		.type	          = PERF_TYPE_SOFTWARE,
>>>> +		.config           = PERF_COUNT_SW_DUMMY,
>>>> +		.watermark        = 1,
>>>> +		.bpf_event        = 1,
>>>> +		.wakeup_watermark = 1,
>>>> +		.size	   = sizeof(attr), /* to capture ABI version */
>>>> +	};
>>>> +	struct perf_evsel *evsel = perf_evsel__new_idx(&attr,
>>>> +						       evlist->nr_entries);
>>>> +
>>>> +	if (evsel == NULL)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	perf_evlist__add(evlist, evsel);
> 
>>> You could use:
> 
>>> 	struct perf_evlist *evlist = perf_evlist__new_dummy();
>>> 	if (evlist != NULL) {
>>> 		struct perf_evsel *evsel == perf_evlist__first(evlist);
>>> 		evsel->attr.bpf_event = evsel->attr.watermark = evsel->attr.wakeup_watermark = 1;
>>> 		return 0;
>>> 	}
>>> 	return -1;
> 
>> This looks cleaner. Let me fix in next version. 
> 
>>> Because in this case all you'll have in this evlist is the bpf tracker,
>>> right? The add_bpf_tracker would be handy if we would want to have a
>>> pre-existing evlist with some other events and wanted to add a bpf
>>> tracker, no?
> 
>> I think all we need is a side-band evlist instead of the main evlist. May
>> be we should call it side-band evlist, and make it more generic?
> 
> Sure, you could for instance have something like:
> 
> 	struct perf_event_attr attr = {
> 		.watermark        = 1,
> 		.bpf_event        = 1,
> 		.wakeup_watermark = 1,
> 	}
> 	struct perf_evlist *evlist = perf_evlist__new_side_band(&attr);
> 
> 
> And the other details will be set by it, i.e. the .config
> 
> 	.type	          = PERF_TYPE_SOFTWARE,
> 	.config           = PERF_COUNT_SW_DUMMY,
> 	.size	   = sizeof(attr), /* to capture ABI version */
> 
> And the idx arg.
> 
> - Arnaldo

This looks good. Let me revise the patch in that direction.

Thanks,
Song
diff mbox series

Patch

diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index 2355e0a9eda0..46abb44aaaab 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -1106,6 +1106,8 @@  static int __cmd_record(struct record *rec, int argc, const char **argv)
 	struct perf_data *data = &rec->data;
 	struct perf_session *session;
 	bool disabled = false, draining = false;
+	struct bpf_event_poll_args poll_args;
+	bool bpf_thread_running = false;
 	int fd;
 
 	atexit(record__sig_exit);
@@ -1206,6 +1208,14 @@  static int __cmd_record(struct record *rec, int argc, const char **argv)
 		goto out_child;
 	}
 
+	if (rec->opts.bpf_event) {
+		poll_args.env = &session->header.env;
+		poll_args.target = &rec->opts.target;
+		poll_args.done = &done;
+		if (bpf_event__start_polling_thread(&poll_args) == 0)
+			bpf_thread_running = true;
+	}
+
 	err = record__synthesize(rec, false);
 	if (err < 0)
 		goto out_child;
@@ -1456,6 +1466,9 @@  static int __cmd_record(struct record *rec, int argc, const char **argv)
 
 out_delete_session:
 	perf_session__delete(session);
+
+	if (bpf_thread_running)
+		bpf_event__stop_polling_thread(&poll_args);
 	return status;
 }
 
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index 5271d7211b9c..2586ee081967 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -1524,10 +1524,12 @@  int cmd_top(int argc, const char **argv)
 			"number of thread to run event synthesize"),
 	OPT_END()
 	};
+	struct bpf_event_poll_args poll_args;
 	const char * const top_usage[] = {
 		"perf top [<options>]",
 		NULL
 	};
+	bool bpf_thread_running = false;
 	int status = hists__init();
 
 	if (status < 0)
@@ -1652,8 +1654,18 @@  int cmd_top(int argc, const char **argv)
 		signal(SIGWINCH, winch_sig);
 	}
 
+	if (top.record_opts.bpf_event) {
+		poll_args.env = &perf_env;
+		poll_args.target = target;
+		poll_args.done = &done;
+		if (bpf_event__start_polling_thread(&poll_args) == 0)
+			bpf_thread_running = true;
+	}
 	status = __cmd_top(&top);
 
+	if (bpf_thread_running)
+		bpf_event__stop_polling_thread(&poll_args);
+
 out_delete_evlist:
 	perf_evlist__delete(top.evlist);
 
diff --git a/tools/perf/util/bpf-event.c b/tools/perf/util/bpf-event.c
index 4f347d61ed96..0caf137c515b 100644
--- a/tools/perf/util/bpf-event.c
+++ b/tools/perf/util/bpf-event.c
@@ -8,6 +8,7 @@ 
 #include "machine.h"
 #include "env.h"
 #include "session.h"
+#include "evlist.h"
 
 #define ptr_to_u64(ptr)    ((__u64)(unsigned long)(ptr))
 
@@ -316,3 +317,131 @@  int perf_event__synthesize_bpf_events(struct perf_session *session,
 	free(event);
 	return err;
 }
+
+static void perf_env_add_bpf_info(struct perf_env *env, u32 id)
+{
+	struct bpf_prog_info_linear *info_linear;
+	struct bpf_prog_info_node *info_node;
+	struct btf *btf = NULL;
+	u64 arrays;
+	u32 btf_id;
+	int fd;
+
+	fd = bpf_prog_get_fd_by_id(id);
+	if (fd < 0)
+		return;
+
+	arrays = 1UL << BPF_PROG_INFO_JITED_KSYMS;
+	arrays |= 1UL << BPF_PROG_INFO_JITED_FUNC_LENS;
+	arrays |= 1UL << BPF_PROG_INFO_FUNC_INFO;
+	arrays |= 1UL << BPF_PROG_INFO_PROG_TAGS;
+	arrays |= 1UL << BPF_PROG_INFO_JITED_INSNS;
+	arrays |= 1UL << BPF_PROG_INFO_LINE_INFO;
+	arrays |= 1UL << BPF_PROG_INFO_JITED_LINE_INFO;
+
+	info_linear = bpf_program__get_prog_info_linear(fd, arrays);
+	if (IS_ERR_OR_NULL(info_linear)) {
+		pr_debug("%s: failed to get BPF program info. aborting\n", __func__);
+		goto out;
+	}
+
+	btf_id = info_linear->info.btf_id;
+
+	info_node = malloc(sizeof(struct bpf_prog_info_node));
+	if (info_node) {
+		info_node->info_linear = info_linear;
+		perf_env__insert_bpf_prog_info(env, info_node);
+	} else
+		free(info_linear);
+
+	if (btf_id == 0)
+		goto out;
+
+	if (btf__get_from_id(btf_id, &btf)) {
+		pr_debug("%s: failed to get BTF of id %u, aborting\n",
+			 __func__, btf_id);
+		goto out;
+	}
+	perf_fetch_btf(env, btf_id, btf);
+
+out:
+	free(btf);
+	close(fd);
+}
+
+static void *bpf_poll_thread(void *arg)
+{
+	struct bpf_event_poll_args *args = arg;
+	int i;
+
+	while (!*(args->done)) {
+		perf_evlist__poll(args->evlist, 1000);
+
+		for (i = 0; i < args->evlist->nr_mmaps; i++) {
+			struct perf_mmap *map = &args->evlist->mmap[i];
+			union perf_event *event;
+
+			if (perf_mmap__read_init(map))
+				continue;
+			while ((event = perf_mmap__read_event(map)) != NULL) {
+				pr_debug("processing vip event of type %d\n",
+					 event->header.type);
+				switch (event->header.type) {
+				case PERF_RECORD_BPF_EVENT:
+					if (event->bpf_event.type != PERF_BPF_EVENT_PROG_LOAD)
+						break;
+					perf_env_add_bpf_info(args->env, event->bpf_event.id);
+					break;
+				default:
+					break;
+				}
+				perf_mmap__consume(map);
+			}
+			perf_mmap__read_done(map);
+		}
+	}
+	return NULL;
+}
+
+pthread_t poll_thread;
+
+int bpf_event__start_polling_thread(struct bpf_event_poll_args *args)
+{
+	struct perf_evsel *counter;
+
+	args->evlist = perf_evlist__new();
+
+	if (args->evlist == NULL)
+		return -1;
+
+	if (perf_evlist__create_maps(args->evlist, args->target))
+		return -1;
+
+	if (perf_evlist__add_bpf_tracker(args->evlist))
+		return -1;
+
+	evlist__for_each_entry(args->evlist, counter) {
+		if (perf_evsel__open(counter, args->evlist->cpus,
+				     args->evlist->threads) < 0)
+			return -1;
+	}
+
+	if (perf_evlist__mmap(args->evlist, UINT_MAX))
+		return -1;
+
+	evlist__for_each_entry(args->evlist, counter) {
+		if (perf_evsel__enable(counter))
+			return -1;
+	}
+
+	if (pthread_create(&poll_thread, NULL, bpf_poll_thread, args))
+		return -1;
+
+	return 0;
+}
+
+void bpf_event__stop_polling_thread(struct bpf_event_poll_args *args)
+{
+	pthread_join(poll_thread, NULL);
+	perf_evlist__exit(args->evlist);
+}
diff --git a/tools/perf/util/bpf-event.h b/tools/perf/util/bpf-event.h
index c4f0f1395ea5..61914827c1e3 100644
--- a/tools/perf/util/bpf-event.h
+++ b/tools/perf/util/bpf-event.h
@@ -12,12 +12,17 @@ 
 #include <bpf/libbpf.h>
 #include <linux/btf.h>
 #include <linux/rbtree.h>
+#include <pthread.h>
+#include <api/fd/array.h>
 #include "event.h"
 
 struct machine;
 union perf_event;
+struct perf_env;
 struct perf_sample;
 struct record_opts;
+struct evlist;
+struct target;
 
 struct bpf_prog_info_node {
 	struct bpf_prog_info_linear	*info_linear;
@@ -31,6 +36,13 @@  struct btf_node {
 	char		data[];
 };
 
+struct bpf_event_poll_args {
+	struct perf_env		*env;
+	struct perf_evlist	*evlist;
+	struct target		*target;
+	volatile int		*done;
+};
+
 #ifdef HAVE_LIBBPF_SUPPORT
 int machine__process_bpf_event(struct machine *machine, union perf_event *event,
 			       struct perf_sample *sample);
@@ -39,6 +51,8 @@  int perf_event__synthesize_bpf_events(struct perf_session *session,
 				      perf_event__handler_t process,
 				      struct machine *machine,
 				      struct record_opts *opts);
+int bpf_event__start_polling_thread(struct bpf_event_poll_args *args);
+void bpf_event__stop_polling_thread(struct bpf_event_poll_args *args);
 #else
 static inline int machine__process_bpf_event(struct machine *machine __maybe_unused,
 					     union perf_event *event __maybe_unused,
@@ -54,5 +68,13 @@  static inline int perf_event__synthesize_bpf_events(struct perf_session *session
 {
 	return 0;
 }
+
+static inline int bpf_event__start_polling_thread(struct bpf_event_poll_args *args __maybe_unused)
+{
+	return 0;
+}
+void bpf_event__stop_polling_thread(struct bpf_event_poll_args *args __maybe_unused)
+{
+}
 #endif // HAVE_LIBBPF_SUPPORT
 #endif
diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
index 8c902276d4b4..612c079579ce 100644
--- a/tools/perf/util/evlist.c
+++ b/tools/perf/util/evlist.c
@@ -271,6 +271,26 @@  int perf_evlist__add_dummy(struct perf_evlist *evlist)
 	return 0;
 }
 
+int perf_evlist__add_bpf_tracker(struct perf_evlist *evlist)
+{
+	struct perf_event_attr attr = {
+		.type	          = PERF_TYPE_SOFTWARE,
+		.config           = PERF_COUNT_SW_DUMMY,
+		.watermark        = 1,
+		.bpf_event        = 1,
+		.wakeup_watermark = 1,
+		.size	   = sizeof(attr), /* to capture ABI version */
+	};
+	struct perf_evsel *evsel = perf_evsel__new_idx(&attr,
+						       evlist->nr_entries);
+
+	if (evsel == NULL)
+		return -ENOMEM;
+
+	perf_evlist__add(evlist, evsel);
+	return 0;
+}
+
 static int perf_evlist__add_attrs(struct perf_evlist *evlist,
 				  struct perf_event_attr *attrs, size_t nr_attrs)
 {
diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h
index 868294491194..a2d22715188e 100644
--- a/tools/perf/util/evlist.h
+++ b/tools/perf/util/evlist.h
@@ -84,6 +84,8 @@  int __perf_evlist__add_default_attrs(struct perf_evlist *evlist,
 
 int perf_evlist__add_dummy(struct perf_evlist *evlist);
 
+int perf_evlist__add_bpf_tracker(struct perf_evlist *evlist);
+
 int perf_evlist__add_newtp(struct perf_evlist *evlist,
 			   const char *sys, const char *name, void *handler);