Re: [PATCH 01/12] libtracefs: Add new internal APIs for dynamic events

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, 28 Oct 2021 15:08:56 +0300
"Tzvetomir Stoyanov (VMware)" <tz.stoyanov@xxxxxxxxx> wrote:

> Ftrace supports dynamic events, created by the user - kprobes, uprobes,
> eprobes and synthetic events. There are two interfaces for managing
> these events - new common "dynamic_events" file and event specific
> "kprobe_events", "uprobe_events", "synthetic_events" files. The
> configuration syntax for all dynamic events is almost the same.
> To simplify support of dynamic events in thw tracefs library, a new
> internal helper layer is implemented. It handles both configuration
> interfaces - the common "dynamic_events" file is preferred, if
> available. On the old kernels, where this file is missing, the event
> specific files are used. The new helper layer can be used to create,
> delete and get ftrace dynamic events form any type. Most of the APIs
> are internal, not exposed to the library users. Only one API is exposed
> publicly:
>  tracefs_dynevent_list_free()
> This new logic is designed to be used internally within the library,
> from the APIs that implement kprobes, uprobes, eprobes and synthetic
> events support.
> 
> Signed-off-by: Tzvetomir Stoyanov (VMware) <tz.stoyanov@xxxxxxxxx>
> ---
>  include/tracefs-local.h |  34 +++
>  include/tracefs.h       |   3 +
>  src/Makefile            |   1 +
>  src/tracefs-dynevents.c | 463 ++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 501 insertions(+)
>  create mode 100644 src/tracefs-dynevents.c
> 
> diff --git a/include/tracefs-local.h b/include/tracefs-local.h
> index 684eccf..f15896f 100644
> --- a/include/tracefs-local.h
> +++ b/include/tracefs-local.h
> @@ -94,4 +94,38 @@ int synth_add_start_field(struct tracefs_synth *synth,
>  			  const char *start_field,
>  			  const char *name,
>  			  enum tracefs_hist_key_type type);
> +
> +/* Internal interface for ftrace dynamic events */
> +#define DYNEVENT_ADD_BIT(M, B)		((M) |= (1ULL<<(B)))
> +#define DYNEVENT_CHECK_BIT(M, B)	((M)&(1ULL<<(B)))

The above macros are generic enough that they don't need DYNEVENT in the
name. Just call them:

#define SET_BIT(M, B)		do { (M) |= (1ULL << (B)); } while (0)
#define TEST_BIT(M, B)		((M) & (1ULL << (B)))

And might as well add for completeness:

#define CLEAR_BIT(M,B)		do { (M) &= (1ULL << (B)); } while (0)

It is common to use the terminology of "set,clear,test" for bitmasks. Also,
it is best to add the "do { } while (0)" notation for code, as that helps
with some side effects macros can have.


> +
> +enum trace_dynevent_type {
> +	TRACE_DYNEVENT_KPROBE = 0,

No real need for the "= 0" but I'm OK if you want to leave it in. That's
because the C standard states that the first enum is zero.

> +	TRACE_DYNEVENT_KRETPROBE,
> +	TRACE_DYNEVENT_UPROBE,
> +	TRACE_DYNEVENT_URETPROBE,
> +	TRACE_DYNEVENT_EPROBE,
> +	TRACE_DYNEVENT_SYNTH,
> +	TRACE_DYNEVENT_MAX,
> +};
> +
> +struct tracefs_dynevent {
> +	char *trace_file;
> +	char *prefix;
> +	char *system;
> +	char *event;
> +	char *address;
> +	char *format;
> +	enum trace_dynevent_type type;
> +};
> +
> +struct tracefs_dynevent *
> +dynevent_alloc(enum trace_dynevent_type type, const char *system,
> +	       const char *event, const char *address, const char *format);
> +void dynevent_free(struct tracefs_dynevent *devent);
> +int dynevent_create(struct tracefs_dynevent *devent);
> +int dynevent_destroy(struct tracefs_dynevent *devent);
> +int dynevent_get_all(unsigned long type_mask, const char *system,
> +		     struct tracefs_dynevent ***ret_events);

