From patchwork Tue Feb 22 23:23:14 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Beau Belgrave X-Patchwork-Id: 12756056 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2B88CC433F5 for ; Tue, 22 Feb 2022 23:23:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234538AbiBVXXr (ORCPT ); Tue, 22 Feb 2022 18:23:47 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46726 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232933AbiBVXXr (ORCPT ); Tue, 22 Feb 2022 18:23:47 -0500 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id E9DF595A18 for ; Tue, 22 Feb 2022 15:23:20 -0800 (PST) Received: from localhost.localdomain (c-73-140-2-214.hsd1.wa.comcast.net [73.140.2.214]) by linux.microsoft.com (Postfix) with ESMTPSA id A3E9120C31BB; Tue, 22 Feb 2022 15:23:20 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com A3E9120C31BB DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1645572200; bh=VylUIfdhAMJuU9a/tsdmGlIJqBBOQxlfD3XRnfI/8gs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TNp+yZ2hMDVMNs5C1HUcMW/5jeIq8lH0pRUq5yPnpooiWH61f0HBRoa6PaLcJ24Rc ysZoijiomqlz6s5tM9YvuPB5Q6/7xCAwl87JMn/7paiqacOAIu664cofV6WDBg0v35 421J7EuIba34gPIFF1cCnwc/x+TwqffCMbhycj3s= From: Beau Belgrave To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, beaub@linux.microsoft.com Subject: [PATCH v2 1/3] libtracefs: Add user_events to libtracefs sources Date: Tue, 22 Feb 2022 15:23:14 -0800 Message-Id: <20220222232316.14640-2-beaub@linux.microsoft.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20220222232316.14640-1-beaub@linux.microsoft.com> References: <20220222232316.14640-1-beaub@linux.microsoft.com> Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org The user events are scheduled to be included into Linux 5.18, which register a special mmapped page to denote when the user event is enabled (from an external source). This API adds a wrapper to the kernel interface that makes it easy to register user events and test if they are enabled and to record the event when it is. Link: https://lore.kernel.org/linux-trace-devel/20220121192833.GA3128@kbox/T/#m2bcf53c373fbeaba2c46d1a053b3174171167e4e Signed-off-by: Beau Belgrave --- Makefile | 8 + include/tracefs-local.h | 24 ++ include/tracefs.h | 67 +++++ src/Makefile | 4 + src/tracefs-userevents.c | 516 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 619 insertions(+) create mode 100644 src/tracefs-userevents.c diff --git a/Makefile b/Makefile index 544684c..a4598b4 100644 --- a/Makefile +++ b/Makefile @@ -154,6 +154,14 @@ CFLAGS ?= -g -Wall CPPFLAGS ?= LDFLAGS ?= +USEREVENTS_INSTALLED := $(shell if (echo "$(pound)include " | $(CC) -E - >/dev/null 2>&1) ; then echo 1; else echo 0 ; fi) +export USEREVENTS_INSTALLED +ifeq ($(USEREVENTS_INSTALLED), 1) +CFLAGS += -DUSEREVENTS +else +$(warning user_events.h not installed, skipping) +endif + CUNIT_INSTALLED := $(shell if (printf "$(pound)include \n void main(){CU_initialize_registry();}" | $(CC) -x c - -lcunit -o /dev/null >/dev/null 2>&1) ; then echo 1; else echo 0 ; fi) export CUNIT_INSTALLED diff --git a/include/tracefs-local.h b/include/tracefs-local.h index bf157e1..9491545 100644 --- a/include/tracefs-local.h +++ b/include/tracefs-local.h @@ -119,4 +119,28 @@ int trace_rescan_events(struct tep_handle *tep, struct tep_event *get_tep_event(struct tep_handle *tep, const char *system, const char *name); +/* Internal interface for ftrace user events */ + +struct tracefs_user_event_group; + +struct tracefs_user_event_internal +{ + struct tracefs_user_event event_external; + int write_index; + int iovecs; + int rels; + int len; + struct tracefs_user_event_group *group; + struct tracefs_user_event_internal *next; +}; + +struct tracefs_user_event_group +{ + int fd; + int mmap_len; + char *mmap; + pthread_mutex_t lock; + struct tracefs_user_event_internal *events; +}; + #endif /* _TRACE_FS_LOCAL_H */ diff --git a/include/tracefs.h b/include/tracefs.h index 1848ad0..74241a9 100644 --- a/include/tracefs.h +++ b/include/tracefs.h @@ -571,4 +571,71 @@ struct tracefs_synth *tracefs_sql(struct tep_handle *tep, const char *name, struct tep_event * tracefs_synth_get_event(struct tep_handle *tep, struct tracefs_synth *synth); +/* User events */ +enum tracefs_uevent_type { + TRACEFS_UEVENT_END, + TRACEFS_UEVENT_u8, + TRACEFS_UEVENT_s8, + TRACEFS_UEVENT_u16, + TRACEFS_UEVENT_s16, + TRACEFS_UEVENT_u32, + TRACEFS_UEVENT_s32, + TRACEFS_UEVENT_u64, + TRACEFS_UEVENT_s64, + TRACEFS_UEVENT_string, + TRACEFS_UEVENT_struct, + TRACEFS_UEVENT_varray, + TRACEFS_UEVENT_vstring, +}; + +enum tracefs_uevent_flags { + /* None */ + TRACEFS_UEVENT_FLAG_NONE = 0, + + /* When BPF is attached, use iterator/no copy */ + TRACEFS_UEVENT_FLAG_bpf_iter = 1 << 0, +}; + +struct tracefs_uevent_item { + /* Type of item */ + enum tracefs_uevent_type type; + + /* Length of data, optional during register */ + int len; + + union { + /* Used during write */ + const void *data; + + /* Used during register */ + const char *name; + }; +}; + +struct tracefs_user_event { + unsigned int size; + char *enabled; +}; + +struct tracefs_user_event_group; + +struct tracefs_user_event_group *tracefs_user_event_group_open(void); + +void tracefs_user_event_group_close(struct tracefs_user_event_group *group); + +int tracefs_user_event_delete(const char *name); + +struct tracefs_user_event * +tracefs_user_event_register(struct tracefs_user_event_group *group, + const char *name, enum tracefs_uevent_flags flags, + struct tracefs_uevent_item *items); + +static inline bool tracefs_user_event_enabled(struct tracefs_user_event *event) +{ + return event && ((volatile char *)event->enabled)[0] != 0; +} + +int tracefs_user_event_record(struct tracefs_user_event *event, + struct tracefs_uevent_item *items); + #endif /* _TRACE_FS_H */ diff --git a/src/Makefile b/src/Makefile index e8afab5..984e8cf 100644 --- a/src/Makefile +++ b/src/Makefile @@ -14,6 +14,10 @@ OBJS += tracefs-filter.o OBJS += tracefs-dynevents.o OBJS += tracefs-eprobes.o +ifeq ($(USEREVENTS_INSTALLED), 1) +OBJS += tracefs-userevents.o +endif + # Order matters for the the three below OBJS += sqlhist-lex.o OBJS += sqlhist.tab.o diff --git a/src/tracefs-userevents.c b/src/tracefs-userevents.c new file mode 100644 index 0000000..ccd511b --- /dev/null +++ b/src/tracefs-userevents.c @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2022 Microsoft Corporation. + * + * Authors: + * Beau Belgrave + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tracefs.h" +#include "tracefs-local.h" + +#define STAT_FILE "user_events_status" +#define DATA_FILE "user_events_data" + +static void free_user_events(struct tracefs_user_event_internal *event) +{ + struct tracefs_user_event_internal *next; + + while (event) { + next = event->next; + free(event); + event = next; + } +} + +static int append_field(struct tracefs_uevent_item *item, struct trace_seq *seq, + int index) +{ + if (index != 0) + trace_seq_printf(seq, ";"); + + switch (item->type) { + case TRACEFS_UEVENT_u8: + trace_seq_printf(seq, " u8 %s", item->name); + break; + + case TRACEFS_UEVENT_s8: + trace_seq_printf(seq, " s8 %s", item->name); + break; + + case TRACEFS_UEVENT_u16: + trace_seq_printf(seq, " u16 %s", item->name); + break; + + case TRACEFS_UEVENT_s16: + trace_seq_printf(seq, " s16 %s", item->name); + break; + + case TRACEFS_UEVENT_u32: + trace_seq_printf(seq, " u32 %s", item->name); + break; + + case TRACEFS_UEVENT_s32: + trace_seq_printf(seq, " s32 %s", item->name); + break; + + case TRACEFS_UEVENT_u64: + trace_seq_printf(seq, " u64 %s", item->name); + break; + + case TRACEFS_UEVENT_s64: + trace_seq_printf(seq, " s64 %s", item->name); + break; + + case TRACEFS_UEVENT_string: + if (item->len <= 0) { + errno = EINVAL; + return -1; + } + + trace_seq_printf(seq, " char[%d] %s", item->len, item->name); + break; + + case TRACEFS_UEVENT_struct: + /* + * struct must have 2 strings, do simple check + * in user, kernel will fully validate + */ + if (!strchr(item->name, ' ')) { + errno = EINVAL; + return -1; + } + + if (item->len <= 0) { + errno = EINVAL; + return -1; + } + + trace_seq_printf(seq, " struct %s %d", item->name, item->len); + break; + + case TRACEFS_UEVENT_varray: + /* Variable length array */ + trace_seq_printf(seq, " __rel_loc u8[] %s", item->name); + break; + + case TRACEFS_UEVENT_vstring: + /* Variable length string */ + trace_seq_printf(seq, " __rel_loc char[] %s", item->name); + break; + + default: + /* Unknown */ + errno = ENOENT; + return -1; + } + + return 0; +} + +static int create_reg_cmd(const char *name, enum tracefs_uevent_flags flags, + struct tracefs_uevent_item *item, struct trace_seq *seq) +{ + int ret, index = 0; + + trace_seq_printf(seq, "%s", name); + + if (flags & TRACEFS_UEVENT_FLAG_bpf_iter) + trace_seq_printf(seq, ":BPF_ITER"); + + while (item->type != TRACEFS_UEVENT_END) { + ret = append_field(item, seq, index++); + + if (ret < 0) + return ret; + + item++; + } + + trace_seq_terminate(seq); + + if (seq->state) { + errno = ENOMEM; + return -1; + } + + return 0; +} + +static int get_write_counts(struct tracefs_user_event_internal *event, + struct tracefs_uevent_item *item) +{ + event->rels = 0; + event->len = 0; + + /* Start at 1, need iovec for write_index */ + event->iovecs = 1; + + while (item->type != TRACEFS_UEVENT_END) { + switch (item->type) { + case TRACEFS_UEVENT_u8: + case TRACEFS_UEVENT_s8: + event->len += sizeof(__u8); + break; + + case TRACEFS_UEVENT_u16: + case TRACEFS_UEVENT_s16: + event->len += sizeof(__u16); + break; + + case TRACEFS_UEVENT_u32: + case TRACEFS_UEVENT_s32: + event->len += sizeof(__u32); + break; + + case TRACEFS_UEVENT_u64: + case TRACEFS_UEVENT_s64: + event->len += sizeof(__u64); + break; + + case TRACEFS_UEVENT_string: + case TRACEFS_UEVENT_struct: + event->len += item->len; + break; + + case TRACEFS_UEVENT_varray: + case TRACEFS_UEVENT_vstring: + /* Requires a rel loc entry */ + event->len += sizeof(__u32); + event->rels++; + break; + + default: + /* Unknown */ + errno = ENOENT; + return -1; + } + + event->iovecs++; + item++; + } + + return 0; +} + +/** + * tracefs_user_event_group_open - Opens a new group to use for user events + * + * Returns a pointer to a group to use for user events. The pointer is valid + * until tracefs_user_event_group_close() is called. In case of an error NULL + * is returned. + */ +struct tracefs_user_event_group *tracefs_user_event_group_open(void) +{ + int stat, write, page_size, i; + struct tracefs_user_event_group *group; + + stat = tracefs_instance_file_open(NULL, STAT_FILE, O_RDWR); + + if (stat < 0) + return NULL; + + write = tracefs_instance_file_open(NULL, DATA_FILE, O_RDWR); + + if (write < 0) + goto put_stat; + + group = malloc(sizeof(*group)); + + if (!group) + goto put_write; + + if (pthread_mutex_init(&group->lock, NULL) < 0) + goto put_group; + + /* Scale up to 16-bit max user events a page at a time */ + page_size = sysconf(_SC_PAGESIZE); + group->mmap_len = page_size; + + for (i = 0; i < 16; ++i) { + group->mmap = mmap(NULL, group->mmap_len, + PROT_READ, MAP_SHARED, stat, 0); + + if (group->mmap == MAP_FAILED && errno == EINVAL) { + /* Increase by page size and try again */ + group->mmap_len += page_size; + continue; + } + + break; + } + + if (group->mmap == MAP_FAILED) + goto put_group; + + group->fd = write; + group->events = NULL; + + /* Status fd no longer needed */ + close(stat); + + return group; + +put_group: + free(group); +put_write: + close(write); +put_stat: + close(stat); + + return NULL; +} + +/** + * tracefs_user_event_delete - Deletes a user event from the system + * @name: Name of the event to delete + * + * Deletes the event from the system if it is not used. + */ +int tracefs_user_event_delete(const char *name) +{ + int ret, write; + + write = tracefs_instance_file_open(NULL, DATA_FILE, O_RDWR); + + if (write < 0) + return write; + + ret = ioctl(write, DIAG_IOCSDEL, name); + + close(write); + + return ret; +} + +/** + * tracefs_user_event_group_close - Closes a group containing user events + * @group: Group to close + * + * Closes a group and all the user events within it. Any user event that has + * been added to the group is no longer valid and cannot be used. + */ +void tracefs_user_event_group_close(struct tracefs_user_event_group *group) +{ + if (!group) + return; + + if (group->mmap != MAP_FAILED) + munmap(group->mmap, group->mmap_len); + + if (group->fd != -1) + close(group->fd); + + free_user_events(group->events); + free(group); +} + +/** + * tracefs_user_event_register - Registers a user event with the system + * @group: Group to add the user event to + * @name: Name of the event to register + * @flags: Flags to use + * @items: Array of items that the event contains + * + * Allocates and registers a user event with the system. The user event will be + * added to the @group. The lifetime of the event is bound to the @group. When + * the @group is closed via tracefs_user_event_group_close() the event will no + * longer exist and should not be used. + * + * The @items are processed in order and the final item type must be set to + * TRACEFS_UEVENT_END to mark the last item. Each item must have the type + * and name defined. The string and struct type also require the len to be set + * for the item. + * + * Return a pointer to a user event on success, or NULL or error. + * + * errno will be set to EINVAL if @group is null or unexpected @items. + */ +struct tracefs_user_event * +tracefs_user_event_register(struct tracefs_user_event_group *group, + const char *name, enum tracefs_uevent_flags flags, + struct tracefs_uevent_item *items) +{ + struct tracefs_user_event_internal *event = NULL; + struct user_reg reg = {0}; + struct trace_seq seq; + + if (!group || !items) { + errno = EINVAL; + return NULL; + } + + trace_seq_init(&seq); + + /* Populate cmd */ + if (create_reg_cmd(name, flags, items, &seq)) + return NULL; + + event = malloc(sizeof(*event)); + + if (!event) + goto put_seq; + + reg.size = sizeof(reg); + reg.name_args = (__u64)seq.buffer; + + /* Register event with kernel */ + if (ioctl(group->fd, DIAG_IOCSREG, ®) == -1) + goto put_event; + + /* Sanity check bounds returned */ + if (reg.status_index >= group->mmap_len) { + errno = EINVAL; + goto put_event; + } + + if (get_write_counts(event, items)) + goto put_event; + + /* Keep track of user view at this point in time */ + event->event_external.size = sizeof(event->event_external); + event->event_external.enabled = &group->mmap[reg.status_index]; + + event->write_index = reg.write_index; + event->group = group; + + /* Add event into the group under lock */ + pthread_mutex_lock(&group->lock); + event->next = group->events; + group->events = event->next; + pthread_mutex_unlock(&group->lock); + + trace_seq_destroy(&seq); + + return &event->event_external; +put_event: + free(event); +put_seq: + trace_seq_destroy(&seq); + + return NULL; +} + +/** + * tracefs_user_event_record - Records an event with data + * @event: User event to record data about + * @items: Items to write for the event + * + * Records items for the event. Callers should check if the cost of recording + * should be performed by calling tracefs_user_event_enabled(). Items are + * checked to ensure they fit within the described items during register. Each + * item must specify the length of the item being recorded. + * + * Return the number of bytes recorded or -1 upon error. + * + * errno will be set to EINVAL if @event or @items is null or @items contains + * an item with a length of less than or equal to 0. + * errno will be set to E2BIG if @items contains more items than previously + * registered for the event. + */ +int tracefs_user_event_record(struct tracefs_user_event *event, + struct tracefs_uevent_item *items) +{ + struct tracefs_user_event_internal *e; + struct iovec *head, *io, *relio, *io_end; + __u32 *rel, *rel_end; + int len, rel_offset, data_offset, used; + + if (!event || !items) { + errno = EINVAL; + return -1; + } + + e = (struct tracefs_user_event_internal *)event; + head = io = alloca(sizeof(*io) * (e->iovecs + e->rels)); + rel = alloca(sizeof(*rel) * e->rels); + + io_end = head + (e->iovecs + e->rels); + rel_end = rel + e->rels; + + /* Relative offset starts at end of static data */ + relio = io + e->iovecs; + rel_offset = e->len; + data_offset = 0; + + /* Write index must be first */ + io->iov_base = &e->write_index; + io->iov_len = sizeof(e->write_index); + io++; + used = 1; + + while (items->type != TRACEFS_UEVENT_END) { + len = items->len; + + if (len <= 0) + goto bad_length; + + if (io >= io_end) + goto bad_count; + + switch (items->type) { + case TRACEFS_UEVENT_varray: + case TRACEFS_UEVENT_vstring: + /* Dual vectors */ + used += 2; + + if (rel >= rel_end || relio >= io_end) + goto bad_count; + + /* __rel_loc types */ + relio->iov_base = (void *)items->data; + relio->iov_len = len; + relio++; + + io->iov_base = (void *)rel; + io->iov_len = sizeof(*rel); + io++; + rel_offset -= sizeof(*rel); + + /* Fill in rel loc data */ + *rel = DYN_LOC(rel_offset + data_offset, len); + data_offset += len; + rel++; + + break; + + default: + /* Single vector */ + used++; + + /* Direct types */ + io->iov_base = (void *)items->data; + io->iov_len = len; + io++; + rel_offset -= len; + + break; + } + + items++; + } + + return writev(e->group->fd, head, used); + +bad_length: + fprintf(stderr, "Bad user_event item length at index %d\n", + used - 1); + errno = EINVAL; + return -1; + +bad_count: + fprintf(stderr, "Too many user_event items passed\n"); + errno = E2BIG; + return -1; +} From patchwork Tue Feb 22 23:23:15 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Beau Belgrave X-Patchwork-Id: 12756058 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2F473C433FE for ; Tue, 22 Feb 2022 23:23:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236163AbiBVXXt (ORCPT ); Tue, 22 Feb 2022 18:23:49 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46730 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S236035AbiBVXXs (ORCPT ); Tue, 22 Feb 2022 18:23:48 -0500 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 2189E95A1C for ; Tue, 22 Feb 2022 15:23:21 -0800 (PST) Received: from localhost.localdomain (c-73-140-2-214.hsd1.wa.comcast.net [73.140.2.214]) by linux.microsoft.com (Postfix) with ESMTPSA id D52EE20C31BE; Tue, 22 Feb 2022 15:23:20 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com D52EE20C31BE DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1645572200; bh=YfgrnOC9I1+1wsswUYjRW5aa9lI3t9PUb7bSGYmefnk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=d3PhHzbi3vIBk9hVlWlu9WxzUShgeXtI80CPtts1/gYlv9vafeh5pK7skx4TqSruW Myi7eyxqMkwkO59F9Hua1d91sZ6gnkyELntwvb5f2V3b7yKA3tbWgR/f6dSjDrozXE cYkmi5XuczcXN4KhNqxPbfcBe2Ai4UtDAxPLS3/Q= From: Beau Belgrave To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, beaub@linux.microsoft.com Subject: [PATCH v2 2/3] libtracefs: Add documentation and sample code for user_events Date: Tue, 22 Feb 2022 15:23:15 -0800 Message-Id: <20220222232316.14640-3-beaub@linux.microsoft.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20220222232316.14640-1-beaub@linux.microsoft.com> References: <20220222232316.14640-1-beaub@linux.microsoft.com> Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org Adds the initial documentation file for user_event APIs within libtracefs. Adds sample code on how to use these new APIs. Signed-off-by: Beau Belgrave --- Documentation/libtracefs-userevents.txt | 260 ++++++++++++++++++++++++ samples/Makefile | 4 + 2 files changed, 264 insertions(+) create mode 100644 Documentation/libtracefs-userevents.txt diff --git a/Documentation/libtracefs-userevents.txt b/Documentation/libtracefs-userevents.txt new file mode 100644 index 0000000..1d32d81 --- /dev/null +++ b/Documentation/libtracefs-userevents.txt @@ -0,0 +1,260 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_user_event_group_open, tracefs_user_event_group_close, tracefs_user_event_register, +tracefs_user_event_enabled, tracefs_user_event_record, tracefs_user_event_delete, +- Creation and management of a user event group, event, and data + +SYNOPSIS +-------- +[verse] +-- +*#include * + +struct tracefs_user_event_group pass:[*]*tracefs_user_event_group_open*(void); +void *tracefs_user_event_group_close*(struct tracefs_user_event_group pass:[*]_group_); +int *tracefs_user_event_delete*(const char pass:[*]_name_); +struct tracefs_user_event pass:[*]*tracefs_user_event_register*(struct tracefs_user_event_group pass:[*]_group_, + const char pass:[*]_name_, + enum tracefs_uevent_flags _flags_, + struct tracefs_uevent_item pass:[*]_items_); +bool *tracefs_user_event_enabled*(struct tracefs_user_event pass:[*]_event_); +int *tracefs_user_event_record*(struct tracefs_user_event pass:[*]_event_, + struct tracefs_uevent_item pass:[*]_items_); +-- + +DESCRIPTION +----------- +User events allow user applications to create trace events from their process. +The status of each event that is created is exposed to the application, which +enables quick checking if the event is being traced out or not. This enables +a way to always have the events on with minimal impact to the performance of +the process. The *tracefs_user_event_enabled*() function should be used for this. +When the event is to be traced out with data the *tracefs_user_event_record*() +function is to be used. Processes should delay any work related to the write +calls until after *tracefs_user_event_enabled*() returns true. This ensures +minimal performance impacts until tracing is enabled on the system for each event. + +To reduce the number of resources, and to manage lifetimes of the events, the +concept of a group exists within this implementation. Groups keep track of all +the events that have been registered within it and ensure that once the group +is closed that all the events are cleaned up properly. All events in a group +share system resources used for writing. Groups and events are thread safe and +do not require additional locking when operating in a multi-threaded process. +Once a group has been closed the events within it are no longer valid to use. +When groups are closed their resources go away, callers need to ensure that a +group is closed only once it's no longer needed by any of the events. + +*tracefs_user_event_group_open*() opens and initializes a group to be used. +Group opening can fail if user_events are not installed, the user doesn't have +permissions to access tracefs or memory allocation errors. The returned group is +then used to register events. + +*tracefs_user_event_group_close*() closes a group and any events that have been +registered within the group. Events within the group are no longer valid. + +*tracefs_user_event_register*() registers an event within the group and the system. +The name and items passed are translated to show up in tracefs format file. +The last item passed to this function must have the type of *TRACEFS_UEVENT_END*. +While events do not require any items, it's expected that each item that will +be written out should have a matching item during registration. + +Flags passed during registration affect the behavior of the event: + +*TRACEFS_UEVENT_FLAG_NONE* - No affect + +*TRACEFS_UEVENT_FLAG_bpf_iter* - Force BPF programs to receive iovecs instead +of a copy of the data. Enables higher performance under some conditions for +events that plan to use BPF programs and require very little overhead when writing. + +Types of items passed during the registration: + +*TRACEFS_UEVENT_END* - Indicates the last item. + +*TRACEFS_UEVENT_u8* - u8 item. + +*TRACEFS_UEVENT_s8* - s8 item. + +*TRACEFS_UEVENT_u16* - u16 item. + +*TRACEFS_UEVENT_s16* - s16 item. + +*TRACEFS_UEVENT_u32* - u32 item. + +*TRACEFS_UEVENT_s32* - s32 item. + +*TRACEFS_UEVENT_u64* - u64 item. + +*TRACEFS_UEVENT_s64* - s64 item. + +*TRACEFS_UEVENT_string* - Specific length array of char data. + +*TRACEFS_UEVENT_struct* - Specific sized struct by name and type (When used the +name of the item must include both the type and name of the struct). + +*TRACEFS_UEVENT_varray* - Variable length array of u8 data. + +*TRACEFS_UEVENT_vstring* - Variable length array of char data. + +Each item has a type, length, and name during registration. When items are being +used for writing each item has a type, length, and the data to write. + +When items are being registered, length is required for theses types: + +*TRACEFS_UEVENT_string* + +*TRACEFS_UEVENT_struct* + +If lengths are not set for the above types the register will fail. All other +types have the length determined automatically. + +When items are being written, all items must indicate their length. If lengths +are not set the write will fail. Automatic length sets are not performed. + +*tracefs_user_event_enabled*() checks if an event is being traced at that +moment. This check is quick and should always be used before writing or +calculating data required for writing out the event data. + +*tracefs_user_event_record*() records data for the event. This causes an entry +to be made in the tracing system that has been attached (perf, ftrace, BPF, +etc.). + +All items passed must set their length, even for common data types (u8, u16, +etc.). If required, callers may mix types from what has been registered as long +as it fits within the allocated resources. A common scenario for this is having +an internal struct that is packed and contains several consecutive registered +types. Passing the struct directly instead of each individual item might be +beneficial. While mixing types is allowed, it's a bug to pass more items than +have been registered. If this occurs the write will fail. + +When the type *TRACEFS_UEVENT_vstring* is being used, the length of the data +must include the null character of a string. If the length does not include the +null character the write will fail. + +*tracefs_user_event_delete*() deletes the event from the system. Delete only +works if the event is not being used by anything, include the calling program. + +RETURN VALUE +------------ +*tracefs_user_event_group_open*() returns an allocated struct +tracefs_user_event_group on success or NULL on error. + +*tracefs_user_event_register*() returns an allocated struct tracefs_user_event +on success or NULL on error. + +*tracefs_user_event_enabled*() returns true if the event is currently being +traced. + +*tracefs_user_event_record*() returns the number of bytes recorded out on +success or -1 on error. + +*tracefs_user_event_delete*() returns 0 on success or -1 on error. + +ERRORS +------ +The following errors are for all the above calls: + +*EPERM* Not run as root user when required. + +*EINVAL* Either a parameter is not valid (NULL when it should not be) + or a item that is missing an attribute. + +*E2BIG* Too many items passed. + +*ENOMEM* not enough memory is available. + +And more errors may have happened from the system calls to the system. + +EXAMPLE +------- +[source,c] +-- +#include +#include + +static struct tracefs_user_event_group *group; +static struct tracefs_user_event *event; + +static void make_event(void) +{ + struct tracefs_uevent_item items[] = { + { TRACEFS_UEVENT_vstring, .name = "message" }, + { TRACEFS_UEVENT_END }, + }; + + group = tracefs_user_event_group_open(); + + event = tracefs_user_event_register(group, "example", + TRACEFS_UEVENT_FLAG_NONE, items); +} + +int main (int argc, char **argv) +{ + make_event(); + + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + return 1; + } + + if (tracefs_user_event_enabled(event)) { + const char *msg = argv[1]; + + struct tracefs_uevent_item items[] = { + { TRACEFS_UEVENT_vstring, strlen(msg)+1, .data = msg }, + { TRACEFS_UEVENT_END }, + }; + + tracefs_user_event_record(event, items); + + printf("Event enabled, recorded '%s'\n", msg); + } else { + printf("Event user_events/example not enabled, enable via tracefs\n"); + } + + tracefs_user_event_group_close(group); + + return 0; +} +-- + +FILES +----- +[verse] +-- +*tracefs.h* + Header file to include in order to have access to the library APIs. +*-ltracefs* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +*libtracefs*(3), +*libtraceevent*(3), +*trace-cmd*(1) + +AUTHOR +------ +[verse] +-- +*Beau Belgrave* +-- +REPORTING BUGS +-------------- +Report bugs to + +LICENSE +------- +libtracefs is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git/ + +COPYING +------- +Copyright \(C) 2022 Microsoft Corporation. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/samples/Makefile b/samples/Makefile index f03aff6..169c106 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -21,6 +21,10 @@ EXAMPLES += tracer EXAMPLES += stream EXAMPLES += instances-affinity +ifeq ($(USEREVENTS_INSTALLED), 1) +EXAMPLES += userevents +endif + TARGETS := TARGETS += sqlhist TARGETS += $(EXAMPLES) From patchwork Tue Feb 22 23:23:16 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Beau Belgrave X-Patchwork-Id: 12756057 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6E7C3C4332F for ; Tue, 22 Feb 2022 23:23:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236035AbiBVXXt (ORCPT ); Tue, 22 Feb 2022 18:23:49 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46736 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232909AbiBVXXs (ORCPT ); Tue, 22 Feb 2022 18:23:48 -0500 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 540BE95A22 for ; Tue, 22 Feb 2022 15:23:21 -0800 (PST) Received: from localhost.localdomain (c-73-140-2-214.hsd1.wa.comcast.net [73.140.2.214]) by linux.microsoft.com (Postfix) with ESMTPSA id 0DA6720C31BF; Tue, 22 Feb 2022 15:23:21 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 0DA6720C31BF DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1645572201; bh=Ep13W27GmfCBsKfhFFQ+OZw8O1dh5rA21NaJlEf/98Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Utx1KFa1la377D6gZAuykGioCLRm3fm2m88wYOtxfApOwYenJpnr5QR+BsSInCa7Z vmU/0FLk12dqyUpgI1IkpuxZ2GN/VanFtQkR7K8mG/FG8LqsNXEzXFxPMpG1sLqob7 1xyu+HPAs3SoQp15w+WxyYB3Wc0Hexbqcb1GnVa8= From: Beau Belgrave To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, beaub@linux.microsoft.com Subject: [PATCH v2 3/3] libtracefs: Add unit tests for user_events Date: Tue, 22 Feb 2022 15:23:16 -0800 Message-Id: <20220222232316.14640-4-beaub@linux.microsoft.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20220222232316.14640-1-beaub@linux.microsoft.com> References: <20220222232316.14640-1-beaub@linux.microsoft.com> Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org Adds unit tests for user_events when available. Ensures APIs are working correctly and appropriate errors are being returned. Signed-off-by: Beau Belgrave --- utest/tracefs-utest.c | 233 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/utest/tracefs-utest.c b/utest/tracefs-utest.c index e8d5c69..ed38d9c 100644 --- a/utest/tracefs-utest.c +++ b/utest/tracefs-utest.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -871,6 +872,235 @@ static void test_eprobes(void) test_eprobes_instance(test_instance); } +#ifdef USEREVENTS +struct user_test_context { + int seen; + int failed; +}; +static int user_callback(struct tep_event *event, struct tep_record *record, + int cpu, void *context) +{ + struct tep_format_field *field; + struct user_test_context *user_context; + __u32 *rel, size, offset; + + user_context = (struct user_test_context *)context; + user_context->seen++; + + field = tep_find_field(event, "u8"); + if (!field || *(__u8 *)(record->data + field->offset) != 1) + { + user_context->failed = 1; + return -1; + } + + field = tep_find_field(event, "s8"); + if (!field || *(__s8 *)(record->data + field->offset) != 2) + { + user_context->failed = 2; + return -1; + } + + field = tep_find_field(event, "u16"); + if (!field || *(__u16 *)(record->data + field->offset) != 3) + { + user_context->failed = 3; + return -1; + } + + field = tep_find_field(event, "s16"); + if (!field || *(__s16 *)(record->data + field->offset) != 4) + { + user_context->failed = 4; + return -1; + } + + field = tep_find_field(event, "u32"); + if (!field || *(__u32 *)(record->data + field->offset) != 5) + { + user_context->failed = 5; + return -1; + } + + field = tep_find_field(event, "s32"); + if (!field || *(__s32 *)(record->data + field->offset) != 6) + { + user_context->failed = 6; + return -1; + } + + field = tep_find_field(event, "u64"); + if (!field || *(__u64 *)(record->data + field->offset) != 7) + { + user_context->failed = 7; + return -1; + } + + field = tep_find_field(event, "s64"); + if (!field || *(__s64 *)(record->data + field->offset) != 8) + { + user_context->failed = 8; + return -1; + } + + field = tep_find_field(event, "string"); + if (!field || memcmp(record->data + field->offset, "12345678", 8)) + { + user_context->failed = 9; + return -1; + } + + field = tep_find_field(event, "struct"); + if (!field || *(__u64 *)(record->data + field->offset) != 9) + { + user_context->failed = 10; + return -1; + } + + field = tep_find_field(event, "varray"); + if (!field) { + user_context->failed = 11; + return -1; + } + + rel = (__u32 *)(record->data + field->offset); + offset = *rel & 0xffff; + size = *rel >> 16; + rel++; + + if (memcmp((void *)(rel) + offset, "Array", size)) { + user_context->failed = 12; + return -1; + } + + field = tep_find_field(event, "vstring"); + if (!field) { + user_context->failed = 13; + return -1; + } + + rel = (__u32 *)(record->data + field->offset); + offset = *rel & 0xffff; + size = *rel >> 16; + rel++; + + if (memcmp((void *)(rel) + offset, "Variable", size)) { + user_context->failed = 14; + return -1; + } + + return 0; +} + +static void test_userevents_instance(struct tracefs_instance *instance) +{ + struct tracefs_user_event_group *group; + struct tracefs_user_event *event; + struct tep_handle *user_tep; + enum tracefs_uevent_flags flags = TRACEFS_UEVENT_FLAG_NONE; + const char *systems[] = { "user_events", NULL }; + const char *name = "libtracefs_utest"; + const char *system = "user_events"; + const char *test_string = "12345678"; + const char *test_array = "Array"; + const char *test_vstring = "Variable"; + __u8 a = 1; + __s8 b = 2; + __u16 c = 3; + __s16 d = 4; + __u32 e = 5; + __s32 f = 6; + __u64 g = 7; + __s64 h = 8; + __u64 i = 9; + struct tracefs_uevent_item all_items[] = { + { TRACEFS_UEVENT_u8, .name = "u8" }, + { TRACEFS_UEVENT_s8, .name = "s8" }, + { TRACEFS_UEVENT_u16, .name = "u16" }, + { TRACEFS_UEVENT_s16, .name = "s16" }, + { TRACEFS_UEVENT_u32, .name = "u32" }, + { TRACEFS_UEVENT_s32, .name = "s32" }, + { TRACEFS_UEVENT_u64, .name = "u64" }, + { TRACEFS_UEVENT_s64, .name = "s64" }, + { TRACEFS_UEVENT_string, .name = "string", .len = 8 }, + { TRACEFS_UEVENT_struct, .name = "test struct", .len = 8 }, + { TRACEFS_UEVENT_varray, .name = "varray" }, + { TRACEFS_UEVENT_vstring, .name = "vstring" }, + { TRACEFS_UEVENT_END }, + }; + struct tracefs_uevent_item write_items[] = { + { TRACEFS_UEVENT_u8, .data = &a, .len = sizeof(a) }, + { TRACEFS_UEVENT_s8, .data = &b, .len = sizeof(b) }, + { TRACEFS_UEVENT_u16, .data = &c, .len = sizeof(c) }, + { TRACEFS_UEVENT_s16, .data = &d, .len = sizeof(d) }, + { TRACEFS_UEVENT_u32, .data = &e, .len = sizeof(e) }, + { TRACEFS_UEVENT_s32, .data = &f, .len = sizeof(f) }, + { TRACEFS_UEVENT_u64, .data = &g, .len = sizeof(g) }, + { TRACEFS_UEVENT_s64, .data = &h, .len = sizeof(h) }, + { TRACEFS_UEVENT_string, .data = test_string, + .len = strlen(test_string) }, + { TRACEFS_UEVENT_struct, .data = &i, .len = sizeof(i) }, + { TRACEFS_UEVENT_varray, .data = test_array, + .len = strlen(test_array) }, + { TRACEFS_UEVENT_vstring, .data = test_vstring, + .len = strlen(test_vstring)+1 }, + { TRACEFS_UEVENT_END }, + }; + struct user_test_context context; + int ret; + + /* Delete if it already exists */ + tracefs_user_event_delete(name); + + group = tracefs_user_event_group_open(); + CU_TEST(group != NULL); + + event = tracefs_user_event_register(group, name, flags, all_items); + CU_TEST(event != NULL); + + /* Test enable and status */ + CU_TEST(!tracefs_user_event_enabled(event)); + CU_TEST(tracefs_event_enable(instance, system, name) == 0); + CU_TEST(tracefs_user_event_enabled(event)); + + /* Correct record should work */ + CU_TEST(tracefs_user_event_record(event, write_items) > 0); + + /* Ensure record output was correct */ + user_tep = tracefs_local_events_system(NULL, systems); + CU_TEST(user_tep != NULL); + + memset(&context, 0, sizeof(context)); + ret = tracefs_iterate_raw_events(user_tep, instance, NULL, 0, + user_callback, &context); + tep_free(user_tep); + + CU_TEST(ret == 0); + CU_TEST(context.seen == 1); + CU_TEST(context.failed == 0); + + /* Simulate bad length */ + write_items[0].len = 0; + CU_TEST(tracefs_user_event_record(event, write_items) == -1); + + /* Simulate bad pointer */ + write_items[0].len = sizeof(a); + write_items[0].data = NULL; + CU_TEST(tracefs_user_event_record(event, write_items) == -1); + + tracefs_user_event_group_close(group); + + /* Disable and deletion must work */ + CU_TEST(tracefs_event_disable(instance, system, name) == 0); + CU_TEST(tracefs_user_event_delete(name) == 0); +} + +static void test_userevents(void) +{ + test_userevents_instance(test_instance); +} +#endif + static void test_instance_file(void) { struct tracefs_instance *instance = NULL; @@ -1706,4 +1936,7 @@ void test_tracefs_lib(void) CU_add_test(suite, "kprobes", test_kprobes); CU_add_test(suite, "syntetic events", test_synthetic); CU_add_test(suite, "eprobes", test_eprobes); +#ifdef USEREVENTS + CU_add_test(suite, "user events", test_userevents); +#endif }