From patchwork Fri Feb 18 22:50:56 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Beau Belgrave X-Patchwork-Id: 12751994 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 4063FC433FE for ; Fri, 18 Feb 2022 22:51:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S240159AbiBRWvW (ORCPT ); Fri, 18 Feb 2022 17:51:22 -0500 Received: from mxb-00190b01.gslb.pphosted.com ([23.128.96.19]:52222 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S240166AbiBRWvV (ORCPT ); Fri, 18 Feb 2022 17:51:21 -0500 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 0F1EA273753 for ; Fri, 18 Feb 2022 14:51:03 -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 B74B320C27FD; Fri, 18 Feb 2022 14:51:02 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com B74B320C27FD DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1645224662; bh=og7F6wNkBt1XhUt7/fU+JrcwQURC3u6/DsVcxqAWLbw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZnaZuvu3Z8gWxFZMRoWVPcLIZsZBcYrHxCY2/JoLMRDxPke7RLOkY7mTexA19HT40 +eytgGpD5RXaNP25CepogAPa2qgjGdpwjMdhyG87IUk+DRJjg2LQRhmw1ZL2gYNBR+ JJh2AEXpeFAqhO7xYqunC5Mgq+YsDx8dAxXCfpxI= From: Beau Belgrave To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, beaub@linux.microsoft.com Subject: [PATCH v1 1/3] libtracefs: Add user_events to libtracefs sources Date: Fri, 18 Feb 2022 14:50:56 -0800 Message-Id: <20220218225058.12701-2-beaub@linux.microsoft.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20220218225058.12701-1-beaub@linux.microsoft.com> References: <20220218225058.12701-1-beaub@linux.microsoft.com> Precedence: bulk List-ID: X-Mailing-List: linux-trace-devel@vger.kernel.org Adds the required APIs to libtracefs to create, manage and write out data to trace events via the user_events kernel mechanism. Signed-off-by: Beau Belgrave --- Makefile | 8 + include/tracefs-local.h | 24 ++ include/tracefs.h | 60 +++++ src/Makefile | 4 + src/tracefs-userevents.c | 545 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 641 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..e768cba 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 +{ + int write_index; + int status_index; + int iovecs; + int rels; + int len; + struct tracefs_user_event_group *group; + struct tracefs_user_event *next; +}; + +struct tracefs_user_event_group +{ + int fd; + int mmap_len; + char *mmap; + pthread_mutex_t lock; + struct tracefs_user_event *events; +}; + #endif /* _TRACE_FS_LOCAL_H */ diff --git a/include/tracefs.h b/include/tracefs.h index 1848ad0..7871dfe 100644 --- a/include/tracefs.h +++ b/include/tracefs.h @@ -571,4 +571,64 @@ 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; +struct tracefs_user_event_group; + +struct tracefs_user_event_group *tracefs_user_event_group_create(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); + +bool tracefs_user_event_test(struct tracefs_user_event *event); + +int tracefs_user_event_write(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..4d64fd8 --- /dev/null +++ b/src/tracefs-userevents.c @@ -0,0 +1,545 @@ +// 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 *event) +{ + struct tracefs_user_event *next; + + while (event) { + next = event->next; + free(event); + event = next; + } +} + +#define LEN_OR_ZERO (len ? len - pos : 0) +static int append_field(struct tracefs_uevent_item *item, char *buf, + int len, int offset, int index) +{ + int pos = offset; + + if (index != 0) + pos += snprintf(buf + pos, LEN_OR_ZERO, ";"); + + switch (item->type) { + case TRACEFS_UEVENT_u8: + pos += snprintf(buf + pos, LEN_OR_ZERO, + " u8 %s", item->name); + break; + + case TRACEFS_UEVENT_s8: + pos += snprintf(buf + pos, LEN_OR_ZERO, + " s8 %s", item->name); + break; + + case TRACEFS_UEVENT_u16: + pos += snprintf(buf + pos, LEN_OR_ZERO, + " u16 %s", item->name); + break; + + case TRACEFS_UEVENT_s16: + pos += snprintf(buf + pos, LEN_OR_ZERO, + " s16 %s", item->name); + break; + + case TRACEFS_UEVENT_u32: + pos += snprintf(buf + pos, LEN_OR_ZERO, + " u32 %s", item->name); + break; + + case TRACEFS_UEVENT_s32: + pos += snprintf(buf + pos, LEN_OR_ZERO, + " s32 %s", item->name); + break; + + case TRACEFS_UEVENT_u64: + pos += snprintf(buf + pos, LEN_OR_ZERO, + " u64 %s", item->name); + break; + + case TRACEFS_UEVENT_s64: + pos += snprintf(buf + pos, LEN_OR_ZERO, + " s64 %s", item->name); + break; + + case TRACEFS_UEVENT_string: + if (item->len <= 0) { + errno = EINVAL; + return -1; + } + + pos += snprintf(buf + pos, LEN_OR_ZERO, + " 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; + } + + pos += snprintf(buf + pos, LEN_OR_ZERO, + " struct %s %d", item->name, item->len); + break; + + case TRACEFS_UEVENT_varray: + /* Variable length array */ + pos += snprintf(buf + pos, LEN_OR_ZERO, + " __rel_loc u8[] %s", item->name); + break; + + case TRACEFS_UEVENT_vstring: + /* Variable length string */ + pos += snprintf(buf + pos, LEN_OR_ZERO, + " __rel_loc char[] %s", item->name); + break; + + default: + /* Unknown */ + errno = ENOENT; + return -1; + } + + return pos; +} + +static int create_reg_cmd(const char *name, enum tracefs_uevent_flags flags, + struct tracefs_uevent_item *items, char *buf, int len) +{ + int pos = 0; + int index = 0; + + pos += snprintf(buf + pos, LEN_OR_ZERO, "%s", name); + + if (flags & TRACEFS_UEVENT_FLAG_bpf_iter) + pos += snprintf(buf + pos, LEN_OR_ZERO, ":BPF_ITER"); + + while (items->type != TRACEFS_UEVENT_END) { + pos = append_field(items, buf, len, pos, index++); + + if (pos < 0) + return pos; + + items++; + } + + return pos + 1; +} +#undef LEN_OR_ZERO + +static int get_write_counts(struct tracefs_user_event *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_create - Create 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_create(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 *event = NULL; + struct user_reg reg = {0}; + char *cmd = NULL; + int len; + + if (!group || !items) { + errno = EINVAL; + return NULL; + } + + /* Determine length of cmd */ + len = create_reg_cmd(name, flags, items, cmd, 0); + + if (len < 0) { + errno = EINVAL; + return NULL; + } + + /* Allocate and fill cmd */ + cmd = malloc(len); + + if (!cmd) + return NULL; + + create_reg_cmd(name, flags, items, cmd, len); + + event = malloc(sizeof(*event)); + + if (!event) + goto put_cmd; + + reg.size = sizeof(reg); + reg.name_args = (__u64)cmd; + + /* 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; + + event->write_index = reg.write_index; + event->status_index = reg.status_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); + + free(cmd); + + return event; +put_event: + free(event); +put_cmd: + free(cmd); + + return NULL; +} + +/** + * tracefs_user_event_test - Tests if an event is currently enabled + * @event: User event to test + * + * Tests if the @event is valid and currently enabled on the system. + * + * Return true if enabled, false otherwise. + */ +bool tracefs_user_event_test(struct tracefs_user_event *event) +{ + return event && event->group->mmap[event->status_index] != 0; +} + +/** + * tracefs_user_event_write - Writes data out to an event + * @event: User event to write data about + * @items: Items to write for the event + * + * Writes out items for the event. Callers should check if the cost of writing + * should be performed by calling tracefs_user_event_test(). Items are checked + * to ensure they fit within the described items during register. Each item + * must specify the length of the item being written. + * + * Return the number of bytes written 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_write(struct tracefs_user_event *event, + struct tracefs_uevent_item *items) +{ + 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; + } + + head = io = alloca(sizeof(*io) * (event->iovecs + event->rels)); + rel = alloca(sizeof(*rel) * event->rels); + + io_end = head + (event->iovecs + event->rels); + rel_end = rel + event->rels; + + /* Relative offset starts at end of static data */ + relio = io + event->iovecs; + rel_offset = event->len; + data_offset = 0; + + /* Write index must be first */ + io->iov_base = &event->write_index; + io->iov_len = sizeof(event->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(event->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 Fri Feb 18 22:50:57 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Beau Belgrave X-Patchwork-Id: 12751995 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 279C9C4332F for ; Fri, 18 Feb 2022 22:51:07 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S240168AbiBRWvX (ORCPT ); Fri, 18 Feb 2022 17:51:23 -0500 Received: from mxb-00190b01.gslb.pphosted.com ([23.128.96.19]:52224 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S240169AbiBRWvV (ORCPT ); Fri, 18 Feb 2022 17:51:21 -0500 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 3735D273754 for ; Fri, 18 Feb 2022 14:51:03 -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 E8EFD20C2809; Fri, 18 Feb 2022 14:51:02 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com E8EFD20C2809 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1645224663; bh=pTt4SFPanZ+4TGk9MvCDXlW6zxBjVjXe1WczoRwqniQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=p+8o2WjRdCxWwhLxJgJGYamKRSJBe9FwcGKCM4pb3Jl0xtePQtBIeQOZ2AGicui+5 fIWVi/fMSIir+4v5ULIaqpiROW/Pbh5I5Em+wvW5lPQeYDkPAIU+JmB4xl4fZMRkmz 0XoIMaK1o+xafpyJdbWMEJRWc9MRfw3Qp8xtcP6A= From: Beau Belgrave To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, beaub@linux.microsoft.com Subject: [PATCH v1 2/3] libtracefs: Add documentation and sample code for user_events Date: Fri, 18 Feb 2022 14:50:57 -0800 Message-Id: <20220218225058.12701-3-beaub@linux.microsoft.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20220218225058.12701-1-beaub@linux.microsoft.com> References: <20220218225058.12701-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 | 258 ++++++++++++++++++++++++ samples/Makefile | 4 + 2 files changed, 262 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..3d43ed7 --- /dev/null +++ b/Documentation/libtracefs-userevents.txt @@ -0,0 +1,258 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_user_event_group_create, tracefs_user_event_group_close, tracefs_user_event_register, +tracefs_user_event_test, tracefs_user_event_write, 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_create*(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_test*(struct tracefs_user_event pass:[*]_event_); +int *tracefs_user_event_write*(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_test*() function should be used for this. +When the event is to be traced out with data the *tracefs_user_event_write*() +function is to be used. Processes should delay any work related to the write +calls until after *tracefs_user_event_test*() 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_create*() allocates and initializes a group to be used. +Group creation 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_test*() 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_write*() writes data out 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_create*() 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_test*() returns true if the event is currently being traced. + +*tracefs_user_event_write*() reuturns the number of bytes written 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_create(); + + 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_test(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_write(event, items); + + printf("Event enabled, wrote '%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 Fri Feb 18 22:50:58 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Beau Belgrave X-Patchwork-Id: 12751996 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 E7CF1C433EF for ; Fri, 18 Feb 2022 22:51:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S240166AbiBRWvW (ORCPT ); Fri, 18 Feb 2022 17:51:22 -0500 Received: from mxb-00190b01.gslb.pphosted.com ([23.128.96.19]:52226 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S240168AbiBRWvV (ORCPT ); Fri, 18 Feb 2022 17:51:21 -0500 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 62534273773 for ; Fri, 18 Feb 2022 14:51:03 -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 23A8B20C280E; Fri, 18 Feb 2022 14:51:03 -0800 (PST) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 23A8B20C280E DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1645224663; bh=RymwtYd6locQp74i9L/sw8SqsXepDIq+OvA9ke+PQTk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tARLZpRnpgCow6pM7PwwDFB9igX+VAUZv42+BNuHr/KZfVaPLHflBtVeTfbclEfz4 npd9cG2xDT/fASnjG73qJBnRsIcYA0I1+ntT+QWIBdjGnSANKixtcpa0XJzot27dc2 Zew6yj3HRQH5kDLUu8a7H2R5lFkkhOub7nXaW4Ic= From: Beau Belgrave To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, beaub@linux.microsoft.com Subject: [PATCH v1 3/3] libtracefs: Add unit tests for user_events Date: Fri, 18 Feb 2022 14:50:58 -0800 Message-Id: <20220218225058.12701-4-beaub@linux.microsoft.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20220218225058.12701-1-beaub@linux.microsoft.com> References: <20220218225058.12701-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..0c4fd1e 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_create(); + 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_test(event)); + CU_TEST(tracefs_event_enable(instance, system, name) == 0); + CU_TEST(tracefs_user_event_test(event)); + + /* Correct write should work */ + CU_TEST(tracefs_user_event_write(event, write_items) > 0); + + /* Ensure write output correctly */ + 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_write(event, write_items) == -1); + + /* Simulate bad pointer */ + write_items[0].len = sizeof(a); + write_items[0].data = NULL; + CU_TEST(tracefs_user_event_write(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 }