I usually prefer to have a list returned, and NULL on error, instead of
passing it in by reference. The "***" gets a bit out of hand.


> +
>  #endif /* _TRACE_FS_LOCAL_H */
> diff --git a/include/tracefs.h b/include/tracefs.h
> index a2cda30..4020551 100644
> --- a/include/tracefs.h
> +++ b/include/tracefs.h
> @@ -238,6 +238,9 @@ ssize_t tracefs_trace_pipe_stream(int fd, struct tracefs_instance *instance, int
>  ssize_t tracefs_trace_pipe_print(struct tracefs_instance *instance, int flags);
>  void tracefs_trace_pipe_stop(struct tracefs_instance *instance);
>  
> +struct tracefs_dynevent;
> +void tracefs_dynevent_list_free(struct tracefs_dynevent ***events);

Why are you passing in the address?

Just use: struct tracefs_dynevent **events

If it is to be consistent with the get_all() then that's another reason to
not pass it in as a parameter. That would be like:

 int malloc(int size, void **m);

and

  void free(void **m)


> +
>  enum tracefs_kprobe_type {
>  	TRACEFS_ALL_KPROBES,
>  	TRACEFS_KPROBE,
> diff --git a/src/Makefile b/src/Makefile
> index 4e38d98..99cd7da 100644
> --- a/src/Makefile
> +++ b/src/Makefile
> @@ -11,6 +11,7 @@ OBJS += tracefs-marker.o
>  OBJS += tracefs-kprobes.o
>  OBJS += tracefs-hist.o
>  OBJS += tracefs-filter.o
> +OBJS += tracefs-dynevents.o
>  
>  # Order matters for the the three below
>  OBJS += sqlhist-lex.o
> diff --git a/src/tracefs-dynevents.c b/src/tracefs-dynevents.c
> new file mode 100644
> index 0000000..68ee95a
> --- /dev/null
> +++ b/src/tracefs-dynevents.c
> @@ -0,0 +1,463 @@
> +// SPDX-License-Identifier: LGPL-2.1
> +/*
> + * Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@xxxxxxxxxxx>
> + *
> + * Updates:
> + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@xxxxxxxxx>
> + *
> + */
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <dirent.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <limits.h>
> +
> +#include "tracefs.h"
> +#include "tracefs-local.h"
> +
> +#define DYNEVENTS_EVENTS "dynamic_events"
> +#define KPROBE_EVENTS "kprobe_events"
> +#define UPROBE_EVENTS "uprobe_events"
> +#define SYNTH_EVENTS "synthetic_events"
> +#define DYNEVENTS_DEFAULT_GROUP "dynamic"
> +#define RETPROBE_DEFAUL_PREFIX	"r:"
> +
> +struct dyn_events_desc;
> +static int dyn_generic_parse(struct dyn_events_desc *,
> +			     const char *, char *, struct tracefs_dynevent **);
> +static int dyn_synth_parse(struct dyn_events_desc *,
> +			   const char *, char *, struct tracefs_dynevent **);
> +static int dyn_generic_del(struct dyn_events_desc *, struct tracefs_dynevent *);
> +static int dyn_synth_del(struct dyn_events_desc *, struct tracefs_dynevent *);
> +
> +struct dyn_events_desc {
> +	enum trace_dynevent_type type;
> +	const char *file;
> +	const char *prefix;
> +	int (*dyn_events_del)(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn);
> +	int (*dyn_events_parse)(struct dyn_events_desc *desc, const char *group,
> +				char *line, struct tracefs_dynevent **ret_dyn);
> +} dynevents[TRACE_DYNEVENT_MAX] = {

You don't need the TRACE_DYNEVENT_MAX. Just have:

 } dynevents[] = {

And the compiler will fill in the rest.

We could add somewhere in the code:

	BUILD_BUG_ON(ARRAY_SIZE(dynevents) != TRACE_DYNEVENT_MAX);

to make sure all are accounted for.

> +	{TRACE_DYNEVENT_KPROBE, NULL, "p:", dyn_generic_del, dyn_generic_parse},
> +	{TRACE_DYNEVENT_KRETPROBE, NULL, "r", dyn_generic_del, dyn_generic_parse},
> +	{TRACE_DYNEVENT_UPROBE, NULL, "p:", dyn_generic_del, dyn_generic_parse},
> +	{TRACE_DYNEVENT_URETPROBE, NULL, "r", dyn_generic_del, dyn_generic_parse},
> +	{TRACE_DYNEVENT_EPROBE, NULL, "e:", dyn_generic_del, dyn_generic_parse},
> +	{TRACE_DYNEVENT_SYNTH, NULL, "s:", dyn_synth_del, dyn_synth_parse},
> +};
> +
> +int dyn_generic_del(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn)
> +{
> +	char *str;
> +	int ret;
> +
> +	if (dyn->system)
> +		ret = asprintf(&str, "-:%s/%s", dyn->system, dyn->event);
> +	else
> +		ret = asprintf(&str, "-:%s", dyn->event);
> +
> +	if (ret < 0)
> +		return -1;
> +
> +	ret = tracefs_instance_file_append(NULL, desc->file, str);
> +	free(str);
> +
> +	return ret < 0 ? ret : 0;
> +}
> +
> +__hidden void dynevent_free(struct tracefs_dynevent *devent)
> +{
> +	if (!devent)
> +		return;
> +	free(devent->system);
> +	free(devent->event);
> +	free(devent->address);
> +	free(devent->format);
> +	free(devent->prefix);
> +	free(devent->trace_file);
> +	free(devent);
> +}
> +
> +static int dyn_generic_parse(struct dyn_events_desc *desc, const char *group,
> +			     char *line, struct tracefs_dynevent **ret_dyn)

Again, I prefer to have the pointer returned, and not assigned as a
parameter. All this returns is 0 on success or -1 on failure. That doesn't
give us anything more than having ret_dyn allocated or not. (return NULL or
non-NULL).

> +{
> +	struct tracefs_dynevent *dyn;
> +	char *address;
> +	char *system;
> +	char *format;
> +	char *event;
> +
> +	if (strncmp(line, desc->prefix, strlen(desc->prefix)))
> +		return -1;
> +
> +	system = strchr(line, ':');
> +	if (!system)
> +		return -1;
> +	event = strchr(line, '/');
> +	if (!event)
> +		return -1;
> +	address = strchr(line, ' ');
> +	if (!address)
> +		return -1;
> +	format = strchr(address+1, ' ');
> +
> +	*address = '\0';
> +	*event = '\0';


The above should use strtok_r.

	char *probe;
	char *sav;

	probe = strtok_r(line, ":", &sav);
	system = strtok_r(NULL, "/", &sav);
	event = strtok_r(NULL, " ", &sav);
	address = strtok_r(NULL, " ", &sav);
	format = strtok_r(NULL, "\n", &sav);




> +
> +	/* KPROBEs and UPROBEs share the same prefix, check the format */
> +	if (desc->type == TRACE_DYNEVENT_UPROBE || desc->type == TRACE_DYNEVENT_URETPROBE) {
> +		if (!strchr(format, '/'))
> +			return -1;
> +	}
> +	if (group && strcmp(group, system+1) != 0)

No need for +1.

> +		return -1;
> +
> +	if (!ret_dyn)
> +		return 0;
> +
> +	dyn = calloc(1, sizeof(*dyn));
> +	if (!dyn)
> +		return -1;

add space here. All error checks should have a blank line after them.

> +	dyn->type = desc->type;
> +	dyn->trace_file = strdup(desc->file);
> +	if (!dyn->trace_file)
> +		goto error;

> +	dyn->system = strdup(system+1);
> +	if (!dyn->system)
> +		goto error;

> +	*(system+1) = '\0';
> +	/* Prefix of KRETPROBE can contain MAXACTIVE integer */
> +	dyn->prefix = strdup(line);
> +	if (!dyn->prefix)
> +		goto error;

> +	dyn->event = strdup(event+1);
> +	if (!dyn->event)
> +		goto error;

> +	if (desc->type == TRACE_DYNEVENT_SYNTH) {
> +		/* Synthetic events have no address */
> +		dyn->format = strdup(address+1);
> +		if (!dyn->format)
> +			goto error;
> +	} else {
> +		if (format)
> +			*format = '\0';
> +		dyn->address = strdup(address+1);
> +		if (!dyn->address)
> +			goto error;
> +		if (format) {
> +			dyn->format = strdup(format+1);
> +			if (!dyn->format)
> +				goto error;
> +		}

If strtok_r wasn't used, there's no reason to do all these "+1", you could
have just done:

	system++;
	event++;
	address++;
	format++;

It would have made it much cleaner.

> +	}
> +	*ret_dyn = dyn;
> +	return 0;
> +error:
> +	dynevent_free(dyn);
> +	return -1;
> +}
> +
> +int dyn_synth_del(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn)
> +{
> +	char *str;
> +	int ret;
> +
> +	if (strcmp(desc->file, DYNEVENTS_EVENTS))
> +		return dyn_generic_del(desc, dyn);
> +
> +	ret = asprintf(&str, "!%s", dyn->event);
> +	if (ret < 0)
> +		return -1;
> +
> +	ret = tracefs_instance_file_append(NULL, desc->file, str);
> +	free(str);
> +
> +	return ret < 0 ? ret : 0;
> +}
> +
> +static int dyn_synth_parse(struct dyn_events_desc *desc, const char *group,
> +			   char *line, struct tracefs_dynevent **ret_dyn)
> +{
> +	struct tracefs_dynevent *dyn;
> +	char *format;
> +
> +	if (strcmp(desc->file, DYNEVENTS_EVENTS))
> +		return dyn_generic_parse(desc, group, line, ret_dyn);
> +
> +	/* synthetic_events file has slightly different syntax */
> +	format = strchr(line, ' ');
> +	if (!format)
> +		return -1;
> +	if (!ret_dyn)
> +		return 0;
> +
> +	*format = '\0';

strtok_r is your friend.

> +	dyn = calloc(1, sizeof(*dyn));
> +	if (!dyn)
> +		return -1;
> +	dyn->type = desc->type;
> +	dyn->trace_file = strdup(desc->file);
> +	if (!dyn->trace_file)
> +		goto error;
> +
> +	dyn->event = strdup(line);
> +	if (!dyn->event)
> +		goto error;
> +	dyn->format = strdup(format+1);
> +	if (!dyn->format)
> +		goto error;
> +
> +	*ret_dyn = dyn;
> +	return 0;
> +error:
> +	dynevent_free(dyn);
> +	return -1;
> +}
> +
> +static struct dyn_events_desc *get_devent_desc(enum trace_dynevent_type type)
> +{
> +	static bool init;
> +	int i;
> +
> +	if (init)
> +		goto out;
> +
> +	init = true;
> +	/* Use  ftrace dynamic_events, if available */
> +	if (tracefs_file_exists(NULL, DYNEVENTS_EVENTS)) {
> +		for (i = 0; i < TRACE_DYNEVENT_MAX; i++)
> +			dynevents[i].file = DYNEVENTS_EVENTS;
> +		goto out;
> +	}
> +
> +	if (tracefs_file_exists(NULL, KPROBE_EVENTS)) {
> +		dynevents[TRACE_DYNEVENT_KPROBE].file = KPROBE_EVENTS;
> +		dynevents[TRACE_DYNEVENT_KRETPROBE].file = KPROBE_EVENTS;
> +	}
> +	if (tracefs_file_exists(NULL, UPROBE_EVENTS)) {
> +		dynevents[TRACE_DYNEVENT_UPROBE].file = UPROBE_EVENTS;
> +		dynevents[TRACE_DYNEVENT_URETPROBE].file = UPROBE_EVENTS;
> +	}
> +	if (tracefs_file_exists(NULL, SYNTH_EVENTS)) {
> +		dynevents[TRACE_DYNEVENT_SYNTH].file = SYNTH_EVENTS;
> +		dynevents[TRACE_DYNEVENT_SYNTH].prefix = "";
> +	}
> +
> +out:
> +	return &dynevents[type];

I would break up the above into two functions.

static void init_devent_desc(void)
{
	int i;

	/* Use  ftrace dynamic_events, if available */
	if (tracefs_file_exists(NULL, DYNEVENTS_EVENTS)) {
		for (i = 0; i < TRACE_DYNEVENT_MAX; i++)
			dynevents[i].file = DYNEVENTS_EVENTS;
		goto out;
	}

	if (tracefs_file_exists(NULL, KPROBE_EVENTS)) {
		dynevents[TRACE_DYNEVENT_KPROBE].file = KPROBE_EVENTS;
		dynevents[TRACE_DYNEVENT_KRETPROBE].file = KPROBE_EVENTS;
	}
	if (tracefs_file_exists(NULL, UPROBE_EVENTS)) {
		dynevents[TRACE_DYNEVENT_UPROBE].file = UPROBE_EVENTS;
		dynevents[TRACE_DYNEVENT_URETPROBE].file = UPROBE_EVENTS;
	}
	if (tracefs_file_exists(NULL, SYNTH_EVENTS)) {
		dynevents[TRACE_DYNEVENT_SYNTH].file = SYNTH_EVENTS;
		dynevents[TRACE_DYNEVENT_SYNTH].prefix = "";
	}
}


static struct dyn_events_desc *get_devent_desc(enum trace_dynevent_type type)
{
	static bool init;

	if (!init) {
		init_devent_desc();
		init = true;
	}
	return &dynevents[type];
}



> +}
> +
> +__hidden struct tracefs_dynevent *
> +dynevent_alloc(enum trace_dynevent_type type, const char *system,
> +	       const char *event, const char *address, const char *format)
> +{
> +	struct tracefs_dynevent *devent;
> +	struct dyn_events_desc *desc;
> +
> +	if (!event) {
> +		errno = EINVAL;
> +		return NULL;
> +	}
> +
> +	desc = get_devent_desc(type);
> +	if (!desc || !desc->file) {
> +		errno = ENOTSUP;
> +		return NULL;
> +	}
> +
> +	devent = calloc(1, sizeof(*devent));
> +	if (!devent)
> +		return NULL;
> +
> +	devent->type = type;
> +	devent->trace_file = strdup(desc->file);
> +	if (!devent->trace_file)
> +		goto err;
> +	if (!system)
> +		system = DYNEVENTS_DEFAULT_GROUP;
> +	devent->system = strdup(system);
> +	if (!devent->system)
> +		goto err;
> +	devent->event = strdup(event);
> +	if (!devent->event)
> +		goto err;
> +	if (type == TRACE_DYNEVENT_KRETPROBE || type == TRACE_DYNEVENT_URETPROBE)
> +		devent->prefix = strdup(RETPROBE_DEFAUL_PREFIX);
> +	else
> +		devent->prefix = strdup(desc->prefix);
> +
> +	if (!devent->prefix)
> +		goto err;
> +	if (address) {
> +		devent->address = strdup(address);
> +		if (!devent->address)
> +			goto err;
> +	}
> +	if (format) {
> +		devent->format = strdup(format);
> +		if (!devent->format)
> +			goto err;
> +	}
> +
> +	return devent;
> +err:
> +	dynevent_free(devent);
> +	return NULL;
> +}
> +
> +__hidden int dynevent_create(struct tracefs_dynevent *devent)
> +{
> +	char *str;
> +	int ret;
> +
> +	if (!devent)
> +		return -1;

Again, space after error paths.

> +	if (devent->system && devent->system[0])
> +		ret = asprintf(&str, "%s%s/%s %s %s\n",
> +				devent->prefix, devent->system, devent->event,
> +				devent->address?devent->address:"",

Add spaces around "?" and ":" and for the ones below.

> +				devent->format?devent->format:"");
> +	else
> +		ret = asprintf(&str, "%s%s %s %s\n",
> +				devent->prefix, devent->event,
> +				devent->address?devent->address:"",
> +				devent->format?devent->format:"");
> +	if (ret < 0)
> +		return -1;

> +	ret = tracefs_instance_file_append(NULL, devent->trace_file, str);
> +	free(str);
> +
> +	return ret < 0 ? ret : 0;
> +}
> +
> +__hidden int dynevent_destroy(struct tracefs_dynevent *devent)
> +{
> +	struct dyn_events_desc *desc;
> +
> +	if (!devent)
> +		return -1;

space.

> +	desc = get_devent_desc(devent->type);
> +	if (!desc)
> +		return -1;

space.

> +	return desc->dyn_events_del(desc, devent);
> +}
> +
> +static int get_all_type(enum trace_dynevent_type type, const char *system,
> +			struct tracefs_dynevent ***ret_all)
> +{
> +	struct dyn_events_desc *desc;
> +	struct tracefs_dynevent *devent, **tmp, **all = NULL;
> +	char *content;
> +	int count = 0;
> +	char *line;
> +	char *next;
> +	int ret;
> +
> +	desc = get_devent_desc(type);
> +	if (!desc)
> +		return -1;
> +
> +	content = tracefs_instance_file_read(NULL, desc->file, NULL);
> +	if (!content)
> +		return -1;

space.

> +	line = content;
> +	do {
> +		next = strchr(line, '\n');
> +		if (next)
> +			*next = '\0';
> +		ret = desc->dyn_events_parse(desc, system, line, ret_all ? &devent : NULL);

So if ret_all is NULL, then we allocate a bunch of devent and lose them?

> +		if (!ret) {
> +			if (ret_all) {
> +				tmp = realloc(all, (count+1)*sizeof(struct tracefs_dynevent *));
> +				if (!tmp)
> +					goto error;
> +				all = tmp;
> +				all[count] = devent;
> +			}
> +			count++;
> +		}
> +		line = next + 1;
> +	} while (next);
> +
> +	free(content);
> +	if (ret_all)
> +		*ret_all = all;

We really should just return all, and if we want a count, we can pass a
pointer to an integer, and put the count in there. Not the other way around.

> +	return count;
> +
> +error:
> +	free(content);
> +	free(all);
> +	return -1;
> +}
> +
> +/**
> + * tracefs_dynevent_list_free - Deletes an array of pointers to dynamic event contexts
> + * @events: An array of pointers to dynamic event contexts. The last element of the array
> + *	    must be a NULL pointer.
> + */
> +void tracefs_dynevent_list_free(struct tracefs_dynevent ***events)

Again, just pass in ** not ***.

> +{
> +	struct tracefs_dynevent **devent;
> +	int i = 0;
> +
> +	if (!events || !(*events))
> +		return;
> +
> +	devent = *events;
> +	while (devent[i])
> +		dynevent_free(devent[i++]);
> +
> +	free(*events);
> +}
> +
> +__hidden int dynevent_get_all(unsigned long type_mask, const char *system,
> +			      struct tracefs_dynevent ***ret_events)
> +{
> +	struct tracefs_dynevent **events, **tmp, **all_events = NULL;
> +	int count, all = 0;
> +	int i;
> +
> +	for (i = 0; i < TRACE_DYNEVENT_MAX; i++) {
> +		if (!DYNEVENT_CHECK_BIT(type_mask, i))
> +			continue;

space.

> +		count = get_all_type(i, system, ret_events ? &events : NULL);
> +		if (count > 0) {
> +			if (ret_events) {
> +				tmp = realloc(all_events,
> +					     (all + count)*sizeof(struct tracefs_dynevent *));
> +				if (!tmp)
> +					goto error;
> +				all_events = tmp;
> +				memcpy(all_events + all, events,
> +				       count*sizeof(struct tracefs_dynevent *));
> +			}
> +			all += count;
> +		}
> +
> +	}
> +
> +	if (ret_events) {
> +		/* Add a NULL pointer at the end */
> +		if (all > 0) {
> +			tmp = realloc(all_events,
> +				     (all + 1)*sizeof(struct tracefs_dynevent *));
> +			if (!tmp)
> +				goto error;
> +			all_events = tmp;
> +			all_events[all] = NULL;
> +		}
> +		*ret_events = all_events;
> +	}
> +
> +	return all;
> +
> +error:
> +	if (all_events) {
> +		for (i = 0; i < all; i++)
> +			free(all_events[i]);
> +		free(all_events);
> +	}
> +	return -1;
> +}


-- Steve



[Index of Archives]     [Linux USB Development]     [Linux USB Development]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux