diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 07:26:15 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 07:26:15 +0000 |
commit | c72e01b9ea891fa5cbbbe9876bdfb49079b55be7 (patch) | |
tree | 44c55a81a281a6c211745c0abc68352aeaf0180f | |
parent | Initial commit. (diff) | |
download | libtracefs-c72e01b9ea891fa5cbbbe9876bdfb49079b55be7.tar.xz libtracefs-c72e01b9ea891fa5cbbbe9876bdfb49079b55be7.zip |
Adding upstream version 1.6.4.upstream/1.6.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
84 files changed, 30187 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e72a58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*~ +\#*\# +.pc +libtracefs.pc +TAGS +tags +lib/ +bin/ +patches/ +build_prefix +build_uninstall +tfs_version.h +*.o +.*.d +sqlhist diff --git a/Documentation/.gitignore b/Documentation/.gitignore new file mode 100644 index 0000000..4d2414d --- /dev/null +++ b/Documentation/.gitignore @@ -0,0 +1,5 @@ +*.3 +sqlhist.1 +*.m +*.xml +*.html diff --git a/Documentation/Makefile b/Documentation/Makefile new file mode 100644 index 0000000..afcb6a5 --- /dev/null +++ b/Documentation/Makefile @@ -0,0 +1,247 @@ +# SPDX-License-Identifier: LGPL-2.1 + +include $(src)/scripts/utils.mk + + +# This Makefile and manpage XSL files were taken from libtraceevent +# and modified for libtracefs. + +MAN3_TXT= \ + $(wildcard libtracefs-*.txt) \ + libtracefs.txt + +MAN1_TEXT= \ + $(wildcard libtracefs-*.txt.1) + +MAN_TXT = $(MAN3_TXT) +_MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT)) +_MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT)) +_DOC_MAN3=$(patsubst %.txt,%.m,$(MAN3_TXT)) + +MAN_XML=$(addprefix $(OUTPUT),$(_MAN_XML)) +MAN_HTML=$(addprefix $(OUTPUT),$(_MAN_HTML)) +DOC_MAN3=$(addprefix $(OUTPUT),$(_DOC_MAN3)) + +_MAN1_XML=$(patsubst %.txt.1,%.xml,$(MAN1_TEXT)) +_MAN1_HTML=$(patsubst %.txt.1,%.html,$(MAN1_TEXT)) +_DOC_MAN1=$(patsubst %.txt.1,%.m,$(MAN1_TEXT)) + +MAN1_XML=$(addprefix $(OUTPUT),$(_MAN1_XML)) +MAN1_HTML=$(addprefix $(OUTPUT),$(_MAN1_HTML)) +DOC_MAN1=$(addprefix $(OUTPUT),$(_DOC_MAN1)) + + +# Make the path relative to DESTDIR, not prefix +ifndef DESTDIR +prefix?=$(HOME) +endif +bindir?=$(prefix)/bin +htmldir?=$(prefix)/share/doc/libtracefs-doc +pdfdir?=$(prefix)/share/doc/libtracefs-doc +mandir?=$(prefix)/share/man +man3dir=$(mandir)/man3 +man1dir=$(mandir)/man1 + +ASCIIDOC=asciidoc +ASCIIDOC_EXTRA = --unsafe -f asciidoc.conf +ASCIIDOC_HTML = xhtml11 +MANPAGE_XSL = manpage-normal.xsl +XMLTO_EXTRA = +INSTALL?=install +RM ?= rm -f + +ifdef USE_ASCIIDOCTOR +ASCIIDOC = asciidoctor +ASCIIDOC_EXTRA = -a compat-mode +ASCIIDOC_EXTRA += -I. -rasciidoctor-extensions +ASCIIDOC_EXTRA += -a mansource="libtracefs" -a manmanual="libtracefs Manual" +ASCIIDOC_HTML = xhtml5 +endif + +ASCIIDOC_INSTALLED := $(shell command -v $(ASCIIDOC) 2> /dev/null) +ifndef ASCIIDOC_INSTALLED + missing_tools += $(ASCIIDOC) +endif + +XMLTO=xmlto +XMLTO_INSTALLED := $(shell command -v $(XMLTO) 2> /dev/null) +ifndef XMLTO_INSTALLED + missing_tools += $(XMLTO) +endif + +# +# For asciidoc ... +# -7.1.2, no extra settings are needed. +# 8.0-, set ASCIIDOC8. +# + +# +# For docbook-xsl ... +# -1.68.1, set ASCIIDOC_NO_ROFF? (based on changelog from 1.73.0) +# 1.69.0, no extra settings are needed? +# 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP? +# 1.71.1, no extra settings are needed? +# 1.72.0, set DOCBOOK_XSL_172. +# 1.73.0-, set ASCIIDOC_NO_ROFF +# + +# +# If you had been using DOCBOOK_XSL_172 in an attempt to get rid +# of 'the ".ft C" problem' in your generated manpages, and you +# instead ended up with weird characters around callouts, try +# using ASCIIDOC_NO_ROFF instead (it works fine with ASCIIDOC8). +# + +ifdef ASCIIDOC8 +ASCIIDOC_EXTRA += -a asciidoc7compatible +endif +ifdef DOCBOOK_XSL_172 +ASCIIDOC_EXTRA += -a libtracefs-asciidoc-no-roff +MANPAGE_XSL = manpage-1.72.xsl +else + ifdef ASCIIDOC_NO_ROFF + # docbook-xsl after 1.72 needs the regular XSL, but will not + # pass-thru raw roff codes from asciidoc.conf, so turn them off. + ASCIIDOC_EXTRA += -a libtracefs-asciidoc-no-roff + endif +endif +ifdef MAN_BOLD_LITERAL +XMLTO_EXTRA += -m manpage-bold-literal.xsl +endif +ifdef DOCBOOK_SUPPRESS_SP +XMLTO_EXTRA += -m manpage-suppress-sp.xsl +endif + +SHELL_PATH ?= $(SHELL) +# Shell quote; +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) + +DESTDIR ?= +DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))' + +export DESTDIR DESTDIR_SQ + +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring $(MAKEFLAGS),w),w) +PRINT_DIR = --no-print-directory +else # "make -w" +NO_SUBDIR = : +endif + +ifneq ($(findstring $(MAKEFLAGS),s),s) +ifneq ($(V),1) + QUIET_ASCIIDOC = @echo ' ASCIIDOC '$@; + QUIET_XMLTO = @echo ' XMLTO '$@; + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) \ + echo ' SUBDIR ' $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir + export V +endif +endif + +all: check-man-tools html man + +man: man3 man1 +man3: $(DOC_MAN3) +man1: $(DOC_MAN1) + +html: $(MAN_HTML) $(MAN1_HTML) + +$(MAN_HTML) $(MAN1_HTML) $(DOC_MAN3) $(DOC_MAN1): asciidoc.conf + +install: check-man-tools install-man install-html + +check-man-tools: +ifdef missing_tools + $(error "You need to install $(missing_tools) for man pages") +endif + +install-%.3: $(OUTPUT)%.3 + $(Q)$(call do_install,$<,$(man3dir),644); + +install-%.1: $(OUTPUT)%.1 + $(Q)$(call do_install,$<,$(man1dir),644); + +do-install-man: man $(addprefix install-,$(wildcard $(OUTPUT)*.3)) $(addprefix install-,$(wildcard $(OUTPUT)*.1)) + +install-man: man + $(Q)$(MAKE) -C . do-install-man + +install-%.txt: $(OUTPUT)%.html + $(Q)$(call do_install,$<,$(htmldir),644); + +install-%.txt.1: $(OUTPUT)%.html + $(Q)$(call do_install,$<,$(htmldir),644); + +do-install-html: html $(addprefix install-,$(wildcard *.txt)) $(addprefix install-,$(wildcard *.txt.1)) + +install-html: html do-install-html + +uninstall: uninstall-man uninstall-html + +uninstall-man: + $(Q)$(RM) $(addprefix $(DESTDIR)$(man3dir)/,$(DOC_MAN3)) $(addprefix $(DESTDIR)$(man1dir)/,$(DOC_MAN1)) + +uninstall-html: + $(Q)$(RM) $(addprefix $(DESTDIR)$(htmldir)/,$(MAN_HTML)) $(addprefix $(DESTDIR)$(htmldir)/,$(MAN1_HTML)) + +ifdef missing_tools + DO_INSTALL_MAN = $(warning Please install $(missing_tools) to have the man pages installed) +else + DO_INSTALL_MAN = do-install-man +endif + +CLEAN_FILES = \ + $(MAN_XML) $(addsuffix +,$(MAN_XML)) \ + $(MAN_HTML) $(addsuffix +,$(MAN_HTML)) \ + $(MAN1_HTML) $(addsuffix +,$(MAN1_HTML)) \ + $(filter-out $(MAN1_TEXT),$(wildcard *.1)) \ + $(DOC_MAN3) *.3 *.m + +clean: + $(Q) $(RM) $(CLEAN_FILES) + +ifdef USE_ASCIIDOCTOR +$(OUTPUT)%.3 : $(OUTPUT)%.txt + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + $(ASCIIDOC) -b manpage -d manpage \ + $(ASCIIDOC_EXTRA) -alibtracefs_version=$(TRACEFS_VERSION) -o $@+ $< && \ + mv $@+ $@ +$(OUTPUT)%.1 : $(OUTPUT)%.txt.1 + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + $(ASCIIDOC) -b manpage -d manpage \ + $(ASCIIDOC_EXTRA) -alibtracefs_version=$(TRACEFS_VERSION) -o $@+ $< && \ + mv $@+ $@ +endif + +$(OUTPUT)%.m : $(OUTPUT)%.xml + $(QUIET_XMLTO)$(RM) $@ && \ + $(XMLTO) -o $(OUTPUT). -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<; \ + touch $@ + +$(OUTPUT)%.xml : %.txt + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + $(ASCIIDOC) -b docbook -d manpage \ + $(ASCIIDOC_EXTRA) -alibtracefs_version=$(TRACEFS_VERSION) -o $@+ $< && \ + mv $@+ $@ + +$(OUTPUT)%.xml : %.txt.1 + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + $(ASCIIDOC) -b docbook -d manpage \ + $(ASCIIDOC_EXTRA) -alibtracefs_version=$(TRACEFS_VERSION) -o $@+ $< && \ + mv $@+ $@ + +$(MAN_HTML): $(OUTPUT)%.html : %.txt + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + $(ASCIIDOC) -b $(ASCIIDOC_HTML) -d manpage \ + $(ASCIIDOC_EXTRA) -alibtracefs_version=$(TRACEFS_VERSION) -o $@+ $< && \ + mv $@+ $@ + +$(MAN1_HTML): $(OUTPUT)%.html : %.txt.1 + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + $(ASCIIDOC) -b $(ASCIIDOC_HTML) -d manpage \ + $(ASCIIDOC_EXTRA) -alibtracefs_version=$(TRACEFS_VERSION) -o $@+ $< && \ + mv $@+ $@ diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf new file mode 100644 index 0000000..c15aa13 --- /dev/null +++ b/Documentation/asciidoc.conf @@ -0,0 +1,120 @@ +## linktep: macro +# +# Usage: linktep:command[manpage-section] +# +# Note, {0} is the manpage section, while {target} is the command. +# +# Show TEP link as: <command>(<section>); if section is defined, else just show +# the command. + +[macros] +(?su)[\\]?(?P<name>linktep):(?P<target>\S*?)\[(?P<attrlist>.*?)\]= + +[attributes] +asterisk=* +plus=+ +caret=^ +startsb=[ +endsb=] +tilde=~ + +ifdef::backend-docbook[] +[linktep-inlinemacro] +{0%{target}} +{0#<citerefentry>} +{0#<refentrytitle>{target}</refentrytitle><manvolnum>{0}</manvolnum>} +{0#</citerefentry>} +endif::backend-docbook[] + +ifdef::backend-docbook[] +ifndef::tep-asciidoc-no-roff[] +# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this. +# v1.72 breaks with this because it replaces dots not in roff requests. +[listingblock] +<example><title>{title}</title> +<literallayout> +ifdef::doctype-manpage[] + .ft C +endif::doctype-manpage[] +| +ifdef::doctype-manpage[] + .ft +endif::doctype-manpage[] +</literallayout> +{title#}</example> +endif::tep-asciidoc-no-roff[] + +ifdef::tep-asciidoc-no-roff[] +ifdef::doctype-manpage[] +# The following two small workarounds insert a simple paragraph after screen +[listingblock] +<example><title>{title}</title> +<literallayout> +| +</literallayout><simpara></simpara> +{title#}</example> + +[verseblock] +<formalpara{id? id="{id}"}><title>{title}</title><para> +{title%}<literallayout{id? id="{id}"}> +{title#}<literallayout> +| +</literallayout> +{title#}</para></formalpara> +{title%}<simpara></simpara> +endif::doctype-manpage[] +endif::tep-asciidoc-no-roff[] +endif::backend-docbook[] + +ifdef::doctype-manpage[] +ifdef::backend-docbook[] +[header] +template::[header-declarations] +<refentry> +<refmeta> +<refentrytitle>{mantitle}</refentrytitle> +<manvolnum>{manvolnum}</manvolnum> +<refmiscinfo class="source">libtracefs</refmiscinfo> +<refmiscinfo class="version">{libtracefs_version}</refmiscinfo> +<refmiscinfo class="manual">libtracefs Manual</refmiscinfo> +</refmeta> +<refnamediv> + <refname>{manname1}</refname> + <refname>{manname2}</refname> + <refname>{manname3}</refname> + <refname>{manname4}</refname> + <refname>{manname5}</refname> + <refname>{manname6}</refname> + <refname>{manname7}</refname> + <refname>{manname8}</refname> + <refname>{manname9}</refname> + <refname>{manname10}</refname> + <refname>{manname11}</refname> + <refname>{manname12}</refname> + <refname>{manname13}</refname> + <refname>{manname14}</refname> + <refname>{manname15}</refname> + <refname>{manname16}</refname> + <refname>{manname17}</refname> + <refname>{manname18}</refname> + <refname>{manname19}</refname> + <refname>{manname20}</refname> + <refname>{manname21}</refname> + <refname>{manname22}</refname> + <refname>{manname23}</refname> + <refname>{manname24}</refname> + <refname>{manname25}</refname> + <refname>{manname26}</refname> + <refname>{manname27}</refname> + <refname>{manname28}</refname> + <refname>{manname29}</refname> + <refname>{manname30}</refname> + <refpurpose>{manpurpose}</refpurpose> +</refnamediv> +endif::backend-docbook[] +endif::doctype-manpage[] + +ifdef::backend-xhtml11[] +[linktep-inlinemacro] +<a href="{target}.html">{target}{0?({0})}</a> +endif::backend-xhtml11[] diff --git a/Documentation/libtracefs-cpu-open.txt b/Documentation/libtracefs-cpu-open.txt new file mode 100644 index 0000000..c5a900a --- /dev/null +++ b/Documentation/libtracefs-cpu-open.txt @@ -0,0 +1,100 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_cpu_open, tracefs_cpu_close, tracefs_cpu_alloc_fd, tracefs_cpu_free_fd - Opening trace_pipe_raw data for reading + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +struct tracefs_cpu pass:[*]*tracefs_cpu_open*(struct tracefs_instance pass:[*]_instance_, + int _cpu_, bool _nonblock_); +void *tracefs_cpu_close*(struct tracefs_cpu pass:[*]_tcpu_); + +struct tracefs_cpu pass:[*]*tracefs_cpu_alloc_fd*(int _fd_, int _subbuf_size_, bool _nonblock_); +void *tracefs_cpu_free_fd*(struct tracefs_cpu pass:[*]_tcpu_); +-- + +DESCRIPTION +----------- +This set of APIs can be used to open the raw data from the trace_pipe_raw +files in the tracefs file system in oder to read them with the *tracefs_cpu_read*(3) +functions. + +The *tracefs_cpu_open()* creates a descriptor that can read the tracefs +trace_pipe_raw file for a given _cpu_ in a given _instance_. If _instance_ is +NULL than the toplevel trace_pipe_raw file is used. + +The *tracefs_cpu_close()* closes all the file descriptors associated to the trace_pipe_raw +opened by *tracefs_cpu_open()*. + +The *tracefs_cpu_alloc_fd()* will create a tracefs_cpu descriptor from an existing +file descriptor _fd_. This is useful to use when connecting to a socket or pipe where +the other end is feeding raw tracing data in the same format as the trace_pipe_raw +file would (like in guest to host tracing). The caller is responsible for determining +the _subbuf_size_ that will be used to break up the sub-buffers being read by the +file descriptor. The _nonblock_ is treated the same as the same parameter in +*tracefs_cpu_open()*. + +The *tracefs_cpu_free_fd()* is used to free the descriptor returned by *tracefs_cpu_alloc_fd()*. +It does all the clean up that *tracefs_cpu_close()* performs, and that could also be +used to free up the descriptor created by *tracefs_cpu_alloc_fd()* but will also close +the file descriptor passed in. Note that *tracefs_cpu_free_fd()* should not be used +on the descriptor returned by *tracefs_cpu_open()* as it will not close the file descriptor +created by it. + +RETURN VALUE +------------ +The *tracefs_cpu_open()* returns a struct tracefs_cpu descriptor that can be +used by the other functions or NULL on error. + +The *tracefs_cpu_alloc_fd()* returns a struct tracefs_cpu descriptor that can +be used by the *tracefs_cpu_read*(3) related functions, where the descriptor +will be reading the passed in _fd_ file descriptor. + +EXAMPLE +------- +See *tracefs_cpu_read*(3) for an example. + +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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 Google, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-cpu.txt b/Documentation/libtracefs-cpu.txt new file mode 100644 index 0000000..d6215d9 --- /dev/null +++ b/Documentation/libtracefs-cpu.txt @@ -0,0 +1,240 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_cpu_read_size, tracefs_cpu_read, tracefs_cpu_buffered_read, tracefs_cpu_write, +tracefs_cpu_stop, tracefs_cpu_flush, tracefs_cpu_flush_write, tracefs_cpu_pipe +- Reading trace_pipe_raw data + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_cpu_read_size*(struct tracefs_cpu pass:[*]_tcpu_); +int *tracefs_cpu_read*(struct tracefs_cpu pass:[*]_tcpu_, void pass:[*]_buffer_, bool _nonblock_); +int *tracefs_cpu_buffered_read*(struct tracefs_cpu pass:[*]_tcpu_, void pass:[*]_buffer_, bool _nonblock_); +int *tracefs_cpu_write*(struct tracefs_cpu pass:[*]_tcpu_, int _wfd_, bool _nonblock_); +int *tracefs_cpu_stop*(struct tracefs_cpu pass:[*]_tcpu_); +int *tracefs_cpu_flush*(struct tracefs_cpu pass:[*]_tcpu_, void pass:[*]_buffer_); +int *tracefs_cpu_flush_write*(struct tracefs_cpu pass:[*]_tcpu_, int _wfd_); +int *tracefs_cpu_pipe*(struct tracefs_cpu pass:[*]_tcpu_, int _wfd_, bool _nonblock_); +-- + +DESCRIPTION +----------- +This set of APIs can be used to read the raw data from the trace_pipe_raw +files in the tracefs file system. + +The *tracefs_cpu_read_size()* returns the subbuffer size of the trace_pipe_raw. This +returns the minimum size of the buffer that is passed to the below functions. + +The *tracefs_cpu_read()* reads the trace_pipe_raw files associated to _tcpu_ into _buffer_. +_buffer_ must be at least the size of the sub buffer of the ring buffer, +which is returned by *tracefs_cpu_read_size()*. If _nonblock_ is set, and +there's no data available, it will return immediately. Otherwise depending +on how _tcpu_ was opened, it will block. If _tcpu_ was opened with nonblock +set, then this _nonblock_ will make no difference. + +The *tracefs_cpu_buffered_read()* is basically the same as *tracefs_cpu_read()* +except that it uses a pipe through splice to buffer reads. This will batch +reads keeping the reading from the ring buffer less intrusive to the system, +as just reading all the time can cause quite a disturbance. Note, one +difference between this and *tracefs_cpu_read()* is that it will read only in +sub buffer pages. If the ring buffer has not filled a page, then it will not +return anything, even with _nonblock_ set. Calls to *tracefs_cpu_flush()* +should be done to read the rest of the file at the end of the trace. + +The *tracefs_cpu_write()* will pipe the data from the trace_pipe_raw +file associated with _tcpu_ into the _wfd_ file descriptor. If _nonblock_ is set, +then it will not block on if there's nothing to write. Note, it will only write +sub buffer size data to _wfd_. Calls to tracefs_cpu_flush_write() are needed to +write out the rest. + +The *tracefs_cpu_stop()* will attempt to unblock a task blocked on _tcpu_ reading it. +On older kernels, it may not do anything for the pipe reads, as older kernels do not +wake up tasks waiting on the ring buffer. Returns 0 if it definitely woke up any possible +waiters, but returns 1 if it is not sure it worked and waiters may need to have a signal +sent to them. + +The *tracefs_cpu_flush()* reads the trace_pipe_raw file associated by the _tcpu_ and puts it +into _buffer_, which must be the size of the sub buffer which is retrieved. +by *tracefs_cpu_read_size()*. This should be called at the end of tracing +to get the rest of the data. This call will convert the file descriptor of +trace_pipe_raw into non-blocking mode. + +The *tracefs_cpu_flush_write()* same as *trace_cpu_flush()* except it takes a file +descriptor _wfd_ to flush the data into. + +The *tracefs_cpu_pipe()* is similar to *tracefs_cpu_write()* but the _wfd_ file descriptor +must be a pipe. This call is an optimization of *tracefs_cpu_write()* that uses two calls +to *splice*(2) in order to connect the trace_pipe_raw file descriptor with the write file +descriptor. *splice*(2) requires that one of the passed in file descriptors is a pipe. +If the application wants to pass the data to an existing pipe, there's no reason for +there to be two *splice*(2) system calls and *tracefs_cpu_pipe()* can simply use a single +call to _wfd_. + +RETURN VALUE +------------ +The *tracefs_cpu_open()* returns a struct tracefs_cpu descriptor that can be +used by the other functions or NULL on error. + +The *tracefs_cpu_read_size()* returns the minimum size of the buffers to be +used with *tracefs_cpu_read()*, *tracefs_cpu_buffered_read()* and *tracefs_cpu_flush()*. +Returns negative on error. + +The *tracefs_cpu_read()* returns the number of bytes read, or negative on error. + +The *tracefs_cpu_buffered_read()* returns the number of bytes read or negative on error. + +The *tracefs_cpu_write()* returns the number of bytes written to the file +or negative on error. + +The *tracefs_cpu_stop()* returns zero if any waiters were guaranteed to be +woken up from waiting on input, or returns one if this is an older kernel +that does not supply that guarantee, and a signal may need to be sent to +any waiters. Returns negative on error. + +The *tracefs_cpu_flush()* returns the number of bytes read or negative on error. + +The *tracefs_cpu_flush_write()* returns the number of bytes written to the +file or negative on error. + +EXAMPLE +------- +[source,c] +-- +#define _LARGEFILE64_SOURCE +#include <stdlib.h> +#include <ctype.h> +#include <pthread.h> +#include <unistd.h> +#include <tracefs.h> + +struct thread_data { + struct tracefs_cpu *tcpu; + int done; + int fd; +}; + +static void *thread_run(void *arg) +{ + struct thread_data *data = arg; + struct tracefs_cpu *tcpu = data->tcpu; + int fd = data->fd; + int ret; + + while (!data->done) { + ret = tracefs_cpu_write(tcpu, fd, false); + printf("wrote %d\n", ret); + } + return NULL; +} + +int main (int argc, char **argv) +{ + struct tracefs_instance *instance; + struct thread_data data; + pthread_t thread; + char *file; + int secs = 10; + int cpu; + int ret; + + if (argc < 3 || !isdigit(argv[1][0])) { + printf("usage: %s cpu file_destination [sleep secs]\n\n", argv[0]); + exit(-1); + } + + cpu = atoi(argv[1]); + file = argv[2]; + + if (argc > 3) + secs = atoi(argv[3]); + + instance = tracefs_instance_create("cpu_write"); + if (!instance) { + perror("create instance"); + exit(-1); + } + + memset(&data, 0, sizeof(data)); + + data.tcpu = tracefs_cpu_open(instance, cpu, 0); + if (!data.tcpu) { + perror("Open instance"); + exit(-1); + } + + data.fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, 0644); + if (data.fd < 0) { + perror(file); + exit(-1); + } + + pthread_create(&thread, NULL, thread_run, &data); + + sleep(secs); + + data.done = 1; + printf("stopping\n"); + ret = tracefs_cpu_stop(data.tcpu); + + printf("joining %d\n", ret); + pthread_join(thread, NULL); + + tracefs_trace_off(instance); + do { + ret = tracefs_cpu_flush_write(data.tcpu, data.fd); + printf("flushed %d\n", ret); + } while (ret > 0); + tracefs_trace_on(instance); + + tracefs_cpu_close(data.tcpu); + close(data.fd); + + 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 +-------- +*tracefs_cpu_open*(3) +*tracefs_cpu_close*(3) +*libtracefs*(3), +*libtraceevent*(3), +*trace-cmd*(1) + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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 Google, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-dynevents.txt b/Documentation/libtracefs-dynevents.txt new file mode 100644 index 0000000..2c6db47 --- /dev/null +++ b/Documentation/libtracefs-dynevents.txt @@ -0,0 +1,283 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_dynevent_create, tracefs_dynevent_destroy, tracefs_dynevent_destroy_all, +tracefs_dynevent_free, tracefs_dynevent_list_free, tracefs_dynevent_get, tracefs_dynevent_get_all, +tracefs_dynevent_info, tracefs_dynevent_get_event - Create, destroy, free and get dynamic events. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +struct *tracefs_dynevent*; +enum *tracefs_dynevent_type*; +int *tracefs_dynevent_create*(struct tracefs_dynevent pass:[*]_devent_); +int *tracefs_dynevent_destroy*(struct tracefs_dynevent pass:[*]_devent_, bool _force_); +int *tracefs_dynevent_destroy_all*(unsigned int _types_, bool _force_); +void *tracefs_dynevent_free*(struct tracefs_dynevent pass:[*]_devent_); +void *tracefs_dynevent_list_free*(struct tracefs_dynevent pass:[*]pass:[*]_events_); +struct tracefs_dynevent pass:[*]*tracefs_dynevent_get*(enum tracefs_dynevent_type _type_, const char pass:[*]_system_, const char pass:[*]_event_); +struct tracefs_dynevent pass:[*]pass:[*]*tracefs_dynevent_get_all*(unsigned int _types_, const char pass:[*]_system_); +enum tracefs_dynevent_type *tracefs_dynevent_info*(struct tracefs_dynevent pass:[*]_dynevent_, char pass:[*]pass:[*]_system_, char pass:[*]pass:[*]_event_, char pass:[*]pass:[*]_prefix_, char pass:[*]pass:[*]_addr_, char pass:[*]pass:[*]_format_); +struct tep_event pass:[*]*tracefs_dynevent_get_event*(struct tep_handle pass:[*]_tep_, struct tracefs_dynevent pass:[*]_dynevent_); +-- + +DESCRIPTION +----------- + +The *tracefs_dynevent_create*() function creates dynamic event _devent_ in the system. + +The *tracefs_dynevent_destroy*() function removes dynamic event _devent_ from the system. If _force_ +is true, the function will attempt to disable all events in all trace instances, before removing +the dynamic event. The _devent_ context is not freed, use *tracefs_dynevent_free*() to free it. + +The *tracefs_dynevent_destroy_all*() function removes all dynamic events of given types from the +system. The _types_ parameter is a type of specific dynamic event, or a bitmask of dynamic events +types *tracefs_dynevent_type*, that will be removed. If _types_ is 0, dynamic events from all types +will be removed. If _force_ is true, the function will attempt to disable all events in all trace +instances, before removing the dynamic events. + +The *tracefs_dynevent_get*() function allocates and returns a single instance of a dynamic +event that matches the given *type*, *system* and *event* that is passed to it. NULL is returned +if there is no match. The returned event is what is found in the system, and must be freed +with *tracefs_dynevent_free*(). If *system* is NULL, then the first *event* of any system +of the given type that has the name of *event* will be returned. + +The *tracefs_dynevent_get_all*() function allocates and returns an array of pointers to dynamic +events of given types that exist in the system. The last element of the array is a NULL pointer. +The array must be freed with *tracefs_dynevent_list_free*(). If there are no events a NULL pointer is +returned. The _types_ parameter is a type of specific dynamic event, or a bitmask of dynamic events +types *tracefs_dynevent_type*, that will be retrieved. If _types_ is 0, dynamic events from all +types will be retrieved. + +The *tracefs_dynevent_free*() function frees a dynamic event context _devent_. + +The *tracefs_dynevent_list_free*() function frees an array of pointers to dynamic event, returned +by *tracefs_dynevent_get_all()* API. + +The *tracefs_dynevent_info*() function returns the type and information of a given dynamic event +_dynevent_. If any of the _system_, _event_, _prefix_, _addr_ or _format_ arguments are not NULL, +then strings are allocated and returned back via these arguments. The _system_ and _event_ holds the +system and the name of the dynamic event. If _prefix_ is non NULL, then it will hold an allocated +string that holds the prefix portion of the dynamic event (the content up to the ":", exluding it). +If _addr_ is non NULL, it will hold the address or function that the dynamic event is attached to, +if relevant for this event type. If _format_ is non NULL, it will hold the format string of the +dynamic event. Note, that the content in _group_, _event_, _prefix_, _addr_, and _format_ must be +freed with free(3) if they are set. + +The *tracefs_dynevent_get_event*() function returns a tep event, describing the given dynamic event. +The API detects any newly created or removed dynamic events. The returned pointer to tep event is +controlled by @tep and must not be freed. + +RETURN VALUE +------------ + +*tracefs_dynevent_create*() returns 0 on success, or -1 on error. If a parsing error occurs then +*tracefs_error_last*(3) may be used to retrieve the error message explaining the parsing issue. + +*tracefs_dynevent_destroy*() and *tracefs_dynevent_destroy_all*() return 0 on success, or -1 on +error. If _force_ is enabled, the functions may fail on disabling the events. + +*tracefs_dynevent_get*() function returns an allocated dynamic event from the system that matches +the type, system and event given. + +*tracefs_dynevent_get_all*() function returns allocated array of pointers to dynamic events, or NULL +in case of an error or in case there are no events in the system. That array must be freed by +*tracefs_dynevent_list_free*(). + +*tracefs_dynevent_info*() returns the type of the given dynamic event or TRACEFS_DYNEVENT_UNKNOWN +on error. If _system_, _event_, _prefix_, _addr_, or _format_ are non NULL, they will contain +allocated strings that must be freed by free(3). + +The *tracefs_dynevent_get_event*() function returns a pointer to a tep event or NULL in case of an +error or if the requested dynamic event is missing. The returned pointer to tep event is controlled +by @tep and must not be freed. + +ERRORS +------ +The following errors are for all the above calls: + +*ENODEV* dynamic events of requested type are not configured for the running kernel. + +*ENOMEM* Memory allocation error. + +*tracefs_dynevent_create*() can fail with the following errors: + +*EINVAL* Most likely a parsing error occurred (use *tracefs_error_last*(3) to possibly + see what that error was). + +Other errors may also happen caused by internal system calls. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> + +#include <tracefs.h> + +static struct tep_event *open_event; +static struct tep_format_field *file_field; + +static struct tep_event *openret_event; +static struct tep_format_field *ret_field; + +static int callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct trace_seq seq; + + trace_seq_init(&seq); + tep_print_event(event->tep, &seq, record, "%d-%s: ", TEP_PRINT_PID, TEP_PRINT_COMM); + + if (event->id == open_event->id) { + trace_seq_puts(&seq, "open file='"); + tep_print_field(&seq, record->data, file_field); + trace_seq_puts(&seq, "'\n"); + } else if (event->id == openret_event->id) { + unsigned long long ret; + tep_read_number_field(ret_field, record->data, &ret); + trace_seq_printf(&seq, "open ret=%lld\n", ret); + } else { + goto out; + } + + trace_seq_terminate(&seq); + trace_seq_do_printf(&seq); +out: + trace_seq_destroy(&seq); + + return 0; +} + +static pid_t run_exec(char **argv, char **env) +{ + pid_t pid; + + pid = fork(); + if (pid) + return pid; + + execve(argv[0], argv, env); + perror("exec"); + exit(-1); +} + +const char *mykprobe = "my_kprobes"; + +int main (int argc, char **argv, char **env) +{ + struct tracefs_dynevent *kprobe, *kretprobe; + const char *sysnames[] = { mykprobe, NULL }; + struct tracefs_instance *instance; + struct tep_handle *tep; + pid_t pid; + + if (argc < 2) { + printf("usage: %s command\n", argv[0]); + exit(-1); + } + + instance = tracefs_instance_create("exec_open"); + if (!instance) { + perror("creating instance"); + exit(-1); + } + + tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, true); + + kprobe = tracefs_kprobe_alloc(mykprobe, "open", "do_sys_openat2", + "file=+0($arg2):ustring flags=+0($arg3):x64 mode=+8($arg3):x64\n"); + kretprobe = tracefs_kretprobe_alloc(mykprobe, "openret", "do_sys_openat2", "ret=%ax", 0); + if (!kprobe || !kretprobe) { + perror("allocating dynamic events"); + exit(-1); + } + + if (tracefs_dynevent_create(kprobe) || tracefs_dynevent_create(kretprobe)){ + char *err = tracefs_error_last(NULL); + perror("Failed to create kprobes:"); + if (err && strlen(err)) + fprintf(stderr, "%s\n", err); + exit(-1); + } + + tep = tracefs_local_events_system(NULL, sysnames); + if (!tep) { + perror("reading events"); + exit(-1); + } + open_event = tep_find_event_by_name(tep, mykprobe, "open"); + file_field = tep_find_field(open_event, "file"); + + openret_event = tep_find_event_by_name(tep, mykprobe, "openret"); + ret_field = tep_find_field(openret_event, "ret"); + + tracefs_event_enable(instance, mykprobe, NULL); + pid = run_exec(&argv[1], env); + + /* Let the child start to run */ + sched_yield(); + + do { + tracefs_load_cmdlines(NULL, tep); + tracefs_iterate_raw_events(tep, instance, NULL, 0, callback, NULL); + } while (waitpid(pid, NULL, WNOHANG) != pid); + + /* Will disable the events */ + tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, true); + tracefs_dynevent_free(kprobe); + tracefs_dynevent_free(kretprobe); + tracefs_instance_destroy(instance); + tep_free(tep); + + 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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*Yordan Karadzhov* <y.karadz@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2021 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-eprobes.txt b/Documentation/libtracefs-eprobes.txt new file mode 100644 index 0000000..a96ad1b --- /dev/null +++ b/Documentation/libtracefs-eprobes.txt @@ -0,0 +1,187 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_eprobe_alloc - Allocate new event probe (eprobe) + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +struct tracefs_dynevent pass:[*] +*tracefs_eprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_target_system_, const char pass:[*]_target_event_, + const char pass:[*]_fetchargs_); +-- + +DESCRIPTION +----------- +*tracefs_eprobe_alloc*() allocates a new eprobe context. The ebrobe is not configured in the system. +The new eprobe will be in the _system_ group (or eprobes if _system_ is NULL) and have the name of +_event_. The eprobe will be attached to _target_event_, located in _target_system_. The list of +arguments, described in _fetchargs_, will be fetched from _target_event_. The returned pointer to +the event probe must be freed with *tracefs_dynevent_free*(). + + +RETURN VALUE +------------ +The *tracefs_eprobe_alloc*() API returns a pointer to an allocated tracefs_dynevent structure, +describing the event probe. This pointer must be freed by *tracefs_dynevent_free*(3). Note, this +only allocates a descriptor representing the eprobe. It does not modify the running system. On error +NULL is returned. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> + +#include <tracefs.h> + +static struct tep_event *open_event; +static struct tep_format_field *file_field; + +static int callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct trace_seq seq; + + trace_seq_init(&seq); + tep_print_event(event->tep, &seq, record, "%d-%s: ", TEP_PRINT_PID, TEP_PRINT_COMM); + + if (event->id == open_event->id) { + trace_seq_puts(&seq, "open file='"); + tep_print_field(&seq, record->data, file_field); + trace_seq_puts(&seq, "'\n"); + } + + trace_seq_terminate(&seq); + trace_seq_do_printf(&seq); + trace_seq_destroy(&seq); + + return 0; +} + +static pid_t run_exec(char **argv, char **env) +{ + pid_t pid; + + pid = fork(); + if (pid) + return pid; + + execve(argv[0], argv, env); + perror("exec"); + exit(-1); +} + +const char *myprobe = "my_eprobes"; + +int main (int argc, char **argv, char **env) +{ + struct tracefs_dynevent *eprobe; + struct tracefs_instance *instance; + struct tep_handle *tep; + const char *sysnames[] = { myprobe, NULL }; + pid_t pid; + + if (argc < 2) { + printf("usage: %s command\n", argv[0]); + exit(-1); + } + + instance = tracefs_instance_create("exec_open"); + if (!instance) { + perror("creating instance"); + exit(-1); + } + + tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_EPROBE, true); + + eprobe = tracefs_eprobe_alloc(myprobe, "sopen", "syscalls", "sys_enter_openat2", + "file=+0($filename):ustring"); + if (!eprobe) { + perror("allocating event probe"); + exit(-1); + } + + if (tracefs_dynevent_create(eprobe)) { + perror("creating event probe"); + exit(-1); + } + + tep = tracefs_local_events_system(NULL, sysnames); + if (!tep) { + perror("reading events"); + exit(-1); + } + + open_event = tep_find_event_by_name(tep, myprobe, "sopen"); + file_field = tep_find_field(open_event, "file"); + + tracefs_event_enable(instance, myprobe, "sopen"); + pid = run_exec(&argv[1], env); + + /* Let the child start to run */ + sched_yield(); + + do { + tracefs_load_cmdlines(NULL, tep); + tracefs_iterate_raw_events(tep, instance, NULL, 0, callback, NULL); + } while (waitpid(pid, NULL, WNOHANG) != pid); + + /* Will disable the events */ + tracefs_dynevent_destroy(eprobe, true); + tracefs_dynevent_free(eprobe); + tracefs_instance_destroy(instance); + tep_free(tep); + + 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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- + +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2021 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-error.txt b/Documentation/libtracefs-error.txt new file mode 100644 index 0000000..a45332d --- /dev/null +++ b/Documentation/libtracefs-error.txt @@ -0,0 +1,137 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_error_last, tracefs_error_all, tracefs_error_clear - +functions to read and clear the tracefs error log. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +char pass:[*]*tracefs_error_last*(struct tracefs_instance pass:[*]_instance_); +char pass:[*]*tracefs_error_all*(struct tracefs_instance pass:[*]_instance_); +int *tracefs_error_clear*(struct tracefs_instance pass:[*]_instance_); +-- + +DESCRIPTION +----------- +The *tracefs_error_last*() returns the last error message in the tracefs +error log. Several actions that require proper syntax written into the +tracefs file system may produce error messages in the error log. This +function will show the most recent error in the error log. + +The *tracefs_error_all*() returns all messages saved in the error log. +Note, this may not be all messages that were ever produced, as the kernel +only keeps a limited amount of messages, and older ones may be discarded +by the kernel. + +The *tracefs_error_clear*() will clear the error log. + +RETURN VALUE +------------ +Both *tracefs_error_last*() and *tracefs_error_all*() will return an allocated +string an error exists in the log, otherwise NULL is returned. If an error +occurs, errno will be set, otherwise if there is no error messages to display +then errno is not touched. + +*tracefs_error_clear*() returns 0 on success or -1 on error. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + +#include <tracefs.h> + +int main (int argc, char **argv, char **env) +{ + struct tracefs_dynevent *kevent; + char *system = NULL; + char *kprobe; + char *format; + char *addr; + int arg = 1; + int ret; + + if (argc < 4) { + printf("usage: %s [system] kprobe addr fmt\n", argv[0]); + exit(-1); + } + + if (argc > 5) + system = argv[arg++]; + + kprobe = argv[arg++]; + addr = argv[arg++]; + format = argv[arg++]; + + tracefs_error_clear(NULL); + kevent = tracefs_dynevent_get(TRACEFS_DYNEVENT_KPROBE, system, kprobe); + if (kevent) { + tracefs_dynevent_destroy(kevent, true); + tracefs_dynevent_free(kevent); + } + + ret = tracefs_kprobe_raw(system, kprobe, addr, format); + if (ret < 0) { + char *err; + + perror("Failed creating kprobe"); + errno = 0; + err = tracefs_error_last(NULL); + if (err) + fprintf(stderr, "%s\n", err); + else if (errno) + perror("Failed reading error log"); + free(err); + } + + exit(ret); +} +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-events-file.txt b/Documentation/libtracefs-events-file.txt new file mode 100644 index 0000000..425eebd --- /dev/null +++ b/Documentation/libtracefs-events-file.txt @@ -0,0 +1,217 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_event_get_file, tracefs_event_file_read, tracefs_event_file_write, tracefs_event_file_append, +tracefs_event_file_clear, tracefs_event_file_exists - Work with trace event files. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +char pass:[*]*tracefs_event_get_file*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_); +char pass:[*]*tracefs_event_file_read*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_, int pass:[*]_psize_); +int *tracefs_event_file_write*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_, const char pass:[*]_str_); +int *tracefs_event_file_append*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_, const char pass:[*]_str_); +int *tracefs_event_file_clear*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_); +bool *tracefs_event_file_exists*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_) + +-- + +DESCRIPTION +----------- +These are functions for accessing tracefs event specific files. +These functions act similar to the tracefs instance file functions +but are easier to get to if the system and events are known before hand. + +The *tracefs_event_get_file()* returns the full path of the _file_ for +the given _system_ and _event_ that is within the given _instance_. +If _instance_ is NULL, then the file for the _event_ for the top level +instance is returned. Note, there is no check to see if the file actually +exists or even if the system and event exist. It only creates the path +name for such an event if it did exist. This acts similar to the +*tracefs_instance_get_file*(3), but is to be used to get to event files +if the _system_ and _event_ are already known. + +The *tracefs_event_file_read()* reads the content for the _event_ _file_ +for the given _instance_ or the top level instance if _instance_ is +NULL. The content of the file is returned and _psize_ is set to the amount +of data that was read. The returned content must be freed with *free*(3). +This acts similar to the *tracefs_instance_file_read*(3), but is +to be used to read the event file if the _system_ and _event_ are already +known. + +The *tracefs_event_file_write()* writes _str_ to the _event_ _file_. +It will truncate anything that is already in that file. +This acts similar to the *tracefs_instance_file_write*(3), but is +to be used to read the event file if the _system_ and _event_ are already +known. + +The *tracefs_event_file_append()* appends _str_ to the _event_ _file_. +It will not clear out the file as it writes _sting_. +This acts similar to the *tracefs_instance_file_append*(3), but is +to be used to read the event file if the _system_ and _event_ are already +known. + +The *tracefs_event_file_clear()* clears the content of the _event_ _file_. +This acts similar to the *tracefs_instance_file_clear*(3), but is +to be used to read the event file if the _system_ and _event_ are already +known. + +The *tracefs_event_file_exists()* returns true if the _event_ _file_ +exists, and false otherwise. This acts similar to the *tracefs_instance_file_exists*(3), +but is to be used to read the event file if the _system_ and _event_ are already +known. + +RETURN VALUE +------------ +*tracefs_event_get_file()* returns the path of the given _system_/_event_ _file_ on +success and NULL on error. The return value must be freed with *tracefs_put_tracing_file*(3). + +*tracefs_event_file_read()* reads the content of the _system_/_event_ _file_ or +NULL on error. The return pointer must be freed with *free*(3). + +*tracefs_event_file_write()* and *tracefs_event_file_append()* returns the number of +bytes written to the _system_/_event_ _file_ or negative on error. + +*tracefs_event_file_clear()* returns zero on success and -1 on error. + +*tracefs_event_file_exists()* returns true if the _system_/_event_ _file_ exists for +the given _instance_ (or top level if _instance_ is NULL) or false otherwise. + +EXAMPLE +------- +[source,c] +-- +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <tracefs.h> + +int main(int argc, char **argv) +{ + char *system; + char *event; + char *file; + char *cmd = NULL; + char *buf; + char *str; + char ch = 'r'; + int size; + + if (argc < 4) { + printf("usage: %s sytem event file [(-a|-w) write | -c]\n" + " reads the system/event file or writes if [write is supplied]\n", + argv[0]); + exit(0); + } + + system = argv[1]; + event = argv[2]; + file = argv[3]; + if (argc > 4) + cmd = argv[4]; + + if (!tracefs_event_file_exists(NULL, system, event, file)) { + fprintf(stderr, "File %s/%s/%s does not exist\n", + system, event, file); + exit(-1); + } + + if (cmd) { + if (cmd[0] != '-') + ch = cmd[0]; + else + ch = cmd[1]; + if (!ch) + ch = 'c'; + } + + switch (ch) { + case 'r': + buf = tracefs_event_file_read(NULL, system, event, file, &size); + if (buf) + printf("%s", buf); + else + fprintf(stderr, "Failed to read %s/%s/%s\n", + system, event, file); + free(buf); + break; + case 'w': + case 'a': + if (argc < 6) { + fprintf(stderr, "%s command requires something to write\n", + ch == 'w' ? "write" : "append"); + exit(-1); + } + if (ch == 'w') + size = tracefs_event_file_write(NULL, system, event, file, argv[5]); + else + size = tracefs_event_file_append(NULL, system, event, file, argv[5]); + if (size < 0) { + fprintf(stderr, "Failed to write '%s' to %s/%s/%s\n", + argv[5], system, event, file); + exit(-1); + } + break; + case 'c': + if (tracefs_event_file_clear(NULL, system, event, file) < 0) { + fprintf(stderr, "Failed to clear %s/%s/%s\n", + system, event, file); + exit(-1); + } + break; + default: + fprintf(stderr, "Unknown command '%c'\n", ch); + exit(-1); + } + exit(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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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 Google, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-events-tep.txt b/Documentation/libtracefs-events-tep.txt new file mode 100644 index 0000000..22d3dd5 --- /dev/null +++ b/Documentation/libtracefs-events-tep.txt @@ -0,0 +1,148 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_local_events, tracefs_local_events_system, tracefs_fill_local_events, +tracefs_load_cmdlines - +Initialize a tep handler with trace events from the local system. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +struct tep_handle pass:[*]*tracefs_local_events*(const char pass:[*]_tracing_dir_); +struct tep_handle pass:[*]*tracefs_local_events_system*(const char pass:[*]_tracing_dir_, const char pass:[*] const pass:[*]_sys_names_); +int *tracefs_fill_local_events*(const char pass:[*]_tracing_dir_, struct tep_handle pass:[*]_tep_, int pass:[*]_parsing_failures_); +int *tracefs_load_cmdlines*(const char pass:[*]_tracing_dir_, struct tep_handle pass:[*]_tep_); +-- + +DESCRIPTION +----------- +Functions for initializing a tep handler with trace events from the local system. + +The *tracefs_local_events()* function allocates a new _tep_ handler and +initializes it with events from all trace systems, located in the given +_tracing_dir_ directory. This could be NULL or the location of the tracefs +mount point for the trace systems of the local machine, or it may be a path +to a copy of the tracefs directory from another machine. + +The *tracefs_local_events_system()* function allocates a new _tep_ handler +and initializes it with events from specified trace systems _sys_names_, +located in the given _tracing_dir_ directory. This could be NULL or the +location of the tracefs mount point for the trace systems of the local +machine, or it may be a path to a copy of the tracefs directory from another +machine. The _sys_names_ argument is an array of trace system names, that +will be used for _tep_ handler initialization. The last element in that +array must be a NULL pointer. + +The *tracefs_fill_local_events()* function initializes already allocated _tep_ +handler with events from all trace systems, located in the given _tracing_dir_ +directory. This could be NULL or the location of the tracefs mount point +for the trace systems of the local machine, or it may be a path to a copy +of the tracefs directory from another machine. The _tep_ argument must be +a pointer to already allocated tep handler, that is going to be initialized. +The _parsing_failures_ argument could be NULL or a pointer to an integer, +where the number of failures while parsing the event files are returned. + +The above functions will also load the mappings between pids and the process +command line names. In some cases the _tep_ handle is created with one +of the above before tracing begins. As the mappings get updated during the +trace, there may be a need to read the mappings again after the trace. +The *tracefs_load_cmdlines()* does just that. The _tracing_dir_ is +the directory of the mount point to load from, or NULL to use the +mount point of the tracefs file system. + +RETURN VALUE +------------ +The *tracefs_local_events()* and *tracefs_local_events_system()* functions +return pointer to allocated and initialized _tep_ handler, or NULL in +case of an error. The returned _tep_ handler must be freed with *tep_free*(3). + +The *tracefs_fill_local_events()* function returns -1 in case of an error or +0 otherwise. + +The *tracefs_load_cmdlines()* function returns -1 in case of an error, or +0 otherwise. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + +struct tep_handle *tep; +... + tep = tracefs_local_events(NULL); + if (!tep) { + /* Failed to initialise tep handler with local events from top instance */ + ... + } +... + tep_free(tep); +... + const char *systems[] = {"ftrace", "irq", NULL}; + tep = tracefs_local_events_system(NULL, systems); + if (!tep) { + /* Failed to initialise tep handler with local events from + * ftrace and irq systems in top instance. + */ + ... + } +... + tep_free(tep); +... + int parsing_failures; + tep = tep_alloc(); + if (!tep) { + /* Failed to allocate a tep handler */ + ... + } + if (tracefs_fill_local_events(NULL, tep, &parsing_failures) < 0) { + /* Failed to initialise tep handler with local events from top instance */ + } + tracefs_load_cmdlines(NULL, tep); +... + tep_free(tep); +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-events.txt b/Documentation/libtracefs-events.txt new file mode 100644 index 0000000..90c54b8 --- /dev/null +++ b/Documentation/libtracefs-events.txt @@ -0,0 +1,195 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_event_systems, tracefs_system_events, tracefs_event_enable, tracefs_event_disable, +tracefs_event_is_enabled - Work with trace systems and events. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +enum tracefs_event_state { + TRACEFS_ERROR = -1, + TRACEFS_ALL_DISABLED = 0, + TRACEFS_ALL_ENABLED = 1, + TRACEFS_SOME_ENABLED = 2, +}; + +char pass:[*]pass:[*]*tracefs_event_systems*(const char pass:[*]_tracing_dir_); +char pass:[*]pass:[*]*tracefs_system_events*(const char pass:[*]_tracing_dir_, const char pass:[*]_system_); +int *tracefs_event_enable*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, + const char pass:[*]_event_); +int *tracefs_event_disable*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, + const char pass:[*]_event_); +enum tracefs_enable_state *tracefs_event_is_enabled*(struct tracefs_instance pass:[*]_instance_, + const char pass:[*]_system_, const char pass:[*]_event_); +-- + +DESCRIPTION +----------- +Trace systems and events related APIs. + +The *tracefs_event_systems()* function returns array of strings with the +names of all registered trace systems, located in the given _tracing_dir_ +directory. This could be NULL or the location of the tracefs mount point +for the trace systems of the local machine, or it may be a path to a copy +of the tracefs directory from another machine. The last entry in the array +is a NULL pointer. The array must be freed with *tracefs_list_free()* API. + +The *tracefs_system_events()* function returns array of strings with the +names of all registered trace events for given trace system specified by +_system_, located in the given _tracing_dir_ directory. This could be NULL +or the location of the tracefs mount point for the trace systems of the +local machine, or it may be a path to a copy of the tracefs directory +from another machine. The last entry in the array as a NULL pointer. +The array must be freed with *tracefs_list_free()* API. + +The *tracefs_event_enable()* function enables a given event based on +the _system_ and _event_ passed in for the given _instance_. If _instance_ +is NULL, then the top level tracing directory is used. If _system_ +and _event_ are both NULL, then all events are enabled for the _instance_. +If _event_ is NULL then all events within the _system_ are enabled. +If _system_ is NULL, then all systems are searched and any event within +a system that matches _event_ is enabled. Both _system_ and _event_ may +be regular expressions as defined by *regex*(3). + +The *tracefs_event_disable()* function disables the events that match +the _system_ and _event_ parameters for the given _instance_. What events +are disable follow the same rules as *tracefs_event_enable()* for matching +events. That is, if _instance_ is NULL, then the top level tracing directory +is used. If both _system_ and _event_ are NULL then all events are disabled +for the given _instance_, and so on. + +The *tracefs_event_is_enabled()* returns if an event is enabled, a set of +events are enabled, a system is enabled, or all events are enabled. If both +_system_ and _event_ are NULL, then it returns the enable state of all events. +If _system_ is not NULL and _event_ is NULL, then it will check if all the events +in all the systems that _system_ and return the enable state of those events. +If _system_ is NULL and _event_ is not NULL, then it will match all the events +in all systems that match _event_ and return their enabled state. If both _system_ +and _event_ are not NULL, then it will return the enabled state of all matching +events. The enabled state is defined as: + +*TRACEFS_ERROR* - An error occurred including no event were matched. + +*TRACEFS_ALL_DISABLED* - All matching events are disabled. + +*TRACEFS_ALL_ENABLED* - All matching events are enabled. + +*TRACEFS_SOME_ENABLED* - Some matching events were enabled while others were not. + +RETURN VALUE +------------ +The *tracefs_event_systems()* and *tracefs_system_events()* functions return +an array of strings. The last element in that array is a NULL pointer. The array +must be freed with *tracefs_list_free()* API. In case of an error, NULL is returned. + +Both *tracefs_event_enable()* and *tracefs_event_disable()* return 0 if they found +any matching events (Note it does not check the previous status of the event. If +*tracefs_event_enable()* finds an event that is already enabled, and there are no +other errors, then it will return 0). If an error occurs, even if other events were +found, it will return -1 and errno will be set. If no errors occur, but no events +are found that match the _system_ and _event_ parameters, then -1 is returned +and errno is not set. + +The *tracefs_event_is_enabled()* returns the enabled status of the matching events +or TRACEFS_ERROR on error. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + +char **systems = tracefs_event_systems(NULL); + + if (systems) { + int i = 0; + /* Got registered trace systems from the top trace instance */ + while (systems[i]) { + char **events = tracefs_system_events(NULL, systems[i]); + if (events) { + /* Got registered events in system[i] from the top trace instance */ + int j = 0; + + while (events[j]) { + /* Got event[j] in system[i] from the top trace instance */ + j++; + } + tracefs_list_free(events); + } + i++; + } + tracefs_list_free(systems); + } +.... +static int records_walk(struct tep_event *tep, struct tep_record *record, int cpu, void *context) +{ + /* Got recorded event on cpu */ + return 0; +} +... +struct tep_handle *tep = tracefs_local_events(NULL); + + if (!tep) { + /* Failed to initialise tep handler with local events */ + ... + } + + errno = 0; + ret = tracefs_event_enable(NULL, "sched", NULL); + if (ret < 0 && !errno) + printf("Could not find 'sched' events\n"); + tracefs_event_enable(NULL, "irq", "irq_handler_\(enter\|exit\)"); + + if (tracefs_iterate_raw_events(tep, NULL, NULL, 0, records_walk, NULL) < 0) { + /* Error walking through the recorded raw events */ + } + + /* Disable all events */ + tracefs_event_disable(NULL, NULL, NULL); + tep_free(tep); +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-files.txt b/Documentation/libtracefs-files.txt new file mode 100644 index 0000000..d22e759 --- /dev/null +++ b/Documentation/libtracefs-files.txt @@ -0,0 +1,131 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_get_tracing_file, tracefs_put_tracing_file, tracefs_tracing_dir, tracefs_debug_dir, tracefs_set_tracing_dir, +tracefs_tracing_dir_is_mounted - Find and set locations of trace directory and files. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +char pass:[*]*tracefs_get_tracing_file*(const char pass:[*]_name_); +void *tracefs_put_tracing_file*(char pass:[*]_name_); +const char pass:[*]*tracefs_tracing_dir*(void); +const char pass:[*]*tracefs_debug_dir*(void); +int *tracefs_set_tracing_dir*(char pass:[*]_tracing_dir_) +int *tracefs_tracing_dir_is_mounted*(bool _mount_, const char pass:[**]_path_); +-- + +DESCRIPTION +----------- +This set of APIs can be used to find the full path of the trace file +system mount point and trace files in it. + +The *tracefs_set_tracing_dir()* function sets a custom location of the +system's tracing directory mount point. Usually, the library auto detects +it using the information from the /proc/mounts file. Use this API only if the +mount point is not standard and cannot be detected by the library. The _tracing_dir_ +argument can be NULL, in that case the custom location is deleted and the library +auto detection logic is used. + +The *tracefs_get_tracing_file()* function returns the full path of the +file with given _name_ in the trace file system. The function works +only with files in the tracefs main directory, it is not trace instance +aware. It is recommended to use *tracefs_instance_get_file()* and +*tracefs_instance_get_dir()* instead. The returned string must be freed +with *tracefs_put_tracing_file()*. + +The *tracefs_put_tracing_file()* function frees trace file name, +returned by *tracefs_get_tracing_file()*. + +The *tracefs_tracing_dir()* function returns the full path to the +trace file system. In the first function call, the mount point of the +tracing file system is located, cached and returned. It will mount it, +if it is not mounted. On any subsequent call the cached path is returned. +The return string must _not_ be freed. + +The *tracefs_debug_dir()* is similar to *tracefs_tracing_dir()* except +that it will return where the debugfs file system is mounted. If it +is not mounted it will try to mount it. The return string must _not_ +be freed. + +*tracefs_tracing_dir_is_mounted()* returns 1 if the tracing directory is +already mounted and 0 if it is not. If _mount_ is true, it will try to +mount it if it is not, and returns 0 if it succesfully mounted it and -1 +if it did not. If _path_ is set, it will be assigned to the path where it +mounted it. _path_ is internal and should not be freed. + +RETURN VALUE +------------ +The *tracefs_set_tracing_dir()* function returns 0 on success, -1 otherwise. + +The *tracefs_get_tracing_file()* function returns a string or NULL in case +of an error. The returned string must be freed with *tracefs_put_tracing_file()*. + +The *tracefs_tracing_dir()* function returns a constant string or NULL +in case of an error. The returned string must _not_ be freed. + +The *tracefs_debug_dir()* function returns a constant string or NULL +in case of an error. The returned string must _not_ be freed. + +The *tracefs_tracing_dir_is_mounted()* returns 1 if the tracing directory +is already mounted, 0 if it is not, and -1 on error. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> +... +char *trace_on = tracefs_get_tracing_file("tracing_on"); + if (trace_on) { + ... + tracefs_put_tracing_file(trace_on); + } +... +const char *trace_dir = tracefs_tracing_dir(); + +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-filter.txt b/Documentation/libtracefs-filter.txt new file mode 100644 index 0000000..12726b9 --- /dev/null +++ b/Documentation/libtracefs-filter.txt @@ -0,0 +1,345 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_filter_string_append, tracefs_filter_string_verify, tracefs_event_filter_apply, tracefs_event_filter_clear - +Add, verify and apply event filters + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_filter_string_append*(struct tep_event pass:[*]_event_, char pass:[**]_filter_, + struct tracefs_filter _type_, const char pass:[*]_field_, + enum tracefs_synth_compare _compare_, const char pass:[*]_val_); +int *tracefs_filter_string_verify*(struct tep_event pass:[*]_event_, const char pass:[*]_filter_, char pass:[**]_err_); +int *tracefs_event_filter_apply*(struct tracefs_instance pass:[*]_instance_, struct tep_event pass:[*]_event_, const char pass:[*]_filter_); +int *tracefs_event_filter_clear*(struct tracefs_instance pass:[*]_instance_, struct tep_event pass:[*]_event_); + +-- + +DESCRIPTION +----------- +*tracefs_filter_string_append*() is a way to create and verify event filters for +a given event. It will verify that the _field_ belongs to the event and that +the _compare_ option that is used is valid for the type of the field, as well +as _val_. For the _type_ that is not of *TRACEFS_FILTER_COMPARE*, it will build +the logical string and also make sure that the syntax is correct. For example, +there are no more close parenthesis than open parenthesis. An AND (&&) or OR +(||) is not misplaced, etc. + +*tracefs_synth_append_start_filter*() creates a filter or appends to it for the +starting event. Depending on _type_, it will build a string of tokens for +parenthesis or logic statemens, or it may add a comparison of _field_ +to _val_ based on _compare_. + +If _type_ is: +*TRACEFS_FILTER_COMPARE* - See below +*TRACEFS_FILTER_AND* - Append "&&" to the filter +*TRACEFS_FILTER_OR* - Append "||" to the filter +*TRACEFS_FILTER_NOT* - Append "!" to the filter +*TRACEFS_FILTER_OPEN_PAREN* - Append "(" to the filter +*TRACEFS_FILTER_CLOSE_PAREN* - Append ")" to the filter + +_field_, _compare_, and _val_ are ignored unless _type_ is equal to +*TRACEFS_FILTER_COMPARE*, then _compare will be used for the following: + +*TRACEFS_COMPARE_EQ* - _field_ == _val_ + +*TRACEFS_COMPARE_NE* - _field_ != _val_ + +*TRACEFS_COMPARE_GT* - _field_ > _val_ + +*TRACEFS_COMPARE_GE* - _field_ >= _val_ + +*TRACEFS_COMPARE_LT* - _field_ < _val_ + +*TRACEFS_COMPARE_LE* - _field_ <pass:[=] _val_ + +*TRACEFS_COMPARE_RE* - _field_ ~ "_val_" : where _field_ is a string. + +*TRACEFS_COMPARE_AND* - _field_ & _val_ : where _field_ is a flags field. + +*tracefs_filter_string_verify*() will parse _filter_ to make sure that the +fields are for the _event_, and that the syntax is correct. If there's an +error in the syntax, and _err_ is not NULL, then it will be allocated with an +error message stating what was found wrong with the filter. _err_ must be freed +with *free*(). + +*tracefs_event_filter_apply*() applies given _filter_ string on _event_ in given _instance_. + +*tracefs_event_filter_clear*() clear all filters on _event_ in given _instance_. + +RETURN VALUE +------------ +*tracefs_filter_string_append*() returns 0 on success and -1 on error. + +*tracefs_filter_string_verify*() returns 0 on success and -1 on error. if there +is an error, and _errno_ is not *ENOMEM*, then _err_ is allocated and will +contain a string describing what was found wrong with _filter_. _err_ must be +freed with *free*(). + +*tracefs_event_filter_apply*() returns 0 on success and -1 on error. + +*tracefs_event_filter_clear*() returns 0 on success and -1 on error. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <tracefs.h> + +static void usage(char **argv) +{ + fprintf(stderr, "usage: %s [system] event filter\n", argv[0]); + exit(-1); +} + +int main (int argc, char **argv) +{ + struct tep_handle *tep; + struct tep_event *event; + const char *system = NULL; + const char *event_name; + const char *filter; + char *new_filter = NULL; + char *err = NULL; + int i; + + if (argc < 3) + usage(argv); + + if (argc < 4) { + event_name = argv[1]; + filter = argv[2]; + } else { + system = argv[1]; + event_name = argv[2]; + filter = argv[3]; + } + + /* Load all events from the system */ + tep = tracefs_local_events(NULL); + if (!tep) { + perror("tep"); + exit(-1); + } + + event = tep_find_event_by_name(tep, system, event_name); + if (!event) { + fprintf(stderr, "Event %s%s%s not found\n", + system ? system : "" , system ? " " : "", + event_name); + exit(-1); + } + + if (tracefs_filter_string_verify(event, filter, &err) < 0) { + perror("tracecfs_event_verify_filter"); + if (err) + fprintf(stderr, "%s", err); + free(err); + exit(-1); + } + + for (i = 0; filter[i]; i++) { + char buf[strlen(filter)]; + char *field = NULL; + char *val = NULL; + enum tracefs_filter type; + enum tracefs_compare compare = 0; + int start_i, n; + int quote; + bool backslash; + + while (isspace(filter[i])) + i++; + + switch(filter[i]) { + case '(': + type = TRACEFS_FILTER_OPEN_PAREN; + break; + case ')': + type = TRACEFS_FILTER_CLOSE_PAREN; + break; + case '!': + type = TRACEFS_FILTER_NOT; + break; + case '&': + type = TRACEFS_FILTER_AND; + i++; + break; + case '|': + type = TRACEFS_FILTER_OR; + i++; + break; + default: + type = TRACEFS_FILTER_COMPARE; + + while (isspace(filter[i])) + i++; + + start_i = i; + for (; filter[i]; i++) { + switch(filter[i]) { + case 'a' ... 'z': + case 'A' ... 'Z': + case '0' ... '9': + case '_': + continue; + } + break; + } + + n = i - start_i; + field = buf; + strncpy(field, filter + start_i, n); + field[n++] = '\0'; + + val = buf + n; + + while (isspace(filter[i])) + i++; + + start_i = i; + switch(filter[i++]) { + case '>': + compare = TRACEFS_COMPARE_GT; + if (filter[i] == '=') { + i++; + compare = TRACEFS_COMPARE_GE; + } + break; + case '<': + compare = TRACEFS_COMPARE_LT; + if (filter[i] == '=') { + i++; + compare = TRACEFS_COMPARE_LE; + } + break; + case '=': + compare = TRACEFS_COMPARE_EQ; + i++; + break; + case '!': + compare = TRACEFS_COMPARE_NE; + i++; + break; + case '~': + compare = TRACEFS_COMPARE_RE; + break; + case '&': + compare = TRACEFS_COMPARE_AND; + break; + } + + while (isspace(filter[i])) + i++; + + quote = 0; + backslash = false; + start_i = i; + for (; filter[i]; i++) { + if (quote) { + if (backslash) + backslash = false; + else if (filter[i] == '\\') + backslash = true; + else if (filter[i] == quote) + quote = 0; + continue; + } + switch(filter[i]) { + case '"': case '\'': + quote = filter[i]; + continue; + case 'a' ... 'z': + case 'A' ... 'Z': + case '0' ... '9': + case '_': + continue; + } + break; + } + n = i - start_i; + strncpy(val, filter + start_i, n); + val[n] = '\0'; + break; + } + n = tracefs_filter_string_append(event, &new_filter, type, + field, compare, val); + if (n < 0) { + fprintf(stderr, "Failed making new filter:\n'%s'\n", + new_filter ? new_filter : "(null)"); + exit(-1); + } + } + + if (tracefs_event_filter_apply(NULL, event, new_filter)) + fprintf(stderr, "Failed to apply filter on event"); + + tep_free(tep); + + printf("Created new filter: '%s'\n", new_filter); + free(new_filter); + + exit(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), +*tracefs_hist_alloc*(3), +*tracefs_hist_alloc_2d*(3), +*tracefs_hist_alloc_nd*(3), +*tracefs_hist_free*(3), +*tracefs_hist_add_key*(3), +*tracefs_hist_add_value*(3), +*tracefs_hist_add_name*(3), +*tracefs_hist_start*(3), +*tracefs_hist_destory*(3), +*tracefs_hist_add_sort_key*(3), +*tracefs_hist_sort_key_direction*(3) + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-function-filter.txt b/Documentation/libtracefs-function-filter.txt new file mode 100644 index 0000000..2a141fd --- /dev/null +++ b/Documentation/libtracefs-function-filter.txt @@ -0,0 +1,237 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_function_filter, tracefs_function_notrace, tracefs_filter_functions +- Functions to modify the the function trace filters + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_function_filter*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_filter_, const char pass:[*]_module_, int _flags_); +int *tracefs_function_notrace*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_filter_, const char pass:[*]_module_, int _flags_); +int *tracefs_filter_functions*(const char pass:[*]_filter_, const char pass:[*]_module_, char pass:[*]pass:[*]pass:[*]_list_); +-- + +DESCRIPTION +----------- +*tracefs_function_filter* and *tracefs_function_notrace* can be used to limit the +Linux kernel functions that would be traced by the function and function-graph tracers. +The *tracefs_function_filter* defines a list of functions that can be traced. +The *tracefs_function_notrace* defines a list of functions that will not be traced. +If a function is in both lists, it will not be traced. + +They take an _instance_ , that can be NULL for the top level tracing, +_filter_, a string that represents a filter that should +be applied to define what functions are to be traced, +_module_, to limit the filtering on a specific module (or NULL to filter on all functions), +_flags_ which holds control knobs on how the filters will be handled (see *FLAGS*) +section below. + +The *tracefs_filter_functions* returns a list of functions that can be filtered on +via the _filter_ and _module_ that are supplied. If both _filter_ and _module_ are +NULL then, all available functions that can be filtered is returned. +On success, _list_ must be freed with *tracefs_list_free()*(3). + +The _filter_ may be either a straight match of a +function, a glob or regex(3). A glob is where 'pass:[*]' matches zero or more +characters, '?' will match zero or one character, and '.' only matches a +period. If the _filter_ is determined to be a regex (where it contains +anything other than alpha numeric characters, or '.', 'pass:[*]', '?') the _filter_ +will be processed as a regex(3) following the rules of regex(3), and '.' is +not a period, but will match any one character. To force a regular +expression, either prefix _filter_ with a '^' or append it with a '$' as +the _filter_ does complete matches of the functions anyway. + +If _module_ is set and _filter_ is NULL, this will imply the same as _filter_ being +equal to "pass:[*]". Which will enable all functions for a given _module_. Otherwise +the _filter_ may be NULL if a previous call to *tracefs_function_filter()* with +the same _instance_ had *TRACEFS_FL_CONTINUE* set and this call does not. This is +useful to simply commit the previous filters. It may also be NULL +if *TRACEFS_FL_RESET* is set and the previous call did not have the same _instance_ +and *TRACEFS_FL_CONTINUE* set. This is useful to just clear the filter. + +FLAGS +----- + +The _flags_ parameter may have the following set, or be zero. + +*TRACEFS_FL_RESET* : +If _flags_ contains *TRACEFS_FL_RESET*, then it will clear the filters that +are currently set before applying _filter_. Otherwise, _filter_ is added to +the current set of filters already enabled. If this flag is set and the +previous call to tracefs_function_filter() had the same _instance_ and the +*TRACEFS_FL_CONTINUE* flag was set, then the function will fail with a +return of -1 and errno set to EBUSY. + +*TRACEFS_FL_CONTINUE* : +If _flags_ contains *TRACEFS_FL_CONTINUE*, then _filter_ will not take +effect after a successful call to tracefs_function_filter(). This allows for +multiple calls to tracefs_function_filter() to update the filter function +and then a single call (one without the *TRACEFS_FL_CONTINUE* flag set) to +commit all the filters. +It can be called multiple times to add more filters. A call without this +flag set will commit the changes before returning (if the _filter_ passed in +successfully matched). A tracefs_function_filter() call after one that had +the *TRACEFS_FL_CONTINUE* flag set for the same instance will fail if +*TRACEFS_FL_RESET* flag is set, as the reset flag is only applicable for the +first filter to be added before committing. + +*TRACEFS_FL_FUTURE* : +If _flags_ contains *TRACEFS_FL_FUTURE* and _module_ holds a string of a module, +then if the module is not loaded it will attemp to write the filter with the module +in the filter file. Starting in Linux v4.13 module functions could be added to the +filter before they are loaded. The filter will be cached, and when the module is +loaded, the filter will be set before the module executes, allowing to trace +init functions of a module. This will only work if the _filter_ is not a +regular expression. + +RETURN VALUE +------------ + +For *tracefs_function_filter()* and *tracefs_function_notrace()* a +return of 0 means success. If the there is an error but the filtering was not +started, then 1 is returned. If filtering was started but an error occurs, +then -1 is returned. The state of the filtering may be in an unknown state. + +If *TRACEFS_FL_CONTINUE* was set, and 0 or -1 was returned, then another call +to *tracefs_function_filter()* must be done without *TRACEFS_FL_CONTINUE* set +in order to commit (and close) the filtering. + +For *tracefs_filter_functions()*, a return of 0 means success, and the _list_ +parameter is filled with a list of function names that matched _filter_ and +_module_. _list_ is a string array, where the last string pointer in the +array is NULL. The _list_ must be freed with *tracefs_list_free()*. +On failure, a negative is returned, and _list_ is ignored. + +ERRORS +------ + +*tracefs_function_filter*() can fail with the following errors: + +*EINVAL* The filter is invalid or did not match any functions. + +*EBUSY* The previous call of *tracefs_function_filter*() was called +with the same instance and *TRACEFS_FL_CONTINUE* set and the current call +had *TRACEFS_FL_RESET* set. + +Other errors may also happen caused by internal system calls. + +EXAMPLE +------- +[source,c] +-- +#include <stdio.h> +#include <errno.h> +#include <tracefs.h> + +#define INST "dummy" + +static const char *filters[] = { "run_init_process", "try_to_run_init_process", "dummy1", NULL }; + +int main(int argc, char *argv[]) +{ + struct tracefs_instance *inst = tracefs_instance_create(INST); + char **func_list; + int ret; + int i; + + if (!inst) { + /* Error creating new trace instance */ + } + + if (tracefs_filter_functions("*lock*", NULL, &func_list) < 0) { + printf("Failed to read filter functions\n"); + goto out; + } + printf("Ignoring the following functions:\n"); + for (i = 0; func_list[i]; i++) + printf(" %s\n", func_list[i]); + tracefs_list_free(func_list); + + /* Do not trace any function with the word "lock" in it */ + ret = tracefs_function_notrace(inst, "*lock*", NULL, TRACEFS_FL_RESET); + if (ret) { + printf("Failed to set the notrace filter\n"); + goto out; + } + + /* First reset the filter */ + ret = tracefs_function_filter(inst, NULL, NULL, + TRACEFS_FL_RESET | TRACEFS_FL_CONTINUE); + if (ret) { + printf("Failed to reset the filter\n"); + /* Make sure it is closed, -1 means filter was started */ + if (ret < 0) + tracefs_function_filter(inst, NULL, NULL, 0); + } + + for (i = 0; filters[i]; i++) { + ret = tracefs_function_filter(inst, filters[i], NULL, + TRACEFS_FL_CONTINUE); + + if (ret) { + if (errno == EINVAL) + printf("Filter %s did not match\n", filters[i]); + else + printf("Failed writing %s\n", filters[i]); + } + } + + ret = tracefs_function_filter(inst, "*", "ext4", 0); + if (ret) { + printf("Failed to set filters for ext4\n"); + /* Force the function to commit previous filters */ + tracefs_function_filter(inst, NULL, NULL, 0); + } + + out: + tracefs_instance_destroy(inst); + return ret; +} +-- + +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-hist-cont.txt b/Documentation/libtracefs-hist-cont.txt new file mode 100644 index 0000000..7159e27 --- /dev/null +++ b/Documentation/libtracefs-hist-cont.txt @@ -0,0 +1,222 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_hist_start, tracefs_hist_destroy, tracefs_hist_pause, +tracefs_hist_continue, tracefs_hist_reset - Pause, continue, or clear an existing histogram + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_hist_start*(struct tracefs_instance pass:[*]_instance_, struct tracefs_hist pass:[*]_hist_); +int *tracefs_hist_destroy*(struct tracefs_instance pass:[*]_instance_, struct tracefs_hist pass:[*]_hist_); +int *tracefs_hist_pause*(struct tracefs_instance pass:[*]_instance_, struct tracefs_hist pass:[*]_hist_); +int *tracefs_hist_continue*(struct tracefs_instance pass:[*]_instance_, struct tracefs_hist pass:[*]_hist_); +int *tracefs_hist_reset*(struct tracefs_instance pass:[*]_instance_, struct tracefs_hist pass:[*]_hist_); + +-- + +DESCRIPTION +----------- + +*tracefs_hist_start()* is called to actually start the histogram _hist_. +The _instance_ is the instance to start the histogram in, NULL if it +should start at the top level. + +*tracefs_hist_pause()* is called to pause the histogram _hist_. +The _instance_ is the instance to pause the histogram in, NULL if it +is in the top level. + +*tracefs_hist_continue()* is called to continue a paused histogram _hist_. +The _instance_ is the instance to continue the histogram, NULL if it +is in the top level. + +*tracefs_hist_reset()* is called to clear / reset the histogram _hist_. +The _instance_ is the instance to clear the histogram, NULL if it +is in the top level. + +*tracefs_hist_destroy()* is called to delete the histogram where it will no longer +exist. The _instance_ is the instance to delete the histogram from, NULL if it +is in the top level. + +RETURN VALUE +------------ +All the return zero on success or -1 on error. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <tracefs.h> + +enum commands { + START, + PAUSE, + CONT, + RESET, + DELETE, + SHOW, +}; + +int main (int argc, char **argv, char **env) +{ + struct tracefs_instance *instance; + struct tracefs_hist *hist; + struct tep_handle *tep; + enum commands cmd; + char *cmd_str; + int ret; + + if (argc < 2) { + fprintf(stderr, "usage: %s command\n", argv[0]); + exit(-1); + } + + cmd_str = argv[1]; + + if (!strcmp(cmd_str, "start")) + cmd = START; + else if (!strcmp(cmd_str, "pause")) + cmd = PAUSE; + else if (!strcmp(cmd_str, "cont")) + cmd = CONT; + else if (!strcmp(cmd_str, "reset")) + cmd = RESET; + else if (!strcmp(cmd_str, "delete")) + cmd = DELETE; + else if (!strcmp(cmd_str, "show")) + cmd = SHOW; + else { + fprintf(stderr, "Unknown command %s\n", cmd_str); + exit(-1); + } + + tep = tracefs_local_events(NULL); + if (!tep) { + perror("Reading tracefs"); + exit(-1); + } + + instance = tracefs_instance_create("hist_test"); + if (!instance) { + fprintf(stderr, "Failed instance create\n"); + exit(-1); + } + + hist = tracefs_hist_alloc_2d(tep, "kmem", "kmalloc", + "call_site",TRACEFS_HIST_KEY_SYM, + "bytes_req", 0); + if (!hist) { + fprintf(stderr, "Failed hist create\n"); + exit(-1); + } + + ret = tracefs_hist_add_value(hist, "bytes_alloc"); + ret |= tracefs_hist_add_sort_key(hist, "bytes_req"); + ret |= tracefs_hist_add_sort_key(hist, "bytes_alloc"); + + ret |= tracefs_hist_sort_key_direction(hist, "bytes_alloc", + TRACEFS_HIST_SORT_DESCENDING); + if (ret) { + fprintf(stderr, "Failed modifying histogram\n"); + exit(-1); + } + + tracefs_error_clear(instance); + + switch (cmd) { + case START: + ret = tracefs_hist_start(instance, hist); + if (ret) { + char *err = tracefs_error_last(instance); + if (err) + fprintf(stderr, "\n%s\n", err); + } + break; + case PAUSE: + ret = tracefs_hist_pause(instance, hist); + break; + case CONT: + ret = tracefs_hist_continue(instance, hist); + break; + case RESET: + ret = tracefs_hist_reset(instance, hist); + break; + case DELETE: + ret = tracefs_hist_destroy(instance, hist); + break; + case SHOW: { + char *content; + content = tracefs_event_file_read(instance, "kmem", "kmalloc", + "hist", NULL); + ret = content ? 0 : -1; + if (content) { + printf("%s\n", content); + free(content); + } + break; + } + } + if (ret) + fprintf(stderr, "Failed: command\n"); + exit(ret); +} + +-- + +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), +*tracefs_hist_alloc*(3), +*tracefs_hist_alloc_2d*(3), +*tracefs_hist_alloc_nd*(3), +*tracefs_hist_free*(3), +*tracefs_hist_add_key*(3), +*tracefs_hist_add_value*(3), +*tracefs_hist_add_name*(3), +*tracefs_hist_start*(3), +*tracefs_hist_destory*(3), +*tracefs_hist_add_sort_key*(3), +*tracefs_hist_sort_key_direction*(3) + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-hist-mod.txt b/Documentation/libtracefs-hist-mod.txt new file mode 100644 index 0000000..b308c7e --- /dev/null +++ b/Documentation/libtracefs-hist-mod.txt @@ -0,0 +1,540 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_hist_add_sort_key, tracefs_hist_set_sort_key, tracefs_hist_sort_key_direction, +tracefs_hist_add_name, tracefs_hist_append_filter, tracefs_hist_echo_cmd, tracefs_hist_command, +tracefs_hist_get_name, tracefs_hist_get_event, tracefs_hist_get_system - Update and describe an event histogram + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_hist_add_sort_key*(struct tracefs_hist pass:[*]_hist_, + const char pass:[*]_sort_key_); + +int *tracefs_hist_set_sort_key*(struct tracefs_hist pass:[*]_hist_, + const char pass:[*]_sort_key_, _..._); +int *tracefs_hist_sort_key_direction*(struct tracefs_hist pass:[*]_hist_, + const char pass:[*]_sort_key_, + enum tracefs_hist_sort_direction _dir_); + +int *tracefs_hist_add_name*(struct tracefs_hist pass:[*]_hist_, const char pass:[*]_name_); + +int *tracefs_hist_append_filter*(struct tracefs_hist pass:[*]_hist_, + enum tracefs_filter _type_, + const char pass:[*]_field_, + enum tracefs_compare _compare_, + const char pass:[*]_val_); + +int *tracefs_hist_echo_cmd*(struct trace_seq pass:[*]_s_, struct tracefs_instance pass:[*]_instance_, + struct tracefs_hist pass:[*]_hist_, + enum tracefs_hist_command _command_); + +int *tracefs_hist_command*(struct tracefs_instance pass:[*]_instance_, + struct tracefs_hist pass:[*]_hist_, + enum tracefs_hist_command _command_); + +const char pass:[*]*tracefs_hist_get_name*(struct tracefs_hist pass:[*]_hist_); + +const char pass:[*]*tracefs_hist_get_event*(struct tracefs_hist pass:[*]_hist_); + +const char pass:[*]*tracefs_hist_get_system*(struct tracefs_hist pass:[*]_hist_); + +-- + +DESCRIPTION +----------- +Event histograms are created by the trigger file in the event directory. +The syntax can be complex and difficult to get correct. This API handles the +syntax, and facilitates the creation and interaction with the event histograms. +See https://www.kernel.org/doc/html/latest/trace/histogram.html for more information. + +*tracefs_hist_add_sort_key*() will add a key to sort on. The _hist_ is the +histogram descriptor to add the sort key to. The _sort_key_ is a string +that must match either an already defined key of the histogram, or an already +defined value. If _hist_ already has sorting keys (previously added) the new +_sort_key_ will have lower priority(be secondary or so on) when sorting. + +*tracefs_hist_set_sort_key*() will reset the list of key to sort on. The _hist_ is +the histogram descriptor to reset the sort key to. The _sort_key_ is a string +that must match either an already defined key of the histogram, or an already +defined value. Multiple sort keys may be added to denote a secondary, sort order +and so on, but all sort keys must match an existing key or value, or be +TRACEFS_HIST_HITCOUNT. The last parameter of *tracefs_hist_add_sort_key*() must +be NULL. + +*tracefs_hist_sort_key_direction*() allows to change the direction of an +existing sort key of _hist_. The _sort_key_ is the sort key to change, and +_dir_ can be either TRACEFS_HIST_SORT_ASCENDING or TRACEFS_HIST_SORT_DESCENDING, +to make the direction of the sort key either ascending or descending respectively. + +*tracefs_hist_add_name*() adds a name to a histogram. A histogram may be +named and if the name matches between more than one event, and they have +compatible keys, the multiple histograms with the same name will be merged +into a single histogram (shown by either event's hist file). The _hist_ +is the histogram to name, and the _name_ is the name to give it. + +*tracefs_hist_append_filter*() creates a filter or appends to it for the +histogram event. Depending on _type_, it will build a string of tokens for +parenthesis or logic statements, or it may add a comparison of _field_ +to _val_ based on _compare_. + +If _type_ is: +*TRACEFS_FILTER_COMPARE* - See below +*TRACEFS_FILTER_AND* - Append "&&" to the filter +*TRACEFS_FILTER_OR* - Append "||" to the filter +*TRACEFS_FILTER_NOT* - Append "!" to the filter +*TRACEFS_FILTER_OPEN_PAREN* - Append "(" to the filter +*TRACEFS_FILTER_CLOSE_PAREN* - Append ")" to the filter + +_field_, _compare_, and _val_ are ignored unless _type_ is equal to +*TRACEFS_FILTER_COMPARE*, then _compare_ will be used for the following: + +*TRACEFS_COMPARE_EQ* - _field_ == _val_ + +*TRACEFS_COMPARE_NE* - _field_ != _val_ + +*TRACEFS_COMPARE_GT* - _field_ > _val_ + +*TRACEFS_COMPARE_GE* - _field_ >= _val_ + +*TRACEFS_COMPARE_LT* - _field_ < _val_ + +*TRACEFS_COMPARE_LE* - _field_ <pass:[=] _val_ + +*TRACEFS_COMPARE_RE* - _field_ ~ "_val_" : where _field_ is a string. + +*TRACEFS_COMPARE_AND* - _field_ & _val_ : where _field_ is a flags field. + +*trace_hist_echo_cmd*() prints the commands needed to create the given histogram +in the given _instance_, or NULL for the top level, into the _seq_. +The command that is printed is described by _command_ and shows the functionality +that would be done by *tracefs_hist_command*(3). + +*tracefs_hist_command*() is called to process a command on the histogram +_hist_ for its event in the given _instance_, or NULL for the top level. +The _cmd_ can be one of: + +*TRACEFS_HIST_CMD_START* or zero to start execution of the histogram. + +*TRACEFS_HIST_CMD_PAUSE* to pause the given histogram. + +*TRACEFS_HIST_CMD_CONT* to continue a paused histogram. + +*TRACEFS_HIST_CMD_CLEAR* to reset the values of a histogram. + +*TRACEFS_HIST_CMD_DESTROY* to destroy the histogram (undo a START). + +The below functions are wrappers to tracefs_hist_command() to make the +calling conventions a bit easier to understand what is happening. + +KEY TYPES +--------- + +*tracefs_hist_alloc_nd*() and *tracefs_hist_add_key*() both add a key and requires +that key to have a type. The types may be: + +*TRACEFS_HIST_KEY_NORMAL* or zero (0) which is to not modify the type. + +*TRACEFS_HIST_KEY_HEX* to display the key in hex. + +*TRACEFS_HIST_KEY_SYM* to display the key as a kernel symbol (if found). If +the key is an address, this is useful as it will display the function names instead +of just a number. + +*TRACEFS_HIST_KEY_SYM_OFFSET* similar to *TRACEFS_HIST_KEY_SYM* but will also include +the offset of the function to match the exact address. + +*TRACEFS_HIST_KEY_SYSCALL* If the key is a system call "id" (the number passed from user +space to the kernel to tell it what system call it is calling), then the name of +the system call is displayed. + +*TRACEFS_HIST_KEY_EXECNAME* If "common_pid" is the key (the pid of the executing task), +instead of showing the number, show the name of the running task. + +*TRACEFS_HIST_KEY_LOG* will display the key in a binary logarithmic scale. + +*TRACEFS_HIST_KEY_USECS* for use with "common_timestamp" or TRACEFS_HIST_TIMESTAMP, +in which case it will show the timestamp in microseconds instead of nanoseconds. + +RETURN VALUE +------------ +*tracefs_hist_get_name*() returns the name of the histogram or NULL on error. +The returned string belongs to the histogram object and is freed with the histogram +by *tracefs_hist_free*(). + +*tracefs_hist_get_event*() returns the event name of the histogram or NULL on error. +The returned string belongs to the histogram object and is freed with the histogram +by *tracefs_hist_free*(). + +*tracefs_hist_get_system*() returns the system name of the histogram or NULL on error. +The returned string belongs to the histogram object and is freed with the histogram +by *tracefs_hist_free*(). + +*tracefs_hist_alloc_nd*() returns an allocated histogram descriptor which must +be freed by *tracefs_hist_free*() or NULL on error. + +*tracefs_hist_get_name*(), *tracefs_hist_get_event*() and *tracefs_hist_get_system*() +return strings owned by the histogram object. + +All the other functions return zero on success or -1 on error. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <unistd.h> +#include <tracefs.h> + +enum commands { + START, + PAUSE, + CONT, + RESET, + DELETE, + SHOW, +}; + +static void parse_system_event(char *group, char **system, char **event) +{ + *system = strtok(group, "/"); + *event = strtok(NULL, "/"); + if (!*event) { + *event = *system; + *system = NULL; + } +} + +static int parse_keys(char *keys, struct tracefs_hist_axis **axes) +{ + char *sav = NULL; + char *key; + int cnt = 0; + + for (key = strtok_r(keys, ",", &sav); key; key = strtok_r(NULL, ",", &sav)) { + struct tracefs_hist_axis *ax; + char *att; + + ax = realloc(*axes, sizeof(*ax) * (cnt + 2)); + if (!ax) { + perror("Failed to allocate axes"); + exit(-1); + } + ax[cnt].key = key; + ax[cnt].type = 0; + ax[cnt + 1].key = NULL; + ax[cnt + 1].type = 0; + + *axes = ax; + + att = strchr(key, '.'); + if (att) { + *att++ = '\0'; + if (strcmp(att, "hex") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_HEX; + else if (strcmp(att, "sym") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_SYM; + else if (strcmp(att, "sym_offset") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_SYM_OFFSET; + else if (strcmp(att, "syscall") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_SYSCALL; + else if (strcmp(att, "exec") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_EXECNAME; + else if (strcmp(att, "log") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_LOG; + else if (strcmp(att, "usecs") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_USECS; + else { + fprintf(stderr, "Undefined attribute '%s'\n", att); + fprintf(stderr," Acceptable attributes:\n"); + fprintf(stderr," hex, sym, sym_offset, syscall, exe, log, usecs\n"); + exit(-1); + } + } + cnt++; + } + return cnt; +} + +static void process_hist(enum commands cmd, const char *instance_name, + char *group, char *keys, char *vals, char *sort, + char *ascend, char *desc) +{ + struct tracefs_instance *instance = NULL; + struct tracefs_hist *hist; + struct tep_handle *tep; + struct tracefs_hist_axis *axes = NULL; + char *system; + char *event; + char *sav; + char *val; + int ret; + int cnt; + + if (instance_name) { + instance = tracefs_instance_create(instance_name); + if (!instance) { + fprintf(stderr, "Failed instance create\n"); + exit(-1); + } + } + + tep = tracefs_local_events(NULL); + if (!tep) { + perror("Could not read events"); + exit(-1); + } + + parse_system_event(group, &system, &event); + + if (cmd == SHOW) { + char *content; + content = tracefs_event_file_read(instance, system, event, + "hist", NULL); + if (!content) { + perror("Reading hist file"); + exit(-1); + } + printf("%s\n", content); + free(content); + return; + } + + if (!keys) { + fprintf(stderr, "Command needs -k option\n"); + exit(-1); + } + + cnt = parse_keys(keys, &axes); + if (!cnt) { + fprintf(stderr, "No keys??\n"); + exit(-1); + } + + /* Show examples of hist1d and hist2d */ + switch (cnt) { + case 1: + hist = tracefs_hist_alloc(tep, system, event, + axes[0].key, axes[0].type); + break; + case 2: + hist = tracefs_hist_alloc_2d(tep, system, event, + axes[0].key, axes[0].type, + axes[1].key, axes[1].type); + break; + default: + /* Really, 1 and 2 could use this too */ + hist = tracefs_hist_alloc_nd(tep, system, event, axes); + } + if (!hist) { + fprintf(stderr, "Failed hist create\n"); + exit(-1); + } + + if (vals) { + sav = NULL; + for (val = strtok_r(vals, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) { + ret = tracefs_hist_add_value(hist, val); + if (ret) { + fprintf(stderr, "Failed to add value %s\n", val); + exit(-1); + } + } + } + + if (sort) { + sav = NULL; + for (val = strtok_r(sort, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) { + ret = tracefs_hist_add_sort_key(hist, val); + if (ret) { + fprintf(stderr, "Failed to add sort key/val %s\n", val); + exit(-1); + } + } + } + + if (ascend) { + sav = NULL; + for (val = strtok_r(ascend, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) { + ret = tracefs_hist_sort_key_direction(hist, val, TRACEFS_HIST_SORT_ASCENDING); + if (ret) { + fprintf(stderr, "Failed to add ascending key/val %s\n", val); + exit(-1); + } + } + } + + if (desc) { + sav = NULL; + for (val = strtok_r(desc, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) { + ret = tracefs_hist_sort_key_direction(hist, val, TRACEFS_HIST_SORT_DESCENDING); + if (ret) { + fprintf(stderr, "Failed to add descending key/val %s\n", val); + exit(-1); + } + } + } + + tracefs_error_clear(instance); + + switch (cmd) { + case START: + ret = tracefs_hist_start(instance, hist); + if (ret) { + char *err = tracefs_error_last(instance); + if (err) + fprintf(stderr, "\n%s\n", err); + } + break; + case PAUSE: + ret = tracefs_hist_pause(instance, hist); + break; + case CONT: + ret = tracefs_hist_continue(instance, hist); + break; + case RESET: + ret = tracefs_hist_reset(instance, hist); + break; + case DELETE: + ret = tracefs_hist_destroy(instance, hist); + break; + case SHOW: + /* Show was already done */ + break; + } + if (ret) + fprintf(stderr, "Failed: command\n"); + exit(ret); +} + +int main (int argc, char **argv, char **env) +{ + enum commands cmd; + char *instance = NULL; + char *cmd_str; + char *event = NULL; + char *keys = NULL; + char *vals = NULL; + char *sort = NULL; + char *desc = NULL; + char *ascend = NULL; + + if (argc < 2) { + fprintf(stderr, "usage: %s command [-B instance][-e [system/]event][-k keys][-v vals][-s sort]\n", argv[0]); + fprintf(stderr, " [-a ascending][-d descending]\n"); + exit(-1); + } + + cmd_str = argv[1]; + + if (!strcmp(cmd_str, "start")) + cmd = START; + else if (!strcmp(cmd_str, "pause")) + cmd = PAUSE; + else if (!strcmp(cmd_str, "cont")) + cmd = CONT; + else if (!strcmp(cmd_str, "reset")) + cmd = RESET; + else if (!strcmp(cmd_str, "delete")) + cmd = DELETE; + else if (!strcmp(cmd_str, "show")) + cmd = SHOW; + else { + fprintf(stderr, "Unknown command %s\n", cmd_str); + exit(-1); + } + + for (;;) { + int c; + + c = getopt(argc - 1, argv + 1, "e:k:v:B:s:d:a:"); + if (c == -1) + break; + + switch (c) { + case 'e': + event = optarg; + break; + case 'k': + keys = optarg; + break; + case 'v': + vals = optarg; + break; + case 'B': + instance = optarg; + break; + case 's': + sort = optarg; + break; + case 'd': + desc = optarg; + break; + case 'a': + ascend = optarg; + break; + } + } + if (!event) { + event = "kmem/kmalloc"; + if (!keys) + keys = "call_site.sym,bytes_req"; + if (!vals) + vals = "bytes_alloc"; + if (!sort) + sort = "bytes_req,bytes_alloc"; + if (!desc) + desc = "bytes_alloc"; + } + process_hist(cmd, instance, event, keys, vals, sort, ascend, desc); +} +-- + +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), +*tracefs_hist_pause*(3), +*tracefs_hist_continue*(3), +*tracefs_hist_reset*(3) + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-hist.txt b/Documentation/libtracefs-hist.txt new file mode 100644 index 0000000..7503fd0 --- /dev/null +++ b/Documentation/libtracefs-hist.txt @@ -0,0 +1,531 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_hist_alloc, tracefs_hist_alloc_2d, tracefs_hist_alloc_nd, tracefs_hist_alloc_nd_cnt, tracefs_hist_free, +tracefs_hist_add_key, tracefs_hist_add_key_cnt, tracefs_hist_add_value - Create and destroy event histograms + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +enum *tracefs_hist_key_type* { + *TRACEFS_HIST_KEY_NORMAL* = 0, + *TRACEFS_HIST_KEY_HEX*, + *TRACEFS_HIST_KEY_SYM*, + *TRACEFS_HIST_KEY_SYM_OFFSET*, + *TRACEFS_HIST_KEY_SYSCALL*, + *TRACEFS_HIST_KEY_EXECNAME*, + *TRACEFS_HIST_KEY_LOG*, + *TRACEFS_HIST_KEY_USECS*, + *TRACEFS_HIST_KEY_MAX* +}; + +struct *tracefs_hist_axis* { + const char pass:[*]_key_; + enum tracefs_hist_key_type _type_; +}; + +struct tracefs_hist pass:[*]*tracefs_hist_alloc*(struct tracefs_tep pass:[*] _tep_, + const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_key_, enum tracefs_hist_key_type _type_); +struct tracefs_hist pass:[*]*tracefs_hist_alloc_2d*(struct tracefs_tep pass:[*] _tep_, + const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_key1_, enum tracefs_hist_key_type _type1_, + const char pass:[*]_key2_, enum tracefs_hist_key_type _type2_)); +struct tracefs_hist pass:[*]*tracefs_hist_alloc_nd*(struct tracefs_tep pass:[*] _tep_, + const char pass:[*]_system_, const char pass:[*]_event_, + struct tracefs_hist_axis pass:[*]_axes_); +struct tracefs_hist pass:[*]*tracefs_hist_alloc_nd_cnt*(struct tep_handle pass:[*]_tep_, + const char pass:[*]_system_, const char pass:[*]_event_name_, + struct tracefs_hist_axis_cnt pass:[*]_axes_); +void *tracefs_hist_free*(struct tracefs_hist pass:[*]_hist_); + +int *tracefs_hist_add_key*(struct tracefs_hist pass:[*]_hist_, const char pass:[*]_key_, + enum tracefs_hist_key_type _type_); +int *tracefs_hist_add_key_cnt*(struct tracefs_hist pass:[*]_hist_, const char pass:[*]_key_, + enum tracefs_hist_key_type _type_, int _cnt_); +int *tracefs_hist_add_value*(struct tracefs_hist pass:[*]_hist_, const char pass:[*]_value_); +-- + +DESCRIPTION +----------- +Event histograms are created by the trigger file in the event directory. +The syntax can be complex and difficult to get correct. This API handles the +syntax, and facilitates the creation and interaction with the event histograms. +See https://www.kernel.org/doc/html/latest/trace/histogram.html for more information. + +*tracefs_hist_alloc*() allocates a "struct tracefs_hist" descriptor of a one-dimensional +histogram and returns the address of it. This descriptor must be freed by *tracefs_hist_free*(). +The _tep_ is a trace event handle (see *tracefs_local_events*(3)), that holds the +_system_ and _event_ that the histogram will be attached to. The _system_ is the +system or group of the event. The _event_ is the event to attach the histogram to. +The _key_ is a field of the event that will be used as the key(dimension) of the histogram. +The _type_ is the type of the _key_. See KEY TYPES below. + +*tracefs_hist_alloc_2d*() allocates a "struct tracefs_hist" descriptor of a two-dimensional +histogram and returns the address of it. This descriptor must be freed by *tracefs_hist_free*(). +The _tep_ is a trace event handle (see *tracefs_local_events*(3)), that holds the +_system_ and _event_ that the histogram will be attached to. The _system_ is the +system or group of the event. The _event_ is the event to attach the histogram to. +The _key1_ is the first field of the event that will be used as the key(dimension) +of the histogram. The _type1_ is the type of the _key1_. See KEY TYPES below. +The _key2_ is the second field of the event that will be used as the key(dimension) +of the histogram. The _type2_ is the type of the _key2_. See KEY TYPES below. + +*tracefs_hist_alloc_nd*() allocates a "struct tracefs_hist" descriptor of an N-dimensional +histogram and returns the address of it. This descriptor must be freed by *tracefs_hist_free*(). +The _tep_ is a trace event handle (see *tracefs_local_events*(3)), that holds the +_system_ and _event_ that the histogram will be attached to. The _system_ is the +system or group of the event. The _event_ is the event to attach the histogram to. +The _axes_ is an array of _key_ / _type_ pairs, defining the dimensions of the histogram. + +*tracefs_hist_alloc_nd_cnt*() will initialize a histogram descriptor that will be attached to +the _system_/_event_. This only initializes the descriptor with the given _axes_ keys as primaries. +This only initializes the descriptor, it does not start the histogram in the kernel. +The difference between this and *tracefs_hist_alloc_nd()* is that the _axes_ parameter +is of type *struct tracefs_hist_axis_cnt* and not *struct tracefs_hist_axis*. + +*tracefs_hist_free*() frees the _tracefs_hist_ descriptor. Note, it does not stop +or disable the running histogram if it was started. *tracefs_hist_destroy*() needs +to be called to do so. + +*tracefs_hist_add_key*() Adds a secondary or tertiary key to the histogram. +The key passed to *tracefs_hist_alloc_nd*() is the primary key of the histogram. +The first time this function is called, it will add a secondary key (or two dimensional +histogram). If this function is called again on the same histogram, it will add +a _tertiary_ key (or three dimensional histogram). The _hist_ parameter is the +histogram descriptor to add the _key_ to. The _type_ is the type of key to add +(See KEY TYPES below). + +The *tracefs_hist_add_key_cnt*() is the same as *tracefs_hist_add_key*() except +that it allows to add a counter for the given type. Currently, there's only +the *buckets* type that requires a counter. When adding a key with the buckets +type, *tracefs_hist_add_key*() is not sufficient, as the *buckets* type requires +a counter or bucket size. Use *tracefs_hist_add_key_cnt*() when specifing +a key that is broken up into buckets, and pass in the size of those buckets +into _cnt_. + +*tracefs_hist_add_value*() will add a value to record. By default, the value is +simply the "hitcount" of the number of times a instance of the histogram's +key was hit. The _hist_ is the histogram descriptor to add the value to. +The _value_ is a field in the histogram to add to when an instance of the +key is hit. + +KEY TYPES +--------- + +*tracefs_hist_alloc_nd*() and *tracefs_hist_add_key*() both add a key and requires +that key to have a type. The types may be: + +*TRACEFS_HIST_KEY_NORMAL* or zero (0) which is to not modify the type. + +*TRACEFS_HIST_KEY_HEX* to display the key in hex. + +*TRACEFS_HIST_KEY_SYM* to display the key as a kernel symbol (if found). If +the key is an address, this is useful as it will display the function names instead +of just a number. + +*TRACEFS_HIST_KEY_SYM_OFFSET* similar to *TRACEFS_HIST_KEY_SYM* but will also include +the offset of the function to match the exact address. + +*TRACEFS_HIST_KEY_SYSCALL* If the key is a system call "id" (the number passed from user +space to the kernel to tell it what system call it is calling), then the name of +the system call is displayed. + +*TRACEFS_HIST_KEY_EXECNAME* If "common_pid" is the key (the pid of the executing task), +instead of showing the number, show the name of the running task. + +*TRACEFS_HIST_KEY_LOG* will display the key in a binary logarithmic scale. + +*TRACEFS_HIST_KEY_USECS* for use with "common_timestamp" or TRACEFS_HIST_TIMESTAMP, +in which case it will show the timestamp in microseconds instead of nanoseconds. + +RETURN VALUE +------------ +*tracefs_hist_alloc_nd*() returns an allocated histogram descriptor which must +be freed by *tracefs_hist_free*() or NULL on error. + +All the other functions return zero on success or -1 on error. + +If *tracefs_hist_start*() returns an error, a message may be displayed +in the kernel that can be retrieved by *tracefs_error_last()*. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <ctype.h> +#include <unistd.h> +#include <tracefs.h> + +enum commands { + START, + PAUSE, + CONT, + RESET, + DELETE, + SHOW, +}; + +static void parse_system_event(char *group, char **system, char **event) +{ + *system = strtok(group, "/"); + *event = strtok(NULL, "/"); + if (!*event) { + *event = *system; + *system = NULL; + } +} + +static int parse_keys(char *keys, struct tracefs_hist_axis_cnt **axes) +{ + char *sav = NULL; + char *key; + int cnt = 0; + + for (key = strtok_r(keys, ",", &sav); key; key = strtok_r(NULL, ",", &sav)) { + struct tracefs_hist_axis_cnt *ax; + char *att; + + ax = realloc(*axes, sizeof(*ax) * (cnt + 2)); + if (!ax) { + perror("Failed to allocate axes"); + exit(-1); + } + ax[cnt].key = key; + ax[cnt].type = 0; + ax[cnt + 1].key = NULL; + ax[cnt + 1].type = 0; + + *axes = ax; + + att = strchr(key, '.'); + if (att) { + *att++ = '\0'; + if (strcmp(att, "hex") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_HEX; + else if (strcmp(att, "sym") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_SYM; + else if (strcmp(att, "sym_offset") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_SYM_OFFSET; + else if (strcmp(att, "syscall") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_SYSCALL; + else if (strcmp(att, "exec") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_EXECNAME; + else if (strcmp(att, "log") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_LOG; + else if (strcmp(att, "usecs") == 0) + ax[cnt].type = TRACEFS_HIST_KEY_USECS; + else if (strncmp(att, "buckets", 7) == 0) { + if (att[7] != '=' && !isdigit(att[8])) { + fprintf(stderr, "'buckets' key type requires '=<size>'\n"); + exit(-1); + } + ax[cnt].type = TRACEFS_HIST_KEY_BUCKETS; + ax[cnt].cnt = atoi(&att[8]); + } else { + fprintf(stderr, "Undefined attribute '%s'\n", att); + fprintf(stderr," Acceptable attributes:\n"); + fprintf(stderr," hex, sym, sym_offset, syscall, exe, log, usecs\n"); + exit(-1); + } + } + cnt++; + } + return cnt; +} + +static void process_hist(enum commands cmd, const char *instance_name, + char *group, char *keys, char *vals, char *sort, + char *ascend, char *desc) +{ + struct tracefs_instance *instance = NULL; + struct tracefs_hist *hist; + struct tep_handle *tep; + struct tracefs_hist_axis_cnt *axes = NULL; + char *system; + char *event; + char *sav; + char *val; + int ret; + int cnt; + + if (instance_name) { + instance = tracefs_instance_create(instance_name); + if (!instance) { + fprintf(stderr, "Failed instance create\n"); + exit(-1); + } + } + + tep = tracefs_local_events(NULL); + if (!tep) { + perror("Could not read events"); + exit(-1); + } + + parse_system_event(group, &system, &event); + + if (cmd == SHOW) { + char *content; + content = tracefs_event_file_read(instance, system, event, + "hist", NULL); + if (!content) { + perror("Reading hist file"); + exit(-1); + } + printf("%s\n", content); + free(content); + return; + } + + if (!keys) { + fprintf(stderr, "Command needs -k option\n"); + exit(-1); + } + + cnt = parse_keys(keys, &axes); + if (!cnt) { + fprintf(stderr, "No keys??\n"); + exit(-1); + } + + /* buckets require the nd_cnt function */ + switch (cnt) { + case 2: + if (axes[1].type == TRACEFS_HIST_KEY_BUCKETS) + cnt = -1; + /* fall through */ + case 1: + if (axes[0].type == TRACEFS_HIST_KEY_BUCKETS) + cnt = -1; + } + + /* Show examples of hist1d and hist2d */ + switch (cnt) { + case 1: + hist = tracefs_hist_alloc(tep, system, event, + axes[0].key, axes[0].type); + break; + case 2: + hist = tracefs_hist_alloc_2d(tep, system, event, + axes[0].key, axes[0].type, + axes[1].key, axes[1].type); + break; + default: + /* Really, 1 and 2 could use this too */ + hist = tracefs_hist_alloc_nd_cnt(tep, system, event, axes); + } + if (!hist) { + fprintf(stderr, "Failed hist create\n"); + exit(-1); + } + + if (vals) { + sav = NULL; + for (val = strtok_r(vals, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) { + ret = tracefs_hist_add_value(hist, val); + if (ret) { + fprintf(stderr, "Failed to add value %s\n", val); + exit(-1); + } + } + } + + if (sort) { + sav = NULL; + for (val = strtok_r(sort, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) { + ret = tracefs_hist_add_sort_key(hist, val); + if (ret) { + fprintf(stderr, "Failed to add sort key/val %s\n", val); + exit(-1); + } + } + } + + if (ascend) { + sav = NULL; + for (val = strtok_r(ascend, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) { + ret = tracefs_hist_sort_key_direction(hist, val, TRACEFS_HIST_SORT_ASCENDING); + if (ret) { + fprintf(stderr, "Failed to add ascending key/val %s\n", val); + exit(-1); + } + } + } + + if (desc) { + sav = NULL; + for (val = strtok_r(desc, ",", &sav); val; val = strtok_r(NULL, ",", &sav)) { + ret = tracefs_hist_sort_key_direction(hist, val, TRACEFS_HIST_SORT_DESCENDING); + if (ret) { + fprintf(stderr, "Failed to add descending key/val %s\n", val); + exit(-1); + } + } + } + + tracefs_error_clear(instance); + + switch (cmd) { + case START: + ret = tracefs_hist_start(instance, hist); + if (ret) { + char *err = tracefs_error_last(instance); + if (err) + fprintf(stderr, "\n%s\n", err); + } + break; + case PAUSE: + ret = tracefs_hist_pause(instance, hist); + break; + case CONT: + ret = tracefs_hist_continue(instance, hist); + break; + case RESET: + ret = tracefs_hist_reset(instance, hist); + break; + case DELETE: + ret = tracefs_hist_destroy(instance, hist); + break; + case SHOW: + /* Show was already done */ + break; + } + if (ret) + fprintf(stderr, "Failed: command\n"); + exit(ret); +} + +int main (int argc, char **argv, char **env) +{ + enum commands cmd; + char *instance = NULL; + char *cmd_str; + char *event = NULL; + char *keys = NULL; + char *vals = NULL; + char *sort = NULL; + char *desc = NULL; + char *ascend = NULL; + + if (argc < 2) { + fprintf(stderr, "usage: %s command [-B instance][-e [system/]event][-k keys][-v vals][-s sort]\n", argv[0]); + fprintf(stderr, " [-a ascending][-d descending]\n"); + exit(-1); + } + + cmd_str = argv[1]; + + if (!strcmp(cmd_str, "start")) + cmd = START; + else if (!strcmp(cmd_str, "pause")) + cmd = PAUSE; + else if (!strcmp(cmd_str, "cont")) + cmd = CONT; + else if (!strcmp(cmd_str, "reset")) + cmd = RESET; + else if (!strcmp(cmd_str, "delete")) + cmd = DELETE; + else if (!strcmp(cmd_str, "show")) + cmd = SHOW; + else { + fprintf(stderr, "Unknown command %s\n", cmd_str); + exit(-1); + } + + for (;;) { + int c; + + c = getopt(argc - 1, argv + 1, "e:k:v:B:s:d:a:"); + if (c == -1) + break; + + switch (c) { + case 'e': + event = optarg; + break; + case 'k': + keys = optarg; + break; + case 'v': + vals = optarg; + break; + case 'B': + instance = optarg; + break; + case 's': + sort = optarg; + break; + case 'd': + desc = optarg; + break; + case 'a': + ascend = optarg; + break; + } + } + if (!event) { + event = "kmem/kmalloc"; + if (!keys) + keys = "call_site.sym,bytes_req"; + if (!vals) + vals = "bytes_alloc"; + if (!sort) + sort = "bytes_req,bytes_alloc"; + if (!desc) + desc = "bytes_alloc"; + } + process_hist(cmd, instance, event, keys, vals, sort, ascend, desc); +} + +-- + +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), +*tracefs_hist_pause*(3), +*tracefs_hist_continue*(3), +*tracefs_hist_reset*(3) + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-instances-affinity.txt b/Documentation/libtracefs-instances-affinity.txt new file mode 100644 index 0000000..39dd44c --- /dev/null +++ b/Documentation/libtracefs-instances-affinity.txt @@ -0,0 +1,200 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_instance_set_affinity, tracefs_instance_set_affinity_set, tracefs_instance_set_affinity_raw, +tracefs_instance_get_affinity, tracefs_instance_get_affinity_set, tracefs_instance_get_affinity_raw +- Sets or retrieves the affinity for an instance or top level for what CPUs enable tracing. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_instance_set_affinity*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_cpu_str_); +int *tracefs_instance_set_affinity_set*(struct tracefs_instance pass:[*]_instance_, cpu_set_t pass:[*]_set_, size_t _set_size_); +int *tracefs_instance_set_affinity_raw*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_mask_); + +char pass:[*]*tracefs_instance_get_affinity*(struct tracefs_instance pass:[*]_instance_); +int *tracefs_instance_get_affinity_set*(struct tracefs_instance pass:[*]_instance_, cpu_set_t pass:[*]_set_, size_t _set_size_); +char pass:[*]*tracefs_instance_get_affinity_raw*(struct tracefs_instance pass:[*]_instance_); +-- + +DESCRIPTION +----------- +These functions set or retrieve the CPU affinity that limits what CPUs will have tracing enabled +for a given instance defined by the _instance_ parameter. If _instance_ is NULL, then +the top level instance is affected. + +The *tracefs_instance_set_affinity()* function takes a string _cpu_str_ that is a +list of CPUs to set the affinity for. If _cpu_str_ is NULL, then all the CPUs in +the system will be set. The format of _cpu_str_ is a comma delimited string of +decimal numbers with no spaces. A range may be specified by a hyphen. + +For example: "1,4,6-8" + +The numbers do not need to be in order except for ranges, where the second number +must be equal to or greater than the first. + +The *tracefs_instance_set_affinity_set()* function takes a CPU set defined by +*CPU_SET*(3). The size of the set defined by _set_size_ is the size in bytes of +_set_. If _set_ is NULL then all the CPUs on the system will be set, and _set_size_ +is ignored. + +The *tracefs_instance_set_affinity_raw()* function takes a string that holds +a hexidecimal bitmask, where each 32 bits is separated by a comma. For a +machine with more that 32 CPUs, to set CPUS 1-10 and CPU 40: + + "100,000007fe" + +Where the above is a hex representation of bits 1-10 and bit 40 being set. + +The *tracefs_instance_get_affinity()* will retrieve the affinity in a human readable +form. + +For example: "1,4,6-8" + +The string returned must be freed with *free*(3). + +The *tracefs_instance_get_affinity_set()* will set all the bits in the passed in +cpu set (from *CPU_SET*(3)). Note it will not clear any bits that are already set +in the set but the CPUs are not. If only the bits for the CPUs that are enabled +should be set, a CPU_ZERO_S() should be performed on the set before calling this +function. + +The *tracefs_instance_get_affinity_raw()* will simply read the instance tracing_cpumask +and return that string. The returned string must be freed with *free*(3). + +RETURN VALUE +------------ +All the set functions return 0 on success and -1 on error. + +The functions *tracefs_instance_get_affinity()* and *tracefs_instance_get_affinity_raw()* +returns an allocated string that must be freed with *free*(3), or NULL on error. + +The function *tracefs_instance_get_affinity_set()* returns the number of CPUs that +were found set, or -1 on error. + + +ERRORS +------ +The following errors are for all the above calls: + +*EFBIG* if a CPU is set that is greater than what is in the system. + +*EINVAL* One of the parameters was invalid. + +The following errors are for *tracefs_instance_set_affinity*() and *tracefs_instance_set_affinity_set*(): + +*ENOMEM* Memory allocation error. + +*ENODEV* dynamic events of requested type are not configured for the running kernel. + +The following errors are just for *tracefs_instance_set_affinity*() + +*EACCES* The _cpu_str_ was modified by another thread when processing it. + +EXAMPLE +------- +[source,c] +-- +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <tracefs.h> + +int main (int argc, char **argv) +{ + struct trace_seq seq; + cpu_set_t *set; + size_t set_size; + char *c; + int cpu1; + int cpu2; + int i; + + c = tracefs_instance_get_affinity(NULL); + printf("The affinity was %s\n", c); + free(c); + + if (argc < 2) { + tracefs_instance_set_affinity(NULL, NULL); + exit(0); + } + /* Show example using a set */ + if (argc == 2 && !strchr(argv[1],',')) { + cpu1 = atoi(argv[1]); + c = strchr(argv[1], '-'); + if (c++) + cpu2 = atoi(c); + else + cpu2 = cpu1; + if (cpu2 < cpu1) { + fprintf(stderr, "Invalid CPU range\n"); + exit(-1); + } + set = CPU_ALLOC(cpu2 + 1); + set_size = CPU_ALLOC_SIZE(cpu2 + 1); + CPU_ZERO_S(set_size, set); + for ( ; cpu1 <= cpu2; cpu1++) + CPU_SET(cpu1, set); + tracefs_instance_set_affinity_set(NULL, set, set_size); + CPU_FREE(set); + exit(0); + } + + trace_seq_init(&seq); + for (i = 1; i < argc; i++) { + if (i > 1) + trace_seq_putc(&seq, ','); + trace_seq_puts(&seq, argv[i]); + } + trace_seq_terminate(&seq); + tracefs_instance_set_affinity(NULL, seq.buffer); + trace_seq_destroy(&seq); + exit(0); + + 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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-instances-file-manip.txt b/Documentation/libtracefs-instances-file-manip.txt new file mode 100644 index 0000000..8c04240 --- /dev/null +++ b/Documentation/libtracefs-instances-file-manip.txt @@ -0,0 +1,199 @@ +libtracefs(3) +============= + +NAME +---- + +tracefs_instance_file_open, +tracefs_instance_file_write, tracefs_instance_file_append, tracefs_instance_file_clear, +tracefs_instance_file_read, tracefs_instance_file_read_number - Work with files in tracing instances. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_instance_file_open*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_, int _mode_); +int *tracefs_instance_file_write*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_, const char pass:[*]_str_); +int *tracefs_instance_file_append*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_, const char pass:[*]_str_); +int *tracefs_instance_file_clear*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_); +char pass:[*]*tracefs_instance_file_read*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_, int pass:[*]_psize_); +int *tracefs_instance_file_read_number*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_, long long int pass:[*]_res_); + +-- + +DESCRIPTION +----------- +This set of APIs can be used to work with trace files in all trace instances. +Each of these APIs take an _instance_ argument, that can be NULL to act +on the top level instance. Otherwise, it acts on an instance created with +*tracefs_insance_create*(3) + +The *tracefs_instance_file_open()* function opens trace _file_ from given _instance_ and returns +a file descriptor to it. The file access _mode_ can be specified, see *open*(3) for more details. +If -1 is passed as _mode_, default O_RDWR is used. + +The *tracefs_instance_file_write()* function writes a string _str_ in a _file_ from +the given _instance_, without the terminating NULL character. When opening the file, this function +tries to truncates the size of the file to zero, which clears all previously existing settings. + +The *tracefs_instance_file_append()* function writes a string _str_ in a _file_ from +the given _instance_, without the terminating NULL character. This function is similar to +*tracefs_instance_file_write()*, but the existing content of the is not cleared. Thus the +new settings are appended to the existing ones (if any). + +The *tracefs_instance_file_clear()* function tries to truncates the size of the file to zero, +which clears all previously existing settings. If the file has content that does not get +cleared in this way, this will not have any effect. + +The *tracefs_instance_file_read()* function reads the content of a _file_ from +the given _instance_. + +The *tracefs_instance_file_read_number()* function reads the content of a _file_ from +the given _instance_ and converts it to a long long integer, which is stored in _res_. + +RETURN VALUE +------------ +The *tracefs_instance_file_open()* function returns a file descriptor to the opened file. It must be +closed with *close*(3). In case of an error, -1 is returned. + +The *tracefs_instance_file_write()* function returns the number of written bytes, +or -1 in case of an error. + +The *tracefs_instance_file_append()* function returns the number of written bytes, +or -1 in case of an error. + +The *tracefs_instance_file_clear()* function returns 0 on success, or -1 in case of an error. + +The *tracefs_instance_file_read()* function returns a pointer to a NULL terminated +string, read from the file, or NULL in case of an error. The returned string must +be freed with free(). + +The *tracefs_instance_file_read_number()* function returns 0 if a valid integer is read from +the file and stored in _res_ or -1 in case of an error. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + +struct tracefs_instance *inst = tracefs_instance_create("foo"); + if (!inst) { + /* Error creating a new trace instance */ + ... + } + + if (tracefs_file_exists(inst,"trace_clock")) { + /* The instance foo supports trace clock */ + char *path, *clock; + int size; + + path = = tracefs_instance_get_file(inst, "trace_clock") + if (!path) { + /* Error getting the path to trace_clock file in instance foo */ + ... + } + ... + tracefs_put_tracing_file(path); + + clock = tracefs_instance_file_read(inst, "trace_clock", &size); + if (!clock) { + /* Failed to read trace_clock file in instance foo */ + ... + } + ... + free(clock); + + if (tracefs_instance_file_write(inst, "trace_clock", "global") != strlen("global")) { + /* Failed to set gloabl trace clock in instance foo */ + ... + } + } else { + /* The instance foo does not support trace clock */ + } + + if (tracefs_dir_exists(inst,"options")) { + /* The instance foo supports trace options */ + char *path = tracefs_instance_get_file(inst, "options"); + if (!path) { + /* Error getting the path to options directory in instance foo */ + ... + } + + tracefs_put_tracing_file(path); + } else { + /* The instance foo does not support trace options */ + } + + ... + + if (tracefs_instance_is_new(inst)) + tracefs_instance_destroy(inst); + else + tracefs_instance_free(inst); + ... + + long long int res; + if (tracefs_instance_file_read_number(NULL, "tracing_on", &res) == 0) { + if (res == 0) { + /* tracing is disabled in the top instance */ + } else if (res == 1) { + /* tracing is enabled in the top instance */ + } else { + /* Unknown tracing state of the top instance */ + } + } else { + /* Failed to read integer from tracing_on file */ + } + + ... + + int fd; + fd = tracefs_instance_file_open(NULL, "tracing_on", O_WRONLY); + if (fd >= 0) { + /* Got file descriptor to the tracing_on file from the top instance for writing */ + ... + close(fd); + } +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-instances-files.txt b/Documentation/libtracefs-instances-files.txt new file mode 100644 index 0000000..e298557 --- /dev/null +++ b/Documentation/libtracefs-instances-files.txt @@ -0,0 +1,173 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_file_exists, tracefs_dir_exists, +tracefs_instance_get_file, tracefs_instance_get_dir - Work with files directories in tracing instances. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +bool *tracefs_file_exists*(struct tracefs_instance pass:[*]_instance_, char pass:[*]_name_); +bool *tracefs_dir_exists*(struct tracefs_instance pass:[*]_instance_, char pass:[*]_name_); +char pass:[*]*tracefs_instance_get_file*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_); +char pass:[*]*tracefs_instance_get_dir*(struct tracefs_instance pass:[*]_instance_); + +-- + +DESCRIPTION +----------- +This set of APIs can be used to work with trace files in all trace instances. +Each of these APIs take an _instance_ argument, that can be NULL to act +on the top level instance. Otherwise, it acts on an instance created with +*tracefs_insance_create*(3) + +The *tracefs_file_exists()* function checks if a file with _name_ exists in _instance_. + +The *tracefs_dir_exists()* function checks if a directory with _name_ exists in _instance_. + +The *tracefs_instance_get_file()* function returns the full path of the file +with given _name_ in _instance_. Note, it does not check if the file exists in +the instance. + +The *tracefs_instance_get_dir()* function returns the full path of the directory +with given _name_ in _instance_. Note, it does not check if the directory exists +in the instance. + +RETURN VALUE +------------ +The *tracefs_file_exists()* and *tracefs_dir_exists()* functions return true if the +file / directory exist in the given instance or false if it does not exist. + +The *tracefs_instance_get_file()* and *tracefs_instance_get_dir()* functions return +a string or NULL in case of an error. The returned string must be freed with +*tracefs_put_tracing_file()*. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + +struct tracefs_instance *inst = tracefs_instance_create("foo"); + if (!inst) { + /* Error creating a new trace instance */ + ... + } + + if (tracefs_file_exists(inst,"trace_clock")) { + /* The instance foo supports trace clock */ + char *path, *clock; + int size; + + path = = tracefs_instance_get_file(inst, "trace_clock") + if (!path) { + /* Error getting the path to trace_clock file in instance foo */ + ... + } + ... + tracefs_put_tracing_file(path); + + clock = tracefs_instance_file_read(inst, "trace_clock", &size); + if (!clock) { + /* Failed to read trace_clock file in instance foo */ + ... + } + ... + free(clock); + + if (tracefs_instance_file_write(inst, "trace_clock", "global") != strlen("global")) { + /* Failed to set gloabl trace clock in instance foo */ + ... + } + } else { + /* The instance foo does not support trace clock */ + } + + if (tracefs_dir_exists(inst,"options")) { + /* The instance foo supports trace options */ + char *path = tracefs_instance_get_file(inst, "options"); + if (!path) { + /* Error getting the path to options directory in instance foo */ + ... + } + + tracefs_put_tracing_file(path); + } else { + /* The instance foo does not support trace options */ + } + + ... + + if (tracefs_instance_is_new(inst)) + tracefs_instance_destroy(inst); + else + tracefs_instance_free(inst); + ... + + long long int res; + if (tracefs_instance_file_read_number(NULL, "tracing_on", &res) == 0) { + if (res == 0) { + /* tracing is disabled in the top instance */ + } else if (res == 1) { + /* tracing is enabled in the top instance */ + } else { + /* Unknown tracing state of the top instance */ + } + } else { + /* Failed to read integer from tracing_on file */ + } + + ... + + int fd; + fd = tracefs_instance_file_open(NULL, "tracing_on", O_WRONLY); + if (fd >= 0) { + /* Got file descriptor to the tracing_on file from the top instance for writing */ + ... + close(fd); + } +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-instances-manage.txt b/Documentation/libtracefs-instances-manage.txt new file mode 100644 index 0000000..c03a272 --- /dev/null +++ b/Documentation/libtracefs-instances-manage.txt @@ -0,0 +1,150 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_instance_create, tracefs_instance_destroy, tracefs_instance_alloc, tracefs_instance_free, +tracefs_instance_is_new, tracefs_instances - Manage trace instances. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +struct tracefs_instance pass:[*]*tracefs_instance_create*(const char pass:[*]_name_); +int *tracefs_instance_destroy*(struct tracefs_instance pass:[*]_instance_); +struct tracefs_instance pass:[*]*tracefs_instance_alloc*(const char pass:[*]_tracing_dir_, const char pass:[*]_name_); +void *tracefs_instance_free*(struct tracefs_instance pass:[*]_instance_); +bool *tracefs_instance_is_new*(struct tracefs_instance pass:[*]_instance_); +char pass:[**]*tracefs_instances*(const char pass:[*]_regex_); + +-- + +DESCRIPTION +----------- +This set of functions can be used to manage trace instances. A trace +instance is a sub buffer used by the Linux tracing system. Given a unique +name, the events enabled in an instance do not affect the main tracing +system, nor other instances, as events enabled in the main tracing system +or other instances do not affect the given instance. + +The *tracefs_instance_create()* function allocates and initializes a new +tracefs_instance structure and returns it. If the instance with _name_ does +not yet exist in the system, it will be created. The _name_ could be NULL, +then the new tracefs_instance structure is initialized for the top instance. +Note that the top instance cannot be created in the system, if it does not +exist. + +The *tracefs_instance_destroy()* removes the instance from the system, but +does not free the structure. *tracefs_instance_free()* must still be called +on _instance_. + +The tracefs_instance_alloc()* function allocates a new tracefs_instance structure +for existing trace instance. If the instance does not exist in the system, the function +fails. The _tracing_dir_ parameter points to the system trace directory. It can be +NULL, then default system trace directory is used. This parameter is useful to allocate +instances to trace directories, copied from another machine. The _name_ is the name of +the instance, or NULL for the top instance in the given _tracing_dir_. + +The *tracefs_instance_free()* function frees the tracefs_instance structure, +without removing the trace instance from the system. + +The *tracefs_instance_is_new()* function checks if the given _instance_ is +newly created by *tracefs_instance_create()*, or it has been in the system +before that. + +The *tracefs_instances*() function returns a list of instances that exist in +the system that match the regular expression _regex_. If _regex_ is NULL, then +it will match all instances that exist. The returned list must be freed with +*tracefs_list_free*(3). Note, if no instances are found an empty list is returned +and that too needs to be free with *tracefs_list_free*(3). + +RETURN VALUE +------------ +The *tracefs_instance_create()* and *tracefs_instance_alloc()* functions return a pointer to +a newly allocated tracefs_instance structure. It must be freed with *tracefs_instance_free()*. + +The *tracefs_instance_destroy()* function returns 0 if it succeeds to remove +the instance, otherwise it returns -1 if the instance does not exist or it +fails to remove it. + +The *tracefs_instance_is_new()* function returns true if the +*tracefs_instance_create()* that allocated _instance_ also created the +trace instance in the system, or false if the trace instance already +existed in the system when _instance_ was allocated by +*tracefs_instance_create()* or *tracefs_instance_alloc()*. + +The *tracefs_instances()* returns a list of instance names that exist on the system. +The list must be freed with *tracefs_list_free*(3). An empty list is returned if +no instance exists that matches _regex_, and this needs to be freed with +*tracefs_list_free*(3) as well. NULL is returned on error. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + +struct tracefs_instance *inst = tracefs_instance_create("foo"); + if (!inst) { + /* Error creating a new trace instance */ + ... + } + + ... + + if (tracefs_instance_is_new(inst)) + tracefs_instance_destroy(inst); + tracefs_instance_free(inst); +... + +struct tracefs_instance *inst = tracefs_instance_alloc(NULL, "bar"); + if (!inst) { + /* Error allocating 'bar' trace instance */ + ... + } + + ... + + tracefs_instance_free(inst); +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-instances-utils.txt b/Documentation/libtracefs-instances-utils.txt new file mode 100644 index 0000000..bc8c9a7 --- /dev/null +++ b/Documentation/libtracefs-instances-utils.txt @@ -0,0 +1,141 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_instance_get_name, tracefs_instance_get_trace_dir, tracefs_instances_walk, tracefs_instance_exists, +tracefs_instance_get_buffer_size, tracefs_instance_set_buffer_size - Helper functions for working with tracing instances. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +const char pass:[*]*tracefs_instance_get_name*(struct tracefs_instance pass:[*]_instance_); +const char pass:[*]*tracefs_instance_get_trace_dir*(struct tracefs_instance pass:[*]_instance_); +int *tracefs_instances_walk*(int (pass:[*]_callback_)(const char pass:[*], void pass:[*]), void pass:[*]_context)_; +bool *tracefs_instance_exists*(const char pass:[*]_name_); +size_t *tracefs_instance_get_buffer_size*(struct tracefs_instance pass:[*]_instance_, int _cpu_); +int *tracefs_instance_set_buffer_size*(struct tracefs_instance pass:[*]_instance_, size_t _size_, int _cpu_); +-- + +DESCRIPTION +----------- +Helper functions for working with trace instances. + +The *tracefs_instance_get_name()* function returns the name of the given _instance_. +Note that the top instance has no name, the function returns NULL for it. + +The *tracefs_instance_get_trace_dir()* function returns the tracing directory, where +the given _instance_ is configured. + +The *tracefs_instances_walk()* function walks through all configured tracing +instances in the system and calls _callback_ for each one of them. The _context_ +argument is passed to the _callback_, together with the instance name. If the +_callback_ returns non-zero, the iteration stops. Note, the _callback_ is not +called for the top top instance. + +The *tracefs_instance_exists()* function checks if an instance with the given +_name_ exists in the system. + +The *tracefs_instace_get_buffer_size()* returns the size of the ring buffer. If _cpu_ +is negative, it returns the total size of all the per CPU ring buffers, otherwise +it returns the size of the per CPU ring buffer for _cpu_. + +The *tracefs_instance_set_buffer_size()* function sets the size of the ring buffer. +If _cpu_ is negative, then it sets all the per CPU ring buffers to _size_ (note +the total size is the number of CPUs * _size_). If _cpu_ is specified, then it only +sets the size of the per CPU ring buffer. + +RETURN VALUE +------------ +The *tracefs_instance_get_name()* returns a string or NULL in case of the top +instance. The returned string must _not_ be freed. + +The *tracefs_instance_get_trace_dir()* returns a string or NULL in case of an error. +The returned string must _not_ be freed. + +The *tracefs_instances_walk()* function returns 0, if all instances were iterated, 1 +if the iteration was stopped by the _callback_, or -1 in case of an error. + +The *tracefs_instance_exists()* returns true if an instance with the given _name_ +exists in the system or false otherwise. + +The *tracefs_instance_get_buffer_size()* returns the size of the ring buffer depending on +the _cpu_ value passed in, or -1 on error. + +The *tracefs_instance_set_buffer_size()* returns zero on success and -1 on error. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + +struct tracefs_instance *inst; +.... +char *name = tracefs_instance_get_name(inst); + if(name) { + /* Got name of the instance */ + } +char *dir = tracefs_instance_get_trace_dir(inst); + if(dir) { + /* Got tracing directory of the instance */ + } +... +static int instance_walk(char *name, void *context) +{ + /* Got instance with name */ + return 0; +} +... + if (tracefs_instances_walk(instance_walk, NULL) < 0) { + /* Error walking through the instances */ + } +... + if (tracefs_instance_exists("foo")) { + /* There is instance with name foo in the system */ + } else { + /* There is no instance with name foo in the system */ + } +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-iterator.txt b/Documentation/libtracefs-iterator.txt new file mode 100644 index 0000000..b971bd0 --- /dev/null +++ b/Documentation/libtracefs-iterator.txt @@ -0,0 +1,229 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_iterate_raw_events, tracefs_iterate_stop, tracefs_follow_event, tracefs_follow_missed_events - Iterate over events in the ring buffer + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_iterate_raw_events*(struct tep_handle pass:[*]_tep_, struct tracefs_instance pass:[*]_instance_, + cpu_set_t pass:[*]_cpus_, int _cpu_size_, + int (pass:[*]_callback_)(struct tep_event pass:[*], struct tep_record pass:[*], int, void pass:[*]), + void pass:[*]_callback_context_); +void *tracefs_iterate_stop*(struct tracefs_instance pass:[*]_instance_); + +int *tracefs_follow_event*(struct tep_handle pass:[*]_tep_, struct tracefs_instance pass:[*]_instance_, + const char pass:[*]_system_, const char pass:[*]_event_name_, + int (pass:[*]_callback_)(struct tep_event pass:[*], + struct tep_record pass:[*], + int, void pass:[*]), + void pass:[*]_callback_data_); +int *tracefs_follow_missed_events*(struct tracefs_instance pass:[*]_instance_, + int (pass:[*]_callback_)(struct tep_event pass:[*], + struct tep_record pass:[*], + int, void pass:[*]), + void pass:[*]_callback_data_); +-- + +DESCRIPTION +----------- +Trace iterator over raw events. + +The *tracefs_iterate_raw_events()* function will read the tracefs raw +data buffers and call the specified _callback_ function for every event it +encounters. Events are iterated in sorted order: oldest first. An initialized +_tep_ handler is required (See *tracefs_local_events*(3)). If _instance_ is +NULL, then the toplevel tracefs buffer is used, otherwise the buffer for +the corresponding _instance_ is read. To filter only on a subset of CPUs, +_cpus_ and _cpu_size_ may be set to only call _callback_ with events that +occurred on the CPUs specified, otherwise if _cpus_ is NULL then the _callback_ +function will be called for all events, and _cpu_size_ is ignored. The +_callback_ function will be called with the following parameters: A +pointer to a struct tep_event that corresponds to the type of event the +record is; The record representing the event; The CPU that the event +occurred on; and a pointer to user specified _callback_context_. If the _callback_ +returns non-zero, the iteration stops. + +Use *tracefs_iterate_stop()* to force a executing *tracefs_iterate_raw_events()* +to halt. This can be called from either a callback that is called by +the iterator (even though a return of non-zero will stop it), or from another +thread. + +The *tracefs_follow_event()* is used with *tracefs_iterate_raw_events()* but +intead of the callback being called for every event, it is only called for the +specified _system_ / _event_name_ given to the function. The _callback_ is the +same as for *tracefs_iterate_raw_events()*, and the passed in _callback_context_ +will be passed to the _callback_ as well. Note, if it returns something other +than 0, it will stop the loop before the _callback_ of *tracefs_iterate_raw_events()* +is called. + +The *tracefs_follow_missed_events()* will call the _callback_ when missed +events are detected. It will set the _record_ parameter of the callback to the +record that came after the missed events and _event_ will be of the type of +event _record_ is. _cpu_ will be set to the CPU that missed the events, and +_callback_data_ will be the content that was passed in to the function. + +RETURN VALUE +------------ +The *tracefs_iterate_raw_events()* function returns -1 in case of an error or +0 otherwise. + +EXAMPLE +------- +[source,c] +-- +#include <unistd.h> +#include <tracefs.h> +#include <stdbool.h> +#include <signal.h> + +struct my_struct { + bool stopped; +}; + +#define MAX_COUNT 500000 +static int counter; + +static int callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct my_struct *my_data = data; + static struct trace_seq seq; + + if (counter++ > MAX_COUNT) { + my_data->stopped = true; + return 1; + } + + if (!seq.buffer) + trace_seq_init(&seq); + + tep_print_event(event->tep, &seq, record, "%16s-%-5d [%03d] %6.1000d %s: %s\n", + TEP_PRINT_COMM, TEP_PRINT_PID, TEP_PRINT_CPU, + TEP_PRINT_TIME, TEP_PRINT_NAME, TEP_PRINT_INFO); + trace_seq_terminate(&seq); + trace_seq_do_printf(&seq); + trace_seq_reset(&seq); + return 0; +} + +static int sched_callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + static struct tep_format_field *prev_pid; + static struct tep_format_field *next_pid; + unsigned long long pid; + int this_pid = *(int *)data; + + if (!prev_pid) { + prev_pid = tep_find_field(event, "prev_pid"); + next_pid = tep_find_field(event, "next_pid"); + if (!prev_pid || !next_pid) { + fprintf(stderr, "No pid fields??\n"); + return -1; + } + } + + tep_read_number_field(prev_pid, record->data, &pid); + if (pid == this_pid) + printf("WE ARE LEAVING!\n"); + tep_read_number_field(next_pid, record->data, &pid); + if (pid == this_pid) + printf("WE ARE ARRIVING!\n"); + return 0; +} + +static int missed_callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + printf("OOPS! cpu %d dropped ", cpu); + if (record->missed_events > 0) + printf("%lld ", record->missed_events); + printf("events\n"); + return 0; +} + +static struct tracefs_instance *instance; +static struct my_struct my_data; + +static void sig(int s) +{ + tracefs_iterate_stop(instance); + my_data.stopped = true; +} + +int main (int argc, char **argv, char **env) +{ + struct tep_handle *tep; + int this_pid = getpid(); + + instance = tracefs_instance_create("my-buffer"); + if (!instance) + return -1; + + signal(SIGINT, sig); + + tracefs_event_enable(instance, NULL, NULL); + sleep(1); + tracefs_event_disable(instance, NULL, NULL); + tep = tracefs_local_events(NULL); + tep_load_plugins(tep); + tracefs_follow_missed_events(instance, missed_callback, NULL); + tracefs_follow_event(tep, instance, "sched", "sched_switch", sched_callback, &this_pid); + tracefs_iterate_raw_events(tep, instance, NULL, 0, callback, &my_data); + tracefs_instance_destroy(instance); + + if (my_data.stopped) { + if (counter > MAX_COUNT) + printf("Finished max count\n"); + else + printf("Finished via signal\n"); + } + + 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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-kprobes.txt b/Documentation/libtracefs-kprobes.txt new file mode 100644 index 0000000..593ef9e --- /dev/null +++ b/Documentation/libtracefs-kprobes.txt @@ -0,0 +1,273 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_kprobe_alloc, tracefs_kretprobe_alloc, tracefs_kprobe_raw, tracefs_kretprobe_raw - +Allocate, get, and create kprobes + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +struct tracefs_dynevent pass:[*] +*tracefs_kprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_addr_, const char pass:[*]_format_); +struct tracefs_dynevent pass:[*] +*tracefs_kretprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_addr_, const char pass:[*]_format_, unsigned int _max_); +int *tracefs_kprobe_raw*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_addr_, const char pass:[*]_format_); +int *tracefs_kretprobe_raw*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_addr_, const char pass:[*]_format_); +-- + +DESCRIPTION +----------- +*tracefs_kprobe_alloc*() allocates a new kprobe context. The kbrobe is not configured in the system. +The new kprobe will be in the _system_ group (or kprobes if _system_ is NULL) and have the name of +_event_ (or _addr_ if _event_ is NULL). The kprobe will be inserted to _addr_ (function name, with +or without offset, or a address), and the _format_ will define the format of the kprobe. See the +Linux documentation file under: Documentation/trace/kprobetrace.rst + +*tracefs_kretprobe_alloc*() is the same as *tracefs_kprobe_alloc*, but allocates context for +kretprobe. It has one additional parameter, which is optional, _max_ - maxactive count. +See description of kretprobes in the Documentation/trace/kprobetrace.rst file. + +*tracefs_kprobe_raw*() will create a kprobe event. If _system_ is NULL, then +the default "kprobes" is used for the group (event system). Otherwise if _system_ +is specified then the kprobe will be created under the group by that name. The +_event_ is the name of the kprobe event to create. The _addr_ can be a function, +a function and offset, or a kernel address. This is where the location of the +kprobe will be inserted in the kernel. The _format_ is the kprobe format as +specified as FETCHARGS in the Linux kernel source in the Documentation/trace/kprobetrace.rst +document. + +*tracefs_kretprobe_raw*() is the same as *tracefs_kprobe_raw()*, except that it +creates a kretprobe instead of a kprobe. The difference is also described +in the Linux kernel source in the Documentation/trace/kprobetrace.rst file. + +RETURN VALUE +------------ + +*tracefs_kprobe_raw*() and *tracefs_kretprobe_raw*() return 0 on success, or -1 on error. +If a parsing error occurs on *tracefs_kprobe_raw*() or *tracefs_kretprobe_raw*() then +*tracefs_error_last*(3) may be used to retrieve the error message explaining the parsing issue. + +The *tracefs_kprobe_alloc*() and *tracefs_kretprobe_alloc*() APIs return a pointer to an allocated +tracefs_dynevent structure, describing the probe. This pointer must be freed by +*tracefs_dynevent_free*(3). Note, this only allocates a descriptor representing the kprobe. It does +not modify the running system. + +ERRORS +------ +The following errors are for all the above calls: + +*EPERM* Not run as root user + +*ENODEV* Kprobe events are not configured for the running kernel. + +*ENOMEM* Memory allocation error. + +*tracefs_kprobe_raw*(), *tracefs_kretprobe_raw*(), *tracefs_kprobe_alloc*(), +and *tracefs_kretprobe_alloc*() can fail with the following errors: + +*EBADMSG* if _addr_ is NULL. + +*EINVAL* Most likely a parsing error occurred (use *tracefs_error_last*(3) to possibly + see what that error was). + +Other errors may also happen caused by internal system calls. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> + +#include <tracefs.h> + +static struct tep_event *open_event; +static struct tep_format_field *file_field; + +static struct tep_event *openret_event; +static struct tep_format_field *ret_field; + +static int callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct trace_seq seq; + + trace_seq_init(&seq); + tep_print_event(event->tep, &seq, record, "%d-%s: ", TEP_PRINT_PID, TEP_PRINT_COMM); + + if (event->id == open_event->id) { + trace_seq_puts(&seq, "open file='"); + tep_print_field(&seq, record->data, file_field); + trace_seq_puts(&seq, "'\n"); + } else if (event->id == openret_event->id) { + unsigned long long ret; + tep_read_number_field(ret_field, record->data, &ret); + trace_seq_printf(&seq, "open ret=%lld\n", ret); + } else { + goto out; + } + + trace_seq_terminate(&seq); + trace_seq_do_printf(&seq); +out: + trace_seq_destroy(&seq); + + return 0; +} + +static pid_t run_exec(char **argv, char **env) +{ + pid_t pid; + + pid = fork(); + if (pid) + return pid; + + execve(argv[0], argv, env); + perror("exec"); + exit(-1); +} + +const char *mykprobe = "my_kprobes"; + +enum kprobe_type { + KPROBE, + KRETPROBE, +}; + +static void __kprobe_create(enum kprobe_type type, const char *event, + const char *addr, const char *fmt) +{ + char *err; + int r; + + if (type == KPROBE) + r = tracefs_kprobe_raw(mykprobe, event, addr, fmt); + else + r = tracefs_kretprobe_raw(mykprobe, event, addr, fmt); + if (r < 0) { + err = tracefs_error_last(NULL); + perror("Failed to create kprobe:"); + if (err && strlen(err)) + fprintf(stderr, "%s\n", err); + } +} + +static void kprobe_create(const char *event, const char *addr, + const char *fmt) +{ + __kprobe_create(KPROBE, event, addr, fmt); +} + +static void kretprobe_create(const char *event, const char *addr, + const char *fmt) +{ + __kprobe_create(KRETPROBE, event, addr, fmt); +} + +int main (int argc, char **argv, char **env) +{ + struct tracefs_instance *instance; + struct tep_handle *tep; + const char *sysnames[] = { mykprobe, NULL }; + pid_t pid; + + if (argc < 2) { + printf("usage: %s command\n", argv[0]); + exit(-1); + } + + instance = tracefs_instance_create("exec_open"); + if (!instance) { + perror("creating instance"); + exit(-1); + } + + tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, true); + + kprobe_create("open", "do_sys_openat2", + "file=+0($arg2):ustring flags=+0($arg3):x64 mode=+8($arg3):x64\n"); + + kretprobe_create("openret", "do_sys_openat2", "ret=%ax"); + + tep = tracefs_local_events_system(NULL, sysnames); + if (!tep) { + perror("reading events"); + exit(-1); + } + open_event = tep_find_event_by_name(tep, mykprobe, "open"); + file_field = tep_find_field(open_event, "file"); + + openret_event = tep_find_event_by_name(tep, mykprobe, "openret"); + ret_field = tep_find_field(openret_event, "ret"); + + tracefs_event_enable(instance, mykprobe, NULL); + pid = run_exec(&argv[1], env); + + /* Let the child start to run */ + sched_yield(); + + do { + tracefs_load_cmdlines(NULL, tep); + tracefs_iterate_raw_events(tep, instance, NULL, 0, callback, NULL); + } while (waitpid(pid, NULL, WNOHANG) != pid); + + /* Will disable the events */ + tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, true); + tracefs_instance_destroy(instance); + tep_free(tep); + + 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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2021 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-log.txt b/Documentation/libtracefs-log.txt new file mode 100644 index 0000000..4b72df1 --- /dev/null +++ b/Documentation/libtracefs-log.txt @@ -0,0 +1,76 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_set_loglevel - Set log level of the library + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_set_loglevel*(enum tep_loglevel _level_); +-- + +DESCRIPTION +----------- +The *tracefs_set_loglevel()* function sets the level of the library logs that will be printed on +the console. See _libtraceevent(3)_ for detailed description of the log levels. Setting the log +level to specific value means that logs from the previous levels will be printed too. For example +_TEP_LOG_WARNING_ will print any logs with severity _TEP_LOG_WARNING_, _TEP_LOG_ERROR_ and +_TEP_LOG_CRITICAL_. The default log level is _TEP_LOG_CRITICAL_. When a new level is set, it is +also propagated to the libtraceevent. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + +tracefs_set_loglevel(TEP_LOG_ALL); +... +/* call libtracefs or libtraceevent APIs and observe any logs they produce */ +... +tracefs_set_loglevel(TEP_LOG_CRITICAL); +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2021 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-marker.txt b/Documentation/libtracefs-marker.txt new file mode 100644 index 0000000..adc5419 --- /dev/null +++ b/Documentation/libtracefs-marker.txt @@ -0,0 +1,116 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_print_init, tracefs_print_close, tracefs_printf, tracefs_vprintf - +Open, close and write formated strings in the trace buffer. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_print_init*(struct tracefs_instance pass:[*]_instance_); +int *tracefs_printf*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_fmt_, _..._); +int *tracefs_vprintf*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_fmt_, va_list _ap_); +void *tracefs_print_close*(struct tracefs_instance pass:[*]_instance_); + +-- + +DESCRIPTION +----------- +Set of functions to write formated strings in the trace buffer. +See Documentation/trace/ftrace.rst from the Linux kernel tree for more information about writing +data from user space in the trace buffer. All these APIs have _instance_ as a first argument. If +NULL is passed as _instance_, the top trace instance is used. + +The *tracefs_print_init()* function initializes the library for writing into the trace buffer of +the selected _instance_. It is not mandatory to call this API before writing strings, any of +the printf APIs will call it automatically, if the library is not yet initialized. But calling +*tracefs_print_init()* in advance will speed up the writing. + +The *tracefs_printf()* function writes a formatted string in the trace buffer of the selected +_instance_. The _fmt_ argument is a string in printf format, followed by variable arguments _..._. + +The *tracefs_vprintf()* function writes a formatted string in the trace buffer of the selected +_instance_. The _fmt_ argument is a string in printf format, followed by list _ap_ of arguments. + +The *tracefs_print_close()* function closes the resources, used by the library for writing in +the trace buffer of the selected instance. + +RETURN VALUE +------------ +The *tracefs_print_init()*, *tracefs_printf()*, and *tracefs_vprintf()* functions return 0 if +the operation is successful, or -1 in case of an error. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + +if (tracefs_print_init(NULL) < 0) { + /* Failed to initialize the library for writing in the trace buffer of the top trace instance */ +} + +void foo_print(char *format, ...) +{ + va_list ap; + va_start(ap, format); + if (tracefs_vprintf(NULL, format, ap) < 0) { + /* Failed to print in the trace buffer */ + } + va_end(ap); +} + +void foo_print_string(char *message) +{ + if (tracefs_printf(NULL, "Message from user space: %s", message) < 0) { + /* Failed to print in the trace buffer */ + } +} + +tracefs_print_close(); +-- +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), +Documentation/trace/ftrace.rst from the Linux kernel tree + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2021 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-marker_raw.txt b/Documentation/libtracefs-marker_raw.txt new file mode 100644 index 0000000..9682f20 --- /dev/null +++ b/Documentation/libtracefs-marker_raw.txt @@ -0,0 +1,102 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_binary_init, tracefs_binary_close, tracefs_binary_write - +Open, close and write binary data in the trace buffer. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_binary_init*(struct tracefs_instance pass:[*]_instance_); +int *tracefs_binary_write*(struct tracefs_instance pass:[*]_instance_, void pass:[*]_data_, int _len_); +void *tracefs_binary_close*(struct tracefs_instance pass:[*]_instance_); + +-- + +DESCRIPTION +----------- +Set of functions to write binary data in the trace buffer. +See Documentation/trace/ftrace.rst from the Linux kernel tree for more information about writing +data from user space in the trace buffer. All these APIs have _instance_ as a first argument. If +NULL is passed as _instance_, the top trace instance is used. + +The *tracefs_binary_init()* function initializes the library for writing into the trace buffer of +the selected _instance_. It is not mandatory to call this API before writing data, the +*tracefs_binary_write()* will call it automatically, if the library is not yet initialized. +But calling *tracefs_binary_init()* in advance will speed up the writing. + +The *tracefs_binary_write()* function writes a binary data in the trace buffer of the selected +_instance_. The _data_ points to the data with length _len_, that is going to be written in +the trace buffer. + +The *tracefs_binary_close()* function closes the resources, used by the library for writing in +the trace buffer of the selected instance. + +RETURN VALUE +------------ +The *tracefs_binary_init()*, and *tracefs_binary_write()* functions return 0 if the operation is +successful, or -1 in case of an error. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + +if (tracefs_binary_init(NULL) < 0) { + /* Failed to initialize the library for writing in the trace buffer of the top trace instance */ +} + +unsigned int data = 0xdeadbeef; + + if (tracefs_binary_write(NULL, &data, sizeof(data)) < 0) { + /* Failed to write in the trace buffer */ + } + +tracefs_binary_close(); +-- +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), +Documentation/trace/ftrace.rst from the Linux kernel tree + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2021 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-option-get.txt b/Documentation/libtracefs-option-get.txt new file mode 100644 index 0000000..8a688a7 --- /dev/null +++ b/Documentation/libtracefs-option-get.txt @@ -0,0 +1,141 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_options_get_supported, tracefs_option_is_supported, tracefs_options_get_enabled, +tracefs_option_is_enabled, tracefs_option_mask_is_set, tracefs_option_id +- Get and check ftrace options. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +const struct tracefs_options_mask pass:[*]*tracefs_options_get_supported*(struct tracefs_instance pass:[*]_instance_); +bool *tracefs_option_is_supported*(struct tracefs_instance pass:[*]_instance_, enum tracefs_option_id _id_); +const struct tracefs_options_mask pass:[*]*tracefs_options_get_enabled*(struct tracefs_instance pass:[*]_instance_); +bool *tracefs_option_is_enabled*(struct tracefs_instance pass:[*]_instance_, enum tracefs_option_id _id_); +bool *tracefs_option_mask_is_set*(const struct tracefs_options_mask *options, enum tracefs_option_id id); +enum tracefs_option_id *tracefs_option_id*(const char pass:[*]_name_); +-- + +DESCRIPTION +----------- +This set of APIs can be used to get and check current ftrace options. Supported ftrace options may +depend on the kernel version and the kernel configuration. + +The *tracefs_options_get_supported()* function gets all ftrace options supported by the system in +the given _instance_. If _instance_ is NULL, supported options of the top trace instance are +returned. The set of supported options is the same in all created trace instances, but may be different +than the top trace instance. + +The *tracefs_option_is_supported()/ function checks if the option with given _id_ is supported by +the system in the given _instance_. If _instance_ is NULL, the top trace instance is used. If an +option is supported at the top trace instance, it it may not be supported in a created trace instance. + +The *tracefs_options_get_enabled()* function gets all ftrace options, currently enabled in +the given _instance_. If _instance_ is NULL, enabled options of the top trace instance are returned. + +The *tracefs_option_is_enabled()* function checks if the option with given _id_ is enabled in the +given _instance_. If _instance_ is NULL, the top trace instance is used. + +The *tracefs_option_mask_is_set()* function checks if the bit, corresponding to the option with _id_ is +set in the _options_ bitmask returned from *tracefs_option_get_enabled()* and *tracefs_option_is_supported()*. + +The *tracefs_option_id()* converts an option _name_ into its corresponding id, if it is found. +This allows to find the option _id_ to use in the other functions if only the _name_ is known. + +RETURN VALUE +------------ +The *tracefs_options_get_supported()* and *tracefs_options_get_enabled()* functions, on success, +return a pointer to the bitmask within the instance, or a global bitmask for the top level, +or NULL in case of an error. As the returned bitmask is part of the instance structure (or a +global variable) and must not be freed or modified. + +The *tracefs_option_is_supported()* and *tracefs_option_is_enabled()* functions return true if the +option in supported / enabled, or false otherwise. + +The *tracefs_option_mask_is_set()* returns true if the corresponding option is set in the mask +or false otherwise. + +The *tracefs_option_id()* returns the corresponding id defined by *tracefs_options*(3) from +the given _name_. If the _name_ can not be found, then TRACEFS_OPTION_INVALID is returned. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> +... +const struct tracefs_options_mask *options; +... +options = tracefs_options_get_supported(NULL); +if (!options) { + /* Failed to get supported options */ +} else { + ... +} +... +options = tracefs_options_get_enabled(NULL); +if (!options) { + /* Failed to get options, enabled in the top instance */ +} else { + ... +} +if (tracefs_options_mask_is_set(options, TRACEFS_OPTION_LATENCY_FORMAT)) { + ... +} +... + +if (tracefs_option_is_supported(NULL, TRACEFS_OPTION_LATENCY_FORMAT)) { + /* Latency format option is supprted */ +} + +... + +if (tracefs_option_is_enabled(NULL, TRACEFS_OPTION_STACKTRACE)) { + /* Stacktrace option is enabled in the top instance */ +} + +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-option-misc.txt b/Documentation/libtracefs-option-misc.txt new file mode 100644 index 0000000..c690bfd --- /dev/null +++ b/Documentation/libtracefs-option-misc.txt @@ -0,0 +1,100 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_option_enable, tracefs_option_disable, tracefs_option_name - +Various trace option functions. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_option_enable*(struct tracefs_instance pass:[*]_instance_, enum tracefs_option_id _id_); +int *tracefs_option_disable*(struct tracefs_instance pass:[*]_instance_, enum tracefs_option_id _id_); +const char pass:[*]*tracefs_option_name*(enum tracefs_option_id _id_); +-- + +DESCRIPTION +----------- +This set of APIs can be used to enable and disable ftrace options and to get the name of an option. + +The *tracefs_option_enable()* function enables the option with _id_ in the given _instance_. If +_instance_ is NULL, the option is enabled in the top trace instance. + +The *tracefs_option_disable()* function disables the option with _id_ in the given _instance_. If +_instance_ is NULL, the option is disabled in the top trace instance. + +The *tracefs_option_name()* function returns a string, representing the option with _id_. The string +must *not* be freed. + + +RETURN VALUE +------------ +The *tracefs_option_enable()* and *tracefs_option_disable()* functions return 0 if the state of the +option is set successfully, or -1 in case of an error. + +The *tracefs_option_name()* function returns string with option name, or "unknown" in case of an +error. The returned string must *not* be freed. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> +... +if (tracefs_option_enable(NULL, TRACEFS_OPTION_ANNOTATE)) { + /* Failed to enable annotate option in top trace instance */ +} +... +if (tracefs_option_disable(NULL, TRACEFS_OPTION_CONTEXT_INFO)) { + /* Failed to disable context info option in top trace instance */ +} +... +char *name = tracefs_option_name(TRACEFS_OPTION_FUNC_STACKTRACE); +if (strcmp(name, "unknown")) { + /* Cannot get the name of the option */ +} + +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-options.txt b/Documentation/libtracefs-options.txt new file mode 100644 index 0000000..2c720f2 --- /dev/null +++ b/Documentation/libtracefs-options.txt @@ -0,0 +1,159 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_options - ftrace options, that can be controlled using tracefs library. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +enum tracefs_option_id { + *TRACEFS_OPTION_INVALID*, + *TRACEFS_OPTION_ANNOTATE*, + *TRACEFS_OPTION_BIN*, + *TRACEFS_OPTION_BLK_CGNAME*, + *TRACEFS_OPTION_BLK_CGROUP*, + *TRACEFS_OPTION_BLK_CLASSIC*, + *TRACEFS_OPTION_BLOCK*, + *TRACEFS_OPTION_CONTEXT_INFO*, + *TRACEFS_OPTION_DISABLE_ON_FREE*, + *TRACEFS_OPTION_DISPLAY_GRAPH*, + *TRACEFS_OPTION_EVENT_FORK*, + *TRACEFS_OPTION_FGRAPH_ABSTIME*, + *TRACEFS_OPTION_FGRAPH_CPU*, + *TRACEFS_OPTION_FGRAPH_DURATION*, + *TRACEFS_OPTION_FGRAPH_IRQS*, + *TRACEFS_OPTION_FGRAPH_OVERHEAD*, + *TRACEFS_OPTION_FGRAPH_OVERRUN*, + *TRACEFS_OPTION_FGRAPH_PROC*, + *TRACEFS_OPTION_FGRAPH_TAIL*, + *TRACEFS_OPTION_FUNC_STACKTRACE*, + *TRACEFS_OPTION_FUNCTION_FORK*, + *TRACEFS_OPTION_FUNCTION_TRACE*, + *TRACEFS_OPTION_GRAPH_TIME*, + *TRACEFS_OPTION_HEX*, + *TRACEFS_OPTION_IRQ_INFO*, + *TRACEFS_OPTION_LATENCY_FORMAT*, + *TRACEFS_OPTION_MARKERS*, + *TRACEFS_OPTION_OVERWRITE*, + *TRACEFS_OPTION_PAUSE_ON_TRACE*, + *TRACEFS_OPTION_PRINTK_MSG_ONLY*, + *TRACEFS_OPTION_PRINT_PARENT*, + *TRACEFS_OPTION_RAW*, + *TRACEFS_OPTION_RECORD_CMD*, + *TRACEFS_OPTION_RECORD_TGID*, + *TRACEFS_OPTION_SLEEP_TIME*, + *TRACEFS_OPTION_STACKTRACE*, + *TRACEFS_OPTION_SYM_ADDR*, + *TRACEFS_OPTION_SYM_OFFSET*, + *TRACEFS_OPTION_SYM_USEROBJ*, + *TRACEFS_OPTION_TRACE_PRINTK*, + *TRACEFS_OPTION_USERSTACKTRACE*, + *TRACEFS_OPTION_VERBOSE*, +}; +-- + +DESCRIPTION +----------- +This enum contains all ftrace options, that can be manipulated using tracefs library. More detailed +information about each option is available in Documentation/trace/ftrace.rst from the Linux +kernel tree, in the trace_options section. Note that some ftrace options cannot be manipulated by +this library, as they are intended for internal, debug purposes. These options control the tracers +or the trace output. All options have two states - on and off, the default state is different for +each of them. +[verse] +-- +Common options for all tracers: + *TRACEFS_OPTION_INVALID* Not a valid ftrace option, used by the API to indicate an error. + *TRACEFS_OPTION_ANNOTATE* Display when a new CPU buffer started. + *TRACEFS_OPTION_BIN* Display the formats in raw binary. + *TRACEFS_OPTION_CONTEXT_INFO* Show only the event data. Hides the comm, PID, timestamp, CPU, and other useful data. + *TRACEFS_OPTION_BLOCK* When set, reading trace_pipe will not block when polled. + *TRACEFS_OPTION_DISABLE_ON_FREE* When the free_buffer is closed, tracing will stop. + *TRACEFS_OPTION_DISPLAY_GRAPH* When set, the latency tracers will use function graph tracing instead of function tracing. + *TRACEFS_OPTION_EVENT_FORK* When set, tasks with PIDs listed in set_event_pid will have the PIDs of their children added to set_event_pid when those tasks fork. + *TRACEFS_OPTION_FUNCTION_FORK* When set, tasks with PIDs listed in set_ftrace_pid will have the PIDs of their children added to set_ftrace_pid when those tasks fork. + *TRACEFS_OPTION_FUNCTION_TRACE* When enabled, the latency tracers will trace functions. + *TRACEFS_OPTION_HEX* Display numbers in a hexadecimal format. + *TRACEFS_OPTION_IRQ_INFO* Display the interrupt, preempt count, need resched data. + *TRACEFS_OPTION_LATENCY_FORMAT* Display additional information about the latency. + *TRACEFS_OPTION_MARKERS* When set, the trace_marker is enabled - writable (only by root). + *TRACEFS_OPTION_OVERWRITE* Controls what happens when the trace buffer is full. If set, the oldest events are discarded and overwritten. If disabled, then the newest events are discarded. + *TRACEFS_OPTION_PAUSE_ON_TRACE* When set, opening the trace file for read, will pause writing to the ring buffer. When the file is closed, tracing will be enabled again. + *TRACEFS_OPTION_PRINTK_MSG_ONLY* When set, trace_printk()s will only show the format and not their parameters. + *TRACEFS_OPTION_PRINT_PARENT* On function traces, display the calling (parent) function as well as the function being traced. + *TRACEFS_OPTION_RAW* Display raw numbers. + *TRACEFS_OPTION_RECORD_CMD* Save a mapping with a pid and corresponding command. + *TRACEFS_OPTION_RECORD_TGID* Save a mapping with a pid and corresponding Thread Group IDs. + *TRACEFS_OPTION_STACKTRACE* Record a stack trace after any trace event. + *TRACEFS_OPTION_SYM_ADDR* Display the function address as well as the function name. + *TRACEFS_OPTION_SYM_OFFSET* Display not only the function name, but also the offset in the function. + *TRACEFS_OPTION_SYM_USEROBJ* When *TRACEFS_OPTION_USERSTACKTRACE* is set, look up which object the address belongs to, and print the object and a relative address. + *TRACEFS_OPTION_TRACE_PRINTK* Disable trace_printk() from writing into the buffer. + *TRACEFS_OPTION_USERSTACKTRACE* Records a stack trace of the current user space thread after each trace event. + *TRACEFS_OPTION_VERBOSE* When *TRACEFS_OPTION_LATENCY_FORMAT* is enabled, print more detailed information. + +Options, specific to function tracer: + *TRACEFS_OPTION_FUNC_STACKTRACE* Record a stack trace after every function. + +Options, specific to function_graph tracer: + *TRACEFS_OPTION_FGRAPH_ABSTIME* Display the timestamp at each line. + *TRACEFS_OPTION_FGRAPH_CPU* Display the CPU number of the CPU where the trace occurred. + *TRACEFS_OPTION_FGRAPH_DURATION* Display the duration of the amount of time at the end of each function, in microseconds. + *TRACEFS_OPTION_FGRAPH_IRQS* Trace functions that happen inside an interrupt. + *TRACEFS_OPTION_FGRAPH_OVERHEAD* Display a marker if a function takes longer than a certain amount of time. + *TRACEFS_OPTION_FGRAPH_OVERRUN* Display "overrun" of the call graph, in the case of functions missed due to big callstack. + *TRACEFS_OPTION_FGRAPH_PROC* Display the command of each process at every line. + *TRACEFS_OPTION_FGRAPH_TAIL* Display the function name on its return. + *TRACEFS_OPTION_SLEEP_TIME* Account time the task has been scheduled out as part of the function call. + *TRACEFS_OPTION_GRAPH_TIME* Display the time to call nested functions, if function profiler is enabled. + +Options, specific to blk tracer: + *TRACEFS_OPTION_BLK_CGNAME* + *TRACEFS_OPTION_BLK_CGROUP* + *TRACEFS_OPTION_BLK_CLASSIC* Display a more minimalistic output. +-- +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), +_Documentation/trace/ftrace.rst_ from the Linux kernel tree. + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2021 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-sql.txt b/Documentation/libtracefs-sql.txt new file mode 100644 index 0000000..6d606db --- /dev/null +++ b/Documentation/libtracefs-sql.txt @@ -0,0 +1,628 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_sql - Create a synthetic event via an SQL statement + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +struct tracefs_synth pass:[*]*tracefs_sql*(struct tep_handle pass:[*]_tep_, const char pass:[*]_name_, + const char pass:[*]_sql_buffer_, char pass:[**]_err_); +-- + +DESCRIPTION +----------- +Synthetic events are dynamically created events that attach two existing events +together via one or more matching fields between the two events. It can be used +to find the latency between the events, or to simply pass fields of the first event +on to the second event to display as one event. + +The Linux kernel interface to create synthetic events is complex, and there needs +to be a better way to create synthetic events that is easy and can be understood +via existing technology. + +If you think of each event as a table, where the fields are the column of the table +and each instance of the event as a row, you can understand how SQL can be used +to attach two events together and form another event (table). Utilizing the +SQL *SELECT* *FROM* *JOIN* *ON* [ *WHERE* ] syntax, a synthetic event can easily +be created from two different events. + +For simple SQL queries to make a histogram instead of a synthetic event, see +HISTOGRAMS below. + +*tracefs_sql*() takes in a _tep_ handler (See _tep_local_events_(3)) that is used to +verify the events within the _sql_buffer_ expression. The _name_ is the name of the +synthetic event to create. If _err_ points to an address of a string, it will be filled +with a detailed message on any type of parsing error, including fields that do not belong +to an event, or if the events or fields are not properly compared. + +The example program below is a fully functional parser where it will create a synthetic +event from a SQL syntax passed in via the command line or a file. + +The SQL format is as follows: + +*SELECT* <fields> *FROM* <start-event> *JOIN* <end-event> *ON* <matching-fields> *WHERE* <filter> + +Note, although the examples show the SQL commands in uppercase, they are not required to +be so. That is, you can use "SELECT" or "select" or "sElEct". + +For example: +[source,c] +-- +SELECT syscalls.sys_enter_read.fd, syscalls.sys_exit_read.ret FROM syscalls.sys_enter_read + JOIN syscalls.sys_exit_read + ON syscalls.sys_enter_read.common_pid = syscalls.sys_exit_write.common_pid +-- + +Will create a synthetic event that with the fields: + + u64 fd; s64 ret; + +Because the function takes a _tep_ handle, and usually all event names are unique, you can +leave off the system (group) name of the event, and *tracefs_sql*() will discover the +system for you. + +That is, the above statement would work with: + +[source,c] +-- +SELECT sys_enter_read.fd, sys_exit_read.ret FROM sys_enter_read JOIN sys_exit_read + ON sys_enter_read.common_pid = sys_exit_write.common_pid +-- + +The *AS* keyword can be used to name the fields as well as to give an alias to the +events, such that the above can be simplified even more as: + +[source,c] +-- +SELECT start.fd, end.ret FROM sys_enter_read AS start JOIN sys_exit_read AS end ON start.common_pid = end.common_pid +-- + +The above aliases _sys_enter_read_ as *start* and _sys_exit_read_ as *end* and uses +those aliases to reference the event throughout the statement. + +Using the *AS* keyword in the selection portion of the SQL statement will define what +those fields will be called in the synthetic event. + +[source,c] +-- +SELECT start.fd AS filed, end.ret AS return FROM sys_enter_read AS start JOIN sys_exit_read AS end + ON start.common_pid = end.common_pid +-- + +The above labels the _fd_ of _start_ as *filed* and the _ret_ of _end_ as *return* where +the synthetic event that is created will now have the fields: + + u64 filed; s64 return; + +The fields can also be calculated with results passed to the synthetic event: + +[source,c] +-- +select start.truesize, end.len, (start.truesize - end.len) as diff from napi_gro_receive_entry as start + JOIN netif_receive_skb as end ON start.skbaddr = end.skbaddr +-- + +Which would show the *truesize* of the _napi_gro_receive_entry_ event, the actual +_len_ of the content, shown by the _netif_receive_skb_, and the delta between +the two and expressed by the field *diff*. + +The code also supports recording the timestamps at either event, and performing calculations +on them. For wakeup latency, you have: + +[source,c] +-- +select start.pid, (end.TIMESTAMP_USECS - start.TIMESTAMP_USECS) as lat from sched_waking as start + JOIN sched_switch as end ON start.pid = end.next_pid +-- + +The above will create a synthetic event that records the _pid_ of the task being woken up, +and the time difference between the _sched_waking_ event and the _sched_switch_ event. +The *TIMESTAMP_USECS* will truncate the time down to microseconds as the timestamp usually +recorded in the tracing buffer has nanosecond resolution. If you do not want that +truncation, use *TIMESTAMP* instead of *TIMESTAMP_USECS*. + +Finally, the *WHERE* clause can be added, that will let you add filters on either or both events. + +[source,c] +-- +select start.pid, (end.TIMESTAMP_USECS - start.TIMESTAMP_USECS) as lat from sched_waking as start + JOIN sched_switch as end ON start.pid = end.next_pid + WHERE start.prio < 100 && (!(end.prev_pid < 1 || end.prev_prio > 100) || end.prev_pid == 0) +-- + +*NOTE* + +Although both events can be used together in the *WHERE* clause, they must not be mixed outside +the top most "&&" statements. You can not OR (||) the events together, where a filter of one +event is OR'd to a filter of the other event. This does not make sense, as the synthetic event +requires both events to take place to be recorded. If one is filtered out, then the synthetic +event does not execute. + +[source,c] +-- +select start.pid, (end.TIMESTAMP_USECS - start.TIMESTAMP_USECS) as lat from sched_waking as start + JOIN sched_switch as end ON start.pid = end.next_pid + WHERE start.prio < 100 && end.prev_prio < 100 +-- + +The above is valid. + +Where as the below is not. + +[source,c] +-- +select start.pid, (end.TIMESTAMP_USECS - start.TIMESTAMP_USECS) as lat from sched_waking as start + JOIN sched_switch as end ON start.pid = end.next_pid + WHERE start.prio < 100 || end.prev_prio < 100 +-- + + +KEYWORDS AS EVENT FIELDS +------------------------ + +In some cases, an event may have a keyword. For example, regcache_drop_region has "from" +as a field and the following will not work + +[source,c] +-- + select from from regcache_drop_region +-- + +In such cases, add a backslash to the conflicting field, and this will tell the parser +that the "from" is a field and not a keyword: + +[source,c] +-- + select \from from regcache_drop_region +-- + +HISTOGRAMS +---------- + +Simple SQL statements without the *JOIN* *ON* may also be used, which will create a histogram +instead. When doing this, the struct tracefs_hist descriptor can be retrieved from the +returned synthetic event descriptor via the *tracefs_synth_get_start_hist*(3). + +In order to utilize the histogram types (see xxx) the CAST command of SQL can be used. + +That is: + +[source,c] +-- + select CAST(common_pid AS comm), CAST(id AS syscall) FROM sys_enter +-- + +Which produces: + +[source,c] +-- + # echo 'hist:keys=common_pid.execname,id.syscall' > events/raw_syscalls/sys_enter/trigger + + # cat events/raw_syscalls/sys_enter/hist + +{ common_pid: bash [ 18248], id: sys_setpgid [109] } hitcount: 1 +{ common_pid: sendmail [ 1812], id: sys_read [ 0] } hitcount: 1 +{ common_pid: bash [ 18247], id: sys_getpid [ 39] } hitcount: 1 +{ common_pid: bash [ 18247], id: sys_dup2 [ 33] } hitcount: 1 +{ common_pid: gmain [ 13684], id: sys_inotify_add_watch [254] } hitcount: 1 +{ common_pid: cat [ 18247], id: sys_access [ 21] } hitcount: 1 +{ common_pid: bash [ 18248], id: sys_getpid [ 39] } hitcount: 1 +{ common_pid: cat [ 18247], id: sys_fadvise64 [221] } hitcount: 1 +{ common_pid: sendmail [ 1812], id: sys_openat [257] } hitcount: 1 +{ common_pid: less [ 18248], id: sys_munmap [ 11] } hitcount: 1 +{ common_pid: sendmail [ 1812], id: sys_close [ 3] } hitcount: 1 +{ common_pid: gmain [ 1534], id: sys_poll [ 7] } hitcount: 1 +{ common_pid: bash [ 18247], id: sys_execve [ 59] } hitcount: 1 +-- + +Note, string fields may not be cast. + +The possible types to cast to are: + +*HEX* - convert the value to use hex and not decimal + +*SYM* - convert a pointer to symbolic (kallsyms values) + +*SYM-OFFSET* - convert a pointer to symbolic and include the offset. + +*SYSCALL* - convert the number to the mapped system call name + +*EXECNAME* or *COMM* - can only be used with the common_pid field. Will show the task +name of the process. + +*LOG* or *LOG2* - bucket the key values in a log 2 values (1, 2, 3-4, 5-8, 9-16, 17-32, ...) + +The above fields are not case sensitive, and "LOG2" works as good as "log". + +A special CAST to _COUNTER_ or __COUNTER__ will make the field a value and not +a key. For example: + +[source,c] +-- + SELECT common_pid, CAST(bytes_req AS _COUNTER_) FROM kmalloc +-- + +Which will create + +[source,c] +-- + echo 'hist:keys=common_pid:vals=bytes_req' > events/kmem/kmalloc/trigger + + cat events/kmem/kmalloc/hist + +{ common_pid: 1812 } hitcount: 1 bytes_req: 32 +{ common_pid: 9111 } hitcount: 2 bytes_req: 272 +{ common_pid: 1768 } hitcount: 3 bytes_req: 1112 +{ common_pid: 0 } hitcount: 4 bytes_req: 512 +{ common_pid: 18297 } hitcount: 11 bytes_req: 2004 +-- + +RETURN VALUE +------------ +Returns 0 on success and -1 on failure. On failure, if _err_ is defined, it will be +allocated to hold a detailed description of what went wrong if it the error was caused +by a parsing error, or that an event, field does not exist or is not compatible with +what it was combined with. + +CREATE A TOOL +------------- + +The below example is a functional program that can be used to parse SQL commands into +synthetic events. + +[source, c] +-- + man tracefs_sql | sed -ne '/^EXAMPLE/,/FILES/ { /EXAMPLE/d ; /FILES/d ; p}' > sqlhist.c + gcc -o sqlhist sqlhist.c `pkg-config --cflags --libs libtracefs` +-- + +Then you can run the above examples: + +[source, c] +-- + sudo ./sqlhist 'select start.pid, (end.TIMESTAMP_USECS - start.TIMESTAMP_USECS) as lat from sched_waking as start + JOIN sched_switch as end ON start.pid = end.next_pid + WHERE start.prio < 100 || end.prev_prio < 100' +-- +EXAMPLE +------- +[source,c] +-- +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <tracefs.h> + +static void usage(char **argv) +{ + fprintf(stderr, "usage: %s [-ed][-n name][-s][-S fields][-m var][-c var][-T][-t dir][-f file | sql-command-line]\n" + " -n name - name of synthetic event 'Anonymous' if left off\n" + " -t dir - use dir instead of /sys/kernel/tracing\n" + " -e - execute the commands to create the synthetic event\n" + " -m - trigger the action when var is a new max.\n" + " -c - trigger the action when var changes.\n" + " -s - used with -m or -c to do a snapshot of the tracing buffer\n" + " -S - used with -m or -c to save fields of the end event (comma deliminated)\n" + " -T - used with -m or -c to do both a snapshot and a trace\n" + " -f file - read sql lines from file otherwise from the command line\n" + " if file is '-' then read from standard input.\n", + argv[0]); + exit(-1); +} + +enum action { + ACTION_DEFAULT = 0, + ACTION_SNAPSHOT = (1 << 0), + ACTION_TRACE = (1 << 1), + ACTION_SAVE = (1 << 2), + ACTION_MAX = (1 << 3), + ACTION_CHANGE = (1 << 4), +}; + +#define ACTIONS ((ACTION_MAX - 1)) + +static int do_sql(const char *instance_name, + const char *buffer, const char *name, const char *var, + const char *trace_dir, bool execute, int action, + char **save_fields) +{ + struct tracefs_synth *synth; + struct tep_handle *tep; + struct trace_seq seq; + enum tracefs_synth_handler handler; + char *err; + int ret; + + if ((action & ACTIONS) && !var) { + fprintf(stderr, "Error: -s, -S and -T not supported without -m or -c"); + exit(-1); + } + + if (!name) + name = "Anonymous"; + + trace_seq_init(&seq); + tep = tracefs_local_events(trace_dir); + if (!tep) { + if (!trace_dir) + trace_dir = "tracefs directory"; + perror(trace_dir); + exit(-1); + } + + synth = tracefs_sql(tep, name, buffer, &err); + if (!synth) { + perror("Failed creating synthetic event!"); + if (err) + fprintf(stderr, "%s", err); + free(err); + exit(-1); + } + + if (tracefs_synth_complete(synth)) { + if (var) { + if (action & ACTION_MAX) + handler = TRACEFS_SYNTH_HANDLE_MAX; + else + handler = TRACEFS_SYNTH_HANDLE_CHANGE; + + if (action & ACTION_SAVE) { + ret = tracefs_synth_save(synth, handler, var, save_fields); + if (ret < 0) { + err = "adding save"; + goto failed_action; + } + } + if (action & ACTION_TRACE) { + /* + * By doing the trace before snapshot, it will be included + * in the snapshot. + */ + ret = tracefs_synth_trace(synth, handler, var); + if (ret < 0) { + err = "adding trace"; + goto failed_action; + } + } + if (action & ACTION_SNAPSHOT) { + ret = tracefs_synth_snapshot(synth, handler, var); + if (ret < 0) { + err = "adding snapshot"; + failed_action: + perror(err); + if (errno == ENODEV) + fprintf(stderr, "ERROR: '%s' is not a variable\n", + var); + exit(-1); + } + } + } + tracefs_synth_echo_cmd(&seq, synth); + if (execute) { + ret = tracefs_synth_create(synth); + if (ret < 0) { + fprintf(stderr, "%s\n", tracefs_error_last(NULL)); + exit(-1); + } + } + } else { + struct tracefs_instance *instance = NULL; + struct tracefs_hist *hist; + + hist = tracefs_synth_get_start_hist(synth); + if (!hist) { + perror("get_start_hist"); + exit(-1); + } + if (instance_name) { + if (execute) + instance = tracefs_instance_create(instance_name); + else + instance = tracefs_instance_alloc(trace_dir, + instance_name); + if (!instance) { + perror("Failed to create instance"); + exit(-1); + } + } + tracefs_hist_echo_cmd(&seq, instance, hist, 0); + if (execute) { + ret = tracefs_hist_start(instance, hist); + if (ret < 0) { + fprintf(stderr, "%s\n", tracefs_error_last(instance)); + exit(-1); + } + } + } + + tracefs_synth_free(synth); + + trace_seq_do_printf(&seq); + trace_seq_destroy(&seq); + return 0; +} + +int main (int argc, char **argv) +{ + char *trace_dir = NULL; + char *buffer = NULL; + char buf[BUFSIZ]; + int buffer_size = 0; + const char *file = NULL; + const char *instance = NULL; + bool execute = false; + char **save_fields = NULL; + const char *name; + const char *var; + int action = 0; + char *tok; + FILE *fp; + size_t r; + int c; + int i; + + for (;;) { + c = getopt(argc, argv, "ht:f:en:m:c:sS:TB:"); + if (c == -1) + break; + + switch(c) { + case 'h': + usage(argv); + case 't': + trace_dir = optarg; + break; + case 'f': + file = optarg; + break; + case 'e': + execute = true; + break; + case 'm': + action |= ACTION_MAX; + var = optarg; + break; + case 'c': + action |= ACTION_CHANGE; + var = optarg; + break; + case 's': + action |= ACTION_SNAPSHOT; + break; + case 'S': + action |= ACTION_SAVE; + tok = strtok(optarg, ","); + while (tok) { + save_fields = tracefs_list_add(save_fields, tok); + tok = strtok(NULL, ","); + } + if (!save_fields) { + perror(optarg); + exit(-1); + } + break; + case 'T': + action |= ACTION_TRACE | ACTION_SNAPSHOT; + break; + case 'B': + instance = optarg; + break; + case 'n': + name = optarg; + break; + } + } + + if ((action & (ACTION_MAX|ACTION_CHANGE)) == (ACTION_MAX|ACTION_CHANGE)) { + fprintf(stderr, "Can not use both -m and -c together\n"); + exit(-1); + } + if (file) { + if (!strcmp(file, "-")) + fp = stdin; + else + fp = fopen(file, "r"); + if (!fp) { + perror(file); + exit(-1); + } + while ((r = fread(buf, 1, BUFSIZ, fp)) > 0) { + buffer = realloc(buffer, buffer_size + r + 1); + strncpy(buffer + buffer_size, buf, r); + buffer_size += r; + } + fclose(fp); + if (buffer_size) + buffer[buffer_size] = '\0'; + } else if (argc == optind) { + usage(argv); + } else { + for (i = optind; i < argc; i++) { + r = strlen(argv[i]); + buffer = realloc(buffer, buffer_size + r + 2); + if (i != optind) + buffer[buffer_size++] = ' '; + strcpy(buffer + buffer_size, argv[i]); + buffer_size += r; + } + } + + do_sql(instance, buffer, name, var, trace_dir, execute, action, save_fields); + free(buffer); + + 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 +-------- +*sqlhist*(1), +*libtracefs*(3), +*libtraceevent*(3), +*trace-cmd*(1), +*tracefs_synth_init*(3), +*tracefs_synth_add_match_field*(3), +*tracefs_synth_add_compare_field*(3), +*tracefs_synth_add_start_field*(3), +*tracefs_synth_add_end_field*(3), +*tracefs_synth_append_start_filter*(3), +*tracefs_synth_append_end_filter*(3), +*tracefs_synth_create*(3), +*tracefs_synth_destroy*(3), +*tracefs_synth_free*(3), +*tracefs_synth_echo_cmd*(3), +*tracefs_hist_alloc*(3), +*tracefs_hist_alloc_2d*(3), +*tracefs_hist_alloc_nd*(3), +*tracefs_hist_free*(3), +*tracefs_hist_add_key*(3), +*tracefs_hist_add_value*(3), +*tracefs_hist_add_name*(3), +*tracefs_hist_start*(3), +*tracefs_hist_destory*(3), +*tracefs_hist_add_sort_key*(3), +*tracefs_hist_sort_key_direction*(3) + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-sqlhist.txt.1 b/Documentation/libtracefs-sqlhist.txt.1 new file mode 100644 index 0000000..875b250 --- /dev/null +++ b/Documentation/libtracefs-sqlhist.txt.1 @@ -0,0 +1,356 @@ +SQLHIST(1) +========== + +NAME +---- +sqlhist - Tool that uses SQL language to create / show creation of tracefs histograms and synthetic events. + +SYNOPSIS +-------- +*sqlhist* ['OPTIONS'] ['SQL-select-command'] + +DESCRIPTION +----------- +The sqlhist(1) will take an SQL like statement to create tracefs histograms and +synthetic events that can perform various actions for various handling of the +data. + +The tracefs file system interfaces with the Linux tracing infrastructure that +has various dynamic and static events through out the kernel. Each of these +events can have a "histogram" attached to it, where the fields of the event +will define the buckets of the histogram. + +A synthetic event is a way to attach two separate events and use the fields +and time stamps of those events to create a new dynamic event. This new +dynamic event is call a synthetic event. The fields of each event can have +simple calculations done on them where, for example, the delta between +a field of one event to a field of the other event can be taken. This also +works for the time stamps of the events where the time delta between the +two events can also be extracted and placed into the synthetic event. + +Other actions can be done from the fields of the events. A snapshot can +be taken of the kernel ring buffer a variable used in the synthetic +event creating hits a max, or simply changes. + +The commands to create histograms and synthetic events are complex and +not easy to remember. *sqlhist* is used to convert SQL syntax into the +commands needed to create the histogram or synthetic event. + +The *SQL-select-command* is a SQL string defined by *tracefs_sql*(3). + +Note, this must be run as root (or sudo) as interacting with the tracefs +directory requires root privilege, unless the *-t* option is given with +a copy of the _tracefs_ directory and its events. + +The *sqlhist* is a simple program where its code actual exists in the +*tracefs_sql*(3) man page. + +OPTIONS +------- +*-n* 'name':: + The name of the synthetic event to create. This event can then be + used like any other event, and enabled via *trace-cmd*(1). + +*-t* 'tracefs-dir':: + In order to test this out as non root user, a copy of the tracefs directory + can be used, and passing that directory with this option will allow + the program to work. Obviously, *-e* will not work as non-root because + it will not be able to execute. + + # mkdir /tmp/tracing + # cp -r /sys/kernel/tracing/events /tmp/tracing + # exit + $ ./sqlhist -t /tmp/tracing ... + +*-e*:: + Not only display the commands to create the histogram, but also execute them. + This requires root privilege. + +*-f* 'file':: + Instead of reading the SQL commands from the command line, read them from + _file_. If _file_ is '-' then read from standard input. + +*-m* 'var':: + Do the given action when the variable _var_ hits a new maximum. This can + not be used with *-c*. + +*-c* 'var':: + Do the given action when the variable _var_ changes its value. This can + not be used with *-m*. + +*-s*:: + Perform a snapshot instead of calling the synthetic event. + +*-T*:: + Perform both a snapshot and trace the synthetic event. + +*-S* 'fields[,fields]':: + Save the given fields. The fields must be fields of the "end" event given + in the *SQL-select-command* + +*-B* 'instance':: + For simple statements that only produce a histogram, the instance given here + will be where the histogram will be created. This is ignored for full synthetic + event creation, as sythetic events have a global affect on all tracing instances, + where as, histograms only affect a single instance. + +EXAMPLES +-------- + +Create the sqlhist executable: + +[source, c] +-- + man tracefs_sql | sed -ne '/^EXAMPLE/,/FILES/ { /EXAMPLE/d ; /FILES/d ; p}' > sqlhist.c + gcc -o sqlhist sqlhist.c `pkg-config --cflags --libs libtracefs` +-- + +As described above, for testing purposes, make a copy of the event directory: +[source, c] +-- + $ mkdir /tmp/tracing + $ sudo cp -r /sys/kernel/tracing/events /tmp/tracing/ + $ sudo chmod -R 0644 /tmp/tracing/ +-- + +For an example of simple histogram output using the copy of the tracefs directory. +[source, c] +-- + $ ./sqlhist -t /tmp/tracing/ 'SELECT CAST(call_site as SYM-OFFSET), bytes_req, CAST(bytes_alloc AS _COUNTER_) FROM kmalloc' +-- + +Produces the output: +[source, c] +-- + echo 'hist:keys=call_site.sym-offset,bytes_req:vals=bytes_alloc' > /sys/kernel/tracing/events/kmem/kmalloc/trigger +-- + +Which could be used by root: +[source, c] +-- + # echo 'hist:keys=call_site.sym-offset,bytes_req:vals=bytes_alloc' > /sys/kernel/tracing/events/kmem/kmalloc/trigger + # cat /sys/kernel/tracing/events/kmem/kmalloc/hist +# event histogram +# +# trigger info: hist:keys=call_site.sym-offset,bytes_req:vals=hitcount,bytes_alloc:sort=hitcount:size=2048 [active] +# + +{ call_site: [ffffffff813f8d8a] load_elf_phdrs+0x4a/0xb0 , bytes_req: 728 } hitcount: 1 bytes_alloc: 1024 +{ call_site: [ffffffffc0c69e74] nf_ct_ext_add+0xd4/0x1d0 [nf_conntrack] , bytes_req: 128 } hitcount: 1 bytes_alloc: 128 +{ call_site: [ffffffff818355e6] dma_resv_get_fences+0xf6/0x440 , bytes_req: 8 } hitcount: 1 bytes_alloc: 8 +{ call_site: [ffffffffc06dc73f] intel_gt_get_buffer_pool+0x15f/0x290 [i915] , bytes_req: 424 } hitcount: 1 bytes_alloc: 512 +{ call_site: [ffffffff813f8d8a] load_elf_phdrs+0x4a/0xb0 , bytes_req: 616 } hitcount: 1 bytes_alloc: 1024 +{ call_site: [ffffffff8161a44c] __sg_alloc_table+0x11c/0x180 , bytes_req: 32 } hitcount: 1 bytes_alloc: 32 +{ call_site: [ffffffffc070749d] shmem_get_pages+0xad/0x5d0 [i915] , bytes_req: 16 } hitcount: 1 bytes_alloc: 16 +{ call_site: [ffffffffc07507f5] intel_framebuffer_create+0x25/0x60 [i915] , bytes_req: 408 } hitcount: 1 bytes_alloc: 512 +{ call_site: [ffffffffc06fc20f] eb_parse+0x34f/0x910 [i915] , bytes_req: 408 } hitcount: 1 bytes_alloc: 512 +{ call_site: [ffffffffc0700ebd] i915_gem_object_get_pages_internal+0x5d/0x270 [i915] , bytes_req: 16 } hitcount: 1 bytes_alloc: 16 +{ call_site: [ffffffffc0771188] intel_frontbuffer_get+0x38/0x220 [i915] , bytes_req: 400 } hitcount: 1 bytes_alloc: 512 +{ call_site: [ffffffff8161a44c] __sg_alloc_table+0x11c/0x180 , bytes_req: 128 } hitcount: 1 bytes_alloc: 128 +{ call_site: [ffffffff813f8f45] load_elf_binary+0x155/0x1680 , bytes_req: 28 } hitcount: 1 bytes_alloc: 32 +{ call_site: [ffffffffc07038c8] __assign_mmap_offset+0x208/0x3d0 [i915] , bytes_req: 288 } hitcount: 1 bytes_alloc: 512 +{ call_site: [ffffffff813737b2] alloc_bprm+0x32/0x2f0 , bytes_req: 416 } hitcount: 1 bytes_alloc: 512 +{ call_site: [ffffffff813f9027] load_elf_binary+0x237/0x1680 , bytes_req: 64 } hitcount: 1 bytes_alloc: 64 +{ call_site: [ffffffff8161a44c] __sg_alloc_table+0x11c/0x180 , bytes_req: 64 } hitcount: 1 bytes_alloc: 64 +{ call_site: [ffffffffc040ffe7] drm_vma_node_allow+0x27/0xe0 [drm] , bytes_req: 40 } hitcount: 2 bytes_alloc: 128 +{ call_site: [ffffffff813cda98] __do_sys_timerfd_create+0x58/0x1c0 , bytes_req: 336 } hitcount: 2 bytes_alloc: 1024 +{ call_site: [ffffffff818355e6] dma_resv_get_fences+0xf6/0x440 , bytes_req: 40 } hitcount: 2 bytes_alloc: 128 +{ call_site: [ffffffff8139b75a] single_open+0x2a/0xa0 , bytes_req: 32 } hitcount: 2 bytes_alloc: 64 +{ call_site: [ffffffff815df715] bio_kmalloc+0x25/0x80 , bytes_req: 136 } hitcount: 2 bytes_alloc: 384 +{ call_site: [ffffffffc071e5cd] i915_vma_work+0x1d/0x50 [i915] , bytes_req: 416 } hitcount: 3 bytes_alloc: 1536 +{ call_site: [ffffffff81390d0d] alloc_fdtable+0x4d/0x100 , bytes_req: 56 } hitcount: 3 bytes_alloc: 192 +{ call_site: [ffffffffc06ff65f] i915_gem_do_execbuffer+0x158f/0x2440 [i915] , bytes_req: 16 } hitcount: 4 bytes_alloc: 64 +{ call_site: [ffffffff8137713c] alloc_pipe_info+0x5c/0x230 , bytes_req: 384 } hitcount: 5 bytes_alloc: 2560 +{ call_site: [ffffffff813771b4] alloc_pipe_info+0xd4/0x230 , bytes_req: 640 } hitcount: 5 bytes_alloc: 5120 +{ call_site: [ffffffff81834cdb] dma_resv_list_alloc+0x1b/0x40 , bytes_req: 40 } hitcount: 6 bytes_alloc: 384 +{ call_site: [ffffffff81834cdb] dma_resv_list_alloc+0x1b/0x40 , bytes_req: 56 } hitcount: 9 bytes_alloc: 576 +{ call_site: [ffffffff8120086e] tracing_map_sort_entries+0x9e/0x3e0 , bytes_req: 24 } hitcount: 60 bytes_alloc: 1920 + +Totals: + Hits: 122 + Entries: 30 + Dropped: 0 +-- + +Note, although the examples use uppercase for the SQL keywords, they do not have +to be. 'SELECT' could also be 'select' or even 'sElEcT'. + +By using the full SQL language, synthetic events can be made and processed. +For example, using *sqlhist* along with *trace-cmd*(1), wake up latency can +be recorded by creating a synthetic event by attaching the _sched_waking_ +and the _sched_switch_ events. + +[source, c] +-- + # sqlhist -n wakeup_lat -e -T -m lat 'SELECT end.next_comm AS comm, (end.TIMESTAMP_USECS - start.TIMESTAMP_USECS) AS lat FROM ' \ + 'sched_waking AS start JOIN sched_switch AS end ON start.pid = end.next_pid WHERE end.next_prio < 100 && end.next_comm == "cyclictest"' + # trace-cmd start -e all -e wakeup_lat -R stacktrace + # cyclictest -l 1000 -p80 -i250 -a -t -q -m -d 0 -b 1000 --tracemark + # trace-cmd show -s | tail -30 + <idle>-0 [002] dNh4 23454.902246: sched_wakeup: comm=cyclictest pid=12272 prio=120 target_cpu=002 + <idle>-0 [005] ...1 23454.902246: cpu_idle: state=4294967295 cpu_id=5 + <idle>-0 [007] d..1 23454.902246: cpu_idle: state=0 cpu_id=7 + <idle>-0 [002] dNh1 23454.902247: hrtimer_expire_exit: hrtimer=0000000037956dc2 + <idle>-0 [005] d..1 23454.902248: cpu_idle: state=0 cpu_id=5 + <idle>-0 [002] dNh1 23454.902248: write_msr: 6e0, value 4866ce957272 + <idle>-0 [006] ...1 23454.902248: cpu_idle: state=4294967295 cpu_id=6 + <idle>-0 [002] dNh1 23454.902249: local_timer_exit: vector=236 + <idle>-0 [006] d..1 23454.902250: cpu_idle: state=0 cpu_id=6 + <idle>-0 [002] .N.1 23454.902250: cpu_idle: state=4294967295 cpu_id=2 + <idle>-0 [002] dN.1 23454.902251: rcu_utilization: Start context switch + <idle>-0 [002] dN.1 23454.902252: rcu_utilization: End context switch + <idle>-0 [001] ...1 23454.902252: cpu_idle: state=4294967295 cpu_id=1 + <idle>-0 [002] dN.3 23454.902253: prandom_u32: ret=3692516021 + <idle>-0 [001] d..1 23454.902254: cpu_idle: state=0 cpu_id=1 + <idle>-0 [002] d..2 23454.902254: sched_switch: prev_comm=swapper/2 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=cyclictest next_pid=12275 next_prio=19 + <idle>-0 [002] d..4 23454.902256: wakeup_lat: next_comm=cyclictest lat=17 + <idle>-0 [002] d..5 23454.902258: <stack trace> + => trace_event_raw_event_synth + => action_trace + => event_hist_trigger + => event_triggers_call + => trace_event_buffer_commit + => trace_event_raw_event_sched_switch + => __traceiter_sched_switch + => __schedule + => schedule_idle + => do_idle + => cpu_startup_entry + => secondary_startup_64_no_verify +-- + +Here's the options for *sqlhist* explained: + + *-n wakeup_lat* :: + Name the synthetic event to use *wakeup_lat*. + + *-e*:: + Execute the commands that are printed. + + *-T*:: + Perform both a trace action and then a snapshot action (swap the buffer into the static 'snapshot' buffer). + + *-m lat*:: + Trigger the actions whenever 'lat' hits a new maximum value. + +Now a breakdown of the SQL statement: +[source, c] +-- + 'SELECT end.next_comm AS comm, (end.TIMESTAMP_USECS - start.TIMESTAMP_USECS) AS lat FROM ' \ + 'sched_waking AS start JOIN sched_switch AS end ON start.pid = end.next_pid WHERE end.next_prio < 100 && end.next_comm == "cyclictest"' +-- + *end.next_comm AS comm*:: + Save the 'sched_switch' field *next_comm* and place it into the *comm* field of the 'wakeup_lat' synthetic event. + + *(end.TIMESTAMP_USECS - start.TIMESTAMP_USECS) AS lat*:: + Take the delta of the time stamps from the 'sched_switch' event and the 'sched_waking' event. + As time stamps are usually recorded in nanoseconds, *TIMESTAMP* would give the full nanosecond time stamp, + but here, the *TIMESTAMP_USECS* will truncate it into microseconds. The value is saved in the + variable *lat*, which will also be recorded in the synthetic event. + + *FROM 'sched_waking' AS start JOIN sched_switch AS end ON start.pid = end.next_pid*:: + Create the synthetic event by joining _sched_waking_ to _sched_switch_, matching + the _sched_waking_ 'pid' field with the _sched_switch_ 'next_pid' field. + Also make *start* an alias for _sched_waking_ and *end* an alias for _sched_switch_ + which then an use *start* and *end* as a subsitute for _sched_waking_ and _sched_switch_ + respectively through out the rest of the SQL statement. + + *WHERE end.next_prio < 100 && end.next_comm == "cyclictest"*:: + Filter the logic where it executes only if _sched_waking_ 'next_prio' field + is less than 100. (Note, in the Kernel, priorities are inverse, and the real-time + priorities are represented from 0-100 where 0 is the highest priority). + Also only trace when the 'next_comm' (the task scheduling in) of the _sched_switch_ + event has the name "cyclictest". + +For the *trace-cmd*(3) command: +[source, c] +-- + trace-cmd start -e all -e wakeup_lat -R stacktrace +-- + + *trace-cmd start*:: + Enables tracing (does not record to a file). + + *-e all*:: + Enable all events + + *-e wakeup_lat -R stacktrace*:: + have the "wakeup_lat" event (our synthetic event) enable the *stacktrace* trigger, were + for every instance of the "wakeup_lat" event, a kernel stack trace will be recorded + in the ring buffer. + +After calling *cyclictest* (a real-time tool to measure wakeup latency), read the snapshot +buffer. + + *trace-cmd show -s*:: + *trace-cmd show* reads the kernel ring buffer, and the *-s* option will read the *snapshot* + buffer instead of the normal one. + +[source, c] +-- + <idle>-0 [002] d..4 23454.902256: wakeup_lat: next_comm=cyclictest lat=17 +-- + We see on the "wakeup_lat" event happened on CPU 2, with a wake up latency 17 microseconds. + +This can be extracted into a *trace.dat* file that *trace-cmd*(3) can read and do further +analysis, as well as *kernelshark*. + +[source, c] +-- + # trace-cmd extract -s + # trace-cmd report --cpu 2 | tail -30 + <idle>-0 [002] 23454.902238: prandom_u32: ret=1633425088 + <idle>-0 [002] 23454.902239: sched_wakeup: cyclictest:12275 [19] CPU:002 + <idle>-0 [002] 23454.902241: hrtimer_expire_exit: hrtimer=0xffffbbd68286fe60 + <idle>-0 [002] 23454.902241: hrtimer_cancel: hrtimer=0xffffbbd6826efe70 + <idle>-0 [002] 23454.902242: hrtimer_expire_entry: hrtimer=0xffffbbd6826efe70 now=23455294430750 function=hrtimer_wakeup/0x0 + <idle>-0 [002] 23454.902243: sched_waking: comm=cyclictest pid=12272 prio=120 target_cpu=002 + <idle>-0 [002] 23454.902244: prandom_u32: ret=1102749734 + <idle>-0 [002] 23454.902246: sched_wakeup: cyclictest:12272 [120] CPU:002 + <idle>-0 [002] 23454.902247: hrtimer_expire_exit: hrtimer=0xffffbbd6826efe70 + <idle>-0 [002] 23454.902248: write_msr: 6e0, value 4866ce957272 + <idle>-0 [002] 23454.902249: local_timer_exit: vector=236 + <idle>-0 [002] 23454.902250: cpu_idle: state=4294967295 cpu_id=2 + <idle>-0 [002] 23454.902251: rcu_utilization: Start context switch + <idle>-0 [002] 23454.902252: rcu_utilization: End context switch + <idle>-0 [002] 23454.902253: prandom_u32: ret=3692516021 + <idle>-0 [002] 23454.902254: sched_switch: swapper/2:0 [120] R ==> cyclictest:12275 [19] + <idle>-0 [002] 23454.902256: wakeup_lat: next_comm=cyclictest lat=17 + <idle>-0 [002] 23454.902258: kernel_stack: <stack trace > +=> trace_event_raw_event_synth (ffffffff8121a0db) +=> action_trace (ffffffff8121e9fb) +=> event_hist_trigger (ffffffff8121ca8d) +=> event_triggers_call (ffffffff81216c72) +=> trace_event_buffer_commit (ffffffff811f7618) +=> trace_event_raw_event_sched_switch (ffffffff8110fda4) +=> __traceiter_sched_switch (ffffffff8110d449) +=> __schedule (ffffffff81c02002) +=> schedule_idle (ffffffff81c02c86) +=> do_idle (ffffffff8111e898) +=> cpu_startup_entry (ffffffff8111eba9) +=> secondary_startup_64_no_verify (ffffffff81000107) +-- + +BUGS +---- + +As *sqlhist* is just example code from a man page, it is guaranteed to contain +lots of bugs. For one thing, not all error paths are covered properly. + +SEE ALSO +-------- +trace-cmd(1), tracefs_sql(3) + +AUTHOR +------ +Written by Steven Rostedt, <rostedt@goodmis.org> + +RESOURCES +--------- +https://git.kernel.org/pub/scm/utils/trace-cmd/trace-cmd.git/ + +COPYING +------- +Copyright \(C) 2021 , Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). + diff --git a/Documentation/libtracefs-stream.txt b/Documentation/libtracefs-stream.txt new file mode 100644 index 0000000..8008be8 --- /dev/null +++ b/Documentation/libtracefs-stream.txt @@ -0,0 +1,126 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_trace_pipe_stream, tracefs_trace_pipe_print, tracefs_trace_pipe_stop - +redirect the stream of trace data to an output or stdout. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +ssize_t *tracefs_trace_pipe_stream*(int _fd_, struct tracefs_instance pass:[*]_instance_, int _flags_); +ssize_t *tracefs_trace_pipe_print*(struct tracefs_instance pass:[*]_instance_, int _flags_); +void *tracefs_trace_pipe_stop*(struct tracefs_instance pass:[*]_instance_); + + +-- + +DESCRIPTION +----------- +If NULL is passed as _instance_, the top trace instance is used. + +The reading of the trace_pipe file can be stopped by calling *tracefs_trace_pipe_stop()* +which could be placed in a signal handler in case the application wants to stop the +reading, for example, with the user pressing Ctrl-C. + +The *tracefs_trace_pipe_stream()* function redirects the stream of trace data to an output +file. The "splice" system call is used to moves the data without copying between kernel +address space and user address space. The _fd_ is the file descriptor of the output file +and _flags_ is a bit mask of flags to be passed to the open system call of the trace_pipe +file (see ). If flags contain O_NONBLOCK, then that is also passed to the splice calls +that may read the file to the output stream file descriptor. + +The *tracefs_trace_pipe_print()* function is similar to *tracefs_trace_pipe_stream()*, but +the stream of trace data is redirected to stdout. + + +RETURN VALUE +------------ +The *tracefs_trace_pipe_stream()*, and *tracefs_trace_pipe_print()* functions return the +number of bytes transfered if the operation is successful, or -1 in case of an error. + +EXAMPLE +------- +[source,c] +-- +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> + +#include <tracefs.h> + +void stop(int sig) +{ + tracefs_trace_pipe_stop(NULL); +} + +int main(int argc, char **argv) +{ + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + const char *filename; + int fd; + int ret; + + if (argc < 2) { + fprintf(stderr, "usage: %s output_file\n", argv[0]); + exit(-1); + } + filename = argv[1]; + fd = creat(filename, mode); + if (fd < 0) { + perror(filename); + exit(-1); + } + signal(SIGINT, stop); + ret = tracefs_trace_pipe_stream(fd, NULL, SPLICE_F_NONBLOCK); + close(fd); + + return ret; +} +-- +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), +Documentation/trace/ftrace.rst from the Linux kernel tree + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2021 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-synth-info.txt b/Documentation/libtracefs-synth-info.txt new file mode 100644 index 0000000..6ee0320 --- /dev/null +++ b/Documentation/libtracefs-synth-info.txt @@ -0,0 +1,298 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_synth_echo_cmd, tracefs_synth_get_start_hist, tracefs_synth_get_name, +tracefs_synth_raw_fmt, tracefs_synth_show_event, tracefs_synth_show_start_hist, tracefs_synth_show_end_hist, +tracefs_synth_get_event - Retrieve data of synthetic events. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_synth_echo_cmd*(struct trace_seq pass:[*]_seq_, struct tracefs_synth pass:[*]_synth_); +struct tracefs_hist pass:[*]*tracefs_synth_get_start_hist*(struct tracefs_synth pass:[*]_synth_); + +const char pass:[*]*tracefs_synth_get_name*(struct tracefs_synth pass:[*]_synth_); +int *tracefs_synth_raw_fmt*(struct trace_seq pass:[*]_seq_, struct tracefs_synth pass:[*]_synth_); +const char pass:[*]*tracefs_synth_show_event*(struct tracefs_synth pass:[*]_synth_); +const char pass:[*]*tracefs_synth_show_start_hist*(struct tracefs_synth pass:[*]_synth_); +const char pass:[*]*tracefs_synth_show_end_hist*(struct tracefs_synth pass:[*]_synth_); +struct tep_event pass:[*]*tracefs_synth_get_event*(struct tep_handle pass:[*]_tep_, struct tracefs_synth pass:[*]_synth_); + +-- + +DESCRIPTION +----------- +Synthetic events are dynamic events that are created by matching +two other events which triggers a synthetic event. One event is the starting +event which some field is recorded, and when the second event is executed, +if it has a field (or fields) that matches the starting event's field (or fields) +then it will trigger the synthetic event. The field values other than the matching +fields may be passed from the starting event to the end event to perform calculations +on, or to simply pass as a parameter to the synthetic event. + +One common use case is to set "sched_waking" as the starting event. This event is +triggered when a process is awoken. Then set "sched_switch" as the ending event. +This event is triggered when a new task is scheduled on the CPU. By setting +the "common_pid" of both events as the matching fields, the time between the +two events is considered the wake up latency of that process. Use *TRACEFS_TIMESTAMP* +as a field for both events to calculate the delta in nanoseconds, or use +*TRACEFS_TIMESTAMP_USECS* as the compare fields for both events to calculate the +delta in microseconds. This is used as the example below. + +See *tracefs_synth_alloc*(3) for allocation of synthetic events, and +*tracefs_synth_create*() for creating the synthetic event on the system. + +*tracefs_synth_echo_cmd*() acts like *tracefs_synth_create*(), but instead of creating +the synthetic event in the system, it will write the echo commands to manually create +it in the _seq_ given. + +*tracefs_synth_get_start_hist*() returns a struct tracefs_hist descriptor describing +the histogram used to create the synthetic event. + +[verse] +-- +enum tracefs_synth_handler { + *TRACEFS_SYNTH_HANDLE_MATCH*, + *TRACEFS_SYNTH_HANDLE_MAX*, + *TRACEFS_SYNTH_HANDLE_CHANGE*, +}; +-- + +*tracefs_synth_get_name*() returns the name of the synthetic event or NULL on error. +The returned string belongs to the synth event object and is freed with the event +by *tracefs_synth_free*(). + +*tracefs_synth_raw_fmt*() writes the raw format strings (dynamic event and histograms) of +the synthetic event in the _seq_ given. + +*tracefs_synth_show_event*() returns the format of the dynamic event used by the synthetic +event or NULL on error. The returned string belongs to the synth event object and is freed +with the event by *tracefs_synth_free*(). + +*tracefs_synth_show_start_hist*() returns the format of the start histogram used by the +synthetic event or NULL on error. The returned string belongs to the synth event object +and is freed with the event by *tracefs_synth_free*(). + +*tracefs_synth_show_end_hist*() returns the format of the end histogram used by the +synthetic event or NULL on error. The returned string belongs to the synth event object +and is freed with the event by *tracefs_synth_free*(). + +The *tracefs_synth_get_event*() function returns a tep event, describing the given synthetic +event. The API detects any newly created or removed dynamic events. The returned pointer to +tep event is controlled by @tep and must not be freed. + +RETURN VALUE +------------ +*tracefs_synth_get_name*(), *tracefs_synth_show_event*(), *tracefs_synth_show_start_hist*() +and *tracefs_synth_show_end_hist*() return a string owned by the synth event object. + +The *tracefs_synth_get_event*() function returns a pointer to a tep event or NULL in case of an +error or if the requested synthetic event is missing. The returned pointer to tep event is +controlled by @tep and must not be freed. + +All other functions return zero 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 field that is not compatible for calculations. + +*ENODEV* An event or one of its fields is not found. + +*EBADE* The fields of the start and end events are not compatible for + either matching or comparing. + +*ENOMEM* not enough memory is available. + +And more errors may have happened from the system calls to the system. + +EXAMPLE +------- +See *tracefs_sql*(3) for a more indepth use of some of this code. + +[source,c] +-- +#include <stdlib.h> +#include <tracefs.h> + +#define start_event "sched_waking" +#define start_field "pid" + +#define end_event "sched_switch" +#define end_field "next_pid" + +#define match_name "pid" + +static struct tracefs_synth *synth; + +static void make_event(void) +{ + struct tep_handle *tep; + + /* Load all events from the system */ + tep = tracefs_local_events(NULL); + + /* Initialize the synthetic event */ + synth = tracefs_synth_alloc(tep, "wakeup_lat", + NULL, start_event, + NULL, end_event, + start_field, end_field, + match_name); + + /* The tep is no longer needed */ + tep_free(tep); + + + /* Save the "prio" field as "prio" from the start event */ + tracefs_synth_add_start_field(synth, "prio", NULL); + + /* Save the "next_comm" as "comm" from the end event */ + tracefs_synth_add_end_field(synth, "next_comm", "comm"); + + /* Save the "prev_prio" as "prev_prio" from the end event */ + tracefs_synth_add_end_field(synth, "prev_prio", NULL); + + /* + * Take a microsecond time difference between end and start + * and record as "delta" + */ + tracefs_synth_add_compare_field(synth, TRACEFS_TIMESTAMP_USECS, + TRACEFS_TIMESTAMP_USECS, + TRACEFS_SYNTH_DELTA_END, "delta"); + + /* Only record if start event "prio" is less than 100 */ + tracefs_synth_append_start_filter(synth, TRACEFS_FILTER_COMPARE, + "prio", TRACEFS_COMPARE_LT, "100"); + + /* + * Only record if end event "next_prio" is less than 50 + * or the previous task's prio was not greater than or equal to 100. + * next_prio < 50 || !(prev_prio >= 100) + */ + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_COMPARE, + "next_prio", TRACEFS_COMPARE_LT, "50"); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_OR, NULL, 0, NULL); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_NOT, NULL, 0, NULL); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_OPEN_PAREN, NULL, 0, NULL); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_COMPARE, + "prev_prio", TRACEFS_COMPARE_GE, "100"); + /* + * Note, the above only added: "next_prio < 50 || !(prev_prio >= 100" + * That's because, when the synth is executed, the remaining close parenthesis + * will be added. That is, the string will end up being: + * "next_prio < 50 || !(prev_prio >= 100)" when one of tracefs_sync_create() + * or tracefs_sync_echo_cmd() is run. + */ +} + +/* Display how to create the synthetic event */ +static void show_event(void) +{ + struct trace_seq s; + + trace_seq_init(&s); + + tracefs_synth_echo_cmd(&s, synth); + trace_seq_terminate(&s); + trace_seq_do_printf(&s); + trace_seq_destroy(&s); +} + +int main (int argc, char **argv) +{ + make_event(); + + if (argc > 1) { + if (!strcmp(argv[1], "create")) { + /* Create the synthetic event */ + tracefs_synth_create(synth); + } else if (!strcmp(argv[1], "delete")) { + /* Delete the synthetic event */ + tracefs_synth_destroy(synth); + } else { + printf("usage: %s [create|delete]\n", argv[0]); + exit(-1); + } + } else + show_event(); + + tracefs_synth_free(synth); + + 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), +*tracefs_hist_alloc*(3), +*tracefs_hist_alloc_2d*(3), +*tracefs_hist_alloc_nd*(3), +*tracefs_hist_free*(3), +*tracefs_hist_add_key*(3), +*tracefs_hist_add_value*(3), +*tracefs_hist_add_name*(3), +*tracefs_hist_start*(3), +*tracefs_hist_destory*(3), +*tracefs_hist_add_sort_key*(3), +*tracefs_hist_sort_key_direction*(3), +*tracefs_synth_alloc*(3), +*tracefs_synth_add_match_field*(3), +*tracefs_synth_add_compare_field*(3), +*tracefs_synth_add_start_field*(3), +*tracefs_synth_add_end_field*(3), +*tracefs_synth_append_start_filter*(3), +*tracefs_synth_append_end_filter*(3), +*tracefs_synth_free*(3), +*tracefs_synth_create*(3), +*tracefs_synth_destroy*(3), +*tracefs_synth_complete*(3), +*tracefs_synth_trace*(3), +*tracefs_synth_snapshot*(3), +*tracefs_synth_save*(3), + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-synth.txt b/Documentation/libtracefs-synth.txt new file mode 100644 index 0000000..c57725d --- /dev/null +++ b/Documentation/libtracefs-synth.txt @@ -0,0 +1,368 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_synth_alloc, tracefs_synth_add_match_field, tracefs_synth_add_compare_field, tracefs_synth_add_start_field, +tracefs_synth_add_end_field, tracefs_synth_append_start_filter, tracefs_synth_append_end_filter, tracefs_synth_free, +- Creation of a synthetic event descriptor + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +struct tracefs_synth pass:[*]*tracefs_synth_alloc*(struct tep_handle pass:[*]_tep_, + const char pass:[*]_name_, + const char pass:[*]_start_system_, + const char pass:[*]_start_event_, + const char pass:[*]_end_system_, + const char pass:[*]_end_event_, + const char pass:[*]_start_match_field_, + const char pass:[*]_end_match_field_, + const char pass:[*]_match_name_); +int *tracefs_synth_add_match_field*(struct tracefs_synth pass:[*]_synth_, + const char pass:[*]_start_match_field_, + const char pass:[*]_end_match_field_, + const char pass:[*]_name_); +int *tracefs_synth_add_compare_field*(struct tracefs_synth pass:[*]_synth_, + const char pass:[*]_start_compare_field_, + const char pass:[*]_end_compare_field_, + enum tracefs_synth_calc _calc_, + const char pass:[*]_name_); +int *tracefs_synth_add_start_field*(struct tracefs_synth pass:[*]_synth_, + const char pass:[*]_start_field_, + const char pass:[*]_name_); +int *tracefs_synth_add_end_field*(struct tracefs_synth pass:[*]_synth_, + const char pass:[*]_end_field_, + const char pass:[*]_name_); +int *tracefs_synth_append_start_filter*(struct tracefs_synth pass:[*]_synth_, + struct tracefs_filter _type_, + const char pass:[*]_field_, + enum tracefs_synth_compare _compare_, + const char pass:[*]_val_); +int *tracefs_synth_append_end_filter*(struct tracefs_synth pass:[*]_synth_, + struct tracefs_filter _type_, + const char pass:[*]_field_, + enum tracefs_synth_compare _compare_, + const char pass:[*]_val_); +void *tracefs_synth_free*(struct tracefs_synth pass:[*]_synth_); +-- + +DESCRIPTION +----------- +Synthetic events are dynamic events that are created by matching +two other events which triggers a synthetic event. One event is the starting +event which some field is recorded, and when the second event is executed, +if it has a field (or fields) that matches the starting event's field (or fields) +then it will trigger the synthetic event. The field values other than the matching +fields may be passed from the starting event to the end event to perform calculations +on, or to simply pass as a parameter to the synthetic event. + +One common use case is to set "sched_waking" as the starting event. This event is +triggered when a process is awoken. Then set "sched_switch" as the ending event. +This event is triggered when a new task is scheduled on the CPU. By setting +the "common_pid" of both events as the matching fields, the time between the +two events is considered the wake up latency of that process. Use *TRACEFS_TIMESTAMP* +as a field for both events to calculate the delta in nanoseconds, or use +*TRACEFS_TIMESTAMP_USECS" as the compare fields for both events to calculate the +delta in microseconds. This is used as the example below. + +*tracefs_synth_alloc*() allocates and initializes a synthetic event. +It does not create the synthetic event, but supplies the minimal information +to do so. See *tracefs_synth_create*(3) for how to create the synthetic +event in the system. It requires a _tep_ handler that can be created by +*tracefs_local_events*(3) for more information. The _name_ holds the name +of the synthetic event that will be created. The _start_system_ is the name +of the system for the starting event. It may be NULL and the first event +with the name of _start_event_ will be chosen. The _end_system_ is the +name of the system for theh ending event. It may be NULL and the first event +with the name of _end_event_ will be chosen as the ending event. If _match_name_ +is given, then this will be the field of the created synthetic event that +holds the matching keys of the starting event's _start_match_field_ and +the ending event's _end_match_field_. If _match_name_ is NULL, then it will +not be recorded in the created synthetic event. + +*tracefs_synth_add_match_field*() will add a second key to match between the +starting event and the ending event. If _name_ is given, then the content +of the matching field will be saved by this _name_ in the synthetic event. +The _start_match_field_ is the field of the starting event to mach with the +ending event's _end_match_field_. + +*tracefs_synth_add_compare_field*() is used to compare the _start_compare_field_ +of the starting event with the _end_compare_field_ of the ending event. The _name_ +must be given so that the result will be saved by the synthetic event. It makes +no sense to not pass this to the synthetic event after doing the work of +the compared fields, as it serves no other purpose. The _calc_ parameter +can be one of: + +*TRACEFS_SYNTH_DELTA_END* - calculate the difference between the content in + the _end_compare_field_ from the content of the _start_compare_field_. + +_name_ = _end_compare_field_ - _start_compare_field_ + +*TRACEFS_SYNTH_DELTA_START* - calculate the difference between the content in + the _start_compare_field_ from the content of the _end_compare_field_. + +_name_ = _start_compare_field_ - _end_compare_field_ + +*TRACEFS_SYNTH_ADD* - Add the content of the _start_compare_field_ to the + content of the _end_compare_field_. + +_name_ = _start_compare_field_ + _end_compare_field_ + +*tracefs_synth_add_start_field*() - Records the _start_field_ of the starting +event as _name_ in the synthetic event. If _name_ is NULL, then the name used +will be the same as _start_field_. + +*tracefs_synth_add_end_field*() - Records the _end_field_ of the ending +event as _name_ in the synthetic event. If _name_ is NULL, then the name used +will be the same as _end_field_. + +*tracefs_synth_append_start_filter*() creates a filter or appends to it for the +starting event. Depending on _type_, it will build a string of tokens for +parenthesis or logic statements, or it may add a comparison of _field_ +to _val_ based on _compare_. + +If _type_ is: +*TRACEFS_FILTER_COMPARE* - See below +*TRACEFS_FILTER_AND* - Append "&&" to the filter +*TRACEFS_FILTER_OR* - Append "||" to the filter +*TRACEFS_FILTER_NOT* - Append "!" to the filter +*TRACEFS_FILTER_OPEN_PAREN* - Append "(" to the filter +*TRACEFS_FILTER_CLOSE_PAREN* - Append ")" to the filter + +_field_, _compare_, and _val_ are ignored unless _type_ is equal to +*TRACEFS_FILTER_COMPARE*, then _compare will be used for the following: + +*TRACEFS_COMPARE_EQ* - _field_ == _val_ + +*TRACEFS_COMPARE_NE* - _field_ != _val_ + +*TRACEFS_COMPARE_GT* - _field_ > _val_ + +*TRACEFS_COMPARE_GE* - _field_ >= _val_ + +*TRACEFS_COMPARE_LT* - _field_ < _val_ + +*TRACEFS_COMPARE_LE* - _field_ <pass:[=] _val_ + +*TRACEFS_COMPARE_RE* - _field_ ~ "_val_" : where _field_ is a string. + +*TRACEFS_COMPARE_AND* - _field_ & _val_ : where _field_ is a flags field. + +*tracefs_synth_append_end_filter*() is the same as *tracefs_synth_append_start_filter* but +filters on the ending event. + +*tracefs_synth_free*() frees the allocated descriptor returned by +*tracefs_synth_alloc*(). + +RETURN VALUE +------------ +*tracefs_synth_alloc*() returns an allocated struct tracefs_synth descriptor +on success or NULL on error. + +All other functions that return an integer returns zero 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 field that is not compatible for calculations. + +*ENODEV* An event or one of its fields is not found. + +*EBADE* The fields of the start and end events are not compatible for + either matching or comparing. + +*ENOMEM* not enough memory is available. + +And more errors may have happened from the system calls to the system. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <tracefs.h> + +#define start_event "sched_waking" +#define start_field "pid" + +#define end_event "sched_switch" +#define end_field "next_pid" + +#define match_name "pid" + +static struct tracefs_synth *synth; + +static void make_event(void) +{ + struct tep_handle *tep; + + /* Load all events from the system */ + tep = tracefs_local_events(NULL); + + /* Initialize the synthetic event */ + synth = tracefs_synth_alloc(tep, "wakeup_lat", + NULL, start_event, + NULL, end_event, + start_field, end_field, + match_name); + + /* The tep is no longer needed */ + tep_free(tep); + + + /* Save the "prio" field as "prio" from the start event */ + tracefs_synth_add_start_field(synth, "prio", NULL); + + /* Save the "next_comm" as "comm" from the end event */ + tracefs_synth_add_end_field(synth, "next_comm", "comm"); + + /* Save the "prev_prio" as "prev_prio" from the end event */ + tracefs_synth_add_end_field(synth, "prev_prio", NULL); + + /* + * Take a microsecond time difference between end and start + * and record as "delta" + */ + tracefs_synth_add_compare_field(synth, TRACEFS_TIMESTAMP_USECS, + TRACEFS_TIMESTAMP_USECS, + TRACEFS_SYNTH_DELTA_END, "delta"); + + /* Only record if start event "prio" is less than 100 */ + tracefs_synth_append_start_filter(synth, TRACEFS_FILTER_COMPARE, + "prio", TRACEFS_COMPARE_LT, "100"); + + /* + * Only record if end event "next_prio" is less than 50 + * or the previous task's prio was not greater than or equal to 100. + * next_prio < 50 || !(prev_prio >= 100) + */ + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_COMPARE, + "next_prio", TRACEFS_COMPARE_LT, "50"); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_OR, NULL, 0, NULL); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_NOT, NULL, 0, NULL); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_OPEN_PAREN, NULL, 0, NULL); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_COMPARE, + "prev_prio", TRACEFS_COMPARE_GE, "100"); + /* + * Note, the above only added: "next_prio < 50 || !(prev_prio >= 100" + * That's because, when the synth is executed, the remaining close parenthesis + * will be added. That is, the string will end up being: + * "next_prio < 50 || !(prev_prio >= 100)" when one of tracefs_sync_create() + * or tracefs_sync_echo_cmd() is run. + */ +} + +/* Display how to create the synthetic event */ +static void show_event(void) +{ + struct trace_seq s; + + trace_seq_init(&s); + + tracefs_synth_echo_cmd(&s, synth); + trace_seq_terminate(&s); + trace_seq_do_printf(&s); + trace_seq_destroy(&s); +} + +int main (int argc, char **argv) +{ + make_event(); + + if (argc > 1) { + if (!strcmp(argv[1], "create")) { + /* Create the synthetic event */ + tracefs_synth_create(synth); + } else if (!strcmp(argv[1], "delete")) { + /* Delete the synthetic event */ + tracefs_synth_destroy(synth); + } else { + printf("usage: %s [create|delete]\n", argv[0]); + exit(-1); + } + } else + show_event(); + + tracefs_synth_free(synth); + + 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 +-------- +*tracefs_synth_create*(3), +*tracefs_synth_destroy*(3), +*tracfes_synth_echo_cmd*(3), +*libtracefs*(3), +*libtraceevent*(3), +*trace-cmd*(1), +*tracefs_hist_alloc*(3), +*tracefs_hist_alloc_2d*(3), +*tracefs_hist_alloc_nd*(3), +*tracefs_hist_free*(3), +*tracefs_hist_add_key*(3), +*tracefs_hist_add_value*(3), +*tracefs_hist_add_name*(3), +*tracefs_hist_start*(3), +*tracefs_hist_destory*(3), +*tracefs_hist_add_sort_key*(3), +*tracefs_hist_sort_key_direction*(3), +*tracefs_synth_create*(3), +*tracefs_synth_destroy*(3), +*tracefs_synth_complete*(3), +*tracefs_synth_trace*(3), +*tracefs_synth_snapshot*(3), +*tracefs_synth_save*(3), +*tracefs_synth_echo_cmd*(3), +*tracefs_synth_get_start_hist*(3), +*tracefs_synth_get_name*(3), +*tracefs_synth_raw_fmt*(3), +*tracefs_synth_show_event*(3), +*tracefs_synth_show_start_hist*(3), +*tracefs_synth_show_end_hist*(3), +*tracefs_synth_get_event*(3), + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-synth2.txt b/Documentation/libtracefs-synth2.txt new file mode 100644 index 0000000..7e8e6cc --- /dev/null +++ b/Documentation/libtracefs-synth2.txt @@ -0,0 +1,281 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_synth_create, tracefs_synth_destroy, tracefs_synth_complete, +tracefs_synth_trace, tracefs_synth_snapshot, tracefs_synth_save +- Creation of synthetic events + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_synth_create*(struct tracefs_synth pass:[*]_synth_); +int *tracefs_synth_destroy*(struct tracefs_synth pass:[*]_synth_); +bool *tracefs_synth_complete*(struct tracefs_synth pass:[*]_synth_); + +int *tracefs_synth_trace*(struct tracefs_synth pass:[*]_synth_, + enum tracefs_synth_handler _type_, const char pass:[*]_var_); +int *tracefs_synth_snapshot*(struct tracefs_synth pass:[*]_synth_, + enum tracefs_synth_handler _type_, const char pass:[*]_var_); +int *tracefs_synth_save*(struct tracefs_synth pass:[*]_synth_, + enum tracefs_synth_handler _type_, const char pass:[*]_var_, + char pass:[**]_save_fields_); +-- + +DESCRIPTION +----------- +Synthetic events are dynamic events that are created by matching +two other events which triggers a synthetic event. One event is the starting +event which some field is recorded, and when the second event is executed, +if it has a field (or fields) that matches the starting event's field (or fields) +then it will trigger the synthetic event. The field values other than the matching +fields may be passed from the starting event to the end event to perform calculations +on, or to simply pass as a parameter to the synthetic event. + +One common use case is to set "sched_waking" as the starting event. This event is +triggered when a process is awoken. Then set "sched_switch" as the ending event. +This event is triggered when a new task is scheduled on the CPU. By setting +the "common_pid" of both events as the matching fields, the time between the +two events is considered the wake up latency of that process. Use *TRACEFS_TIMESTAMP* +as a field for both events to calculate the delta in nanoseconds, or use +*TRACEFS_TIMESTAMP_USECS* as the compare fields for both events to calculate the +delta in microseconds. This is used as the example below. + +*tracefs_synth_create*() creates the synthetic event in the system. The synthetic events apply +across all instances. A synthetic event must be created with *tracefs_synth_alloc*(3) before +it can be created. + +*tracefs_synth_destroy*() destroys the synthetic event. It will attempt to stop the running of it in +its instance (top by default), but if its running in another instance this may fail as busy. + +*tracefs_synth_complete*() returns true if the synthetic event _synth_ has both +a starting and ending event. + +*tracefs_synth_trace*() Instead of doing just a trace on matching of the start and +end events, do the _type_ handler where *TRACEFS_SYNTH_HANDLE_MAX* will do a trace +when the given variable _var_ hits a new max for the matching keys. Or +*TRACEFS_SYNTH_HANDLE_CHANGE* for when the _var_ changes. _var_ must be one of +the _name_ elements used in *tracefs_synth_add_end_field*(3). + +*tracefs_synth_snapshot*() When the given variable _var_ is either a new max if +_handler_ is *TRACEFS_SYNTH_HANDLE_MAX* or simply changed if *TRACEFS_SYNTH_HANDLE_CHANGE* +then take a "snapshot" of the buffer. The snapshot moves the normal "trace" buffer +into a "snapshot" buffer, that can be accessed via the "snapshot" file in the +top level tracefs directory, or one of the instances. _var_ changes. _var_ must be one of +the _name_ elements used in *tracefs_synth_add_end_field*(3). + +*tracefs_synth_save*() When the given variable _var_ is either a new max if +_handler_ is *TRACEFS_SYNTH_HANDLE_MAX* or simpy changed if *TRACEFS_SYNTH_HANDLE_CHANGE* +then save the given _save_fields_ list. The fields will be stored in the histogram +"hist" file of the event that can be retrieved with *tracefs_event_file_read*(3). +_var_ must be one of the _name_ elements used in *tracefs_synth_add_end_field*(3). + +RETURN VALUE +------------ +All functions return zero 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 field that is not compatible for calculations. + +*ENODEV* An event or one of its fields is not found. + +*EBADE* The fields of the start and end events are not compatible for + either matching or comparing. + +*ENOMEM* not enough memory is available. + +And more errors may have happened from the system calls to the system. + +EXAMPLE +------- +See *tracefs_sql*(3) for a more indepth use of some of this code. + +[source,c] +-- +#include <stdlib.h> +#include <tracefs.h> + +#define start_event "sched_waking" +#define start_field "pid" + +#define end_event "sched_switch" +#define end_field "next_pid" + +#define match_name "pid" + +static struct tracefs_synth *synth; + +static void make_event(void) +{ + struct tep_handle *tep; + + /* Load all events from the system */ + tep = tracefs_local_events(NULL); + + /* Initialize the synthetic event */ + synth = tracefs_synth_alloc(tep, "wakeup_lat", + NULL, start_event, + NULL, end_event, + start_field, end_field, + match_name); + + /* The tep is no longer needed */ + tep_free(tep); + + + /* Save the "prio" field as "prio" from the start event */ + tracefs_synth_add_start_field(synth, "prio", NULL); + + /* Save the "next_comm" as "comm" from the end event */ + tracefs_synth_add_end_field(synth, "next_comm", "comm"); + + /* Save the "prev_prio" as "prev_prio" from the end event */ + tracefs_synth_add_end_field(synth, "prev_prio", NULL); + + /* + * Take a microsecond time difference between end and start + * and record as "delta" + */ + tracefs_synth_add_compare_field(synth, TRACEFS_TIMESTAMP_USECS, + TRACEFS_TIMESTAMP_USECS, + TRACEFS_SYNTH_DELTA_END, "delta"); + + /* Only record if start event "prio" is less than 100 */ + tracefs_synth_append_start_filter(synth, TRACEFS_FILTER_COMPARE, + "prio", TRACEFS_COMPARE_LT, "100"); + + /* + * Only record if end event "next_prio" is less than 50 + * or the previous task's prio was not greater than or equal to 100. + * next_prio < 50 || !(prev_prio >= 100) + */ + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_COMPARE, + "next_prio", TRACEFS_COMPARE_LT, "50"); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_OR, NULL, 0, NULL); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_NOT, NULL, 0, NULL); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_OPEN_PAREN, NULL, 0, NULL); + tracefs_synth_append_end_filter(synth, TRACEFS_FILTER_COMPARE, + "prev_prio", TRACEFS_COMPARE_GE, "100"); + /* + * Note, the above only added: "next_prio < 50 || !(prev_prio >= 100" + * That's because, when the synth is executed, the remaining close parenthesis + * will be added. That is, the string will end up being: + * "next_prio < 50 || !(prev_prio >= 100)" when one of tracefs_sync_create() + * or tracefs_sync_echo_cmd() is run. + */ +} + +/* Display how to create the synthetic event */ +static void show_event(void) +{ + struct trace_seq s; + + trace_seq_init(&s); + + tracefs_synth_echo_cmd(&s, synth); + trace_seq_terminate(&s); + trace_seq_do_printf(&s); + trace_seq_destroy(&s); +} + +int main (int argc, char **argv) +{ + make_event(); + + if (argc > 1) { + if (!strcmp(argv[1], "create")) { + /* Create the synthetic event */ + tracefs_synth_create(synth); + } else if (!strcmp(argv[1], "delete")) { + /* Delete the synthetic event */ + tracefs_synth_destroy(synth); + } else { + printf("usage: %s [create|delete]\n", argv[0]); + exit(-1); + } + } else + show_event(); + + tracefs_synth_free(synth); + + 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), +*tracefs_hist_alloc*(3), +*tracefs_hist_alloc_2d*(3), +*tracefs_hist_alloc_nd*(3), +*tracefs_hist_free*(3), +*tracefs_hist_add_key*(3), +*tracefs_hist_add_value*(3), +*tracefs_hist_add_name*(3), +*tracefs_hist_start*(3), +*tracefs_hist_destory*(3), +*tracefs_hist_add_sort_key*(3), +*tracefs_hist_sort_key_direction*(3), +*tracefs_synth_alloc*(3), +*tracefs_synth_add_match_field*(3), +*tracefs_synth_add_compare_field*(3), +*tracefs_synth_add_start_field*(3), +*tracefs_synth_add_end_field*(3), +*tracefs_synth_append_start_filter*(3), +*tracefs_synth_append_end_filter*(3), +*tracefs_synth_free*(3), +*tracefs_synth_echo_cmd*(3), +*tracefs_synth_get_start_hist*(3), +*tracefs_synth_get_name*(3), +*tracefs_synth_raw_fmt*(3), +*tracefs_synth_show_event*(3), +*tracefs_synth_show_start_hist*(3), +*tracefs_synth_show_end_hist*(3), +*tracefs_synth_get_event*(3), + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-traceon.txt b/Documentation/libtracefs-traceon.txt new file mode 100644 index 0000000..28c79ed --- /dev/null +++ b/Documentation/libtracefs-traceon.txt @@ -0,0 +1,151 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_trace_is_on, tracefs_trace_on, tracefs_trace_off, tracefs_trace_on_get_fd, +tracefs_trace_on_fd, tracefs_trace_off_fd - Functions to enable or disable tracing. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_trace_is_on*(struct tracefs_instance pass:[*]_instance_); +int *tracefs_trace_on*(struct tracefs_instance pass:[*]_instance_); +int *tracefs_trace_off*(struct tracefs_instance pass:[*]_instance_); +int *tracefs_trace_on_get_fd*(struct tracefs_instance pass:[*]_instance_); +int *tracefs_trace_on_fd*(int _fd_); +int *tracefs_trace_off_fd*(int _fd_); +-- + +DESCRIPTION +----------- +This set of functions can be used to check, enable or disable writing to the ring buffer in +the given trace instance. The tracing is enabled when writing to the ring buffer is enabled. + +The *tracefs_trace_is_on()* function checks if tracing is enabled for the given _instance_. If +_instance_ is NULL, the top instance is used. + +The *tracefs_trace_on()* and *tracefs_trace_off()* functions set the tracing in the _instance_ +to enable or disable state. If _instance_ is NULL, the top instance is used. + +The *tracefs_trace_on_get_fd()* function returns a file descriptor to the "tracing_on" file from +the given _instance_. If _instance_ is NULL, the top trace instance is used. The returned descriptor +can be used for fast enabling or disabling the tracing of the instance. + +The *tracefs_trace_on_fd()* and *tracefs_trace_off_fd()* functions set the tracing state to enable +or disable using the given _fd_. This file descriptor must be opened for writing with +*tracefs_trace_on_get_fd*(3) of the desired trace instance. These functions are faster than +*tracefs_trace_on* and *tracefs_trace_off*. + +RETURN VALUE +------------ +The *tracefs_trace_is_on()* function returns 0 if tracing is disable, 1 if it is enabled or +-1 in case of an error. + +The *tracefs_trace_on_get_fd()* function returns a file descriptor to "tracing_on" +file for reading and writing, which must be closed wuth close(). In case of an error -1 is returned. + +The *tracefs_trace_on()*, *tracefs_trace_off()*, *tracefs_trace_on_fd()* and +*tracefs_trace_off_fd()* functions return -1 in case of an error or 0 otherwise. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + + int ret; + + ret = tracefs_trace_is_on(NULL); + if (ret == 0) { + /* Tracing is disabled in the top instance */ + } else if (ret == 1) { + /* Tracing is enabled in the top instance */ + } else { + /* Error getting tracing state of the top instance */ + } + + ... + + if (!tracefs_trace_on(NULL)) { + /* Enabled tracing in the top instance */ + + ... + + if (!tracefs_trace_off(NULL)) { + /* Disabled tracing in the top instance */ + } else { + /* Error disabling tracing in the top instance */ + } + } else { + /* Error enabling tracing in the top instance */ + } + + ... + + int fd = tracefs_trace_on_get_fd(NULL); + + if (fd < 0) { + /* Error opening tracing_on file */ + } + ... + if (!tracefs_trace_on_fd(fd)) { + /* Enabled tracing in the top instance */ + + ... + + if (!tracefs_trace_off_fd(fd)) { + /* Disabled tracing in the top instance */ + } else { + /* Error disabling tracing in the top instance */ + } + } else { + /* Error enabling tracing in the top instance */ + } + + ... + + close(fd); +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2021 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-tracer.txt b/Documentation/libtracefs-tracer.txt new file mode 100644 index 0000000..ea57962 --- /dev/null +++ b/Documentation/libtracefs-tracer.txt @@ -0,0 +1,221 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_tracer_set, tracefs_tracer_clear - Enable or disable a tracer in an instance or the top level + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +int *tracefs_tracer_set*(struct tracefs_instance pass:[*]_instance_, enum tracefs_tracers _tracer_); +int *tracefs_tracer_set*(struct tracefs_instance pass:[*]_instance_, enum tracefs_tracers _tracer_, const char pass:[*]_name_); +int *tracefs_tracer_clear*(struct tracefs_instance pass:[*]_instance_); +-- + +DESCRIPTION +----------- +*tracefs_tracer_set* enables a tracer in the given instance, defined by the +_instance_ parameter. If _instance_ is NULL, then the top level instance is +changed. If _tracer_ is set to *TRACFES_TRACER_CUSTOM* then a _name_ +string must be passed in as the third parameter, and that is written into the +instance to enable the tracer with that name. This is useful for newer or +custom kernels that contain tracers that are not yet identified by the +tracefs_tracers enum. + +*tracefs_tracer_clear* disables the tracer for the given instance defined by +the _instance_ variable, or the top level instance if it is NULL. +This is the same as calling *tracefs_tracer_set* with TRACEFS_TRACER_NOP as +the _tracer_ parameter. + +TRACEFS_TRACER ENUMS +-------------------- + +The currently defined enums that are accepted are: + +*TRACEFS_TRACER_NOP* : +This is the idle tracer, which does nothing and is used to clear any +active tracer. + +*TRACEFS_TRACER_FUNCTION* : +Enables most functions in the kernel to be traced. + +*TRACEFS_TRACER_FUNCTION_GRAPH* : +Enables most functions in the kernel to be traced as well as the return +of the function. + +*TRACEFS_TRACER_IRQSOFF* : +Tracers the latency of interrupts disabled. + +*TRACEFS_TRACER_PREEMPTOFF* : +Tracers the latency of preemption disabled (the time in the kernel that +tasks can not be scheduled from the CPU). + +*TRACEFS_TRACER_PREEMPTIRQSOFF* : +Traces the combined total latency of when interrupts are disabled as well as when +preemption is disabled. + +*TRACEFS_TRACER_WAKEUP* : +Traces the latency of when the highest priority task takes to wake up. + +*TRACEFS_TRACER_WAKEUP_RT* : +Traces the latency of when the highest priority real-time task takes to wake up. +All other tasks are ignored. + +*TRACEFS_TRACER_WAKEUP_DL* : +Traces the latency of when the highest priority DEADLINE task takes to wake up. +All other tasks are ignored. + +*TRACEFS_TRACER_MMIOTRACE* : +Traces the interaction of devices with the kernel. + +*TRACEFS_TRACER_HWLAT* : +Detects latency caused by the hardware that is outside the scope of the kernel. + +*TRACEFS_TRACER_BRANCH* : +Traces when likely or unlikely branches are taken. + +*TRACEFS_TRACER_BLOCK* : +Special tracer for the block devices. + +Note that the above tracers may not be available in the kernel and +*tracefs_tracer_set()* will return an error with errno set to ENODEV, +if the kernel does not support the _tracer_ option, or the custom one +if TRACEFS_TRACER_CUSTOM is used. + +RETURN VALUE +------------ +Returns 0 on success, or -1 on error. + +ERRORS +------ + +*tracefs_tracer_set*() can fail with the following errors: + +*EINVAL* The _tracer_ parameter is outside the scope of what is defined. + +*ENOMEM* Memory allocation error. + +*ENOENT* Tracers are not supported on the running kernel. + +*ENODEV* The specified tracer is not supported on the running kernel. + +Other errors may also happen caused by internal system calls. + +EXAMPLE +------- +[source,c] +-- +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> +#include <errno.h> +#include <tracefs.h> + +int main(int argc, char *argv[]) +{ + struct tracefs_instance *inst = NULL; + enum tracefs_tracers t = TRACEFS_TRACER_NOP; + const char *buf = NULL; + const char *cust; + int ret; + int ch; + + while ((ch = getopt(argc, argv, "nfgiwdc:B:")) > 0) { + switch (ch) { + case 'f': t = TRACEFS_TRACER_FUNCTION; break; + case 'g': t = TRACEFS_TRACER_FUNCTION_GRAPH; break; + case 'i': t = TRACEFS_TRACER_PREEMPTIRQSOFF; break; + case 'w': t = TRACEFS_TRACER_WAKEUP_RT; break; + case 'd': t = TRACEFS_TRACER_WAKEUP_DL; break; + case 'c': + t = TRACEFS_TRACER_CUSTOM; + cust = optarg; + break; + case 'B': + buf = optarg; + break; + case 'n': + /* nop */ + break; + default: + printf("Unknow arg %c\n", ch); + exit(-1); + } + } + + if (buf) { + inst = tracefs_instance_create(buf); + if (!inst) { + printf("failed to create instance\n"); + exit(-1); + } + } + + if (t == TRACEFS_TRACER_CUSTOM) + ret = tracefs_tracer_set(inst, t, cust); + else + ret = tracefs_tracer_set(inst, t); + + if (ret < 0) { + if (inst) { + tracefs_instance_destroy(inst); + tracefs_instance_free(inst); + } + if (errno == ENODEV) + printf("Tracer not supported by kernel\n"); + else + perror("Error"); + exit(-1); + } + + if (inst) + tracefs_instance_free(inst); + + exit(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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +*sameeruddin shaik* <sameeruddin.shaik8@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-uprobes.txt b/Documentation/libtracefs-uprobes.txt new file mode 100644 index 0000000..1ca80da --- /dev/null +++ b/Documentation/libtracefs-uprobes.txt @@ -0,0 +1,189 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_uprobe_alloc,tracefs_uretprobe_alloc - Allocate new user (return) probe + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +struct tracefs_dynevent pass:[*] +*tracefs_uprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_, unsigned long long _offset_, const char pass:[*]_fetchargs_) +struct tracefs_dynevent pass:[*] +*tracefs_uretprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_, unsigned long long _offset_, const char pass:[*]_fetchargs_) +-- + +DESCRIPTION +----------- +*tracefs_uprobe_alloc*() allocates a new uprobe context. It will be in the _system_ group +(or uprobes if _system_ is NULL) and with _event_ name. The uprobe will be attached to _offset_ +within the _file_. The list of arguments, described in _fetchargs_, will be fetched with the uprobe. +The returned pointer to the user probe context must be freed with *tracefs_dynevent_free*(). +The ubrobe is not configured in the system, tracefs_dynevent_* set of APIs can be used to configure +it. + +The *tracefs_uretprobe_alloc*() behaves the same as *tracefs_uprobe_alloc*(), the only difference is +that it allocates context to user return probe (uretprobe). + +RETURN VALUE +------------ +The *tracefs_uprobe_alloc*() and *tracefs_uretprobe_alloc*() APIs return a pointer to an allocated +tracefs_dynevent structure, describing the user probe. This pointer must be freed with +*tracefs_dynevent_free*(3). Note, this only allocates a descriptor representing the uprobe. It does +not modify the running system. On error NULL is returned. + +EXAMPLE +------- +[source,c] +-- + +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> + +#include <tracefs.h> + +static int callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct trace_seq seq; + + trace_seq_init(&seq); + tep_print_event(event->tep, &seq, record, "%d-%s: %s", + TEP_PRINT_PID, TEP_PRINT_COMM, TEP_PRINT_NAME); + trace_seq_puts(&seq, "'\n"); + + trace_seq_terminate(&seq); + trace_seq_do_printf(&seq); + trace_seq_destroy(&seq); + + return 0; +} + +static pid_t run_exec(char **argv, char **env) +{ + pid_t pid; + + pid = fork(); + if (pid) + return pid; + + execve(argv[0], argv, env); + perror("exec"); + exit(-1); +} + +const char *myprobe = "my_urobes"; + +int main (int argc, char **argv, char **env) +{ + struct tracefs_dynevent *uprobe, *uretprobe; + struct tep_handle *tep; + struct tracefs_instance *instance; + const char *sysnames[] = { myprobe, NULL }; + long addr; + pid_t pid; + + if (argc < 3) { + printf("usage: %s file_offset command\n", argv[0]); + exit(-1); + } + addr = strtol(argv[1], NULL, 0); + + instance = tracefs_instance_create("exec_open"); + if (!instance) { + perror("creating instance"); + exit(-1); + } + + tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_UPROBE|TRACEFS_DYNEVENT_URETPROBE, true); + + uprobe = tracefs_uprobe_alloc(myprobe, "user_probe", argv[2], addr, NULL); + uretprobe = tracefs_uretprobe_alloc(myprobe, "user_retprobe", argv[2], addr, NULL); + if (!uprobe || !uretprobe) { + perror("allocating user probes"); + exit(-1); + } + + if (tracefs_dynevent_create(uprobe) || + tracefs_dynevent_create(uretprobe)) { + perror("creating user probes"); + exit(-1); + } + + tep = tracefs_local_events_system(NULL, sysnames); + if (!tep) { + perror("reading events"); + exit(-1); + } + + tracefs_event_enable(instance, myprobe, "user_probe"); + tracefs_event_enable(instance, myprobe, "user_retprobe"); + + pid = run_exec(&argv[2], env); + + /* Let the child start to run */ + sched_yield(); + + do { + tracefs_load_cmdlines(NULL, tep); + tracefs_iterate_raw_events(tep, instance, NULL, 0, callback, NULL); + } while (waitpid(pid, NULL, WNOHANG) != pid); + + /* disable and destroy the events */ + tracefs_dynevent_destroy(uprobe, true); + tracefs_dynevent_destroy(uretprobe, true); + tracefs_dynevent_free(uprobe); + tracefs_dynevent_free(uretprobe); + tracefs_instance_destroy(instance); + + 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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- + +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs-utils.txt b/Documentation/libtracefs-utils.txt new file mode 100644 index 0000000..ab16cc6 --- /dev/null +++ b/Documentation/libtracefs-utils.txt @@ -0,0 +1,139 @@ +libtracefs(3) +============= + +NAME +---- +tracefs_tracers, tracefs_tracer_available, tracefs_get_clock, tracefs_list_free, +tracefs_list_add, tracefs_list_size - Helper functions for working with trace file system. + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +char pass:[*]pass:[*]*tracefs_tracers*(const char pass:[*]_tracing_dir_); +bool *tracefs_tracer_available*(const char pass:[*]_tracing_dir_, const char pass:[*]_tracer_); +char pass:[*]*tracefs_get_clock*(struct tracefs_instance pass:[*]_instance_); +void *tracefs_list_free*(char pass:[*]pass:[*]_list_); +char pass:[**]*tracefs_list_add*(char **_list_, const char *_string_); +int *tracefs_list_size*(char pass:[**]_list_); +-- + +DESCRIPTION +----------- +Various useful functions for working with trace file system. + +The *tracefs_tracers()* function returns array of strings with the +names of supported tracer plugins, located in the given _tracing_dir_ +directory. This could be NULL or the location of the tracefs mount point +for the trace systems of the local machine, or it may be a path to a copy +of the tracefs directory from another machine. The last entry in the array +as a NULL pointer. The array must be freed with *tracefs_list_free()* API. + +The *tracefs_tracer_available()* returns true if the _tracer_ is available +in the given _tracing_dir_director_, and false otherwise. + +The *tracefs_get_clock()* function returns name of the current trace clock, +used in the given _instance_. If _instance_ is NULL, the clock of the main +trace instance is returned. The returned string must be freed with free(). + +The *tracefs_list_free()* function frees an array of strings, returned by +*tracefs_event_systems()*, *tracefs_system_events()* and *tracefs_tracers()* +APIs. + +The *tracefs_list_add()* function adds _string_ to the string _list_. If +_list_ is NULL, then a new list is created with the first element a copy +of _string_, and the second element will be a NULL pointer. If _list_ is +not NULL, then it is reallocated to include a new element and a NULL terminator, +and the new allocated array is returned. The list passed in should be ignored, +as it wil be freed if a new one was allocated. + +The *tracefs_list_size()* is a fast way to find out the number of elements +in a string array that is to be freed with *tracefs_list_free()*. Note, this +depends on meta data that is created for these lists and will not work on +normal string arrays like argv. + +RETURN VALUE +------------ +The *tracefs_tracers()* returns array of strings. The last element in that +array is a NULL pointer. The array must be freed with *tracefs_list_free()* API. +In case of an error, NULL is returned. + +The *tracefs_tracer_available()* returns true if the _tracer_ is available, +and false otherwise. + +The *tracefs_get_clock()* returns string, that must be freed with free(), or NULL +in case of an error. + +The *tracefs_list_add()* returns an allocated string array that must be freed +with *tracefs_list_free()* on success or NULL on error. If NULL is returned, +then the passed in _list_ is untouched. Thus, *tracefs_list_add()* should be +treated similarly to *realloc*(3). + +The *tracefs_list_size()* returns the number of strings in the _list_. The +passed in list must be one that is to be freed with *tracefs_list_free()* +as the list has meta data that is used to determine the size and this does +not work on any normal string array like argv. + +EXAMPLE +------- +[source,c] +-- +#include <tracefs.h> + +char **tracers = tracefs_tracers(NULL); + + if (tracers) { + /* Got tracer plugins from the top trace instance */ + ... + tracefs_list_free(tracers); + } +.... +char *clock = tracefs_get_clock(NULL); + + if (clock) { + /* Got current trace clock of the top trace instance */ + ... + free(clock); + } +-- +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] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/libtracefs.txt b/Documentation/libtracefs.txt new file mode 100644 index 0000000..c3f448d --- /dev/null +++ b/Documentation/libtracefs.txt @@ -0,0 +1,344 @@ +libtracefs(3) +============= + +NAME +---- +libtracefs - Linux kernel trace file system library + +SYNOPSIS +-------- +[verse] +-- +*#include <tracefs.h>* + +Locations of tracing files and directories: + char pass:[*]*tracefs_get_tracing_file*(const char pass:[*]_name_); + void *tracefs_put_tracing_file*(char pass:[*]_name_); + const char pass:[*]*tracefs_tracing_dir*(void); + const char pass:[*]*tracefs_debug_dir*(void); + int *tracefs_set_tracing_dir*(char pass:[*]_tracing_dir_) + int *tracefs_tracing_dir_is_mounted*(bool _mount_, const char pass:[**]_path_); + +Trace instances: + struct tracefs_instance pass:[*]*tracefs_instance_create*(const char pass:[*]_name_); + int *tracefs_instance_destroy*(struct tracefs_instance pass:[*]_instance_); + struct tracefs_instance pass:[*]*tracefs_instance_alloc*(const char pass:[*]_tracing_dir_, const char pass:[*]_name_); + void *tracefs_instance_free*(struct tracefs_instance pass:[*]_instance_); + char pass:[**]*tracefs_instances*(const char pass:[*]_regex_); + bool *tracefs_instance_is_new*(struct tracefs_instance pass:[*]_instance_); + bool *tracefs_file_exists*(struct tracefs_instance pass:[*]_instance_, char pass:[*]_name_); + bool *tracefs_dir_exists*(struct tracefs_instance pass:[*]_instance_, char pass:[*]_name_); + char pass:[*]*tracefs_instance_get_file*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_); + char pass:[*]*tracefs_instance_get_dir*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_instance_file_open*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_, int _mode_); + int *tracefs_instance_file_write*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_, const char pass:[*]_str_); + int *tracefs_instance_file_append*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_, const char pass:[*]_str_); + int *tracefs_instance_file_clear*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_); + char pass:[*]*tracefs_instance_file_read*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_, int pass:[*]_psize_); + int *tracefs_instance_file_read_number*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_file_, long long int pass:[*]_res_); + const char pass:[*]*tracefs_instance_get_name*(struct tracefs_instance pass:[*]_instance_); + const char pass:[*]*tracefs_instance_get_trace_dir*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_instances_walk*(int (pass:[*]_callback_)(const char pass:[*], void pass:[*]), void pass:[*]_context)_; + bool *tracefs_instance_exists*(const char pass:[*]_name_); + int *tracefs_instance_set_affinity*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_cpu_str_); + int *tracefs_instance_set_affinity_set*(struct tracefs_instance pass:[*]_instance_, cpu_set_t pass:[*]_set_, size_t _set_size_); + int *tracefs_instance_set_affinity_raw*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_mask_); + char pass:[*]*tracefs_instance_get_affinity*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_instance_get_affinity_set*(struct tracefs_instance pass:[*]_instance_, cpu_set_t pass:[*]_set_, size_t _set_size_); + char pass:[*]*tracefs_instance_get_affinity_raw*(struct tracefs_instance pass:[*]_instance_); + size_t *tracefs_instance_get_buffer_size*(struct tracefs_instance pass:[*]_instance_, int _cpu_); + int *tracefs_instance_set_buffer_size*(struct tracefs_instance pass:[*]_instance_, size_t _size_, int _cpu_); + +Trace events: + char pass:[*]pass:[*]*tracefs_event_systems*(const char pass:[*]_tracing_dir_); + char pass:[*]pass:[*]*tracefs_system_events*(const char pass:[*]_tracing_dir_, const char pass:[*]_system_); + int *tracefs_event_enable*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, + const char pass:[*]_event_); + int *tracefs_event_disable*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, + const char pass:[*]_event_); + enum tracefs_enable_state *tracefs_event_is_enabled*(struct tracefs_instance pass:[*]_instance_, + const char pass:[*]_system_, const char pass:[*]_event_); + int *tracefs_iterate_raw_events*(struct tep_handle pass:[*]_tep_, struct tracefs_instance pass:[*]_instance_, cpu_set_t pass:[*]_cpus_, int _cpu_size_, int (pass:[*]_callback_)(struct tep_event pass:[*], struct tep_record pass:[*], int, void pass:[*]), void pass:[*]_callback_context_); + void *tracefs_iterate_stop*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_follow_event*(struct tep_handle pass:[*]_tep_, struct tracefs_instance pass:[*]_instance_, + const char pass:[*]_system_, const char pass:[*]_event_name_, + int (pass:[*]_callback_)(struct tep_event pass:[*], + struct tep_record pass:[*], + int, void pass:[*]), + void pass:[*]_callback_data_); + int *tracefs_follow_missed_events*(struct tracefs_instance pass:[*]_instance_, + int (pass:[*]_callback_)(struct tep_event pass:[*], + struct tep_record pass:[*], + int, void pass:[*]), + void pass:[*]_callback_data_); + struct tep_handle pass:[*]*tracefs_local_events*(const char pass:[*]_tracing_dir_); + struct tep_handle pass:[*]*tracefs_local_events_system*(const char pass:[*]_tracing_dir_, const char pass:[*] const pass:[*]_sys_names_); + int *tracefs_fill_local_events*(const char pass:[*]_tracing_dir_, struct tep_handle pass:[*]_tep_, int pass:[*]_parsing_failures_); + int *tracefs_load_cmdlines*(const char pass:[*]_tracing_dir_, struct tep_handle pass:[*]_tep_); + char pass:[*]*tracefs_event_get_file*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_); + char pass:[*]*tracefs_event_file_read*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_, int pass:[*]_psize_); + int *tracefs_event_file_write*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_, const char pass:[*]_str_); + int *tracefs_event_file_append*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_, const char pass:[*]_str_); + int *tracefs_event_file_clear*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_); + bool *tracefs_event_file_exists*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_system_, const char pass:[*]_event_, + +Event filters: + int *tracefs_filter_string_append*(struct tep_event pass:[*]_event_, char pass:[**]_filter_, + struct tracefs_filter _type_, const char pass:[*]_field_, + enum tracefs_synth_compare _compare_, const char pass:[*]_val_); + int *tracefs_filter_string_verify*(struct tep_event pass:[*]_event_, const char pass:[*]_filter_, char pass:[**]_err_); + int *tracefs_event_filter_apply*(struct tracefs_instance pass:[*]_instance_, struct tep_event pass:[*]_event_, const char pass:[*]_filter_); + int *tracefs_event_filter_clear*(struct tracefs_instance pass:[*]_instance_, struct tep_event pass:[*]_event_); + +Function filters: + int *tracefs_function_filter*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_filter_, const char pass:[*]_module_, int _flags_); + int *tracefs_function_notrace*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_filter_, const char pass:[*]_module_, int _flags_); + int *tracefs_filter_functions*(const char pass:[*]_filter_, const char pass:[*]_module_, char pass:[*]pass:[*]pass:[*]_list_); + +Trace helper functions: + void *tracefs_list_free*(char pass:[*]pass:[*]_list_); + char pass:[**]*tracefs_list_add*(char **_list_, const char *_string_); + int *tracefs_list_size*(char pass:[**]_list_); + char pass:[*]*tracefs_get_clock*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_trace_is_on*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_trace_on*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_trace_off*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_trace_on_get_fd*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_trace_on_fd*(int _fd_); + int *tracefs_trace_off_fd*(int _fd_); + +Trace stream: + ssize_t *tracefs_trace_pipe_stream*(int _fd_, struct tracefs_instance pass:[*]_instance_, int _flags_); + ssize_t *tracefs_trace_pipe_print*(struct tracefs_instance pass:[*]_instance_, int _flags_); + void *tracefs_trace_pipe_stop*(struct tracefs_instance pass:[*]_instance_); + +Trace options: + const struct tracefs_options_mask pass:[*]*tracefs_options_get_supported*(struct tracefs_instance pass:[*]_instance_); + bool *tracefs_option_is_supported*(struct tracefs_instance pass:[*]_instance_, enum tracefs_option_id _id_); + const struct tracefs_options_mask pass:[*]*tracefs_options_get_enabled*(struct tracefs_instance pass:[*]_instance_); + bool *tracefs_option_is_enabled*(struct tracefs_instance pass:[*]_instance_, enum tracefs_option_id _id_); + bool *tracefs_option_mask_is_set*(const struct tracefs_options_mask *options, enum tracefs_option_id id); + int *tracefs_option_enable*(struct tracefs_instance pass:[*]_instance_, enum tracefs_option_id _id_); + int *tracefs_option_disable*(struct tracefs_instance pass:[*]_instance_, enum tracefs_option_id _id_); + const char pass:[*]*tracefs_option_name*(enum tracefs_option_id _id_); + enum tracefs_option_id *tracefs_option_id*(const char pass:[*]_name_); + +Ftrace tracers: + char pass:[*]pass:[*]*tracefs_tracers*(const char pass:[*]_tracing_dir_); + bool *tracefs_tracer_available*(const char pass:[*]_tracing_dir_, const char pass:[*]_tracer_); + int *tracefs_tracer_set*(struct tracefs_instance pass:[*]_instance_, enum tracefs_tracers _tracer_); + int *tracefs_tracer_set*(struct tracefs_instance pass:[*]_instance_, enum tracefs_tracers _tracer_, const char pass:[*]_name_); + int *tracefs_tracer_clear*(struct tracefs_instance pass:[*]_instance_); + +Writing data in the trace buffer: + int *tracefs_print_init*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_printf*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_fmt_, _..._); + int *tracefs_vprintf*(struct tracefs_instance pass:[*]_instance_, const char pass:[*]_fmt_, va_list _ap_); + void *tracefs_print_close*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_binary_init*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_binary_write*(struct tracefs_instance pass:[*]_instance_, void pass:[*]_data_, int _len_); + void *tracefs_binary_close*(struct tracefs_instance pass:[*]_instance_); + +Control library logs: + int *tracefs_set_loglevel*(enum tep_loglevel _level_); + +Dynamic event generic APIs: + struct *tracefs_dynevent*; + enum *tracefs_dynevent_type*; + int *tracefs_dynevent_create*(struct tracefs_dynevent pass:[*]_devent_); + int *tracefs_dynevent_destroy*(struct tracefs_dynevent pass:[*]_devent_, bool _force_); + int *tracefs_dynevent_destroy_all*(unsigned int _types_, bool _force_); + void *tracefs_dynevent_free*(struct tracefs_dynevent pass:[*]_devent_); + void *tracefs_dynevent_list_free*(struct tracefs_dynevent pass:[*]pass:[*]_events_); + struct tracefs_dynevent pass:[*]*tracefs_dynevent_get*(enum tracefs_dynevent_type _type_, const char pass:[*]_system_, const char pass:[*]_event_); + struct tracefs_dynevent pass:[*]pass:[*]*tracefs_dynevent_get_all*(unsigned int _types_, const char pass:[*]_system_); + enum tracefs_dynevent_type *tracefs_dynevent_info*(struct tracefs_dynevent pass:[*]_dynevent_, char pass:[*]pass:[*]_system_, char pass:[*]pass:[*]_event_, char pass:[*]pass:[*]_prefix_, char pass:[*]pass:[*]_addr_, char pass:[*]pass:[*]_format_); + struct tep_event pass:[*]*tracefs_dynevent_get_event*(struct tep_handle pass:[*]_tep_, struct tracefs_dynevent pass:[*]_dynevent_); + +Even probes (eprobes): + struct tracefs_dynevent pass:[*] *tracefs_eprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_target_system_, const char pass:[*]_target_event_, const char pass:[*]_fetchargs_); + +Uprobes, Kprobes and Kretprobes: + struct tracefs_dynevent pass:[*] *tracefs_kprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_addr_, const char pass:[*]_format_); + struct tracefs_dynevent pass:[*] *tracefs_kretprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_addr_, const char pass:[*]_format_, unsigned int _max_); + int *tracefs_kprobe_raw*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_addr_, const char pass:[*]_format_); + int *tracefs_kretprobe_raw*(const char pass:[*]_system_, const char pass:[*]_event_, const char pass:[*]_addr_, const char pass:[*]_format_); + *tracefs_uprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_, unsigned long long _offset_, const char pass:[*]_fetchargs_) + *tracefs_uretprobe_alloc*(const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_file_, unsigned long long _offset_, const char pass:[*]_fetchargs_); + +Synthetic events: + struct tracefs_synth pass:[*]*tracefs_sql*(struct tep_handle pass:[*]_tep_, const char pass:[*]_name_, + const char pass:[*]_sql_buffer_, char pass:[**]_err_); + struct tracefs_synth pass:[*]*tracefs_synth_alloc*(struct tep_handle pass:[*]_tep_, + const char pass:[*]_name_, + const char pass:[*]_start_system_, + const char pass:[*]_start_event_, + const char pass:[*]_end_system_, + const char pass:[*]_end_event_, + const char pass:[*]_start_match_field_, + const char pass:[*]_end_match_field_, + const char pass:[*]_match_name_); + int *tracefs_synth_add_match_field*(struct tracefs_synth pass:[*]_synth_, + const char pass:[*]_start_match_field_, + const char pass:[*]_end_match_field_, + const char pass:[*]_name_); + int *tracefs_synth_add_compare_field*(struct tracefs_synth pass:[*]_synth_, + const char pass:[*]_start_compare_field_, + const char pass:[*]_end_compare_field_, + enum tracefs_synth_calc _calc_, + const char pass:[*]_name_); + int *tracefs_synth_add_start_field*(struct tracefs_synth pass:[*]_synth_, + const char pass:[*]_start_field_, + const char pass:[*]_name_); + int *tracefs_synth_add_end_field*(struct tracefs_synth pass:[*]_synth_, + const char pass:[*]_end_field_, + const char pass:[*]_name_); + int *tracefs_synth_append_start_filter*(struct tracefs_synth pass:[*]_synth_, + struct tracefs_filter _type_, + const char pass:[*]_field_, + enum tracefs_synth_compare _compare_, + const char pass:[*]_val_); + int *tracefs_synth_append_end_filter*(struct tracefs_synth pass:[*]_synth_, + struct tracefs_filter _type_, + const char pass:[*]_field_, + enum tracefs_synth_compare _compare_, + const char pass:[*]_val_); + void *tracefs_synth_free*(struct tracefs_synth pass:[*]_synth_); + int *tracefs_synth_create*(struct tracefs_synth pass:[*]_synth_); + int *tracefs_synth_destroy*(struct tracefs_synth pass:[*]_synth_); + int *tracefs_synth_echo_cmd*(struct trace_seq pass:[*]_seq_, struct tracefs_synth pass:[*]_synth_); + bool *tracefs_synth_complete*(struct tracefs_synth pass:[*]_synth_); + struct tracefs_hist pass:[*]*tracefs_synth_get_start_hist*(struct tracefs_synth pass:[*]_synth_); + int *tracefs_synth_trace*(struct tracefs_synth pass:[*]_synth_, + enum tracefs_synth_handler _type_, const char pass:[*]_var_); + int *tracefs_synth_snapshot*(struct tracefs_synth pass:[*]_synth_, + enum tracefs_synth_handler _type_, const char pass:[*]_var_); + int *tracefs_synth_save*(struct tracefs_synth pass:[*]_synth_, + enum tracefs_synth_handler _type_, const char pass:[*]_var_, + char pass:[**]_save_fields_); + const char pass:[*]*tracefs_synth_get_name*(struct tracefs_synth pass:[*]_synth_); + int *tracefs_synth_raw_fmt*(struct trace_seq pass:[*]_seq_, struct tracefs_synth pass:[*]_synth_); + const char pass:[*]*tracefs_synth_show_event*(struct tracefs_synth pass:[*]_synth_); + const char pass:[*]*tracefs_synth_show_start_hist*(struct tracefs_synth pass:[*]_synth_); + const char pass:[*]*tracefs_synth_show_end_hist*(struct tracefs_synth pass:[*]_synth_); + struct tep_event pass:[*]*tracefs_synth_get_event*(struct tep_handle pass:[*]_tep_, struct tracefs_synth pass:[*]_synth_); + +Ftrace errors reporting: + char pass:[*]*tracefs_error_last*(struct tracefs_instance pass:[*]_instance_); + char pass:[*]*tracefs_error_all*(struct tracefs_instance pass:[*]_instance_); + int *tracefs_error_clear*(struct tracefs_instance pass:[*]_instance_); + +Histograms: + struct tracefs_hist pass:[*]*tracefs_hist_alloc*(struct tracefs_tep pass:[*] _tep_, + const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_key_, enum tracefs_hist_key_type _type_); + struct tracefs_hist pass:[*]*tracefs_hist_alloc_2d*(struct tracefs_tep pass:[*] _tep_, + const char pass:[*]_system_, const char pass:[*]_event_, + const char pass:[*]_key1_, enum tracefs_hist_key_type _type1_, + const char pass:[*]_key2_, enum tracefs_hist_key_type _type2_)); + struct tracefs_hist pass:[*]*tracefs_hist_alloc_nd*(struct tracefs_tep pass:[*] _tep_, + const char pass:[*]_system_, const char pass:[*]_event_, + struct tracefs_hist_axis pass:[*]_axes_); + struct tracefs_hist pass:[*]*tracefs_hist_alloc_nd_cnt*(struct tep_handle pass:[*]_tep_, + const char pass:[*]_system_, const char pass:[*]_event_name_, + struct tracefs_hist_axis_cnt pass:[*]_axes_); + void *tracefs_hist_free*(struct tracefs_hist pass:[*]_hist_); + int *tracefs_hist_add_key*(struct tracefs_hist pass:[*]_hist_, const char pass:[*]_key_, + enum tracefs_hist_key_type _type_); + int *tracefs_hist_add_key_cnt*(struct tracefs_hist pass:[*]_hist_, const char pass:[*]_key_, + enum tracefs_hist_key_type _type_, int _cnt_); + int *tracefs_hist_add_value*(struct tracefs_hist pass:[*]_hist_, const char pass:[*]_value_); + int *tracefs_hist_add_sort_key*(struct tracefs_hist pass:[*]_hist_, + const char pass:[*]_sort_key_); + int *tracefs_hist_set_sort_key*(struct tracefs_hist pass:[*]_hist_, + const char pass:[*]_sort_key_, _..._); + int *tracefs_hist_sort_key_direction*(struct tracefs_hist pass:[*]_hist_, + const char pass:[*]_sort_key_, + enum tracefs_hist_sort_direction _dir_); + int *tracefs_hist_add_name*(struct tracefs_hist pass:[*]_hist_, const char pass:[*]_name_); + int *tracefs_hist_append_filter*(struct tracefs_hist pass:[*]_hist_, + enum tracefs_filter _type_, + const char pass:[*]_field_, + enum tracefs_compare _compare_, + const char pass:[*]_val_); + int *tracefs_hist_echo_cmd*(struct trace_seq pass:[*]_s_, struct tracefs_instance pass:[*]_instance_, + struct tracefs_hist pass:[*]_hist_, + enum tracefs_hist_command _command_); + int *tracefs_hist_command*(struct tracefs_instance pass:[*]_instance_, + struct tracefs_hist pass:[*]_hist_, + enum tracefs_hist_command _command_); + const char pass:[*]*tracefs_hist_get_name*(struct tracefs_hist pass:[*]_hist_); + const char pass:[*]*tracefs_hist_get_event*(struct tracefs_hist pass:[*]_hist_); + const char pass:[*]*tracefs_hist_get_system*(struct tracefs_hist pass:[*]_hist_); + int *tracefs_hist_start*(struct tracefs_instance pass:[*]_instance_, struct tracefs_hist pass:[*]_hist_); + int *tracefs_hist_destroy*(struct tracefs_instance pass:[*]_instance_, struct tracefs_hist pass:[*]_hist_); + int *tracefs_hist_pause*(struct tracefs_instance pass:[*]_instance_, struct tracefs_hist pass:[*]_hist_); + int *tracefs_hist_continue*(struct tracefs_instance pass:[*]_instance_, struct tracefs_hist pass:[*]_hist_); + int *tracefs_hist_reset*(struct tracefs_instance pass:[*]_instance_, struct tracefs_hist pass:[*]_hist_); + +Recording of trace_pipe_raw files: + struct tracefs_cpu pass:[*]*tracefs_cpu_open*(struct tracefs_instance pass:[*]_instance_, + int _cpu_, bool _nonblock_); + struct tracefs_cpu pass:[*]*tracefs_cpu_alloc_fd*(int _fd_, int _subbuf_size_, bool _nonblock_); + void *tracefs_cpu_close*(struct tracefs_cpu pass:[*]_tcpu_); + void *tracefs_cpu_free_fd*(struct tracefs_cpu pass:[*]_tcpu_); + int *tracefs_cpu_read_size*(struct tracefs_cpu pass:[*]_tcpu_); + int *tracefs_cpu_read*(struct tracefs_cpu pass:[*]_tcpu_, void pass:[*]_buffer_, bool _nonblock_); + int *tracefs_cpu_buffered_read*(struct tracefs_cpu pass:[*]_tcpu_, void pass:[*]_buffer_, bool _nonblock_); + int *tracefs_cpu_write*(struct tracefs_cpu pass:[*]_tcpu_, int _wfd_, bool _nonblock_); + int *tracefs_cpu_stop*(struct tracefs_cpu pass:[*]_tcpu_); + int *tracefs_cpu_flush*(struct tracefs_cpu pass:[*]_tcpu_, void pass:[*]_buffer_); + int *tracefs_cpu_flush_write*(struct tracefs_cpu pass:[*]_tcpu_, int _wfd_); + int *tracefs_cpu_pipe*(struct tracefs_cpu pass:[*]_tcpu_, int _wfd_, bool _nonblock_); + +-- + +DESCRIPTION +----------- +The libtracefs(3) library provides APIs to access kernel trace file system. + +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 +-------- +*libtraceevent*(3), +*trace-cmd*(1) + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org> +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com> +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +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) 2020 VMware, Inc. Free use of this software is granted under +the terms of the GNU Public License (GPL). diff --git a/Documentation/manpage-1.72.xsl b/Documentation/manpage-1.72.xsl new file mode 100644 index 0000000..b4d315c --- /dev/null +++ b/Documentation/manpage-1.72.xsl @@ -0,0 +1,14 @@ +<!-- manpage-1.72.xsl: + special settings for manpages rendered from asciidoc+docbook + handles peculiarities in docbook-xsl 1.72.0 --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<xsl:import href="manpage-base.xsl"/> + +<!-- these are the special values for the roff control characters + needed for docbook-xsl 1.72.0 --> +<xsl:param name="git.docbook.backslash">▓</xsl:param> +<xsl:param name="git.docbook.dot" >⌂</xsl:param> + +</xsl:stylesheet> diff --git a/Documentation/manpage-base.xsl b/Documentation/manpage-base.xsl new file mode 100644 index 0000000..a264fa6 --- /dev/null +++ b/Documentation/manpage-base.xsl @@ -0,0 +1,35 @@ +<!-- manpage-base.xsl: + special formatting for manpages rendered from asciidoc+docbook --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<!-- these params silence some output from xmlto --> +<xsl:param name="man.output.quietly" select="1"/> +<xsl:param name="refentry.meta.get.quietly" select="1"/> + +<!-- convert asciidoc callouts to man page format; + git.docbook.backslash and git.docbook.dot params + must be supplied by another XSL file or other means --> +<xsl:template match="co"> + <xsl:value-of select="concat( + $git.docbook.backslash,'fB(', + substring-after(@id,'-'),')', + $git.docbook.backslash,'fR')"/> +</xsl:template> +<xsl:template match="calloutlist"> + <xsl:value-of select="$git.docbook.dot"/> + <xsl:text>sp </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> +<xsl:template match="callout"> + <xsl:value-of select="concat( + $git.docbook.backslash,'fB', + substring-after(@arearefs,'-'), + '. ',$git.docbook.backslash,'fR')"/> + <xsl:apply-templates/> + <xsl:value-of select="$git.docbook.dot"/> + <xsl:text>br </xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/Documentation/manpage-bold-literal.xsl b/Documentation/manpage-bold-literal.xsl new file mode 100644 index 0000000..608eb5d --- /dev/null +++ b/Documentation/manpage-bold-literal.xsl @@ -0,0 +1,17 @@ +<!-- manpage-bold-literal.xsl: + special formatting for manpages rendered from asciidoc+docbook --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<!-- render literal text as bold (instead of plain or monospace); + this makes literal text easier to distinguish in manpages + viewed on a tty --> +<xsl:template match="literal"> + <xsl:value-of select="$git.docbook.backslash"/> + <xsl:text>fB</xsl:text> + <xsl:apply-templates/> + <xsl:value-of select="$git.docbook.backslash"/> + <xsl:text>fR</xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/Documentation/manpage-normal.xsl b/Documentation/manpage-normal.xsl new file mode 100644 index 0000000..a48f5b1 --- /dev/null +++ b/Documentation/manpage-normal.xsl @@ -0,0 +1,13 @@ +<!-- manpage-normal.xsl: + special settings for manpages rendered from asciidoc+docbook + handles anything we want to keep away from docbook-xsl 1.72.0 --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<xsl:import href="manpage-base.xsl"/> + +<!-- these are the normal values for the roff control characters --> +<xsl:param name="git.docbook.backslash">\</xsl:param> +<xsl:param name="git.docbook.dot" >.</xsl:param> + +</xsl:stylesheet> diff --git a/Documentation/manpage-suppress-sp.xsl b/Documentation/manpage-suppress-sp.xsl new file mode 100644 index 0000000..a63c763 --- /dev/null +++ b/Documentation/manpage-suppress-sp.xsl @@ -0,0 +1,21 @@ +<!-- manpage-suppress-sp.xsl: + special settings for manpages rendered from asciidoc+docbook + handles erroneous, inline .sp in manpage output of some + versions of docbook-xsl --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<!-- attempt to work around spurious .sp at the tail of the line + that some versions of docbook stylesheets seem to add --> +<xsl:template match="simpara"> + <xsl:variable name="content"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + <xsl:if test="not(ancestor::authorblurb) and + not(ancestor::personblurb)"> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> + +</xsl:stylesheet> diff --git a/LICENSES/GPL-2.0 b/LICENSES/GPL-2.0 new file mode 100644 index 0000000..ff0812f --- /dev/null +++ b/LICENSES/GPL-2.0 @@ -0,0 +1,359 @@ +Valid-License-Identifier: GPL-2.0 +Valid-License-Identifier: GPL-2.0-only +Valid-License-Identifier: GPL-2.0+ +Valid-License-Identifier: GPL-2.0-or-later +SPDX-URL: https://spdx.org/licenses/GPL-2.0.html +Usage-Guide: + To use this license in source code, put one of the following SPDX + tag/value pairs into a comment according to the placement + guidelines in the licensing rules documentation. + For 'GNU General Public License (GPL) version 2 only' use: + SPDX-License-Identifier: GPL-2.0 + or + SPDX-License-Identifier: GPL-2.0-only + For 'GNU General Public License (GPL) version 2 or any later version' use: + SPDX-License-Identifier: GPL-2.0+ + or + SPDX-License-Identifier: GPL-2.0-or-later +License-Text: + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/LICENSES/LGPL-2.1 b/LICENSES/LGPL-2.1 new file mode 100644 index 0000000..27bb434 --- /dev/null +++ b/LICENSES/LGPL-2.1 @@ -0,0 +1,503 @@ +Valid-License-Identifier: LGPL-2.1 +Valid-License-Identifier: LGPL-2.1+ +SPDX-URL: https://spdx.org/licenses/LGPL-2.1.html +Usage-Guide: + To use this license in source code, put one of the following SPDX + tag/value pairs into a comment according to the placement + guidelines in the licensing rules documentation. + For 'GNU Lesser General Public License (LGPL) version 2.1 only' use: + SPDX-License-Identifier: LGPL-2.1 + For 'GNU Lesser General Public License (LGPL) version 2.1 or any later + version' use: + SPDX-License-Identifier: LGPL-2.1+ +License-Text: + +GNU LESSER GENERAL PUBLIC LICENSE +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as +the successor of the GNU Library Public License, version 2, hence the +version number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to +share and change it. By contrast, the GNU General Public Licenses are +intended to guarantee your freedom to share and change free software--to +make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software +Foundation and other authors who decide to use it. You can use it too, but +we suggest you first think carefully about whether this license or the +ordinary General Public License is the better strategy to use in any +particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not +price. Our General Public Licenses are designed to make sure that you have +the freedom to distribute copies of free software (and charge for this +service if you wish); that you receive source code or can get it if you +want it; that you can change the software and use pieces of it in new free +programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for you if +you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You +must make sure that they, too, receive or can get the source code. If you +link other code with the library, you must provide complete object files to +the recipients, so that they can relink them with the library after making +changes to the library and recompiling it. And you must show them these +terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone +else and passed on, the recipients should know that what they have is not +the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any +free program. We wish to make sure that a company cannot effectively +restrict the users of a free program by obtaining a restrictive license +from a patent holder. Therefore, we insist that any patent license obtained +for a version of the library must be consistent with the full freedom of +use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public +License, applies to certain designated libraries, and is quite different +from the ordinary General Public License. We use this license for certain +libraries in order to permit linking those libraries into non-free +programs. + +When a program is linked with a library, whether statically or using a +shared library, the combination of the two is legally speaking a combined +work, a derivative of the original library. The ordinary General Public +License therefore permits such linking only if the entire combination fits +its criteria of freedom. The Lesser General Public License permits more lax +criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does +Less to protect the user's freedom than the ordinary General Public +License. It also provides other free software developers Less of an +advantage over competing non-free programs. These disadvantages are the +reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + +For example, on rare occasions, there may be a special need to encourage +the widest possible use of a certain library, so that it becomes a de-facto +standard. To achieve this, non-free programs must be allowed to use the +library. A more frequent case is that a free library does the same job as +widely used non-free libraries. In this case, there is little to gain by +limiting the free library to free software only, so we use the Lesser +General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free +software. For example, permission to use the GNU C Library in non-free +programs enables many more people to use the whole GNU operating system, as +well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a +modified version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code +derived from the library, whereas the latter must be combined with the +library in order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program + which contains a notice placed by the copyright holder or other + authorized party saying it may be distributed under the terms of this + Lesser General Public License (also called "this License"). Each + licensee is addressed as "you". + + A "library" means a collection of software functions and/or data + prepared so as to be conveniently linked with application programs + (which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work which + has been distributed under these terms. A "work based on the Library" + means either the Library or any derivative work under copyright law: + that is to say, a work containing the Library or a portion of it, either + verbatim or with modifications and/or translated straightforwardly into + another language. (Hereinafter, translation is included without + limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for making + modifications to it. For a library, complete source code means all the + source code for all modules it contains, plus any associated interface + definition files, plus the scripts used to control compilation and + installation of the library. + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of running + a program using the Library is not restricted, and output from such a + program is covered only if its contents constitute a work based on the + Library (independent of the use of the Library in a tool for writing + it). Whether that is true depends on what the Library does and what the + program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete + source code as you receive it, in any medium, provided that you + conspicuously and appropriately publish on each copy an appropriate + copyright notice and disclaimer of warranty; keep intact all the notices + that refer to this License and to the absence of any warranty; and + distribute a copy of this License along with the Library. + + You may charge a fee for the physical act of transferring a copy, and + you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, + thus forming a work based on the Library, and copy and distribute such + modifications or work under the terms of Section 1 above, provided that + you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating + that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to + all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table + of data to be supplied by an application program that uses the + facility, other than as an argument passed when the facility is + invoked, then you must make a good faith effort to ensure that, in + the event an application does not supply such function or table, the + facility still operates, and performs whatever part of its purpose + remains meaningful. + + (For example, a function in a library to compute square roots has a + purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must be + optional: if the application does not supply it, the square root + function must still compute square roots.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Library, and + can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based on + the Library, the distribution of the whole must be on the terms of this + License, whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Library. + + In addition, mere aggregation of another work not based on the Library + with the Library (or with a work based on the Library) on a volume of a + storage or distribution medium does not bring the other work under the + scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public + License instead of this License to a given copy of the Library. To do + this, you must alter all the notices that refer to this License, so that + they refer to the ordinary GNU General Public License, version 2, + instead of to this License. (If a newer version than version 2 of the + ordinary GNU General Public License has appeared, then you can specify + that version instead if you wish.) Do not make any other change in these + notices. + + Once this change is made in a given copy, it is irreversible for that + copy, so the ordinary GNU General Public License applies to all + subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of the + Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of + it, under Section 2) in object code or executable form under the terms + of Sections 1 and 2 above provided that you accompany it with the + complete corresponding machine-readable source code, which must be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange. + + If distribution of object code is made by offering access to copy from a + designated place, then offering equivalent access to copy the source + code from the same place satisfies the requirement to distribute the + source code, even though third parties are not compelled to copy the + source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but + is designed to work with the Library by being compiled or linked with + it, is called a "work that uses the Library". Such a work, in isolation, + is not a derivative work of the Library, and therefore falls outside the + scope of this License. + + However, linking a "work that uses the Library" with the Library creates + an executable that is a derivative of the Library (because it contains + portions of the Library), rather than a "work that uses the + library". The executable is therefore covered by this License. Section 6 + states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file + that is part of the Library, the object code for the work may be a + derivative work of the Library even though the source code is + not. Whether this is true is especially significant if the work can be + linked without the Library, or if the work is itself a library. The + threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data structure + layouts and accessors, and small macros and small inline functions (ten + lines or less in length), then the use of the object file is + unrestricted, regardless of whether it is legally a derivative + work. (Executables containing this object code plus portions of the + Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may + distribute the object code for the work under the terms of Section + 6. Any executables containing that work also fall under Section 6, + whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a + "work that uses the Library" with the Library to produce a work + containing portions of the Library, and distribute that work under terms + of your choice, provided that the terms permit modification of the work + for the customer's own use and reverse engineering for debugging such + modifications. + + You must give prominent notice with each copy of the work that the + Library is used in it and that the Library and its use are covered by + this License. You must supply a copy of this License. If the work during + execution displays copyright notices, you must include the copyright + notice for the Library among them, as well as a reference directing the + user to the copy of this License. Also, you must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable + source code for the Library including whatever changes were used in + the work (which must be distributed under Sections 1 and 2 above); + and, if the work is an executable linked with the Library, with the + complete machine-readable "work that uses the Library", as object + code and/or source code, so that the user can modify the Library and + then relink to produce a modified executable containing the modified + Library. (It is understood that the user who changes the contents of + definitions files in the Library will not necessarily be able to + recompile the application to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a copy + of the library already present on the user's computer system, rather + than copying library functions into the executable, and (2) will + operate properly with a modified version of the library, if the user + installs one, as long as the modified version is interface-compatible + with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least three + years, to give the same user the materials specified in Subsection + 6a, above, for a charge no more than the cost of performing this + distribution. + + d) If distribution of the work is made by offering access to copy from a + designated place, offer equivalent access to copy the above specified + materials from the same place. + + e) Verify that the user has already received a copy of these materials + or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the Library" + must include any data and utility programs needed for reproducing the + executable from it. However, as a special exception, the materials to be + distributed need not include anything that is normally distributed (in + either source or binary form) with the major components (compiler, + kernel, and so on) of the operating system on which the executable runs, + unless that component itself accompanies the executable. + + It may happen that this requirement contradicts the license restrictions + of other proprietary libraries that do not normally accompany the + operating system. Such a contradiction means you cannot use both them + and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library + side-by-side in a single library together with other library facilities + not covered by this License, and distribute such a combined library, + provided that the separate distribution of the work based on the Library + and of the other library facilities is otherwise permitted, and provided + that you do these two things: + + a) Accompany the combined library with a copy of the same work based on + the Library, uncombined with any other library facilities. This must + be distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part + of it is a work based on the Library, and explaining where to find + the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the + Library except as expressly provided under this License. Any attempt + otherwise to copy, modify, sublicense, link with, or distribute the + Library is void, and will automatically terminate your rights under this + License. However, parties who have received copies, or rights, from you + under this License will not have their licenses terminated so long as + such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed + it. However, nothing else grants you permission to modify or distribute + the Library or its derivative works. These actions are prohibited by law + if you do not accept this License. Therefore, by modifying or + distributing the Library (or any work based on the Library), you + indicate your acceptance of this License to do so, and all its terms and + conditions for copying, distributing or modifying the Library or works + based on it. + +10. Each time you redistribute the Library (or any work based on the + Library), the recipient automatically receives a license from the + original licensor to copy, distribute, link with or modify the Library + subject to these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted + herein. You are not responsible for enforcing compliance by third + parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Library at all. For example, if a patent license + would not permit royalty-free redistribution of the Library by all + those who receive copies directly or indirectly through you, then the + only way you could satisfy both it and this License would be to refrain + entirely from distribution of the Library. + + If any portion of this section is held invalid or unenforceable under + any particular circumstance, the balance of the section is intended to + apply, and the section as a whole is intended to apply in other + circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system which is implemented + by public license practices. Many people have made generous + contributions to the wide range of software distributed through that + system in reliance on consistent application of that system; it is up + to the author/donor to decide if he or she is willing to distribute + software through any other system and a licensee cannot impose that + choice. + + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain + countries either by patents or by copyrighted interfaces, the original + copyright holder who places the Library under this License may add an + explicit geographical distribution limitation excluding those + countries, so that distribution is permitted only in or among countries + not thus excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of + the Lesser General Public License from time to time. Such new versions + will be similar in spirit to the present version, but may differ in + detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Library + specifies a version number of this License which applies to it and "any + later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Library does not specify a license + version number, you may choose any version ever published by the Free + Software Foundation. + +14. If you wish to incorporate parts of the Library into other free + programs whose distribution conditions are incompatible with these, + write to the author to ask for permission. For software which is + copyrighted by the Free Software Foundation, write to the Free Software + Foundation; we sometimes make exceptions for this. Our decision will be + guided by the two goals of preserving the free status of all + derivatives of our free software and of promoting the sharing and reuse + of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY + FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN + OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES + PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER + EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH + YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL + NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR + REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL + DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY + (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED + INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF + THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR + OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. +Copyright (C) year name of author + +This library is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or (at +your option) any later version. + +This library is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this library; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add +information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob' (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice +That's all there is to it! diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..61ed976 --- /dev/null +++ b/Makefile @@ -0,0 +1,397 @@ +# SPDX-License-Identifier: LGPL-2.1 +# libtracefs version +TFS_VERSION = 1 +TFS_PATCHLEVEL = 6 +TFS_EXTRAVERSION = 4 +TRACEFS_VERSION = $(TFS_VERSION).$(TFS_PATCHLEVEL).$(TFS_EXTRAVERSION) + +export TFS_VERSION +export TFS_PATCHLEVEL +export TFS_EXTRAVERSION +export TRACEFS_VERSION + +LIBTRACEEVENT_MIN_VERSION = 1.3 + +# taken from trace-cmd +MAKEFLAGS += --no-print-directory + +# Makefiles suck: This macro sets a default value of $(2) for the +# variable named by $(1), unless the variable has been set by +# environment or command line. This is necessary for CC and AR +# because make sets default values, so the simpler ?= approach +# won't work as expected. +define allow-override + $(if $(or $(findstring environment,$(origin $(1))),\ + $(findstring command line,$(origin $(1)))),,\ + $(eval $(1) = $(2))) +endef + +# Allow setting CC and AR, or setting CROSS_COMPILE as a prefix. +$(call allow-override,CC,$(CROSS_COMPILE)gcc) +$(call allow-override,AR,$(CROSS_COMPILE)ar) +$(call allow-override,PKG_CONFIG,pkg-config) +$(call allow-override,LD_SO_CONF_PATH,/etc/ld.so.conf.d/) +$(call allow-override,LDCONFIG,ldconfig) + +EXT = -std=gnu99 +INSTALL = install + +# Use DESTDIR for installing into a different root directory. +# This is useful for building a package. The program will be +# installed in this directory as if it was the root directory. +# Then the build tool can move it later. +DESTDIR ?= +DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))' + +LP64 := $(shell echo __LP64__ | ${CC} ${CFLAGS} -E -x c - | tail -n 1) +ifeq ($(LP64), 1) + libdir_relative_temp = lib64 +else + libdir_relative_temp = lib +endif + +libdir_relative ?= $(libdir_relative_temp) +prefix ?= /usr/local +man_dir ?= $(prefix)/share/man +man_dir_SQ = '$(subst ','\'',$(man_dir))' +libdir ?= $(prefix)/$(libdir_relative) +libdir_SQ = '$(subst ','\'',$(libdir))' +includedir_relative ?= include/tracefs +includedir ?= $(prefix)/$(includedir_relative) +includedir_SQ = '$(subst ','\'',$(includedir))' +pkgconfig_dir ?= $(word 1,$(shell $(PKG_CONFIG) \ + --variable pc_path pkg-config | tr ":" " ")) + +TEST_LIBTRACEEVENT = $(shell sh -c "$(PKG_CONFIG) --atleast-version $(LIBTRACEEVENT_MIN_VERSION) libtraceevent > /dev/null 2>&1 && echo y") + +ifeq ("$(TEST_LIBTRACEEVENT)", "y") +LIBTRACEEVENT_INCLUDES = $(shell sh -c "$(PKG_CONFIG) --cflags libtraceevent") +LIBTRACEEVENT_LIBS = $(shell sh -c "$(PKG_CONFIG) --libs libtraceevent") +else + ifneq ($(MAKECMDGOALS),clean) + $(error libtraceevent.so minimum version of $(LIBTRACEEVENT_MIN_VERSION) not installed) + endif +endif + +etcdir ?= /etc +etcdir_SQ = '$(subst ','\'',$(etcdir))' + +export man_dir man_dir_SQ html_install html_install_SQ INSTALL +export img_install img_install_SQ +export DESTDIR DESTDIR_SQ + +pound := \# + +HELP_DIR = -DHELP_DIR=$(html_install) +HELP_DIR_SQ = '$(subst ','\'',$(HELP_DIR))' +#' emacs highlighting gets confused by the above escaped quote. + +BASH_COMPLETE_DIR ?= $(etcdir)/bash_completion.d + +# copy a bit from Linux kbuild + +ifeq ("$(origin V)", "command line") + VERBOSE = $(V) +endif +ifndef VERBOSE + VERBOSE = 0 +endif + +SILENT := $(if $(findstring s,$(filter-out --%,$(MAKEFLAGS))),1) + +# $(call test-build, snippet, ret) -> ret if snippet compiles +# -> empty otherwise +test-build = $(if $(shell sh -c 'echo "$(1)" | \ + $(CC) -o /dev/null -c -x c - > /dev/null 2>&1 && echo y'), $2) + +ifeq ("$(origin O)", "command line") + + saved-output := $(O) + BUILD_OUTPUT := $(shell cd $(O) && /bin/pwd) + $(if $(BUILD_OUTPUT),, \ + $(error output directory "$(saved-output)" does not exist)) + +else + BUILD_OUTPUT = $(CURDIR) +endif + +srctree := $(if $(BUILD_SRC),$(BUILD_SRC),$(CURDIR)) +objtree := $(BUILD_OUTPUT) +src := $(srctree) +obj := $(objtree) +bdir := $(obj)/lib + +export prefix src obj bdir + +LIBTRACEFS_STATIC = $(bdir)/libtracefs.a +LIBTRACEFS_SHARED = $(bdir)/libtracefs.so.$(TRACEFS_VERSION) + +LIBTRACEFS_SHARED_SO = $(bdir)/libtracefs.so +LIBTRACEFS_SHARED_VERSION = $(bdir)/libtracefs.so.$(TFS_VERSION) + +PKG_CONFIG_SOURCE_FILE = libtracefs.pc +PKG_CONFIG_FILE := $(addprefix $(obj)/,$(PKG_CONFIG_SOURCE_FILE)) + +LPTHREAD ?= -lpthread +LIBS = $(LIBTRACEEVENT_LIBS) $(LPTHREAD) + +export LIBS +export LIBTRACEFS_STATIC LIBTRACEFS_SHARED +export LIBTRACEEVENT_LIBS LIBTRACEEVENT_INCLUDES +export LIBTRACEFS_SHARED_SO LIBTRACEFS_SHARED_VERSION + +export Q SILENT VERBOSE EXT + +# Include the utils +include scripts/utils.mk + +INCLUDES = -I$(src)/include +INCLUDES += -I$(src)/include/tracefs + +include $(src)/scripts/features.mk + +# Set compile option CFLAGS if not set elsewhere +CFLAGS ?= -g -Wall +CPPFLAGS ?= +LDFLAGS ?= + +CUNIT_INSTALLED := $(shell if (printf "$(pound)include <CUnit/Basic.h>\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 + +export CFLAGS +export INCLUDES + +# Append required CFLAGS +override CFLAGS += -D_GNU_SOURCE $(LIBTRACEEVENT_INCLUDES) $(INCLUDES) + +# Make sure 32 bit stat() works on large file systems +override CFLAGS += -D_FILE_OFFSET_BITS=64 + +all: all_cmd + +LIB_TARGET = libtracefs.a libtracefs.so.$(TRACEFS_VERSION) +LIB_INSTALL = libtracefs.a libtracefs.so* +LIB_INSTALL := $(addprefix $(bdir)/,$(LIB_INSTALL)) + +TARGETS = libtracefs.so libtracefs.a + +all_cmd: $(TARGETS) $(PKG_CONFIG_FILE) + +libtracefs.a: $(bdir) $(LIBTRACEFS_STATIC) +libtracefs.so: $(bdir) $(LIBTRACEFS_SHARED) + +libs: libtracefs.a libtracefs.so + +VALGRIND = $(shell which valgrind) +UTEST_DIR = utest +UTEST_BINARY = trace-utest + +test: force $(LIBTRACEFS_STATIC) +ifneq ($(CUNIT_INSTALLED),1) + $(error CUnit framework not installed, cannot build unit tests)) +endif + $(Q)$(call descend,$(src)/$(UTEST_DIR),$@) + +test_mem: test +ifeq (, $(VALGRIND)) + $(error "No valgrind in $(PATH), cannot run memory test") +endif +ifneq ($(shell id -u), 0) + $(error "The unit test should be run as root, as it reuqires full access to tracefs") +endif + CK_FORK=no LD_LIBRARY_PATH=$(bdir) $(VALGRIND) \ + --show-leak-kinds=all --leak-resolution=high \ + --leak-check=full --show-possibly-lost=yes \ + --track-origins=yes -s \ + $(src)/$(UTEST_DIR)/$(UTEST_BINARY) + +define find_tag_files + find $(src) -name '\.pc' -prune -o -name '*\.[ch]' -print -o -name '*\.[ch]pp' \ + ! -name '\.#' -print +endef + +define do_make_pkgconfig_file + cp -f ${PKG_CONFIG_SOURCE_FILE}.template ${PKG_CONFIG_FILE}; \ + sed -i "s|INSTALL_PREFIX|${1}|g" ${PKG_CONFIG_FILE}; \ + sed -i "s|LIB_VERSION|${TRACEFS_VERSION}|g" ${PKG_CONFIG_FILE}; \ + sed -i "s|LIB_DIR|${libdir_relative}|g" ${PKG_CONFIG_FILE}; \ + sed -i "s|HEADER_DIR|$(includedir_relative)|g" ${PKG_CONFIG_FILE}; \ + sed -i "s|LIBTRACEEVENT_MIN|$(LIBTRACEEVENT_MIN_VERSION)|g" ${PKG_CONFIG_FILE}; +endef + +BUILD_PREFIX := $(BUILD_OUTPUT)/build_prefix + +VERSION_FILE = tfs_version.h + +$(BUILD_PREFIX): force + $(Q)$(call build_prefix,$(prefix)) + +$(PKG_CONFIG_FILE) : ${PKG_CONFIG_SOURCE_FILE}.template $(BUILD_PREFIX) $(VERSION_FILE) + $(Q) $(call do_make_pkgconfig_file,$(prefix)) + +VIM_TAGS = $(obj)/tags +EMACS_TAGS = $(obj)/TAGS +CSCOPE_TAGS = $(obj)/cscope + +$(VIM_TAGS): force + $(RM) $@ + $(call find_tag_files) | (cd $(obj) && xargs ctags --extra=+f --c-kinds=+px) + +$(EMACS_TAGS): force + $(RM) $@ + $(call find_tag_files) | (cd $(obj) && xargs etags) + +$(CSCOPE_TAGS): force + $(RM) $(obj)/cscope* + $(call find_tag_files) | cscope -b -q + +tags: $(VIM_TAGS) +TAGS: $(EMACS_TAGS) +cscope: $(CSCOPE_TAGS) + +ifeq ("$(DESTDIR)", "") +# If DESTDIR is not defined, then test if after installing the library +# and running ldconfig, if the library is visible by ld.so. +# If not, add the path to /etc/ld.so.conf.d/trace.conf and run ldconfig again. +define install_ld_config + if $(LDCONFIG); then \ + if ! grep -q "^$(libdir)$$" $(LD_SO_CONF_PATH)/* ; then \ + $(CC) -o $(objtree)/test $(srctree)/test.c -I $(includedir_SQ) \ + -L $(libdir_SQ) -ltracefs &> /dev/null; \ + if ! $(objtree)/test &> /dev/null; then \ + $(call print_install, trace.conf, $(LD_SO_CONF_PATH)) \ + echo $(libdir_SQ) >> $(LD_SO_CONF_PATH)/trace.conf; \ + $(LDCONFIG); \ + fi; \ + $(RM) $(objtree)/test; \ + fi; \ + fi +endef +else +# If installing to a location for another machine or package, do not bother +# with running ldconfig. +define install_ld_config +endef +endif # DESTDIR = "" + +install_libs: libs install_pkgconfig + $(Q)$(call do_install,$(LIBTRACEFS_SHARED),$(libdir_SQ)); \ + cp -fpR $(LIB_INSTALL) $(DESTDIR)$(libdir_SQ) + $(Q)$(call do_install,$(src)/include/tracefs.h,$(includedir_SQ),644) + $(Q)$(call install_ld_config) + +install: install_libs + +install_pkgconfig: $(PKG_CONFIG_FILE) + $(Q)$(call , $(PKG_CONFIG_FILE)) \ + $(call do_install_pkgconfig_file,$(prefix)) + +doc: check_doc + $(Q)$(call descend,$(src)/Documentation,all) + +doc_clean: + $(Q)$(call descend,$(src)/Documentation,clean) + +install_doc: + $(Q)$(call descend,$(src)/Documentation,install) + +check_doc: force + $(Q)$(src)/check-manpages.sh $(src)/Documentation + +define build_uninstall_script + $(Q)mkdir $(BUILD_OUTPUT)/tmp_build + $(Q)$(MAKE) -C $(src) DESTDIR=$(BUILD_OUTPUT)/tmp_build/ O=$(BUILD_OUTPUT) $1 > /dev/null + $(Q)find $(BUILD_OUTPUT)/tmp_build ! -type d -printf "%P\n" > $(BUILD_OUTPUT)/build_$2 + $(Q)$(RM) -rf $(BUILD_OUTPUT)/tmp_build +endef + +build_uninstall: $(BUILD_PREFIX) + $(call build_uninstall_script,install,uninstall) + +$(BUILD_OUTPUT)/build_uninstall: build_uninstall + +define uninstall_file + if [ -f $(DESTDIR)/$1 -o -h $(DESTDIR)/$1 ]; then \ + $(call print_uninstall,$(DESTDIR)/$1)$(RM) $(DESTDIR)/$1; \ + fi; +endef + +uninstall: $(BUILD_OUTPUT)/build_uninstall + @$(foreach file,$(shell cat $(BUILD_OUTPUT)/build_uninstall),$(call uninstall_file,$(file))) + +PHONY += force +force: + +# Declare the contents of the .PHONY variable as phony. We keep that +# information in a variable so we can use it in if_changed and friends. +.PHONY: $(PHONY) + +DEFAULT_TARGET = $(LIBTRACEFS_STATIC) + +OBJS = +OBJS += tracefs-utils.o +OBJS += tracefs-instance.o +OBJS += tracefs-events.o + +OBJS := $(OBJS:%.o=$(bdir)/%.o) + +all: $(DEFAULT_TARGET) + +$(bdir): + @mkdir -p $(bdir) + +VERSION = $(TFS_VERSION) +PATCHLEVEL = $(TFS_PATCHLEVEL) +EXTRAVERSION = $(TFS_EXTRAVERSION) + +define make_version.h + (echo '/* This file is automatically generated. Do not modify. */'; \ + echo \#define VERSION_CODE $(shell \ + expr $(VERSION) \* 256 + $(PATCHLEVEL)); \ + echo '#define EXTRAVERSION ' $(EXTRAVERSION); \ + echo '#define VERSION_STRING "'$(VERSION).$(PATCHLEVEL).$(EXTRAVERSION)'"'; \ + ) > $1 +endef + +define update_version.h + ($(call make_version.h, $@.tmp); \ + if [ -r $@ ] && cmp -s $@ $@.tmp; then \ + rm -f $@.tmp; \ + else \ + echo ' UPDATE $@'; \ + mv -f $@.tmp $@; \ + fi); +endef + +$(VERSION_FILE): force + $(Q)$(call update_version.h) + +$(LIBTRACEFS_STATIC): force + $(Q)$(call descend,$(src)/src,$@) + +$(bdir)/libtracefs.so.$(TRACEFS_VERSION): force + $(Q)$(call descend,$(src)/src,libtracefs.so) + +samples/sqlhist: libtracefs.a + $(Q)$(call descend,$(src)/samples,sqlhist) + +sqlhist: samples/sqlhist + +samples: libtracefs.a force + $(Q)$(call descend,$(src)/samples,all) + +clean: + $(Q)$(call descend_clean,utest) + $(Q)$(call descend_clean,src) + $(Q)$(call descend_clean,samples) + $(Q)$(call do_clean, \ + $(TARGETS) $(bdir)/*.a $(bdir)/*.so $(bdir)/*.so.* $(bdir)/*.o $(bdir)/.*.d \ + $(PKG_CONFIG_FILE) \ + $(VERSION_FILE) \ + $(BUILD_PREFIX)) + +.PHONY: clean + +# libtracefs.a and libtracefs.so would concurrently enter the same directory - +# a recipe for collisions. +.NOTPARALLEL: @@ -0,0 +1,56 @@ + +The official repository is here: + + https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git/ + +This repository requires libtraceevent to be installed: + + https://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git/ + + +To build: + + make; + sudo make install; + +To build in a specific directory outside of the source directory: + + make O=/path/to/build; sudo make O=/path/to/build + + Note that the path needs to exist before building. + +To set the install path (the expected final location): + + make prefix=/usr; sudo make O=/path/to/build + +To install in a directory not for the local system (for use to move +to another machine): + + make DESTDIR=/path/to/dest/ install + + Note, if you have write permission to the DESTDIR, then there is + no reason to use sudo or switch to root. + + Note, DESTDIR must end with '/', otherwise the files will be appended + to the path, which will most likely have unwanted results. + +Contributions: + + For questions about the use of the library, please send email to: + + linux-trace-users@vger.kernel.org + + Subscribe: http://vger.kernel.org/vger-lists.html#linux-trace-users + Archives: https://lore.kernel.org/linux-trace-users/ + + For contributions to development, please send patches to: + + linux-trace-devel@vger.kernel.org + + Subscribe: http://vger.kernel.org/vger-lists.html#linux-trace-devel + Archives: https://lore.kernel.org/linux-trace-devel/ + + Note, this project follows the style of submitting patches as described + by the Linux kernel. + + https://www.kernel.org/doc/html/v5.4/process/submitting-patches.html diff --git a/check-manpages.sh b/check-manpages.sh new file mode 100755 index 0000000..776365c --- /dev/null +++ b/check-manpages.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# SPDX-License-Identifier: LGPL-2.1 +# Copyright (C) 2022, Google Inc, Steven Rostedt <rostedt@goodmis.org> +# +# This checks if any function is listed in a man page that is not listed +# in the main man page. + +if [ $# -lt 1 ]; then + echo "usage: check-manpages man-page-path" + exit 1 +fi + +cd $1 + +MAIN=libtracefs +MAIN_FILE=${MAIN}.txt + +PROCESSED="" + +# Ignore man pages that do not contain functions +IGNORE="libtracefs-options.txt" + +for man in ${MAIN}-*.txt; do + + for a in `sed -ne '/^NAME/,/^SYNOP/{/^[a-z]/{s/, *$//;s/,/\n/g;s/ //g;s/-.*$/-/;/-/{s/-//p;q};p}}' $man`; do + if [ "${PROCESSED/:${a} /}" != "${PROCESSED}" ]; then + P="${PROCESSED/:${a} */}" + echo "Found ${a} in ${man} and in ${P/* /}" + fi + PROCESSED="${man}:${a} ${PROCESSED}" + if [ "${IGNORE/$man/}" != "${IGNORE}" ]; then + continue + fi + if ! grep -q '\*'${a}'\*' $MAIN_FILE; then + if [ "$last" == "" ]; then + echo + fi + if [ "$last" != "$man" ]; then + echo "Missing functions from $MAIN_FILE that are in $man" + last=$man + fi + echo " ${a}" + fi + done +done + +DEPRECATED="*tracefs_event_append_filter* *tracefs_event_verify_filter*" + +sed -ne 's/^[a-z].*[ \*]\([a-z_][a-z_]*\)(.*/\1/p' -e 's/^\([a-z_][a-z_]*\)(.*/\1/p' ../include/tracefs.h | while read f; do + if ! grep -q '\*'${f}'\*' $MAIN_FILE; then + if [ "${DEPRECATED/\*$f\*/}" != "${DEPRECATED}" ]; then + continue; + fi + if [ "$last" == "" ]; then + echo + echo "Missing functions from $MAIN_FILE that are in tracefs.h" + last=$f + fi + echo " ${f}" + fi +done diff --git a/include/tracefs-local.h b/include/tracefs-local.h new file mode 100644 index 0000000..2007d26 --- /dev/null +++ b/include/tracefs-local.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ +/* + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#ifndef _TRACE_FS_LOCAL_H +#define _TRACE_FS_LOCAL_H + +#include <pthread.h> + +#define __hidden __attribute__((visibility ("hidden"))) +#define __weak __attribute__((weak)) + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) + +/* Will cause a division by zero warning if cond is true */ +#define BUILD_BUG_ON(cond) \ + do { if (!(1/!(cond))) { } } while (0) + +#define HASH_BITS 10 + +struct tracefs_options_mask { + unsigned long long mask; +}; + +struct follow_event { + struct tep_event *event; + void *callback_data; + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *); +}; + +struct tracefs_instance { + struct tracefs_options_mask supported_opts; + struct tracefs_options_mask enabled_opts; + struct follow_event *followers; + struct follow_event *missed_followers; + char *trace_dir; + char *name; + pthread_mutex_t lock; + int ref; + int flags; + int ftrace_filter_fd; + int ftrace_notrace_fd; + int ftrace_marker_fd; + int ftrace_marker_raw_fd; + int nr_followers; + int nr_missed_followers; + bool pipe_keep_going; + bool iterate_keep_going; +}; + +extern pthread_mutex_t toplevel_lock; + +static inline pthread_mutex_t *trace_get_lock(struct tracefs_instance *instance) +{ + return instance ? &instance->lock : &toplevel_lock; +} + +void trace_put_instance(struct tracefs_instance *instance); +int trace_get_instance(struct tracefs_instance *instance); + +/* Can be overridden */ +void tracefs_warning(const char *fmt, ...); + +int str_read_file(const char *file, char **buffer, bool warn); +char *trace_append_file(const char *dir, const char *name); +char *trace_find_tracing_dir(bool debugfs); + +#ifndef ACCESSPERMS +#define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO) /* 0777 */ +#endif + +#ifndef ALLPERMS +#define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO) /* 07777 */ +#endif + +#ifndef DEFFILEMODE +#define DEFFILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) /* 0666*/ +#endif + +struct tracefs_options_mask * +supported_opts_mask(struct tracefs_instance *instance); + +struct tracefs_options_mask * +enabled_opts_mask(struct tracefs_instance *instance); + +char **trace_list_create_empty(void); +int trace_list_pop(char **list); + +char *append_string(char *str, const char *delim, const char *add); +int trace_test_state(int state); +bool trace_verify_event_field(struct tep_event *event, + const char *field_name, + const struct tep_format_field **ptr_field); +int trace_append_filter(char **filter, unsigned int *state, + unsigned int *open_parens, + struct tep_event *event, + enum tracefs_filter type, + const char *field_name, + enum tracefs_compare compare, + const char *val); + +struct tracefs_synth *synth_init_from(struct tep_handle *tep, + const char *start_system, + const char *start_event); + +#define HIST_COUNTER_TYPE (TRACEFS_HIST_KEY_MAX + 100) +int synth_add_start_field(struct tracefs_synth *synth, + const char *start_field, + const char *name, + enum tracefs_hist_key_type type, int cnt); + +/* Internal interface for ftrace dynamic events */ + +struct tracefs_dynevent { + char *trace_file; + char *prefix; + char *system; + char *event; + char *address; + char *format; + enum tracefs_dynevent_type type; +}; + +struct tracefs_dynevent * +dynevent_alloc(enum tracefs_dynevent_type type, const char *system, + const char *event, const char *address, const char *format); +int dynevent_get_count(unsigned int types, const char *system); + +int trace_load_events(struct tep_handle *tep, + const char *tracing_dir, const char *system); +int trace_rescan_events(struct tep_handle *tep, + const char *tracing_dir, const char *system); +struct tep_event *get_tep_event(struct tep_handle *tep, + const char *system, const char *name); + +unsigned int quick_hash(const char *str); + +#endif /* _TRACE_FS_LOCAL_H */ diff --git a/include/tracefs.h b/include/tracefs.h new file mode 100644 index 0000000..3547b5a --- /dev/null +++ b/include/tracefs.h @@ -0,0 +1,637 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ +/* + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#ifndef _TRACE_FS_H +#define _TRACE_FS_H + +#include <fcntl.h> +#include <sched.h> +#include <event-parse.h> + +char *tracefs_get_tracing_file(const char *name); +void tracefs_put_tracing_file(char *name); + +/* the returned string must *not* be freed */ +const char *tracefs_tracing_dir(void); +const char *tracefs_debug_dir(void); +int tracefs_set_tracing_dir(char *tracing_dir); +int tracefs_tracing_dir_is_mounted(bool mount, const char **path); + +/* ftrace instances */ +struct tracefs_instance; + +void tracefs_instance_free(struct tracefs_instance *instance); +struct tracefs_instance *tracefs_instance_create(const char *name); +struct tracefs_instance *tracefs_instance_alloc(const char *tracing_dir, + const char *name); +int tracefs_instance_destroy(struct tracefs_instance *instance); +bool tracefs_instance_is_new(struct tracefs_instance *instance); +const char *tracefs_instance_get_name(struct tracefs_instance *instance); +const char *tracefs_instance_get_trace_dir(struct tracefs_instance *instance); +char * +tracefs_instance_get_file(struct tracefs_instance *instance, const char *file); +char *tracefs_instance_get_dir(struct tracefs_instance *instance); +int tracefs_instance_file_write(struct tracefs_instance *instance, + const char *file, const char *str); +int tracefs_instance_file_append(struct tracefs_instance *instance, + const char *file, const char *str); +int tracefs_instance_file_clear(struct tracefs_instance *instance, + const char *file); +char *tracefs_instance_file_read(struct tracefs_instance *instance, + const char *file, int *psize); +int tracefs_instance_file_read_number(struct tracefs_instance *instance, + const char *file, long long *res); +int tracefs_instance_file_open(struct tracefs_instance *instance, + const char *file, int mode); +int tracefs_instances_walk(int (*callback)(const char *, void *), void *context); +int tracefs_instance_set_affinity_set(struct tracefs_instance *instance, + cpu_set_t *set, size_t set_size); +int tracefs_instance_set_affinity_raw(struct tracefs_instance *instance, + const char *mask); +int tracefs_instance_set_affinity(struct tracefs_instance *instance, + const char *cpu_str); +char *tracefs_instance_get_affinity(struct tracefs_instance *instance); +char *tracefs_instance_get_affinity_raw(struct tracefs_instance *instance); +int tracefs_instance_get_affinity_set(struct tracefs_instance *instance, + cpu_set_t *set, size_t set_size); +ssize_t tracefs_instance_get_buffer_size(struct tracefs_instance *instance, int cpu); +int tracefs_instance_set_buffer_size(struct tracefs_instance *instance, size_t size, int cpu); +char **tracefs_instances(const char *regex); + +bool tracefs_instance_exists(const char *name); +bool tracefs_file_exists(struct tracefs_instance *instance, const char *name); +bool tracefs_dir_exists(struct tracefs_instance *instance, const char *name); + +int tracefs_trace_is_on(struct tracefs_instance *instance); +int tracefs_trace_on(struct tracefs_instance *instance); +int tracefs_trace_off(struct tracefs_instance *instance); +int tracefs_trace_on_fd(int fd); +int tracefs_trace_off_fd(int fd); + +enum tracefs_enable_state { + TRACEFS_ERROR = -1, + TRACEFS_ALL_DISABLED = 0, + TRACEFS_ALL_ENABLED = 1, + TRACEFS_SOME_ENABLED = 2, +}; + +int tracefs_event_enable(struct tracefs_instance *instance, const char *system, const char *event); +int tracefs_event_disable(struct tracefs_instance *instance, const char *system, const char *event); +enum tracefs_enable_state tracefs_event_is_enabled(struct tracefs_instance *instance, + const char *system, const char *event); + +char *tracefs_error_last(struct tracefs_instance *instance); +char *tracefs_error_all(struct tracefs_instance *instance); +int tracefs_error_clear(struct tracefs_instance *instance); + +void tracefs_list_free(char **list); +char **tracefs_list_add(char **list, const char *string); +int tracefs_list_size(char **list); + +bool tracefs_tracer_available(const char *tracing_dir, const char *tracer); + +/** + * tracefs_trace_on_get_fd - Get a file descriptor of "tracing_on" in given instance + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns -1 in case of an error, or a valid file descriptor to "tracing_on" + * file for reading and writing.The returned FD must be closed with close(). + */ +static inline int tracefs_trace_on_get_fd(struct tracefs_instance *instance) +{ + return tracefs_instance_file_open(instance, "tracing_on", O_RDWR); +} + +/* trace print string*/ +int tracefs_print_init(struct tracefs_instance *instance); +int tracefs_printf(struct tracefs_instance *instance, const char *fmt, ...); +int tracefs_vprintf(struct tracefs_instance *instance, const char *fmt, va_list ap); +void tracefs_print_close(struct tracefs_instance *instance); + +/* trace write binary data*/ +int tracefs_binary_init(struct tracefs_instance *instance); +int tracefs_binary_write(struct tracefs_instance *instance, void *data, int len); +void tracefs_binary_close(struct tracefs_instance *instance); + +/* events */ +char **tracefs_event_systems(const char *tracing_dir); +char **tracefs_system_events(const char *tracing_dir, const char *system); +int tracefs_iterate_raw_events(struct tep_handle *tep, + struct tracefs_instance *instance, + cpu_set_t *cpus, int cpu_size, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_context); +void tracefs_iterate_stop(struct tracefs_instance *instance); +int tracefs_follow_event(struct tep_handle *tep, struct tracefs_instance *instance, + const char *system, const char *event_name, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_data); +int tracefs_follow_missed_events(struct tracefs_instance *instance, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_data); + +char *tracefs_event_get_file(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file); +char *tracefs_event_file_read(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file, int *psize); +int tracefs_event_file_write(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file, const char *str); +int tracefs_event_file_append(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file, const char *str); +int tracefs_event_file_clear(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file); +bool tracefs_event_file_exists(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file); + +char **tracefs_tracers(const char *tracing_dir); + +struct tep_handle *tracefs_local_events(const char *tracing_dir); +struct tep_handle *tracefs_local_events_system(const char *tracing_dir, + const char * const *sys_names); +int tracefs_fill_local_events(const char *tracing_dir, + struct tep_handle *tep, int *parsing_failures); + +int tracefs_load_cmdlines(const char *tracing_dir, struct tep_handle *tep); + +char *tracefs_get_clock(struct tracefs_instance *instance); + +enum tracefs_option_id { + TRACEFS_OPTION_INVALID = 0, + TRACEFS_OPTION_ANNOTATE, + TRACEFS_OPTION_BIN, + TRACEFS_OPTION_BLK_CGNAME, + TRACEFS_OPTION_BLK_CGROUP, + TRACEFS_OPTION_BLK_CLASSIC, + TRACEFS_OPTION_BLOCK, + TRACEFS_OPTION_CONTEXT_INFO, + TRACEFS_OPTION_DISABLE_ON_FREE, + TRACEFS_OPTION_DISPLAY_GRAPH, + TRACEFS_OPTION_EVENT_FORK, + TRACEFS_OPTION_FGRAPH_ABSTIME, + TRACEFS_OPTION_FGRAPH_CPU, + TRACEFS_OPTION_FGRAPH_DURATION, + TRACEFS_OPTION_FGRAPH_IRQS, + TRACEFS_OPTION_FGRAPH_OVERHEAD, + TRACEFS_OPTION_FGRAPH_OVERRUN, + TRACEFS_OPTION_FGRAPH_PROC, + TRACEFS_OPTION_FGRAPH_TAIL, + TRACEFS_OPTION_FUNC_STACKTRACE, + TRACEFS_OPTION_FUNCTION_FORK, + TRACEFS_OPTION_FUNCTION_TRACE, + TRACEFS_OPTION_GRAPH_TIME, + TRACEFS_OPTION_HEX, + TRACEFS_OPTION_IRQ_INFO, + TRACEFS_OPTION_LATENCY_FORMAT, + TRACEFS_OPTION_MARKERS, + TRACEFS_OPTION_OVERWRITE, + TRACEFS_OPTION_PAUSE_ON_TRACE, + TRACEFS_OPTION_PRINTK_MSG_ONLY, + TRACEFS_OPTION_PRINT_PARENT, + TRACEFS_OPTION_RAW, + TRACEFS_OPTION_RECORD_CMD, + TRACEFS_OPTION_RECORD_TGID, + TRACEFS_OPTION_SLEEP_TIME, + TRACEFS_OPTION_STACKTRACE, + TRACEFS_OPTION_SYM_ADDR, + TRACEFS_OPTION_SYM_OFFSET, + TRACEFS_OPTION_SYM_USEROBJ, + TRACEFS_OPTION_TRACE_PRINTK, + TRACEFS_OPTION_USERSTACKTRACE, + TRACEFS_OPTION_VERBOSE, +}; +#define TRACEFS_OPTION_MAX (TRACEFS_OPTION_VERBOSE + 1) + +struct tracefs_options_mask; +bool tracefs_option_mask_is_set(const struct tracefs_options_mask *options, + enum tracefs_option_id id); +const struct tracefs_options_mask *tracefs_options_get_supported(struct tracefs_instance *instance); +bool tracefs_option_is_supported(struct tracefs_instance *instance, enum tracefs_option_id id); +const struct tracefs_options_mask *tracefs_options_get_enabled(struct tracefs_instance *instance); +bool tracefs_option_is_enabled(struct tracefs_instance *instance, enum tracefs_option_id id); +int tracefs_option_enable(struct tracefs_instance *instance, enum tracefs_option_id id); +int tracefs_option_disable(struct tracefs_instance *instance, enum tracefs_option_id id); +const char *tracefs_option_name(enum tracefs_option_id id); +enum tracefs_option_id tracefs_option_id(const char *name); + +/* + * RESET - Reset on opening filter file (O_TRUNC) + * CONTINUE - Do not close filter file on return. + * FUTURE - For kernels that support this feature, enable filters for + * a module that has yet to be loaded. + */ +enum { + TRACEFS_FL_RESET = (1 << 0), + TRACEFS_FL_CONTINUE = (1 << 1), + TRACEFS_FL_FUTURE = (1 << 2), +}; + +int tracefs_function_filter(struct tracefs_instance *instance, const char *filter, + const char *module, unsigned int flags); +int tracefs_function_notrace(struct tracefs_instance *instance, const char *filter, + const char *module, unsigned int flags); +int tracefs_filter_functions(const char *filter, const char *module, char ***list); + + +/* Control library logs */ +void tracefs_set_loglevel(enum tep_loglevel level); + +enum tracefs_tracers { + TRACEFS_TRACER_NOP = 0, + TRACEFS_TRACER_CUSTOM, + TRACEFS_TRACER_FUNCTION, + TRACEFS_TRACER_FUNCTION_GRAPH, + TRACEFS_TRACER_IRQSOFF, + TRACEFS_TRACER_PREEMPTOFF, + TRACEFS_TRACER_PREEMPTIRQSOFF, + TRACEFS_TRACER_WAKEUP, + TRACEFS_TRACER_WAKEUP_RT, + TRACEFS_TRACER_WAKEUP_DL, + TRACEFS_TRACER_MMIOTRACE, + TRACEFS_TRACER_HWLAT, + TRACEFS_TRACER_BRANCH, + TRACEFS_TRACER_BLOCK, +}; + +int tracefs_tracer_set(struct tracefs_instance *instance, enum tracefs_tracers tracer, ...); + +int tracefs_tracer_clear(struct tracefs_instance *instance); + +ssize_t tracefs_trace_pipe_stream(int fd, struct tracefs_instance *instance, int flags); +ssize_t tracefs_trace_pipe_print(struct tracefs_instance *instance, int flags); +void tracefs_trace_pipe_stop(struct tracefs_instance *instance); + +/* Dynamic events */ +struct tracefs_dynevent; +enum tracefs_dynevent_type { + TRACEFS_DYNEVENT_UNKNOWN = 0, + TRACEFS_DYNEVENT_KPROBE = 1 << 0, + TRACEFS_DYNEVENT_KRETPROBE = 1 << 1, + TRACEFS_DYNEVENT_UPROBE = 1 << 2, + TRACEFS_DYNEVENT_URETPROBE = 1 << 3, + TRACEFS_DYNEVENT_EPROBE = 1 << 4, + TRACEFS_DYNEVENT_SYNTH = 1 << 5, + TRACEFS_DYNEVENT_MAX = 1 << 6, +}; + +#define TRACEFS_DYNEVENT_ALL 0xFFFFFFFF + +int tracefs_dynevent_create(struct tracefs_dynevent *devent); +int tracefs_dynevent_destroy(struct tracefs_dynevent *devent, bool force); +int tracefs_dynevent_destroy_all(unsigned int types, bool force); +void tracefs_dynevent_free(struct tracefs_dynevent *devent); +void tracefs_dynevent_list_free(struct tracefs_dynevent **events); +struct tracefs_dynevent ** +tracefs_dynevent_get_all(unsigned int types, const char *system); +struct tracefs_dynevent * +tracefs_dynevent_get(enum tracefs_dynevent_type type, const char *system, const char *event); +enum tracefs_dynevent_type +tracefs_dynevent_info(struct tracefs_dynevent *dynevent, char **system, + char **event, char **prefix, char **addr, char **format); +struct tep_event * +tracefs_dynevent_get_event(struct tep_handle *tep, struct tracefs_dynevent *dynevent); + +struct tracefs_dynevent * +tracefs_eprobe_alloc(const char *system, const char *event, + const char *target_system, const char *target_event, const char *fetchargs); +struct tracefs_dynevent * +tracefs_uprobe_alloc(const char *system, const char *event, + const char *file, unsigned long long offset, const char *fetchargs); +struct tracefs_dynevent * +tracefs_uretprobe_alloc(const char *system, const char *event, + const char *file, unsigned long long offset, const char *fetchargs); + +struct tracefs_dynevent * +tracefs_kprobe_alloc(const char *system, const char *event, const char *addr, const char *format); +struct tracefs_dynevent * +tracefs_kretprobe_alloc(const char *system, const char *event, + const char *addr, const char *format, unsigned int max); +int tracefs_kprobe_raw(const char *system, const char *event, + const char *addr, const char *format); +int tracefs_kretprobe_raw(const char *system, const char *event, + const char *addr, const char *format); + +enum tracefs_hist_key_type { + TRACEFS_HIST_KEY_NORMAL = 0, + TRACEFS_HIST_KEY_HEX, + TRACEFS_HIST_KEY_SYM, + TRACEFS_HIST_KEY_SYM_OFFSET, + TRACEFS_HIST_KEY_SYSCALL, + TRACEFS_HIST_KEY_EXECNAME, + TRACEFS_HIST_KEY_LOG, + TRACEFS_HIST_KEY_USECS, + TRACEFS_HIST_KEY_BUCKETS, + TRACEFS_HIST_KEY_MAX +}; + +enum tracefs_hist_sort_direction { + TRACEFS_HIST_SORT_ASCENDING, + TRACEFS_HIST_SORT_DESCENDING, +}; + +#define TRACEFS_HIST_TIMESTAMP "common_timestamp" +#define TRACEFS_HIST_TIMESTAMP_USECS "common_timestamp.usecs" +#define TRACEFS_HIST_CPU "cpu" + +#define TRACEFS_HIST_COUNTER "__COUNTER__" + +#define TRACEFS_HIST_HITCOUNT "hitcount" + +struct tracefs_hist; + +enum tracefs_hist_command { + TRACEFS_HIST_CMD_START = 0, + TRACEFS_HIST_CMD_PAUSE, + TRACEFS_HIST_CMD_CONT, + TRACEFS_HIST_CMD_CLEAR, + TRACEFS_HIST_CMD_DESTROY, +}; + +enum tracefs_filter { + TRACEFS_FILTER_COMPARE, + TRACEFS_FILTER_AND, + TRACEFS_FILTER_OR, + TRACEFS_FILTER_NOT, + TRACEFS_FILTER_OPEN_PAREN, + TRACEFS_FILTER_CLOSE_PAREN, +}; + +enum tracefs_compare { + TRACEFS_COMPARE_EQ, + TRACEFS_COMPARE_NE, + TRACEFS_COMPARE_GT, + TRACEFS_COMPARE_GE, + TRACEFS_COMPARE_LT, + TRACEFS_COMPARE_LE, + TRACEFS_COMPARE_RE, + TRACEFS_COMPARE_AND, +}; + +void tracefs_hist_free(struct tracefs_hist *hist); +struct tracefs_hist * +tracefs_hist_alloc(struct tep_handle *tep, + const char *system, const char *event_name, + const char *key, enum tracefs_hist_key_type type); +struct tracefs_hist * +tracefs_hist_alloc_2d(struct tep_handle *tep, + const char *system, const char *event_name, + const char *key1, enum tracefs_hist_key_type type1, + const char *key2, enum tracefs_hist_key_type type2); + +struct tracefs_hist_axis { + const char *key; + enum tracefs_hist_key_type type; +}; + +struct tracefs_hist_axis_cnt { + const char *key; + enum tracefs_hist_key_type type; + int cnt; +}; + +struct tracefs_hist * +tracefs_hist_alloc_nd(struct tep_handle *tep, + const char *system, const char *event_name, + struct tracefs_hist_axis *axes); +struct tracefs_hist * +tracefs_hist_alloc_nd_cnt(struct tep_handle *tep, + const char *system, const char *event_name, + struct tracefs_hist_axis_cnt *axes); +const char *tracefs_hist_get_name(struct tracefs_hist *hist); +const char *tracefs_hist_get_event(struct tracefs_hist *hist); +const char *tracefs_hist_get_system(struct tracefs_hist *hist); +int tracefs_hist_add_key(struct tracefs_hist *hist, const char *key, + enum tracefs_hist_key_type type); +int tracefs_hist_add_key_cnt(struct tracefs_hist *hist, const char *key, + enum tracefs_hist_key_type type, int cnt); +int tracefs_hist_add_value(struct tracefs_hist *hist, const char *value); +int tracefs_hist_add_sort_key(struct tracefs_hist *hist, + const char *sort_key); +int tracefs_hist_set_sort_key(struct tracefs_hist *hist, + const char *sort_key, ...); +int tracefs_hist_sort_key_direction(struct tracefs_hist *hist, + const char *sort_key, + enum tracefs_hist_sort_direction dir); +int tracefs_hist_add_name(struct tracefs_hist *hist, const char *name); +int tracefs_hist_append_filter(struct tracefs_hist *hist, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val); +int tracefs_hist_echo_cmd(struct trace_seq *seq, struct tracefs_instance *instance, + struct tracefs_hist *hist, enum tracefs_hist_command command); +int tracefs_hist_command(struct tracefs_instance *instance, + struct tracefs_hist *hist, enum tracefs_hist_command cmd); + +/** + * tracefs_hist_start - enable a histogram + * @instance: The instance the histogram will be in (NULL for toplevel) + * @hist: The histogram to start + * + * Starts executing a histogram. + * + * Returns 0 on success, -1 on error. + */ +static inline int tracefs_hist_start(struct tracefs_instance *instance, + struct tracefs_hist *hist) +{ + return tracefs_hist_command(instance, hist, 0); +} + +/** + * tracefs_hist_pause - pause a histogram + * @instance: The instance the histogram is in (NULL for toplevel) + * @hist: The histogram to pause + * + * Pause a histogram. + * + * Returns 0 on success, -1 on error. + */ +static inline int tracefs_hist_pause(struct tracefs_instance *instance, + struct tracefs_hist *hist) +{ + return tracefs_hist_command(instance, hist, TRACEFS_HIST_CMD_PAUSE); +} + +/** + * tracefs_hist_continue - continue a paused histogram + * @instance: The instance the histogram is in (NULL for toplevel) + * @hist: The histogram to continue + * + * Continue a histogram. + * + * Returns 0 on success, -1 on error. + */ +static inline int tracefs_hist_continue(struct tracefs_instance *instance, + struct tracefs_hist *hist) +{ + return tracefs_hist_command(instance, hist, TRACEFS_HIST_CMD_CONT); +} + +/** + * tracefs_hist_reset - clear a histogram + * @instance: The instance the histogram is in (NULL for toplevel) + * @hist: The histogram to reset + * + * Resets a histogram. + * + * Returns 0 on success, -1 on error. + */ +static inline int tracefs_hist_reset(struct tracefs_instance *instance, + struct tracefs_hist *hist) +{ + return tracefs_hist_command(instance, hist, TRACEFS_HIST_CMD_CLEAR); +} + +/** + * tracefs_hist_destroy - deletes a histogram (needs to be enabled again) + * @instance: The instance the histogram is in (NULL for toplevel) + * @hist: The histogram to delete + * + * Deletes (removes) a running histogram. This is different than + * clear, as clear only clears the data but the histogram still exists. + * This deletes the histogram and should be called before + * tracefs_hist_free() to clean up properly. + * + * Returns 0 on success, -1 on error. + */ +static inline int tracefs_hist_destroy(struct tracefs_instance *instance, + struct tracefs_hist *hist) +{ + return tracefs_hist_command(instance, hist, TRACEFS_HIST_CMD_DESTROY); +} + +struct tracefs_synth; + +/* + * DELTA_END - end_field - start_field + * DELTA_START - start_field - end_field + * ADD - start_field + end_field + */ +enum tracefs_synth_calc { + TRACEFS_SYNTH_DELTA_END, + TRACEFS_SYNTH_DELTA_START, + TRACEFS_SYNTH_ADD, +}; + +int tracefs_filter_string_append(struct tep_event *event, char **filter, + enum tracefs_filter type, + const char *field, enum tracefs_compare compare, + const char *val); +int tracefs_filter_string_verify(struct tep_event *event, const char *filter, + char **err); + +int tracefs_event_filter_apply(struct tracefs_instance *instance, + struct tep_event *event, const char *filter); + +int tracefs_event_filter_clear(struct tracefs_instance *instance, + struct tep_event *event); + +/** Deprecated do not use: Instead use tracefs_filter_string_append() **/ +int tracefs_event_append_filter(struct tep_event *event, char **filter, + enum tracefs_filter type, + const char *field, enum tracefs_compare compare, + const char *val); + +/** Deprecated do not use: Instead use tracefs_filter_string_verify() **/ +int tracefs_event_verify_filter(struct tep_event *event, const char *filter, + char **err); + +#define TRACEFS_TIMESTAMP "common_timestamp" +#define TRACEFS_TIMESTAMP_USECS "common_timestamp.usecs" + +enum tracefs_synth_handler { + TRACEFS_SYNTH_HANDLE_NONE = 0, + TRACEFS_SYNTH_HANDLE_MATCH, + TRACEFS_SYNTH_HANDLE_MAX, + TRACEFS_SYNTH_HANDLE_CHANGE, +}; + +const char *tracefs_synth_get_name(struct tracefs_synth *synth); +struct tracefs_synth *tracefs_synth_alloc(struct tep_handle *tep, + const char *name, + const char *start_system, + const char *start_event, + const char *end_system, + const char *end_event, + const char *start_match_field, + const char *end_match_field, + const char *match_name); +int tracefs_synth_add_match_field(struct tracefs_synth *synth, + const char *start_match_field, + const char *end_match_field, + const char *name); +int tracefs_synth_add_compare_field(struct tracefs_synth *synth, + const char *start_compare_field, + const char *end_compare_field, + enum tracefs_synth_calc calc, + const char *name); +int tracefs_synth_add_start_field(struct tracefs_synth *synth, + const char *start_field, + const char *name); +int tracefs_synth_add_end_field(struct tracefs_synth *synth, + const char *end_field, + const char *name); +int tracefs_synth_append_start_filter(struct tracefs_synth *synth, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val); +int tracefs_synth_append_end_filter(struct tracefs_synth *synth, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val); +int tracefs_synth_trace(struct tracefs_synth *synth, + enum tracefs_synth_handler type, const char *field); +int tracefs_synth_snapshot(struct tracefs_synth *synth, + enum tracefs_synth_handler type, const char *field); +int tracefs_synth_save(struct tracefs_synth *synth, + enum tracefs_synth_handler type, const char *field, + char **save_fields); +bool tracefs_synth_complete(struct tracefs_synth *synth); +struct tracefs_hist *tracefs_synth_get_start_hist(struct tracefs_synth *synth); +int tracefs_synth_create(struct tracefs_synth *synth); +int tracefs_synth_destroy(struct tracefs_synth *synth); +void tracefs_synth_free(struct tracefs_synth *synth); +int tracefs_synth_echo_cmd(struct trace_seq *seq, struct tracefs_synth *synth); +int tracefs_synth_raw_fmt(struct trace_seq *seq, struct tracefs_synth *synth); +const char *tracefs_synth_show_event(struct tracefs_synth *synth); +const char *tracefs_synth_show_start_hist(struct tracefs_synth *synth); +const char *tracefs_synth_show_end_hist(struct tracefs_synth *synth); + +struct tracefs_synth *tracefs_sql(struct tep_handle *tep, const char *name, + const char *sql_buffer, char **err); +struct tep_event * +tracefs_synth_get_event(struct tep_handle *tep, struct tracefs_synth *synth); + +struct tracefs_cpu; + +struct tracefs_cpu *tracefs_cpu_alloc_fd(int fd, int subbuf_size, bool nonblock); +struct tracefs_cpu *tracefs_cpu_open(struct tracefs_instance *instance, + int cpu, bool nonblock); +void tracefs_cpu_close(struct tracefs_cpu *tcpu); +void tracefs_cpu_free_fd(struct tracefs_cpu *tcpu); +int tracefs_cpu_read_size(struct tracefs_cpu *tcpu); +int tracefs_cpu_read(struct tracefs_cpu *tcpu, void *buffer, bool nonblock); +int tracefs_cpu_buffered_read(struct tracefs_cpu *tcpu, void *buffer, bool nonblock); +int tracefs_cpu_write(struct tracefs_cpu *tcpu, int wfd, bool nonblock); +int tracefs_cpu_stop(struct tracefs_cpu *tcpu); +int tracefs_cpu_flush(struct tracefs_cpu *tcpu, void *buffer); +int tracefs_cpu_flush_write(struct tracefs_cpu *tcpu, int wfd); +int tracefs_cpu_pipe(struct tracefs_cpu *tcpu, int wfd, bool nonblock); + +#endif /* _TRACE_FS_H */ diff --git a/libtracefs.pc.template b/libtracefs.pc.template new file mode 100644 index 0000000..09b335b --- /dev/null +++ b/libtracefs.pc.template @@ -0,0 +1,11 @@ +prefix=INSTALL_PREFIX +libdir=${prefix}/LIB_DIR +includedir=${prefix}/HEADER_DIR + +Name: libtracefs +URL: https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git/ +Description: Library for accessing ftrace file system +Version: LIB_VERSION +Requires: libtraceevent > LIBTRACEEVENT_MIN +Cflags: -I${includedir} +Libs: -L${libdir} -ltracefs diff --git a/samples/Makefile b/samples/Makefile new file mode 100644 index 0000000..743bddb --- /dev/null +++ b/samples/Makefile @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: LGPL-2.1 + +# +# The samples are pulled out of the examples used in the man pages +# that are located in the Documentation directory. +# + +include $(src)/scripts/utils.mk + +EXAMPLES := +EXAMPLES += dynevents +EXAMPLES += kprobes +EXAMPLES += eprobes +EXAMPLES += uprobes +EXAMPLES += synth +EXAMPLES += error +EXAMPLES += filter +EXAMPLES += function-filter +EXAMPLES += hist +EXAMPLES += hist-cont +EXAMPLES += tracer +EXAMPLES += stream +EXAMPLES += instances-affinity +EXAMPLES += cpu + +TARGETS := +TARGETS += sqlhist +TARGETS += $(EXAMPLES) + +sdir := $(obj)/bin + +TARGETS := $(patsubst %,$(sdir)/%,$(TARGETS)) + +all: $(TARGETS) + +$(bdir)/sqlhist.c: $(src)/Documentation/libtracefs-sql.txt + $(call extract_example,$<,$@) + +$(bdir)/%.c: ../Documentation/libtracefs-%.txt + $(call extract_example,$<,$@) + +$(sdir): + @mkdir -p $(sdir) + +sqlhist: $(sdir)/sqlhist + +$(TARGETS): $(sdir) + +# sqlhist is unique and stands on its own +$(sdir)/sqlhist: $(bdir)/sqlhist.c $(LIBTRACEFS_STATIC) + $(call do_sample_build,$@,$<) + +$(sdir)/%: $(bdir)/%.o + $(call do_sample_build,$@,$<) + +$(EXAMPLES): $(patsubst %,$(sdir)/%,$(TARGETS)) + +## The intermediate files get removed by Make. +## To examine the .c files created by one of the man pages, +## uncomment the below, and replace the XX with the exec example +## name, and the file will not be discarded by make. +# +# $(bdir)/XX.o: $(bdir)/XX.c +# $(CC) -g -Wall $(CFLAGS) -c -o $@ $^ -I../include/ $(LIBTRACEEVENT_INCLUDES) + +$(bdir)/%.o: $(bdir)/%.c + $(call do_sample_obj,$@,$^) + +$(bdir)/XX.o: $(bdir)/hist.c + $(CC) -g -Wall $(CFLAGS) -c -o $@ $^ -I../include/ $(LIBTRACEEVENT_INCLUDES) + +clean: + $(Q)$(call do_clean,$(sdir)/* $(bdir)/sqlhist.c $(bdir)/sqlhist.o) + +.PHONY: sqlhist diff --git a/scripts/features.mk b/scripts/features.mk new file mode 100644 index 0000000..9c7f8c3 --- /dev/null +++ b/scripts/features.mk @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0 + +# taken from perf which was based on Linux Kbuild +# try-cc +# Usage: option = $(call try-cc, source-to-build, cc-options) +try-cc = $(shell sh -c \ + 'TMP="$(BUILD_OUTPUT)$(TMPOUT).$$$$"; \ + echo "$(1)" | \ + $(CC) -x c - $(2) -o "$$TMP" > /dev/null 2>&1 && echo y; \ + rm -f "$$TMP"') + +define SOURCE_PTRACE +#include <stdio.h> +#include <sys/ptrace.h> + +int main (void) +{ + int ret; + ret = ptrace(PTRACE_ATTACH, 0, NULL, 0); + ptrace(PTRACE_TRACEME, 0, NULL, 0); + ptrace(PTRACE_GETSIGINFO, 0, NULL, NULL); + ptrace(PTRACE_GETEVENTMSG, 0, NULL, NULL); + ptrace(PTRACE_SETOPTIONS, NULL, NULL, + PTRACE_O_TRACEFORK | + PTRACE_O_TRACEVFORK | + PTRACE_O_TRACECLONE | + PTRACE_O_TRACEEXIT); + ptrace(PTRACE_CONT, NULL, NULL, 0); + ptrace(PTRACE_DETACH, 0, NULL, NULL); + ptrace(PTRACE_SETOPTIONS, 0, NULL, + PTRACE_O_TRACEFORK | + PTRACE_O_TRACEVFORK | + PTRACE_O_TRACECLONE | + PTRACE_O_TRACEEXIT); + return ret; +} +endef diff --git a/scripts/utils.mk b/scripts/utils.mk new file mode 100644 index 0000000..4d0f8bc --- /dev/null +++ b/scripts/utils.mk @@ -0,0 +1,196 @@ +# SPDX-License-Identifier: LGPL-2.1 + +# Utils + + PWD := $(shell /bin/pwd) + GOBJ = $(notdir $(strip $@)) + BASE1 = $(notdir $(strip $1)) + BASE2 = $(notdir $(strip $2)) + BASEPWD = $(notdir $(strip $(PWD))) + + +ifeq ($(VERBOSE),1) + Q = + S = +else + Q = @ + S = -s +endif + +# Use empty print_* macros if either SILENT or VERBOSE. +ifeq ($(findstring 1,$(SILENT)$(VERBOSE)),1) + print_compile = + print_app_build = + print_fpic_compile = + print_shared_lib_compile = + print_plugin_obj_compile = + print_plugin_build = + print_install = + print_uninstall = + print_update = + print_descend = + print_clean = + print_extract = + print_sample_build = + print_sample_obj = +else + print_compile = echo ' COMPILE '$(GOBJ); + print_app_build = echo ' BUILD '$(GOBJ); + print_fpic_compile = echo ' COMPILE FPIC '$(GOBJ); + print_shared_lib_compile = echo ' COMPILE SHARED LIB '$(GOBJ); + print_plugin_obj_compile = echo ' COMPILE PLUGIN OBJ '$(GOBJ); + print_plugin_build = echo ' BUILD PLUGIN '$(GOBJ); + print_static_lib_build = echo ' BUILD STATIC LIB '$(GOBJ); + print_install = echo ' INSTALL '$1' to $(DESTDIR_SQ)$2'; + print_uninstall = echo ' UNINSTALL $(DESTDIR_SQ)$1'; + print_update = echo ' UPDATE '$(GOBJ); + print_descend = echo ' DESCEND '$(BASE1) $(BASE2); + print_clean = echo ' CLEAN '$(BASEPWD); + print_extract = echo ' EXTRACT '$(GOBJ); + print_sample_build = echo ' COMPILE SAMPLE '$(GOBJ); + print_sample_obj = echo ' COMPILE SAMPLE OBJ '$(GOBJ); +endif + +do_fpic_compile = \ + ($(print_fpic_compile) \ + $(CC) -Wp,-MMD,$(@D)/.$(@F).d,-MT,$@ -MP -c $(CPPFLAGS) $(CFLAGS) $(EXT) -fPIC $< -o $@) + +do_compile = \ + ($(if $(GENERATE_PIC), $(do_fpic_compile), \ + $(print_compile) \ + $(CC) -Wp,-MMD,$(@D)/.$(@F).d,-MT,$@ -MP -c $(CPPFLAGS) $(CFLAGS) $(EXT) $< -o $@)) + +do_app_build = \ + ($(print_app_build) \ + $(CC) $^ -rdynamic -o $@ $(LDFLAGS) $(CONFIG_LIBS) $(LIBS)) + +do_build_static_lib = \ + ($(print_static_lib_build) \ + if [ -f $@ ]; then \ + mv $@ $@.rm; $(RM) $@.rm; \ + fi; \ + $(AR) rcs $@ $^) + +do_compile_shared_library = \ + ($(print_shared_lib_compile) \ + $(CC) --shared $^ '-Wl,-soname,$(1),-rpath=$$ORIGIN' -o $@ $(LDFLAGS) $(LIBS)) + +do_compile_plugin_obj = \ + ($(print_plugin_obj_compile) \ + $(CC) -c $(CPPFLAGS) $(CFLAGS) -fPIC -o $@ $<) + +do_plugin_build = \ + ($(print_plugin_build) \ + $(CC) $(CFLAGS) $(LDFLAGS) -shared -nostartfiles -o $@ $<) + +do_compile_python_plugin_obj = \ + ($(print_plugin_obj_compile) \ + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PYTHON_DIR_SQ) $(PYTHON_INCLUDES) -fPIC -o $@ $<) + +do_python_plugin_build = \ + ($(print_plugin_build) \ + $(CC) $< -shared $(LDFLAGS) $(PYTHON_LDFLAGS) -o $@) + +do_clean = \ + ($(print_clean) \ + $(RM) $1) + +extract_example = \ + $(Q)($(print_extract) \ + cat $1 | sed -ne '/^EXAMPLE/,/FILES/ { /EXAMPLE/,+2d ; /^FILES/d ; /^--/d ; p}' > $2) + +do_sample_build = \ + $(Q)($(print_sample_build) \ + $(CC) -o $1 $2 $(CFLAGS) $(LIBTRACEFS_STATIC) $(LIBTRACEEVENT_LIBS) -lpthread) + +do_sample_obj = \ + $(Q)($(print_sample_obj) \ + $(CC) -g -Wall -c $(CFLAGS) -o $1 $2 -I../include/ $(LIBTRACEEVENT_INCLUDES)) + +ifneq ($(findstring $(MAKEFLAGS), w),w) +PRINT_DIR = --no-print-directory +else +NO_SUBDIR = : +endif + +# +# Define a callable command for descending to a new directory +# +# Call by doing: $(call descend,directory[,target]) +# +descend = \ + ($(print_descend) \ + mkdir -p $(obj)/$(BASE1); \ + $(MAKE) $(PRINT_DIR) bdir=$(obj)/$(BASE1) -C $(1) $(2)) + +descend_clean = \ + $(MAKE) $(PRINT_DIR) bdir=$(obj)/$(BASE1) -C $(1) clean + +define make_version.h + (echo '/* This file is automatically generated. Do not modify. */'; \ + echo \#define VERSION_CODE $(shell \ + expr $(VERSION) \* 256 + $(PATCHLEVEL)); \ + echo '#define EXTRAVERSION ' $(EXTRAVERSION); \ + echo '#define VERSION_STRING "'$(VERSION).$(PATCHLEVEL).$(EXTRAVERSION)'"'; \ + echo '#define FILE_VERSION '$(FILE_VERSION); \ + if [ -d $(src)/.git ]; then \ + d=`git diff`; \ + x=""; \ + if [ ! -z "$$d" ]; then x="+"; fi; \ + echo '#define VERSION_GIT "'$(shell \ + git log -1 --pretty=format:"%H" 2>/dev/null)$$x'"'; \ + else \ + echo '#define VERSION_GIT "not-a-git-repo"'; \ + fi \ + ) > $1 +endef + +define update_version.h + ($(call make_version.h, $@.tmp); \ + if [ -r $@ ] && cmp -s $@ $@.tmp; then \ + rm -f $@.tmp; \ + else \ + $(print_update) \ + mv -f $@.tmp $@; \ + fi); +endef + +define update_dir + (echo $1 > $@.tmp; \ + if [ -r $@ ] && cmp -s $@ $@.tmp; then \ + rm -f $@.tmp; \ + else \ + $(print_update) \ + mv -f $@.tmp $@; \ + fi); +endef + +define build_prefix + (echo $1 > $@.tmp; \ + if [ -r $@ ] && cmp -s $@ $@.tmp; then \ + rm -f $@.tmp; \ + else \ + $(print_update) \ + mv -f $@.tmp $@; \ + fi); +endef + +define do_install_mkdir + if [ ! -d '$(DESTDIR_SQ)$1' ]; then \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$1'; \ + fi +endef + +define do_install + $(print_install) \ + $(call do_install_mkdir,$2); \ + $(INSTALL) $(if $3,-m $3,) $1 '$(DESTDIR_SQ)$2' +endef + +define do_install_pkgconfig_file + if [ -n "${pkgconfig_dir}" ]; then \ + $(call do_install,$(PKG_CONFIG_FILE),$(pkgconfig_dir),644); \ + else \ + (echo Failed to locate pkg-config directory) 1>&2; \ + fi +endef diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..e2965bc --- /dev/null +++ b/src/Makefile @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: LGPL-2.1 + +include $(src)/scripts/utils.mk + +OBJS = +OBJS += tracefs-utils.o +OBJS += tracefs-instance.o +OBJS += tracefs-events.o +OBJS += tracefs-tools.o +OBJS += tracefs-marker.o +OBJS += tracefs-kprobes.o +OBJS += tracefs-hist.o +OBJS += tracefs-filter.o +OBJS += tracefs-dynevents.o +OBJS += tracefs-eprobes.o +OBJS += tracefs-uprobes.o +OBJS += tracefs-record.o + +# Order matters for the the three below +OBJS += sqlhist-lex.o +OBJS += sqlhist.tab.o +OBJS += tracefs-sqlhist.o + +OBJS := $(OBJS:%.o=$(bdir)/%.o) + +$(LIBTRACEFS_STATIC): $(OBJS) + $(Q)$(call do_build_static_lib) + +$(LIBTRACEFS_SHARED): $(OBJS) + $(Q)$(call do_compile_shared_library,$(notdir $(LIBTRACEFS_SHARED_VERSION))) + +$(LIBTRACEFS_SHARED_VERSION): $(LIBTRACEFS_SHARED) + @ln -sf $(<F) $@ + +$(LIBTRACEFS_SHARED_SO): $(LIBTRACEFS_SHARED_VERSION) + @ln -sf $(<F) $@ + +libtracefs.so: $(LIBTRACEFS_SHARED_SO) + +# bison will create both sqlhist.tab.c and sqlhist.tab.h +sqlhist.tab.h: +sqlhist.tab.c: sqlhist.y sqlhist.tab.h + bison --debug -v --report-file=bison.report -d -o $@ $< + +sqlhist-lex.c: sqlhist.l sqlhist.tab.c + flex -o $@ $< + +$(bdir)/%.o: %.c + $(Q)$(call do_fpic_compile) + +tracefs-sqlhist.o: sqlhist.tab.h + +$(OBJS): | $(bdir) + +clean: + $(Q)$(call do_clean,$(OBJS) .*.d) + +-include .*.d + +$(bdir)/tracefs-sqlhist.o tracefs-sqlhist.o: sqlhist.tab.h + +.PHONY: $(LIBTRACEFS_SHARED_SO) $(LIBTRACEFS_STATIC) diff --git a/src/sqlhist-lex.c b/src/sqlhist-lex.c new file mode 100644 index 0000000..5c75280 --- /dev/null +++ b/src/sqlhist-lex.c @@ -0,0 +1,2204 @@ +#line 1 "sqlhist-lex.c" + +#line 3 "sqlhist-lex.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yyget_lval +#define yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval yyget_lval +#endif + +#ifdef yyset_lval +#define yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval yyset_lval +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include <inttypes.h> +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin , yyscanner ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + #define YY_LINENO_REWIND_TO(ptr) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +static void yyensure_buffer_stack ( yyscan_t yyscanner ); +static void yy_load_buffer_state ( yyscan_t yyscanner ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ +typedef flex_uint8_t YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); +static int yy_get_next_buffer ( yyscan_t yyscanner ); +static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; +#define YY_NUM_RULES 24 +#define YY_END_OF_BUFFER 25 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[72] = + { 0, + 0, 0, 25, 23, 21, 22, 20, 23, 19, 20, + 12, 12, 19, 20, 19, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 23, 23, 19, 13, 0, 9, + 17, 12, 0, 14, 16, 15, 10, 10, 2, 10, + 10, 10, 5, 10, 10, 10, 10, 18, 11, 10, + 10, 10, 10, 10, 10, 7, 3, 4, 10, 0, + 10, 10, 0, 6, 1, 0, 0, 0, 0, 8, + 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 4, 5, 1, 1, 1, 6, 1, 7, + 7, 7, 7, 7, 8, 9, 7, 10, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 1, 1, 12, + 13, 14, 1, 1, 15, 16, 17, 16, 18, 19, + 20, 21, 22, 23, 20, 24, 25, 26, 27, 20, + 20, 28, 29, 30, 20, 20, 31, 32, 33, 20, + 1, 34, 1, 1, 20, 1, 35, 16, 36, 16, + + 37, 38, 20, 39, 40, 41, 20, 42, 43, 44, + 45, 20, 20, 46, 47, 48, 20, 20, 49, 50, + 51, 20, 1, 52, 1, 53, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static const YY_CHAR yy_meta[54] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 3, 1, 1, 1, 4, 4, 4, 4, 4, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 6, 5, 1, 4, 4, 4, 4, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + 5, 1, 1 + } ; + +static const flex_int16_t yy_base[77] = + { 0, + 0, 0, 94, 180, 180, 180, 58, 62, 59, 180, + 22, 23, 51, 50, 49, 64, 102, 41, 31, 0, + 33, 40, 52, 47, 0, 9, 180, 180, 53, 180, + 180, 37, 0, 180, 180, 180, 0, 0, 0, 59, + 63, 69, 0, 68, 71, 79, 0, 180, 0, 74, + 80, 81, 97, 49, 96, 0, 0, 0, 109, 101, + 111, 101, 113, 0, 0, 114, 106, 118, 111, 180, + 180, 159, 163, 168, 171, 175 + } ; + +static const flex_int16_t yy_def[77] = + { 0, + 71, 1, 71, 71, 71, 71, 71, 72, 71, 71, + 73, 73, 71, 71, 71, 74, 74, 17, 17, 74, + 74, 74, 74, 74, 75, 71, 71, 71, 72, 71, + 71, 73, 76, 71, 71, 71, 74, 17, 74, 17, + 74, 74, 74, 74, 74, 74, 74, 71, 76, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 71, + 74, 74, 71, 74, 74, 71, 71, 71, 71, 71, + 0, 71, 71, 71, 71, 71 + } ; + +static const flex_int16_t yy_nxt[234] = + { 0, + 4, 5, 6, 7, 8, 9, 10, 10, 4, 11, + 12, 13, 14, 15, 16, 17, 18, 17, 19, 20, + 20, 20, 21, 20, 20, 20, 22, 20, 23, 20, + 24, 20, 20, 25, 16, 18, 17, 19, 20, 20, + 21, 20, 20, 20, 22, 20, 23, 20, 24, 20, + 20, 26, 27, 33, 71, 40, 60, 30, 41, 42, + 48, 36, 35, 34, 31, 43, 30, 46, 71, 44, + 28, 33, 71, 38, 38, 40, 41, 42, 38, 38, + 38, 38, 38, 43, 45, 46, 71, 50, 44, 51, + 52, 53, 39, 71, 71, 54, 55, 71, 38, 38, + + 38, 38, 45, 56, 57, 50, 58, 51, 52, 53, + 39, 38, 38, 54, 59, 55, 38, 38, 38, 38, + 38, 56, 57, 61, 58, 62, 71, 63, 64, 71, + 65, 66, 67, 59, 68, 69, 38, 38, 38, 38, + 70, 61, 71, 71, 62, 63, 71, 64, 65, 71, + 66, 67, 68, 71, 69, 71, 71, 71, 70, 29, + 29, 29, 29, 29, 29, 32, 32, 71, 32, 37, + 37, 37, 37, 37, 47, 47, 47, 49, 49, 3, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71 + } ; + +static const flex_int16_t yy_chk[234] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 11, 12, 18, 54, 29, 19, 21, + 26, 15, 14, 13, 9, 22, 8, 24, 32, 23, + 7, 11, 12, 16, 16, 18, 19, 21, 16, 16, + 16, 16, 16, 22, 23, 24, 32, 40, 23, 41, + 42, 44, 16, 3, 0, 45, 46, 0, 16, 16, + + 16, 16, 23, 50, 51, 40, 52, 41, 42, 44, + 16, 17, 17, 45, 53, 46, 17, 17, 17, 17, + 17, 50, 51, 55, 52, 59, 0, 60, 61, 0, + 62, 63, 66, 53, 67, 68, 17, 17, 17, 17, + 69, 55, 0, 0, 59, 60, 0, 61, 62, 0, + 63, 66, 67, 0, 68, 0, 0, 0, 69, 72, + 72, 72, 72, 72, 72, 73, 73, 0, 73, 74, + 74, 74, 74, 74, 75, 75, 75, 76, 76, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, + 71, 71, 71 + } ; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +#line 1 "sqlhist.l" +#line 2 "sqlhist.l" +/* code here */ + +#include <stdarg.h> +#include "sqlhist-parse.h" + +extern int my_yyinput(void *extra, char *buf, int max); + +#undef YY_INPUT +#define YY_INPUT(b, r, m) ({r = my_yyinput(yyextra, b, m);}) + +#define YY_NO_INPUT +#define YY_NO_UNPUT + +#define YY_EXTRA_TYPE struct sqlhist_bison * + +#define yytext yyg->yytext_r + +#define TRACE_SB ((struct sqlhist_bison *)yyextra) +#define HANDLE_COLUMN do { TRACE_SB->line_idx += strlen(yytext); } while (0) + +#line 527 "sqlhist-lex.c" +#line 528 "sqlhist-lex.c" + +#define INITIAL 0 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include <unistd.h> +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + int yy_n_chars; + int yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals ( yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + int yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef YY_NO_UNPUT + + static void yyunput ( int c, char *buf_ptr , yyscan_t yyscanner); + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( yyscan_t yyscanner ); +#else +static int input ( yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yylval = yylval_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_load_buffer_state( yyscanner ); + } + + { +#line 33 "sqlhist.l" + + +#line 805 "sqlhist-lex.c" + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 72 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 180 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 35 "sqlhist.l" +{ HANDLE_COLUMN; return SELECT; } + YY_BREAK +case 2: +YY_RULE_SETUP +#line 36 "sqlhist.l" +{ HANDLE_COLUMN; return AS; } + YY_BREAK +case 3: +YY_RULE_SETUP +#line 37 "sqlhist.l" +{ HANDLE_COLUMN; return FROM; } + YY_BREAK +case 4: +YY_RULE_SETUP +#line 38 "sqlhist.l" +{ HANDLE_COLUMN; return JOIN; } + YY_BREAK +case 5: +YY_RULE_SETUP +#line 39 "sqlhist.l" +{ HANDLE_COLUMN; return ON; } + YY_BREAK +case 6: +YY_RULE_SETUP +#line 40 "sqlhist.l" +{ HANDLE_COLUMN; return WHERE; } + YY_BREAK +case 7: +YY_RULE_SETUP +#line 41 "sqlhist.l" +{ HANDLE_COLUMN; return CAST; } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 43 "sqlhist.l" +{ + HANDLE_COLUMN; + yylval->string = store_str(TRACE_SB, yyg->yytext_r); + return FIELD; +} + YY_BREAK +case 9: +/* rule 9 can match eol */ +YY_RULE_SETUP +#line 49 "sqlhist.l" +{ + HANDLE_COLUMN; + yylval->string = store_str(TRACE_SB, yyg->yytext_r); + return STRING; +} + YY_BREAK +case 10: +YY_RULE_SETUP +#line 55 "sqlhist.l" +{ + const char *str = yyg->yytext_r; + HANDLE_COLUMN; + if (str[0] == '\\') { str++; }; + yylval->string = store_str(TRACE_SB, str); + return FIELD; +} + YY_BREAK +case 11: +YY_RULE_SETUP +#line 63 "sqlhist.l" +{ + HANDLE_COLUMN; + yylval->number = strtol(yyg->yytext_r, NULL, 0); + return NUMBER; +} + YY_BREAK +case 12: +YY_RULE_SETUP +#line 69 "sqlhist.l" +{ + HANDLE_COLUMN; + yylval->number = strtol(yyg->yytext_r, NULL, 0); + return NUMBER; +} + YY_BREAK +case 13: +YY_RULE_SETUP +#line 75 "sqlhist.l" +{ HANDLE_COLUMN; return NEQ; } + YY_BREAK +case 14: +YY_RULE_SETUP +#line 76 "sqlhist.l" +{ HANDLE_COLUMN; return LE; } + YY_BREAK +case 15: +YY_RULE_SETUP +#line 77 "sqlhist.l" +{ HANDLE_COLUMN; return GE; } + YY_BREAK +case 16: +YY_RULE_SETUP +#line 78 "sqlhist.l" +{ HANDLE_COLUMN; return EQ; } + YY_BREAK +case 17: +YY_RULE_SETUP +#line 79 "sqlhist.l" +{ HANDLE_COLUMN; return AND; } + YY_BREAK +case 18: +YY_RULE_SETUP +#line 80 "sqlhist.l" +{ HANDLE_COLUMN; return OR; } + YY_BREAK +case 19: +YY_RULE_SETUP +#line 81 "sqlhist.l" +{ HANDLE_COLUMN; return yytext[0]; } + YY_BREAK +case 20: +YY_RULE_SETUP +#line 83 "sqlhist.l" +{ HANDLE_COLUMN; return yytext[0]; } + YY_BREAK +case 21: +YY_RULE_SETUP +#line 85 "sqlhist.l" +{ HANDLE_COLUMN; } + YY_BREAK +case 22: +/* rule 22 can match eol */ +YY_RULE_SETUP +#line 86 "sqlhist.l" +{ TRACE_SB->line_idx = 0; TRACE_SB->line_no++; } + YY_BREAK +case 23: +YY_RULE_SETUP +#line 88 "sqlhist.l" +{ HANDLE_COLUMN; return PARSE_ERROR; } + YY_BREAK +case 24: +YY_RULE_SETUP +#line 89 "sqlhist.l" +ECHO; + YY_BREAK +#line 1006 "sqlhist-lex.c" +case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( yywrap( yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = yyg->yytext_ptr; + int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin , yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + int new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + yy_state_type yy_current_state; + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 72 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + char *yy_cp = yyg->yy_c_buf_p; + + YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 72 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 71); + + (void)yyg; + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + + static void yyunput (int c, char * yy_bp , yyscan_t yyscanner) +{ + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_cp = yyg->yy_c_buf_p; + + /* undo effects of setting up yytext */ + *yy_cp = yyg->yy_hold_char; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + int number_to_move = yyg->yy_n_chars + 2; + char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + yyg->yy_n_chars = (int) YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + yyg->yytext_ptr = yy_bp; + yyg->yy_hold_char = *yy_cp; + yyg->yy_c_buf_p = yy_cp; +} + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr); + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin , yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( yyscanner ) ) + return 0; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); + yy_load_buffer_state( yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file , yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * @param yyscanner The scanner object. + */ + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf , yyscanner ); + + yyfree( (void *) b , yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_flush_buffer( b , yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(yyscanner); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (yyscan_t yyscanner) +{ + yy_size_t num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b , yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n , yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n , yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +int yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param _line_number line number + * @param yyscanner The scanner object. + */ +void yyset_lineno (int _line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); + + yylineno = _line_number; +} + +/** Set the current column. + * @param _column_no column number + * @param yyscanner The scanner object. + */ +void yyset_column (int _column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_column called with no buffer" ); + + yycolumn = _column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * @param yyscanner The scanner object. + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = _out_str ; +} + +int yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void yyset_debug (int _bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = _bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +/* User-visible API */ + +/* yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ +int yylex_init(yyscan_t* ptr_yy_globals) +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* yylex_init_extra has the same functionality as yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to yyalloc in + * the yyextra field. + */ +int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) +{ + struct yyguts_t dummy_yyguts; + + yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = NULL; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = NULL; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + yyfree(yyg->yy_buffer_stack , yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + yyfree( yyg->yy_start_stack , yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s , yyscan_t yyscanner) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 89 "sqlhist.l" + + +int yywrap(void *data) +{ + return 1; +} + +void yyerror(struct sqlhist_bison *sb, char *fmt, ...) +{ + struct yyguts_t * yyg = (struct yyguts_t*)sb->scanner; + va_list ap; + + va_start(ap, fmt); + sql_parse_error(sb, yytext, fmt, ap); + va_end(ap); +} + diff --git a/src/sqlhist-parse.h b/src/sqlhist-parse.h new file mode 100644 index 0000000..7bd2a63 --- /dev/null +++ b/src/sqlhist-parse.h @@ -0,0 +1,77 @@ +#ifndef __SQLHIST_PARSE_H +#define __SQLHIST_PARSE_H + +#include <stdarg.h> +#include <tracefs.h> + +#include <tracefs-local.h> + +struct str_hash; + +struct sql_table; + +struct sqlhist_bison { + void *scanner; + const char *buffer; + size_t buffer_size; + size_t buffer_idx; + int line_no; + int line_idx; + struct sql_table *table; + char *parse_error_str; + struct str_hash *str_hash[1 << HASH_BITS]; +}; + +#include "sqlhist.tab.h" + +enum filter_type { + FILTER_GROUP, + FILTER_NOT_GROUP, + FILTER_EQ, + FILTER_NE, + FILTER_LE, + FILTER_LT, + FILTER_GE, + FILTER_GT, + FILTER_BIN_AND, + FILTER_STR_CMP, + FILTER_AND, + FILTER_OR, +}; + +enum compare_type { + COMPARE_GROUP, + COMPARE_ADD, + COMPARE_SUB, + COMPARE_MUL, + COMPARE_DIV, + COMPARE_BIN_AND, + COMPARE_BIN_OR, + COMPARE_AND, + COMPARE_OR, +}; + +char * store_str(struct sqlhist_bison *sb, const char *str); + +int table_start(struct sqlhist_bison *sb); + +void *add_field(struct sqlhist_bison *sb, const char *field, const char *label); + +void *add_filter(struct sqlhist_bison *sb, void *A, void *B, enum filter_type op); + +int add_match(struct sqlhist_bison *sb, void *A, void *B); +void *add_compare(struct sqlhist_bison *sb, void *A, void *B, enum compare_type type); +int add_where(struct sqlhist_bison *sb, void *expr); + +int add_selection(struct sqlhist_bison *sb, void *item, const char *label); +int add_from(struct sqlhist_bison *sb, void *item); +int add_to(struct sqlhist_bison *sb, void *item); +void *add_cast(struct sqlhist_bison *sb, void *field, const char *type); + +void *add_string(struct sqlhist_bison *sb, const char *str); +void *add_number(struct sqlhist_bison *sb, long val); + +extern void sql_parse_error(struct sqlhist_bison *sb, const char *text, + const char *fmt, va_list ap); + +#endif diff --git a/src/sqlhist.l b/src/sqlhist.l new file mode 100644 index 0000000..4df475a --- /dev/null +++ b/src/sqlhist.l @@ -0,0 +1,104 @@ +%{ +/* code here */ + +#include <stdarg.h> +#include "sqlhist-parse.h" + +extern int my_yyinput(void *extra, char *buf, int max); + +#undef YY_INPUT +#define YY_INPUT(b, r, m) ({r = my_yyinput(yyextra, b, m);}) + +#define YY_NO_INPUT +#define YY_NO_UNPUT + +#define YY_EXTRA_TYPE struct sqlhist_bison * + +#define yytext yyg->yytext_r + +#define TRACE_SB ((struct sqlhist_bison *)yyextra) +#define HANDLE_COLUMN do { TRACE_SB->line_idx += strlen(yytext); } while (0) + +%} + +%option caseless +%option reentrant +%option bison-bridge + +field \\?[a-z_][a-z0-9_\.]* +qstring \"[^\"]*\" + +hexnum 0x[0-9a-f]+ +number [0-9a-f]+ +%% + +select { HANDLE_COLUMN; return SELECT; } +as { HANDLE_COLUMN; return AS; } +from { HANDLE_COLUMN; return FROM; } +join { HANDLE_COLUMN; return JOIN; } +on { HANDLE_COLUMN; return ON; } +where { HANDLE_COLUMN; return WHERE; } +cast { HANDLE_COLUMN; return CAST; } + +sym-offset { + HANDLE_COLUMN; + yylval->string = store_str(TRACE_SB, yyg->yytext_r); + return FIELD; +} + +{qstring} { + HANDLE_COLUMN; + yylval->string = store_str(TRACE_SB, yyg->yytext_r); + return STRING; +} + +{field} { + const char *str = yyg->yytext_r; + HANDLE_COLUMN; + if (str[0] == '\\') { str++; }; + yylval->string = store_str(TRACE_SB, str); + return FIELD; +} + +{hexnum} { + HANDLE_COLUMN; + yylval->number = strtol(yyg->yytext_r, NULL, 0); + return NUMBER; +} + +{number} { + HANDLE_COLUMN; + yylval->number = strtol(yyg->yytext_r, NULL, 0); + return NUMBER; +} + +\!= { HANDLE_COLUMN; return NEQ; } +\<= { HANDLE_COLUMN; return LE; } +\>= { HANDLE_COLUMN; return GE; } +== { HANDLE_COLUMN; return EQ; } +&& { HANDLE_COLUMN; return AND; } +"||" { HANDLE_COLUMN; return OR; } +[<>&~] { HANDLE_COLUMN; return yytext[0]; } + +[\!()\-\+\*/,=] { HANDLE_COLUMN; return yytext[0]; } + +[ \t] { HANDLE_COLUMN; } +\n { TRACE_SB->line_idx = 0; TRACE_SB->line_no++; } + +. { HANDLE_COLUMN; return PARSE_ERROR; } +%% + +int yywrap(void *data) +{ + return 1; +} + +void yyerror(struct sqlhist_bison *sb, char *fmt, ...) +{ + struct yyguts_t * yyg = (struct yyguts_t*)sb->scanner; + va_list ap; + + va_start(ap, fmt); + sql_parse_error(sb, yytext, fmt, ap); + va_end(ap); +} diff --git a/src/sqlhist.tab.c b/src/sqlhist.tab.c new file mode 100644 index 0000000..6393e95 --- /dev/null +++ b/src/sqlhist.tab.c @@ -0,0 +1,1755 @@ +/* A Bison parser, made by GNU Bison 3.6.4. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2020 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "3.6.4" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + +/* Substitute the type names. */ +#define YYSTYPE TRACEFS_STYPE +/* Substitute the variable and function names. */ +#define yyparse tracefs_parse +#define yylex tracefs_lex +#define yyerror tracefs_error +#define yydebug tracefs_debug +#define yynerrs tracefs_nerrs + +/* First part of user prologue. */ +#line 1 "sqlhist.y" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "sqlhist-parse.h" + +#define scanner sb->scanner + +extern int yylex(YYSTYPE *yylval, void *); +extern void yyerror(struct sqlhist_bison *, char *fmt, ...); + +#define CHECK_RETURN_PTR(x) \ + do { \ + if (!(x)) { \ + printf("FAILED MEMORY: %s\n", #x); \ + return -ENOMEM; \ + } \ + } while (0) + +#define CHECK_RETURN_VAL(x) \ + do { \ + if ((x) < 0) { \ + printf("FAILED MEMORY: %s\n", #x); \ + return -ENOMEM; \ + } \ + } while (0) + + +#line 108 "sqlhist.tab.c" + +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast<Type> (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val) +# else +# define YY_CAST(Type, Val) ((Type) (Val)) +# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) +# endif +# endif +# ifndef YY_NULLPTR +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# else +# define YY_NULLPTR ((void*)0) +# endif +# endif + +/* Use api.header.include to #include this header + instead of duplicating it here. */ +#ifndef YY_TRACEFS_SQLHIST_TAB_H_INCLUDED +# define YY_TRACEFS_SQLHIST_TAB_H_INCLUDED +/* Debug traces. */ +#ifndef TRACEFS_DEBUG +# if defined YYDEBUG +#if YYDEBUG +# define TRACEFS_DEBUG 1 +# else +# define TRACEFS_DEBUG 0 +# endif +# else /* ! defined YYDEBUG */ +# define TRACEFS_DEBUG 1 +# endif /* ! defined YYDEBUG */ +#endif /* ! defined TRACEFS_DEBUG */ +#if TRACEFS_DEBUG +extern int tracefs_debug; +#endif + +/* Token kinds. */ +#ifndef TRACEFS_TOKENTYPE +# define TRACEFS_TOKENTYPE + enum tracefs_tokentype + { + TRACEFS_EMPTY = -2, + TRACEFS_EOF = 0, /* "end of file" */ + TRACEFS_error = 256, /* error */ + TRACEFS_UNDEF = 257, /* "invalid token" */ + AS = 258, /* AS */ + SELECT = 259, /* SELECT */ + FROM = 260, /* FROM */ + JOIN = 261, /* JOIN */ + ON = 262, /* ON */ + WHERE = 263, /* WHERE */ + PARSE_ERROR = 264, /* PARSE_ERROR */ + CAST = 265, /* CAST */ + NUMBER = 266, /* NUMBER */ + field_type = 267, /* field_type */ + STRING = 268, /* STRING */ + FIELD = 269, /* FIELD */ + LE = 270, /* LE */ + GE = 271, /* GE */ + EQ = 272, /* EQ */ + NEQ = 273, /* NEQ */ + AND = 274, /* AND */ + OR = 275 /* OR */ + }; + typedef enum tracefs_tokentype tracefs_token_kind_t; +#endif + +/* Value type. */ +#if ! defined TRACEFS_STYPE && ! defined TRACEFS_STYPE_IS_DECLARED +union TRACEFS_STYPE +{ +#line 46 "sqlhist.y" + + int s32; + char *string; + long number; + void *expr; + +#line 193 "sqlhist.tab.c" + +}; +typedef union TRACEFS_STYPE TRACEFS_STYPE; +# define TRACEFS_STYPE_IS_TRIVIAL 1 +# define TRACEFS_STYPE_IS_DECLARED 1 +#endif + + + +int tracefs_parse (struct sqlhist_bison *sb); +/* "%code provides" blocks. */ +#line 37 "sqlhist.y" + + #define YYSTYPE TRACEFS_STYPE + #define yylex tracefs_lex + #define yyerror tracefs_error + +#line 211 "sqlhist.tab.c" + +#endif /* !YY_TRACEFS_SQLHIST_TAB_H_INCLUDED */ +/* Symbol kind. */ +enum yysymbol_kind_t +{ + YYSYMBOL_YYEMPTY = -2, + YYSYMBOL_YYEOF = 0, /* "end of file" */ + YYSYMBOL_YYerror = 1, /* error */ + YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ + YYSYMBOL_AS = 3, /* AS */ + YYSYMBOL_SELECT = 4, /* SELECT */ + YYSYMBOL_FROM = 5, /* FROM */ + YYSYMBOL_JOIN = 6, /* JOIN */ + YYSYMBOL_ON = 7, /* ON */ + YYSYMBOL_WHERE = 8, /* WHERE */ + YYSYMBOL_PARSE_ERROR = 9, /* PARSE_ERROR */ + YYSYMBOL_CAST = 10, /* CAST */ + YYSYMBOL_NUMBER = 11, /* NUMBER */ + YYSYMBOL_field_type = 12, /* field_type */ + YYSYMBOL_STRING = 13, /* STRING */ + YYSYMBOL_FIELD = 14, /* FIELD */ + YYSYMBOL_LE = 15, /* LE */ + YYSYMBOL_GE = 16, /* GE */ + YYSYMBOL_EQ = 17, /* EQ */ + YYSYMBOL_NEQ = 18, /* NEQ */ + YYSYMBOL_AND = 19, /* AND */ + YYSYMBOL_OR = 20, /* OR */ + YYSYMBOL_21_ = 21, /* '+' */ + YYSYMBOL_22_ = 22, /* '-' */ + YYSYMBOL_23_ = 23, /* '*' */ + YYSYMBOL_24_ = 24, /* '/' */ + YYSYMBOL_25_ = 25, /* '<' */ + YYSYMBOL_26_ = 26, /* '>' */ + YYSYMBOL_27_ = 27, /* ',' */ + YYSYMBOL_28_ = 28, /* '(' */ + YYSYMBOL_29_ = 29, /* ')' */ + YYSYMBOL_30_ = 30, /* '=' */ + YYSYMBOL_31_ = 31, /* "!=" */ + YYSYMBOL_32_ = 32, /* '&' */ + YYSYMBOL_33_ = 33, /* '~' */ + YYSYMBOL_34_ = 34, /* '!' */ + YYSYMBOL_YYACCEPT = 35, /* $accept */ + YYSYMBOL_start = 36, /* start */ + YYSYMBOL_label = 37, /* label */ + YYSYMBOL_select = 38, /* select */ + YYSYMBOL_select_statement = 39, /* select_statement */ + YYSYMBOL_selection_list = 40, /* selection_list */ + YYSYMBOL_selection = 41, /* selection */ + YYSYMBOL_selection_expr = 42, /* selection_expr */ + YYSYMBOL_selection_addition = 43, /* selection_addition */ + YYSYMBOL_item = 44, /* item */ + YYSYMBOL_field = 45, /* field */ + YYSYMBOL_named_field = 46, /* named_field */ + YYSYMBOL_name = 47, /* name */ + YYSYMBOL_str_val = 48, /* str_val */ + YYSYMBOL_val = 49, /* val */ + YYSYMBOL_compare = 50, /* compare */ + YYSYMBOL_compare_and_or = 51, /* compare_and_or */ + YYSYMBOL_compare_items = 52, /* compare_items */ + YYSYMBOL_compare_cmds = 53, /* compare_cmds */ + YYSYMBOL_compare_list = 54, /* compare_list */ + YYSYMBOL_where_clause = 55, /* where_clause */ + YYSYMBOL_opt_where_clause = 56, /* opt_where_clause */ + YYSYMBOL_opt_join_clause = 57, /* opt_join_clause */ + YYSYMBOL_table_exp = 58, /* table_exp */ + YYSYMBOL_from_clause = 59, /* from_clause */ + YYSYMBOL_join_clause = 60, /* join_clause */ + YYSYMBOL_match = 61, /* match */ + YYSYMBOL_match_clause = 62 /* match_clause */ +}; +typedef enum yysymbol_kind_t yysymbol_kind_t; + + + + +#ifdef short +# undef short +#endif + +/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure + <limits.h> and (if available) <stdint.h> are included + so that the code can choose integer types of a good width. */ + +#ifndef __PTRDIFF_MAX__ +# include <limits.h> /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include <stdint.h> /* INFRINGES ON USER NAME SPACE */ +# define YY_STDINT_H +# endif +#endif + +/* Narrow types that promote to a signed type and that can represent a + signed or unsigned integer of at least N bits. In tables they can + save space and decrease cache pressure. Promoting to a signed type + helps avoid bugs in integer arithmetic. */ + +#ifdef __INT_LEAST8_MAX__ +typedef __INT_LEAST8_TYPE__ yytype_int8; +#elif defined YY_STDINT_H +typedef int_least8_t yytype_int8; +#else +typedef signed char yytype_int8; +#endif + +#ifdef __INT_LEAST16_MAX__ +typedef __INT_LEAST16_TYPE__ yytype_int16; +#elif defined YY_STDINT_H +typedef int_least16_t yytype_int16; +#else +typedef short yytype_int16; +#endif + +#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST8_TYPE__ yytype_uint8; +#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST8_MAX <= INT_MAX) +typedef uint_least8_t yytype_uint8; +#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX +typedef unsigned char yytype_uint8; +#else +typedef short yytype_uint8; +#endif + +#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST16_TYPE__ yytype_uint16; +#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST16_MAX <= INT_MAX) +typedef uint_least16_t yytype_uint16; +#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX +typedef unsigned short yytype_uint16; +#else +typedef int yytype_uint16; +#endif + +#ifndef YYPTRDIFF_T +# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ +# define YYPTRDIFF_T __PTRDIFF_TYPE__ +# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ +# elif defined PTRDIFF_MAX +# ifndef ptrdiff_t +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# endif +# define YYPTRDIFF_T ptrdiff_t +# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX +# else +# define YYPTRDIFF_T long +# define YYPTRDIFF_MAXIMUM LONG_MAX +# endif +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned +# endif +#endif + +#define YYSIZE_MAXIMUM \ + YY_CAST (YYPTRDIFF_T, \ + (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ + ? YYPTRDIFF_MAXIMUM \ + : YY_CAST (YYSIZE_T, -1))) + +#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) + + +/* Stored state numbers (used for stacks). */ +typedef yytype_int8 yy_state_t; + +/* State numbers in computations. */ +typedef int yy_state_fast_t; + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include <libintl.h> /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif +#endif + + +#ifndef YY_ATTRIBUTE_PURE +# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) +# else +# define YY_ATTRIBUTE_PURE +# endif +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +# else +# define YY_ATTRIBUTE_UNUSED +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(E) ((void) (E)) +#else +# define YYUSE(E) /* empty */ +#endif + +#if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + +#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ +# define YY_IGNORE_USELESS_CAST_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") +# define YY_IGNORE_USELESS_CAST_END \ + _Pragma ("GCC diagnostic pop") +#endif +#ifndef YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_END +#endif + + +#define YY_ASSERT(E) ((void) (0 && (E))) + +#if !defined yyoverflow + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include <alloca.h> /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include <malloc.h> /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* !defined yyoverflow */ + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined TRACEFS_STYPE_IS_TRIVIAL && TRACEFS_STYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yy_state_t yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYPTRDIFF_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / YYSIZEOF (*yyptr); \ + } \ + while (0) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYPTRDIFF_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 5 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 104 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 35 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 28 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 61 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 111 + +#define YYMAXUTOK 276 + + +/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, with out-of-bounds checking. */ +#define YYTRANSLATE(YYX) \ + (0 <= (YYX) && (YYX) <= YYMAXUTOK \ + ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ + : YYSYMBOL_YYUNDEF) + +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex. */ +static const yytype_int8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 34, 2, 2, 2, 2, 32, 2, + 28, 29, 23, 21, 27, 22, 2, 24, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 25, 30, 26, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 33, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 31 +}; + +#if TRACEFS_DEBUG + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 75, 75, 78, 79, 82, 86, 90, 91, 95, + 99, 106, 107, 108, 109, 110, 117, 122, 130, 131, + 135, 139, 143, 147, 151, 152, 157, 158, 159, 160, + 161, 162, 163, 164, 165, 166, 170, 171, 172, 173, + 174, 178, 179, 180, 181, 182, 186, 195, 196, 197, + 201, 204, 206, 209, 211, 215, 219, 234, 238, 239, + 244, 245 +}; +#endif + +/** Accessing symbol of state STATE. */ +#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) + +#if TRACEFS_DEBUG || 0 +/* The user-facing name of the symbol whose (internal) number is + YYSYMBOL. No bounds checking. */ +static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; + +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "\"invalid token\"", "AS", "SELECT", "FROM", + "JOIN", "ON", "WHERE", "PARSE_ERROR", "CAST", "NUMBER", "field_type", + "STRING", "FIELD", "LE", "GE", "EQ", "NEQ", "AND", "OR", "'+'", "'-'", + "'*'", "'/'", "'<'", "'>'", "','", "'('", "')'", "'='", "\"!=\"", "'&'", + "'~'", "'!'", "$accept", "start", "label", "select", "select_statement", + "selection_list", "selection", "selection_expr", "selection_addition", + "item", "field", "named_field", "name", "str_val", "val", "compare", + "compare_and_or", "compare_items", "compare_cmds", "compare_list", + "where_clause", "opt_where_clause", "opt_join_clause", "table_exp", + "from_clause", "join_clause", "match", "match_clause", YY_NULLPTR +}; + +static const char * +yysymbol_name (yysymbol_kind_t yysymbol) +{ + return yytname[yysymbol]; +} +#endif + +#ifdef YYPRINT +/* YYTOKNUM[NUM] -- (External) token number corresponding to the + (internal) symbol number NUM (which must be that of a token). */ +static const yytype_int16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 43, 45, 42, 47, 60, 62, 44, 40, 41, + 61, 276, 38, 126, 33 +}; +#endif + +#define YYPACT_NINF (-58) + +#define yypact_value_is_default(Yyn) \ + ((Yyn) == YYPACT_NINF) + +#define YYTABLE_NINF (-1) + +#define yytable_value_is_error(Yyn) \ + 0 + + /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int8 yypact[] = +{ + 4, -58, 13, 11, -58, -58, 5, -58, 43, 53, + 45, 29, -58, 19, 43, 44, 32, 60, -58, 73, + 11, 75, -58, -58, -58, 43, 43, 87, -58, -58, + 29, -58, -58, -58, 60, 83, -58, -58, -58, -58, + -58, 78, -58, 86, 14, -58, -58, 65, 60, -4, + -12, 34, -58, 76, -15, -58, -58, -10, 68, -58, + 1, -58, 18, -4, -58, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 84, 14, 14, 14, 60, 60, + 60, -4, -58, -4, -4, -58, 49, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, 51, -58, -58, -58, + -58 +}; + + /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_int8 yydefact[] = +{ + 0, 5, 0, 0, 2, 1, 0, 20, 0, 0, + 7, 9, 13, 11, 0, 0, 0, 0, 6, 53, + 0, 0, 22, 10, 4, 0, 0, 0, 14, 12, + 20, 56, 19, 18, 0, 51, 54, 8, 3, 16, + 17, 0, 21, 0, 0, 52, 55, 0, 0, 0, + 0, 0, 45, 46, 47, 50, 15, 0, 60, 57, + 0, 40, 0, 0, 44, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 39, 0, 0, 42, 0, 25, 23, 24, + 28, 29, 31, 32, 26, 27, 30, 33, 34, 35, + 41, 49, 48, 59, 58, 61, 0, 37, 36, 43, + 38 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -58, -58, 69, -58, -58, 80, -58, -58, 90, -16, + -3, -58, 81, 27, 15, -41, -57, 28, -58, -21, + -58, -58, -58, -58, -58, -58, -58, 24 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 2, 23, 3, 4, 9, 10, 11, 12, 57, + 51, 33, 24, 89, 90, 61, 62, 53, 54, 55, + 45, 46, 35, 18, 19, 36, 58, 59 +}; + + /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_int8 yytable[] = +{ + 13, 31, 7, 52, 76, 16, 86, 78, 1, 64, + 7, 27, 77, 5, 32, 7, 63, 13, 43, 82, + 79, 6, 39, 40, 106, 7, 107, 108, 7, 81, + 60, 32, 21, 14, 52, 52, 52, 83, 84, 8, + 25, 26, 49, 22, 87, 32, 88, 85, 50, 65, + 66, 67, 68, 25, 26, 101, 102, 7, 17, 69, + 70, 29, 103, 104, 71, 72, 73, 74, 83, 84, + 83, 84, 20, 28, 30, 32, 32, 32, 109, 34, + 110, 91, 92, 93, 94, 95, 96, 97, 98, 22, + 41, 44, 47, 48, 56, 80, 75, 88, 15, 42, + 37, 99, 38, 100, 105 +}; + +static const yytype_int8 yycheck[] = +{ + 3, 17, 14, 44, 19, 8, 63, 17, 4, 50, + 14, 14, 27, 0, 17, 14, 28, 20, 34, 60, + 30, 10, 25, 26, 81, 14, 83, 84, 14, 28, + 34, 34, 3, 28, 75, 76, 77, 19, 20, 28, + 21, 22, 28, 14, 11, 48, 13, 29, 34, 15, + 16, 17, 18, 21, 22, 76, 77, 14, 5, 25, + 26, 29, 78, 79, 30, 31, 32, 33, 19, 20, + 19, 20, 27, 29, 14, 78, 79, 80, 29, 6, + 29, 66, 67, 68, 69, 70, 71, 72, 73, 14, + 3, 8, 14, 7, 29, 27, 20, 13, 8, 30, + 20, 74, 21, 75, 80 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_int8 yystos[] = +{ + 0, 4, 36, 38, 39, 0, 10, 14, 28, 40, + 41, 42, 43, 45, 28, 43, 45, 5, 58, 59, + 27, 3, 14, 37, 47, 21, 22, 45, 29, 29, + 14, 44, 45, 46, 6, 57, 60, 40, 47, 45, + 45, 3, 37, 44, 8, 55, 56, 14, 7, 28, + 34, 45, 50, 52, 53, 54, 29, 44, 61, 62, + 34, 50, 51, 28, 50, 15, 16, 17, 18, 25, + 26, 30, 31, 32, 33, 20, 19, 27, 17, 30, + 27, 28, 50, 19, 20, 29, 51, 11, 13, 48, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 48, + 52, 54, 54, 44, 44, 62, 51, 51, 51, 29, + 29 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_int8 yyr1[] = +{ + 0, 35, 36, 37, 37, 38, 39, 40, 40, 41, + 41, 42, 42, 42, 42, 42, 43, 43, 44, 44, + 45, 46, 47, 48, 49, 49, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, + 51, 52, 52, 52, 52, 52, 53, 54, 54, 54, + 55, 56, 56, 57, 57, 58, 59, 60, 61, 61, + 62, 62 +}; + + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ +static const yytype_int8 yyr2[] = +{ + 0, 2, 1, 2, 1, 1, 3, 1, 3, 1, + 2, 1, 3, 1, 3, 6, 3, 3, 1, 1, + 1, 2, 1, 1, 1, 1, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 4, 2, + 1, 3, 3, 4, 2, 1, 1, 1, 3, 3, + 2, 0, 1, 0, 1, 3, 2, 4, 3, 3, + 1, 3 +}; + + +enum { YYENOMEM = -2 }; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = TRACEFS_EMPTY) + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ + do \ + if (yychar == TRACEFS_EMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (sb, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ + while (0) + +/* Backward compatibility with an undocumented macro. + Use TRACEFS_error or TRACEFS_UNDEF. */ +#define YYERRCODE TRACEFS_UNDEF + + +/* Enable debugging if requested. */ +#if TRACEFS_DEBUG + +# ifndef YYFPRINTF +# include <stdio.h> /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + +/* This macro is provided for backward compatibility. */ +# ifndef YY_LOCATION_PRINT +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif + + +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Kind, Value, sb); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*-----------------------------------. +| Print this symbol's value on YYO. | +`-----------------------------------*/ + +static void +yy_symbol_value_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, struct sqlhist_bison *sb) +{ + FILE *yyoutput = yyo; + YYUSE (yyoutput); + YYUSE (sb); + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yykind < YYNTOKENS) + YYPRINT (yyo, yytoknum[yykind], *yyvaluep); +# endif + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YYUSE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + +/*---------------------------. +| Print this symbol on YYO. | +`---------------------------*/ + +static void +yy_symbol_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, struct sqlhist_bison *sb) +{ + YYFPRINTF (yyo, "%s %s (", + yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); + + yy_symbol_value_print (yyo, yykind, yyvaluep, sb); + YYFPRINTF (yyo, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +static void +yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +static void +yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, + int yyrule, struct sqlhist_bison *sb) +{ + int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), + &yyvsp[(yyi + 1) - (yynrhs)], sb); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule, sb); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !TRACEFS_DEBUG */ +# define YYDPRINTF(Args) ((void) 0) +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !TRACEFS_DEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + + + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +static void +yydestruct (const char *yymsg, + yysymbol_kind_t yykind, YYSTYPE *yyvaluep, struct sqlhist_bison *sb) +{ + YYUSE (yyvaluep); + YYUSE (sb); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YYUSE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + + + + + +/*----------. +| yyparse. | +`----------*/ + +int +yyparse (struct sqlhist_bison *sb) +{ +/* The lookahead symbol. */ +int yychar; + + +/* The semantic value of the lookahead symbol. */ +/* Default value used for initialization, for pacifying older GCCs + or non-GCC compilers. */ +YY_INITIAL_VALUE (static YYSTYPE yyval_default;) +YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); + + /* Number of syntax errors so far. */ + int yynerrs; + + yy_state_fast_t yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + 'yyss': related to states. + 'yyvs': related to semantic values. + + Refer to the stacks through separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* Their size. */ + YYPTRDIFF_T yystacksize; + + /* The state stack. */ + yy_state_t yyssa[YYINITDEPTH]; + yy_state_t *yyss; + yy_state_t *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + int yyn; + /* The return value of yyparse. */ + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yynerrs = 0; + yystate = 0; + yyerrstatus = 0; + + yystacksize = YYINITDEPTH; + yyssp = yyss = yyssa; + yyvsp = yyvs = yyvsa; + + + YYDPRINTF ((stderr, "Starting parse\n")); + + yychar = TRACEFS_EMPTY; /* Cause a token to be read. */ + goto yysetstate; + + +/*------------------------------------------------------------. +| yynewstate -- push a new state, which is found in yystate. | +`------------------------------------------------------------*/ +yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + +/*--------------------------------------------------------------------. +| yysetstate -- set current state (the top of the stack) to yystate. | +`--------------------------------------------------------------------*/ +yysetstate: + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + YY_ASSERT (0 <= yystate && yystate < YYNSTATES); + YY_IGNORE_USELESS_CAST_BEGIN + *yyssp = YY_CAST (yy_state_t, yystate); + YY_IGNORE_USELESS_CAST_END + YY_STACK_PRINT (yyss, yyssp); + + if (yyss + yystacksize - 1 <= yyssp) +#if !defined yyoverflow && !defined YYSTACK_RELOCATE + goto yyexhaustedlab; +#else + { + /* Get the current used size of the three stacks, in elements. */ + YYPTRDIFF_T yysize = yyssp - yyss + 1; + +# if defined yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + yy_state_t *yyss1 = yyss; + YYSTYPE *yyvs1 = yyvs; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * YYSIZEOF (*yyssp), + &yyvs1, yysize * YYSIZEOF (*yyvsp), + &yystacksize); + yyss = yyss1; + yyvs = yyvs1; + } +# else /* defined YYSTACK_RELOCATE */ + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yy_state_t *yyss1 = yyss; + union yyalloc *yyptr = + YY_CAST (union yyalloc *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YY_IGNORE_USELESS_CAST_BEGIN + YYDPRINTF ((stderr, "Stack size increased to %ld\n", + YY_CAST (long, yystacksize))); + YY_IGNORE_USELESS_CAST_END + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } +#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ + if (yychar == TRACEFS_EMPTY) + { + YYDPRINTF ((stderr, "Reading a token\n")); + yychar = yylex (&yylval, scanner); + } + + if (yychar <= TRACEFS_EOF) + { + yychar = TRACEFS_EOF; + yytoken = YYSYMBOL_YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else if (yychar == TRACEFS_error) + { + /* The scanner already issued an error message, process directly + to error recovery. But do not keep the error token as + lookahead, it is too special and may lead us to an endless + loop in error recovery. */ + yychar = TRACEFS_UNDEF; + yytoken = YYSYMBOL_YYerror; + goto yyerrlab1; + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + /* Discard the shifted token. */ + yychar = TRACEFS_EMPTY; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + '$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 3: +#line 78 "sqlhist.y" + { CHECK_RETURN_PTR((yyval.string) = store_str(sb, (yyvsp[0].string))); } +#line 1328 "sqlhist.tab.c" + break; + + case 4: +#line 79 "sqlhist.y" + { CHECK_RETURN_PTR((yyval.string) = store_str(sb, (yyvsp[0].string))); } +#line 1334 "sqlhist.tab.c" + break; + + case 5: +#line 82 "sqlhist.y" + { table_start(sb); } +#line 1340 "sqlhist.tab.c" + break; + + case 9: +#line 96 "sqlhist.y" + { + CHECK_RETURN_VAL(add_selection(sb, (yyvsp[0].expr), NULL)); + } +#line 1348 "sqlhist.tab.c" + break; + + case 10: +#line 100 "sqlhist.y" + { + CHECK_RETURN_VAL(add_selection(sb, (yyvsp[-1].expr), (yyvsp[0].string))); + } +#line 1356 "sqlhist.tab.c" + break; + + case 12: +#line 107 "sqlhist.y" + { (yyval.expr) = (yyvsp[-1].expr); } +#line 1362 "sqlhist.tab.c" + break; + + case 14: +#line 109 "sqlhist.y" + { (yyval.expr) = (yyvsp[-1].expr); } +#line 1368 "sqlhist.tab.c" + break; + + case 15: +#line 110 "sqlhist.y" + { + (yyval.expr) = add_cast(sb, (yyvsp[-3].expr), (yyvsp[-1].string)); + CHECK_RETURN_PTR((yyval.expr)); + } +#line 1377 "sqlhist.tab.c" + break; + + case 16: +#line 118 "sqlhist.y" + { + (yyval.expr) = add_compare(sb, (yyvsp[-2].expr), (yyvsp[0].expr), COMPARE_ADD); + CHECK_RETURN_PTR((yyval.expr)); + } +#line 1386 "sqlhist.tab.c" + break; + + case 17: +#line 123 "sqlhist.y" + { + (yyval.expr) = add_compare(sb, (yyvsp[-2].expr), (yyvsp[0].expr), COMPARE_SUB); + CHECK_RETURN_PTR((yyval.expr)); + } +#line 1395 "sqlhist.tab.c" + break; + + case 20: +#line 135 "sqlhist.y" + { (yyval.expr) = add_field(sb, (yyvsp[0].string), NULL); CHECK_RETURN_PTR((yyval.expr)); } +#line 1401 "sqlhist.tab.c" + break; + + case 21: +#line 139 "sqlhist.y" + { (yyval.expr) = add_field(sb, (yyvsp[-1].string), (yyvsp[0].string)); CHECK_RETURN_PTR((yyval.expr)); } +#line 1407 "sqlhist.tab.c" + break; + + case 23: +#line 147 "sqlhist.y" + { (yyval.expr) = add_string(sb, (yyvsp[0].string)); CHECK_RETURN_PTR((yyval.expr)); } +#line 1413 "sqlhist.tab.c" + break; + + case 25: +#line 152 "sqlhist.y" + { (yyval.expr) = add_number(sb, (yyvsp[0].number)); CHECK_RETURN_PTR((yyval.expr)); } +#line 1419 "sqlhist.tab.c" + break; + + case 26: +#line 157 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_LT); CHECK_RETURN_PTR((yyval.expr)); } +#line 1425 "sqlhist.tab.c" + break; + + case 27: +#line 158 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_GT); CHECK_RETURN_PTR((yyval.expr)); } +#line 1431 "sqlhist.tab.c" + break; + + case 28: +#line 159 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_LE); CHECK_RETURN_PTR((yyval.expr)); } +#line 1437 "sqlhist.tab.c" + break; + + case 29: +#line 160 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_GE); CHECK_RETURN_PTR((yyval.expr)); } +#line 1443 "sqlhist.tab.c" + break; + + case 30: +#line 161 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_EQ); CHECK_RETURN_PTR((yyval.expr)); } +#line 1449 "sqlhist.tab.c" + break; + + case 31: +#line 162 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_EQ); CHECK_RETURN_PTR((yyval.expr)); } +#line 1455 "sqlhist.tab.c" + break; + + case 32: +#line 163 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_NE); CHECK_RETURN_PTR((yyval.expr)); } +#line 1461 "sqlhist.tab.c" + break; + + case 33: +#line 164 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_NE); CHECK_RETURN_PTR((yyval.expr)); } +#line 1467 "sqlhist.tab.c" + break; + + case 34: +#line 165 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_BIN_AND); CHECK_RETURN_PTR((yyval.expr)); } +#line 1473 "sqlhist.tab.c" + break; + + case 35: +#line 166 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_STR_CMP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1479 "sqlhist.tab.c" + break; + + case 36: +#line 170 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_OR); CHECK_RETURN_PTR((yyval.expr)); } +#line 1485 "sqlhist.tab.c" + break; + + case 37: +#line 171 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_AND); CHECK_RETURN_PTR((yyval.expr)); } +#line 1491 "sqlhist.tab.c" + break; + + case 38: +#line 172 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-1].expr), NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1497 "sqlhist.tab.c" + break; + + case 39: +#line 173 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[0].expr), NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1503 "sqlhist.tab.c" + break; + + case 41: +#line 178 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-2].expr), (yyvsp[0].expr), FILTER_OR); CHECK_RETURN_PTR((yyval.expr)); } +#line 1509 "sqlhist.tab.c" + break; + + case 42: +#line 179 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-1].expr), NULL, FILTER_GROUP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1515 "sqlhist.tab.c" + break; + + case 43: +#line 180 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[-1].expr), NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1521 "sqlhist.tab.c" + break; + + case 44: +#line 181 "sqlhist.y" + { (yyval.expr) = add_filter(sb, (yyvsp[0].expr), NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR((yyval.expr)); } +#line 1527 "sqlhist.tab.c" + break; + + case 46: +#line 186 "sqlhist.y" + { CHECK_RETURN_VAL(add_where(sb, (yyvsp[0].expr))); } +#line 1533 "sqlhist.tab.c" + break; + + case 56: +#line 219 "sqlhist.y" + { CHECK_RETURN_VAL(add_from(sb, (yyvsp[0].expr))); } +#line 1539 "sqlhist.tab.c" + break; + + case 57: +#line 234 "sqlhist.y" + { add_to(sb, (yyvsp[-2].expr)); } +#line 1545 "sqlhist.tab.c" + break; + + case 58: +#line 238 "sqlhist.y" + { CHECK_RETURN_VAL(add_match(sb, (yyvsp[-2].expr), (yyvsp[0].expr))); } +#line 1551 "sqlhist.tab.c" + break; + + case 59: +#line 239 "sqlhist.y" + { CHECK_RETURN_VAL(add_match(sb, (yyvsp[-2].expr), (yyvsp[0].expr))); } +#line 1557 "sqlhist.tab.c" + break; + + +#line 1561 "sqlhist.tab.c" + + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + + *++yyvsp = yyval; + + /* Now 'shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + { + const int yylhs = yyr1[yyn] - YYNTOKENS; + const int yyi = yypgoto[yylhs] + *yyssp; + yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp + ? yytable[yyi] + : yydefgoto[yylhs]); + } + + goto yynewstate; + + +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == TRACEFS_EMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; + yyerror (sb, YY_("syntax error")); + } + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= TRACEFS_EOF) + { + /* Return failure if at end of input. */ + if (yychar == TRACEFS_EOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, sb); + yychar = TRACEFS_EMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + /* Pacify compilers when the user code never invokes YYERROR and the + label yyerrorlab therefore never appears in user code. */ + if (0) + YYERROR; + + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + /* Pop stack until we find a state that shifts the error token. */ + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYSYMBOL_YYerror; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + YY_ACCESSING_SYMBOL (yystate), yyvsp, sb); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + + +#if !defined yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (sb, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + + +/*-----------------------------------------------------. +| yyreturn -- parsing is finished, return the result. | +`-----------------------------------------------------*/ +yyreturn: + if (yychar != TRACEFS_EMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, sb); + } + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + YY_ACCESSING_SYMBOL (+*yyssp), yyvsp, sb); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + + return yyresult; +} + +#line 248 "sqlhist.y" + diff --git a/src/sqlhist.tab.h b/src/sqlhist.tab.h new file mode 100644 index 0000000..b02a782 --- /dev/null +++ b/src/sqlhist.tab.h @@ -0,0 +1,118 @@ +/* A Bison parser, made by GNU Bison 3.6.4. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2020 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +#ifndef YY_TRACEFS_SQLHIST_TAB_H_INCLUDED +# define YY_TRACEFS_SQLHIST_TAB_H_INCLUDED +/* Debug traces. */ +#ifndef TRACEFS_DEBUG +# if defined YYDEBUG +#if YYDEBUG +# define TRACEFS_DEBUG 1 +# else +# define TRACEFS_DEBUG 0 +# endif +# else /* ! defined YYDEBUG */ +# define TRACEFS_DEBUG 1 +# endif /* ! defined YYDEBUG */ +#endif /* ! defined TRACEFS_DEBUG */ +#if TRACEFS_DEBUG +extern int tracefs_debug; +#endif + +/* Token kinds. */ +#ifndef TRACEFS_TOKENTYPE +# define TRACEFS_TOKENTYPE + enum tracefs_tokentype + { + TRACEFS_EMPTY = -2, + TRACEFS_EOF = 0, /* "end of file" */ + TRACEFS_error = 256, /* error */ + TRACEFS_UNDEF = 257, /* "invalid token" */ + AS = 258, /* AS */ + SELECT = 259, /* SELECT */ + FROM = 260, /* FROM */ + JOIN = 261, /* JOIN */ + ON = 262, /* ON */ + WHERE = 263, /* WHERE */ + PARSE_ERROR = 264, /* PARSE_ERROR */ + CAST = 265, /* CAST */ + NUMBER = 266, /* NUMBER */ + field_type = 267, /* field_type */ + STRING = 268, /* STRING */ + FIELD = 269, /* FIELD */ + LE = 270, /* LE */ + GE = 271, /* GE */ + EQ = 272, /* EQ */ + NEQ = 273, /* NEQ */ + AND = 274, /* AND */ + OR = 275 /* OR */ + }; + typedef enum tracefs_tokentype tracefs_token_kind_t; +#endif + +/* Value type. */ +#if ! defined TRACEFS_STYPE && ! defined TRACEFS_STYPE_IS_DECLARED +union TRACEFS_STYPE +{ +#line 46 "sqlhist.y" + + int s32; + char *string; + long number; + void *expr; + +#line 99 "sqlhist.tab.h" + +}; +typedef union TRACEFS_STYPE TRACEFS_STYPE; +# define TRACEFS_STYPE_IS_TRIVIAL 1 +# define TRACEFS_STYPE_IS_DECLARED 1 +#endif + + + +int tracefs_parse (struct sqlhist_bison *sb); +/* "%code provides" blocks. */ +#line 37 "sqlhist.y" + + #define YYSTYPE TRACEFS_STYPE + #define yylex tracefs_lex + #define yyerror tracefs_error + +#line 117 "sqlhist.tab.h" + +#endif /* !YY_TRACEFS_SQLHIST_TAB_H_INCLUDED */ diff --git a/src/sqlhist.y b/src/sqlhist.y new file mode 100644 index 0000000..fade9a4 --- /dev/null +++ b/src/sqlhist.y @@ -0,0 +1,248 @@ +%{ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "sqlhist-parse.h" + +#define scanner sb->scanner + +extern int yylex(YYSTYPE *yylval, void *); +extern void yyerror(struct sqlhist_bison *, char *fmt, ...); + +#define CHECK_RETURN_PTR(x) \ + do { \ + if (!(x)) { \ + printf("FAILED MEMORY: %s\n", #x); \ + return -ENOMEM; \ + } \ + } while (0) + +#define CHECK_RETURN_VAL(x) \ + do { \ + if ((x) < 0) { \ + printf("FAILED MEMORY: %s\n", #x); \ + return -ENOMEM; \ + } \ + } while (0) + +%} + +%define api.pure + +/* Change the globals to use tracefs_ prefix */ +%define api.prefix{tracefs_} +%code provides +{ + #define YYSTYPE TRACEFS_STYPE + #define yylex tracefs_lex + #define yyerror tracefs_error +} + +%lex-param {void *scanner} +%parse-param {struct sqlhist_bison *sb} + +%union { + int s32; + char *string; + long number; + void *expr; +} + +%token AS SELECT FROM JOIN ON WHERE PARSE_ERROR CAST +%token <number> NUMBER field_type +%token <string> STRING +%token <string> FIELD +%token <string> LE GE EQ NEQ AND OR + +%left '+' '-' +%left '*' '/' +%left '<' '>' +%left AND OR + +%type <string> name label + +%type <expr> selection_expr field item named_field +%type <expr> selection_addition +%type <expr> compare compare_list compare_cmds compare_items +%type <expr> compare_and_or +%type <expr> str_val val + +%% + +start : + select_statement + ; + +label : AS name { CHECK_RETURN_PTR($$ = store_str(sb, $2)); } + | name { CHECK_RETURN_PTR($$ = store_str(sb, $1)); } + ; + +select : SELECT { table_start(sb); } + ; + +select_statement : + select selection_list table_exp + ; + +selection_list : + selection + | selection ',' selection_list + ; + +selection : + selection_expr + { + CHECK_RETURN_VAL(add_selection(sb, $1, NULL)); + } + | selection_expr label + { + CHECK_RETURN_VAL(add_selection(sb, $1, $2)); + } + ; + +selection_expr : + field + | '(' field ')' { $$ = $2; } + | selection_addition + | '(' selection_addition ')' { $$ = $2; } + | CAST '(' field AS FIELD ')' { + $$ = add_cast(sb, $3, $5); + CHECK_RETURN_PTR($$); + } + ; + +selection_addition : + field '+' field + { + $$ = add_compare(sb, $1, $3, COMPARE_ADD); + CHECK_RETURN_PTR($$); + } + | field '-' field + { + $$ = add_compare(sb, $1, $3, COMPARE_SUB); + CHECK_RETURN_PTR($$); + } + ; + +item : + named_field + | field + ; + +field : + FIELD { $$ = add_field(sb, $1, NULL); CHECK_RETURN_PTR($$); } + ; + +named_field : + FIELD label { $$ = add_field(sb, $1, $2); CHECK_RETURN_PTR($$); } + ; + +name : + FIELD + ; + +str_val : + STRING { $$ = add_string(sb, $1); CHECK_RETURN_PTR($$); } + ; + +val : + str_val + | NUMBER { $$ = add_number(sb, $1); CHECK_RETURN_PTR($$); } + ; + + +compare : + field '<' val { $$ = add_filter(sb, $1, $3, FILTER_LT); CHECK_RETURN_PTR($$); } + | field '>' val { $$ = add_filter(sb, $1, $3, FILTER_GT); CHECK_RETURN_PTR($$); } + | field LE val { $$ = add_filter(sb, $1, $3, FILTER_LE); CHECK_RETURN_PTR($$); } + | field GE val { $$ = add_filter(sb, $1, $3, FILTER_GE); CHECK_RETURN_PTR($$); } + | field '=' val { $$ = add_filter(sb, $1, $3, FILTER_EQ); CHECK_RETURN_PTR($$); } + | field EQ val { $$ = add_filter(sb, $1, $3, FILTER_EQ); CHECK_RETURN_PTR($$); } + | field NEQ val { $$ = add_filter(sb, $1, $3, FILTER_NE); CHECK_RETURN_PTR($$); } + | field "!=" val { $$ = add_filter(sb, $1, $3, FILTER_NE); CHECK_RETURN_PTR($$); } + | field '&' val { $$ = add_filter(sb, $1, $3, FILTER_BIN_AND); CHECK_RETURN_PTR($$); } + | field '~' str_val { $$ = add_filter(sb, $1, $3, FILTER_STR_CMP); CHECK_RETURN_PTR($$); } +; + +compare_and_or : + compare_and_or OR compare_and_or { $$ = add_filter(sb, $1, $3, FILTER_OR); CHECK_RETURN_PTR($$); } + | compare_and_or AND compare_and_or { $$ = add_filter(sb, $1, $3, FILTER_AND); CHECK_RETURN_PTR($$); } + | '!' '(' compare_and_or ')' { $$ = add_filter(sb, $3, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); } + | '!' compare { $$ = add_filter(sb, $2, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); } + | compare + ; + +compare_items : + compare_items OR compare_items { $$ = add_filter(sb, $1, $3, FILTER_OR); CHECK_RETURN_PTR($$); } + | '(' compare_and_or ')' { $$ = add_filter(sb, $2, NULL, FILTER_GROUP); CHECK_RETURN_PTR($$); } + | '!' '(' compare_and_or ')' { $$ = add_filter(sb, $3, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); } + | '!' compare { $$ = add_filter(sb, $2, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); } + | compare + ; + +compare_cmds : + compare_items { CHECK_RETURN_VAL(add_where(sb, $1)); } + ; + +/* + * Top level AND is equal to ',' but the compare_cmds in them must + * all be of for the same event (start or end exclusive). + * That is, OR is not to be used between start and end events. + */ +compare_list : + compare_cmds + | compare_cmds ',' compare_list + | compare_cmds AND compare_list + ; + +where_clause : + WHERE compare_list + ; + +opt_where_clause : + /* empty */ + | where_clause +; + +opt_join_clause : + /* empty set */ + | join_clause + ; + +table_exp : + from_clause opt_join_clause opt_where_clause + ; + +from_clause : + FROM item { CHECK_RETURN_VAL(add_from(sb, $2)); } + +/* + * Select from a from clause confuses the variable parsing. + * disable it for now. + + | FROM '(' select_statement ')' label + { + from_table_end($5); + $$ = store_printf("FROM (%s) AS %s", $3, $5); + } +*/ + ; + +join_clause : + JOIN item ON match_clause { add_to(sb, $2); } + ; + +match : + item '=' item { CHECK_RETURN_VAL(add_match(sb, $1, $3)); } + | item EQ item { CHECK_RETURN_VAL(add_match(sb, $1, $3)); } + + ; + +match_clause : + match + | match ',' match_clause + ; + +%% diff --git a/src/tracefs-dynevents.c b/src/tracefs-dynevents.c new file mode 100644 index 0000000..7a3c45c --- /dev/null +++ b/src/tracefs-dynevents.c @@ -0,0 +1,779 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org> + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#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 EVENT_INDEX(B) (ffs(B) - 1) + +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 tracefs_dynevent_type type; + const char *file; + const char *prefix; + int (*del)(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn); + int (*parse)(struct dyn_events_desc *desc, const char *group, + char *line, struct tracefs_dynevent **ret_dyn); +} dynevents[] = { + {TRACEFS_DYNEVENT_KPROBE, KPROBE_EVENTS, "p", dyn_generic_del, dyn_generic_parse}, + {TRACEFS_DYNEVENT_KRETPROBE, KPROBE_EVENTS, "r", dyn_generic_del, dyn_generic_parse}, + {TRACEFS_DYNEVENT_UPROBE, UPROBE_EVENTS, "p", dyn_generic_del, dyn_generic_parse}, + {TRACEFS_DYNEVENT_URETPROBE, UPROBE_EVENTS, "r", dyn_generic_del, dyn_generic_parse}, + {TRACEFS_DYNEVENT_EPROBE, "", "e", dyn_generic_del, dyn_generic_parse}, + {TRACEFS_DYNEVENT_SYNTH, SYNTH_EVENTS, "", dyn_synth_del, dyn_synth_parse}, +}; + + + +static 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; +} + +/** + * tracefs_dynevent_free - Free a dynamic event context + * @devent: Pointer to a dynamic event context + * + * The dynamic event, described by this context, is not + * removed from the system by this API. It only frees the memory. + */ +void tracefs_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 void parse_prefix(char *word, char **prefix, char **system, char **name) +{ + char *sav; + + *prefix = NULL; + *system = NULL; + *name = NULL; + + *prefix = strtok_r(word, ":", &sav); + *system = strtok_r(NULL, "/", &sav); + if (!(*system)) + return; + + *name = strtok_r(NULL, " \t", &sav); + if (!(*name)) { + *name = *system; + *system = NULL; + } +} + +/* + * Parse lines from dynamic_events, kprobe_events and uprobe_events files + * PREFIX[:[SYSTEM/]EVENT] [ADDRSS] [FORMAT] + */ +static int dyn_generic_parse(struct dyn_events_desc *desc, const char *group, + char *line, struct tracefs_dynevent **ret_dyn) +{ + struct tracefs_dynevent *dyn; + char *word; + char *format = NULL; + char *address = NULL; + char *system; + char *prefix; + char *event; + char *sav; + + if (strncmp(line, desc->prefix, strlen(desc->prefix))) + return -1; + + word = strtok_r(line, " \t", &sav); + if (!word || *word == '\0') + return -1; + + parse_prefix(word, &prefix, &system, &event); + if (!prefix) + return -1; + + if (desc->type != TRACEFS_DYNEVENT_SYNTH) { + address = strtok_r(NULL, " \t", &sav); + if (!address || *address == '\0') + return -1; + } + + format = strtok_r(NULL, "", &sav); + + /* KPROBEs and UPROBEs share the same prefix, check the format */ + if (desc->type & (TRACEFS_DYNEVENT_UPROBE | TRACEFS_DYNEVENT_URETPROBE)) { + if (!strchr(address, '/')) + return -1; + } + + if (group && (!system || strcmp(group, system) != 0)) + return -1; + + if (!ret_dyn) + return 0; + + 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->prefix = strdup(prefix); + if (!dyn->prefix) + goto error; + + if (system) { + dyn->system = strdup(system); + if (!dyn->system) + goto error; + } + + if (event) { + dyn->event = strdup(event); + if (!dyn->event) + goto error; + } + + if (address) { + dyn->address = strdup(address); + if (!dyn->address) + goto error; + } + + if (format) { + dyn->format = strdup(format); + if (!dyn->format) + goto error; + } + + *ret_dyn = dyn; + return 0; +error: + tracefs_dynevent_free(dyn); + return -1; +} + +static 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; +} + +/* + * Parse lines from synthetic_events file + * EVENT ARG [ARG] + */ +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; + char *event; + char *sav; + + if (!strcmp(desc->file, DYNEVENTS_EVENTS)) + return dyn_generic_parse(desc, group, line, ret_dyn); + + /* synthetic_events file has slightly different syntax */ + event = strtok_r(line, " \t", &sav); + if (!event || *event == '\0') + return -1; + + format = strtok_r(NULL, "", &sav); + if (!format || *format == '\0') + return -1; + + if (!ret_dyn) + return 0; + + 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(event); + if (!dyn->event) + goto error; + + dyn->format = strdup(format+1); + if (!dyn->format) + goto error; + + *ret_dyn = dyn; + return 0; +error: + tracefs_dynevent_free(dyn); + return -1; +} + +static void init_devent_desc(void) +{ + int i; + + BUILD_BUG_ON(ARRAY_SIZE(dynevents) != EVENT_INDEX(TRACEFS_DYNEVENT_MAX)); + + if (!tracefs_file_exists(NULL, DYNEVENTS_EVENTS)) + return; + + /* Use ftrace dynamic_events, if available */ + for (i = 0; i < EVENT_INDEX(TRACEFS_DYNEVENT_MAX); i++) + dynevents[i].file = DYNEVENTS_EVENTS; + + dynevents[EVENT_INDEX(TRACEFS_DYNEVENT_SYNTH)].prefix = "s"; +} + +static struct dyn_events_desc *get_devent_desc(enum tracefs_dynevent_type type) +{ + + static bool init; + + if (type >= TRACEFS_DYNEVENT_MAX) + return NULL; + + if (!init) { + init_devent_desc(); + init = true; + } + + return &dynevents[EVENT_INDEX(type)]; +} + +/** + * dynevent_alloc - Allocate new dynamic event + * @type: Type of the dynamic event + * @system: The system name (NULL for the default dynamic) + * @event: Name of the event + * @addr: The function and offset (or address) to insert the probe + * @format: The format string to define the probe. + * + * Allocate a dynamic event context that will be in the @system group + * (or dynamic if @system is NULL). Have the name of @event and + * will be associated to @addr, if applicable for that event type + * (function name, with or without offset, or a address). And the @format will + * define the format of the kprobe. + * The dynamic event is not created in the system. + * + * Return a pointer to a dynamic event context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + * errno will be set to EINVAL if event is NULL. + */ +__hidden struct tracefs_dynevent * +dynevent_alloc(enum tracefs_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; + + 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: + tracefs_dynevent_free(devent); + return NULL; +} + +/** + * tracefs_dynevent_create - Create a dynamic event in the system + * @devent: Pointer to a dynamic event context, describing the event + * + * Return 0 on success, or -1 on error. + */ +int tracefs_dynevent_create(struct tracefs_dynevent *devent) +{ + char *str; + int ret; + + if (!devent) + return -1; + + if (devent->system && devent->system[0]) + ret = asprintf(&str, "%s%s%s/%s %s %s\n", + devent->prefix, strlen(devent->prefix) ? ":" : "", + devent->system, devent->event, + devent->address ? devent->address : "", + devent->format ? devent->format : ""); + else + ret = asprintf(&str, "%s%s%s %s %s\n", + devent->prefix, strlen(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; +} + +static void disable_events(const char *system, const char *event, + char **list) +{ + struct tracefs_instance *instance; + int i; + + /* + * Note, this will not fail even on error. + * That is because even if something fails, it may still + * work enough to clear the kprobes. If that's the case + * the clearing after the loop will succeed and the function + * is a success, even though other parts had failed. If + * one of the kprobe events is enabled in one of the + * instances that fail, then the clearing will fail too + * and the function will return an error. + */ + + tracefs_event_disable(NULL, system, event); + /* No need to test results */ + + if (!list) + return; + + for (i = 0; list[i]; i++) { + instance = tracefs_instance_alloc(NULL, list[i]); + /* If this fails, try the next one */ + if (!instance) + continue; + tracefs_event_disable(instance, system, event); + tracefs_instance_free(instance); + } +} + +/** + * tracefs_dynevent_destroy - Remove a dynamic event from the system + * @devent: A dynamic event context, describing the dynamic event that will be deleted. + * @force: Will attempt to disable all events before removing them. + * + * The dynamic event context is not freed by this API. It only removes the event from the system. + * If there are any enabled events, and @force is not set, then it will error with -1 and errno + * to be EBUSY. + * + * Return 0 on success, or -1 on error. + */ +int tracefs_dynevent_destroy(struct tracefs_dynevent *devent, bool force) +{ + struct dyn_events_desc *desc; + char **instance_list; + + if (!devent) + return -1; + + if (force) { + instance_list = tracefs_instances(NULL); + disable_events(devent->system, devent->event, instance_list); + tracefs_list_free(instance_list); + } + + desc = get_devent_desc(devent->type); + if (!desc) + return -1; + + return desc->del(desc, devent); +} + +static int get_all_dynevents(enum tracefs_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; + + line = content; + do { + next = strchr(line, '\n'); + if (next) + *next = '\0'; + ret = desc->parse(desc, system, line, ret_all ? &devent : NULL); + if (!ret) { + if (ret_all) { + tmp = realloc(all, (count + 1) * sizeof(*tmp)); + if (!tmp) + goto error; + all = tmp; + all[count] = devent; + } + count++; + } + line = next + 1; + } while (next); + + free(content); + if (ret_all) + *ret_all = all; + 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) +{ + int i; + + if (!events) + return; + + for (i = 0; events[i]; i++) + tracefs_dynevent_free(events[i]); + + free(events); +} + +/** + * tracefs_dynevent_get_all - return an array of pointers to dynamic events of given types + * @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types + * are considered. + * @system: Get events from that system only. If @system is NULL, events from all systems + * are returned. + * + * Returns an array of pointers to dynamic events of given types that exist in the system. + * The array must be freed with tracefs_dynevent_list_free(). If there are no events a NULL + * pointer is returned. + */ +struct tracefs_dynevent ** +tracefs_dynevent_get_all(unsigned int types, const char *system) +{ + struct tracefs_dynevent **events, **tmp, **all_events = NULL; + int count, all = 0; + int i; + + for (i = 1; i < TRACEFS_DYNEVENT_MAX; i <<= 1) { + if (types) { + if (i > types) + break; + if (!(types & i)) + continue; + } + count = get_all_dynevents(i, system, &events); + if (count > 0) { + tmp = realloc(all_events, (all + count + 1) * sizeof(*tmp)); + if (!tmp) + goto error; + all_events = tmp; + memcpy(all_events + all, events, count * sizeof(*events)); + all += count; + /* Add a NULL pointer at the end */ + all_events[all] = NULL; + free(events); + } + } + + return all_events; + +error: + if (all_events) { + for (i = 0; i < all; i++) + free(all_events[i]); + free(all_events); + } + return NULL; +} + +/** + * tracefs_dynevent_get - return a single dynamic event if it exists + * @type; Dynamic event type + * @system: Get events from that system only. May be NULL. + * @event: Get event of the system type (may not be NULL) + * + * Returns the dynamic event of the given @type and @system for with the @event + * name. If @system is NULL, it will return the first dynamic event that it finds + * that matches the @event name. + * + * The returned event must be freed with tracefs_dynevent_free(). + * NULL is returned if no event match is found, or other error. + */ +struct tracefs_dynevent * +tracefs_dynevent_get(enum tracefs_dynevent_type type, const char *system, + const char *event) +{ + struct tracefs_dynevent **events; + struct tracefs_dynevent *devent = NULL; + int count; + int i; + + if (!event) { + errno = -EINVAL; + return NULL; + } + + count = get_all_dynevents(type, system, &events); + if (count <= 0) + return NULL; + + for (i = 0; i < count; i++) { + if (strcmp(events[i]->event, event) == 0) + break; + } + if (i < count) { + devent = events[i]; + events[i] = NULL; + } + + tracefs_dynevent_list_free(events); + + return devent; +} + +/** + * tracefs_dynevent_destroy_all - removes all dynamic events of given types from the system + * @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types + * are considered. + * @force: Will attempt to disable all events before removing them. + * + * Will remove all dynamic events of the given types from the system. If there are any enabled + * events, and @force is not set, then the removal of these will fail. If @force is set, then + * it will attempt to disable all the events in all instances before removing them. + * + * Returns zero if all requested events are removed successfully, or -1 if some of them are not + * removed. + */ +int tracefs_dynevent_destroy_all(unsigned int types, bool force) +{ + struct tracefs_dynevent **all; + int ret = 0; + int i; + + all = tracefs_dynevent_get_all(types, NULL); + if (!all) + return 0; + + for (i = 0; all[i]; i++) { + if (tracefs_dynevent_destroy(all[i], force)) + ret = -1; + } + + tracefs_dynevent_list_free(all); + + return ret; +} + +/** + * dynevent_get_count - Count dynamic events of given types and system + * @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types + * are considered. + * @system: Count events from that system only. If @system is NULL, events from all systems + * are counted. + * + * Return the count of requested dynamic events + */ +__hidden int dynevent_get_count(unsigned int types, const char *system) +{ + int count, all = 0; + int i; + + for (i = 1; i < TRACEFS_DYNEVENT_MAX; i <<= 1) { + if (types) { + if (i > types) + break; + if (!(types & i)) + continue; + } + count = get_all_dynevents(i, system, NULL); + if (count > 0) + all += count; + } + + return all; +} + +static enum tracefs_dynevent_type +dynevent_info(struct tracefs_dynevent *dynevent, char **system, + char **event, char **prefix, char **addr, char **format) +{ + char **lv[] = { system, event, prefix, addr, format }; + char **rv[] = { &dynevent->system, &dynevent->event, &dynevent->prefix, + &dynevent->address, &dynevent->format }; + int i; + + for (i = 0; i < ARRAY_SIZE(lv); i++) { + if (lv[i]) { + if (*rv[i]) { + *lv[i] = strdup(*rv[i]); + if (!*lv[i]) + goto error; + } else { + *lv[i] = NULL; + } + } + } + + return dynevent->type; + +error: + for (i--; i >= 0; i--) { + if (lv[i]) + free(*lv[i]); + } + + return TRACEFS_DYNEVENT_UNKNOWN; +} + +/** + * tracefs_dynevent_info - return details of a dynamic event + * @dynevent: A dynamic event context, describing given dynamic event. + * @group: return, group in which the dynamic event is configured + * @event: return, name of the dynamic event + * @prefix: return, prefix string of the dynamic event + * @addr: return, the function and offset (or address) of the dynamic event + * @format: return, the format string of the dynamic event + * + * Returns the type of the dynamic event, or TRACEFS_DYNEVENT_UNKNOWN in case of an error. + * Any of the @group, @event, @prefix, @addr and @format parameters are optional. + * If a valid pointer is passed, in case of success - a string is allocated and returned. + * These strings must be freed with free(). + */ +enum tracefs_dynevent_type +tracefs_dynevent_info(struct tracefs_dynevent *dynevent, char **system, + char **event, char **prefix, char **addr, char **format) +{ + if (!dynevent) + return TRACEFS_DYNEVENT_UNKNOWN; + + return dynevent_info(dynevent, system, event, prefix, addr, format); +} + +/** + * tracefs_dynevent_get_event - return tep event representing the given dynamic event + * @tep: a handle to the trace event parser context that holds the events + * @dynevent: a dynamic event context, describing given dynamic event. + * + * Returns a pointer to a tep event describing the given dynamic event. The pointer + * is managed by the @tep handle and must not be freed. In case of an error, or in case + * the requested dynamic event is missing in the @tep handler - NULL is returned. + */ +struct tep_event * +tracefs_dynevent_get_event(struct tep_handle *tep, struct tracefs_dynevent *dynevent) +{ + if (!tep || !dynevent || !dynevent->event) + return NULL; + + return get_tep_event(tep, dynevent->system, dynevent->event); +} diff --git a/src/tracefs-eprobes.c b/src/tracefs-eprobes.c new file mode 100644 index 0000000..cc25f8e --- /dev/null +++ b/src/tracefs-eprobes.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#include <stdlib.h> +#include <errno.h> + +#include "tracefs.h" +#include "tracefs-local.h" + +#define EPROBE_DEFAULT_GROUP "eprobes" + +/** + * tracefs_eprobe_alloc - Allocate new eprobe + * @system: The system name (NULL for the default eprobes) + * @event: The name of the event to create + * @target_system: The system of the target event + * @target_event: The name of the target event + * @fetchargs: String with arguments, that will be fetched from @target_event + * + * Allocate an eprobe context that will be in the @system group (or eprobes if + * @system is NULL). Have the name of @event. The new eprobe will be attached to + * given @target_event which is in the given @target_system. The arguments + * described in @fetchargs will fetched from the @target_event. + * + * The eprobe is not created in the system. + * + * Return a pointer to a eprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + */ +struct tracefs_dynevent * +tracefs_eprobe_alloc(const char *system, const char *event, + const char *target_system, const char *target_event, const char *fetchargs) +{ + struct tracefs_dynevent *kp; + char *target; + + if (!event || !target_system || !target_event) { + errno = EINVAL; + return NULL; + } + + if (!system) + system = EPROBE_DEFAULT_GROUP; + + if (asprintf(&target, "%s.%s", target_system, target_event) < 0) + return NULL; + + kp = dynevent_alloc(TRACEFS_DYNEVENT_EPROBE, system, event, target, fetchargs); + free(target); + + return kp; +} + diff --git a/src/tracefs-events.c b/src/tracefs-events.c new file mode 100644 index 0000000..c2adf41 --- /dev/null +++ b/src/tracefs-events.c @@ -0,0 +1,1515 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + * Updates: + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <dirent.h> +#include <unistd.h> +#include <errno.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <limits.h> + +#include <kbuffer.h> + +#include "tracefs.h" +#include "tracefs-local.h" + +static struct follow_event *root_followers; +static int nr_root_followers; + +static struct follow_event *root_missed_followers; +static int nr_root_missed_followers; + +struct cpu_iterate { + struct tracefs_cpu *tcpu; + struct tep_record record; + struct tep_event *event; + struct kbuffer *kbuf; + void *page; + int psize; + int cpu; +}; + +static int read_kbuf_record(struct cpu_iterate *cpu) +{ + unsigned long long ts; + void *ptr; + + if (!cpu || !cpu->kbuf) + return -1; + ptr = kbuffer_read_event(cpu->kbuf, &ts); + if (!ptr) + return -1; + + memset(&cpu->record, 0, sizeof(cpu->record)); + cpu->record.ts = ts; + cpu->record.size = kbuffer_event_size(cpu->kbuf); + cpu->record.record_size = kbuffer_curr_size(cpu->kbuf); + cpu->record.missed_events = kbuffer_missed_events(cpu->kbuf); + cpu->record.cpu = cpu->cpu; + cpu->record.data = ptr; + cpu->record.ref_count = 1; + + kbuffer_next_event(cpu->kbuf, NULL); + + return 0; +} + +int read_next_page(struct tep_handle *tep, struct cpu_iterate *cpu) +{ + enum kbuffer_long_size long_size; + enum kbuffer_endian endian; + int r; + + if (!cpu->tcpu) + return -1; + + r = tracefs_cpu_buffered_read(cpu->tcpu, cpu->page, true); + /* + * tracefs_cpu_buffered_read() only reads in full subbuffer size, + * but this wants partial buffers as well. If the function returns + * empty (-1 for EAGAIN), try tracefs_cpu_read() next, as that can + * read partially filled buffers too, but isn't as efficient. + */ + if (r <= 0) + r = tracefs_cpu_read(cpu->tcpu, cpu->page, true); + if (r <= 0) + return -1; + + if (!cpu->kbuf) { + if (tep_is_file_bigendian(tep)) + endian = KBUFFER_ENDIAN_BIG; + else + endian = KBUFFER_ENDIAN_LITTLE; + + if (tep_get_header_page_size(tep) == 8) + long_size = KBUFFER_LSIZE_8; + else + long_size = KBUFFER_LSIZE_4; + + cpu->kbuf = kbuffer_alloc(long_size, endian); + if (!cpu->kbuf) + return -1; + } + + kbuffer_load_subbuffer(cpu->kbuf, cpu->page); + if (kbuffer_subbuffer_size(cpu->kbuf) > r) { + tracefs_warning("%s: page_size > %d", __func__, r); + return -1; + } + + return 0; +} + +int read_next_record(struct tep_handle *tep, struct cpu_iterate *cpu) +{ + int id; + + do { + while (!read_kbuf_record(cpu)) { + id = tep_data_type(tep, &(cpu->record)); + cpu->event = tep_find_event(tep, id); + if (cpu->event) + return 0; + } + } while (!read_next_page(tep, cpu)); + + return -1; +} + +/** + * tracefs_follow_missed_events - Add callback for missed events for iterators + * @instance: The instance to follow + * @callback: The function to call when missed events is detected + * @callback_data: The data to pass to @callback + * + * This attaches a callback to an @instance or the root instance if @instance + * is NULL, where if tracefs_iterate_raw_events() is called, that if missed + * events are detected, it will call @callback, with the following parameters: + * @event: The event pointer of the record with the missing events + * @record; The event instance of @event. + * @cpu: The cpu that the event happened on. + * @callback_data: The same as @callback_data passed to the function. + * + * If the count of missing events is available, @record->missed_events + * will have a positive number holding the number of missed events since + * the last event on the same CPU, or just -1 if that number is unknown + * but missed events did happen. + * + * Returns 0 on success and -1 on error. + */ +int tracefs_follow_missed_events(struct tracefs_instance *instance, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_data) +{ + struct follow_event **followers; + struct follow_event *follower; + struct follow_event follow; + int *nr_followers; + + follow.event = NULL; + follow.callback = callback; + follow.callback_data = callback_data; + + if (instance) { + followers = &instance->missed_followers; + nr_followers = &instance->nr_missed_followers; + } else { + followers = &root_missed_followers; + nr_followers = &nr_root_missed_followers; + } + follower = realloc(*followers, sizeof(*follower) * + ((*nr_followers) + 1)); + if (!follower) + return -1; + + *followers = follower; + follower[(*nr_followers)++] = follow; + + return 0; +} + +static int call_missed_events(struct tracefs_instance *instance, + struct tep_event *event, struct tep_record *record, int cpu) +{ + struct follow_event *followers; + int nr_followers; + int ret = 0; + int i; + + if (instance) { + followers = instance->missed_followers; + nr_followers = instance->nr_missed_followers; + } else { + followers = root_missed_followers; + nr_followers = nr_root_missed_followers; + } + + if (!followers) + return 0; + + for (i = 0; i < nr_followers; i++) { + ret |= followers[i].callback(event, record, + cpu, followers[i].callback_data); + } + + return ret; +} + +static int call_followers(struct tracefs_instance *instance, + struct tep_event *event, struct tep_record *record, int cpu) +{ + struct follow_event *followers; + int nr_followers; + int ret = 0; + int i; + + if (record->missed_events) + ret = call_missed_events(instance, event, record, cpu); + if (ret) + return ret; + + if (instance) { + followers = instance->followers; + nr_followers = instance->nr_followers; + } else { + followers = root_followers; + nr_followers = nr_root_followers; + } + + if (!followers) + return 0; + + for (i = 0; i < nr_followers; i++) { + if (followers[i].event == event) + ret |= followers[i].callback(event, record, + cpu, followers[i].callback_data); + } + + return ret; +} + +static int read_cpu_pages(struct tep_handle *tep, struct tracefs_instance *instance, + struct cpu_iterate *cpus, int count, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_context, + bool *keep_going) +{ + bool has_data = false; + int ret; + int i, j; + + for (i = 0; i < count; i++) { + ret = read_next_record(tep, cpus + i); + if (!ret) + has_data = true; + } + + while (has_data && *(volatile bool *)keep_going) { + j = count; + for (i = 0; i < count; i++) { + if (!cpus[i].event) + continue; + if (j == count || cpus[j].record.ts > cpus[i].record.ts) + j = i; + } + if (j < count) { + if (call_followers(instance, cpus[j].event, &cpus[j].record, cpus[j].cpu)) + break; + if (callback && + callback(cpus[j].event, &cpus[j].record, cpus[j].cpu, callback_context)) + break; + cpus[j].event = NULL; + read_next_record(tep, cpus + j); + } else { + has_data = false; + } + } + + return 0; +} + +static int open_cpu_files(struct tracefs_instance *instance, cpu_set_t *cpus, + int cpu_size, struct cpu_iterate **all_cpus, int *count) +{ + struct tracefs_cpu *tcpu; + struct cpu_iterate *tmp; + int nr_cpus; + int cpu; + int i = 0; + + *all_cpus = NULL; + + nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + for (cpu = 0; cpu < nr_cpus; cpu++) { + if (cpus && !CPU_ISSET_S(cpu, cpu_size, cpus)) + continue; + tcpu = tracefs_cpu_open(instance, cpu, true); + tmp = realloc(*all_cpus, (i + 1) * sizeof(*tmp)); + if (!tmp) { + i--; + goto error; + } + + *all_cpus = tmp; + + memset(tmp + i, 0, sizeof(*tmp)); + + if (!tcpu) + goto error; + + tmp[i].tcpu = tcpu; + tmp[i].cpu = cpu; + tmp[i].psize = tracefs_cpu_read_size(tcpu); + tmp[i].page = malloc(tmp[i].psize); + + if (!tmp[i++].page) + goto error; + } + *count = i; + return 0; + error: + tmp = *all_cpus; + for (; i >= 0; i--) { + tracefs_cpu_close(tmp[i].tcpu); + free(tmp[i].page); + } + free(tmp); + *all_cpus = NULL; + return -1; +} + +/** + * tracefs_follow_event - Add callback for specific events for iterators + * @tep: a handle to the trace event parser context + * @instance: The instance to follow + * @system: The system of the event to track + * @event_name: The name of the event to track + * @callback: The function to call when the event is hit in an iterator + * @callback_data: The data to pass to @callback + * + * This attaches a callback to an @instance or the root instance if @instance + * is NULL, where if tracefs_iterate_raw_events() is called, that if the specified + * event is hit, it will call @callback, with the following parameters: + * @event: The event pointer that was found by @system and @event_name. + * @record; The event instance of @event. + * @cpu: The cpu that the event happened on. + * @callback_data: The same as @callback_data passed to the function. + * + * Returns 0 on success and -1 on error. + */ +int tracefs_follow_event(struct tep_handle *tep, struct tracefs_instance *instance, + const char *system, const char *event_name, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_data) +{ + struct follow_event **followers; + struct follow_event *follower; + struct follow_event follow; + int *nr_followers; + + if (!tep) { + errno = EINVAL; + return -1; + } + + follow.event = tep_find_event_by_name(tep, system, event_name); + if (!follow.event) { + errno = ENOENT; + return -1; + } + + follow.callback = callback; + follow.callback_data = callback_data; + + if (instance) { + followers = &instance->followers; + nr_followers = &instance->nr_followers; + } else { + followers = &root_followers; + nr_followers = &nr_root_followers; + } + follower = realloc(*followers, sizeof(*follower) * + ((*nr_followers) + 1)); + if (!follower) + return -1; + + *followers = follower; + follower[(*nr_followers)++] = follow; + + return 0; +} + +static bool top_iterate_keep_going; + +/* + * tracefs_iterate_raw_events - Iterate through events in trace_pipe_raw, + * per CPU trace buffers + * @tep: a handle to the trace event parser context + * @instance: ftrace instance, can be NULL for the top instance + * @cpus: Iterate only through the buffers of CPUs, set in the mask. + * If NULL, iterate through all CPUs. + * @cpu_size: size of @cpus set + * @callback: A user function, called for each record from the file + * @callback_context: A custom context, passed to the user callback function + * + * If the @callback returns non-zero, the iteration stops - in that case all + * records from the current page will be lost from future reads + * The events are iterated in sorted order, oldest first. + * + * Returns -1 in case of an error, or 0 otherwise + */ +int tracefs_iterate_raw_events(struct tep_handle *tep, + struct tracefs_instance *instance, + cpu_set_t *cpus, int cpu_size, + int (*callback)(struct tep_event *, + struct tep_record *, + int, void *), + void *callback_context) +{ + bool *keep_going = instance ? &instance->iterate_keep_going : + &top_iterate_keep_going; + struct follow_event *followers; + struct cpu_iterate *all_cpus; + int count = 0; + int ret; + int i; + + (*(volatile bool *)keep_going) = true; + + if (!tep) + return -1; + + if (instance) + followers = instance->followers; + else + followers = root_followers; + if (!callback && !followers) + return -1; + + ret = open_cpu_files(instance, cpus, cpu_size, &all_cpus, &count); + if (ret < 0) + goto out; + ret = read_cpu_pages(tep, instance, all_cpus, count, + callback, callback_context, + keep_going); + +out: + if (all_cpus) { + for (i = 0; i < count; i++) { + kbuffer_free(all_cpus[i].kbuf); + tracefs_cpu_close(all_cpus[i].tcpu); + free(all_cpus[i].page); + } + free(all_cpus); + } + + return ret; +} + +/** + * tracefs_iterate_stop - stop the iteration over the raw events. + * @instance: ftrace instance, can be NULL for top tracing instance. + */ +void tracefs_iterate_stop(struct tracefs_instance *instance) +{ + if (instance) + instance->iterate_keep_going = false; + else + top_iterate_keep_going = false; +} + +static int add_list_string(char ***list, const char *name) +{ + char **tmp; + + tmp = tracefs_list_add(*list, name); + if (!tmp) { + tracefs_list_free(*list); + *list = NULL; + return -1; + } + + *list = tmp; + return 0; +} + +__hidden char *trace_append_file(const char *dir, const char *name) +{ + char *file; + int ret; + + ret = asprintf(&file, "%s/%s", dir, name); + + return ret < 0 ? NULL : file; +} + +static int event_file(char **path, const char *system, + const char *event, const char *file) +{ + if (!system || !event || !file) + return -1; + + return asprintf(path, "events/%s/%s/%s", + system, event, file); +} + +/** + * tracefs_event_get_file - return a file in an event directory + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * + * Returns a path to a file in the event director. + * or NULL on error. The path returned must be freed with + * tracefs_put_tracing_file(). + */ +char *tracefs_event_get_file(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file) +{ + char *instance_path; + char *path; + int ret; + + ret = event_file(&path, system, event, file); + if (ret < 0) + return NULL; + + instance_path = tracefs_instance_get_file(instance, path); + free(path); + + return instance_path; +} + +/** + * tracefs_event_file_read - read the content from an event file + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * @psize: the size of the content read. + * + * Reads the content of the event file that is passed via the + * arguments and returns the content. + * + * Return a string containing the content of the file or NULL + * on error. The string returned must be freed with free(). + */ +char *tracefs_event_file_read(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file, int *psize) +{ + char *content; + char *path; + int ret; + + ret = event_file(&path, system, event, file); + if (ret < 0) + return NULL; + + content = tracefs_instance_file_read(instance, path, psize); + free(path); + return content; +} + +/** + * tracefs_event_file_write - write to an event file + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * @str: The string to write into the file + * + * Writes the content of @str to a file in the instance directory. + * The content of the file will be overwritten by @str. + * + * Return 0 on success, and -1 on error. + */ +int tracefs_event_file_write(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file, const char *str) +{ + char *path; + int ret; + + ret = event_file(&path, system, event, file); + if (ret < 0) + return -1; + + ret = tracefs_instance_file_write(instance, path, str); + free(path); + return ret; +} + +/** + * tracefs_event_file_append - write to an event file + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * @str: The string to write into the file + * + * Writes the content of @str to a file in the instance directory. + * The content of @str will be appended to the content of the file. + * The current content should not be lost. + * + * Return 0 on success, and -1 on error. + */ +int tracefs_event_file_append(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file, const char *str) +{ + char *path; + int ret; + + ret = event_file(&path, system, event, file); + if (ret < 0) + return -1; + + ret = tracefs_instance_file_append(instance, path, str); + free(path); + return ret; +} + +/** + * tracefs_event_file_clear - clear an event file + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * + * Clears the content of the event file. That is, it is opened + * with O_TRUNC and then closed. + * + * Return 0 on success, and -1 on error. + */ +int tracefs_event_file_clear(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file) +{ + char *path; + int ret; + + ret = event_file(&path, system, event, file); + if (ret < 0) + return -1; + + ret = tracefs_instance_file_clear(instance, path); + free(path); + return ret; +} + +/** + * tracefs_event_file_exits - test if a file exists + * @instance: The instance the event is in (NULL for top level) + * @system: The system name that the event file is in + * @event: The event name of the event + * @file: The name of the file in the event directory. + * + * Return true if the file exists, false if it odes not or + * an error occurred. + */ +bool tracefs_event_file_exists(struct tracefs_instance *instance, + const char *system, const char *event, + const char *file) +{ + char *path; + bool ret; + + if (event_file(&path, system, event, file) < 0) + return false; + + ret = tracefs_file_exists(instance, path); + free(path); + return ret; +} + +/** + * tracefs_event_systems - return list of systems for tracing + * @tracing_dir: directory holding the "events" directory + * if NULL, top tracing directory is used + * + * Returns an allocated list of system names. Both the names and + * the list must be freed with tracefs_list_free() + * The list returned ends with a "NULL" pointer + */ +char **tracefs_event_systems(const char *tracing_dir) +{ + struct dirent *dent; + char **systems = NULL; + char *events_dir; + struct stat st; + DIR *dir; + int ret; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + if (!tracing_dir) + return NULL; + + events_dir = trace_append_file(tracing_dir, "events"); + if (!events_dir) + return NULL; + + /* + * Search all the directories in the events directory, + * and collect the ones that have the "enable" file. + */ + ret = stat(events_dir, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + goto out_free; + + dir = opendir(events_dir); + if (!dir) + goto out_free; + + while ((dent = readdir(dir))) { + const char *name = dent->d_name; + char *enable; + char *sys; + + if (strcmp(name, ".") == 0 || + strcmp(name, "..") == 0) + continue; + + sys = trace_append_file(events_dir, name); + ret = stat(sys, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) { + free(sys); + continue; + } + + enable = trace_append_file(sys, "enable"); + + ret = stat(enable, &st); + if (ret >= 0) { + if (add_list_string(&systems, name) < 0) + goto out_free; + } + free(enable); + free(sys); + } + + closedir(dir); + + out_free: + free(events_dir); + return systems; +} + +/** + * tracefs_system_events - return list of events for system + * @tracing_dir: directory holding the "events" directory + * @system: the system to return the events for + * + * Returns an allocated list of event names. Both the names and + * the list must be freed with tracefs_list_free() + * The list returned ends with a "NULL" pointer + */ +char **tracefs_system_events(const char *tracing_dir, const char *system) +{ + struct dirent *dent; + char **events = NULL; + char *system_dir = NULL; + struct stat st; + DIR *dir; + int ret; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + if (!tracing_dir || !system) + return NULL; + + asprintf(&system_dir, "%s/events/%s", tracing_dir, system); + if (!system_dir) + return NULL; + + ret = stat(system_dir, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + goto out_free; + + dir = opendir(system_dir); + if (!dir) + goto out_free; + + while ((dent = readdir(dir))) { + const char *name = dent->d_name; + char *event; + + if (strcmp(name, ".") == 0 || + strcmp(name, "..") == 0) + continue; + + event = trace_append_file(system_dir, name); + ret = stat(event, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) { + free(event); + continue; + } + + if (add_list_string(&events, name) < 0) + goto out_free; + + free(event); + } + + closedir(dir); + + out_free: + free(system_dir); + + return events; +} + +/** + * tracefs_tracers - returns an array of available tracers + * @tracing_dir: The directory that contains the tracing directory + * + * Returns an allocate list of plugins. The array ends with NULL + * Both the plugin names and array must be freed with tracefs_list_free() + */ +char **tracefs_tracers(const char *tracing_dir) +{ + char *available_tracers; + struct stat st; + char **plugins = NULL; + char *buf; + char *str, *saveptr; + char *plugin; + int slen; + int len; + int ret; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + if (!tracing_dir) + return NULL; + + available_tracers = trace_append_file(tracing_dir, "available_tracers"); + if (!available_tracers) + return NULL; + + ret = stat(available_tracers, &st); + if (ret < 0) + goto out_free; + + len = str_read_file(available_tracers, &buf, true); + if (len <= 0) + goto out_free; + + for (str = buf; ; str = NULL) { + plugin = strtok_r(str, " ", &saveptr); + if (!plugin) + break; + slen = strlen(plugin); + if (!slen) + continue; + + /* chop off any newlines */ + if (plugin[slen - 1] == '\n') + plugin[slen - 1] = '\0'; + + /* Skip the non tracers */ + if (strcmp(plugin, "nop") == 0 || + strcmp(plugin, "none") == 0) + continue; + + if (add_list_string(&plugins, plugin) < 0) + break; + } + free(buf); + + out_free: + free(available_tracers); + + return plugins; +} + +static int load_events(struct tep_handle *tep, + const char *tracing_dir, const char *system, bool check) +{ + int ret = 0, failure = 0; + char **events = NULL; + struct stat st; + int len = 0; + int i; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + events = tracefs_system_events(tracing_dir, system); + if (!events) + return -ENOENT; + + for (i = 0; events[i]; i++) { + char *format; + char *buf; + + ret = asprintf(&format, "%s/events/%s/%s/format", + tracing_dir, system, events[i]); + if (ret < 0) { + failure = -ENOMEM; + break; + } + + ret = stat(format, &st); + if (ret < 0) + goto next_event; + + /* check if event is already added, to avoid duplicates */ + if (check && tep_find_event_by_name(tep, system, events[i])) + goto next_event; + + len = str_read_file(format, &buf, true); + if (len <= 0) + goto next_event; + + ret = tep_parse_event(tep, buf, len, system); + free(buf); +next_event: + free(format); + if (ret) + failure = ret; + } + + tracefs_list_free(events); + return failure; +} + +__hidden int trace_rescan_events(struct tep_handle *tep, + const char *tracing_dir, const char *system) +{ + /* ToDo: add here logic for deleting removed events from tep handle */ + return load_events(tep, tracing_dir, system, true); +} + +__hidden int trace_load_events(struct tep_handle *tep, + const char *tracing_dir, const char *system) +{ + return load_events(tep, tracing_dir, system, false); +} + +__hidden struct tep_event *get_tep_event(struct tep_handle *tep, + const char *system, const char *name) +{ + struct tep_event *event; + + /* Check if event exists in the system */ + if (!tracefs_event_file_exists(NULL, system, name, "format")) + return NULL; + + /* If the event is already loaded in the tep, return it */ + event = tep_find_event_by_name(tep, system, name); + if (event) + return event; + + /* Try to load any new events from the given system */ + if (trace_rescan_events(tep, NULL, system)) + return NULL; + + return tep_find_event_by_name(tep, system, name); +} + +static int read_header(struct tep_handle *tep, const char *tracing_dir) +{ + struct stat st; + char *header; + char *buf; + int len; + int ret = -1; + + header = trace_append_file(tracing_dir, "events/header_page"); + + ret = stat(header, &st); + if (ret < 0) + goto out; + + len = str_read_file(header, &buf, true); + if (len <= 0) + goto out; + + tep_parse_header_page(tep, buf, len, sizeof(long)); + + free(buf); + + ret = 0; + out: + free(header); + return ret; +} + +static bool contains(const char *name, const char * const *names) +{ + if (!names) + return false; + for (; *names; names++) + if (strcmp(name, *names) == 0) + return true; + return false; +} + +static void load_kallsyms(struct tep_handle *tep) +{ + char *buf; + + if (str_read_file("/proc/kallsyms", &buf, false) <= 0) + return; + + tep_parse_kallsyms(tep, buf); + free(buf); +} + +static int load_saved_cmdlines(const char *tracing_dir, + struct tep_handle *tep, bool warn) +{ + char *path; + char *buf; + int ret; + + path = trace_append_file(tracing_dir, "saved_cmdlines"); + if (!path) + return -1; + + ret = str_read_file(path, &buf, false); + free(path); + if (ret <= 0) + return -1; + + ret = tep_parse_saved_cmdlines(tep, buf); + free(buf); + + return ret; +} + +static void load_printk_formats(const char *tracing_dir, + struct tep_handle *tep) +{ + char *path; + char *buf; + int ret; + + path = trace_append_file(tracing_dir, "printk_formats"); + if (!path) + return; + + ret = str_read_file(path, &buf, false); + free(path); + if (ret <= 0) + return; + + tep_parse_printk_formats(tep, buf); + free(buf); +} + +/* + * Do a best effort attempt to load kallsyms, saved_cmdlines and + * printk_formats. If they can not be loaded, then this will not + * do the mappings. But this does not fail the loading of events. + */ +static void load_mappings(const char *tracing_dir, + struct tep_handle *tep) +{ + load_kallsyms(tep); + + /* If there's no tracing_dir no reason to go further */ + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + if (!tracing_dir) + return; + + load_saved_cmdlines(tracing_dir, tep, false); + load_printk_formats(tracing_dir, tep); +} + +int tracefs_load_cmdlines(const char *tracing_dir, struct tep_handle *tep) +{ + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + if (!tracing_dir) + return -1; + + return load_saved_cmdlines(tracing_dir, tep, true); +} + +static int fill_local_events_system(const char *tracing_dir, + struct tep_handle *tep, + const char * const *sys_names, + int *parsing_failures) +{ + char **systems = NULL; + int ret; + int i; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + if (!tracing_dir) + return -1; + + systems = tracefs_event_systems(tracing_dir); + if (!systems) + return -1; + + ret = read_header(tep, tracing_dir); + if (ret < 0) { + ret = -1; + goto out; + } + + if (parsing_failures) + *parsing_failures = 0; + + for (i = 0; systems[i]; i++) { + if (sys_names && !contains(systems[i], sys_names)) + continue; + ret = trace_load_events(tep, tracing_dir, systems[i]); + if (ret && parsing_failures) + (*parsing_failures)++; + } + + /* Include ftrace, as it is excluded for not having "enable" file */ + if (!sys_names || contains("ftrace", sys_names)) + trace_load_events(tep, tracing_dir, "ftrace"); + + load_mappings(tracing_dir, tep); + + /* always succeed because parsing failures are not critical */ + ret = 0; +out: + tracefs_list_free(systems); + return ret; +} + +static void set_tep_cpus(const char *tracing_dir, struct tep_handle *tep) +{ + struct stat st; + char path[PATH_MAX]; + int cpus = sysconf(_SC_NPROCESSORS_CONF); + int max_cpu = 0; + int ret; + int i; + + if (!tracing_dir) + tracing_dir = tracefs_tracing_dir(); + + /* + * Paranoid: in case sysconf() above does not work. + * And we also only care about the number of tracing + * buffers that exist. If cpus is 32, but the top half + * is offline, there may only be 16 tracing buffers. + * That's what we want to know. + */ + for (i = 0; !cpus || i < cpus; i++) { + snprintf(path, PATH_MAX, "%s/per_cpu/cpu%d", tracing_dir, i); + ret = stat(path, &st); + if (!ret && S_ISDIR(st.st_mode)) + max_cpu = i + 1; + else if (i >= cpus) + break; + } + + if (!max_cpu) + max_cpu = cpus; + + tep_set_cpus(tep, max_cpu); +} + +/** + * tracefs_local_events_system - create a tep from the events of the specified subsystem. + * + * @tracing_dir: The directory that contains the events. + * @sys_name: Array of system names, to load the events from. + * The last element from the array must be NULL + * + * Returns a tep structure that contains the tep local to + * the system. + */ +struct tep_handle *tracefs_local_events_system(const char *tracing_dir, + const char * const *sys_names) +{ + struct tep_handle *tep = NULL; + + tep = tep_alloc(); + if (!tep) + return NULL; + + if (fill_local_events_system(tracing_dir, tep, sys_names, NULL)) { + tep_free(tep); + tep = NULL; + } + + set_tep_cpus(tracing_dir, tep); + + /* Set the long size for this tep handle */ + tep_set_long_size(tep, tep_get_header_page_size(tep)); + + return tep; +} + +/** + * tracefs_local_events - create a tep from the events on system + * @tracing_dir: The directory that contains the events. + * + * Returns a tep structure that contains the teps local to + * the system. + */ +struct tep_handle *tracefs_local_events(const char *tracing_dir) +{ + return tracefs_local_events_system(tracing_dir, NULL); +} + +/** + * tracefs_fill_local_events - Fill a tep with the events on system + * @tracing_dir: The directory that contains the events. + * @tep: Allocated tep handler which will be filled + * @parsing_failures: return number of failures while parsing the event files + * + * Returns whether the operation succeeded + */ +int tracefs_fill_local_events(const char *tracing_dir, + struct tep_handle *tep, int *parsing_failures) +{ + return fill_local_events_system(tracing_dir, tep, + NULL, parsing_failures); +} + +static bool match(const char *str, regex_t *re) +{ + return regexec(re, str, 0, NULL, 0) == 0; +} + +enum event_state { + STATE_INIT, + STATE_ENABLED, + STATE_DISABLED, + STATE_MIXED, + STATE_ERROR, +}; + +static int read_event_state(struct tracefs_instance *instance, const char *file, + enum event_state *state) +{ + char *val; + int ret = 0; + + if (*state == STATE_ERROR) + return -1; + + val = tracefs_instance_file_read(instance, file, NULL); + if (!val) + return -1; + + switch (val[0]) { + case '0': + switch (*state) { + case STATE_INIT: + *state = STATE_DISABLED; + break; + case STATE_ENABLED: + *state = STATE_MIXED; + break; + default: + break; + } + break; + case '1': + switch (*state) { + case STATE_INIT: + *state = STATE_ENABLED; + break; + case STATE_DISABLED: + *state = STATE_MIXED; + break; + default: + break; + } + break; + case 'X': + *state = STATE_MIXED; + break; + default: + *state = TRACEFS_ERROR; + ret = -1; + break; + } + free(val); + + return ret; +} + +static int enable_disable_event(struct tracefs_instance *instance, + const char *system, const char *event, + bool enable, enum event_state *state) +{ + const char *str = enable ? "1" : "0"; + char *system_event; + int ret; + + ret = asprintf(&system_event, "events/%s/%s/enable", system, event); + if (ret < 0) + return ret; + + if (state) + ret = read_event_state(instance, system_event, state); + else + ret = tracefs_instance_file_write(instance, system_event, str); + free(system_event); + + return ret; +} + +static int enable_disable_system(struct tracefs_instance *instance, + const char *system, bool enable, + enum event_state *state) +{ + const char *str = enable ? "1" : "0"; + char *system_path; + int ret; + + ret = asprintf(&system_path, "events/%s/enable", system); + if (ret < 0) + return ret; + + if (state) + ret = read_event_state(instance, system_path, state); + else + ret = tracefs_instance_file_write(instance, system_path, str); + free(system_path); + + return ret; +} + +static int enable_disable_all(struct tracefs_instance *instance, + bool enable) +{ + const char *str = enable ? "1" : "0"; + int ret; + + ret = tracefs_instance_file_write(instance, "events/enable", str); + return ret < 0 ? ret : 0; +} + +static int make_regex(regex_t *re, const char *match) +{ + int len = strlen(match); + char str[len + 3]; + char *p = &str[0]; + + if (!len || match[0] != '^') + *(p++) = '^'; + + strcpy(p, match); + p += len; + + if (!len || match[len-1] != '$') + *(p++) = '$'; + + *p = '\0'; + + return regcomp(re, str, REG_ICASE|REG_NOSUB); +} + +static int event_enable_disable(struct tracefs_instance *instance, + const char *system, const char *event, + bool enable, enum event_state *state) +{ + regex_t system_re, event_re; + char **systems; + char **events = NULL; + int ret = -1; + int s, e; + + /* Handle all events first */ + if (!system && !event) + return enable_disable_all(instance, enable); + + systems = tracefs_event_systems(NULL); + if (!systems) + goto out_free; + + if (system) { + ret = make_regex(&system_re, system); + if (ret < 0) + goto out_free; + } + if (event) { + ret = make_regex(&event_re, event); + if (ret < 0) { + if (system) + regfree(&system_re); + goto out_free; + } + } + + ret = -1; + for (s = 0; systems[s]; s++) { + if (system && !match(systems[s], &system_re)) + continue; + + /* Check for the short cut first */ + if (!event) { + ret = enable_disable_system(instance, systems[s], enable, state); + if (ret < 0) + break; + ret = 0; + continue; + } + + events = tracefs_system_events(NULL, systems[s]); + if (!events) + continue; /* Error? */ + + for (e = 0; events[e]; e++) { + if (!match(events[e], &event_re)) + continue; + ret = enable_disable_event(instance, systems[s], + events[e], enable, state); + if (ret < 0) + break; + ret = 0; + } + tracefs_list_free(events); + events = NULL; + } + if (system) + regfree(&system_re); + if (event) + regfree(&event_re); + + out_free: + tracefs_list_free(systems); + tracefs_list_free(events); + return ret; +} + +/** + * tracefs_event_enable - enable specified events + * @instance: ftrace instance, can be NULL for the top instance + * @system: A regex of a system (NULL to match all systems) + * @event: A regex of the event in the system (NULL to match all events) + * + * This will enable events that match the @system and @event. + * If both @system and @event are NULL, then it will enable all events. + * If @system is NULL, it will look at all systems for matching events + * to @event. + * If @event is NULL, then it will enable all events in the systems + * that match @system. + * + * Returns 0 on success, and -1 if it encountered an error, + * or if no events matched. If no events matched, then -1 is set + * but errno will not be. + */ +int tracefs_event_enable(struct tracefs_instance *instance, + const char *system, const char *event) +{ + return event_enable_disable(instance, system, event, true, NULL); +} + +int tracefs_event_disable(struct tracefs_instance *instance, + const char *system, const char *event) +{ + return event_enable_disable(instance, system, event, false, NULL); +} + +/** + * tracefs_event_is_enabled - return if the event is enabled or not + * @instance: ftrace instance, can be NULL for the top instance + * @system: The name of the system to check + * @event: The name of the event to check + * + * Checks is an event or multiple events are enabled. + * + * If @system is NULL, then it will check all the systems where @event is + * a match. + * + * If @event is NULL, then it will check all events where @system is a match. + * + * If both @system and @event are NULL, then it will check all events + * + * Returns TRACEFS_ALL_ENABLED if all matching are enabled. + * Returns TRACEFS_SOME_ENABLED if some are enabled and some are not + * Returns TRACEFS_ALL_DISABLED if none of the events are enabled. + * Returns TRACEFS_ERROR if there is an error reading the events. + */ +enum tracefs_enable_state +tracefs_event_is_enabled(struct tracefs_instance *instance, + const char *system, const char *event) +{ + enum event_state state = STATE_INIT; + int ret; + + ret = event_enable_disable(instance, system, event, false, &state); + + if (ret < 0) + return TRACEFS_ERROR; + + switch (state) { + case STATE_ENABLED: + return TRACEFS_ALL_ENABLED; + case STATE_DISABLED: + return TRACEFS_ALL_DISABLED; + case STATE_MIXED: + return TRACEFS_SOME_ENABLED; + default: + return TRACEFS_ERROR; + } +} diff --git a/src/tracefs-filter.c b/src/tracefs-filter.c new file mode 100644 index 0000000..a3dd77b --- /dev/null +++ b/src/tracefs-filter.c @@ -0,0 +1,807 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org> + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#include <trace-seq.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> + +#include "tracefs.h" +#include "tracefs-local.h" + +enum { + S_START, + S_COMPARE, + S_NOT, + S_CONJUNCTION, + S_OPEN_PAREN, + S_CLOSE_PAREN, +}; + +static const struct tep_format_field common_timestamp = { + .type = "u64", + .name = "common_timestamp", + .size = 8, +}; + +static const struct tep_format_field common_timestamp_usecs = { + .type = "u64", + .name = "common_timestamp.usecs", + .size = 8, +}; + +static const struct tep_format_field common_comm = { + .type = "char *", + .name = "common_comm", + .size = 16, +}; + +/* + * This also must be able to accept fields that are OK via the histograms, + * such as common_timestamp. + */ +static const struct tep_format_field *get_event_field(struct tep_event *event, + const char *field_name) +{ + const struct tep_format_field *field; + + if (!strcmp(field_name, TRACEFS_TIMESTAMP)) + return &common_timestamp; + + if (!strcmp(field_name, TRACEFS_TIMESTAMP_USECS)) + return &common_timestamp_usecs; + + field = tep_find_any_field(event, field_name); + if (!field && (!strcmp(field_name, "COMM") || !strcmp(field_name, "comm"))) + return &common_comm; + + return field; +} + +__hidden bool +trace_verify_event_field(struct tep_event *event, + const char *field_name, + const struct tep_format_field **ptr_field) +{ + const struct tep_format_field *field; + + field = get_event_field(event, field_name); + if (!field) { + errno = ENODEV; + return false; + } + + if (ptr_field) + *ptr_field = field; + + return true; +} + +__hidden int trace_test_state(int state) +{ + switch (state) { + case S_START: + case S_CLOSE_PAREN: + case S_COMPARE: + return 0; + } + + errno = EBADE; + return -1; +} + +static int append_filter(char **filter, unsigned int *state, + unsigned int *open_parens, + struct tep_event *event, + enum tracefs_filter type, + const char *field_name, + enum tracefs_compare compare, + const char *val) +{ + const struct tep_format_field *field; + bool is_string; + char *conj = "||"; + char *tmp; + + switch (type) { + case TRACEFS_FILTER_COMPARE: + switch (*state) { + case S_START: + case S_OPEN_PAREN: + case S_CONJUNCTION: + case S_NOT: + break; + default: + goto inval; + } + break; + + case TRACEFS_FILTER_AND: + conj = "&&"; + /* Fall through */ + case TRACEFS_FILTER_OR: + switch (*state) { + case S_COMPARE: + case S_CLOSE_PAREN: + break; + default: + goto inval; + } + /* Don't lose old filter on failure */ + tmp = strdup(*filter); + if (!tmp) + return -1; + tmp = append_string(tmp, NULL, conj); + if (!tmp) + return -1; + free(*filter); + *filter = tmp; + *state = S_CONJUNCTION; + return 0; + + case TRACEFS_FILTER_NOT: + switch (*state) { + case S_START: + case S_OPEN_PAREN: + case S_CONJUNCTION: + case S_NOT: + break; + default: + goto inval; + } + if (*filter) { + tmp = strdup(*filter); + tmp = append_string(tmp, NULL, "!"); + } else { + tmp = strdup("!"); + } + if (!tmp) + return -1; + free(*filter); + *filter = tmp; + *state = S_NOT; + return 0; + + case TRACEFS_FILTER_OPEN_PAREN: + switch (*state) { + case S_START: + case S_OPEN_PAREN: + case S_NOT: + case S_CONJUNCTION: + break; + default: + goto inval; + } + if (*filter) { + tmp = strdup(*filter); + tmp = append_string(tmp, NULL, "("); + } else { + tmp = strdup("("); + } + if (!tmp) + return -1; + free(*filter); + *filter = tmp; + *state = S_OPEN_PAREN; + (*open_parens)++; + return 0; + + case TRACEFS_FILTER_CLOSE_PAREN: + switch (*state) { + case S_CLOSE_PAREN: + case S_COMPARE: + break; + default: + goto inval; + } + if (!*open_parens) + goto inval; + + tmp = strdup(*filter); + if (!tmp) + return -1; + tmp = append_string(tmp, NULL, ")"); + if (!tmp) + return -1; + free(*filter); + *filter = tmp; + *state = S_CLOSE_PAREN; + (*open_parens)--; + return 0; + } + + if (!field_name || !val) + goto inval; + + if (!trace_verify_event_field(event, field_name, &field)) + return -1; + + is_string = field->flags & TEP_FIELD_IS_STRING; + + if (!is_string && (field->flags & TEP_FIELD_IS_ARRAY)) + goto inval; + + if (*filter) { + tmp = strdup(*filter); + if (!tmp) + return -1; + tmp = append_string(tmp, NULL, field_name); + } else { + tmp = strdup(field_name); + } + + switch (compare) { + case TRACEFS_COMPARE_EQ: tmp = append_string(tmp, NULL, " == "); break; + case TRACEFS_COMPARE_NE: tmp = append_string(tmp, NULL, " != "); break; + case TRACEFS_COMPARE_RE: + if (!is_string) + goto inval; + tmp = append_string(tmp, NULL, "~"); + break; + default: + if (is_string) + goto inval; + } + + switch (compare) { + case TRACEFS_COMPARE_GT: tmp = append_string(tmp, NULL, " > "); break; + case TRACEFS_COMPARE_GE: tmp = append_string(tmp, NULL, " >= "); break; + case TRACEFS_COMPARE_LT: tmp = append_string(tmp, NULL, " < "); break; + case TRACEFS_COMPARE_LE: tmp = append_string(tmp, NULL, " <= "); break; + case TRACEFS_COMPARE_AND: tmp = append_string(tmp, NULL, " & "); break; + default: break; + } + + tmp = append_string(tmp, NULL, val); + + if (!tmp) + return -1; + + free(*filter); + *filter = tmp; + *state = S_COMPARE; + + return 0; +inval: + errno = EINVAL; + return -1; +} + +static int count_parens(char *filter, unsigned int *state) +{ + bool backslash = false; + int quote = 0; + int open = 0; + int i; + + if (!filter) + return 0; + + for (i = 0; filter[i]; i++) { + if (quote) { + if (backslash) + backslash = false; + else if (filter[i] == '\\') + backslash = true; + else if (quote == filter[i]) + quote = 0; + continue; + } + + switch (filter[i]) { + case '(': + *state = S_OPEN_PAREN; + open++; + break; + case ')': + *state = S_CLOSE_PAREN; + open--; + break; + case '\'': + case '"': + *state = S_COMPARE; + quote = filter[i]; + break; + case '!': + switch (filter[i+1]) { + case '=': + case '~': + *state = S_COMPARE; + i++; + break; + default: + *state = S_NOT; + } + break; + case '&': + case '|': + if (filter[i] == filter[i+1]) { + *state = S_CONJUNCTION; + i++; + break; + } + /* Fall through */ + case '0' ... '9': + case 'a' ... 'z': + case 'A' ... 'Z': + case '_': case '+': case '-': case '*': case '/': + *state = S_COMPARE; + break; + } + } + return open; +} + +__hidden int trace_append_filter(char **filter, unsigned int *state, + unsigned int *open_parens, + struct tep_event *event, + enum tracefs_filter type, + const char *field_name, + enum tracefs_compare compare, + const char *val) +{ + return append_filter(filter, state, open_parens, event, type, + field_name, compare, val); +} + +/** + * tracefs_filter_string_append - create or append a filter for an event + * @event: tep_event to create / append a filter for + * @filter: Pointer to string to append to (pointer to NULL to create) + * @type: The type of element to add to the filter + * @field: For @type == TRACEFS_FILTER_COMPARE, the field to compare + * @compare: For @type == TRACEFS_FILTER_COMPARE, how to compare @field to @val + * @val: For @type == TRACEFS_FILTER_COMPARE, what value @field is to be + * + * This will put together a filter string for the starting event + * of @synth. It check to make sure that what is added is correct compared + * to the filter that is already built. + * + * @type can be: + * TRACEFS_FILTER_COMPARE: See below + * TRACEFS_FILTER_AND: Append "&&" to the filter + * TRACEFS_FILTER_OR: Append "||" to the filter + * TRACEFS_FILTER_NOT: Append "!" to the filter + * TRACEFS_FILTER_OPEN_PAREN: Append "(" to the filter + * TRACEFS_FILTER_CLOSE_PAREN: Append ")" to the filter + * + * For all types except TRACEFS_FILTER_COMPARE, the @field, @compare, + * and @val are ignored. + * + * For @type == TRACEFS_FILTER_COMPARE. + * + * @field is the name of the field for the start event to compare. + * If it is not a field for the start event, this return an + * error. + * + * @compare can be one of: + * TRACEFS_COMPARE_EQ: Test @field == @val + * TRACEFS_COMPARE_NE: Test @field != @val + * TRACEFS_COMPARE_GT: Test @field > @val + * TRACEFS_COMPARE_GE: Test @field >= @val + * TRACEFS_COMPARE_LT: Test @field < @val + * TRACEFS_COMPARE_LE: Test @field <= @val + * TRACEFS_COMPARE_RE: Test @field ~ @val + * TRACEFS_COMPARE_AND: Test @field & @val + * + * If the @field is of type string, and @compare is not + * TRACEFS_COMPARE_EQ, TRACEFS_COMPARE_NE or TRACEFS_COMPARE_RE, + * then this will return an error. + * + * Various other checks are made, for instance, if more CLOSE_PARENs + * are added than existing OPEN_PARENs. Or if AND is added after an + * OPEN_PAREN or another AND or an OR or a NOT. + * + * Returns 0 on success and -1 on failure. + */ +int tracefs_filter_string_append(struct tep_event *event, char **filter, + enum tracefs_filter type, + const char *field, enum tracefs_compare compare, + const char *val) +{ + unsigned int open_parens; + unsigned int state = 0; + char *str = NULL; + int open; + int ret; + + if (!filter) { + errno = EINVAL; + return -1; + } + + open = count_parens(*filter, &state); + if (open < 0) { + errno = EINVAL; + return -1; + } + + if (*filter) { + /* append_filter() will free filter on error */ + str = strdup(*filter); + if (!str) + return -1; + } + open_parens = open; + + ret = append_filter(&str, &state, &open_parens, + event, type, field, compare, val); + if (!ret) { + free(*filter); + *filter = str; + } + + return ret; +} + +static int error_msg(char **err, char *str, + const char *filter, int i, const char *msg) +{ + char ws[i+2]; + char *errmsg; + + free(str); + + /* msg is NULL for parsing append_filter failing */ + if (!msg) { + switch(errno) { + case ENODEV: + msg = "field not valid"; + break; + default: + msg = "Invalid filter"; + + } + } else + errno = EINVAL; + + if (!err) + return -1; + + if (!filter) { + *err = strdup(msg); + return -1; + } + + memset(ws, ' ', i); + ws[i] = '^'; + ws[i+1] = '\0'; + + errmsg = strdup(filter); + errmsg = append_string(errmsg, "\n", ws); + errmsg = append_string(errmsg, "\n", msg); + errmsg = append_string(errmsg, NULL, "\n"); + + *err = errmsg; + return -1; +} + +static int get_field_end(const char *filter, int i, int *end) +{ + int start_i = i; + + for (; filter[i]; i++) { + switch(filter[i]) { + case '0' ... '9': + if (i == start_i) + return 0; + /* Fall through */ + case 'a' ... 'z': + case 'A' ... 'Z': + case '_': + continue; + default: + *end = i; + return i - start_i; + } + } + *end = i; + return i - start_i; +} + +static int get_compare(const char *filter, int i, enum tracefs_compare *cmp) +{ + int start_i = i; + + for (; filter[i]; i++) { + if (!isspace(filter[i])) + break; + } + + switch(filter[i]) { + case '=': + if (filter[i+1] != '=') + goto err; + *cmp = TRACEFS_COMPARE_EQ; + i++; + break; + case '!': + if (filter[i+1] == '=') { + *cmp = TRACEFS_COMPARE_NE; + i++; + break; + } + if (filter[i+1] == '~') { + /* todo! */ + } + goto err; + case '>': + if (filter[i+1] == '=') { + *cmp = TRACEFS_COMPARE_GE; + i++; + break; + } + *cmp = TRACEFS_COMPARE_GT; + break; + case '<': + if (filter[i+1] == '=') { + *cmp = TRACEFS_COMPARE_LE; + i++; + break; + } + *cmp = TRACEFS_COMPARE_LT; + break; + case '~': + *cmp = TRACEFS_COMPARE_RE; + break; + case '&': + *cmp = TRACEFS_COMPARE_AND; + break; + default: + goto err; + } + i++; + + for (; filter[i]; i++) { + if (!isspace(filter[i])) + break; + } + return i - start_i; + err: + return start_i - i; /* negative or zero */ +} + +static int get_val_end(const char *filter, int i, int *end) +{ + bool backslash = false; + int start_i = i; + int quote; + + switch (filter[i]) { + case '0': + i++; + if (tolower(filter[i+1]) != 'x' && + !isdigit(filter[i+1])) + break; + /* fall through */ + case '1' ... '9': + switch (tolower(filter[i])) { + case 'x': + for (i++; filter[i]; i++) { + if (!isxdigit(filter[i])) + break; + } + break; + case '0': + for (i++; filter[i]; i++) { + if (filter[i] < '0' || + filter[i] > '7') + break; + } + break; + default: + for (i++; filter[i]; i++) { + if (!isdigit(filter[i])) + break; + } + break; + } + break; + case '"': + case '\'': + quote = filter[i]; + for (i++; filter[i]; i++) { + if (backslash) { + backslash = false; + continue; + } + switch (filter[i]) { + case '\\': + backslash = true; + continue; + case '"': + case '\'': + if (filter[i] == quote) + break; + continue; + default: + continue; + } + break; + } + if (filter[i]) + i++; + break; + default: + break; + } + + *end = i; + return i - start_i; +} + +/** + * tracefs_filter_string_verify - verify a given filter works for an event + * @event: The event to test the given filter for + * @filter: The filter to test + * @err: Error message for syntax errors (NULL to ignore) + * + * Parse the @filter to verify that it is valid for the given @event. + * + * Returns 0 on succes and -1 on error, and except for memory allocation + * errors, @err will be allocated with an error message. It must + * be freed with free(). + */ +int tracefs_filter_string_verify(struct tep_event *event, const char *filter, + char **err) +{ + enum tracefs_filter filter_type; + enum tracefs_compare compare; + char *str = NULL; + char buf[(filter ? strlen(filter) : 0) + 1]; + char *field; + char *val; + unsigned int state = 0; + unsigned int open = 0; + int len; + int end; + int n; + int i; + + if (!filter) + return error_msg(err, str, NULL, 0, "No filter given"); + + len = strlen(filter); + + for (i = 0; i < len; i++) { + field = NULL; + val = NULL; + compare = 0; + + switch (filter[i]) { + case '(': + filter_type = TRACEFS_FILTER_OPEN_PAREN; + break; + case ')': + filter_type = TRACEFS_FILTER_CLOSE_PAREN; + break; + case '!': + filter_type = TRACEFS_FILTER_NOT; + break; + case '&': + case '|': + + if (filter[i] == filter[i+1]) { + i++; + if (filter[i] == '&') + filter_type = TRACEFS_FILTER_AND; + else + filter_type = TRACEFS_FILTER_OR; + break; + } + if (filter[i] == '|') + return error_msg(err, str, filter, i, + "Invalid op"); + + return error_msg(err, str, filter, i, + "Invalid location for '&'"); + default: + if (isspace(filter[i])) + continue; + + field = buf; + + n = get_field_end(filter, i, &end); + if (!n) + return error_msg(err, str, filter, i, + "Invalid field name"); + + strncpy(field, filter+i, n); + + i += n; + field[n++] = '\0'; + + val = field + n; + + n = get_compare(filter, i, &compare); + if (n <= 0) + return error_msg(err, str, filter, i - n, + "Invalid compare"); + + i += n; + get_val_end(filter, i, &end); + n = end - i; + if (!n) + return error_msg(err, str, filter, i, + "Invalid value"); + strncpy(val, filter + i, n); + val[n] = '\0'; + i += n - 1; + + filter_type = TRACEFS_FILTER_COMPARE; + break; + } + n = append_filter(&str, &state, &open, + event, filter_type, field, compare, val); + + if (n < 0) + return error_msg(err, str, filter, i, NULL); + } + + if (open) + return error_msg(err, str, filter, i, + "Not enough closed parenthesis"); + switch (state) { + case S_COMPARE: + case S_CLOSE_PAREN: + break; + default: + return error_msg(err, str, filter, i, + "Unfinished filter"); + } + + free(str); + return 0; +} + +/** + * tracefs_event_filter_apply - apply given filter on event in given instance + * @instance: The instance in which the filter will be applied (NULL for toplevel). + * @event: The event to apply the filter on. + * @filter: The filter to apply. + * + * Apply the @filter to given @event in givem @instance. The @filter string + * should be created with tracefs_filter_string_append(). + * + * Returns 0 on succes and -1 on error. + */ +int tracefs_event_filter_apply(struct tracefs_instance *instance, + struct tep_event *event, const char *filter) +{ + return tracefs_event_file_write(instance, event->system, event->name, + "filter", filter); +} + +/** + * tracefs_event_filter_clear - clear the filter on event in given instance + * @instance: The instance in which the filter will be applied (NULL for toplevel). + * @event: The event to apply the filter on. + * + * Returns 0 on succes and -1 on error. + */ +int tracefs_event_filter_clear(struct tracefs_instance *instance, + struct tep_event *event) +{ + return tracefs_event_file_write(instance, event->system, event->name, + "filter", "0"); +} + +/** Deprecated **/ +int tracefs_event_append_filter(struct tep_event *event, char **filter, + enum tracefs_filter type, + const char *field, enum tracefs_compare compare, + const char *val) +{ + return tracefs_filter_string_append(event, filter, type, field, + compare, val); +} +int tracefs_event_verify_filter(struct tep_event *event, const char *filter, + char **err) +{ + return tracefs_filter_string_verify(event, filter, err); +} diff --git a/src/tracefs-hist.c b/src/tracefs-hist.c new file mode 100644 index 0000000..fb6231e --- /dev/null +++ b/src/tracefs-hist.c @@ -0,0 +1,2401 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org> + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <dirent.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <sys/time.h> +#include <sys/types.h> + +#include "tracefs.h" +#include "tracefs-local.h" + +#define HIST_FILE "hist" + +#define ASCENDING ".ascending" +#define DESCENDING ".descending" + +#define SYNTHETIC_GROUP "synthetic" + +struct tracefs_hist { + struct tep_handle *tep; + struct tep_event *event; + char *system; + char *event_name; + char *name; + char **keys; + char **values; + char **sort; + char *filter; + int size; + unsigned int filter_parens; + unsigned int filter_state; +}; + +/* + * tracefs_hist_get_name - get the name of the histogram + * @hist: The histogram to get the name for + * + * Returns name string owned by @hist on success, or NULL on error. + */ +const char *tracefs_hist_get_name(struct tracefs_hist *hist) +{ + return hist ? hist->name : NULL; +} + +/* + * tracefs_hist_get_event - get the event name of the histogram + * @hist: The histogram to get the event name for + * + * Returns event name string owned by @hist on success, or NULL on error. + */ +const char *tracefs_hist_get_event(struct tracefs_hist *hist) +{ + return hist ? hist->event_name : NULL; +} + +/* + * tracefs_hist_get_system - get the system name of the histogram + * @hist: The histogram to get the system name for + * + * Returns system name string owned by @hist on success, or NULL on error. + */ +const char *tracefs_hist_get_system(struct tracefs_hist *hist) +{ + return hist ? hist->system : NULL; +} + +static void add_list(struct trace_seq *seq, const char *start, + char **list) +{ + int i; + + trace_seq_puts(seq, start); + for (i = 0; list[i]; i++) { + if (i) + trace_seq_putc(seq, ','); + trace_seq_puts(seq, list[i]); + } +} + +static void add_hist_commands(struct trace_seq *seq, struct tracefs_hist *hist, + enum tracefs_hist_command command) +{ + if (command == TRACEFS_HIST_CMD_DESTROY) + trace_seq_putc(seq, '!'); + + add_list(seq, "hist:keys=", hist->keys); + + if (hist->values) + add_list(seq, ":vals=", hist->values); + + if (hist->sort) + add_list(seq, ":sort=", hist->sort); + + if (hist->size) + trace_seq_printf(seq, ":size=%d", hist->size); + + switch(command) { + case TRACEFS_HIST_CMD_START: break; + case TRACEFS_HIST_CMD_PAUSE: trace_seq_puts(seq, ":pause"); break; + case TRACEFS_HIST_CMD_CONT: trace_seq_puts(seq, ":cont"); break; + case TRACEFS_HIST_CMD_CLEAR: trace_seq_puts(seq, ":clear"); break; + default: break; + } + + if (hist->name) + trace_seq_printf(seq, ":name=%s", hist->name); + + if (hist->filter) + trace_seq_printf(seq, " if %s", hist->filter); + +} + +/* + * trace_hist_echo_cmd - show how to start the histogram + * @seq: A trace_seq to store the commands to create + * @hist: The histogram to write into the trigger file + * @command: If not zero, can pause, continue or clear the histogram + * + * This shows the echo commands to create the histogram for an event + * with the given fields. + * + * Returns 0 on succes -1 on error. + */ +int +tracefs_hist_echo_cmd(struct trace_seq *seq, struct tracefs_instance *instance, + struct tracefs_hist *hist, + enum tracefs_hist_command command) +{ + const char *system = hist->system; + const char *event = hist->event_name; + char *path; + + if (!hist->keys) { + errno = -EINVAL; + return -1; + } + + path = tracefs_event_get_file(instance, system, event, "trigger"); + if (!path) + return -1; + + trace_seq_puts(seq, "echo '"); + + add_hist_commands(seq, hist, command); + + trace_seq_printf(seq, "' > %s\n", path); + + tracefs_put_tracing_file(path); + + return 0; +} + +/* + * tracefs_hist_command - Create, start, pause, destroy a histogram for an event + * @instance: The instance the histogram will be in (NULL for toplevel) + * @hist: The histogram to write into the trigger file + * @command: Command to perform on a histogram. + * + * Creates, pause, continue, clears, or destroys a histogram. + * + * Returns 0 on succes -1 on error. + */ +int tracefs_hist_command(struct tracefs_instance *instance, + struct tracefs_hist *hist, + enum tracefs_hist_command command) +{ + const char *system = hist->system; + const char *event = hist->event_name; + struct trace_seq seq; + int ret; + + if (!tracefs_event_file_exists(instance, system, event, HIST_FILE)) + return -1; + + errno = -EINVAL; + if (!hist->keys) + return -1; + + trace_seq_init(&seq); + + add_hist_commands(&seq, hist, command); + + trace_seq_putc(&seq, '\n'); + trace_seq_terminate(&seq); + + ret = -1; + if (seq.state == TRACE_SEQ__GOOD) + ret = tracefs_event_file_append(instance, system, event, + "trigger", seq.buffer); + + trace_seq_destroy(&seq); + + return ret < 0 ? -1 : 0; +} + +/** + * tracefs_hist_free - free a tracefs_hist element + * @hist: The histogram to free + */ +void tracefs_hist_free(struct tracefs_hist *hist) +{ + if (!hist) + return; + + tep_unref(hist->tep); + free(hist->system); + free(hist->event_name); + free(hist->name); + free(hist->filter); + tracefs_list_free(hist->keys); + tracefs_list_free(hist->values); + tracefs_list_free(hist->sort); + free(hist); +} + +/** + * tracefs_hist_alloc - Initialize one-dimensional histogram + * @tep: The tep handle that has the @system and @event. + * @system: The system the histogram event is in. + * @event_name: The name of the event that the histogram will be attached to. + * @key: The primary key the histogram will use + * @type: The format type of the key. + * + * Will initialize a histogram descriptor that will be attached to + * the @system/@event with the given @key as the primary. This only + * initializes the descriptor, it does not start the histogram + * in the kernel. + * + * Returns an initialized histogram on success. + * NULL on failure. + */ +struct tracefs_hist * +tracefs_hist_alloc(struct tep_handle *tep, + const char *system, const char *event_name, + const char *key, enum tracefs_hist_key_type type) +{ + struct tracefs_hist_axis axis[] = {{key, type}, {NULL, 0}}; + + return tracefs_hist_alloc_nd(tep, system, event_name, axis); +} + +/** + * tracefs_hist_alloc_2d - Initialize two-dimensional histogram + * @tep: The tep handle that has the @system and @event. + * @system: The system the histogram event is in. + * @event: The event that the histogram will be attached to. + * @key1: The first primary key the histogram will use + * @type1: The format type of the first key. + * @key2: The second primary key the histogram will use + * @type2: The format type of the second key. + * + * Will initialize a histogram descriptor that will be attached to + * the @system/@event with the given @key1 and @key2 as the primaries. + * This only initializes the descriptor, it does not start the histogram + * in the kernel. + * + * Returns an initialized histogram on success. + * NULL on failure. + */ +struct tracefs_hist * +tracefs_hist_alloc_2d(struct tep_handle *tep, + const char *system, const char *event_name, + const char *key1, enum tracefs_hist_key_type type1, + const char *key2, enum tracefs_hist_key_type type2) +{ + struct tracefs_hist_axis axis[] = {{key1, type1}, + {key2, type2}, + {NULL, 0}}; + + return tracefs_hist_alloc_nd(tep, system, event_name, axis); +} + +static struct tracefs_hist * +hist_alloc_nd(struct tep_handle *tep, + const char *system, const char *event_name, + struct tracefs_hist_axis *axes, + struct tracefs_hist_axis_cnt *axes_cnt) +{ + struct tep_event *event; + struct tracefs_hist *hist; + + if (!system || !event_name) + return NULL; + + event = tep_find_event_by_name(tep, system, event_name); + if (!event) + return NULL; + + hist = calloc(1, sizeof(*hist)); + if (!hist) + return NULL; + + tep_ref(tep); + hist->tep = tep; + hist->event = event; + hist->system = strdup(system); + hist->event_name = strdup(event_name); + if (!hist->system || !hist->event_name) + goto fail; + + for (; axes && axes->key; axes++) + if (tracefs_hist_add_key(hist, axes->key, axes->type) < 0) + goto fail; + + for (; axes_cnt && axes_cnt->key; axes_cnt++) + if (tracefs_hist_add_key_cnt(hist, axes_cnt->key, axes_cnt->type, axes_cnt->cnt) < 0) + goto fail; + + return hist; + + fail: + tracefs_hist_free(hist); + return NULL; +} +/** + * tracefs_hist_alloc_nd - Initialize N-dimensional histogram + * @tep: The tep handle that has the @system and @event. + * @system: The system the histogram event is in + * @event: The event that the histogram will be attached to + * @axes: An array of histogram axes, terminated by a {NULL, 0} entry + * + * Will initialize a histogram descriptor that will be attached to + * the @system/@event. This only initializes the descriptor with the given + * @axes keys as primaries. This only initializes the descriptor, it does + * not start the histogram in the kernel. + * + * Returns an initialized histogram on success. + * NULL on failure. + */ +struct tracefs_hist * +tracefs_hist_alloc_nd(struct tep_handle *tep, + const char *system, const char *event_name, + struct tracefs_hist_axis *axes) +{ + return hist_alloc_nd(tep, system, event_name, axes, NULL); +} + +/** + * tracefs_hist_alloc_nd_cnt - Initialize N-dimensional histogram + * @tep: The tep handle that has the @system and @event. + * @system: The system the histogram event is in + * @event: The event that the histogram will be attached to + * @axes: An array of histogram axes, terminated by a {NULL, 0} entry + * + * Will initialize a histogram descriptor that will be attached to + * the @system/@event. This only initializes the descriptor with the given + * @axes keys as primaries. This only initializes the descriptor, it does + * not start the histogram in the kernel. + * + * Returns an initialized histogram on success. + * NULL on failure. + */ +struct tracefs_hist * +tracefs_hist_alloc_nd_cnt(struct tep_handle *tep, + const char *system, const char *event_name, + struct tracefs_hist_axis_cnt *axes) +{ + return hist_alloc_nd(tep, system, event_name, NULL, axes); +} + +/** + * tracefs_hist_add_key - add to a key to a histogram + * @hist: The histogram to add the key to. + * @key: The name of the key field. + * @type: The type of the key format. + * @cnt: Some types require a counter, for those, this is used + * + * This adds a secondary or tertiary key to the histogram. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_add_key_cnt(struct tracefs_hist *hist, const char *key, + enum tracefs_hist_key_type type, int cnt) +{ + bool use_key = false; + char *key_type = NULL; + char **new_list; + int ret = -1; + + switch (type) { + case TRACEFS_HIST_KEY_NORMAL: + use_key = true; + ret = 0; + break; + case TRACEFS_HIST_KEY_HEX: + ret = asprintf(&key_type, "%s.hex", key); + break; + case TRACEFS_HIST_KEY_SYM: + ret = asprintf(&key_type, "%s.sym", key); + break; + case TRACEFS_HIST_KEY_SYM_OFFSET: + ret = asprintf(&key_type, "%s.sym-offset", key); + break; + case TRACEFS_HIST_KEY_SYSCALL: + ret = asprintf(&key_type, "%s.syscall", key); + break; + case TRACEFS_HIST_KEY_EXECNAME: + ret = asprintf(&key_type, "%s.execname", key); + break; + case TRACEFS_HIST_KEY_LOG: + ret = asprintf(&key_type, "%s.log2", key); + break; + case TRACEFS_HIST_KEY_USECS: + ret = asprintf(&key_type, "%s.usecs", key); + break; + case TRACEFS_HIST_KEY_BUCKETS: + ret = asprintf(&key_type, "%s.buckets=%d", key, cnt); + break; + case TRACEFS_HIST_KEY_MAX: + /* error */ + break; + } + + if (ret < 0) + return -1; + + new_list = tracefs_list_add(hist->keys, use_key ? key : key_type); + free(key_type); + if (!new_list) + return -1; + + hist->keys = new_list; + + return 0; +} + +/** + * tracefs_hist_add_key - add to a key to a histogram + * @hist: The histogram to add the key to. + * @key: The name of the key field. + * @type: The type of the key format. + * + * This adds a secondary or tertiary key to the histogram. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_add_key(struct tracefs_hist *hist, const char *key, + enum tracefs_hist_key_type type) +{ + return tracefs_hist_add_key_cnt(hist, key, type, 0); +} + +/** + * tracefs_hist_add_value - add to a value to a histogram + * @hist: The histogram to add the value to. + * @key: The name of the value field. + * + * This adds a value field to the histogram. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_add_value(struct tracefs_hist *hist, const char *value) +{ + char **new_list; + + new_list = tracefs_list_add(hist->values, value); + if (!new_list) + return -1; + + hist->values = new_list; + + return 0; +} + +/** + * tracefs_hist_add_name - name a histogram + * @hist: The histogram to name. + * @name: The name of the histogram. + * + * Adds a name to the histogram. Named histograms will share their + * data with other events that have the same name as if it was + * a single histogram. + * + * If the histogram already has a name, this will fail. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_add_name(struct tracefs_hist *hist, const char *name) +{ + if (hist->name) + return -1; + + hist->name = strdup(name); + + return hist->name ? 0 : -1; +} + +static char ** +add_sort_key(struct tracefs_hist *hist, const char *sort_key, char **list) +{ + char **key_list = hist->keys; + char **val_list = hist->values; + int i; + + if (strcmp(sort_key, TRACEFS_HIST_HITCOUNT) == 0) + goto out; + + for (i = 0; key_list[i]; i++) { + if (strcmp(key_list[i], sort_key) == 0) + break; + } + + if (!key_list[i] && val_list) { + for (i = 0; val_list[i]; i++) { + if (strcmp(val_list[i], sort_key) == 0) + break; + if (!val_list[i]) + return NULL; + } + } + + + out: + return tracefs_list_add(list, sort_key); +} + +/** + * tracefs_hist_add_sort_key - add a key for sorting the histogram + * @hist: The histogram to add the sort key to + * @sort_key: The key to sort + * + * Call the function to add to the list of sort keys of the histohram in + * the order of priority that the keys would be sorted on output. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_add_sort_key(struct tracefs_hist *hist, + const char *sort_key) +{ + char **list = hist->sort; + + if (!hist || !sort_key) + return -1; + + list = add_sort_key(hist, sort_key, hist->sort); + if (!list) + return -1; + + hist->sort = list; + + return 0; +} + +/** + * tracefs_hist_set_sort_key - set a key for sorting the histogram + * @hist: The histogram to set the sort key to + * @sort_key: The key to sort (and the strings after it) + * Last one must be NULL. + * + * Set a list of sort keys in the order of priority that the + * keys would be sorted on output. Keys must be added first. + * + * Returns 0 on success, -1 on error. + */ +int tracefs_hist_set_sort_key(struct tracefs_hist *hist, + const char *sort_key, ...) +{ + char **list = NULL; + char **tmp; + va_list ap; + + if (!hist || !sort_key) + return -1; + + tmp = add_sort_key(hist, sort_key, list); + if (!tmp) + goto fail; + list = tmp; + + va_start(ap, sort_key); + for (;;) { + sort_key = va_arg(ap, const char *); + if (!sort_key) + break; + tmp = add_sort_key(hist, sort_key, list); + if (!tmp) + goto fail; + list = tmp; + } + va_end(ap); + + tracefs_list_free(hist->sort); + hist->sort = list; + + return 0; + fail: + tracefs_list_free(list); + return -1; +} + +static int end_match(const char *sort_key, const char *ending) +{ + int key_len = strlen(sort_key); + int end_len = strlen(ending); + + if (key_len <= end_len) + return 0; + + sort_key += key_len - end_len; + + return strcmp(sort_key, ending) == 0 ? key_len - end_len : 0; +} + +/** + * tracefs_hist_sort_key_direction - set direction of a sort key + * @hist: The histogram to modify. + * @sort_str: The sort key to set the direction for + * @dir: The direction to set the sort key to. + * + * Returns 0 on success, and -1 on error; + */ +int tracefs_hist_sort_key_direction(struct tracefs_hist *hist, + const char *sort_str, + enum tracefs_hist_sort_direction dir) +{ + char **sort = hist->sort; + char *sort_key; + char *direct; + int match; + int i; + + if (!sort) + return -1; + + for (i = 0; sort[i]; i++) { + if (strcmp(sort[i], sort_str) == 0) + break; + } + if (!sort[i]) + return -1; + + sort_key = sort[i]; + + switch (dir) { + case TRACEFS_HIST_SORT_ASCENDING: + direct = ASCENDING; + break; + case TRACEFS_HIST_SORT_DESCENDING: + direct = DESCENDING; + break; + default: + return -1; + } + + match = end_match(sort_key, ASCENDING); + if (match) { + /* Already match? */ + if (dir == TRACEFS_HIST_SORT_ASCENDING) + return 0; + } else { + match = end_match(sort_key, DESCENDING); + /* Already match? */ + if (match && dir == TRACEFS_HIST_SORT_DESCENDING) + return 0; + } + + if (match) + /* Clear the original text */ + sort_key[match] = '\0'; + + sort_key = realloc(sort_key, strlen(sort_key) + strlen(direct) + 1); + if (!sort_key) { + /* Failed to alloc, may need to put back the match */ + sort_key = sort[i]; + if (match) + sort_key[match] = '.'; + return -1; + } + + strcat(sort_key, direct); + sort[i] = sort_key; + return 0; +} + +int tracefs_hist_append_filter(struct tracefs_hist *hist, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val) +{ + return trace_append_filter(&hist->filter, &hist->filter_state, + &hist->filter_parens, + hist->event, + type, field, compare, val); +} + +enum action_type { + ACTION_NONE, + ACTION_TRACE, + ACTION_SNAPSHOT, + ACTION_SAVE, +}; + +struct action { + struct action *next; + enum action_type type; + enum tracefs_synth_handler handler; + char *handle_field; + char *save; +}; + +struct name_hash { + struct name_hash *next; + char *name; + char *hash; +}; + +/* + * @name: name of the synthetic event + * @start_system: system of the starting event + * @start_event: the starting event + * @end_system: system of the ending event + * @end_event: the ending event + * @actions: List of actions to take + * @match_names: If a match set is to be a synthetic field, it has a name + * @start_match: list of keys in the start event that matches end event + * @end_match: list of keys in the end event that matches the start event + * @compare_names: The synthetic field names of the compared fields + * @start_compare: A list of compare fields in the start to compare to end + * @end_compare: A list of compare fields in the end to compare to start + * @compare_ops: The type of operations to perform between the start and end + * @start_names: The fields in the start event to record + * @end_names: The fields in the end event to record + * @start_filters: The fields in the end event to record + * @end_filters: The fields in the end event to record + * @start_parens: Current parenthesis level for start event + * @end_parens: Current parenthesis level for end event + * @new_format: onmatch().trace(synth_event,..) or onmatch().synth_event(...) + */ +struct tracefs_synth { + struct tracefs_instance *instance; + struct tep_handle *tep; + struct tep_event *start_event; + struct tep_event *end_event; + struct action *actions; + struct action **next_action; + struct tracefs_dynevent *dyn_event; + struct name_hash *name_hash[1 << HASH_BITS]; + char *start_hist; + char *end_hist; + char *name; + char **synthetic_fields; + char **synthetic_args; + char **start_selection; + char **start_keys; + char **end_keys; + char **start_vars; + char **end_vars; + char *start_filter; + char *end_filter; + unsigned int start_parens; + unsigned int start_state; + unsigned int end_parens; + unsigned int end_state; + int *start_type; + char arg_name[16]; + int arg_cnt; + bool new_format; +}; + + /* + * tracefs_synth_get_name - get the name of the synthetic event + * @synth: The synthetic event to get the name for + * + * Returns name string owned by @synth on success, or NULL on error. + */ +const char *tracefs_synth_get_name(struct tracefs_synth *synth) +{ + return synth ? synth->name : NULL; +} + +static void action_free(struct action *action) +{ + free(action->handle_field); + free(action->save); + free(action); +} + +static void free_name_hash(struct name_hash **hash) +{ + struct name_hash *item; + int i; + + for (i = 0; i < 1 << HASH_BITS; i++) { + while ((item = hash[i])) { + hash[i] = item->next; + free(item->name); + free(item->hash); + free(item); + } + } +} + +/** + * tracefs_synth_free - free the resources alloced to a synth + * @synth: The tracefs_synth descriptor + * + * Frees the resources allocated for a @synth created with + * tracefs_synth_alloc(). It does not touch the system. That is, + * any synthetic event created, will not be destroyed by this + * function. + */ +void tracefs_synth_free(struct tracefs_synth *synth) +{ + struct action *action; + + if (!synth) + return; + + free(synth->name); + free(synth->start_hist); + free(synth->end_hist); + tracefs_list_free(synth->synthetic_fields); + tracefs_list_free(synth->synthetic_args); + tracefs_list_free(synth->start_selection); + tracefs_list_free(synth->start_keys); + tracefs_list_free(synth->end_keys); + tracefs_list_free(synth->start_vars); + tracefs_list_free(synth->end_vars); + free_name_hash(synth->name_hash); + free(synth->start_filter); + free(synth->end_filter); + free(synth->start_type); + + tep_unref(synth->tep); + + while ((action = synth->actions)) { + synth->actions = action->next; + action_free(action); + } + tracefs_dynevent_free(synth->dyn_event); + + free(synth); +} + +static bool verify_event_fields(struct tep_event *start_event, + struct tep_event *end_event, + const char *start_field_name, + const char *end_field_name, + const struct tep_format_field **ptr_start_field) +{ + const struct tep_format_field *start_field; + const struct tep_format_field *end_field; + int start_flags, end_flags; + + if (!trace_verify_event_field(start_event, start_field_name, + &start_field)) + return false; + + if (end_event) { + if (!trace_verify_event_field(end_event, end_field_name, + &end_field)) + return false; + + /* A pointer can still match a long */ + start_flags = start_field->flags & ~TEP_FIELD_IS_POINTER; + end_flags = end_field->flags & ~TEP_FIELD_IS_POINTER; + + if (start_flags != end_flags || + start_field->size != end_field->size) { + errno = EBADE; + return false; + } + } + + if (ptr_start_field) + *ptr_start_field = start_field; + + return true; +} + +__hidden char *append_string(char *str, const char *space, const char *add) +{ + char *new; + int len; + + /* String must already be allocated */ + if (!str) + return NULL; + + len = strlen(str) + strlen(add) + 2; + if (space) + len += strlen(space); + + new = realloc(str, len); + if (!new) { + free(str); + return NULL; + } + str = new; + + if (space) + strcat(str, space); + strcat(str, add); + + return str; +} + +static char *add_synth_field(const struct tep_format_field *field, + const char *name) +{ + const char *type; + char size[64]; + char *str; + bool sign; + + if (field->flags & TEP_FIELD_IS_ARRAY) { + str = strdup("char"); + str = append_string(str, " ", name); + str = append_string(str, NULL, "["); + + if (!(field->flags & TEP_FIELD_IS_DYNAMIC)) { + snprintf(size, 64, "%d", field->size); + str = append_string(str, NULL, size); + } + return append_string(str, NULL, "];"); + } + + /* Synthetic events understand pid_t, gfp_t and bool */ + if (strcmp(field->type, "pid_t") == 0 || + strcmp(field->type, "gfp_t") == 0 || + strcmp(field->type, "bool") == 0) { + str = strdup(field->type); + str = append_string(str, " ", name); + return append_string(str, NULL, ";"); + } + + sign = field->flags & TEP_FIELD_IS_SIGNED; + + switch (field->size) { + case 1: + if (!sign) + type = "unsigned char"; + else + type = "char"; + break; + case 2: + if (sign) + type = "s16"; + else + type = "u16"; + break; + case 4: + if (sign) + type = "s32"; + else + type = "u32"; + break; + case 8: + if (sign) + type = "s64"; + else + type = "u64"; + break; + default: + errno = EBADF; + return NULL; + } + + str = strdup(type); + str = append_string(str, " ", name); + return append_string(str, NULL, ";"); +} + +static int add_var(char ***list, const char *name, const char *var, bool is_var) +{ + char **new; + char *assign; + int ret; + + if (is_var) + ret = asprintf(&assign, "%s=$%s", name, var); + else + ret = asprintf(&assign, "%s=%s", name, var); + + if (ret < 0) + return -1; + + new = tracefs_list_add(*list, assign); + free(assign); + + if (!new) + return -1; + *list = new; + return 0; +} + +__hidden struct tracefs_synth * +synth_init_from(struct tep_handle *tep, const char *start_system, + const char *start_event_name) +{ + struct tep_event *start_event; + struct tracefs_synth *synth; + + start_event = tep_find_event_by_name(tep, start_system, + start_event_name); + if (!start_event) { + errno = ENODEV; + return NULL; + } + + synth = calloc(1, sizeof(*synth)); + if (!synth) + return NULL; + + synth->start_event = start_event; + synth->next_action = &synth->actions; + + /* Hold onto a reference to this handler */ + tep_ref(tep); + synth->tep = tep; + + return synth; +} + +static int alloc_synthetic_event(struct tracefs_synth *synth) +{ + char *format; + const char *field; + int i; + + format = strdup(""); + if (!format) + return -1; + + for (i = 0; synth->synthetic_fields && synth->synthetic_fields[i]; i++) { + field = synth->synthetic_fields[i]; + format = append_string(format, i ? " " : NULL, field); + } + + synth->dyn_event = dynevent_alloc(TRACEFS_DYNEVENT_SYNTH, SYNTHETIC_GROUP, + synth->name, NULL, format); + free(format); + + return synth->dyn_event ? 0 : -1; +} + +/* + * See if it is onmatch().trace(synth_event,...) or + * onmatch().synth_event(...) + */ +static bool has_new_format() +{ + char *readme; + char *p; + int size; + + readme = tracefs_instance_file_read(NULL, "README", &size); + if (!readme) + return false; + + p = strstr(readme, "trace(<synthetic_event>,param list)"); + free(readme); + + return p != NULL; +} + +/** + * tracefs_synth_alloc - create a new tracefs_synth instance + * @tep: The tep handle that holds the events to work on + * @name: The name of the synthetic event being created + * @start_system: The name of the system of the start event (can be NULL) + * @start_event_name: The name of the start event + * @end_system: The name of the system of the end event (can be NULL) + * @end_event_name: The name of the end event + * @start_match_field: The name of the field in start event to match @end_match_field + * @end_match_field: The name of the field in end event to match @start_match_field + * @match_name: Name to call the fields that match (can be NULL) + * + * Creates a tracefs_synth instance that has the minimum requirements to + * create a synthetic event. + * + * @name is will be the name of the synthetic event that this can create. + * + * The start event is found with @start_system and @start_event_name. If + * @start_system is NULL, then the first event with @start_event_name will + * be used. + * + * The end event is found with @end_system and @end_event_name. If + * @end_system is NULL, then the first event with @end_event_name will + * be used. + * + * The @start_match_field is the field in the start event that will be used + * to match the @end_match_field of the end event. + * + * If @match_name is given, then the field that matched the start and + * end events will be passed an a field to the sythetic event with this + * as the field name. + * + * Returns an allocated tracefs_synth descriptor on success and NULL + * on error, with the following set in errno. + * + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be + * ENODEV - could not find an event or field + * EBADE - The start and end fields are not compatible to match + * + * Note, this does not modify the system. That is, the synthetic + * event on the system is not created. That needs to be done with + * tracefs_synth_create(). + */ +struct tracefs_synth *tracefs_synth_alloc(struct tep_handle *tep, + const char *name, + const char *start_system, + const char *start_event_name, + const char *end_system, + const char *end_event_name, + const char *start_match_field, + const char *end_match_field, + const char *match_name) +{ + struct tep_event *end_event; + struct tracefs_synth *synth; + int ret = 0; + + if (!tep || !name || !start_event_name || !end_event_name || + !start_match_field || !end_match_field) { + errno = EINVAL; + return NULL; + } + + synth = synth_init_from(tep, start_system, start_event_name); + if (!synth) + return NULL; + + end_event = tep_find_event_by_name(tep, end_system, + end_event_name); + if (!end_event) { + tep_unref(tep); + errno = ENODEV; + return NULL; + } + + synth->end_event = end_event; + + synth->name = strdup(name); + + ret = tracefs_synth_add_match_field(synth, start_match_field, + end_match_field, match_name); + + if (!synth->name || !synth->start_keys || !synth->end_keys || ret) { + tracefs_synth_free(synth); + synth = NULL; + } else + synth->new_format = has_new_format(); + + return synth; +} + +static int add_synth_fields(struct tracefs_synth *synth, + const struct tep_format_field *field, + const char *name, const char *var) +{ + char **list; + char *str; + int ret; + + str = add_synth_field(field, name ? : field->name); + if (!str) + return -1; + + if (!name) + name = var; + + list = tracefs_list_add(synth->synthetic_fields, str); + free(str); + if (!list) + return -1; + synth->synthetic_fields = list; + + ret = asprintf(&str, "$%s", var ? : name); + if (ret < 0) { + trace_list_pop(synth->synthetic_fields); + return -1; + } + + list = tracefs_list_add(synth->synthetic_args, str); + free(str); + if (!list) { + trace_list_pop(synth->synthetic_fields); + return -1; + } + + synth->synthetic_args = list; + + return 0; +} + +/** + * tracefs_synth_add_match_field - add another key to match events + * @synth: The tracefs_synth descriptor + * @start_match_field: The field of the start event to match the end event + * @end_match_field: The field of the end event to match the start event + * @name: The name to show in the synthetic event (NULL is allowed) + * + * This will add another set of keys to use for a match between + * the start event and the end event. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be + * ENODEV - could not find a field + * EBADE - The start and end fields are not compatible to match + */ +int tracefs_synth_add_match_field(struct tracefs_synth *synth, + const char *start_match_field, + const char *end_match_field, + const char *name) +{ + const struct tep_format_field *key_field; + char **list; + int ret; + + if (!synth || !start_match_field || !end_match_field) { + errno = EINVAL; + return -1; + } + + if (!verify_event_fields(synth->start_event, synth->end_event, + start_match_field, end_match_field, + &key_field)) + return -1; + + list = tracefs_list_add(synth->start_keys, start_match_field); + if (!list) + return -1; + + synth->start_keys = list; + + list = tracefs_list_add(synth->end_keys, end_match_field); + if (!list) { + trace_list_pop(synth->start_keys); + return -1; + } + synth->end_keys = list; + + if (!name) + return 0; + + ret = add_var(&synth->end_vars, name, end_match_field, false); + + if (ret < 0) + goto pop_lists; + + ret = add_synth_fields(synth, key_field, name, NULL); + if (ret < 0) + goto pop_lists; + + return 0; + + pop_lists: + trace_list_pop(synth->start_keys); + trace_list_pop(synth->end_keys); + return -1; +} + +static unsigned int make_rand(void) +{ + struct timeval tv; + unsigned long seed; + + gettimeofday(&tv, NULL); + seed = (tv.tv_sec + tv.tv_usec) + getpid(); + + /* taken from the rand(3) man page */ + seed = seed * 1103515245 + 12345; + return((unsigned)(seed/65536) % 32768); +} + +static char *new_name(struct tracefs_synth *synth, const char *name) +{ + int cnt = synth->arg_cnt + 1; + char *arg; + int ret; + + /* Create a unique argument name */ + if (!synth->arg_name[0]) { + /* make_rand() returns at most 32768 (total 13 bytes in use) */ + sprintf(synth->arg_name, "%u", make_rand()); + } + ret = asprintf(&arg, "__%s_%s_%d", name, synth->arg_name, cnt); + if (ret < 0) + return NULL; + + synth->arg_cnt = cnt; + return arg; +} + +static struct name_hash *find_name(struct tracefs_synth *synth, const char *name) +{ + unsigned int key = quick_hash(name); + struct name_hash *hash = synth->name_hash[key]; + + for (; hash; hash = hash->next) { + if (!strcmp(hash->name, name)) + return hash; + } + return NULL; +} + +static const char *hash_name(struct tracefs_synth *synth, const char *name) +{ + struct name_hash *hash; + int key; + + hash = find_name(synth, name); + if (hash) + return hash->hash; + + hash = malloc(sizeof(*hash)); + if (!hash) + return name; + + hash->hash = new_name(synth, name); + if (!hash->hash) { + free(hash); + return name; + } + + key = quick_hash(name); + hash->next = synth->name_hash[key]; + synth->name_hash[key] = hash; + + hash->name = strdup(name); + if (!hash->name) { + free(hash->hash); + free(hash); + return name; + } + return hash->hash; +} + +static char *new_arg(struct tracefs_synth *synth) +{ + return new_name(synth, "arg"); +} + +/** + * tracefs_synth_add_compare_field - add a comparison between start and end + * @synth: The tracefs_synth descriptor + * @start_compare_field: The field of the start event to compare to the end + * @end_compare_field: The field of the end event to compare to the start + * @calc - How to go about the comparing the fields. + * @name: The name to show in the synthetic event (must NOT be NULL) + * + * This will add a way to compare two different fields between the + * start end end events. + * + * The comparing between events is decided by @calc: + * TRACEFS_SYNTH_DELTA_END - name = end - start + * TRACEFS_SYNTH_DELTA_START - name = start - end + * TRACEFS_SYNTH_ADD - name = end + start + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be + * ENODEV - could not find a field + * EBADE - The start and end fields are not compatible to compare + */ +int tracefs_synth_add_compare_field(struct tracefs_synth *synth, + const char *start_compare_field, + const char *end_compare_field, + enum tracefs_synth_calc calc, + const char *name) +{ + const struct tep_format_field *start_field; + const char *hname; + char *start_arg; + char *compare; + int ret; + + /* Compare fields require a name */ + if (!name || !start_compare_field || !end_compare_field) { + errno = -EINVAL; + return -1; + } + + if (!verify_event_fields(synth->start_event, synth->end_event, + start_compare_field, end_compare_field, + &start_field)) + return -1; + + /* Calculations are not allowed on string */ + if (start_field->flags & (TEP_FIELD_IS_ARRAY | + TEP_FIELD_IS_DYNAMIC)) { + errno = -EINVAL; + return -1; + } + + start_arg = new_arg(synth); + if (!start_arg) + return -1; + + ret = add_var(&synth->start_vars, start_arg, start_compare_field, false); + if (ret < 0) { + free(start_arg); + return -1; + } + + ret = -1; + switch (calc) { + case TRACEFS_SYNTH_DELTA_END: + ret = asprintf(&compare, "%s-$%s", end_compare_field, + start_arg); + break; + case TRACEFS_SYNTH_DELTA_START: + ret = asprintf(&compare, "$%s-%s", start_arg, + end_compare_field); + break; + case TRACEFS_SYNTH_ADD: + ret = asprintf(&compare, "%s+$%s", end_compare_field, + start_arg); + break; + } + free(start_arg); + if (ret < 0) + return -1; + + hname = hash_name(synth, name); + ret = add_var(&synth->end_vars, hname, compare, false); + if (ret < 0) + goto out_free; + + ret = add_synth_fields(synth, start_field, name, hname); + if (ret < 0) + goto out_free; + + out_free: + free(compare); + + return ret ? -1 : 0; +} + +__hidden int synth_add_start_field(struct tracefs_synth *synth, + const char *start_field, + const char *name, + enum tracefs_hist_key_type type, int count) +{ + const struct tep_format_field *field; + const char *var; + char *start_arg; + char **tmp; + int *types; + int len; + int ret; + + if (!synth || !start_field) { + errno = EINVAL; + return -1; + } + + if (!name) + name = start_field; + + var = hash_name(synth, name); + + if (!trace_verify_event_field(synth->start_event, start_field, &field)) + return -1; + + start_arg = new_arg(synth); + if (!start_arg) + return -1; + + ret = add_var(&synth->start_vars, start_arg, start_field, false); + if (ret) + goto out_free; + + ret = add_var(&synth->end_vars, var, start_arg, true); + if (ret) + goto out_free; + + ret = add_synth_fields(synth, field, name, var); + if (ret) + goto out_free; + + tmp = tracefs_list_add(synth->start_selection, start_field); + if (!tmp) { + ret = -1; + goto out_free; + } + synth->start_selection = tmp; + + len = tracefs_list_size(tmp); + if (len <= 0) { /* ?? */ + ret = -1; + goto out_free; + } + + types = realloc(synth->start_type, sizeof(*types) * len); + if (!types) { + ret = -1; + goto out_free; + } + synth->start_type = types; + synth->start_type[len - 1] = type; + + out_free: + free(start_arg); + return ret; +} + +/** + * tracefs_synth_add_start_field - add a start field to save + * @synth: The tracefs_synth descriptor + * @start_field: The field of the start event to save + * @name: The name to show in the synthetic event (if NULL @start_field is used) + * + * This adds a field named by @start_field of the start event to + * record in the synthetic event. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be + * ENODEV - could not find a field + */ +int tracefs_synth_add_start_field(struct tracefs_synth *synth, + const char *start_field, + const char *name) +{ + return synth_add_start_field(synth, start_field, name, 0, 0); +} + +/** + * tracefs_synth_add_end_field - add a end field to save + * @synth: The tracefs_synth descriptor + * @end_field: The field of the end event to save + * @name: The name to show in the synthetic event (if NULL @end_field is used) + * + * This adds a field named by @end_field of the start event to + * record in the synthetic event. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be + * ENODEV - could not find a field + */ +int tracefs_synth_add_end_field(struct tracefs_synth *synth, + const char *end_field, + const char *name) +{ + const struct tep_format_field *field; + const char *hname = NULL; + char *tmp_var = NULL; + int ret; + + if (!synth || !end_field) { + errno = EINVAL; + return -1; + } + + if (name) { + if (strncmp(name, "__arg", 5) != 0) + hname = hash_name(synth, name); + else + hname = name; + } + + if (!name) + tmp_var = new_arg(synth); + + if (!trace_verify_event_field(synth->end_event, end_field, &field)) + return -1; + + ret = add_var(&synth->end_vars, name ? hname : tmp_var, end_field, false); + if (ret) + goto out; + + ret = add_synth_fields(synth, field, name, hname ? : tmp_var); + free(tmp_var); + out: + return ret; +} + +/** + * tracefs_synth_append_start_filter - create or append a filter + * @synth: The tracefs_synth descriptor + * @type: The type of element to add to the filter + * @field: For @type == TRACEFS_FILTER_COMPARE, the field to compare + * @compare: For @type == TRACEFS_FILTER_COMPARE, how to compare @field to @val + * @val: For @type == TRACEFS_FILTER_COMPARE, what value @field is to be + * + * This will put together a filter string for the starting event + * of @synth. It check to make sure that what is added is correct compared + * to the filter that is already built. + * + * @type can be: + * TRACEFS_FILTER_COMPARE: See below + * TRACEFS_FILTER_AND: Append "&&" to the filter + * TRACEFS_FILTER_OR: Append "||" to the filter + * TRACEFS_FILTER_NOT: Append "!" to the filter + * TRACEFS_FILTER_OPEN_PAREN: Append "(" to the filter + * TRACEFS_FILTER_CLOSE_PAREN: Append ")" to the filter + * + * For all types except TRACEFS_FILTER_COMPARE, the @field, @compare, + * and @val are ignored. + * + * For @type == TRACEFS_FILTER_COMPARE. + * + * @field is the name of the field for the start event to compare. + * If it is not a field for the start event, this return an + * error. + * + * @compare can be one of: + * TRACEFS_COMPARE_EQ: Test @field == @val + * TRACEFS_COMPARE_NE: Test @field != @val + * TRACEFS_COMPARE_GT: Test @field > @val + * TRACEFS_COMPARE_GE: Test @field >= @val + * TRACEFS_COMPARE_LT: Test @field < @val + * TRACEFS_COMPARE_LE: Test @field <= @val + * TRACEFS_COMPARE_RE: Test @field ~ @val + * TRACEFS_COMPARE_AND: Test @field & @val + * + * If the @field is of type string, and @compare is not + * TRACEFS_COMPARE_EQ, TRACEFS_COMPARE_NE or TRACEFS_COMPARE_RE, + * then this will return an error. + * + * Various other checks are made, for instance, if more CLOSE_PARENs + * are added than existing OPEN_PARENs. Or if AND is added after an + * OPEN_PAREN or another AND or an OR or a NOT. + * + * Returns 0 on success and -1 on failure. + */ +int tracefs_synth_append_start_filter(struct tracefs_synth *synth, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val) +{ + return trace_append_filter(&synth->start_filter, &synth->start_state, + &synth->start_parens, + synth->start_event, + type, field, compare, val); +} + +/** + * tracefs_synth_append_end_filter - create or append a filter + * @synth: The tracefs_synth descriptor + * @type: The type of element to add to the filter + * @field: For @type == TRACEFS_FILTER_COMPARE, the field to compare + * @compare: For @type == TRACEFS_FILTER_COMPARE, how to compare @field to @val + * @val: For @type == TRACEFS_FILTER_COMPARE, what value @field is to be + * + * Performs the same thing as tracefs_synth_append_start_filter() but + * for the @synth end event. + */ +int tracefs_synth_append_end_filter(struct tracefs_synth *synth, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val) +{ + return trace_append_filter(&synth->end_filter, &synth->end_state, + &synth->end_parens, + synth->end_event, + type, field, compare, val); +} + +static int test_max_var(struct tracefs_synth *synth, const char *var) +{ + char **vars = synth->end_vars; + char *p; + int len; + int i; + + len = strlen(var); + + /* Make sure the var is defined for the end event */ + for (i = 0; vars[i]; i++) { + p = strchr(vars[i], '='); + if (!p) + continue; + if (p - vars[i] != len) + continue; + if (!strncmp(var, vars[i], len)) + return 0; + } + errno = ENODEV; + return -1; +} + +static struct action *create_action(enum tracefs_synth_handler type, + struct tracefs_synth *synth, + const char *var) +{ + struct action *action; + int ret; + + switch (type) { + case TRACEFS_SYNTH_HANDLE_MAX: + case TRACEFS_SYNTH_HANDLE_CHANGE: + ret = test_max_var(synth, var); + if (ret < 0) + return NULL; + break; + default: + break; + } + + action = calloc(1, sizeof(*action)); + if (!action) + return NULL; + + if (var) { + ret = asprintf(&action->handle_field, "$%s", var); + if (!action->handle_field) { + free(action); + return NULL; + } + } + return action; +} + +static void add_action(struct tracefs_synth *synth, struct action *action) +{ + *synth->next_action = action; + synth->next_action = &action->next; +} + +/** + * tracefs_synth_trace - Execute the trace option + * @synth: The tracefs_synth descriptor + * @type: The type of handler to attach the trace action with + * @field: The field for handlers onmax and onchange (ignored otherwise) + * + * Add the action 'trace' for handlers onmatch, onmax and onchange. + * + * Returns 0 on succes, -1 on error. + */ +int tracefs_synth_trace(struct tracefs_synth *synth, + enum tracefs_synth_handler type, const char *field) +{ + struct action *action; + + if (!synth || (!field && (type != TRACEFS_SYNTH_HANDLE_MATCH))) { + errno = EINVAL; + return -1; + } + + action = create_action(type, synth, field); + if (!action) + return -1; + + action->type = ACTION_TRACE; + action->handler = type; + add_action(synth, action); + return 0; +} + +/** + * tracefs_synth_snapshot - create a snapshot command to the histogram + * @synth: The tracefs_synth descriptor + * @type: The type of handler to attach the snapshot action with + * @field: The field for handlers onmax and onchange + * + * Add the action to do a snapshot for handlers onmax and onchange. + * + * Returns 0 on succes, -1 on error. + */ +int tracefs_synth_snapshot(struct tracefs_synth *synth, + enum tracefs_synth_handler type, const char *field) +{ + struct action *action; + + if (!synth || !field || (type == TRACEFS_SYNTH_HANDLE_MATCH)) { + errno = EINVAL; + return -1; + } + + action = create_action(type, synth, field); + if (!action) + return -1; + + action->type = ACTION_SNAPSHOT; + action->handler = type; + add_action(synth, action); + return 0; +} + +/** + * tracefs_synth_save - create a save command to the histogram + * @synth: The tracefs_synth descriptor + * @type: The type of handler to attach the save action + * @max_field: The field for handlers onmax and onchange + * @fields: The fields to save for the end variable + * + * Add the action to save fields for handlers onmax and onchange. + * + * Returns 0 on succes, -1 on error. + */ +int tracefs_synth_save(struct tracefs_synth *synth, + enum tracefs_synth_handler type, const char *max_field, + char **fields) +{ + struct action *action; + char *save; + int i; + + if (!synth || !max_field || (type == TRACEFS_SYNTH_HANDLE_MATCH)) { + errno = EINVAL; + return -1; + } + + action = create_action(type, synth, max_field); + if (!action) + return -1; + + action->type = ACTION_SAVE; + action->handler = type; + *synth->next_action = action; + synth->next_action = &action->next; + + save = strdup(".save("); + if (!save) + goto error; + + for (i = 0; fields[i]; i++) { + char *delim = i ? "," : NULL; + + if (!trace_verify_event_field(synth->end_event, fields[i], NULL)) + goto error; + save = append_string(save, delim, fields[i]); + } + save = append_string(save, NULL, ")"); + if (!save) + goto error; + + action->save = save; + add_action(synth, action); + return 0; + error: + action_free(action); + free(save); + return -1; + return 0; +} + +static int remove_hist(struct tracefs_instance *instance, + struct tep_event *event, const char *hist) +{ + char *str; + int ret; + + ret = asprintf(&str, "!%s", hist); + if (ret < 0) + return -1; + + ret = tracefs_event_file_append(instance, event->system, event->name, + "trigger", str); + free(str); + return ret < 0 ? -1 : 0; +} + +static char *create_hist(char **keys, char **vars) +{ + char *hist = strdup("hist:keys="); + char *name; + int i; + + if (!hist) + return NULL; + + for (i = 0; keys[i]; i++) { + name = keys[i]; + if (i) + hist = append_string(hist, NULL, ","); + hist = append_string(hist, NULL, name); + } + + if (!vars) + return hist; + + hist = append_string(hist, NULL, ":"); + + for (i = 0; vars[i]; i++) { + name = vars[i]; + if (i) + hist = append_string(hist, NULL, ","); + hist = append_string(hist, NULL, name); + } + + return hist; +} + +static char *create_onmatch(char *hist, struct tracefs_synth *synth) +{ + hist = append_string(hist, NULL, ":onmatch("); + hist = append_string(hist, NULL, synth->start_event->system); + hist = append_string(hist, NULL, "."); + hist = append_string(hist, NULL, synth->start_event->name); + return append_string(hist, NULL, ")"); +} + +static char *create_trace(char *hist, struct tracefs_synth *synth) +{ + char *name; + int i; + + if (synth->new_format) { + hist = append_string(hist, NULL, ".trace("); + hist = append_string(hist, NULL, synth->name); + hist = append_string(hist, NULL, ","); + } else { + hist = append_string(hist, NULL, "."); + hist = append_string(hist, NULL, synth->name); + hist = append_string(hist, NULL, "("); + } + + for (i = 0; synth->synthetic_args && synth->synthetic_args[i]; i++) { + name = synth->synthetic_args[i]; + + if (i) + hist = append_string(hist, NULL, ","); + hist = append_string(hist, NULL, name); + } + + return append_string(hist, NULL, ")"); +} + +static char *create_max(char *hist, struct tracefs_synth *synth, char *field) +{ + hist = append_string(hist, NULL, ":onmax("); + hist = append_string(hist, NULL, field); + return append_string(hist, NULL, ")"); +} + +static char *create_change(char *hist, struct tracefs_synth *synth, char *field) +{ + hist = append_string(hist, NULL, ":onchange("); + hist = append_string(hist, NULL, field); + return append_string(hist, NULL, ")"); +} + +static char *create_actions(char *hist, struct tracefs_synth *synth) +{ + struct action *action; + + if (!synth->actions) { + /* Default is "onmatch" and "trace" */ + hist = create_onmatch(hist, synth); + return create_trace(hist, synth); + } + + for (action = synth->actions; action; action = action->next) { + switch (action->handler) { + case TRACEFS_SYNTH_HANDLE_MATCH: + hist = create_onmatch(hist, synth); + break; + case TRACEFS_SYNTH_HANDLE_MAX: + hist = create_max(hist, synth, action->handle_field); + break; + case TRACEFS_SYNTH_HANDLE_CHANGE: + hist = create_change(hist, synth, action->handle_field); + break; + default: + continue; + } + switch (action->type) { + case ACTION_TRACE: + hist = create_trace(hist, synth); + break; + case ACTION_SNAPSHOT: + hist = append_string(hist, NULL, ".snapshot()"); + break; + case ACTION_SAVE: + hist = append_string(hist, NULL, action->save); + break; + default: + continue; + } + } + return hist; +} + +static char *create_end_hist(struct tracefs_synth *synth) +{ + char *end_hist; + + end_hist = create_hist(synth->end_keys, synth->end_vars); + return create_actions(end_hist, synth); +} + +/* + * tracefs_synth_raw_fmt - show the raw format of a synthetic event + * @seq: A trace_seq to store the format string + * @synth: The synthetic event to read format from + * + * This shows the raw format that describes the synthetic event, including + * the format of the dynamic event and the start / end histograms. + * + * Returns 0 on succes -1 on error. + */ +int tracefs_synth_raw_fmt(struct trace_seq *seq, struct tracefs_synth *synth) +{ + if (!synth->dyn_event) + return -1; + + trace_seq_printf(seq, "%s", synth->dyn_event->format); + trace_seq_printf(seq, "\n%s", synth->start_hist); + trace_seq_printf(seq, "\n%s", synth->end_hist); + + return 0; +} + +/* + * tracefs_synth_show_event - show the dynamic event used by a synth event + * @synth: The synthetic event to read format from + * + * This shows the raw format of the dynamic event used by the synthetic event. + * + * Returns format string owned by @synth on success, or NULL on error. + */ +const char *tracefs_synth_show_event(struct tracefs_synth *synth) +{ + return synth->dyn_event ? synth->dyn_event->format : NULL; +} + +/* + * tracefs_synth_show_start_hist - show the start histogram used by a synth event + * @synth: The synthetic event to read format from + * + * This shows the raw format of the start histogram used by the synthetic event. + * + * Returns format string owned by @synth on success, or NULL on error. + */ +const char *tracefs_synth_show_start_hist(struct tracefs_synth *synth) +{ + return synth->start_hist; +} + +/* + * tracefs_synth_show_end_hist - show the end histogram used by a synth event + * @synth: The synthetic event to read format from + * + * This shows the raw format of the end histogram used by the synthetic event. + * + * Returns format string owned by @synth on success, or NULL on error. + */ +const char *tracefs_synth_show_end_hist(struct tracefs_synth *synth) +{ + return synth->end_hist; +} + +static char *append_filter(char *hist, char *filter, unsigned int parens) +{ + int i; + + if (!filter) + return hist; + + hist = append_string(hist, NULL, " if "); + hist = append_string(hist, NULL, filter); + for (i = 0; i < parens; i++) + hist = append_string(hist, NULL, ")"); + return hist; +} + +static int verify_state(struct tracefs_synth *synth) +{ + if (trace_test_state(synth->start_state) < 0 || + trace_test_state(synth->end_state) < 0) + return -1; + return 0; +} + +/** + * tracefs_synth_complete - tell if the tracefs_synth is complete or not + * @synth: The synthetic event to get the start hist from. + * + * Retruns true if the synthetic event @synth has both a start and + * end event (ie. a synthetic event, or just a histogram), and + * false otherwise. + */ +bool tracefs_synth_complete(struct tracefs_synth *synth) +{ + return synth && synth->start_event && synth->end_event; +} + +/** + * tracefs_synth_get_start_hist - Return the histogram of the start event + * @synth: The synthetic event to get the start hist from. + * + * On success, returns a tracefs_hist descriptor that holds the + * histogram information of the start_event of the synthetic event + * structure. Returns NULL on failure. + */ +struct tracefs_hist * +tracefs_synth_get_start_hist(struct tracefs_synth *synth) +{ + struct tracefs_hist *hist = NULL; + struct tep_handle *tep; + const char *system; + const char *event; + const char *key; + char **keys; + int *types; + int ret; + int i; + + if (!synth) { + errno = EINVAL; + return NULL; + } + + system = synth->start_event->system; + event = synth->start_event->name; + types = synth->start_type; + keys = synth->start_keys; + tep = synth->tep; + + if (!keys) + keys = synth->start_selection; + + if (!keys) + return NULL; + + for (i = 0; keys[i]; i++) { + int type = types ? types[i] : 0; + + if (type == HIST_COUNTER_TYPE) + continue; + + key = keys[i]; + + if (i) { + ret = tracefs_hist_add_key(hist, key, type); + if (ret < 0) { + tracefs_hist_free(hist); + return NULL; + } + } else { + hist = tracefs_hist_alloc(tep, system, event, + key, type); + if (!hist) + return NULL; + } + } + + if (!hist) + return NULL; + + for (i = 0; keys[i]; i++) { + int type = types ? types[i] : 0; + + if (type != HIST_COUNTER_TYPE) + continue; + + key = keys[i]; + + ret = tracefs_hist_add_value(hist, key); + if (ret < 0) { + tracefs_hist_free(hist); + return NULL; + } + } + + if (synth->start_filter) { + hist->filter = strdup(synth->start_filter); + if (!hist->filter) { + tracefs_hist_free(hist); + return NULL; + } + } + + return hist; +} + +/** + * tracefs_synth_create - creates the synthetic event on the system + * @synth: The tracefs_synth descriptor + * + * This creates the synthetic events. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be or a problem + * writing into the system. + */ +int tracefs_synth_create(struct tracefs_synth *synth) +{ + int ret; + + if (!synth) { + errno = EINVAL; + return -1; + } + + if (!synth->name || !synth->end_event) { + errno = EUNATCH; + return -1; + } + + if (verify_state(synth) < 0) + return -1; + + if (!synth->dyn_event && alloc_synthetic_event(synth)) + return -1; + if (tracefs_dynevent_create(synth->dyn_event)) + return -1; + + synth->start_hist = create_hist(synth->start_keys, synth->start_vars); + synth->start_hist = append_filter(synth->start_hist, synth->start_filter, + synth->start_parens); + if (!synth->start_hist) + goto remove_synthetic; + + synth->end_hist = create_end_hist(synth); + synth->end_hist = append_filter(synth->end_hist, synth->end_filter, + synth->end_parens); + if (!synth->end_hist) + goto remove_synthetic; + + ret = tracefs_event_file_append(synth->instance, synth->start_event->system, + synth->start_event->name, + "trigger", synth->start_hist); + if (ret < 0) + goto remove_synthetic; + + ret = tracefs_event_file_append(synth->instance, synth->end_event->system, + synth->end_event->name, + "trigger", synth->end_hist); + if (ret < 0) + goto remove_start_hist; + + return 0; + + remove_start_hist: + remove_hist(synth->instance, synth->start_event, synth->start_hist); + remove_synthetic: + tracefs_dynevent_destroy(synth->dyn_event, false); + return -1; +} + +/** + * tracefs_synth_destroy - delete the synthetic event from the system + * @synth: The tracefs_synth descriptor + * + * This will destroy a synthetic event created by tracefs_synth_create() + * with the same @synth. + * + * It will attempt to disable the synthetic event in its instance (top by default), + * but if other instances have it active, it is likely to fail, which will likely + * fail on all other parts of tearing down the synthetic event. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + * ENIVAL - a parameter is passed as NULL that should not be or a problem + * writing into the system. + */ +int tracefs_synth_destroy(struct tracefs_synth *synth) +{ + char *hist; + int ret; + + if (!synth) { + errno = EINVAL; + return -1; + } + + if (!synth->name || !synth->end_event) { + errno = EUNATCH; + return -1; + } + + /* Try to disable the event if possible */ + tracefs_event_disable(synth->instance, "synthetic", synth->name); + + hist = create_end_hist(synth); + hist = append_filter(hist, synth->end_filter, + synth->end_parens); + if (!hist) + return -1; + ret = remove_hist(synth->instance, synth->end_event, hist); + free(hist); + + hist = create_hist(synth->start_keys, synth->start_vars); + hist = append_filter(hist, synth->start_filter, + synth->start_parens); + if (!hist) + return -1; + + ret = remove_hist(synth->instance, synth->start_event, hist); + free(hist); + + ret = tracefs_dynevent_destroy(synth->dyn_event, true); + + return ret ? -1 : 0; +} + +/** + * tracefs_synth_echo_cmd - show the command lines to create the synthetic event + * @seq: The trace_seq to store the command lines in + * @synth: The tracefs_synth descriptor + * + * This will list the "echo" commands that are equivalent to what would + * be executed by the tracefs_synth_create() command. + * + * Returns 0 on succes and -1 on error. + * On error, errno is set to: + * ENOMEM - memory allocation failure. + */ +int tracefs_synth_echo_cmd(struct trace_seq *seq, + struct tracefs_synth *synth) +{ + bool new_event = false; + char *hist = NULL; + char *path; + int ret = -1; + + if (!synth) { + errno = EINVAL; + return -1; + } + + if (!synth->name || !synth->end_event) { + errno = EUNATCH; + return -1; + } + + if (!synth->dyn_event) { + if (alloc_synthetic_event(synth)) + return -1; + new_event = true; + } + + path = trace_find_tracing_dir(false); + if (!path) + goto out_free; + + trace_seq_printf(seq, "echo '%s%s%s %s' >> %s/%s\n", + synth->dyn_event->prefix, + strlen(synth->dyn_event->prefix) ? ":" : "", + synth->dyn_event->event, + synth->dyn_event->format, path, synth->dyn_event->trace_file); + + tracefs_put_tracing_file(path); + path = tracefs_instance_get_dir(synth->instance); + + hist = create_hist(synth->start_keys, synth->start_vars); + hist = append_filter(hist, synth->start_filter, + synth->start_parens); + if (!hist) + goto out_free; + + trace_seq_printf(seq, "echo '%s' >> %s/events/%s/%s/trigger\n", + hist, path, synth->start_event->system, + synth->start_event->name); + free(hist); + hist = create_end_hist(synth); + hist = append_filter(hist, synth->end_filter, + synth->end_parens); + if (!hist) + goto out_free; + + trace_seq_printf(seq, "echo '%s' >> %s/events/%s/%s/trigger\n", + hist, path, synth->end_event->system, + synth->end_event->name); + + ret = 0; + out_free: + free(hist); + tracefs_put_tracing_file(path); + if (new_event) { + tracefs_dynevent_free(synth->dyn_event); + synth->dyn_event = NULL; + } + return ret; +} + +/** + * tracefs_synth_get_event - return tep event representing the given synthetic event + * @tep: a handle to the trace event parser context that holds the events + * @synth: a synthetic event context, describing given synthetic event. + * + * Returns a pointer to a tep event describing the given synthetic event. The pointer + * is managed by the @tep handle and must not be freed. In case of an error, or in case + * the requested synthetic event is missing in the @tep handler - NULL is returned. + */ +struct tep_event * +tracefs_synth_get_event(struct tep_handle *tep, struct tracefs_synth *synth) +{ + if (!tep || !synth || !synth->name) + return NULL; + + return get_tep_event(tep, SYNTHETIC_GROUP, synth->name); +} diff --git a/src/tracefs-instance.c b/src/tracefs-instance.c new file mode 100644 index 0000000..57f5c7f --- /dev/null +++ b/src/tracefs-instance.c @@ -0,0 +1,1241 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + * Updates: + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <errno.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <regex.h> +#include <limits.h> +#include <pthread.h> +#include "tracefs.h" +#include "tracefs-local.h" + +enum { + FLAG_INSTANCE_NEWLY_CREATED = (1 << 0), + FLAG_INSTANCE_DELETED = (1 << 1), +}; + + +struct tracefs_options_mask toplevel_supported_opts; +struct tracefs_options_mask toplevel_enabled_opts; + +__hidden inline struct tracefs_options_mask * +supported_opts_mask(struct tracefs_instance *instance) +{ + return instance ? &instance->supported_opts : &toplevel_supported_opts; +} + +__hidden inline struct tracefs_options_mask * +enabled_opts_mask(struct tracefs_instance *instance) +{ + return instance ? &instance->enabled_opts : &toplevel_enabled_opts; +} + +/** + * instance_alloc - allocate a new ftrace instance + * @trace_dir - Full path to the tracing directory, where the instance is + * @name: The name of the instance (instance will point to this) + * + * Returns a newly allocated instance, or NULL in case of an error. + */ +static struct tracefs_instance *instance_alloc(const char *trace_dir, const char *name) +{ + struct tracefs_instance *instance; + + instance = calloc(1, sizeof(*instance)); + if (!instance) + goto error; + instance->trace_dir = strdup(trace_dir); + if (!instance->trace_dir) + goto error; + if (name) { + instance->name = strdup(name); + if (!instance->name) + goto error; + } + + if (pthread_mutex_init(&instance->lock, NULL) < 0) + goto error; + + instance->ftrace_filter_fd = -1; + instance->ftrace_notrace_fd = -1; + instance->ftrace_marker_fd = -1; + instance->ftrace_marker_raw_fd = -1; + + return instance; + +error: + if (instance) { + free(instance->name); + free(instance->trace_dir); + free(instance); + } + return NULL; +} + + +__hidden int trace_get_instance(struct tracefs_instance *instance) +{ + int ret; + + pthread_mutex_lock(&instance->lock); + if (instance->flags & FLAG_INSTANCE_DELETED) { + ret = -1; + } else { + instance->ref++; + ret = 0; + } + pthread_mutex_unlock(&instance->lock); + return ret; +} + +__hidden void trace_put_instance(struct tracefs_instance *instance) +{ + pthread_mutex_lock(&instance->lock); + if (--instance->ref < 0) + instance->flags |= FLAG_INSTANCE_DELETED; + pthread_mutex_unlock(&instance->lock); + + if (!(instance->flags & FLAG_INSTANCE_DELETED)) + return; + + if (instance->ftrace_filter_fd >= 0) + close(instance->ftrace_filter_fd); + + if (instance->ftrace_notrace_fd >= 0) + close(instance->ftrace_notrace_fd); + + if (instance->ftrace_marker_fd >= 0) + close(instance->ftrace_marker_fd); + + if (instance->ftrace_marker_raw_fd >= 0) + close(instance->ftrace_marker_raw_fd); + + free(instance->trace_dir); + free(instance->name); + pthread_mutex_destroy(&instance->lock); + free(instance); +} + +/** + * tracefs_instance_free - Free an instance, previously allocated by + tracefs_instance_create() + * @instance: Pointer to the instance to be freed + * + */ +void tracefs_instance_free(struct tracefs_instance *instance) +{ + if (!instance) + return; + + trace_put_instance(instance); +} + +static mode_t get_trace_file_permissions(char *name) +{ + mode_t rmode = 0; + struct stat st; + char *path; + int ret; + + path = tracefs_get_tracing_file(name); + if (!path) + return 0; + ret = stat(path, &st); + if (ret) + goto out; + rmode = st.st_mode & ACCESSPERMS; +out: + tracefs_put_tracing_file(path); + return rmode; +} + +/** + * tracefs_instance_is_new - Check if the instance is newly created by the library + * @instance: Pointer to an ftrace instance + * + * Returns true, if the ftrace instance is newly created by the library or + * false otherwise. + */ +bool tracefs_instance_is_new(struct tracefs_instance *instance) +{ + if (instance && (instance->flags & FLAG_INSTANCE_NEWLY_CREATED)) + return true; + return false; +} + +/** + * tracefs_instance_create - Create a new ftrace instance + * @name: Name of the instance to be created + * + * Allocates and initializes a new instance structure. If the instance does not + * exist in the system, create it. + * Returns a pointer to a newly allocated instance, or NULL in case of an error. + * The returned instance must be freed by tracefs_instance_free(). + */ +struct tracefs_instance *tracefs_instance_create(const char *name) +{ + struct tracefs_instance *inst = NULL; + char *path = NULL; + const char *tdir; + struct stat st; + mode_t mode; + int ret; + + tdir = tracefs_tracing_dir(); + if (!tdir) + return NULL; + inst = instance_alloc(tdir, name); + if (!inst) + return NULL; + + path = tracefs_instance_get_dir(inst); + ret = stat(path, &st); + if (ret < 0) { + /* Cannot create the top instance, if it does not exist! */ + if (!name) + goto error; + mode = get_trace_file_permissions("instances"); + if (mkdir(path, mode)) + goto error; + inst->flags |= FLAG_INSTANCE_NEWLY_CREATED; + } + tracefs_put_tracing_file(path); + return inst; + +error: + tracefs_instance_free(inst); + return NULL; +} + +/** + * tracefs_instance_alloc - Allocate an instance structure for existing trace instance + * @tracing_dir: full path to the system trace directory, where the new instance is + * if NULL, the default top tracing directory is used. + * @name: Name of the instance. + * + * Allocates and initializes a new instance structure. If the instance does not + * exist, do not create it and exit with error. + * Returns a pointer to a newly allocated instance, or NULL in case of an error + * or the requested instance does not exists. + * The returned instance must be freed by tracefs_instance_free(). + */ +struct tracefs_instance *tracefs_instance_alloc(const char *tracing_dir, + const char *name) +{ + struct tracefs_instance *inst = NULL; + char file[PATH_MAX]; + const char *tdir; + struct stat st; + int ret; + + if (tracing_dir) { + ret = stat(tracing_dir, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + return NULL; + tdir = tracing_dir; + + } else + tdir = tracefs_tracing_dir(); + if (!tdir) + return NULL; + + if (name) { + sprintf(file, "%s/instances/%s", tdir, name); + ret = stat(file, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + return NULL; + } + inst = instance_alloc(tdir, name); + + return inst; +} + +/** + * tracefs_instance_destroy - Remove a ftrace instance + * @instance: Pointer to the instance to be removed + * + * Returns -1 in case of an error, or 0 otherwise. + */ +int tracefs_instance_destroy(struct tracefs_instance *instance) +{ + char *path; + int ret = -1; + + if (!instance || !instance->name) { + tracefs_warning("Cannot remove top instance"); + return -1; + } + + path = tracefs_instance_get_dir(instance); + if (path) + ret = rmdir(path); + tracefs_put_tracing_file(path); + if (ret) { + pthread_mutex_lock(&instance->lock); + instance->flags |= FLAG_INSTANCE_DELETED; + pthread_mutex_unlock(&instance->lock); + } + + return ret; +} + +/** + * tracefs_instance_get_file - return the path to an instance file. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of file to return + * + * Returns the path of the @file for the given @instance, or NULL in + * case of an error. + * + * Must use tracefs_put_tracing_file() to free the returned string. + */ +char * +tracefs_instance_get_file(struct tracefs_instance *instance, const char *file) +{ + char *path = NULL; + int ret; + + if (!instance) + return tracefs_get_tracing_file(file); + if (!instance->name) + ret = asprintf(&path, "%s/%s", instance->trace_dir, file); + else + ret = asprintf(&path, "%s/instances/%s/%s", + instance->trace_dir, instance->name, file); + if (ret < 0) + return NULL; + + return path; +} + +/** + * tracefs_instance_get_dir - return the path to the instance directory. + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns the full path to the instance directory + * + * Must use tracefs_put_tracing_file() to free the returned string. + */ +char *tracefs_instance_get_dir(struct tracefs_instance *instance) +{ + char *path = NULL; + int ret; + + if (!instance) /* Top instance of default system trace directory */ + return trace_find_tracing_dir(false); + + if (!instance->name) + return strdup(instance->trace_dir); + + ret = asprintf(&path, "%s/instances/%s", instance->trace_dir, instance->name); + if (ret < 0) { + tracefs_warning("Failed to allocate path for instance %s", + instance->name); + return NULL; + } + + return path; +} + +/** + * tracefs_instance_get_name - return the name of an instance + * @instance: ftrace instance + * + * Returns the name of the given @instance. + * The returned string must *not* be freed. + */ +const char *tracefs_instance_get_name(struct tracefs_instance *instance) +{ + if (instance) + return instance->name; + return NULL; +} + +/** + * tracefs_instance_get_buffer_size - return the buffer size of the ring buffer + * @instance: The instance to get the buffer size from + * @cpu: if less that zero, will return the total size, otherwise the cpu size + * + * Returns the buffer size. If @cpu is less than zero, it returns the total size + * of the ring buffer otherwise it returs the size of the buffer for the given + * CPU. + * + * Returns -1 on error. + */ +ssize_t tracefs_instance_get_buffer_size(struct tracefs_instance *instance, int cpu) +{ + unsigned long long size; + char *path; + char *val; + int ret; + + if (cpu < 0) { + val = tracefs_instance_file_read(instance, "buffer_total_size_kb", NULL); + } else { + ret = asprintf(&path, "per_cpu/cpu%d/buffer_size_kb", cpu); + if (ret < 0) + return ret; + + val = tracefs_instance_file_read(instance, path, NULL); + free(path); + } + + if (!val) + return -1; + + size = strtoull(val, NULL, 0); + free(val); + return size; +} + +int tracefs_instance_set_buffer_size(struct tracefs_instance *instance, size_t size, int cpu) +{ + char *path; + char *val; + int ret; + + ret = asprintf(&val, "%zd", size); + if (ret < 0) + return ret; + + if (cpu < 0) { + ret = tracefs_instance_file_write(instance, "buffer_size_kb", val); + } else { + ret = asprintf(&path, "per_cpu/cpu%d/buffer_size_kb", cpu); + if (ret < 0) { + free(val); + return ret; + } + + ret = tracefs_instance_file_write(instance, path, val); + free(path); + } + free(val); + + return ret < 0 ? -1 : 0; +} + +/** + * tracefs_instance_get_trace_dir - return the top trace directory, where the instance is confuigred + * @instance: ftrace instance + * + * Returns the top trace directory where the given @instance is configured. + * The returned string must *not* be freed. + */ +const char *tracefs_instance_get_trace_dir(struct tracefs_instance *instance) +{ + if (instance) + return instance->trace_dir; + return NULL; +} + +static int write_file(const char *file, const char *str, int flags) +{ + int ret = 0; + int fd; + + fd = open(file, flags); + if (fd < 0) { + tracefs_warning("Failed to open '%s'", file); + return -1; + } + + if (str) + ret = write(fd, str, strlen(str)); + + close(fd); + return ret; +} + +static int instance_file_write(struct tracefs_instance *instance, + const char *file, const char *str, int flags) +{ + struct stat st; + char *path; + int ret; + + path = tracefs_instance_get_file(instance, file); + if (!path) + return -1; + ret = stat(path, &st); + if (ret == 0) + ret = write_file(path, str, flags); + tracefs_put_tracing_file(path); + + return ret; +} + +/** + * tracefs_instance_file_write - Write in trace file of specific instance. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @str: nul terminated string, that will be written in the file. + * + * Returns the number of written bytes, or -1 in case of an error + */ +int tracefs_instance_file_write(struct tracefs_instance *instance, + const char *file, const char *str) +{ + return instance_file_write(instance, file, str, O_WRONLY | O_TRUNC); +} + +/** + * tracefs_instance_file_append - Append to a trace file of specific instance. + * @instance: ftrace instance, can be NULL for the top instance. + * @file: name of the file. + * @str: nul terminated string, that will be appended to the file. + * + * Returns the number of appended bytes, or -1 in case of an error. + */ +int tracefs_instance_file_append(struct tracefs_instance *instance, + const char *file, const char *str) +{ + return instance_file_write(instance, file, str, O_WRONLY); +} + +/** + * tracefs_instance_file_clear - Clear a trace file of specific instance. + * Note, it only opens with O_TRUNC and closes the file. If the file has + * content that does not get cleared in this way, this will not have any + * effect. For example, set_ftrace_filter can have probes that are not + * cleared by O_TRUNC: + * + * echo "schedule:stacktrace" > set_ftrace_filter + * + * This function will not clear the above "set_ftrace_filter" after that + * command. + * @instance: ftrace instance, can be NULL for the top instance. + * @file: name of the file to clear. + * + * Returns 0 on success, or -1 in case of an error. + */ +int tracefs_instance_file_clear(struct tracefs_instance *instance, + const char *file) +{ + return instance_file_write(instance, file, NULL, O_WRONLY | O_TRUNC); +} + +/** + * tracefs_instance_file_read - Read from a trace file of specific instance. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @psize: returns the number of bytes read + * + * Returns a pointer to a nul terminated string, read from the file, or NULL in + * case of an error. + * The return string must be freed by free() + */ +char *tracefs_instance_file_read(struct tracefs_instance *instance, + const char *file, int *psize) +{ + char *buf = NULL; + int size = 0; + char *path; + + path = tracefs_instance_get_file(instance, file); + if (!path) + return NULL; + + size = str_read_file(path, &buf, true); + + tracefs_put_tracing_file(path); + if (buf && psize) + *psize = size; + + return buf; +} + +/** + * tracefs_instance_file_read_number - Read long long integer from a trace file. + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @res: The integer from the file. + * + * Returns 0 if the reading is successful and the result is stored in res, -1 + * in case of an error. + */ +int tracefs_instance_file_read_number(struct tracefs_instance *instance, + const char *file, long long *res) +{ + long long num; + int ret = -1; + int size = 0; + char *endptr; + char *str; + + str = tracefs_instance_file_read(instance, file, &size); + if (size && str) { + errno = 0; + num = strtoll(str, &endptr, 0); + if (errno == 0 && str != endptr) { + *res = num; + ret = 0; + } + } + free(str); + return ret; +} + +/** + * tracefs_instance_file_open - Open a trace file for reading and writing + * @instance: ftrace instance, can be NULL for the top instance + * @file: name of the file + * @mode: file open flags, -1 for default O_RDWR + * + * Returns -1 in case of an error, or a valid file descriptor otherwise. + * The returned FD must be closed with close() + */ +int tracefs_instance_file_open(struct tracefs_instance *instance, + const char *file, int mode) +{ + int flags = O_RDWR; + int fd = -1; + char *path; + + path = tracefs_instance_get_file(instance, file); + if (!path) + return -1; + + if (mode >= 0) + flags = mode; + fd = open(path, flags); + tracefs_put_tracing_file(path); + + return fd; +} + +static bool check_file_exists(struct tracefs_instance *instance, + const char *name, bool dir) +{ + char file[PATH_MAX]; + struct stat st; + char *path; + int ret; + + path = tracefs_instance_get_dir(instance); + if (name) + snprintf(file, PATH_MAX, "%s/%s", path, name); + else + snprintf(file, PATH_MAX, "%s", path); + tracefs_put_tracing_file(path); + ret = stat(file, &st); + if (ret < 0) + return false; + + return !dir == !S_ISDIR(st.st_mode); +} + +/** + * tracefs_instance_exists - Check an instance with given name exists + * @name: name of the instance + * + * Returns true if the instance exists, false otherwise + * + */ +bool tracefs_instance_exists(const char *name) +{ + char file[PATH_MAX]; + + if (!name) + return false; + snprintf(file, PATH_MAX, "instances/%s", name); + return check_file_exists(NULL, file, true); +} + +/** + * tracefs_file_exists - Check if a file with given name exists in given instance + * @instance: ftrace instance, can be NULL for the top instance + * @name: name of the file + * + * Returns true if the file exists, false otherwise + * + * If a directory with the given name exists, false is returned. + */ +bool tracefs_file_exists(struct tracefs_instance *instance, const char *name) +{ + return check_file_exists(instance, name, false); +} + +/** + * tracefs_dir_exists - Check if a directory with given name exists in given instance + * @instance: ftrace instance, can be NULL for the top instance + * @name: name of the directory + * + * Returns true if the directory exists, false otherwise + */ +bool tracefs_dir_exists(struct tracefs_instance *instance, const char *name) +{ + return check_file_exists(instance, name, true); +} + +/** + * tracefs_instances_walk - Iterate through all ftrace instances in the system + * @callback: user callback, called for each instance. Instance name is passed + * as input parameter. If the @callback returns non-zero, + * the iteration stops. + * @context: user context, passed to the @callback. + * + * Returns -1 in case of an error, 1 if the iteration was stopped because of the + * callback return value or 0 otherwise. + */ +int tracefs_instances_walk(int (*callback)(const char *, void *), void *context) +{ + struct dirent *dent; + char *path = NULL; + DIR *dir = NULL; + struct stat st; + int fret = -1; + int ret; + + path = tracefs_get_tracing_file("instances"); + if (!path) + return -1; + ret = stat(path, &st); + if (ret < 0 || !S_ISDIR(st.st_mode)) + goto out; + + dir = opendir(path); + if (!dir) + goto out; + fret = 0; + while ((dent = readdir(dir))) { + char *instance; + + if (strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) + continue; + instance = trace_append_file(path, dent->d_name); + ret = stat(instance, &st); + free(instance); + if (ret < 0 || !S_ISDIR(st.st_mode)) + continue; + if (callback(dent->d_name, context)) { + fret = 1; + break; + } + } + +out: + if (dir) + closedir(dir); + tracefs_put_tracing_file(path); + return fret; +} + +static inline bool match(const char *str, regex_t *re) +{ + if (!re) + return true; + return regexec(re, str, 0, NULL, 0) == 0; +} + +struct instance_list { + regex_t *re; + char **list; + int failed; +}; + +static int build_list(const char *name, void *data) +{ + struct instance_list *list = data; + char **instances; + int ret = -1; + + if (!match(name, list->re)) + return 0; + + instances = tracefs_list_add(list->list, name); + if (!instances) + goto out; + + list->list = instances; + ret = 0; + + out: + list->failed = ret; + return ret; +} + +/** + * tracefs_instances - return a list of instance names + * @regex: A regex of instances to filter on (NULL to match all) + * + * Returns a list of names of existing instances, that must be + * freed with tracefs_list_free(). Note, if there are no matches + * then an empty list will be returned (not NULL). + * NULL on error. + */ +char **tracefs_instances(const char *regex) +{ + struct instance_list list = { .re = NULL, .list = NULL }; + regex_t re; + int ret; + + if (regex) { + ret = regcomp(&re, regex, REG_ICASE|REG_NOSUB); + if (ret < 0) + return NULL; + list.re = &re; + } + + ret = tracefs_instances_walk(build_list, &list); + if (ret < 0 || list.failed) { + tracefs_list_free(list.list); + list.list = NULL; + } else { + /* No matches should produce an empty list */ + if (!list.list) + list.list = trace_list_create_empty(); + } + return list.list; +} + +/** + * tracefs_get_clock - Get the current trace clock + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns the current trace clock of the given instance, or NULL in + * case of an error. + * The return string must be freed by free() + */ +char *tracefs_get_clock(struct tracefs_instance *instance) +{ + char *all_clocks = NULL; + char *ret = NULL; + int bytes = 0; + char *clock; + char *cont; + + all_clocks = tracefs_instance_file_read(instance, "trace_clock", &bytes); + if (!all_clocks || !bytes) + goto out; + + clock = strstr(all_clocks, "["); + if (!clock) + goto out; + clock++; + cont = strstr(clock, "]"); + if (!cont) + goto out; + *cont = '\0'; + + ret = strdup(clock); +out: + free(all_clocks); + return ret; +} + +/** + * tracefs_instance_set_affinity_raw - write a hex bitmask into the affinity + * @instance: The instance to set affinity to (NULL for top level) + * @mask: String containing the hex value to set the tracing affinity to. + * + * Sets the tracing affinity CPU mask for @instance. The @mask is the raw + * value that is used to write into the tracing system. + * + * Return 0 on success and -1 on error. + */ +int tracefs_instance_set_affinity_raw(struct tracefs_instance *instance, + const char *mask) +{ + return tracefs_instance_file_write(instance, "tracing_cpumask", mask); +} + +/** + * tracefs_instance_set_affinity_set - use a cpu_set to define tracing affinity + * @instance: The instance to set affinity to (NULL for top level) + * @set: A CPU set that describes the CPU affinity to set tracing to. + * @set_size: The size in bytes of @set (use CPU_ALLOC_SIZE() to get this value) + * + * Sets the tracing affinity CPU mask for @instance. The bits in @set will be + * used to set the CPUs to have tracing on. + * + * If @set is NULL, then all CPUs defined by sysconf(_SC_NPROCESSORS_CONF) + * will be set, and @set_size is ignored. + * + * Return 0 on success and -1 on error. + */ +int tracefs_instance_set_affinity_set(struct tracefs_instance *instance, + cpu_set_t *set, size_t set_size) +{ + struct trace_seq seq; + bool free_set = false; + bool hit = false; + int nr_cpus; + int cpu; + int ret = -1; + int w, n, i; + + trace_seq_init(&seq); + + /* NULL set means all CPUs to be set */ + if (!set) { + nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + set = CPU_ALLOC(nr_cpus); + if (!set) + goto out; + set_size = CPU_ALLOC_SIZE(nr_cpus); + CPU_ZERO_S(set_size, set); + /* Set all CPUS */ + for (cpu = 0; cpu < nr_cpus; cpu++) + CPU_SET_S(cpu, set_size, set); + free_set = true; + } + /* Convert to a bitmask hex string */ + nr_cpus = (set_size + 1) * 8; + if (nr_cpus < 1) { + /* Must have at least one bit set */ + errno = EINVAL; + goto out; + } + /* Start backwards from 32 bits */ + for (w = ((nr_cpus + 31) / 32) - 1; w >= 0; w--) { + /* Now move one nibble at a time */ + for (n = 7; n >= 0; n--) { + int nibble = 0; + + if ((n * 4) + (w * 32) >= nr_cpus) + continue; + + /* One bit at a time */ + for (i = 3; i >= 0; i--) { + cpu = (w * 32) + (n * 4) + i; + if (cpu >= nr_cpus) + continue; + if (CPU_ISSET_S(cpu, set_size, set)) { + nibble |= 1 << i; + hit = true; + } + } + if (hit && trace_seq_printf(&seq, "%x", nibble) < 0) + goto out; + } + if (hit && w) + if (trace_seq_putc(&seq, ',') < 0) + goto out; + } + if (!hit) { + errno = EINVAL; + goto out; + } + trace_seq_terminate(&seq); + ret = tracefs_instance_set_affinity_raw(instance, seq.buffer); + out: + trace_seq_destroy(&seq); + if (free_set) + CPU_FREE(set); + return ret; +} + +/** + * tracefs_instance_set_affinity - Set the affinity defined by CPU values. + * @instance: The instance to set affinity to (NULL for top level) + * @cpu_str: A string of values that define what CPUs to set. + * + * Sets the tracing affinity CPU mask for @instance. The @cpu_str is a set + * of decimal numbers used to state which CPU should be part of the affinity + * mask. A range may also be specified via a hyphen. + * + * For example, "1,4,6-8" + * + * The numbers do not need to be in order. + * + * If @cpu_str is NULL, then all CPUs defined by sysconf(_SC_NPROCESSORS_CONF) + * will be set. + * + * Return 0 on success and -1 on error. + */ +int tracefs_instance_set_affinity(struct tracefs_instance *instance, + const char *cpu_str) +{ + cpu_set_t *set = NULL; + size_t set_size; + char *word; + char *cpus; + char *del; + char *c; + int max_cpu = 0; + int cpu1, cpu2; + int len; + int ret = -1; + + /* NULL cpu_str means to set all CPUs in the mask */ + if (!cpu_str) + return tracefs_instance_set_affinity_set(instance, NULL, 0); + + /* First, find out how many CPUs are needed */ + cpus = strdup(cpu_str); + if (!cpus) + return -1; + len = strlen(cpus) + 1; + for (word = strtok_r(cpus, ",", &del); word; word = strtok_r(NULL, ",", &del)) { + cpu1 = atoi(word); + if (cpu1 < 0) { + errno = EINVAL; + goto out; + } + if (cpu1 > max_cpu) + max_cpu = cpu1; + cpu2 = -1; + if ((c = strchr(word, '-'))) { + c++; + cpu2 = atoi(c); + if (cpu2 < cpu1) { + errno = EINVAL; + goto out; + } + if (cpu2 > max_cpu) + max_cpu = cpu2; + } + } + /* + * Now ideally, cpus should fit cpu_str as it was orginally allocated + * by strdup(). But I'm paranoid, and can imagine someone playing tricks + * with threads, and changes cpu_str from another thread and messes + * with this. At least only copy what we know is allocated. + */ + strncpy(cpus, cpu_str, len); + + set = CPU_ALLOC(max_cpu + 1); + if (!set) + goto out; + set_size = CPU_ALLOC_SIZE(max_cpu + 1); + CPU_ZERO_S(set_size, set); + + for (word = strtok_r(cpus, ",", &del); word; word = strtok_r(NULL, ",", &del)) { + cpu1 = atoi(word); + if (cpu1 < 0 || cpu1 > max_cpu) { + /* Someone playing games? */ + errno = EACCES; + goto out; + } + cpu2 = cpu1; + if ((c = strchr(word, '-'))) { + c++; + cpu2 = atoi(c); + if (cpu2 < cpu1 || cpu2 > max_cpu) { + errno = EACCES; + goto out; + } + } + for ( ; cpu1 <= cpu2; cpu1++) + CPU_SET(cpu1, set); + } + ret = tracefs_instance_set_affinity_set(instance, set, set_size); + out: + free(cpus); + CPU_FREE(set); + return ret; +} + +/** + * tracefs_instance_get_affinity_raw - read the affinity instance file + * @instance: The instance to get affinity of (NULL for top level) + * + * Reads the affinity file for @instance (or the top level if @instance + * is NULL) and returns it. The returned string must be freed with free(). + * + * Returns the affinity mask on success, and must be freed with free() + * or NULL on error. + */ +char *tracefs_instance_get_affinity_raw(struct tracefs_instance *instance) +{ + return tracefs_instance_file_read(instance, "tracing_cpumask", NULL); +} + +static inline int update_cpu_set(int cpus, int cpu_set, int cpu, + cpu_set_t *set, size_t set_size) +{ + int bit = 1 << cpu; + + if (!(cpus & bit)) + return 0; + + CPU_SET_S(cpu_set + cpu, set_size, set); + + /* + * It is possible that the passed in set_size is not big enough + * to hold the cpu we just set. If that's the case, do not report + * it as being set. + * + * The CPU_ISSET_S() should return false if the CPU given to it + * is bigger than the set itself. + */ + return CPU_ISSET_S(cpu_set + cpu, set_size, set) ? 1 : 0; +} + +/** + * tracefs_instance_get_affinity_set - Retrieve the cpuset of an instance affinity + * @instance: The instance to get affinity of (NULL for top level) + * @set: A CPU set to put the affinity into. + * @set_size: The size in bytes of @set (use CPU_ALLOC_SIZE() to get this value) + * + * Reads the affinity of a given instance and updates the CPU set by the + * instance. + * + * Returns the number of CPUS that are set, or -1 on error. + */ +int tracefs_instance_get_affinity_set(struct tracefs_instance *instance, + cpu_set_t *set, size_t set_size) +{ + char *affinity; + int cpu_set; + int cpus; + int cnt = 0; + int ch; + int i; + + if (!set || !set_size) { + errno = -EINVAL; + return -1; + } + + affinity = tracefs_instance_get_affinity_raw(instance); + if (!affinity) + return -1; + + /* + * The returned affinity should be a comma delimited + * hex string. Work backwards setting the values. + */ + cpu_set = 0; + i = strlen(affinity); + for (i--; i >= 0; i--) { + ch = affinity[i]; + if (isalnum(ch)) { + ch = tolower(ch); + if (isdigit(ch)) + cpus = ch - '0'; + else + cpus = ch - 'a' + 10; + + cnt += update_cpu_set(cpus, cpu_set, 0, set, set_size); + cnt += update_cpu_set(cpus, cpu_set, 1, set, set_size); + cnt += update_cpu_set(cpus, cpu_set, 2, set, set_size); + cnt += update_cpu_set(cpus, cpu_set, 3, set, set_size); + /* Next nibble */ + cpu_set += 4; + } + } + + free(affinity); + + return cnt; +} + +static inline int update_cpu(int cpus, int cpu_set, int cpu, int s, char **set) +{ + char *list; + int bit = 1 << cpu; + int ret; + + if (*set == (char *)-1) + return s; + + if (cpus & bit) { + /* If the previous CPU is set just return s */ + if (s >= 0) + return s; + /* Otherwise, return this cpu */ + return cpu_set + cpu; + } + + /* If the last CPU wasn't set, just return s */ + if (s < 0) + return s; + + /* Update the string */ + if (s == cpu_set + cpu - 1) { + ret = asprintf(&list, "%s%s%d", + *set ? *set : "", *set ? "," : "", s); + } else { + ret = asprintf(&list, "%s%s%d-%d", + *set ? *set : "", *set ? "," : "", + s, cpu_set + cpu - 1); + } + free(*set); + /* Force *set to be a failure */ + if (ret < 0) + *set = (char *)-1; + else + *set = list; + return -1; +} + +/** + * tracefs_instance_get_affinity - Retrieve a string of CPUs for instance affinity + * @instance: The instance to get affinity of (NULL for top level) + * + * Reads the affinity of a given instance and returns a CPU count of the + * instance. For example, if it reads "eb" it will return: + * "0-1,3,5-7" + * + * If no CPUs are set, an empty string is returned "\0", and it too needs + * to be freed. + * + * Returns an allocated string containing the CPU affinity in "human readable" + * format which needs to be freed with free(), or NULL on error. + */ +char *tracefs_instance_get_affinity(struct tracefs_instance *instance) +{ + char *affinity; + char *set = NULL; + int cpu_set; + int cpus; + int ch; + int s = -1; + int i; + + affinity = tracefs_instance_get_affinity_raw(instance); + if (!affinity) + return NULL; + + /* + * The returned affinity should be a comma delimited + * hex string. Work backwards setting the values. + */ + cpu_set = 0; + i = strlen(affinity); + for (i--; i >= 0; i--) { + ch = affinity[i]; + if (isalnum(ch)) { + ch = tolower(ch); + if (isdigit(ch)) + cpus = ch - '0'; + else + cpus = ch - 'a' + 10; + s = update_cpu(cpus, cpu_set, 0, s, &set); + s = update_cpu(cpus, cpu_set, 1, s, &set); + s = update_cpu(cpus, cpu_set, 2, s, &set); + s = update_cpu(cpus, cpu_set, 3, s, &set); + + if (set == (char *)-1) { + set = NULL; + goto out; + } + /* Next nibble */ + cpu_set += 4; + } + } + /* Clean up in case the last CPU is set */ + s = update_cpu(0, cpu_set, 0, s, &set); + + if (!set) + set = strdup(""); + out: + free(affinity); + + return set; +} diff --git a/src/tracefs-kprobes.c b/src/tracefs-kprobes.c new file mode 100644 index 0000000..a8c0163 --- /dev/null +++ b/src/tracefs-kprobes.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org> + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#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 KPROBE_EVENTS "kprobe_events" +#define KPROBE_DEFAULT_GROUP "kprobes" + +static struct tracefs_dynevent * +kprobe_alloc(enum tracefs_dynevent_type type, const char *system, const char *event, + const char *addr, const char *format) +{ + struct tracefs_dynevent *kp; + const char *sys = system; + const char *ename = event; + char *tmp; + + if (!addr) { + errno = EBADMSG; + return NULL; + } + if (!sys) + sys = KPROBE_DEFAULT_GROUP; + + if (!event) { + ename = strdup(addr); + if (!ename) + return NULL; + tmp = strchr(ename, ':'); + if (tmp) + *tmp = '\0'; + } + + kp = dynevent_alloc(type, sys, ename, addr, format); + if (!event) + free((char *)ename); + + return kp; +} + +/** + * tracefs_kprobe_alloc - Allocate new kprobe + * @system: The system name (NULL for the default kprobes) + * @event: The event to create (NULL to use @addr for the event) + * @addr: The function and offset (or address) to insert the probe + * @format: The format string to define the probe. + * + * Allocate a kprobe context that will be in the @system group (or kprobes if + * @system is NULL). Have the name of @event (or @addr if @event is NULL). Will + * be inserted to @addr (function name, with or without offset, or a address). + * And the @format will define the format of the kprobe. + * + * See the Linux documentation file under: + * Documentation/trace/kprobetrace.rst + * + * The kprobe is not created in the system. + * + * Return a pointer to a kprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + * errno will be set to EBADMSG if addr is NULL. + */ +struct tracefs_dynevent * +tracefs_kprobe_alloc(const char *system, const char *event, const char *addr, const char *format) + +{ + return kprobe_alloc(TRACEFS_DYNEVENT_KPROBE, system, event, addr, format); +} + +/** + * tracefs_kretprobe_alloc - Allocate new kretprobe + * @system: The system name (NULL for the default kprobes) + * @event: The event to create (NULL to use @addr for the event) + * @addr: The function and offset (or address) to insert the retprobe + * @format: The format string to define the retprobe. + * @max: Maximum number of instances of the specified function that + * can be probed simultaneously, or 0 for the default value. + * + * Allocate a kretprobe that will be in the @system group (or kprobes if + * @system is NULL). Have the name of @event (or @addr if @event is + * NULL). Will be inserted to @addr (function name, with or without + * offset, or a address). And the @format will define the raw format + * of the kprobe. See the Linux documentation file under: + * Documentation/trace/kprobetrace.rst + * The kretprobe is not created in the system. + * + * Return a pointer to a kprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + * errno will be set to EBADMSG if addr is NULL. + */ +struct tracefs_dynevent * +tracefs_kretprobe_alloc(const char *system, const char *event, + const char *addr, const char *format, unsigned int max) +{ + struct tracefs_dynevent *kp; + int ret; + + kp = kprobe_alloc(TRACEFS_DYNEVENT_KRETPROBE, system, event, addr, format); + if (!kp) + return NULL; + + if (!max) + return kp; + + free(kp->prefix); + kp->prefix = NULL; + ret = asprintf(&kp->prefix, "r%d:", max); + if (ret < 0) + goto error; + + return kp; +error: + tracefs_dynevent_free(kp); + return NULL; +} + +static int kprobe_raw(enum tracefs_dynevent_type type, const char *system, + const char *event, const char *addr, const char *format) +{ + static struct tracefs_dynevent *kp; + int ret; + + kp = kprobe_alloc(type, system, event, addr, format); + if (!kp) + return -1; + + ret = tracefs_dynevent_create(kp); + tracefs_dynevent_free(kp); + + return ret; +} + +/** + * tracefs_kprobe_raw - Create a kprobe using raw format + * @system: The system name (NULL for the default kprobes) + * @event: The event to create (NULL to use @addr for the event) + * @addr: The function and offset (or address) to insert the probe + * @format: The raw format string to define the probe. + * + * Create a kprobe that will be in the @system group (or kprobes if + * @system is NULL). Have the name of @event (or @addr if @event is + * NULL). Will be inserted to @addr (function name, with or without + * offset, or a address). And the @format will define the raw format + * of the kprobe. See the Linux documentation file under: + * Documentation/trace/kprobetrace.rst + * + * Return 0 on success, or -1 on error. + * If the syntex of @format was incorrect, running + * tracefs_error_last(NULL) may show what went wrong. + * + * errno will be set to EBADMSG if addr or format is NULL. + */ +int tracefs_kprobe_raw(const char *system, const char *event, + const char *addr, const char *format) +{ + return kprobe_raw(TRACEFS_DYNEVENT_KPROBE, system, event, addr, format); +} + +/** + * tracefs_kretprobe_raw - Create a kretprobe using raw format + * @system: The system name (NULL for the default kprobes) + * @event: The event to create (NULL to use @addr for the event) + * @addr: The function and offset (or address) to insert the retprobe + * @format: The raw format string to define the retprobe. + * + * Create a kretprobe that will be in the @system group (or kprobes if + * @system is NULL). Have the name of @event (or @addr if @event is + * NULL). Will be inserted to @addr (function name, with or without + * offset, or a address). And the @format will define the raw format + * of the kprobe. See the Linux documentation file under: + * Documentation/trace/kprobetrace.rst + * + * Return 0 on success, or -1 on error. + * If the syntex of @format was incorrect, running + * tracefs_error_last(NULL) may show what went wrong. + * + * errno will be set to EBADMSG if addr or format is NULL. + */ +int tracefs_kretprobe_raw(const char *system, const char *event, + const char *addr, const char *format) +{ + return kprobe_raw(TRACEFS_DYNEVENT_KRETPROBE, system, event, addr, format); +} diff --git a/src/tracefs-marker.c b/src/tracefs-marker.c new file mode 100644 index 0000000..61a07ab --- /dev/null +++ b/src/tracefs-marker.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <pthread.h> + +#include "tracefs.h" +#include "tracefs-local.h" + +/* File descriptors for Top level trace markers */ +static int ftrace_marker_fd = -1; +static int ftrace_marker_raw_fd = -1; + +static inline int *get_marker_fd(struct tracefs_instance *instance, bool raw) +{ + if (raw) + return instance ? &instance->ftrace_marker_raw_fd : &ftrace_marker_raw_fd; + return instance ? &instance->ftrace_marker_fd : &ftrace_marker_fd; +} + +static int marker_init(struct tracefs_instance *instance, bool raw) +{ + const char *file = raw ? "trace_marker_raw" : "trace_marker"; + pthread_mutex_t *lock = trace_get_lock(instance); + int *fd = get_marker_fd(instance, raw); + int ret; + + if (*fd >= 0) + return 0; + + /* + * The mutex is only to hold the integrity of the file descriptor + * to prevent opening it more than once, or closing the same + * file descriptor more than once. It does not protect against + * one thread closing the file descriptor and another thread + * writing to it. That is up to the application to prevent + * from happening. + */ + pthread_mutex_lock(lock); + /* The file could have been opened since we taken the lock */ + if (*fd < 0) + *fd = tracefs_instance_file_open(instance, file, O_WRONLY | O_CLOEXEC); + + ret = *fd < 0 ? -1 : 0; + pthread_mutex_unlock(lock); + + return ret; +} + +static void marker_close(struct tracefs_instance *instance, bool raw) +{ + pthread_mutex_t *lock = trace_get_lock(instance); + int *fd = get_marker_fd(instance, raw); + + pthread_mutex_lock(lock); + if (*fd >= 0) { + close(*fd); + *fd = -1; + } + pthread_mutex_unlock(lock); +} + +static int marker_write(struct tracefs_instance *instance, bool raw, void *data, int len) +{ + int *fd = get_marker_fd(instance, raw); + int ret; + + /* + * The lock does not need to be taken for writes. As a write + * does not modify the file descriptor. It's up to the application + * to prevent it from being closed if another thread is doing a write. + */ + if (!data || len < 1) + return -1; + if (*fd < 0) { + ret = marker_init(instance, raw); + if (ret < 0) + return ret; + } + + ret = write(*fd, data, len); + + return ret == len ? 0 : -1; +} + +/** + * tracefs_print_init - Open trace marker of selected instance for writing + * @instance: ftrace instance, can be NULL for top tracing instance. + * + * Returns 0 if the trace marker is opened successfully, or -1 in case of an error + */ +int tracefs_print_init(struct tracefs_instance *instance) +{ + return marker_init(instance, false); +} + +/** + * tracefs_vprintf - Write a formatted string in the trace marker + * @instance: ftrace instance, can be NULL for top tracing instance. + * @fmt: pritnf formatted string + * @ap: list of arguments for the formatted string + * + * If the trace marker of the desired instance is not open already, + * this API will open it for writing. It will stay open until + * tracefs_print_close() is called. + * + * Returns 0 if the string is written correctly, or -1 in case of an error + */ +int tracefs_vprintf(struct tracefs_instance *instance, const char *fmt, va_list ap) +{ + char *str = NULL; + int ret; + + ret = vasprintf(&str, fmt, ap); + if (ret < 0) + return ret; + ret = marker_write(instance, false, str, strlen(str)); + free(str); + + return ret; +} + +/** + * tracefs_printf - Write a formatted string in the trace marker + * @instance: ftrace instance, can be NULL for top tracing instance. + * @fmt: pritnf formatted string with variable arguments ... + * + * If the trace marker of the desired instance is not open already, + * this API will open it for writing. It will stay open until + * tracefs_print_close() is called. + * + * Returns 0 if the string is written correctly, or -1 in case of an error + */ +int tracefs_printf(struct tracefs_instance *instance, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = tracefs_vprintf(instance, fmt, ap); + va_end(ap); + + return ret; +} + +/** + * tracefs_print_close - Close trace marker of selected instance + * @instance: ftrace instance, can be NULL for top tracing instance. + * + * Closes the trace marker, previously opened with any of the other tracefs_print APIs + */ +void tracefs_print_close(struct tracefs_instance *instance) +{ + marker_close(instance, false); +} + +/** + * tracefs_binary_init - Open raw trace marker of selected instance for writing + * @instance: ftrace instance, can be NULL for top tracing instance. + * + * Returns 0 if the raw trace marker is opened successfully, or -1 in case of an error + */ +int tracefs_binary_init(struct tracefs_instance *instance) +{ + return marker_init(instance, true); +} + +/** + * tracefs_binary_write - Write binary data in the raw trace marker + * @instance: ftrace instance, can be NULL for top tracing instance. + * @data: binary data, that is going to be written in the trace marker + * @len: length of the @data + * + * If the raw trace marker of the desired instance is not open already, + * this API will open it for writing. It will stay open until + * tracefs_binary_close() is called. + * + * Returns 0 if the data is written correctly, or -1 in case of an error + */ +int tracefs_binary_write(struct tracefs_instance *instance, void *data, int len) +{ + return marker_write(instance, true, data, len); +} + +/** + * tracefs_binary_close - Close raw trace marker of selected instance + * @instance: ftrace instance, can be NULL for top tracing instance. + * + * Closes the raw trace marker, previously opened with any of the other tracefs_binary APIs + */ +void tracefs_binary_close(struct tracefs_instance *instance) +{ + marker_close(instance, true); +} diff --git a/src/tracefs-record.c b/src/tracefs-record.c new file mode 100644 index 0000000..b078c86 --- /dev/null +++ b/src/tracefs-record.c @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2022 Google Inc, Steven Rostedt <rostedt@goodmis.org> + */ +#include <stdio.h> +#include <stdlib.h> +#include <dirent.h> +#include <unistd.h> +#include <fcntl.h> +#include <limits.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/select.h> + +#include <kbuffer.h> + +#include "tracefs.h" +#include "tracefs-local.h" + +enum { + TC_STOP = 1 << 0, /* Stop reading */ + TC_PERM_NONBLOCK = 1 << 1, /* read is always non blocking */ + TC_NONBLOCK = 1 << 2, /* read is non blocking */ +}; + +struct tracefs_cpu { + int fd; + int flags; + int nfds; + int ctrl_pipe[2]; + int splice_pipe[2]; + int pipe_size; + int subbuf_size; + int buffered; + int splice_read_flags; +}; + +/** + * tracefs_cpu_alloc_fd - create a tracefs_cpu instance for an existing fd + * @fd: The file descriptor to attach the tracefs_cpu to + * @subbuf_size: The expected size to read the subbuffer with + * @nonblock: If true, the file will be opened in O_NONBLOCK mode + * + * Return a descriptor that can read the tracefs trace_pipe_raw file + * that is associated with the given @fd and must be read in @subbuf_size. + * + * Returns NULL on error. + */ +struct tracefs_cpu * +tracefs_cpu_alloc_fd(int fd, int subbuf_size, bool nonblock) +{ + struct tracefs_cpu *tcpu; + int mode = O_RDONLY; + int ret; + + tcpu = calloc(1, sizeof(*tcpu)); + if (!tcpu) + return NULL; + + if (nonblock) { + mode |= O_NONBLOCK; + tcpu->flags |= TC_NONBLOCK | TC_PERM_NONBLOCK; + } + + tcpu->splice_pipe[0] = -1; + tcpu->splice_pipe[1] = -1; + + tcpu->fd = fd; + + tcpu->subbuf_size = subbuf_size; + + if (tcpu->flags & TC_PERM_NONBLOCK) { + tcpu->ctrl_pipe[0] = -1; + tcpu->ctrl_pipe[1] = -1; + } else { + /* ctrl_pipe is used to break out of blocked reads */ + ret = pipe(tcpu->ctrl_pipe); + if (ret < 0) + goto fail; + if (tcpu->ctrl_pipe[0] > tcpu->fd) + tcpu->nfds = tcpu->ctrl_pipe[0] + 1; + else + tcpu->nfds = tcpu->fd + 1; + } + + return tcpu; + fail: + free(tcpu); + return NULL; +} + +/** + * tracefs_cpu_open - open an instance raw trace file + * @instance: the instance (NULL for toplevel) of the cpu raw file to open + * @cpu: The CPU that the raw trace file is associated with + * @nonblock: If true, the file will be opened in O_NONBLOCK mode + * + * Return a descriptor that can read the tracefs trace_pipe_raw file + * for a give @cpu in a given @instance. + * + * Returns NULL on error. + */ +struct tracefs_cpu * +tracefs_cpu_open(struct tracefs_instance *instance, int cpu, bool nonblock) +{ + struct tracefs_cpu *tcpu; + struct tep_handle *tep; + char path[128]; + char *buf; + int mode = O_RDONLY; + int subbuf_size; + int len; + int ret; + int fd; + + if (nonblock) + mode |= O_NONBLOCK; + + sprintf(path, "per_cpu/cpu%d/trace_pipe_raw", cpu); + + fd = tracefs_instance_file_open(instance, path, mode); + if (fd < 0) + return NULL; + + tep = tep_alloc(); + if (!tep) + goto fail; + + /* Get the size of the page */ + buf = tracefs_instance_file_read(NULL, "events/header_page", &len); + if (!buf) + goto fail; + + ret = tep_parse_header_page(tep, buf, len, sizeof(long)); + free(buf); + if (ret < 0) + goto fail; + + subbuf_size = tep_get_sub_buffer_size(tep); + tep_free(tep); + tep = NULL; + + tcpu = tracefs_cpu_alloc_fd(fd, subbuf_size, nonblock); + if (!tcpu) + goto fail; + + return tcpu; + fail: + tep_free(tep); + close(fd); + return NULL; +} + +static void close_fd(int fd) +{ + if (fd < 0) + return; + close(fd); +} + +/** + * tracefs_cpu_free_fd - clean up the tracefs_cpu descriptor + * @tcpu: The descriptor created with tracefs_cpu_alloc_fd() + * + * Closes all the internal file descriptors that were opened by + * tracefs_cpu_alloc_fd(), and frees the descriptor. + */ +void tracefs_cpu_free_fd(struct tracefs_cpu *tcpu) +{ + close_fd(tcpu->ctrl_pipe[0]); + close_fd(tcpu->ctrl_pipe[1]); + close_fd(tcpu->splice_pipe[0]); + close_fd(tcpu->splice_pipe[1]); + + free(tcpu); +} + +/** + * tracefs_cpu_close - clean up and close a raw trace descriptor + * @tcpu: The descriptor created with tracefs_cpu_open() + * + * Closes all the file descriptors associated to the trace_pipe_raw + * opened by tracefs_cpu_open(). + */ +void tracefs_cpu_close(struct tracefs_cpu *tcpu) +{ + if (!tcpu) + return; + + close(tcpu->fd); + tracefs_cpu_free_fd(tcpu); +} + +/** + * tracefs_cpu_read_size - Return the size of the sub buffer + * @tcpu: The descriptor that holds the size of the sub buffer + * + * A lot of the functions that read the data from the trace_pipe_raw + * expect the caller to have allocated enough space to store a full + * subbuffer. Calling this function is a requirement to do so. + */ +int tracefs_cpu_read_size(struct tracefs_cpu *tcpu) +{ + if (!tcpu) + return -1; + return tcpu->subbuf_size; +} + +static void set_nonblock(struct tracefs_cpu *tcpu) +{ + long flags; + + if (tcpu->flags & TC_NONBLOCK) + return; + + flags = fcntl(tcpu->fd, F_GETFL); + fcntl(tcpu->fd, F_SETFL, flags | O_NONBLOCK); + tcpu->flags |= TC_NONBLOCK; +} + +static void unset_nonblock(struct tracefs_cpu *tcpu) +{ + long flags; + + if (!(tcpu->flags & TC_NONBLOCK)) + return; + + flags = fcntl(tcpu->fd, F_GETFL); + flags &= ~O_NONBLOCK; + fcntl(tcpu->fd, F_SETFL, flags); + tcpu->flags &= ~TC_NONBLOCK; +} + +/* + * If set to blocking mode, block until the watermark has been + * reached, or the control has said to stop. If the contol is + * set, then nonblock will be set to true on the way out. + */ +static int wait_on_input(struct tracefs_cpu *tcpu, bool nonblock) +{ + fd_set rfds; + int ret; + + if (tcpu->flags & TC_PERM_NONBLOCK) + return 1; + + if (nonblock) { + set_nonblock(tcpu); + return 1; + } else { + unset_nonblock(tcpu); + } + + FD_ZERO(&rfds); + FD_SET(tcpu->fd, &rfds); + FD_SET(tcpu->ctrl_pipe[0], &rfds); + + ret = select(tcpu->nfds, &rfds, NULL, NULL, NULL); + + /* Let the application decide what to do with signals and such */ + if (ret < 0) + return ret; + + if (FD_ISSET(tcpu->ctrl_pipe[0], &rfds)) { + /* Flush the ctrl pipe */ + read(tcpu->ctrl_pipe[0], &ret, 1); + + /* Make nonblock as it is now stopped */ + set_nonblock(tcpu); + /* Permanently set unblock */ + tcpu->flags |= TC_PERM_NONBLOCK; + } + + return FD_ISSET(tcpu->fd, &rfds); +} + +/** + * tracefs_cpu_read - read from the raw trace file + * @tcpu: The descriptor representing the raw trace file + * @buffer: Where to read into (must be at least the size of the subbuffer) + * @nonblock: Hint to not block on the read if there's no data. + * + * Reads the trace_pipe_raw files associated to @tcpu into @buffer. + * @buffer must be at least the size of the sub buffer of the ring buffer, + * which is returned by tracefs_cpu_read_size(). + * + * If @nonblock is set, and there's no data available, it will return + * immediately. Otherwise depending on how @tcpu was opened, it will + * block. If @tcpu was opened with nonblock set, then this @nonblock + * will make no difference. + * + * Returns the amount read or -1 on error. + */ +int tracefs_cpu_read(struct tracefs_cpu *tcpu, void *buffer, bool nonblock) +{ + int ret; + + /* + * If nonblock is set, then the wait_on_input() will return + * immediately, if there's nothing in the buffer, with + * ret == 0. + */ + ret = wait_on_input(tcpu, nonblock); + if (ret <= 0) + return ret; + + ret = read(tcpu->fd, buffer, tcpu->subbuf_size); + + /* It's OK if there's no data to read */ + if (ret < 0 && errno == EAGAIN) { + /* Reset errno */ + errno = 0; + ret = 0; + } + + return ret; +} + +static int init_splice(struct tracefs_cpu *tcpu) +{ + int ret; + + if (tcpu->splice_pipe[0] >= 0) + return 0; + + ret = pipe(tcpu->splice_pipe); + if (ret < 0) + return ret; + + ret = fcntl(tcpu->splice_pipe[0], F_GETPIPE_SZ, &tcpu->pipe_size); + /* + * F_GETPIPE_SZ was introduced in 2.6.35, ftrace was introduced + * in 2.6.31. If we are running on an older kernel, just fall + * back to using subbuf_size for splice(). It could also return + * the size of the pipe and not set pipe_size. + */ + if (ret > 0 && !tcpu->pipe_size) + tcpu->pipe_size = ret; + else if (ret < 0) + tcpu->pipe_size = tcpu->subbuf_size; + + tcpu->splice_read_flags = SPLICE_F_MOVE; + if (tcpu->flags & TC_NONBLOCK) + tcpu->splice_read_flags |= SPLICE_F_NONBLOCK; + + return 0; +} + +/** + * tracefs_cpu_buffered_read - Read the raw trace data buffering through a pipe + * @tcpu: The descriptor representing the raw trace file + * @buffer: Where to read into (must be at least the size of the subbuffer) + * @nonblock: Hint to not block on the read if there's no data. + * + * This is basically the same as tracefs_cpu_read() except that it uses + * a pipe through splice to buffer reads. This will batch reads keeping + * the reading from the ring buffer less intrusive to the system, as + * just reading all the time can cause quite a disturbance. + * + * Note, one difference between this and tracefs_cpu_read() is that it + * will read only in sub buffer pages. If the ring buffer has not filled + * a page, then it will not return anything, even with @nonblock set. + * Calls to tracefs_cpu_flush() should be done to read the rest of + * the file at the end of the trace. + * + * Returns the amount read or -1 on error. + */ +int tracefs_cpu_buffered_read(struct tracefs_cpu *tcpu, void *buffer, bool nonblock) +{ + int mode = SPLICE_F_MOVE; + int ret; + + if (tcpu->buffered < 0) + tcpu->buffered = 0; + + if (tcpu->buffered) + goto do_read; + + ret = wait_on_input(tcpu, nonblock); + if (ret <= 0) + return ret; + + if (tcpu->flags & TC_NONBLOCK) + mode |= SPLICE_F_NONBLOCK; + + ret = init_splice(tcpu); + if (ret < 0) + return ret; + + ret = splice(tcpu->fd, NULL, tcpu->splice_pipe[1], NULL, + tcpu->pipe_size, mode); + if (ret <= 0) + return ret; + + tcpu->buffered = ret; + + do_read: + ret = read(tcpu->splice_pipe[0], buffer, tcpu->subbuf_size); + if (ret > 0) + tcpu->buffered -= ret; + return ret; +} + +/** + * tracefs_cpu_stop - Stop a blocked read of the raw tracing file + * @tcpu: The descriptor representing the raw trace file + * + * This will attempt to unblock a task blocked on @tcpu reading it. + * On older kernels, it may not do anything for the pipe reads, as + * older kernels do not wake up tasks waiting on the ring buffer. + * + * Returns 0 if the tasks reading the raw tracing file does not + * need a nudge. + * + * Returns 1 if that tasks may need a nudge (send a signal). + * + * Returns negative on error. + */ +int tracefs_cpu_stop(struct tracefs_cpu *tcpu) +{ + int ret = 1; + + if (tcpu->flags & TC_PERM_NONBLOCK) + return 0; + + ret = write(tcpu->ctrl_pipe[1], &ret, 1); + if (ret < 0) + return ret; + + /* Calling ioctl() on recent kernels will wake up the waiters */ + ret = ioctl(tcpu->fd, 0); + if (ret < 0) + ret = 1; + else + ret = 0; + + set_nonblock(tcpu); + + return ret; +} + +/** + * tracefs_cpu_flush - Finish out and read the rest of the raw tracing file + * @tcpu: The descriptor representing the raw trace file + * @buffer: Where to read into (must be at least the size of the subbuffer) + * + * Reads the trace_pipe_raw file associated by the @tcpu and puts it + * into @buffer, which must be the size of the sub buffer which is retrieved. + * by tracefs_cpu_read_size(). This should be called at the end of tracing + * to get the rest of the data. + * + * This will set the file descriptor for reading to non-blocking mode. + * + * Returns the number of bytes read, or negative on error. + */ +int tracefs_cpu_flush(struct tracefs_cpu *tcpu, void *buffer) +{ + int ret; + + /* Make sure that reading is now non blocking */ + set_nonblock(tcpu); + + if (tcpu->buffered < 0) + tcpu->buffered = 0; + + if (tcpu->buffered) { + ret = read(tcpu->splice_pipe[0], buffer, tcpu->subbuf_size); + if (ret > 0) + tcpu->buffered -= ret; + return ret; + } + + ret = read(tcpu->fd, buffer, tcpu->subbuf_size); + if (ret > 0 && tcpu->buffered) + tcpu->buffered -= ret; + + /* It's OK if there's no data to read */ + if (ret < 0 && errno == EAGAIN) { + /* Reset errno */ + errno = 0; + ret = 0; + } + + return ret; +} + +/** + * tracefs_cpu_flush_write - Finish out and read the rest of the raw tracing file + * @tcpu: The descriptor representing the raw trace file + * @wfd: The write file descriptor to write the data to + * + * Reads the trace_pipe_raw file associated by the @tcpu and writes it to + * @wfd. This should be called at the end of tracing to get the rest of the data. + * + * Returns the number of bytes written, or negative on error. + */ +int tracefs_cpu_flush_write(struct tracefs_cpu *tcpu, int wfd) +{ + char buffer[tcpu->subbuf_size]; + int ret; + + ret = tracefs_cpu_flush(tcpu, buffer); + if (ret > 0) + ret = write(wfd, buffer, ret); + + /* It's OK if there's no data to read */ + if (ret < 0 && errno == EAGAIN) + ret = 0; + + return ret; +} + +/** + * tracefs_cpu_write - Write the raw trace file into a file descriptor + * @tcpu: The descriptor representing the raw trace file + * @wfd: The write file descriptor to write the data to + * @nonblock: Hint to not block on the read if there's no data. + * + * This will pipe the data from the trace_pipe_raw file associated with @tcpu + * into the @wfd file descriptor. If @nonblock is set, then it will not + * block on if there's nothing to write. Note, it will only write sub buffer + * size data to @wfd. Calls to tracefs_cpu_flush_write() are needed to + * write out the rest. + * + * Returns the number of bytes read or negative on error. + */ +int tracefs_cpu_write(struct tracefs_cpu *tcpu, int wfd, bool nonblock) +{ + char buffer[tcpu->subbuf_size]; + int mode = SPLICE_F_MOVE; + int tot_write = 0; + int tot; + int ret; + + ret = wait_on_input(tcpu, nonblock); + if (ret <= 0) + return ret; + + if (tcpu->flags & TC_NONBLOCK) + mode |= SPLICE_F_NONBLOCK; + + ret = init_splice(tcpu); + if (ret < 0) + return ret; + + tot = splice(tcpu->fd, NULL, tcpu->splice_pipe[1], NULL, + tcpu->pipe_size, mode); + if (tot < 0) + return tot; + + if (tot == 0) + return 0; + + ret = splice(tcpu->splice_pipe[0], NULL, wfd, NULL, + tot, SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + + if (ret >= 0) + return ret; + + /* Some file systems do not allow splicing, try writing instead */ + do { + int r = tcpu->subbuf_size; + + if (r > tot) + r = tot; + + ret = read(tcpu->splice_pipe[0], buffer, r); + if (ret > 0) { + tot -= ret; + ret = write(wfd, buffer, ret); + } + if (ret > 0) + tot_write += ret; + } while (ret > 0); + + if (ret < 0) + return ret; + + return tot_write; +} + +/** + * tracefs_cpu_pipe - Write the raw trace file into a pipe descriptor + * @tcpu: The descriptor representing the raw trace file + * @wfd: The write file descriptor to write the data to (must be a pipe) + * @nonblock: Hint to not block on the read if there's no data. + * + * This will splice directly the file descriptor of the trace_pipe_raw + * file to the given @wfd, which must be a pipe. This can also be used + * if @tcpu was created with tracefs_cpu_create_fd() where the passed + * in @fd there was a pipe, then @wfd does not need to be a pipe. + * + * Returns the number of bytes read or negative on error. + */ +int tracefs_cpu_pipe(struct tracefs_cpu *tcpu, int wfd, bool nonblock) +{ + int mode = SPLICE_F_MOVE; + int ret; + + ret = wait_on_input(tcpu, nonblock); + if (ret <= 0) + return ret; + + if (tcpu->flags & TC_NONBLOCK) + mode |= SPLICE_F_NONBLOCK; + + ret = splice(tcpu->fd, NULL, wfd, NULL, + tcpu->pipe_size, mode); + return ret; +} diff --git a/src/tracefs-sqlhist.c b/src/tracefs-sqlhist.c new file mode 100644 index 0000000..3f571b7 --- /dev/null +++ b/src/tracefs-sqlhist.c @@ -0,0 +1,1653 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org> + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#include <trace-seq.h> +#include <stdlib.h> +#include <stdarg.h> +#include <ctype.h> +#include <errno.h> + +#include "tracefs.h" +#include "tracefs-local.h" +#include "sqlhist-parse.h" + +extern int yylex_init(void* ptr_yy_globals); +extern int yylex_init_extra(struct sqlhist_bison *sb, void* ptr_yy_globals); +extern int yylex_destroy (void * yyscanner ); + +struct str_hash { + struct str_hash *next; + char *str; +}; + +enum alias_type { + ALIAS_EVENT, + ALIAS_FIELD, +}; + +enum field_type { + FIELD_NONE, + FIELD_FROM, + FIELD_TO, +}; + +#define for_each_field(expr, field, table) \ + for (expr = (table)->fields; expr; expr = (field)->next) + +struct field { + struct expr *next; /* private link list */ + const char *system; + const char *event_name; + struct tep_event *event; + const char *raw; + const char *label; + const char *field; + const char *type; + enum field_type ftype; +}; + +struct filter { + enum filter_type type; + struct expr *lval; + struct expr *rval; +}; + +struct match { + struct match *next; + struct expr *lval; + struct expr *rval; +}; + +struct compare { + enum compare_type type; + struct expr *lval; + struct expr *rval; + const char *name; +}; + +enum expr_type +{ + EXPR_NUMBER, + EXPR_STRING, + EXPR_FIELD, + EXPR_FILTER, + EXPR_COMPARE, +}; + +struct expr { + struct expr *free_list; + struct expr *next; + enum expr_type type; + int line; + int idx; + union { + struct field field; + struct filter filter; + struct compare compare; + const char *string; + long number; + }; +}; + +struct sql_table { + struct sqlhist_bison *sb; + const char *name; + struct expr *exprs; + struct expr *fields; + struct expr *from; + struct expr *to; + struct expr *where; + struct expr **next_where; + struct match *matches; + struct match **next_match; + struct expr *selections; + struct expr **next_selection; +}; + +__hidden int my_yyinput(void *extra, char *buf, int max) +{ + struct sqlhist_bison *sb = extra; + + if (!sb || !sb->buffer) + return -1; + + if (sb->buffer_idx + max > sb->buffer_size) + max = sb->buffer_size - sb->buffer_idx; + + if (max) + memcpy(buf, sb->buffer + sb->buffer_idx, max); + + sb->buffer_idx += max; + + return max; +} + +__hidden void sql_parse_error(struct sqlhist_bison *sb, const char *text, + const char *fmt, va_list ap) +{ + const char *buffer = sb->buffer; + struct trace_seq s; + int line = sb->line_no; + int idx = sb->line_idx - strlen(text); + int i; + + if (!buffer) + return; + + trace_seq_init(&s); + if (!s.buffer) { + tracefs_warning("Error allocating internal buffer\n"); + return; + } + + for (i = 0; line && buffer[i]; i++) { + if (buffer[i] == '\n') + line--; + } + for (; buffer[i] && buffer[i] != '\n'; i++) + trace_seq_putc(&s, buffer[i]); + trace_seq_putc(&s, '\n'); + for (i = idx; i > 0; i--) + trace_seq_putc(&s, ' '); + trace_seq_puts(&s, "^\n"); + trace_seq_printf(&s, "ERROR: '%s'\n", text); + trace_seq_vprintf(&s, fmt, ap); + + trace_seq_terminate(&s); + + sb->parse_error_str = strdup(s.buffer); + trace_seq_destroy(&s); +} + +static void parse_error(struct sqlhist_bison *sb, const char *text, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sql_parse_error(sb, text, fmt, ap); + va_end(ap); +} + +__hidden unsigned int quick_hash(const char *str) +{ + unsigned int val = 0; + int len = strlen(str); + + for (; len >= 4; str += 4, len -= 4) { + val += str[0]; + val += str[1] << 8; + val += str[2] << 16; + val += str[3] << 24; + } + for (; len > 0; str++, len--) + val += str[0] << (len * 8); + + val *= 2654435761; + + return val & ((1 << HASH_BITS) - 1); +} + + +static struct str_hash *find_string(struct sqlhist_bison *sb, const char *str) +{ + unsigned int key = quick_hash(str); + struct str_hash *hash = sb->str_hash[key]; + + for (; hash; hash = hash->next) { + if (!strcmp(hash->str, str)) + return hash; + } + return NULL; +} + +/* + * If @str is found, then return the hash string. + * This lets store_str() know to free str. + */ +static char **add_hash(struct sqlhist_bison *sb, const char *str) +{ + struct str_hash *hash; + unsigned int key; + + if ((hash = find_string(sb, str))) { + return &hash->str; + } + + hash = malloc(sizeof(*hash)); + if (!hash) + return NULL; + key = quick_hash(str); + hash->next = sb->str_hash[key]; + sb->str_hash[key] = hash; + hash->str = NULL; + return &hash->str; +} + +__hidden char *store_str(struct sqlhist_bison *sb, const char *str) +{ + char **pstr = add_hash(sb, str); + + if (!pstr) + return NULL; + + if (!(*pstr)) + *pstr = strdup(str); + + return *pstr; +} + +__hidden void *add_cast(struct sqlhist_bison *sb, + void *data, const char *type) +{ + struct expr *expr = data; + struct field *field = &expr->field; + + field->type = type; + return expr; +} + +__hidden int add_selection(struct sqlhist_bison *sb, void *select, + const char *name) +{ + struct sql_table *table = sb->table; + struct expr *expr = select; + + switch (expr->type) { + case EXPR_FIELD: + expr->field.label = name; + break; + case EXPR_COMPARE: + expr->compare.name = name; + break; + case EXPR_NUMBER: + case EXPR_STRING: + case EXPR_FILTER: + default: + return -1; + } + + if (expr->next) + return -1; + + *table->next_selection = expr; + table->next_selection = &expr->next; + + return 0; +} + +static struct expr *find_field(struct sqlhist_bison *sb, + const char *raw, const char *label) +{ + struct field *field; + struct expr *expr; + + for_each_field(expr, field, sb->table) { + field = &expr->field; + + if (!strcmp(field->raw, raw)) { + if (label && !field->label) + field->label = label; + if (label && strcmp(label, field->label) != 0) + continue; + return expr; + } + + if (label && !strcmp(field->raw, label)) { + if (!field->label) { + field->label = label; + field->raw = raw; + } + return expr; + } + + if (!field->label) + continue; + + if (!strcmp(field->label, raw)) + return expr; + + if (label && !strcmp(field->label, label)) + return expr; + } + return NULL; +} + +static void *create_expr(struct sqlhist_bison *sb, + enum expr_type type, struct expr **expr_p) +{ + struct expr *expr; + + expr = calloc(1, sizeof(*expr)); + if (!expr) + return NULL; + + if (expr_p) + *expr_p = expr; + + expr->free_list = sb->table->exprs; + sb->table->exprs = expr; + + expr->type = type; + expr->line = sb->line_no; + expr->idx = sb->line_idx; + + switch (type) { + case EXPR_FIELD: return &expr->field; + case EXPR_COMPARE: return &expr->compare; + case EXPR_NUMBER: return &expr->number; + case EXPR_STRING: return &expr->string; + case EXPR_FILTER: return &expr->filter; + } + + return NULL; +} + +#define __create_expr(var, type, ENUM, expr) \ + do { \ + var = (type *)create_expr(sb, EXPR_##ENUM, expr); \ + } while(0) + +#define create_field(var, expr) \ + __create_expr(var, struct field, FIELD, expr) + +#define create_filter(var, expr) \ + __create_expr(var, struct filter, FILTER, expr) + +#define create_compare(var, expr) \ + __create_expr(var, struct compare, COMPARE, expr) + +#define create_string(var, expr) \ + __create_expr(var, const char *, STRING, expr) + +#define create_number(var, expr) \ + __create_expr(var, long, NUMBER, expr) + +__hidden void *add_field(struct sqlhist_bison *sb, + const char *field_name, const char *label) +{ + struct sql_table *table = sb->table; + struct expr *expr; + struct field *field; + + expr = find_field(sb, field_name, label); + if (expr) + return expr; + + create_field(field, &expr); + + field->next = table->fields; + table->fields = expr; + + field->raw = field_name; + field->label = label; + + return expr; +} + +__hidden void *add_filter(struct sqlhist_bison *sb, + void *A, void *B, enum filter_type op) +{ + struct filter *filter; + struct expr *expr; + + create_filter(filter, &expr); + + filter->lval = A; + filter->rval = B; + + filter->type = op; + + return expr; +} + +__hidden int add_match(struct sqlhist_bison *sb, void *A, void *B) +{ + struct sql_table *table = sb->table; + struct match *match; + + match = calloc(1, sizeof(*match)); + if (!match) + return -1; + + match->lval = A; + match->rval = B; + + *table->next_match = match; + table->next_match = &match->next; + + return 0; +} +__hidden void *add_compare(struct sqlhist_bison *sb, + void *A, void *B, enum compare_type type) +{ + struct compare *compare; + struct expr *expr; + + create_compare(compare, &expr); + + compare = &expr->compare; + compare->lval = A; + compare->rval = B; + compare->type = type; + + return expr; +} + +__hidden int add_where(struct sqlhist_bison *sb, void *item) +{ + struct expr *expr = item; + struct sql_table *table = sb->table; + + if (expr->type != EXPR_FILTER) + return -1; + + *table->next_where = expr; + table->next_where = &expr->next; + + if (expr->next) + return -1; + + return 0; +} + +__hidden int add_from(struct sqlhist_bison *sb, void *item) +{ + struct expr *expr = item; + + if (expr->type != EXPR_FIELD) + return -1; + + sb->table->from = expr; + + return 0; +} + +__hidden int add_to(struct sqlhist_bison *sb, void *item) +{ + struct expr *expr = item; + + if (expr->type != EXPR_FIELD) + return -1; + + sb->table->to = expr; + + return 0; +} + +__hidden void *add_string(struct sqlhist_bison *sb, const char *str) +{ + struct expr *expr; + const char **str_p; + + create_string(str_p, &expr); + *str_p = str; + return expr; +} + +__hidden void *add_number(struct sqlhist_bison *sb, long val) +{ + struct expr *expr; + long *num; + + create_number(num, &expr); + *num = val; + return expr; +} + +__hidden int table_start(struct sqlhist_bison *sb) +{ + struct sql_table *table; + + table = calloc(1, sizeof(*table)); + if (!table) + return -ENOMEM; + + table->sb = sb; + sb->table = table; + + table->next_where = &table->where; + table->next_match = &table->matches; + table->next_selection = &table->selections; + + return 0; +} + +static int test_event_exists(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr, struct tep_event **pevent) +{ + struct field *field = &expr->field; + const char *system = field->system; + const char *event = field->event_name; + + if (!field->event) + field->event = tep_find_event_by_name(tep, system, event); + if (pevent) + *pevent = field->event; + + if (field->event) + return 0; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, "event not found\n"); + return -1; +} + +static int test_field_exists(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr) +{ + struct field *field = &expr->field; + struct tep_format_field *tfield; + char *field_name; + const char *p; + + if (!field->event) { + if (test_event_exists(tep, sb, expr, NULL)) + return -1; + } + + /* The field could have a conversion */ + p = strchr(field->field, '.'); + if (p) + field_name = strndup(field->field, p - field->field); + else + field_name = strdup(field->field); + + if (!field_name) + return -1; + + if (!strcmp(field_name, TRACEFS_TIMESTAMP) || + !strcmp(field->field, TRACEFS_TIMESTAMP_USECS)) + tfield = (void *)1L; + else + tfield = tep_find_any_field(field->event, field_name); + free(field_name); + + if (!tfield && (!strcmp(field->field, "COMM") || !strcmp(field->field, "comm"))) + tfield = (void *)1L; + + if (tfield) + return 0; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "Field '%s' not part of event %s\n", + field->field, field->event_name); + return -1; +} + +static int update_vars(struct tep_handle *tep, + struct sql_table *table, + struct expr *expr) +{ + struct sqlhist_bison *sb = table->sb; + struct field *event_field = &expr->field; + enum field_type ftype = FIELD_NONE; + struct tep_event *event; + struct field *field; + const char *label; + const char *raw = event_field->raw; + const char *event_name; + const char *system; + const char *p; + int label_len = 0, event_len, system_len; + + if (expr == table->to) + ftype = FIELD_TO; + else if (expr == table->from) + ftype = FIELD_FROM; + + p = strchr(raw, '.'); + if (p) { + char *str; + + str = strndup(raw, p - raw); + if (!str) + return -1; + event_field->system = store_str(sb, str); + free(str); + if (!event_field->system) + return -1; + p++; + } else { + p = raw; + } + + event_field->event_name = store_str(sb, p); + if (!event_field->event_name) + return -1; + + if (test_event_exists(tep, sb, expr, &event)) + return -1; + + if (!event_field->system) + event_field->system = store_str(sb, event->system); + + if (!event_field->system) + return -1; + + label = event_field->label; + if (label) + label_len = strlen(label); + + system = event_field->system; + system_len = strlen(system); + + event_name = event_field->event_name; + event_len = strlen(event_name); + + for_each_field(expr, field, table) { + int len; + + field = &expr->field; + + if (field->event) + continue; + + raw = field->raw; + + /* + * The field could be: + * system.event.field... + * event.field... + * label.field... + * We check label first. + */ + + len = label_len; + if (label && !strncmp(raw, label, len) && + raw[len] == '.') { + /* Label matches and takes precedence */ + goto found; + } + + if (!strncmp(raw, system, system_len) && + raw[system_len] == '.') { + raw += system_len + 1; + /* Check the event portion next */ + } + + len = event_len; + if (strncmp(raw, event_name, len) || + raw[len] != '.') { + /* Does not match */ + continue; + } + found: + field->system = system; + field->event_name = event_name; + field->event = event; + field->field = raw + len + 1; + field->ftype = ftype; + + if (!strcmp(field->field, "TIMESTAMP")) + field->field = store_str(sb, TRACEFS_TIMESTAMP); + if (!strcmp(field->field, "TIMESTAMP_USECS")) + field->field = store_str(sb, TRACEFS_TIMESTAMP_USECS); + if (test_field_exists(tep, sb, expr)) + return -1; + } + + return 0; +} + +/* + * Called when there's a FROM but no JOIN(to), which means that the + * selections can be fields and not mention the event itself. + */ +static int update_fields(struct tep_handle *tep, + struct sql_table *table, + struct expr *expr) +{ + struct field *event_field = &expr->field; + struct sqlhist_bison *sb = table->sb; + struct tep_format_field *tfield; + struct tep_event *event; + struct field *field; + const char *p; + int len; + + /* First update fields with aliases an such and add event */ + update_vars(tep, table, expr); + + /* + * If event is not found, the creation of the synth will + * add a proper error, so return "success". + */ + if (!event_field->event) + return 0; + + event = event_field->event; + + for_each_field(expr, field, table) { + const char *field_name; + + field = &expr->field; + + if (field->event) + continue; + + field_name = field->raw; + + p = strchr(field_name, '.'); + if (p) { + len = p - field_name; + p = strndup(field_name, len); + if (!p) + return -1; + field_name = store_str(sb, p); + if (!field_name) + return -1; + free((char *)p); + } + + tfield = tep_find_any_field(event, field_name); + /* Let it error properly later */ + if (!tfield) + continue; + + field->system = event_field->system; + field->event_name = event_field->event_name; + field->event = event; + field->field = field_name; + } + + return 0; +} + +static int match_error(struct sqlhist_bison *sb, struct match *match, + struct field *lmatch, struct field *rmatch) +{ + struct field *lval = &match->lval->field; + struct field *rval = &match->rval->field; + struct field *field; + struct expr *expr; + + if (lval->system != lmatch->system || + lval->event != lmatch->event) { + expr = match->lval; + field = lval; + } else { + expr = match->rval; + field = rval; + } + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "'%s' and '%s' must be a field for each event: '%s' and '%s'\n", + lval->raw, rval->raw, sb->table->to->field.raw, + sb->table->from->field.raw); + + return -1; +} + +static int test_match(struct sql_table *table, struct match *match) +{ + struct field *lval, *rval; + struct field *to, *from; + + if (!match->lval || !match->rval) + return -1; + + if (match->lval->type != EXPR_FIELD || match->rval->type != EXPR_FIELD) + return -1; + + to = &table->to->field; + from = &table->from->field; + + lval = &match->lval->field; + rval = &match->rval->field; + + /* + * Note, strings are stored in the string store, so all + * duplicate strings are the same value, and we can use + * normal "==" and "!=" instead of strcmp(). + * + * Either lval == to and rval == from + * or lval == from and rval == to. + */ + if ((lval->system != to->system) || + (lval->event != to->event)) { + if ((rval->system != to->system) || + (rval->event != to->event) || + (lval->system != from->system) || + (lval->event != from->event)) + return match_error(table->sb, match, from, to); + } else { + if ((rval->system != from->system) || + (rval->event != from->event) || + (lval->system != to->system) || + (lval->event != to->event)) + return match_error(table->sb, match, to, from); + } + return 0; +} + +static void assign_match(const char *system, const char *event, + struct match *match, + const char **start_match, const char **end_match) +{ + struct field *lval, *rval; + + lval = &match->lval->field; + rval = &match->rval->field; + + if (lval->system == system && + lval->event_name == event) { + *start_match = lval->field; + *end_match = rval->field; + } else { + *start_match = rval->field; + *end_match = lval->field; + } +} + +static int build_compare(struct tracefs_synth *synth, + const char *system, const char *event, + struct compare *compare) +{ + const char *start_field; + const char *end_field; + struct field *lval, *rval; + enum tracefs_synth_calc calc; + int ret; + + if (!compare->name) + return -1; + + lval = &compare->lval->field; + rval = &compare->rval->field; + + if (lval->system == system && + lval->event_name == event) { + start_field = lval->field; + end_field = rval->field; + calc = TRACEFS_SYNTH_DELTA_START; + } else { + start_field = rval->field; + end_field = lval->field; + calc = TRACEFS_SYNTH_DELTA_END; + } + + if (compare->type == COMPARE_ADD) + calc = TRACEFS_SYNTH_ADD; + + ret = tracefs_synth_add_compare_field(synth, start_field, + end_field, calc, + compare->name); + return ret; +} + +static int verify_filter_error(struct sqlhist_bison *sb, struct expr *expr, + const char *event) +{ + struct field *field = &expr->field; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "event '%s' can not be grouped or '||' together with '%s'\n" + "All filters between '&&' must be for the same event\n", + field->event, event); + return -1; +} + +static int do_verify_filter(struct sqlhist_bison *sb, struct filter *filter, + const char **system, const char **event, + enum field_type *ftype) +{ + int ret; + + if (filter->type == FILTER_OR || + filter->type == FILTER_AND) { + ret = do_verify_filter(sb, &filter->lval->filter, system, event, ftype); + if (ret) + return ret; + return do_verify_filter(sb, &filter->rval->filter, system, event, ftype); + } + if (filter->type == FILTER_GROUP || + filter->type == FILTER_NOT_GROUP) { + return do_verify_filter(sb, &filter->lval->filter, system, event, ftype); + } + + /* + * system and event will be NULL until we find the left most + * node. Then assign it, and compare on the way back up. + */ + if (!*system && !*event) { + *system = filter->lval->field.system; + *event = filter->lval->field.event_name; + *ftype = filter->lval->field.ftype; + return 0; + } + + if (filter->lval->field.system != *system || + filter->lval->field.event_name != *event) + return verify_filter_error(sb, filter->lval, *event); + + return 0; +} + +static int verify_filter(struct sqlhist_bison *sb, struct filter *filter, + const char **system, const char **event, + enum field_type *ftype) +{ + int ret; + + switch (filter->type) { + case FILTER_OR: + case FILTER_AND: + case FILTER_GROUP: + case FILTER_NOT_GROUP: + break; + default: + return do_verify_filter(sb, filter, system, event, ftype); + } + + ret = do_verify_filter(sb, &filter->lval->filter, system, event, ftype); + if (ret) + return ret; + + switch (filter->type) { + case FILTER_OR: + case FILTER_AND: + return do_verify_filter(sb, &filter->rval->filter, system, event, ftype); + default: + return 0; + } +} + +static int test_field_exists(struct tep_handle *tep, struct sqlhist_bison *sb, + struct expr *expr); + +static void filter_compare_error(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr) +{ + struct field *field = &expr->field; + + switch (errno) { + case ENODEV: + case EBADE: + break; + case EINVAL: + parse_error(sb, field->raw, "Invalid compare\n"); + break; + default: + parse_error(sb, field->raw, "System error?\n"); + return; + } + + /* ENODEV means that an event or field does not exist */ + if (errno == ENODEV) { + if (test_field_exists(tep, sb, expr)) + return; + if (test_field_exists(tep, sb, expr)) + return; + return; + } + + /* fields exist, but values are not compatible */ + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, field->raw, + "Field '%s' is not compatible to be compared with the given value\n", + field->field); +} + +static void filter_error(struct tep_handle *tep, + struct sqlhist_bison *sb, struct expr *expr) +{ + struct filter *filter = &expr->filter; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + switch (filter->type) { + case FILTER_NOT_GROUP: + case FILTER_GROUP: + case FILTER_OR: + case FILTER_AND: + break; + default: + filter_compare_error(tep, sb, filter->lval); + return; + } + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + parse_error(sb, "", "Problem with filter entry?\n"); +} + +static int build_filter(struct tep_handle *tep, struct sqlhist_bison *sb, + struct tracefs_synth *synth, + bool start, struct expr *expr, bool *started) +{ + int (*append_filter)(struct tracefs_synth *synth, + enum tracefs_filter type, + const char *field, + enum tracefs_compare compare, + const char *val); + struct filter *filter = &expr->filter; + enum tracefs_compare cmp; + const char *val; + int and_or = TRACEFS_FILTER_AND; + char num[64]; + int ret; + + if (start) + append_filter = tracefs_synth_append_start_filter; + else + append_filter = tracefs_synth_append_end_filter; + + if (started && *started) { + ret = append_filter(synth, and_or, NULL, 0, NULL); + ret = append_filter(synth, TRACEFS_FILTER_OPEN_PAREN, + NULL, 0, NULL); + } + + switch (filter->type) { + case FILTER_NOT_GROUP: + ret = append_filter(synth, TRACEFS_FILTER_NOT, + NULL, 0, NULL); + if (ret < 0) + goto out; + /* Fall through */ + case FILTER_GROUP: + ret = append_filter(synth, TRACEFS_FILTER_OPEN_PAREN, + NULL, 0, NULL); + if (ret < 0) + goto out; + ret = build_filter(tep, sb, synth, start, filter->lval, NULL); + if (ret < 0) + goto out; + ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN, + NULL, 0, NULL); + goto out; + + case FILTER_OR: + and_or = TRACEFS_FILTER_OR; + /* Fall through */ + case FILTER_AND: + ret = build_filter(tep, sb, synth, start, filter->lval, NULL); + if (ret < 0) + goto out; + ret = append_filter(synth, and_or, NULL, 0, NULL); + + if (ret) + goto out; + ret = build_filter(tep, sb, synth, start, filter->rval, NULL); + goto out; + default: + break; + } + + switch (filter->rval->type) { + case EXPR_NUMBER: + sprintf(num, "%ld", filter->rval->number); + val = num; + break; + case EXPR_STRING: + val = filter->rval->string; + break; + default: + break; + } + + switch (filter->type) { + case FILTER_EQ: cmp = TRACEFS_COMPARE_EQ; break; + case FILTER_NE: cmp = TRACEFS_COMPARE_NE; break; + case FILTER_LE: cmp = TRACEFS_COMPARE_LE; break; + case FILTER_LT: cmp = TRACEFS_COMPARE_LT; break; + case FILTER_GE: cmp = TRACEFS_COMPARE_GE; break; + case FILTER_GT: cmp = TRACEFS_COMPARE_GT; break; + case FILTER_BIN_AND: cmp = TRACEFS_COMPARE_AND; break; + case FILTER_STR_CMP: cmp = TRACEFS_COMPARE_RE; break; + default: + tracefs_warning("Error invalid filter type '%d'", filter->type); + return ERANGE; + } + + ret = append_filter(synth, TRACEFS_FILTER_COMPARE, + filter->lval->field.field, cmp, val); + + if (ret) + filter_error(tep, sb, expr); + out: + if (!ret && started) { + if (*started) + ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN, + NULL, 0, NULL); + *started = true; + } + return ret; +} + +static void *field_match_error(struct tep_handle *tep, struct sqlhist_bison *sb, + struct match *match) +{ + switch (errno) { + case ENODEV: + case EBADE: + break; + default: + /* System error */ + return NULL; + } + + /* ENODEV means that an event or field does not exist */ + if (errno == ENODEV) { + if (test_field_exists(tep, sb, match->lval)) + return NULL; + if (test_field_exists(tep, sb, match->rval)) + return NULL; + return NULL; + } + + /* fields exist, but values are not compatible */ + sb->line_no = match->lval->line; + sb->line_idx = match->lval->idx; + + parse_error(sb, match->lval->field.raw, + "Field '%s' is not compatible to match field '%s'\n", + match->lval->field.raw, match->rval->field.raw); + return NULL; +} + +static void *synth_init_error(struct tep_handle *tep, struct sql_table *table) +{ + struct sqlhist_bison *sb = table->sb; + struct match *match = table->matches; + + switch (errno) { + case ENODEV: + case EBADE: + break; + default: + /* System error */ + return NULL; + } + + /* ENODEV could mean that start or end events do not exist */ + if (errno == ENODEV) { + if (test_event_exists(tep, sb, table->from, NULL)) + return NULL; + if (test_event_exists(tep, sb, table->to, NULL)) + return NULL; + } + + return field_match_error(tep, sb, match); +} + +static void selection_error(struct tep_handle *tep, + struct sqlhist_bison *sb, struct expr *expr) +{ + /* We just care about event not existing */ + if (errno != ENODEV) + return; + + test_field_exists(tep, sb, expr); +} + +static void compare_error(struct tep_handle *tep, + struct sqlhist_bison *sb, struct expr *expr) +{ + struct compare *compare = &expr->compare; + + if (!compare->name) { + sb->line_no = expr->line; + sb->line_idx = expr->idx + strlen("no name"); + + parse_error(sb, "no name", + "Field calculations must be labeled 'AS name'\n"); + } + + switch (errno) { + case ENODEV: + case EBADE: + break; + default: + /* System error */ + return; + } + + /* ENODEV means that an event or field does not exist */ + if (errno == ENODEV) { + if (test_field_exists(tep, sb, compare->lval)) + return; + if (test_field_exists(tep, sb, compare->rval)) + return; + return; + } + + /* fields exist, but values are not compatible */ + sb->line_no = compare->lval->line; + sb->line_idx = compare->lval->idx; + + parse_error(sb, compare->lval->field.raw, + "'%s' is not compatible to compare with '%s'\n", + compare->lval->field.raw, compare->rval->field.raw); +} + +static void compare_no_to_error(struct sqlhist_bison *sb, struct expr *expr) +{ + struct compare *compare = &expr->compare; + + sb->line_no = compare->lval->line; + sb->line_idx = compare->lval->idx; + + parse_error(sb, compare->lval->field.raw, + "Simple SQL (without JOIN/ON) do not allow comparisons\n", + compare->lval->field.raw, compare->rval->field.raw); +} + +static void where_no_to_error(struct sqlhist_bison *sb, struct expr *expr, + const char *from_event, const char *event) +{ + while (expr) { + switch (expr->filter.type) { + case FILTER_OR: + case FILTER_AND: + case FILTER_GROUP: + case FILTER_NOT_GROUP: + expr = expr->filter.lval; + continue; + default: + break; + } + break; + } + sb->line_no = expr->filter.lval->line; + sb->line_idx = expr->filter.lval->idx; + + parse_error(sb, expr->filter.lval->field.raw, + "Event '%s' does not match FROM event '%s'\n", + event, from_event); +} + +static int verify_field_type(struct tep_handle *tep, + struct sqlhist_bison *sb, + struct expr *expr, int *cnt) +{ + struct field *field = &expr->field; + struct tep_event *event; + struct tep_format_field *tfield; + char *type; + int ret; + int i; + + if (!field->type) + return 0; + + sb->line_no = expr->line; + sb->line_idx = expr->idx; + + event = tep_find_event_by_name(tep, field->system, field->event_name); + if (!event) { + parse_error(sb, field->raw, + "Event '%s' not found\n", + field->event_name ? : "(null)"); + return -1; + } + + tfield = tep_find_any_field(event, field->field); + if (!tfield) { + parse_error(sb, field->raw, + "Field '%s' not part of event '%s'\n", + field->field ? : "(null)", field->event); + return -1; + } + + type = strdup(field->type); + if (!type) + return -1; + + if (!strcmp(type, TRACEFS_HIST_COUNTER) || + !strcmp(type, "_COUNTER_")) { + ret = HIST_COUNTER_TYPE; + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) { + parse_error(sb, field->raw, + "'%s' is a string, and counters may only be used with numbers\n"); + ret = -1; + } + goto out; + } + + for (i = 0; type[i]; i++) + type[i] = tolower(type[i]); + + if (!strcmp(type, "hex")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_HEX; + } else if (!strcmp(type, "sym")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_SYM; + } else if (!strcmp(type, "sym-offset")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_SYM_OFFSET; + } else if (!strcmp(type, "syscall")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_SYSCALL; + } else if (!strcmp(type, "execname") || + !strcmp(type, "comm")) { + ret = TRACEFS_HIST_KEY_EXECNAME; + if (strcmp(field->field, "common_pid")) { + parse_error(sb, field->raw, + "'%s' is only allowed for common_pid\n", + type); + ret = -1; + } + } else if (!strcmp(type, "log") || + !strcmp(type, "log2")) { + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_LOG; + } else if (!strncmp(type, "buckets", 7)) { + if (type[7] != '=' || !isdigit(type[8])) { + parse_error(sb, field->raw, + "buckets type must have '=[number]' after it\n"); + ret = -1; + goto out; + } + *cnt = atoi(&type[8]); + if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) + goto fail_type; + ret = TRACEFS_HIST_KEY_BUCKETS; + } else { + parse_error(sb, field->raw, + "Cast of '%s' to unknown type '%s'\n", + field->raw, type); + ret = -1; + } + out: + free(type); + return ret; + fail_type: + parse_error(sb, field->raw, + "Field '%s' cast to '%s' but is of type %s\n", + field->field, type, tfield->flags & TEP_FIELD_IS_STRING ? + "string" : "array"); + free(type); + return -1; +} + +static struct tracefs_synth *build_synth(struct tep_handle *tep, + const char *name, + struct sql_table *table) +{ + struct tracefs_synth *synth; + struct field *field; + struct match *match; + struct expr *expr; + const char *start_system; + const char *start_event; + const char *end_system; + const char *end_event; + const char *start_match; + const char *end_match; + bool started_start = false; + bool started_end = false; + bool non_val = false; + int ret; + + if (!table->from) + return NULL; + + /* This could be a simple SQL statement to only build a histogram */ + if (!table->to) { + ret = update_fields(tep, table, table->from); + if (ret < 0) + return NULL; + + start_system = table->from->field.system; + start_event = table->from->field.event_name; + + synth = synth_init_from(tep, start_system, start_event); + if (!synth) + return synth_init_error(tep, table); + goto hist_only; + } + + ret = update_vars(tep, table, table->from); + if (ret < 0) + return NULL; + + ret = update_vars(tep, table, table->to); + if (ret < 0) + return NULL; + + start_system = table->from->field.system; + start_event = table->from->field.event_name; + + match = table->matches; + if (!match) + return NULL; + + ret = test_match(table, match); + if (ret < 0) + return NULL; + + end_system = table->to->field.system; + end_event = table->to->field.event_name; + + assign_match(start_system, start_event, match, + &start_match, &end_match); + + synth = tracefs_synth_alloc(tep, name, start_system, + start_event, end_system, end_event, + start_match, end_match, NULL); + if (!synth) + return synth_init_error(tep, table); + + for (match = match->next; match; match = match->next) { + ret = test_match(table, match); + if (ret < 0) + goto free; + + assign_match(start_system, start_event, match, + &start_match, &end_match); + + ret = tracefs_synth_add_match_field(synth, + start_match, + end_match, NULL); + if (ret < 0) { + field_match_error(tep, table->sb, match); + goto free; + } + } + + hist_only: + /* table->to may be NULL here */ + + for (expr = table->selections; expr; expr = expr->next) { + if (expr->type == EXPR_FIELD) { + ret = -1; + field = &expr->field; + if (field->ftype != FIELD_TO && + field->system == start_system && + field->event_name == start_event) { + int type; + int cnt = 0; + type = verify_field_type(tep, table->sb, expr, &cnt); + if (type < 0) + goto free; + if (type != HIST_COUNTER_TYPE) + non_val = true; + ret = synth_add_start_field(synth, + field->field, field->label, + type, cnt); + } else if (table->to) { + ret = tracefs_synth_add_end_field(synth, + field->field, field->label); + } + if (ret < 0) { + selection_error(tep, table->sb, expr); + goto free; + } + continue; + } + + if (!table->to) { + compare_no_to_error(table->sb, expr); + goto free; + } + + if (expr->type != EXPR_COMPARE) + goto free; + + ret = build_compare(synth, start_system, end_system, + &expr->compare); + if (ret < 0) { + compare_error(tep, table->sb, expr); + goto free; + } + } + + if (!non_val && !table->to) { + table->sb->line_no = 0; + table->sb->line_idx = 10; + parse_error(table->sb, "CAST", + "Not all SELECT items can be of type _COUNTER_\n"); + goto free; + } + + for (expr = table->where; expr; expr = expr->next) { + const char *filter_system = NULL; + const char *filter_event = NULL; + enum field_type ftype = FIELD_NONE; + bool *started; + bool start; + + ret = verify_filter(table->sb, &expr->filter, &filter_system, + &filter_event, &ftype); + if (ret < 0) + goto free; + + start = filter_system == start_system && + filter_event == start_event && + ftype != FIELD_TO; + + if (start) + started = &started_start; + else if (!table->to) { + where_no_to_error(table->sb, expr, start_event, + filter_event); + goto free; + } else + started = &started_end; + + ret = build_filter(tep, table->sb, synth, start, expr, started); + if (ret < 0) + goto free; + } + + return synth; + free: + tracefs_synth_free(synth); + return NULL; +} + +static void free_sql_table(struct sql_table *table) +{ + struct match *match; + struct expr *expr; + + if (!table) + return; + + while ((expr = table->exprs)) { + table->exprs = expr->free_list; + free(expr); + } + + while ((match = table->matches)) { + table->matches = match->next; + free(match); + } + + free(table); +} + +static void free_str_hash(struct str_hash **hash) +{ + struct str_hash *item; + int i; + + for (i = 0; i < 1 << HASH_BITS; i++) { + while ((item = hash[i])) { + hash[i] = item->next; + free(item->str); + free(item); + } + } +} + +static void free_sb(struct sqlhist_bison *sb) +{ + free_sql_table(sb->table); + free_str_hash(sb->str_hash); + free(sb->parse_error_str); +} + +struct tracefs_synth *tracefs_sql(struct tep_handle *tep, const char *name, + const char *sql_buffer, char **err) +{ + struct tracefs_synth *synth = NULL; + struct sqlhist_bison sb; + int ret; + + if (!tep || !sql_buffer) { + errno = EINVAL; + return NULL; + } + + memset(&sb, 0, sizeof(sb)); + + sb.buffer = sql_buffer; + sb.buffer_size = strlen(sql_buffer); + sb.buffer_idx = 0; + + ret = yylex_init_extra(&sb, &sb.scanner); + if (ret < 0) { + yylex_destroy(sb.scanner); + return NULL; + } + + ret = tracefs_parse(&sb); + yylex_destroy(sb.scanner); + + if (ret) + goto free; + + synth = build_synth(tep, name, sb.table); + + free: + if (!synth) { + if (sb.parse_error_str && err) { + *err = sb.parse_error_str; + sb.parse_error_str = NULL; + } + } + free_sb(&sb); + return synth; +} diff --git a/src/tracefs-tools.c b/src/tracefs-tools.c new file mode 100644 index 0000000..8e7b46d --- /dev/null +++ b/src/tracefs-tools.c @@ -0,0 +1,1273 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + * Updates: + * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <regex.h> +#include <dirent.h> +#include <limits.h> +#include <pthread.h> +#include <errno.h> + +#include "tracefs.h" +#include "tracefs-local.h" + +__hidden pthread_mutex_t toplevel_lock = PTHREAD_MUTEX_INITIALIZER; + +#define TRACE_CTRL "tracing_on" +#define TRACE_FILTER "set_ftrace_filter" +#define TRACE_NOTRACE "set_ftrace_notrace" +#define TRACE_FILTER_LIST "available_filter_functions" +#define CUR_TRACER "current_tracer" + +#define TRACERS \ + C(NOP, "nop"), \ + C(CUSTOM, "CUSTOM"), \ + C(FUNCTION, "function"), \ + C(FUNCTION_GRAPH, "function_graph"), \ + C(IRQSOFF, "irqsoff"), \ + C(PREEMPTOFF, "preemptoff"), \ + C(PREEMPTIRQSOFF, "preemptirqsoff"), \ + C(WAKEUP, "wakeup"), \ + C(WAKEUP_RT, "wakeup_rt"), \ + C(WAKEUP_DL, "wakeup_dl"), \ + C(MMIOTRACE, "mmiotrace"), \ + C(HWLAT, "hwlat"), \ + C(BRANCH, "branch"), \ + C(BLOCK, "block") + +#undef C +#define C(a, b) b +const char *tracers[] = { TRACERS }; + +#undef C +#define C(a, b) TRACEFS_TRACER_##a +const int tracer_enums[] = { TRACERS }; + +/* File descriptor for Top level set_ftrace_filter */ +static int ftrace_filter_fd = -1; +static int ftrace_notrace_fd = -1; + +static const char * const options_map[] = { + "unknown", + "annotate", + "bin", + "blk_cgname", + "blk_cgroup", + "blk_classic", + "block", + "context-info", + "disable_on_free", + "display-graph", + "event-fork", + "funcgraph-abstime", + "funcgraph-cpu", + "funcgraph-duration", + "funcgraph-irqs", + "funcgraph-overhead", + "funcgraph-overrun", + "funcgraph-proc", + "funcgraph-tail", + "func_stack_trace", + "function-fork", + "function-trace", + "graph-time", + "hex", + "irq-info", + "latency-format", + "markers", + "overwrite", + "pause-on-trace", + "printk-msg-only", + "print-parent", + "raw", + "record-cmd", + "record-tgid", + "sleep-time", + "stacktrace", + "sym-addr", + "sym-offset", + "sym-userobj", + "trace_printk", + "userstacktrace", + "verbose" }; + +static int trace_on_off(int fd, bool on) +{ + const char *val = on ? "1" : "0"; + int ret; + + ret = write(fd, val, 1); + if (ret == 1) + return 0; + + return -1; +} + +static int trace_on_off_file(struct tracefs_instance *instance, bool on) +{ + int ret; + int fd; + + fd = tracefs_instance_file_open(instance, TRACE_CTRL, O_WRONLY); + if (fd < 0) + return -1; + ret = trace_on_off(fd, on); + close(fd); + + return ret; +} + +/** + * tracefs_trace_is_on - Check if writing traces to the ring buffer is enabled + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns -1 in case of an error, 0 if tracing is disable or 1 if tracing + * is enabled. + */ +int tracefs_trace_is_on(struct tracefs_instance *instance) +{ + long long res; + + if (tracefs_instance_file_read_number(instance, TRACE_CTRL, &res) == 0) + return (int)res; + + return -1; +} + +/** + * tracefs_trace_on - Enable writing traces to the ring buffer of the given instance + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_trace_on(struct tracefs_instance *instance) +{ + return trace_on_off_file(instance, true); +} + +/** + * tracefs_trace_off - Disable writing traces to the ring buffer of the given instance + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_trace_off(struct tracefs_instance *instance) +{ + return trace_on_off_file(instance, false); +} + +/** + * tracefs_trace_on_fd - Enable writing traces to the ring buffer + * @fd: File descriptor to ftrace tracing_on file, previously opened + * with tracefs_trace_on_get_fd() + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_trace_on_fd(int fd) +{ + if (fd < 0) + return -1; + return trace_on_off(fd, true); +} + +/** + * tracefs_trace_off_fd - Disable writing traces to the ring buffer + * @fd: File descriptor to ftrace tracing_on file, previously opened + * with tracefs_trace_on_get_fd() + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_trace_off_fd(int fd) +{ + if (fd < 0) + return -1; + return trace_on_off(fd, false); +} + +/** + * tracefs_option_name - Get trace option name from id + * @id: trace option id + * + * Returns string with option name, or "unknown" in case of not known option id. + * The returned string must *not* be freed. + */ +const char *tracefs_option_name(enum tracefs_option_id id) +{ + /* Make sure options map contains all the options */ + BUILD_BUG_ON(ARRAY_SIZE(options_map) != TRACEFS_OPTION_MAX); + + if (id < TRACEFS_OPTION_MAX) + return options_map[id]; + + return options_map[0]; +} + +/** + * tracefs_option_id - Get trace option ID from name + * @name: trace option name + * + * Returns trace option ID or TRACEFS_OPTION_INVALID in case of an error or + * unknown option name. + */ +enum tracefs_option_id tracefs_option_id(const char *name) +{ + int i; + + if (!name) + return TRACEFS_OPTION_INVALID; + + for (i = 0; i < TRACEFS_OPTION_MAX; i++) { + if (strlen(name) == strlen(options_map[i]) && + !strcmp(options_map[i], name)) + return i; + } + + return TRACEFS_OPTION_INVALID; +} + +const static struct tracefs_options_mask * +trace_get_options(struct tracefs_instance *instance, bool enabled) +{ + pthread_mutex_t *lock = trace_get_lock(instance); + struct tracefs_options_mask *bitmask; + enum tracefs_option_id id; + unsigned long long set; + char file[PATH_MAX]; + struct stat st; + long long val; + char *path; + int ret; + + bitmask = enabled ? enabled_opts_mask(instance) : + supported_opts_mask(instance); + + for (id = 1; id < TRACEFS_OPTION_MAX; id++) { + snprintf(file, PATH_MAX, "options/%s", options_map[id]); + path = tracefs_instance_get_file(instance, file); + if (!path) + return NULL; + + set = 1; + ret = stat(path, &st); + if (ret < 0 || !S_ISREG(st.st_mode)) { + set = 0; + } else if (enabled) { + ret = tracefs_instance_file_read_number(instance, file, &val); + if (ret != 0 || val != 1) + set = 0; + } + + pthread_mutex_lock(lock); + bitmask->mask = (bitmask->mask & ~(1ULL << (id - 1))) | (set << (id - 1)); + pthread_mutex_unlock(lock); + + tracefs_put_tracing_file(path); + } + + + return bitmask; +} + +/** + * tracefs_options_get_supported - Get all supported trace options in given instance + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns bitmask structure with all trace options, supported in given instance, + * or NULL in case of an error. + */ +const struct tracefs_options_mask * +tracefs_options_get_supported(struct tracefs_instance *instance) +{ + return trace_get_options(instance, false); +} + +/** + * tracefs_options_get_enabled - Get all currently enabled trace options in given instance + * @instance: ftrace instance, can be NULL for the top instance + * + * Returns bitmask structure with all trace options, enabled in given instance, + * or NULL in case of an error. + */ +const struct tracefs_options_mask * +tracefs_options_get_enabled(struct tracefs_instance *instance) +{ + return trace_get_options(instance, true); +} + +static int trace_config_option(struct tracefs_instance *instance, + enum tracefs_option_id id, bool set) +{ + const char *set_str = set ? "1" : "0"; + char file[PATH_MAX]; + const char *name; + + name = tracefs_option_name(id); + if (!name) + return -1; + + snprintf(file, PATH_MAX, "options/%s", name); + if (strlen(set_str) != tracefs_instance_file_write(instance, file, set_str)) + return -1; + return 0; +} + +/** + * tracefs_option_enable - Enable trace option + * @instance: ftrace instance, can be NULL for the top instance + * @id: trace option id + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_option_enable(struct tracefs_instance *instance, enum tracefs_option_id id) +{ + return trace_config_option(instance, id, true); +} + +/** + * tracefs_option_disable - Disable trace option + * @instance: ftrace instance, can be NULL for the top instance + * @id: trace option id + * + * Returns -1 in case of an error or 0 otherwise + */ +int tracefs_option_disable(struct tracefs_instance *instance, enum tracefs_option_id id) +{ + return trace_config_option(instance, id, false); +} + +/** + * tracefs_option_is_supported - Check if an option is supported + * @instance: ftrace instance, can be NULL for the top instance + * @id: trace option id + * + * Returns true if an option with given id is supported by the system, false if + * it is not supported. + */ +bool tracefs_option_is_supported(struct tracefs_instance *instance, enum tracefs_option_id id) +{ + const char *name = tracefs_option_name(id); + char file[PATH_MAX]; + + if (!name) + return false; + snprintf(file, PATH_MAX, "options/%s", name); + return tracefs_file_exists(instance, file); +} + +/** + * tracefs_option_is_enabled - Check if an option is enabled in given instance + * @instance: ftrace instance, can be NULL for the top instance + * @id: trace option id + * + * Returns true if an option with given id is enabled in the given instance, + * false if it is not enabled. + */ +bool tracefs_option_is_enabled(struct tracefs_instance *instance, enum tracefs_option_id id) +{ + const char *name = tracefs_option_name(id); + char file[PATH_MAX]; + long long res; + + if (!name) + return false; + snprintf(file, PATH_MAX, "options/%s", name); + if (!tracefs_instance_file_read_number(instance, file, &res) && res) + return true; + + return false; +} + +/** + * tracefs_option_mask_is_set - Check if given option is set in the bitmask + * @options: Options bitmask + * @id: trace option id + * + * Returns true if an option with given id is set in the bitmask, + * false if it is not set. + */ +bool tracefs_option_mask_is_set(const struct tracefs_options_mask *options, + enum tracefs_option_id id) +{ + if (id > TRACEFS_OPTION_INVALID) + return options->mask & (1ULL << (id - 1)); + return false; +} + +struct func_list { + struct func_list *next; + char *func; + unsigned int start; + unsigned int end; +}; + +struct func_filter { + const char *filter; + regex_t re; + bool set; + bool is_regex; +}; + +static bool is_regex(const char *str) +{ + int i; + + for (i = 0; str[i]; i++) { + switch (str[i]) { + case 'a' ... 'z': + case 'A'...'Z': + case '_': + case '0'...'9': + case '*': + case '.': + /* Dots can be part of a function name */ + case '?': + continue; + default: + return true; + } + } + return false; +} + +static char *update_regex(const char *reg) +{ + int len = strlen(reg); + char *str; + + if (reg[0] == '^' && reg[len - 1] == '$') + return strdup(reg); + + str = malloc(len + 3); + if (reg[0] == '^') { + strcpy(str, reg); + } else { + str[0] = '^'; + strcpy(str + 1, reg); + len++; /* add ^ */ + } + if (str[len - 1] != '$') + str[len++]= '$'; + str[len] = '\0'; + return str; +} + +/* + * Convert a glob into a regular expression. + */ +static char *make_regex(const char *glob) +{ + char *str; + int cnt = 0; + int i, j; + + for (i = 0; glob[i]; i++) { + if (glob[i] == '*'|| glob[i] == '.') + cnt++; + } + + /* '^' + ('*'->'.*' or '.' -> '\.') + '$' + '\0' */ + str = malloc(i + cnt + 3); + if (!str) + return NULL; + + str[0] = '^'; + for (i = 0, j = 1; glob[i]; i++, j++) { + if (glob[i] == '*') + str[j++] = '.'; + /* Dots can be part of a function name */ + if (glob[i] == '.') + str[j++] = '\\'; + str[j] = glob[i]; + } + str[j++] = '$'; + str[j] = '\0'; + return str; +} + +static bool match(const char *str, struct func_filter *func_filter) +{ + return regexec(&func_filter->re, str, 0, NULL, 0) == 0; +} + +/* + * Return 0 on success, -1 error writing, 1 on other errors. + */ +static int write_filter(int fd, const char *filter, const char *module) +{ + char *each_str = NULL; + int write_size; + int size; + + if (module) + write_size = asprintf(&each_str, "%s:mod:%s ", filter, module); + else + write_size = asprintf(&each_str, "%s ", filter); + + if (write_size < 0) + return 1; + + size = write(fd, each_str, write_size); + free(each_str); + + /* compare written bytes*/ + if (size < write_size) + return -1; + + return 0; +} + +static int add_func(struct func_list ***next_func_ptr, unsigned int index) +{ + struct func_list **next_func = *next_func_ptr; + struct func_list *func_list = *next_func; + + if (!func_list) { + func_list = calloc(1, sizeof(*func_list)); + if (!func_list) + return -1; + func_list->start = index; + func_list->end = index; + *next_func = func_list; + return 0; + } + + if (index == func_list->end + 1) { + func_list->end = index; + return 0; + } + *next_func_ptr = &func_list->next; + return add_func(next_func_ptr, index); +} + +static int add_func_str(struct func_list ***next_func_ptr, const char *func) +{ + struct func_list **next_func = *next_func_ptr; + struct func_list *func_list = *next_func; + + if (!func_list) { + func_list = calloc(1, sizeof(*func_list)); + if (!func_list) + return -1; + func_list->func = strdup(func); + if (!func_list->func) + return -1; + *next_func = func_list; + return 0; + } + *next_func_ptr = &func_list->next; + return add_func_str(next_func_ptr, func); +} + +static void free_func_list(struct func_list *func_list) +{ + struct func_list *f; + + while (func_list) { + f = func_list; + func_list = f->next; + free(f->func); + free(f); + } +} + +enum match_type { + FILTER_CHECK = (1 << 0), + FILTER_WRITE = (1 << 1), + FILTER_FUTURE = (1 << 2), + SAVE_STRING = (1 << 2), +}; + +static int match_filters(int fd, struct func_filter *func_filter, + const char *module, struct func_list **func_list, + int flags) +{ + enum match_type type = flags & (FILTER_CHECK | FILTER_WRITE); + bool save_str = flags & SAVE_STRING; + bool future = flags & FILTER_FUTURE; + bool mod_match = false; + char *line = NULL; + size_t size = 0; + char *path; + FILE *fp; + int index = 0; + int ret = 1; + int mlen; + + path = tracefs_get_tracing_file(TRACE_FILTER_LIST); + if (!path) + return 1; + + fp = fopen(path, "r"); + tracefs_put_tracing_file(path); + + if (!fp) + return 1; + + if (module) + mlen = strlen(module); + + while (getline(&line, &size, fp) >= 0) { + char *saveptr = NULL; + char *tok, *mtok; + int len = strlen(line); + + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + tok = strtok_r(line, " ", &saveptr); + if (!tok) + goto next; + + index++; + + if (module) { + mtok = strtok_r(NULL, " ", &saveptr); + if (!mtok) + goto next; + if ((strncmp(mtok + 1, module, mlen) != 0) || + (mtok[mlen + 1] != ']')) + goto next; + if (future) + mod_match = true; + } + switch (type) { + case FILTER_CHECK: + if (match(tok, func_filter)) { + func_filter->set = true; + if (save_str) + ret = add_func_str(&func_list, tok); + else + ret = add_func(&func_list, index); + if (ret) + goto out; + } + break; + case FILTER_WRITE: + /* Writes only have one filter */ + if (match(tok, func_filter)) { + ret = write_filter(fd, tok, module); + if (ret) + goto out; + } + break; + default: + /* Should never happen */ + ret = -1; + goto out; + + } + next: + free(line); + line = NULL; + len = 0; + } + out: + free(line); + fclose(fp); + + /* If there was no matches and future was set, this is a success */ + if (future && !mod_match) + ret = 0; + + return ret; +} + +static int check_available_filters(struct func_filter *func_filter, + const char *module, + struct func_list **func_list, + bool future) +{ + int flags = FILTER_CHECK | (future ? FILTER_FUTURE : 0); + + return match_filters(-1, func_filter, module, func_list, flags); +} + + +static int list_available_filters(struct func_filter *func_filter, + const char *module, + struct func_list **func_list) +{ + int flags = FILTER_CHECK | SAVE_STRING; + + return match_filters(-1, func_filter, module, func_list, flags); +} + +static int set_regex_filter(int fd, struct func_filter *func_filter, + const char *module) +{ + return match_filters(fd, func_filter, module, NULL, FILTER_WRITE); +} + +static int controlled_write(int fd, struct func_filter *func_filter, + const char *module) +{ + const char *filter = func_filter->filter; + int ret; + + if (func_filter->is_regex) + ret = set_regex_filter(fd, func_filter, module); + else + ret = write_filter(fd, filter, module); + + return ret; +} + +static int init_func_filter(struct func_filter *func_filter, const char *filter) +{ + char *str; + int ret; + + if (!(func_filter->is_regex = is_regex(filter))) + str = make_regex(filter); + else + str = update_regex(filter); + + if (!str) + return -1; + + ret = regcomp(&func_filter->re, str, REG_ICASE|REG_NOSUB); + free(str); + + if (ret < 0) + return -1; + + func_filter->filter = filter; + return 0; +} + +static int write_number(int fd, unsigned int start, unsigned int end) +{ + char buf[64]; + unsigned int i; + int n, ret; + + for (i = start; i <= end; i++) { + n = snprintf(buf, 64, "%d ", i); + ret = write(fd, buf, n); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * This will try to write the first number, if that fails, it + * will assume that it is not supported and return 1. + * If the first write succeeds, but a following write fails, then + * the kernel does support this, but something else went wrong, + * in this case, return -1. + */ +static int write_func_list(int fd, struct func_list *list) +{ + int ret; + + if (!list) + return 0; + + ret = write_number(fd, list->start, list->end); + if (ret) + return 1; // try a different way + list = list->next; + while (list) { + ret = write_number(fd, list->start, list->end); + if (ret) + return -1; + list = list->next; + } + return 0; +} + +static int update_filter(const char *filter_path, int *fd, + struct tracefs_instance *instance, const char *filter, + const char *module, unsigned int flags) +{ + struct func_filter func_filter; + struct func_list *func_list = NULL; + bool reset = flags & TRACEFS_FL_RESET; + bool cont = flags & TRACEFS_FL_CONTINUE; + bool future = flags & TRACEFS_FL_FUTURE; + pthread_mutex_t *lock = trace_get_lock(instance); + int open_flags; + int ret = 1; + + /* future flag is only applicable to modules */ + if (future && !module) { + errno = EINVAL; + return 1; + } + + pthread_mutex_lock(lock); + + /* RESET is only allowed if the file is not opened yet */ + if (reset && *fd >= 0) { + errno = EBUSY; + ret = -1; + goto out; + } + + /* + * Set EINVAL on no matching filter. But errno may still be modified + * on another type of failure (allocation or opening a file). + */ + errno = EINVAL; + + /* module set with NULL filter means to enable all functions in a module */ + if (module && !filter) + filter = "*"; + + if (!filter) { + /* OK to call without filters if this is closing the opened file */ + if (!cont && *fd >= 0) { + errno = 0; + ret = 0; + close(*fd); + *fd = -1; + } + /* Also OK to call if reset flag is set */ + if (reset) + goto open_file; + + goto out; + } + + if (init_func_filter(&func_filter, filter) < 0) + goto out; + + ret = check_available_filters(&func_filter, module, &func_list, future); + if (ret) + goto out_free; + + open_file: + ret = 1; + + open_flags = reset ? O_TRUNC : O_APPEND; + + if (*fd < 0) + *fd = open(filter_path, O_WRONLY | O_CLOEXEC | open_flags); + if (*fd < 0) + goto out_free; + + errno = 0; + ret = 0; + + if (filter) { + /* + * If future is set, and no functions were found, then + * set it directly. + */ + if (func_list) + ret = write_func_list(*fd, func_list); + else + ret = 1; + if (ret > 0) + ret = controlled_write(*fd, &func_filter, module); + } + + if (!cont) { + close(*fd); + *fd = -1; + } + + out_free: + if (filter) + regfree(&func_filter.re); + free_func_list(func_list); + out: + pthread_mutex_unlock(lock); + + return ret; +} + +/** + * tracefs_function_filter - filter the functions that are traced + * @instance: ftrace instance, can be NULL for top tracing instance. + * @filter: The filter to filter what functions are to be traced + * @module: Module to be traced or NULL if all functions are to be examined. + * @flags: flags on modifying the filter file + * + * @filter may be a full function name, a glob, or a regex. It will be + * considered a regex, if there's any characters that are not normally in + * function names or "*" or "?" for a glob. + * + * @flags: + * TRACEFS_FL_RESET - will clear the functions in the filter file + * before applying the @filter. This will error with -1 + * and errno of EBUSY if this flag is set and a previous + * call had the same instance and TRACEFS_FL_CONTINUE set. + * TRACEFS_FL_CONTINUE - will keep the filter file open on return. + * The filter is updated on closing of the filter file. + * With this flag set, the file is not closed, and more filters + * may be added before they take effect. The last call of this + * function must be called without this flag for the filter + * to take effect. + * TRACEFS_FL_FUTURE - only applicable if "module" is set. If no match + * is made, and the module is not yet loaded, it will still attempt + * to write the filter plus the module; "<filter>:mod:<module>" + * to the filter file. Starting with Linux kernels 4.13, it is possible + * to load the filter file with module functions for a module that + * is not yet loaded, and when the module is loaded, it will then + * activate the module. + * + * Returns 0 on success, 1 if there was an error but the filtering has not + * yet started, -1 if there was an error but the filtering has started. + * If -1 is returned and TRACEFS_FL_CONTINUE was set, then this function + * needs to be called again without the TRACEFS_FL_CONTINUE flag to commit + * the changes and close the filter file. + */ +int tracefs_function_filter(struct tracefs_instance *instance, const char *filter, + const char *module, unsigned int flags) +{ + char *filter_path; + int *fd; + int ret; + + filter_path = tracefs_instance_get_file(instance, TRACE_FILTER); + if (!filter_path) + return -1; + + if (instance) + fd = &instance->ftrace_filter_fd; + else + fd = &ftrace_filter_fd; + + ret = update_filter(filter_path, fd, instance, filter, module, flags); + tracefs_put_tracing_file(filter_path); + return ret; +} + +/** + * tracefs_function_notrace - filter the functions that are not to be traced + * @instance: ftrace instance, can be NULL for top tracing instance. + * @filter: The filter to filter what functions are not to be traced + * @module: Module to be traced or NULL if all functions are to be examined. + * @flags: flags on modifying the filter file + * + * See tracefs_function_filter, as this has the same functionality but + * for adding to the "notrace" filter. + */ +int tracefs_function_notrace(struct tracefs_instance *instance, const char *filter, + const char *module, unsigned int flags) +{ + char *filter_path; + int *fd; + int ret; + + filter_path = tracefs_instance_get_file(instance, TRACE_NOTRACE); + if (!filter_path) + return -1; + + if (instance) + fd = &instance->ftrace_notrace_fd; + else + fd = &ftrace_notrace_fd; + + ret = update_filter(filter_path, fd, instance, filter, module, flags); + tracefs_put_tracing_file(filter_path); + return ret; +} + +int write_tracer(int fd, const char *tracer) +{ + int ret; + + ret = write(fd, tracer, strlen(tracer)); + if (ret < strlen(tracer)) + return -1; + return ret; +} + +/** + * tracefs_set_tracer - function to set the tracer + * @instance: ftrace instance, can be NULL for top tracing instance + * @tracer: The tracer enum that defines the tracer to be set + * @t: A tracer name if TRACEFS_TRACER_CUSTOM is passed in for @tracer + * + * Set the tracer for the instance based on the tracefs_tracer enums. + * If the user wishes to enable a tracer that is not defined by + * the enum (new or custom kernel), the tracer can be set to + * TRACEFS_TRACER_CUSTOM, and pass in a const char * name for + * the tracer to set. + * + * Returns 0 on succes, negative on error. + */ + +int tracefs_tracer_set(struct tracefs_instance *instance, + enum tracefs_tracers tracer, ...) +{ + char *tracer_path = NULL; + const char *t = NULL; + int ret = -1; + int fd = -1; + int i; + + if (tracer < 0 || tracer > ARRAY_SIZE(tracers)) { + errno = EINVAL; + return -1; + } + + tracer_path = tracefs_instance_get_file(instance, CUR_TRACER); + if (!tracer_path) + return -1; + + fd = open(tracer_path, O_WRONLY); + if (fd < 0) { + errno = ENOENT; + goto out; + } + + if (tracer == TRACEFS_TRACER_CUSTOM) { + va_list ap; + + va_start(ap, tracer); + t = va_arg(ap, const char *); + va_end(ap); + } else if (tracer == tracer_enums[tracer]) { + t = tracers[tracer]; + } else { + for (i = 0; i < ARRAY_SIZE(tracer_enums); i++) { + if (tracer == tracer_enums[i]) { + t = tracers[i]; + break; + } + } + } + if (!t) { + errno = EINVAL; + goto out; + } + ret = write_tracer(fd, t); + /* + * If the tracer does not exist, EINVAL is returned, + * but let the user know this as ENODEV. + */ + if (ret < 0 && errno == EINVAL) + errno = ENODEV; + out: + tracefs_put_tracing_file(tracer_path); + close(fd); + return ret > 0 ? 0 : ret; +} + +int tracefs_tracer_clear(struct tracefs_instance *instance) +{ + return tracefs_tracer_set(instance, TRACEFS_TRACER_NOP); +} + +static bool splice_safe(int fd, int pfd) +{ + int ret; + + errno = 0; + ret = splice(pfd, NULL, fd, NULL, + 10, SPLICE_F_NONBLOCK | SPLICE_F_MOVE); + + return !ret || (ret < 0 && errno == EAGAIN); +} + +static ssize_t read_trace_pipe(bool *keep_going, int in_fd, int out_fd) +{ + char buf[BUFSIZ]; + ssize_t bread = 0; + int ret; + + while (*(volatile bool *)keep_going) { + int r; + ret = read(in_fd, buf, BUFSIZ); + if (ret <= 0) + break; + r = ret; + ret = write(out_fd, buf, r); + if (ret < 0) + break; + bread += ret; + /* + * If the write does a partial write, then + * the iteration should stop. This can happen if + * the destination file system ran out of disk space. + * Sure, it probably lost a little from the read + * but there's not much more that can be + * done. Just return what was transferred. + */ + if (ret < r) + break; + } + + if (ret < 0 && (errno == EAGAIN || errno == EINTR)) + ret = 0; + + return ret < 0 ? ret : bread; +} + +static bool top_pipe_keep_going; + +/** + * tracefs_trace_pipe_stream - redirect the stream of trace data to an output + * file. The "splice" system call is used to moves the data without copying + * between kernel address space and user address space. The user can interrupt + * the streaming of the data by pressing Ctrl-c. + * @fd: The file descriptor of the output file. + * @instance: ftrace instance, can be NULL for top tracing instance. + * @flags: flags for opening the trace_pipe file. + * + * Returns -1 in case of an error or number of bytes transferred otherwise. + */ +ssize_t tracefs_trace_pipe_stream(int fd, struct tracefs_instance *instance, + int flags) +{ + bool *keep_going = instance ? &instance->pipe_keep_going : + &top_pipe_keep_going; + const char *file = "trace_pipe"; + int brass[2], in_fd, ret = -1; + int sflags = flags & O_NONBLOCK ? SPLICE_F_NONBLOCK : 0; + off_t data_size; + ssize_t bread = 0; + + (*(volatile bool *)keep_going) = true; + + in_fd = tracefs_instance_file_open(instance, file, O_RDONLY | flags); + if (in_fd < 0) { + tracefs_warning("Failed to open 'trace_pipe'."); + return ret; + } + + if(pipe(brass) < 0) { + tracefs_warning("Failed to open pipe."); + goto close_file; + } + + data_size = fcntl(brass[0], F_GETPIPE_SZ); + if (data_size <= 0) { + tracefs_warning("Failed to open pipe (size=0)."); + goto close_all; + } + + /* Test if the output is splice safe */ + if (!splice_safe(fd, brass[0])) { + bread = read_trace_pipe(keep_going, in_fd, fd); + ret = 0; /* Force return of bread */ + goto close_all; + } + + errno = 0; + + while (*(volatile bool *)keep_going) { + ret = splice(in_fd, NULL, + brass[1], NULL, + data_size, sflags); + if (ret < 0) + break; + + ret = splice(brass[0], NULL, + fd, NULL, + data_size, sflags); + if (ret < 0) + break; + bread += ret; + } + + /* + * Do not return error in the case when the "splice" system call + * was interrupted by the user (pressing Ctrl-c). + * Or if NONBLOCK was specified. + */ + if (!keep_going || errno == EAGAIN || errno == EINTR) + ret = 0; + + close_all: + close(brass[0]); + close(brass[1]); + close_file: + close(in_fd); + + return ret ? ret : bread; +} + +/** + * tracefs_trace_pipe_print - redirect the stream of trace data to "stdout". + * The "splice" system call is used to moves the data without copying + * between kernel address space and user address space. + * @instance: ftrace instance, can be NULL for top tracing instance. + * @flags: flags for opening the trace_pipe file. + * + * Returns -1 in case of an error or number of bytes transferred otherwise. + */ + +ssize_t tracefs_trace_pipe_print(struct tracefs_instance *instance, int flags) +{ + return tracefs_trace_pipe_stream(STDOUT_FILENO, instance, flags); +} + +/** + * tracefs_trace_pipe_stop - stop the streaming of trace data. + * @instance: ftrace instance, can be NULL for top tracing instance. + */ +void tracefs_trace_pipe_stop(struct tracefs_instance *instance) +{ + if (instance) + instance->pipe_keep_going = false; + else + top_pipe_keep_going = false; +} + +/** + * tracefs_filter_functions - return a list of available functons that can be filtered + * @filter: The filter to filter what functions to list (can be NULL for all) + * @module: Module to be traced or NULL if all functions are to be examined. + * @list: The list to return the list from (freed by tracefs_list_free() on success) + * + * Returns a list of function names that match @filter and @module. If both + * @filter and @module is NULL, then all available functions that can be filtered + * will be returned. (Note, there can be duplicates, if there are more than + * one function with the same name. + * + * On success, zero is returned, and @list contains a list of functions that were + * found, and must be freed with tracefs_list_free(). + * On failure, a negative number is returned, and @list is ignored. + */ +int tracefs_filter_functions(const char *filter, const char *module, char ***list) +{ + struct func_filter func_filter; + struct func_list *func_list = NULL, *f; + char **funcs = NULL; + int ret; + + if (!filter) + filter = ".*"; + + ret = init_func_filter(&func_filter, filter); + if (ret < 0) + return ret; + + ret = list_available_filters(&func_filter, module, &func_list); + if (ret < 0) + goto out; + + ret = -1; + for (f = func_list; f; f = f->next) { + char **tmp; + + tmp = tracefs_list_add(funcs, f->func); + if (!tmp) { + tracefs_list_free(funcs); + goto out; + } + funcs = tmp; + } + + *list = funcs; + ret = 0; +out: + regfree(&func_filter.re); + free_func_list(func_list); + return ret; +} diff --git a/src/tracefs-uprobes.c b/src/tracefs-uprobes.c new file mode 100644 index 0000000..aa39b75 --- /dev/null +++ b/src/tracefs-uprobes.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2022, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#include <stdlib.h> +#include <errno.h> + +#include "tracefs.h" +#include "tracefs-local.h" + +#define UPROBE_DEFAULT_GROUP "uprobes" + +static struct tracefs_dynevent * +uprobe_alloc(enum tracefs_dynevent_type type, const char *system, const char *event, + const char *file, unsigned long long offset, const char *fetchargs) +{ + struct tracefs_dynevent *kp; + char *target; + + if (!event || !file) { + errno = EINVAL; + return NULL; + } + + if (!system) + system = UPROBE_DEFAULT_GROUP; + + if (asprintf(&target, "%s:0x%0*llx", file, (int)(sizeof(void *) * 2), offset) < 0) + return NULL; + + kp = dynevent_alloc(type, system, event, target, fetchargs); + free(target); + + return kp; +} + +/** + * tracefs_uprobe_alloc - Allocate new user probe (uprobe) + * @system: The system name (NULL for the default uprobes) + * @event: The name of the event to create + * @file: The full path to the binary file, where the uprobe will be set + * @offset: Offset within the @file + * @fetchargs: String with arguments, that will be fetched with the uprobe + * + * Allocate new uprobe context that will be in the @system group + * (or uprobes if @system is NULL) and with @event name. The new uprobe will be + * attached to @offset within the @file. The arguments described in @fetchargs + * will fetched with the uprobe. See linux/Documentation/trace/uprobetracer.rst + * for more details. + * + * The uprobe is not created in the system. + * + * Return a pointer to a uprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + */ +struct tracefs_dynevent * +tracefs_uprobe_alloc(const char *system, const char *event, + const char *file, unsigned long long offset, const char *fetchargs) +{ + return uprobe_alloc(TRACEFS_DYNEVENT_UPROBE, system, event, file, offset, fetchargs); +} + +/** + * tracefs_uretprobe_alloc - Allocate new user return probe (uretprobe) + * @system: The system name (NULL for the default uprobes) + * @event: The name of the event to create + * @file: The full path to the binary file, where the uretprobe will be set + * @offset: Offset within the @file + * @fetchargs: String with arguments, that will be fetched with the uretprobe + * + * Allocate mew uretprobe context that will be in the @system group + * (or uprobes if @system is NULL) and with @event name. The new uretprobe will + * be attached to @offset within the @file. The arguments described in @fetchargs + * will fetched with the uprobe. See linux/Documentation/trace/uprobetracer.rst + * for more details. + * + * The uretprobe is not created in the system. + * + * Return a pointer to a uretprobe context on success, or NULL on error. + * The returned pointer must be freed with tracefs_dynevent_free() + * + */ +struct tracefs_dynevent * +tracefs_uretprobe_alloc(const char *system, const char *event, + const char *file, unsigned long long offset, const char *fetchargs) +{ + return uprobe_alloc(TRACEFS_DYNEVENT_URETPROBE, system, event, file, offset, fetchargs); +} diff --git a/src/tracefs-utils.c b/src/tracefs-utils.c new file mode 100644 index 0000000..9acf2ad --- /dev/null +++ b/src/tracefs-utils.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2008, 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + * Updates: + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#include <stdlib.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <linux/limits.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include <event-parse.h> +#include <event-utils.h> +#include "tracefs.h" +#include "tracefs-local.h" + +#define TRACEFS_PATH "/sys/kernel/tracing" +#define DEBUGFS_PATH "/sys/kernel/debug" + +#define ERROR_LOG "error_log" + +#define _STR(x) #x +#define STR(x) _STR(x) + +static int log_level = TEP_LOG_CRITICAL; +static char *custom_tracing_dir; + +/** + * tracefs_set_loglevel - set log level of the library + * @level: desired level of the library messages + */ +void tracefs_set_loglevel(enum tep_loglevel level) +{ + log_level = level; + tep_set_loglevel(level); +} + +void __weak tracefs_warning(const char *fmt, ...) +{ + va_list ap; + + if (log_level < TEP_LOG_WARNING) + return; + + va_start(ap, fmt); + tep_vprint("libtracefs", TEP_LOG_WARNING, true, fmt, ap); + va_end(ap); +} + +static int mount_tracefs(void) +{ + struct stat st; + int ret; + + /* make sure debugfs exists */ + ret = stat(TRACEFS_PATH, &st); + if (ret < 0) + return -1; + + ret = mount("nodev", TRACEFS_PATH, + "tracefs", 0, NULL); + + return ret; +} + +static int mount_debugfs(void) +{ + struct stat st; + int ret; + + /* make sure debugfs exists */ + ret = stat(DEBUGFS_PATH, &st); + if (ret < 0) + return -1; + + ret = mount("nodev", DEBUGFS_PATH, + "debugfs", 0, NULL); + + return ret; +} + +/* Exported for testing purpose only */ +__hidden char *find_tracing_dir(bool debugfs, bool mount) +{ + char *debug_str = NULL; + char fspath[PATH_MAX+1]; + char *tracing_dir; + char type[100]; + int use_debug = 0; + FILE *fp; + + fp = fopen("/proc/mounts", "r"); + if (!fp) { + tracefs_warning("Can't open /proc/mounts for read"); + return NULL; + } + + while (fscanf(fp, "%*s %" + STR(PATH_MAX) + "s %99s %*s %*d %*d\n", + fspath, type) == 2) { + if (!debugfs && strcmp(type, "tracefs") == 0) + break; + if (!debug_str && strcmp(type, "debugfs") == 0) { + if (debugfs) + break; + debug_str = strdup(fspath); + if (!debug_str) { + fclose(fp); + return NULL; + } + } + } + fclose(fp); + + if (debugfs) { + if (strcmp(type, "debugfs") != 0) { + if (!mount || mount_debugfs() < 0) + return NULL; + strcpy(fspath, DEBUGFS_PATH); + } + } else if (strcmp(type, "tracefs") != 0) { + if (!mount || mount_tracefs() < 0) { + if (debug_str) { + strncpy(fspath, debug_str, PATH_MAX); + fspath[PATH_MAX] = 0; + } else { + if (!mount || mount_debugfs() < 0) { + if (mount) + tracefs_warning("debugfs not mounted, please mount"); + free(debug_str); + return NULL; + } + strcpy(fspath, DEBUGFS_PATH); + } + use_debug = 1; + } else + strcpy(fspath, TRACEFS_PATH); + } + free(debug_str); + + if (use_debug) { + int ret; + + ret = asprintf(&tracing_dir, "%s/tracing", fspath); + if (ret < 0) + return NULL; + } else { + tracing_dir = strdup(fspath); + if (!tracing_dir) + return NULL; + } + + return tracing_dir; +} + +/** + * tracefs_tracing_dir_is_mounted - test if the tracing dir is already mounted + * @mount: Mount it if it is not already mounted + * @path: the path to the tracing directory if mounted or was mounted + * + * Returns 1 if the tracing directory is already mounted and 0 if it is not. + * If @mount is set and it fails to mount, it returns -1. + * + * If path is not NULL, and the tracing directory is or was mounted, it holds + * the path to the tracing directory. It must not be freed. + */ +int tracefs_tracing_dir_is_mounted(bool mount, const char **path) +{ + const char *dir; + + dir = find_tracing_dir(false, false); + if (dir) { + if (path) + *path = dir; + return 1; + } + if (!mount) + return 0; + + dir = find_tracing_dir(false, mount); + if (!dir) + return -1; + if (path) + *path = dir; + return 0; +} + +/** + * trace_find_tracing_dir - Find tracing directory + * @debugfs: Boolean to just return the debugfs directory + * + * Returns string containing the full path to the system's tracing directory. + * The string must be freed by free() + */ +__hidden char *trace_find_tracing_dir(bool debugfs) +{ + return find_tracing_dir(debugfs, false); +} + +/** + * tracefs_set_tracing_dir - Set location of the tracing directory + * @tracing_dir: full path to the system's tracing directory mount point. + * + * Set the location to the system's tracing directory. This API should be used + * to set a custom location of the tracing directory. There is no need to call + * it if the location is standard, in that case the library will auto detect it. + * + * Returns 0 on success, -1 otherwise. + */ +int tracefs_set_tracing_dir(char *tracing_dir) +{ + if (custom_tracing_dir) { + free(custom_tracing_dir); + custom_tracing_dir = NULL; + } + + if (tracing_dir) { + custom_tracing_dir = strdup(tracing_dir); + if (!custom_tracing_dir) + return -1; + } + + return 0; +} + +/* Used to check if the directory is still mounted */ +static int test_dir(const char *dir, const char *file) +{ + char path[strlen(dir) + strlen(file) + 2]; + struct stat st; + + sprintf(path, "%s/%s", dir, file); + return stat(path, &st) < 0 ? 0 : 1; +} + +/** + * tracefs_tracing_dir - Get tracing directory + * + * Returns string containing the full path to the system's tracing directory. + * The returned string must *not* be freed. + */ +const char *tracefs_tracing_dir(void) +{ + static const char *tracing_dir; + + /* Do not check custom_tracing_dir */ + if (custom_tracing_dir) + return custom_tracing_dir; + + if (tracing_dir && test_dir(tracing_dir, "trace")) + return tracing_dir; + + tracing_dir = find_tracing_dir(false, true); + return tracing_dir; +} + +/** + * tracefs_debug_dir - Get debugfs directory path + * + * Returns string containing the full path to the system's debugfs directory. + * + * The returned string must *not* be freed. + */ +const char *tracefs_debug_dir(void) +{ + static const char *debug_dir; + + if (debug_dir && test_dir(debug_dir, "tracing")) + return debug_dir; + + debug_dir = find_tracing_dir(true, true); + return debug_dir; +} + +/** + * tracefs_get_tracing_file - Get tracing file + * @name: tracing file name + * + * Returns string containing the full path to a tracing file in + * the system's tracing directory. + * + * Must use tracefs_put_tracing_file() to free the returned string. + */ +char *tracefs_get_tracing_file(const char *name) +{ + const char *tracing; + char *file; + int ret; + + if (!name) + return NULL; + + tracing = tracefs_tracing_dir(); + if (!tracing) + return NULL; + + ret = asprintf(&file, "%s/%s", tracing, name); + if (ret < 0) + return NULL; + + return file; +} + +/** + * tracefs_put_tracing_file - Free tracing file or directory name + * + * Frees tracing file or directory, returned by + * tracefs_get_tracing_file()API. + */ +void tracefs_put_tracing_file(char *name) +{ + free(name); +} + +__hidden int str_read_file(const char *file, char **buffer, bool warn) +{ + char stbuf[BUFSIZ]; + char *buf = NULL; + int size = 0; + char *nbuf; + int fd; + int r; + + fd = open(file, O_RDONLY); + if (fd < 0) { + if (warn) + tracefs_warning("File %s not found", file); + return -1; + } + + do { + r = read(fd, stbuf, BUFSIZ); + if (r <= 0) + continue; + nbuf = realloc(buf, size+r+1); + if (!nbuf) { + if (warn) + tracefs_warning("Failed to allocate file buffer"); + size = -1; + break; + } + buf = nbuf; + memcpy(buf+size, stbuf, r); + size += r; + } while (r > 0); + + close(fd); + if (r == 0 && size > 0) { + buf[size] = '\0'; + *buffer = buf; + } else + free(buf); + + return size; +} + +/** + * tracefs_error_all - return the content of the error log + * @instance: The instance to read the error log from (NULL for top level) + * + * Return NULL if the log is empty, or on error (where errno will be + * set. Otherwise the content of the entire log is returned in a string + * that must be freed with free(). + */ +char *tracefs_error_all(struct tracefs_instance *instance) +{ + char *content; + char *path; + int size; + + errno = 0; + + path = tracefs_instance_get_file(instance, ERROR_LOG); + if (!path) + return NULL; + size = str_read_file(path, &content, false); + tracefs_put_tracing_file(path); + + if (size <= 0) + return NULL; + + return content; +} + +enum line_states { + START, + CARROT, +}; + +/** + * tracefs_error_last - return the last error logged + * @instance: The instance to read the error log from (NULL for top level) + * + * Return NULL if the log is empty, or on error (where errno will be + * set. Otherwise a string containing the content of the last error shown +* in the log that must be freed with free(). + */ +char *tracefs_error_last(struct tracefs_instance *instance) +{ + enum line_states state = START; + char *content; + char *ret; + bool done = false; + int size; + int i; + + content = tracefs_error_all(instance); + if (!content) + return NULL; + + size = strlen(content); + if (!size) /* Should never happen */ + return content; + + for (i = size - 1; i > 0; i--) { + switch (state) { + case START: + if (content[i] == '\n') { + /* Remove extra new lines */ + content[i] = '\0'; + break; + } + if (content[i] == '^') + state = CARROT; + break; + case CARROT: + if (content[i] == '\n') { + /* Remember last new line */ + size = i; + break; + } + if (content[i] == '^') { + /* Go just passed the last newline */ + i = size + 1; + done = true; + } + break; + } + if (done) + break; + } + + if (i) { + ret = strdup(content + i); + free(content); + } else { + ret = content; + } + + return ret; +} + +/** + * tracefs_error_clear - clear the error log of an instance + * @instance: The instance to clear (NULL for top level) + * + * Clear the content of the error log. + * + * Returns 0 on success, -1 otherwise. + */ +int tracefs_error_clear(struct tracefs_instance *instance) +{ + return tracefs_instance_file_clear(instance, ERROR_LOG); +} + +/** + * tracefs_list_free - free list if strings, returned by APIs + * tracefs_event_systems() + * tracefs_system_events() + * + *@list pointer to a list of strings, the last one must be NULL + */ +void tracefs_list_free(char **list) +{ + int i; + + if (!list) + return; + + for (i = 0; list[i]; i++) + free(list[i]); + + /* The allocated list is before the user visible portion */ + list--; + free(list); +} + + +__hidden char ** trace_list_create_empty(void) +{ + char **list; + + list = calloc(2, sizeof(*list)); + + return list ? &list[1] : NULL; +} + +/** + * tracefs_list_add - create or extend a string list + * @list: The list to add to (NULL to create a new one) + * @string: The string to append to @list. + * + * If @list is NULL, a new list is created with the first element + * a copy of @string, and the second element is NULL. + * + * If @list is not NULL, it is then reallocated to include + * a new element and a NULL terminator, and will return the new + * allocated array on success, and the one passed in should be + * ignored. + * + * Returns an allocated string array that must be freed with + * tracefs_list_free() on success. On failure, NULL is returned + * and the @list is untouched. + */ +char **tracefs_list_add(char **list, const char *string) +{ + unsigned long size = 0; + char *str = strdup(string); + char **new_list; + + if (!str) + return NULL; + + /* + * The returned list is really the address of the + * second entry of the list (&list[1]), the first + * entry contains the number of strings in the list. + */ + if (list) { + list--; + size = *(unsigned long *)list; + } + + new_list = realloc(list, sizeof(*list) * (size + 3)); + if (!new_list) { + free(str); + return NULL; + } + + list = new_list; + list[0] = (char *)(size + 1); + list++; + list[size++] = str; + list[size] = NULL; + + return list; +} + +/* + * trace_list_pop - Removes the last string added + * @list: The list to remove the last event from + * + * Returns 0 on success, -1 on error. + * Returns 1 if the list is empty or NULL. + */ +__hidden int trace_list_pop(char **list) +{ + unsigned long size; + + if (!list || list[0]) + return 1; + + list--; + size = *(unsigned long *)list; + /* size must be greater than zero */ + if (!size) + return -1; + size--; + *list = (char *)size; + list++; + list[size] = NULL; + return 0; +} + +/** + * tracefs_list_size - Return the number of strings in the list + * @list: The list to determine the size. + * + * Returns the number of elements in the list. + * If @list is NULL, then zero is returned. + */ +int tracefs_list_size(char **list) +{ + if (!list) + return 0; + + list--; + return (int)*(unsigned long *)list; +} + +/** + * tracefs_tracer_available - test if a tracer is available + * @tracing_dir: The directory that contains the tracing directory + * @tracer: The name of the tracer + * + * Return true if the tracer is available + */ +bool tracefs_tracer_available(const char *tracing_dir, const char *tracer) +{ + bool ret = false; + char **tracers = NULL; + int i; + + tracers = tracefs_tracers(tracing_dir); + if (!tracers) + return false; + + for (i = 0; tracers[i]; i++) { + if (strcmp(tracer, tracers[i]) == 0) { + ret = true; + break; + } + } + + tracefs_list_free(tracers); + return ret; +} @@ -0,0 +1,7 @@ +#include <tracefs.h> + +int main() +{ + tracefs_tracing_dir(); + return 0; +} diff --git a/utest/Makefile b/utest/Makefile new file mode 100644 index 0000000..f10b709 --- /dev/null +++ b/utest/Makefile @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: LGPL-2.1 + +include $(src)/scripts/utils.mk + +bdir:=$(obj)/utest + +TARGETS = $(bdir)/trace-utest + +OBJS = +OBJS += trace-utest.o +OBJS += tracefs-utest.o + +LIBS += -lcunit \ + -ldl \ + $(obj)/lib/libtracefs.a + +OBJS := $(OBJS:%.o=$(bdir)/%.o) + +$(bdir): + @mkdir -p $(bdir) + +$(OBJS): | $(bdir) + +$(bdir)/trace-utest: $(OBJS) $(obj)/lib/libtracefs.a + $(Q)$(do_app_build) + +$(bdir)/%.o: %.c + $(Q)$(call do_fpic_compile) + +-include .*.d + +test: $(TARGETS) + +clean: + $(Q)$(call do_clean,$(TARGETS) $(bdir)/*.o $(bdir)/.*.d) diff --git a/utest/README b/utest/README new file mode 100644 index 0000000..647e460 --- /dev/null +++ b/utest/README @@ -0,0 +1,18 @@ + +Unit tests for tracefs library. The tests use CUnit framework: + http://cunit.sourceforge.net/ +which must be pre installed on the system, before building the unit tests. +The framework can be downloaded, compiled and installed manually, or +using a precompiled distro package: + + Fedora: + CUnit + CUnit-devel + + Ubuntu and Debian: + libcunit1 + libcunit1-doc + libcunit1-dev + + openSUSE and SLE: + cunit-devel diff --git a/utest/trace-utest.c b/utest/trace-utest.c new file mode 100644 index 0000000..58d4d4e --- /dev/null +++ b/utest/trace-utest.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2020, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#include <stdio.h> +#include <unistd.h> +#include <getopt.h> +#include <stdlib.h> + +#include <CUnit/CUnit.h> +#include <CUnit/Basic.h> + +#include "trace-utest.h" + +enum unit_tests { + RUN_NONE = 0, + RUN_TRACEFS = (1 << 0), + RUN_ALL = 0xFFFF +}; + +static void print_help(char **argv) +{ + printf("Usage: %s [OPTIONS]\n", basename(argv[0])); + printf("\t-s, --silent\tPrint test summary\n"); + printf("\t-r, --run test\tRun specific test:\n"); + printf("\t\t tracefs run libtracefs tests\n"); + printf("\t-h, --help\tPrint usage information\n"); + exit(0); +} + +int main(int argc, char **argv) +{ + CU_BasicRunMode verbose = CU_BRM_VERBOSE; + enum unit_tests tests = RUN_NONE; + + for (;;) { + int c; + int index = 0; + const char *opts = "+hsr:"; + static struct option long_options[] = { + {"silent", no_argument, NULL, 's'}, + {"run", required_argument, NULL, 'r'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + c = getopt_long (argc, argv, opts, long_options, &index); + if (c == -1) + break; + switch (c) { + case 'r': + if (strcmp(optarg, "tracefs") == 0) + tests |= RUN_TRACEFS; + else + print_help(argv); + break; + case 's': + verbose = CU_BRM_SILENT; + break; + case 'h': + default: + print_help(argv); + break; + } + } + + if (tests == RUN_NONE) + tests = RUN_ALL; + + if (CU_initialize_registry() != CUE_SUCCESS) { + printf("Test registry cannot be initialized\n"); + return -1; + } + + if (tests & RUN_TRACEFS) + test_tracefs_lib(); + + CU_basic_set_mode(verbose); + CU_basic_run_tests(); + CU_cleanup_registry(); + return 0; +} diff --git a/utest/trace-utest.h b/utest/trace-utest.h new file mode 100644 index 0000000..917c0e7 --- /dev/null +++ b/utest/trace-utest.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ +/* + * Copyright (C) 2020, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#ifndef _TRACE_UTEST_H_ +#define _TRACE_UTEST_H_ + +void test_tracefs_lib(void); + +#endif /* _TRACE_UTEST_H_ */ diff --git a/utest/tracefs-utest.c b/utest/tracefs-utest.c new file mode 100644 index 0000000..e0e3c07 --- /dev/null +++ b/utest/tracefs-utest.c @@ -0,0 +1,2385 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2020, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> +#include <dirent.h> +#include <ftw.h> +#include <libgen.h> +#include <kbuffer.h> +#include <pthread.h> + +#include <sys/mount.h> + +#include <CUnit/CUnit.h> +#include <CUnit/Basic.h> + +#include "tracefs.h" + +#define TRACEFS_SUITE "tracefs library" +#define TEST_INSTANCE_NAME "cunit_test_iter" +#define TEST_TRACE_DIR "/tmp/trace_utest.XXXXXX" +#define TEST_ARRAY_SIZE 5000 + +#define ALL_TRACERS "available_tracers" +#define CUR_TRACER "current_tracer" +#define PER_CPU "per_cpu" +#define TRACE_ON "tracing_on" +#define TRACE_CLOCK "trace_clock" + +#define SQL_1_EVENT "wakeup_1" +#define SQL_1_SQL "select sched_switch.next_pid as woke_pid, sched_waking.common_pid as waking_pid from sched_waking join sched_switch on sched_switch.next_pid = sched_waking.pid" + +#define SQL_2_EVENT "wakeup_2" +#define SQL_2_SQL "select woke.next_pid as woke_pid, wake.common_pid as waking_pid from sched_waking as wake join sched_switch as woke on woke.next_pid = wake.pid" + +#define SQL_3_EVENT "wakeup_lat" +#define SQL_3_SQL "select sched_switch.next_prio as prio, end.prev_prio as pprio, (sched.sched_waking.common_timestamp.usecs - end.TIMESTAMP_USECS) as lat from sched_waking as start join sched_switch as end on start.pid = end.next_pid" + +#define SQL_4_EVENT "wakeup_lat_2" +#define SQL_4_SQL "select start.pid, end.next_prio as prio, (end.TIMESTAMP_USECS - start.TIMESTAMP_USECS) as lat from sched_waking as start join sched_switch as end on start.pid = end.next_pid where (start.prio >= 1 && start.prio < 100) || !(start.pid >= 0 && start.pid <= 1) && end.prev_pid != 0" + +#define SQL_5_EVENT "irq_lat" +#define SQL_5_SQL "select end.common_pid as pid, (end.common_timestamp.usecs - start.common_timestamp.usecs) as irq_lat from irq_disable as start join irq_enable as end on start.common_pid = end.common_pid, start.parent_offs == end.parent_offs where start.common_pid != 0" +#define SQL_5_START "irq_disable" + +#define DEBUGFS_DEFAULT_PATH "/sys/kernel/debug" +#define TRACEFS_DEFAULT_PATH "/sys/kernel/tracing" +#define TRACEFS_DEFAULT2_PATH "/sys/kernel/debug/tracing" + +static struct tracefs_instance *test_instance; +static struct tep_handle *test_tep; +struct test_sample { + int cpu; + int value; +}; +static struct test_sample test_array[TEST_ARRAY_SIZE]; +static int test_found; +static unsigned long long last_ts; + +static int test_callback(struct tep_event *event, struct tep_record *record, + int cpu, void *context) +{ + struct tep_format_field *field; + struct test_sample *sample; + int *cpu_test = (int *)context; + int i; + + CU_TEST(last_ts <= record->ts); + last_ts = record->ts; + + if (cpu_test && *cpu_test >= 0) { + CU_TEST(*cpu_test == cpu); + } + CU_TEST(cpu == record->cpu); + + field = tep_find_field(event, "buf"); + if (field) { + sample = ((struct test_sample *)(record->data + field->offset)); + for (i = 0; i < TEST_ARRAY_SIZE; i++) { + if (test_array[i].value == sample->value && + test_array[i].cpu == cpu) { + test_array[i].value = 0; + test_found++; + break; + } + } + } + + return 0; +} + +static cpu_set_t *cpuset_save; +static cpu_set_t *cpuset; +static int cpu_size; + +static void save_affinity(void) +{ + int cpus; + + cpus = sysconf(_SC_NPROCESSORS_CONF); + cpuset_save = CPU_ALLOC(cpus); + cpuset = CPU_ALLOC(cpus); + cpu_size = CPU_ALLOC_SIZE(cpus); + CU_TEST(cpuset_save != NULL && cpuset != NULL); + CU_TEST(sched_getaffinity(0, cpu_size, cpuset_save) == 0); +} + +static void thread_affinity(void) +{ + sched_setaffinity(0, cpu_size, cpuset_save); +} + +static void reset_affinity(void) +{ + sched_setaffinity(0, cpu_size, cpuset_save); + CPU_FREE(cpuset_save); + CPU_FREE(cpuset); +} + +static void set_affinity(int cpu) +{ + CPU_ZERO_S(cpu_size, cpuset); + CPU_SET_S(cpu, cpu_size, cpuset); + CU_TEST(sched_setaffinity(0, cpu_size, cpuset) == 0); + sched_yield(); /* Force schedule */ +} + +static void test_iter_write(struct tracefs_instance *instance) +{ + char *path; + int i, fd; + int cpus; + int ret; + + cpus = sysconf(_SC_NPROCESSORS_CONF); + save_affinity(); + + path = tracefs_instance_get_file(instance, "trace_marker"); + CU_TEST(path != NULL); + fd = open(path, O_WRONLY); + tracefs_put_tracing_file(path); + CU_TEST(fd >= 0); + + for (i = 0; i < TEST_ARRAY_SIZE; i++) { + test_array[i].cpu = rand() % cpus; + test_array[i].value = random(); + if (!test_array[i].value) + test_array[i].value++; + CU_TEST(test_array[i].cpu < cpus); + set_affinity(test_array[i].cpu); + ret = write(fd, test_array + i, sizeof(struct test_sample)); + CU_TEST(ret == sizeof(struct test_sample)); + } + + reset_affinity(); + close(fd); +} + + +static void iter_raw_events_on_cpu(struct tracefs_instance *instance, int cpu) +{ + int cpus = sysconf(_SC_NPROCESSORS_CONF); + cpu_set_t *cpuset = NULL; + int cpu_size = 0; + int check = 0; + int ret; + int i; + + if (cpu >= 0) { + cpuset = CPU_ALLOC(cpus); + cpu_size = CPU_ALLOC_SIZE(cpus); + CPU_ZERO_S(cpu_size, cpuset); + CPU_SET(cpu, cpuset); + } + test_found = 0; + last_ts = 0; + test_iter_write(instance); + ret = tracefs_iterate_raw_events(test_tep, instance, cpuset, cpu_size, + test_callback, &cpu); + CU_TEST(ret == 0); + if (cpu < 0) { + CU_TEST(test_found == TEST_ARRAY_SIZE); + } else { + for (i = 0; i < TEST_ARRAY_SIZE; i++) { + if (test_array[i].cpu == cpu) { + check++; + CU_TEST(test_array[i].value == 0) + } else { + CU_TEST(test_array[i].value != 0) + } + } + CU_TEST(test_found == check); + } + + if (cpuset) + CPU_FREE(cpuset); +} + +static void test_instance_iter_raw_events(struct tracefs_instance *instance) +{ + int cpus = sysconf(_SC_NPROCESSORS_CONF); + int ret; + int i; + + ret = tracefs_iterate_raw_events(NULL, instance, NULL, 0, test_callback, NULL); + CU_TEST(ret < 0); + last_ts = 0; + ret = tracefs_iterate_raw_events(test_tep, NULL, NULL, 0, test_callback, NULL); + CU_TEST(ret == 0); + ret = tracefs_iterate_raw_events(test_tep, instance, NULL, 0, NULL, NULL); + CU_TEST(ret < 0); + + iter_raw_events_on_cpu(instance, -1); + for (i = 0; i < cpus; i++) + iter_raw_events_on_cpu(instance, i); +} + +static void test_iter_raw_events(void) +{ + test_instance_iter_raw_events(test_instance); +} + +#define RAND_STR_SIZE 20 +#define RAND_ASCII "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +static const char *get_rand_str(void) +{ + static char str[RAND_STR_SIZE]; + static char sym[] = RAND_ASCII; + struct timespec clk; + int i; + + clock_gettime(CLOCK_REALTIME, &clk); + srand(clk.tv_nsec); + for (i = 0; i < RAND_STR_SIZE; i++) + str[i] = sym[rand() % (sizeof(sym) - 1)]; + + str[RAND_STR_SIZE - 1] = 0; + return str; +} + +struct marker_find { + int data_offset; + int event_id; + int count; + int len; + void *data; +}; + +static int test_marker_callback(struct tep_event *event, struct tep_record *record, + int cpu, void *context) +{ + struct marker_find *walk = context; + + if (!walk) + return -1; + if (event->id != walk->event_id) + return 0; + if (record->size < (walk->data_offset + walk->len)) + return 0; + + if (memcmp(walk->data, record->data + walk->data_offset, walk->len) == 0) + walk->count++; + + return 0; +} + +static bool find_test_marker(struct tracefs_instance *instance, + void *data, int len, int expected, bool raw) +{ + struct tep_format_field *field; + struct tep_event *event; + struct marker_find walk; + int ret; + + if (raw) { + event = tep_find_event_by_name(test_tep, "ftrace", "raw_data"); + if (event) + field = tep_find_field(event, "id"); + + } else { + event = tep_find_event_by_name(test_tep, "ftrace", "print"); + if (event) + field = tep_find_field(event, "buf"); + } + + if (!event || !field) + return false; + + walk.data = data; + walk.len = len; + walk.count = 0; + walk.event_id = event->id; + walk.data_offset = field->offset; + ret = tracefs_iterate_raw_events(test_tep, instance, NULL, 0, + test_marker_callback, &walk); + CU_TEST(ret == 0); + + return walk.count == expected; +} + +static int marker_vprint(struct tracefs_instance *instance, char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = tracefs_vprintf(instance, fmt, ap); + va_end(ap); + + return ret; +} + +#define MARKERS_WRITE_COUNT 100 +static void test_instance_ftrace_marker(struct tracefs_instance *instance) +{ + const char *string = get_rand_str(); + unsigned int data = 0xdeadbeef; + char *str; + int i; + + CU_TEST(tracefs_print_init(instance) == 0); + tracefs_print_close(instance); + + CU_TEST(tracefs_binary_init(instance) == 0); + tracefs_binary_close(instance); + + for (i = 0; i < MARKERS_WRITE_COUNT; i++) { + CU_TEST(tracefs_binary_write(instance, &data, sizeof(data)) == 0); + } + CU_TEST(find_test_marker(instance, &data, sizeof(data), MARKERS_WRITE_COUNT, true)); + + for (i = 0; i < MARKERS_WRITE_COUNT; i++) { + CU_TEST(tracefs_printf(instance, "Test marker: %s 0x%X", string, data) == 0); + } + asprintf(&str, "Test marker: %s 0x%X", string, data); + CU_TEST(find_test_marker(instance, str, strlen(str), MARKERS_WRITE_COUNT, false)); + free(str); + + for (i = 0; i < MARKERS_WRITE_COUNT; i++) { + CU_TEST(marker_vprint(instance, "Test marker V: %s 0x%X", string, data) == 0); + } + asprintf(&str, "Test marker V: %s 0x%X", string, data); + CU_TEST(find_test_marker(instance, str, strlen(str), MARKERS_WRITE_COUNT, false)); + free(str); + + tracefs_print_close(instance); + tracefs_binary_close(instance); +} + +static void test_ftrace_marker(void) +{ + test_instance_ftrace_marker(test_instance); +} + +static void test_instance_trace_sql(struct tracefs_instance *instance) +{ + struct tracefs_synth *synth; + struct trace_seq seq; + struct tep_handle *tep; + struct tep_event *event; + int ret; + + tep = tracefs_local_events(NULL); + CU_TEST(tep != NULL); + + trace_seq_init(&seq); + + synth = tracefs_sql(tep, SQL_1_EVENT, SQL_1_SQL, NULL); + CU_TEST(synth != NULL); + ret = tracefs_synth_echo_cmd(&seq, synth); + CU_TEST(ret == 0); + tracefs_synth_free(synth); + trace_seq_reset(&seq); + + synth = tracefs_sql(tep, SQL_2_EVENT, SQL_2_SQL, NULL); + CU_TEST(synth != NULL); + ret = tracefs_synth_echo_cmd(&seq, synth); + CU_TEST(ret == 0); + tracefs_synth_free(synth); + trace_seq_reset(&seq); + + synth = tracefs_sql(tep, SQL_3_EVENT, SQL_3_SQL, NULL); + CU_TEST(synth != NULL); + ret = tracefs_synth_echo_cmd(&seq, synth); + CU_TEST(ret == 0); + tracefs_synth_free(synth); + trace_seq_reset(&seq); + + synth = tracefs_sql(tep, SQL_4_EVENT, SQL_4_SQL, NULL); + CU_TEST(synth != NULL); + ret = tracefs_synth_echo_cmd(&seq, synth); + CU_TEST(ret == 0); + tracefs_synth_free(synth); + trace_seq_reset(&seq); + + event = tep_find_event_by_name(tep, NULL, SQL_5_START); + if (event) { + synth = tracefs_sql(tep, SQL_5_EVENT, SQL_5_SQL, NULL); + CU_TEST(synth != NULL); + ret = tracefs_synth_echo_cmd(&seq, synth); + CU_TEST(ret == 0); + tracefs_synth_free(synth); + trace_seq_reset(&seq); + } + + tep_free(tep); + trace_seq_destroy(&seq); +} + +static void test_trace_sql(void) +{ + test_instance_trace_sql(test_instance); +} + +struct test_cpu_data { + struct tracefs_instance *instance; + struct tracefs_cpu *tcpu; + struct kbuffer *kbuf; + struct tep_handle *tep; + unsigned long long missed_events; + void *buf; + int events_per_buf; + int bufsize; + int data_size; + int this_pid; + int fd; + bool done; +}; + +static void cleanup_trace_cpu(struct test_cpu_data *data) +{ + close(data->fd); + tep_free(data->tep); + tracefs_cpu_close(data->tcpu); + free(data->buf); + kbuffer_free(data->kbuf); +} + +#define EVENT_SYSTEM "syscalls" +#define EVENT_NAME "sys_enter_getppid" + +static int setup_trace_cpu(struct tracefs_instance *instance, struct test_cpu_data *data) +{ + struct tep_format_field **fields; + struct tep_event *event; + char tmpfile[] = "/tmp/utest-libtracefsXXXXXX"; + int max = 0; + int ret; + int i; + + /* Make sure tracing is on */ + tracefs_trace_on(instance); + + memset (data, 0, sizeof(*data)); + + data->instance = instance; + + data->fd = mkstemp(tmpfile); + CU_TEST(data->fd >= 0); + unlink(tmpfile); + if (data->fd < 0) + return -1; + + data->tep = tracefs_local_events(NULL); + CU_TEST(data->tep != NULL); + if (!data->tep) + goto fail; + + data->tcpu = tracefs_cpu_open(instance, 0, true); + CU_TEST(data->tcpu != NULL); + if (!data->tcpu) + goto fail; + + data->bufsize = tracefs_cpu_read_size(data->tcpu); + + data->buf = calloc(1, data->bufsize); + CU_TEST(data->buf != NULL); + if (!data->buf) + goto fail; + + data->kbuf = kbuffer_alloc(sizeof(long) == 8, !tep_is_bigendian()); + CU_TEST(data->kbuf != NULL); + if (!data->kbuf) + goto fail; + + data->data_size = data->bufsize - kbuffer_start_of_data(data->kbuf); + + tracefs_instance_file_clear(instance, "trace"); + + event = tep_find_event_by_name(data->tep, EVENT_SYSTEM, EVENT_NAME); + CU_TEST(event != NULL); + if (!event) + goto fail; + + fields = tep_event_fields(event); + CU_TEST(fields != NULL); + if (!fields) + goto fail; + + for (i = 0; fields[i]; i++) { + int end = fields[i]->offset + fields[i]->size; + if (end > max) + max = end; + } + free(fields); + + CU_TEST(max != 0); + if (!max) + goto fail; + + data->events_per_buf = data->data_size / max; + + data->this_pid = getpid(); + ret = tracefs_event_enable(instance, EVENT_SYSTEM, EVENT_NAME); + CU_TEST(ret == 0); + if (ret) + goto fail; + + + save_affinity(); + set_affinity(0); + + return 0; + fail: + cleanup_trace_cpu(data); + return -1; +} + +static void shutdown_trace_cpu(struct test_cpu_data *data) +{ + struct tracefs_instance *instance = data->instance; + int ret; + + reset_affinity(); + + ret = tracefs_event_disable(instance, EVENT_SYSTEM, EVENT_NAME); + CU_TEST(ret == 0); + + cleanup_trace_cpu(data); +} + +static void call_getppid(int cnt) +{ + int i; + + for (i = 0; i < cnt; i++) + getppid(); +} + +static void test_cpu_read(struct test_cpu_data *data, int expect) +{ + struct tracefs_cpu *tcpu = data->tcpu; + struct kbuffer *kbuf = data->kbuf; + struct tep_record record; + void *buf = data->buf; + unsigned long long ts; + bool first = true; + int pid; + int ret; + int cnt = 0; + + call_getppid(expect); + + for (;;) { + ret = tracefs_cpu_read(tcpu, buf, false); + CU_TEST(ret > 0 || !first); + if (ret <= 0) + break; + first = false; + ret = kbuffer_load_subbuffer(kbuf, buf); + CU_TEST(ret == 0); + for (;;) { + record.data = kbuffer_read_event(kbuf, &ts); + if (!record.data) + break; + record.ts = ts; + pid = tep_data_pid(data->tep, &record); + if (pid == data->this_pid) + cnt++; + kbuffer_next_event(kbuf, NULL); + } + } + CU_TEST(cnt == expect); +} + +static void test_instance_trace_cpu_read(struct tracefs_instance *instance) +{ + struct test_cpu_data data; + + if (setup_trace_cpu(instance, &data)) + return; + + test_cpu_read(&data, 1); + test_cpu_read(&data, data.events_per_buf / 2); + test_cpu_read(&data, data.events_per_buf); + test_cpu_read(&data, data.events_per_buf + 1); + test_cpu_read(&data, data.events_per_buf * 50); + + shutdown_trace_cpu(&data); +} + +static void test_trace_cpu_read(void) +{ + test_instance_trace_cpu_read(NULL); + test_instance_trace_cpu_read(test_instance); +} + +struct follow_data { + struct tep_event *sched_switch; + struct tep_event *sched_waking; + struct tep_event *function; + int missed; +}; + +static int switch_callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct follow_data *fdata = data; + + CU_TEST(cpu == record->cpu); + CU_TEST(event->id == fdata->sched_switch->id); + return 0; +} + +static int waking_callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct follow_data *fdata = data; + + CU_TEST(cpu == record->cpu); + CU_TEST(event->id == fdata->sched_waking->id); + return 0; +} + +static int function_callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct follow_data *fdata = data; + + CU_TEST(cpu == record->cpu); + CU_TEST(event->id == fdata->function->id); + return 0; +} + +static int missed_callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct follow_data *fdata = data; + + fdata->missed = record->missed_events; + return 0; +} + +static int all_callback(struct tep_event *event, struct tep_record *record, + int cpu, void *data) +{ + struct follow_data *fdata = data; + + CU_TEST(fdata->missed == record->missed_events); + fdata->missed = 0; + return 0; +} + +static void *stop_thread(void *arg) +{ + struct tracefs_instance *instance = arg; + + sleep(1); + tracefs_iterate_stop(instance); + return NULL; +} + +static void test_instance_follow_events(struct tracefs_instance *instance) +{ + struct follow_data fdata; + struct tep_handle *tep; + pthread_t thread; + int ret; + + memset(&fdata, 0, sizeof(fdata)); + + tep = tracefs_local_events(NULL); + CU_TEST(tep != NULL); + if (!tep) + return; + + fdata.sched_switch = tep_find_event_by_name(tep, "sched", "sched_switch"); + CU_TEST(fdata.sched_switch != NULL); + if (!fdata.sched_switch) + return; + + fdata.sched_waking = tep_find_event_by_name(tep, "sched", "sched_waking"); + CU_TEST(fdata.sched_waking != NULL); + if (!fdata.sched_waking) + return; + + fdata.function = tep_find_event_by_name(tep, "ftrace", "function"); + CU_TEST(fdata.function != NULL); + if (!fdata.function) + return; + + ret = tracefs_follow_event(tep, instance, "sched", "sched_switch", + switch_callback, &fdata); + CU_TEST(ret == 0); + + ret = tracefs_follow_event(tep, instance, "sched", "sched_waking", + waking_callback, &fdata); + CU_TEST(ret == 0); + + ret = tracefs_follow_event(tep, instance, "ftrace", "function", + function_callback, &fdata); + CU_TEST(ret == 0); + + ret = tracefs_follow_missed_events(instance, missed_callback, &fdata); + CU_TEST(ret == 0); + + ret = tracefs_event_enable(instance, "sched", "sched_switch"); + CU_TEST(ret == 0); + + ret = tracefs_event_enable(instance, "sched", "sched_waking"); + CU_TEST(ret == 0); + + ret = tracefs_tracer_set(instance, TRACEFS_TRACER_FUNCTION); + CU_TEST(ret == 0); + + pthread_create(&thread, NULL, stop_thread, instance); + + ret = tracefs_iterate_raw_events(tep, instance, NULL, 0, all_callback, &fdata); + CU_TEST(ret == 0); + + pthread_join(thread, NULL); + + tracefs_tracer_clear(instance); + tracefs_event_disable(instance, NULL, NULL); +} + +static void test_follow_events(void) +{ + test_instance_follow_events(NULL); + test_instance_follow_events(test_instance); +} + +extern char *find_tracing_dir(bool debugfs, bool mount); +static void test_mounting(void) +{ + const char *tracing_dir; + const char *debug_dir; + struct stat st; + char *save_tracing = NULL; + char *save_debug = NULL; + char *path; + char *dir; + int ret; + + /* First, unmount all instances of debugfs */ + do { + dir = find_tracing_dir(true, false); + if (dir) { + ret = umount(dir); + CU_TEST(ret == 0); + if (ret < 0) + return; + /* Save the first instance that's not /sys/kernel/debug */ + if (!save_debug && strcmp(dir, DEBUGFS_DEFAULT_PATH) != 0) + save_debug = dir; + else + free(dir); + } + } while (dir); + + /* Next, unmount all instances of tracefs */ + do { + dir = find_tracing_dir(false, false); + if (dir) { + ret = umount(dir); + CU_TEST(ret == 0); + if (ret < 0) + return; + /* Save the first instance that's not in /sys/kernel/ */ + if (!save_tracing && strncmp(dir, "/sys/kernel/", 12) != 0) + save_tracing = dir; + else + free(dir); + } + } while (dir); + + /* Mount first the tracing dir (which should mount at /sys/kernel/tracing */ + tracing_dir = tracefs_tracing_dir(); + CU_TEST(tracing_dir != NULL); + if (tracing_dir != NULL) { + CU_TEST(strcmp(tracing_dir, TRACEFS_DEFAULT_PATH) == 0 || + strcmp(tracing_dir, TRACEFS_DEFAULT2_PATH) == 0); + if (strncmp(tracing_dir, "/sys/kernel/", 12) != 0) + printf("Tracing directory mounted at '%s'\n", + tracing_dir); + + /* Make sure the directory has content.*/ + asprintf(&path, "%s/trace", tracing_dir); + CU_TEST(stat(path, &st) == 0); + free(path); + } + + /* Now mount debugfs dir, which should mount at /sys/kernel/debug */ + debug_dir = tracefs_debug_dir(); + CU_TEST(debug_dir != NULL); + if (debug_dir != NULL) { + CU_TEST(strcmp(debug_dir, DEBUGFS_DEFAULT_PATH) == 0); + if (strcmp(debug_dir, DEBUGFS_DEFAULT_PATH) != 0) + printf("debug directory mounted at '%s'\n", + debug_dir); + + /* Make sure the directory has content.*/ + asprintf(&path, "%s/tracing", debug_dir); + CU_TEST(stat(path, &st) == 0); + free(path); + } + + if (save_debug) + mount("debugfs", save_debug, "debugfs", 0, NULL); + + if (save_tracing && + (!save_debug || strncmp(save_debug, save_tracing, strlen(save_debug)) != 0)) + mount("tracefs", save_tracing, "tracefs", 0, NULL); + + free(save_debug); + free(save_tracing); +} + +static int read_trace_cpu_file(struct test_cpu_data *data) +{ + unsigned long long ts; + struct tep_record record; + struct kbuffer *kbuf = data->kbuf; + void *buf = data->buf; + bool first = true; + int bufsize = data->bufsize; + int fd = data->fd; + int missed; + int pid; + int ret; + int cnt = 0; + + ret = lseek64(fd, 0, SEEK_SET); + CU_TEST(ret == 0); + if (ret) + return -1; + + for (;;) { + ret = read(fd, buf, bufsize); + CU_TEST(ret > 0 || !first); + if (ret <= 0) + break; + first = false; + + ret = kbuffer_load_subbuffer(kbuf, buf); + CU_TEST(ret == 0); + missed = kbuffer_missed_events(kbuf); + if (missed) + printf("missed events %d\n", missed); + for (;;) { + record.data = kbuffer_read_event(kbuf, &ts); + if (!record.data) + break; + record.ts = ts; + pid = tep_data_pid(data->tep, &record); + if (pid == data->this_pid) + cnt++; + kbuffer_next_event(kbuf, NULL); + } + } + return ret == 0 ? cnt : ret; +} + +static void *trace_cpu_thread(void *arg) +{ + struct test_cpu_data *data = arg; + struct tracefs_cpu *tcpu = data->tcpu; + int fd = data->fd; + long ret = 0; + + thread_affinity(); + + while (!data->done && ret >= 0) { + ret = tracefs_cpu_write(tcpu, fd, false); + if (ret < 0 && errno == EAGAIN) + ret = 0; + } + if (ret >= 0 || errno == EAGAIN) { + do { + ret = tracefs_cpu_flush_write(tcpu, fd); + } while (ret > 0); + } + + return (void *)ret; +} + +static void test_cpu_pipe(struct test_cpu_data *data, int expect) +{ + pthread_t thread; + void *retval; + long ret; + int cnt; + + tracefs_instance_file_clear(data->instance, "trace"); + ftruncate(data->fd, 0); + + data->done = false; + + pthread_create(&thread, NULL, trace_cpu_thread, data); + sleep(1); + + call_getppid(expect); + + data->done = true; + tracefs_cpu_stop(data->tcpu); + pthread_join(thread, &retval); + ret = (long)retval; + CU_TEST(ret >= 0); + + cnt = read_trace_cpu_file(data); + + CU_TEST(cnt == expect); +} + +static void test_instance_trace_cpu_pipe(struct tracefs_instance *instance) +{ + struct test_cpu_data data; + + if (setup_trace_cpu(instance, &data)) + return; + + test_cpu_pipe(&data, 1); + test_cpu_pipe(&data, data.events_per_buf / 2); + test_cpu_pipe(&data, data.events_per_buf); + test_cpu_pipe(&data, data.events_per_buf + 1); + test_cpu_pipe(&data, data.events_per_buf * 1000); + + shutdown_trace_cpu(&data); +} + +static void test_trace_cpu_pipe(void) +{ + test_instance_trace_cpu_pipe(NULL); + test_instance_trace_cpu_pipe(test_instance); +} + +static struct tracefs_dynevent **get_dynevents_check(enum tracefs_dynevent_type types, int count) +{ + struct tracefs_dynevent **devents; + int i; + + devents = tracefs_dynevent_get_all(types, NULL); + if (count) { + CU_TEST(devents != NULL); + if (!devents) + return NULL; + i = 0; + while (devents[i]) + i++; + CU_TEST(i == count); + } else { + CU_TEST(devents == NULL); + } + + return devents; +} + + +struct test_synth { + char *name; + char *start_system; + char *start_event; + char *end_system; + char *end_event; + char *start_match_field; + char *end_match_field; + char *match_name; +}; + +static void test_synth_compare(struct test_synth *synth, struct tracefs_dynevent **devents) +{ + enum tracefs_dynevent_type stype; + char *format; + char *event; + int i; + + for (i = 0; devents && devents[i]; i++) { + stype = tracefs_dynevent_info(devents[i], NULL, + &event, NULL, NULL, &format); + CU_TEST(stype == TRACEFS_DYNEVENT_SYNTH); + CU_TEST(strcmp(event, synth[i].name) == 0); + if (synth[i].match_name) { + CU_TEST(strstr(format, synth[i].match_name) != NULL); + } + free(event); + free(format); + } + CU_TEST(devents == NULL || devents[i] == NULL); +} + +static void test_instance_synthetic(struct tracefs_instance *instance) +{ + struct test_synth sevents[] = { + {"synth_1", "sched", "sched_waking", "sched", "sched_switch", "pid", "next_pid", "pid_match"}, + {"synth_2", "syscalls", "sys_enter_openat2", "syscalls", "sys_exit_openat2", "__syscall_nr", "__syscall_nr", "nr_match"}, + }; + int sevents_count = sizeof(sevents) / sizeof((sevents)[0]); + struct tracefs_dynevent **devents; + struct tracefs_synth **synth; + struct tep_handle *tep; + int ret; + int i; + + synth = calloc(sevents_count + 1, sizeof(*synth)); + + tep = tracefs_local_events(NULL); + CU_TEST(tep != NULL); + + /* kprobes APIs */ + ret = tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_SYNTH, true); + CU_TEST(ret == 0); + get_dynevents_check(TRACEFS_DYNEVENT_SYNTH, 0); + + for (i = 0; i < sevents_count; i++) { + synth[i] = tracefs_synth_alloc(tep, sevents[i].name, + sevents[i].start_system, sevents[i].start_event, + sevents[i].end_system, sevents[i].end_event, + sevents[i].start_match_field, sevents[i].end_match_field, + sevents[i].match_name); + CU_TEST(synth[i] != NULL); + } + + get_dynevents_check(TRACEFS_DYNEVENT_SYNTH, 0); + + for (i = 0; i < sevents_count; i++) { + ret = tracefs_synth_create(synth[i]); + CU_TEST(ret == 0); + } + + devents = get_dynevents_check(TRACEFS_DYNEVENT_SYNTH, sevents_count); + CU_TEST(devents != NULL); + test_synth_compare(sevents, devents); + tracefs_dynevent_list_free(devents); + + for (i = 0; i < sevents_count; i++) { + ret = tracefs_synth_destroy(synth[i]); + CU_TEST(ret == 0); + } + + get_dynevents_check(TRACEFS_DYNEVENT_SYNTH, 0); + + for (i = 0; i < sevents_count; i++) + tracefs_synth_free(synth[i]); + + tep_free(tep); + free(synth); +} + +static void test_synthetic(void) +{ + test_instance_synthetic(test_instance); +} + +static void test_trace_file(void) +{ + const char *tmp = get_rand_str(); + const char *tdir; + struct stat st; + char *file; + + tdir = tracefs_tracing_dir(); + CU_TEST(tdir != NULL); + CU_TEST(stat(tdir, &st) == 0); + CU_TEST(S_ISDIR(st.st_mode)); + + file = tracefs_get_tracing_file(NULL); + CU_TEST(file == NULL); + file = tracefs_get_tracing_file(tmp); + CU_TEST(file != NULL); + CU_TEST(stat(file, &st) != 0); + tracefs_put_tracing_file(file); + + file = tracefs_get_tracing_file("trace"); + CU_TEST(file != NULL); + CU_TEST(stat(file, &st) == 0); + tracefs_put_tracing_file(file); +} + +static void test_instance_file_read(struct tracefs_instance *inst, const char *fname) +{ + const char *tdir = tracefs_tracing_dir(); + char buf[BUFSIZ]; + char *fpath; + char *file; + size_t fsize = 0; + int size = 0; + int fd; + + if (inst) { + CU_TEST(asprintf(&fpath, "%s/instances/%s/%s", + tdir, tracefs_instance_get_name(inst), fname) > 0); + } else { + CU_TEST(asprintf(&fpath, "%s/%s", tdir, fname) > 0); + } + + memset(buf, 0, BUFSIZ); + fd = open(fpath, O_RDONLY); + CU_TEST(fd >= 0); + fsize = read(fd, buf, BUFSIZ); + CU_TEST(fsize >= 0); + close(fd); + buf[BUFSIZ - 1] = 0; + + file = tracefs_instance_file_read(inst, fname, &size); + CU_TEST(file != NULL); + CU_TEST(size == fsize); + CU_TEST(strcmp(file, buf) == 0); + + free(fpath); + free(file); +} + +struct probe_test { + enum tracefs_dynevent_type type; + char *prefix; + char *system; + char *event; + char *address; + char *format; +}; + +static bool check_probes(struct probe_test *probes, int count, + struct tracefs_dynevent **devents, bool in_system, + struct tracefs_instance *instance, struct tep_handle *tep) +{ + enum tracefs_dynevent_type type; + struct tep_event *tevent; + char *ename; + char *address; + char *event; + char *system; + char *format; + char *prefix; + int found = 0; + int ret; + int i, j; + + for (i = 0; devents && devents[i]; i++) { + type = tracefs_dynevent_info(devents[i], &system, + &event, &prefix, &address, &format); + for (j = 0; j < count; j++) { + if (type != probes[j].type) + continue; + if (probes[j].event) + ename = probes[j].event; + else + ename = probes[j].address; + if (strcmp(ename, event)) + continue; + if (probes[j].system) { + CU_TEST(strcmp(probes[j].system, system) == 0); + } + CU_TEST(strcmp(probes[j].address, address) == 0); + if (probes[j].format) { + CU_TEST(strcmp(probes[j].format, format) == 0); + } + if (probes[j].prefix) { + CU_TEST(strcmp(probes[j].prefix, prefix) == 0); + } + ret = tracefs_event_enable(instance, system, event); + if (in_system) { + CU_TEST(ret == 0); + } else { + CU_TEST(ret != 0); + } + ret = tracefs_event_disable(instance, system, event); + if (in_system) { + CU_TEST(ret == 0); + } else { + CU_TEST(ret != 0); + } + + tevent = tracefs_dynevent_get_event(tep, devents[i]); + if (in_system) { + CU_TEST(tevent != NULL); + if (tevent) { + CU_TEST(strcmp(tevent->name, event) == 0); + CU_TEST(strcmp(tevent->system, system) == 0); + } + } else { + CU_TEST(tevent == NULL); + } + + found++; + break; + } + free(system); + free(event); + free(prefix); + free(address); + free(format); + } + + CU_TEST(found == count); + if (found != count) + return false; + + return true; +} + +static void test_kprobes_instance(struct tracefs_instance *instance) +{ + struct probe_test ktests[] = { + { TRACEFS_DYNEVENT_KPROBE, "p", NULL, "mkdir", "do_mkdirat", "path=+u0($arg2):ustring" }, + { TRACEFS_DYNEVENT_KPROBE, "p", NULL, "close", "close_fd", NULL }, + { TRACEFS_DYNEVENT_KPROBE, "p", "ptest", "open2", "do_sys_openat2", + "file=+u0($arg2):ustring flags=+0($arg3):x64" }, + }; + struct probe_test kretests[] = { + { TRACEFS_DYNEVENT_KRETPROBE, NULL, NULL, "retopen", "do_sys_openat2", "ret=$retval" }, + { TRACEFS_DYNEVENT_KRETPROBE, NULL, NULL, NULL, "do_sys_open", "ret=$retval" }, + }; + int kretprobe_count = sizeof(kretests) / sizeof((kretests)[0]); + int kprobe_count = sizeof(ktests) / sizeof((ktests)[0]); + struct tracefs_dynevent **dkretprobe; + struct tracefs_dynevent **dkprobe; + struct tracefs_dynevent **devents; + struct tep_handle *tep; + char *tmp; + int ret; + int i; + + tep = tep_alloc(); + CU_TEST(tep != NULL); + + dkprobe = calloc(kprobe_count + 1, sizeof(*dkprobe)); + dkretprobe = calloc(kretprobe_count + 1, sizeof(*dkretprobe)); + + /* Invalid parameters */ + CU_TEST(tracefs_kprobe_alloc("test", NULL, NULL, "test") == NULL); + CU_TEST(tracefs_kretprobe_alloc("test", NULL, NULL, "test", 0) == NULL); + CU_TEST(tracefs_dynevent_create(NULL) != 0); + CU_TEST(tracefs_dynevent_info(NULL, &tmp, &tmp, &tmp, &tmp, &tmp) == TRACEFS_DYNEVENT_UNKNOWN); + CU_TEST(tracefs_kprobe_raw("test", "test", NULL, "test") != 0); + CU_TEST(tracefs_kretprobe_raw("test", "test", NULL, "test") != 0); + + /* kprobes APIs */ + ret = tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, true); + CU_TEST(ret == 0); + get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, 0); + + for (i = 0; i < kprobe_count; i++) { + dkprobe[i] = tracefs_kprobe_alloc(ktests[i].system, ktests[i].event, + ktests[i].address, ktests[i].format); + CU_TEST(dkprobe[i] != NULL); + } + dkprobe[i] = NULL; + get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, 0); + CU_TEST(check_probes(ktests, kprobe_count, dkprobe, false, instance, tep)); + + for (i = 0; i < kretprobe_count; i++) { + dkretprobe[i] = tracefs_kretprobe_alloc(kretests[i].system, kretests[i].event, + kretests[i].address, kretests[i].format, 0); + CU_TEST(dkretprobe[i] != NULL); + } + dkretprobe[i] = NULL; + get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, 0); + CU_TEST(check_probes(kretests, kretprobe_count, dkretprobe, false, instance, tep)); + + for (i = 0; i < kprobe_count; i++) { + CU_TEST(tracefs_dynevent_create(dkprobe[i]) == 0); + } + devents = get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, + kprobe_count); + CU_TEST(check_probes(ktests, kprobe_count, devents, true, instance, tep)); + CU_TEST(check_probes(kretests, kretprobe_count, dkretprobe, false, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + for (i = 0; i < kretprobe_count; i++) { + CU_TEST(tracefs_dynevent_create(dkretprobe[i]) == 0); + } + devents = get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, + kprobe_count + kretprobe_count); + CU_TEST(check_probes(ktests, kprobe_count, devents, true, instance, tep)); + CU_TEST(check_probes(kretests, kretprobe_count, devents, true, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + for (i = 0; i < kretprobe_count; i++) { + CU_TEST(tracefs_dynevent_destroy(dkretprobe[i], false) == 0); + } + devents = get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, + kprobe_count); + CU_TEST(check_probes(ktests, kprobe_count, devents, true, instance, tep)); + CU_TEST(check_probes(kretests, kretprobe_count, dkretprobe, false, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + for (i = 0; i < kprobe_count; i++) { + CU_TEST(tracefs_dynevent_destroy(dkprobe[i], false) == 0); + } + get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, 0); + CU_TEST(check_probes(ktests, kprobe_count, dkprobe, false, instance, tep)); + CU_TEST(check_probes(kretests, kretprobe_count, dkretprobe, false, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + for (i = 0; i < kprobe_count; i++) + tracefs_dynevent_free(dkprobe[i]); + for (i = 0; i < kretprobe_count; i++) + tracefs_dynevent_free(dkretprobe[i]); + + /* kprobes raw APIs */ + ret = tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, true); + CU_TEST(ret == 0); + get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, 0); + + for (i = 0; i < kprobe_count; i++) { + ret = tracefs_kprobe_raw(ktests[i].system, ktests[i].event, + ktests[i].address, ktests[i].format); + CU_TEST(ret == 0); + } + + devents = get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, kprobe_count); + CU_TEST(check_probes(ktests, kprobe_count, devents, true, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + for (i = 0; i < kretprobe_count; i++) { + ret = tracefs_kretprobe_raw(kretests[i].system, kretests[i].event, + kretests[i].address, kretests[i].format); + CU_TEST(ret == 0); + } + + devents = get_dynevents_check(TRACEFS_DYNEVENT_KPROBE, kprobe_count); + CU_TEST(check_probes(ktests, kprobe_count, devents, true, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + devents = get_dynevents_check(TRACEFS_DYNEVENT_KRETPROBE, kretprobe_count); + CU_TEST(check_probes(kretests, kretprobe_count, devents, true, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + devents = get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, + kprobe_count + kretprobe_count); + CU_TEST(check_probes(ktests, kprobe_count, devents, true, instance, tep)); + CU_TEST(check_probes(kretests, kretprobe_count, devents, true, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + ret = tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, true); + CU_TEST(ret == 0); + get_dynevents_check(TRACEFS_DYNEVENT_KPROBE | TRACEFS_DYNEVENT_KRETPROBE, 0); + free(dkretprobe); + free(dkprobe); + tep_free(tep); +} + +static void test_kprobes(void) +{ + test_kprobes_instance(test_instance); +} + +static void test_eprobes_instance(struct tracefs_instance *instance) +{ + struct probe_test etests[] = { + { TRACEFS_DYNEVENT_EPROBE, "e", NULL, "sopen_in", "syscalls.sys_enter_openat", + "file=+0($filename):ustring" }, + { TRACEFS_DYNEVENT_EPROBE, "e", "etest", "sopen_out", "syscalls.sys_exit_openat", + "res=$ret:u64" }, + }; + int count = sizeof(etests) / sizeof((etests)[0]); + struct tracefs_dynevent **deprobes; + struct tracefs_dynevent **devents; + struct tep_handle *tep; + char *tsys, *tevent; + char *tmp, *sav; + int ret; + int i; + + tep = tep_alloc(); + CU_TEST(tep != NULL); + + deprobes = calloc(count + 1, sizeof(*deprobes)); + + /* Invalid parameters */ + CU_TEST(tracefs_eprobe_alloc("test", NULL, "test", "test", "test") == NULL); + CU_TEST(tracefs_eprobe_alloc("test", "test", NULL, "test", "test") == NULL); + CU_TEST(tracefs_eprobe_alloc("test", "test", "test", NULL, "test") == NULL); + + ret = tracefs_dynevent_destroy_all(TRACEFS_DYNEVENT_EPROBE, true); + CU_TEST(ret == 0); + get_dynevents_check(TRACEFS_DYNEVENT_EPROBE, 0); + + for (i = 0; i < count; i++) { + tmp = strdup(etests[i].address); + tsys = strtok_r(tmp, "./", &sav); + tevent = strtok_r(NULL, "", &sav); + deprobes[i] = tracefs_eprobe_alloc(etests[i].system, etests[i].event, + tsys, tevent, etests[i].format); + free(tmp); + CU_TEST(deprobes[i] != NULL); + } + deprobes[i] = NULL; + + get_dynevents_check(TRACEFS_DYNEVENT_EPROBE, 0); + CU_TEST(check_probes(etests, count, deprobes, false, instance, tep)); + + for (i = 0; i < count; i++) { + CU_TEST(tracefs_dynevent_create(deprobes[i]) == 0); + } + + devents = get_dynevents_check(TRACEFS_DYNEVENT_EPROBE, count); + CU_TEST(check_probes(etests, count, devents, true, instance, tep)); + tracefs_dynevent_list_free(devents); + devents = NULL; + + for (i = 0; i < count; i++) { + CU_TEST(tracefs_dynevent_destroy(deprobes[i], false) == 0); + } + get_dynevents_check(TRACEFS_DYNEVENT_EPROBE, 0); + CU_TEST(check_probes(etests, count, deprobes, false, instance, tep)); + + for (i = 0; i < count; i++) + tracefs_dynevent_free(deprobes[i]); + + free(deprobes); + tep_free(tep); +} + +static void test_eprobes(void) +{ + test_eprobes_instance(test_instance); +} + +#define FOFFSET 1000ll +static void test_uprobes_instance(struct tracefs_instance *instance) +{ + struct probe_test utests[] = { + { TRACEFS_DYNEVENT_UPROBE, "p", "utest", "utest_u", NULL, "arg1=$stack2" }, + { TRACEFS_DYNEVENT_URETPROBE, "r", "utest", "utest_r", NULL, "arg1=$retval" }, + }; + int count = sizeof(utests) / sizeof((utests)[0]); + struct tracefs_dynevent **duprobes; + struct tracefs_dynevent **duvents; + char self[PATH_MAX] = { 0 }; + struct tep_handle *tep; + char *target = NULL; + int i; + + tep = tep_alloc(); + CU_TEST(tep != NULL); + + duprobes = calloc(count + 1, sizeof(*duvents)); + CU_TEST(duprobes != NULL); + CU_TEST(readlink("/proc/self/exe", self, sizeof(self)) > 0); + CU_TEST(asprintf(&target, "%s:0x%0*llx", self, (int)(sizeof(void *) * 2), FOFFSET) > 0); + + for (i = 0; i < count; i++) + utests[i].address = target; + + /* Invalid parameters */ + CU_TEST(tracefs_uprobe_alloc(NULL, NULL, self, 0, NULL) == NULL); + CU_TEST(tracefs_uprobe_alloc(NULL, "test", NULL, 0, NULL) == NULL); + CU_TEST(tracefs_uretprobe_alloc(NULL, NULL, self, 0, NULL) == NULL); + CU_TEST(tracefs_uretprobe_alloc(NULL, "test", NULL, 0, NULL) == NULL); + + for (i = 0; i < count; i++) { + if (utests[i].type == TRACEFS_DYNEVENT_UPROBE) + duprobes[i] = tracefs_uprobe_alloc(utests[i].system, utests[i].event, + self, FOFFSET, utests[i].format); + else + duprobes[i] = tracefs_uretprobe_alloc(utests[i].system, utests[i].event, + self, FOFFSET, utests[i].format); + CU_TEST(duprobes[i] != NULL); + } + duprobes[i] = NULL; + + get_dynevents_check(TRACEFS_DYNEVENT_UPROBE | TRACEFS_DYNEVENT_URETPROBE, 0); + CU_TEST(check_probes(utests, count, duprobes, false, instance, tep)); + + for (i = 0; i < count; i++) { + CU_TEST(tracefs_dynevent_create(duprobes[i]) == 0); + } + + duvents = get_dynevents_check(TRACEFS_DYNEVENT_UPROBE | TRACEFS_DYNEVENT_URETPROBE, count); + CU_TEST(check_probes(utests, count, duvents, true, instance, tep)); + tracefs_dynevent_list_free(duvents); + + for (i = 0; i < count; i++) { + CU_TEST(tracefs_dynevent_destroy(duprobes[i], false) == 0); + } + get_dynevents_check(TRACEFS_DYNEVENT_UPROBE | TRACEFS_DYNEVENT_URETPROBE, 0); + CU_TEST(check_probes(utests, count, duprobes, false, instance, tep)); + + for (i = 0; i < count; i++) + tracefs_dynevent_free(duprobes[i]); + + free(duprobes); + free(target); + tep_free(tep); +} + +static void test_uprobes(void) +{ + test_uprobes_instance(test_instance); +} + +static void test_instance_file(void) +{ + struct tracefs_instance *instance = NULL; + struct tracefs_instance *second = NULL; + const char *name = get_rand_str(); + const char *inst_name = NULL; + const char *tdir; + char *inst_file; + char *inst_dir; + struct stat st; + char *file1; + char *file2; + char *tracer; + char *fname; + int size; + int ret; + + tdir = tracefs_tracing_dir(); + CU_TEST(tdir != NULL); + CU_TEST(asprintf(&inst_dir, "%s/instances/%s", tdir, name) > 0); + CU_TEST(stat(inst_dir, &st) != 0); + + CU_TEST(tracefs_instance_exists(name) == false); + instance = tracefs_instance_create(name); + CU_TEST(instance != NULL); + CU_TEST(tracefs_instance_is_new(instance)); + second = tracefs_instance_create(name); + CU_TEST(second != NULL); + CU_TEST(!tracefs_instance_is_new(second)); + tracefs_instance_free(second); + CU_TEST(tracefs_instance_exists(name) == true); + CU_TEST(stat(inst_dir, &st) == 0); + CU_TEST(S_ISDIR(st.st_mode)); + inst_name = tracefs_instance_get_name(instance); + CU_TEST(inst_name != NULL); + CU_TEST(strcmp(inst_name, name) == 0); + + fname = tracefs_instance_get_dir(NULL); + CU_TEST(fname != NULL); + CU_TEST(strcmp(fname, tdir) == 0); + free(fname); + + fname = tracefs_instance_get_dir(instance); + CU_TEST(fname != NULL); + CU_TEST(strcmp(fname, inst_dir) == 0); + free(fname); + + CU_TEST(asprintf(&fname, "%s/"ALL_TRACERS, tdir) > 0); + CU_TEST(fname != NULL); + inst_file = tracefs_instance_get_file(NULL, ALL_TRACERS); + CU_TEST(inst_file != NULL); + CU_TEST(strcmp(fname, inst_file) == 0); + tracefs_put_tracing_file(inst_file); + free(fname); + + CU_TEST(asprintf(&fname, "%s/instances/%s/"ALL_TRACERS, tdir, name) > 0); + CU_TEST(fname != NULL); + CU_TEST(stat(fname, &st) == 0); + inst_file = tracefs_instance_get_file(instance, ALL_TRACERS); + CU_TEST(inst_file != NULL); + CU_TEST(strcmp(fname, inst_file) == 0); + + test_instance_file_read(NULL, ALL_TRACERS); + test_instance_file_read(instance, ALL_TRACERS); + + file1 = tracefs_instance_file_read(instance, ALL_TRACERS, NULL); + CU_TEST(file1 != NULL); + tracer = strtok(file1, " "); + CU_TEST(tracer != NULL); + ret = tracefs_instance_file_write(instance, CUR_TRACER, tracer); + CU_TEST(ret == strlen(tracer)); + file2 = tracefs_instance_file_read(instance, CUR_TRACER, &size); + CU_TEST(file2 != NULL); + CU_TEST(size >= strlen(tracer)); + CU_TEST(strncmp(file2, tracer, strlen(tracer)) == 0); + free(file1); + free(file2); + + tracefs_put_tracing_file(inst_file); + free(fname); + + CU_TEST(tracefs_file_exists(NULL, (char *)name) == false); + CU_TEST(tracefs_dir_exists(NULL, (char *)name) == false); + CU_TEST(tracefs_file_exists(instance, (char *)name) == false); + CU_TEST(tracefs_dir_exists(instance, (char *)name) == false); + + CU_TEST(tracefs_file_exists(NULL, CUR_TRACER) == true); + CU_TEST(tracefs_dir_exists(NULL, CUR_TRACER) == false); + CU_TEST(tracefs_file_exists(instance, CUR_TRACER) == true); + CU_TEST(tracefs_dir_exists(instance, CUR_TRACER) == false); + + CU_TEST(tracefs_file_exists(NULL, PER_CPU) == false); + CU_TEST(tracefs_dir_exists(NULL, PER_CPU) == true); + CU_TEST(tracefs_file_exists(instance, PER_CPU) == false); + CU_TEST(tracefs_dir_exists(instance, PER_CPU) == true); + + CU_TEST(tracefs_instance_destroy(NULL) != 0); + CU_TEST(tracefs_instance_destroy(instance) == 0); + CU_TEST(tracefs_instance_destroy(instance) != 0); + tracefs_instance_free(instance); + CU_TEST(stat(inst_dir, &st) != 0); + free(inst_dir); +} + +static bool check_fd_name(int fd, const char *dir, const char *name) +{ + char link[PATH_MAX + 1]; + char path[PATH_MAX + 1]; + struct stat st; + char *file; + int ret; + + snprintf(link, PATH_MAX, "/proc/self/fd/%d", fd); + ret = lstat(link, &st); + CU_TEST(ret == 0); + if (ret < 0) + return false; + CU_TEST(S_ISLNK(st.st_mode)); + if (!S_ISLNK(st.st_mode)) + return false; + ret = readlink(link, path, PATH_MAX); + CU_TEST(ret > 0); + if (ret > PATH_MAX || ret < 0) + return false; + path[ret] = 0; + ret = strncmp(dir, path, strlen(dir)); + CU_TEST(ret == 0); + if (ret) + return false; + file = basename(path); + CU_TEST(file != NULL); + if (!file) + return false; + ret = strcmp(file, name); + CU_TEST(ret == 0); + if (ret) + return false; + return true; +} + +#define FLAGS_STR "flags:" +static bool check_fd_mode(int fd, int mode) +{ + char path[PATH_MAX + 1]; + long fmode = -1; + char *line = NULL; + struct stat st; + size_t len = 0; + ssize_t size; + FILE *file; + int ret; + + snprintf(path, PATH_MAX, "/proc/self/fdinfo/%d", fd); + ret = stat(path, &st); + CU_TEST(ret == 0); + if (ret < 0) + return false; + file = fopen(path, "r"); + if (!file) + return false; + while ((size = getline(&line, &len, file)) > 0) { + if (strncmp(line, FLAGS_STR, strlen(FLAGS_STR))) + continue; + fmode = strtol(line + strlen(FLAGS_STR), NULL, 8); + break; + } + free(line); + fclose(file); + if (fmode < 0 || + (O_ACCMODE & fmode) != (O_ACCMODE & mode)) + return false; + return true; +} + +static void test_instance_file_fd(struct tracefs_instance *instance) +{ + const char *name = get_rand_str(); + const char *tdir = tracefs_instance_get_trace_dir(instance); + long long res = -1; + char rd[2]; + int fd; + + CU_TEST(tdir != NULL); + fd = tracefs_instance_file_open(instance, name, -1); + CU_TEST(fd == -1); + fd = tracefs_instance_file_open(instance, TRACE_ON, O_RDONLY); + CU_TEST(fd >= 0); + + CU_TEST(check_fd_name(fd, tdir, TRACE_ON)); + CU_TEST(check_fd_mode(fd, O_RDONLY)); + + CU_TEST(tracefs_instance_file_read_number(instance, ALL_TRACERS, &res) != 0); + CU_TEST(tracefs_instance_file_read_number(instance, name, &res) != 0); + CU_TEST(tracefs_instance_file_read_number(instance, TRACE_ON, &res) == 0); + CU_TEST((res == 0 || res == 1)); + CU_TEST(read(fd, &rd, 1) == 1); + rd[1] = 0; + CU_TEST(res == atoi(rd)); + + close(fd); +} + +static void test_file_fd(void) +{ + test_instance_file_fd(test_instance); +} + +static void test_instance_tracing_onoff(struct tracefs_instance *instance) +{ + const char *tdir = tracefs_instance_get_trace_dir(instance); + long long res = -1; + int fd; + + CU_TEST(tdir != NULL); + fd = tracefs_trace_on_get_fd(instance); + CU_TEST(fd >= 0); + CU_TEST(check_fd_name(fd, tdir, TRACE_ON)); + CU_TEST(check_fd_mode(fd, O_RDWR)); + CU_TEST(tracefs_instance_file_read_number(instance, TRACE_ON, &res) == 0); + if (res == 1) { + CU_TEST(tracefs_trace_is_on(instance) == 1); + CU_TEST(tracefs_trace_off(instance) == 0); + CU_TEST(tracefs_trace_is_on(instance) == 0); + CU_TEST(tracefs_trace_on(instance) == 0); + CU_TEST(tracefs_trace_is_on(instance) == 1); + + CU_TEST(tracefs_trace_off_fd(fd) == 0); + CU_TEST(tracefs_trace_is_on(instance) == 0); + CU_TEST(tracefs_trace_on_fd(fd) == 0); + CU_TEST(tracefs_trace_is_on(instance) == 1); + } else { + CU_TEST(tracefs_trace_is_on(instance) == 0); + CU_TEST(tracefs_trace_on(instance) == 0); + CU_TEST(tracefs_trace_is_on(instance) == 1); + CU_TEST(tracefs_trace_off(instance) == 0); + CU_TEST(tracefs_trace_is_on(instance) == 0); + + CU_TEST(tracefs_trace_on_fd(fd) == 0); + CU_TEST(tracefs_trace_is_on(instance) == 1); + CU_TEST(tracefs_trace_off_fd(fd) == 0); + CU_TEST(tracefs_trace_is_on(instance) == 0); + } + + if (fd >= 0) + close(fd); +} + +static void test_tracing_onoff(void) +{ + test_instance_tracing_onoff(test_instance); +} + +static bool check_option(struct tracefs_instance *instance, + enum tracefs_option_id id, bool exist, int enabled) +{ + const char *name = tracefs_option_name(id); + char file[PATH_MAX]; + char *path = NULL; + bool ret = false; + bool supported; + struct stat st; + char buf[10]; + int fd = 0; + int r; + int rstat; + + CU_TEST(name != NULL); + supported = tracefs_option_is_supported(instance, id); + CU_TEST(supported == exist); + if (supported != exist) + goto out; + snprintf(file, PATH_MAX, "options/%s", name); + path = tracefs_instance_get_file(instance, file); + CU_TEST(path != NULL); + rstat = stat(path, &st); + if (exist) { + CU_TEST(rstat == 0); + if (rstat != 0) + goto out; + } else { + CU_TEST(stat(path, &st) == -1); + if (rstat != -1) + goto out; + } + + fd = open(path, O_RDONLY); + if (exist) { + CU_TEST(fd >= 0); + if (fd < 0) + goto out; + } else { + CU_TEST(fd < 0); + if (fd >= 0) + goto out; + } + + if (exist && enabled >= 0) { + int val = enabled ? '1' : '0'; + + r = read(fd, buf, 10); + CU_TEST(r >= 1); + CU_TEST(buf[0] == val); + if (buf[0] != val) + goto out; + } + + ret = true; +out: + tracefs_put_tracing_file(path); + if (fd >= 0) + close(fd); + return ret; +} + +static void test_instance_tracing_options(struct tracefs_instance *instance) +{ + const struct tracefs_options_mask *enabled; + const struct tracefs_options_mask *all_copy; + const struct tracefs_options_mask *all; + enum tracefs_option_id i = 1; + char file[PATH_MAX]; + const char *name; + + all = tracefs_options_get_supported(instance); + all_copy = tracefs_options_get_supported(instance); + enabled = tracefs_options_get_enabled(instance); + CU_TEST(all != NULL); + + /* Invalid parameters test */ + CU_TEST(!tracefs_option_is_supported(instance, TRACEFS_OPTION_INVALID)); + CU_TEST(!tracefs_option_is_enabled(instance, TRACEFS_OPTION_INVALID)); + CU_TEST(tracefs_option_enable(instance, TRACEFS_OPTION_INVALID) == -1); + CU_TEST(tracefs_option_disable(instance, TRACEFS_OPTION_INVALID) == -1); + name = tracefs_option_name(TRACEFS_OPTION_INVALID); + CU_TEST(!strcmp(name, "unknown")); + /* Test all valid options */ + for (i = 1; i < TRACEFS_OPTION_MAX; i++) { + name = tracefs_option_name(i); + CU_TEST(name != NULL); + CU_TEST(strcmp(name, "unknown")); + snprintf(file, PATH_MAX, "options/%s", name); + + if (tracefs_option_mask_is_set(all, i)) { + CU_TEST(check_option(instance, i, true, -1)); + CU_TEST(tracefs_option_is_supported(instance, i)); + } else { + CU_TEST(check_option(instance, i, false, -1)); + CU_TEST(!tracefs_option_is_supported(instance, i)); + } + + if (tracefs_option_mask_is_set(enabled, i)) { + CU_TEST(check_option(instance, i, true, 1)); + CU_TEST(tracefs_option_is_supported(instance, i)); + CU_TEST(tracefs_option_is_enabled(instance, i)); + CU_TEST(tracefs_option_disable(instance, i) == 0); + CU_TEST(check_option(instance, i, true, 0)); + CU_TEST(tracefs_option_enable(instance, i) == 0); + CU_TEST(check_option(instance, i, true, 1)); + } else if (tracefs_option_mask_is_set(all_copy, i)) { + CU_TEST(check_option(instance, i, true, 0)); + CU_TEST(tracefs_option_is_supported(instance, i)); + CU_TEST(!tracefs_option_is_enabled(instance, i)); + CU_TEST(tracefs_option_enable(instance, i) == 0); + CU_TEST(check_option(instance, i, true, 1)); + CU_TEST(tracefs_option_disable(instance, i) == 0); + CU_TEST(check_option(instance, i, true, 0)); + } + } +} + +static void test_tracing_options(void) +{ + test_instance_tracing_options(test_instance); +} + +static void exclude_string(char **strings, char *name) +{ + int i; + + for (i = 0; strings[i]; i++) { + if (strcmp(strings[i], name) == 0) { + free(strings[i]); + strings[i] = strdup("/"); + return; + } + } +} + +static void test_check_files(const char *fdir, char **files) +{ + struct dirent *dent; + DIR *dir; + int i; + + dir = opendir(fdir); + CU_TEST(dir != NULL); + + while ((dent = readdir(dir))) + exclude_string(files, dent->d_name); + + closedir(dir); + + for (i = 0; files[i]; i++) + CU_TEST(files[i][0] == '/'); +} + +static void system_event(const char *tdir) +{ + + char **systems; + char **events; + char *sdir = NULL; + + systems = tracefs_event_systems(tdir); + CU_TEST(systems != NULL); + + events = tracefs_system_events(tdir, systems[0]); + CU_TEST(events != NULL); + + asprintf(&sdir, "%s/events/%s", tdir, systems[0]); + CU_TEST(sdir != NULL); + test_check_files(sdir, events); + free(sdir); + sdir = NULL; + + asprintf(&sdir, "%s/events", tdir); + CU_TEST(sdir != NULL); + test_check_files(sdir, systems); + + tracefs_list_free(systems); + tracefs_list_free(events); + + free(sdir); +} + +static void test_system_event(void) +{ + const char *tdir; + + tdir = tracefs_tracing_dir(); + CU_TEST(tdir != NULL); + system_event(tdir); +} + +static void test_instance_tracers(struct tracefs_instance *instance) +{ + const char *tdir; + char **tracers; + char *tfile; + char *tracer; + int i; + + tdir = tracefs_instance_get_trace_dir(instance); + CU_TEST(tdir != NULL); + + tracers = tracefs_tracers(tdir); + CU_TEST(tracers != NULL); + + for (i = 0; tracers[i]; i++) + CU_TEST(tracefs_tracer_available(tdir, tracers[i])); + + tfile = tracefs_instance_file_read(NULL, ALL_TRACERS, NULL); + + tracer = strtok(tfile, " "); + while (tracer) { + exclude_string(tracers, tracer); + tracer = strtok(NULL, " "); + } + + for (i = 0; tracers[i]; i++) + CU_TEST(tracers[i][0] == '/'); + + tracefs_list_free(tracers); + free(tfile); +} + +static void test_tracers(void) +{ + test_instance_tracers(test_instance); +} + +static void test_check_events(struct tep_handle *tep, char *system, bool exist) +{ + struct dirent *dent; + char file[PATH_MAX]; + char buf[1024]; + char *edir = NULL; + const char *tdir; + DIR *dir; + int fd; + + tdir = tracefs_tracing_dir(); + CU_TEST(tdir != NULL); + + asprintf(&edir, "%s/events/%s", tdir, system); + dir = opendir(edir); + CU_TEST(dir != NULL); + + while ((dent = readdir(dir))) { + if (dent->d_name[0] == '.') + continue; + sprintf(file, "%s/%s/id", edir, dent->d_name); + fd = open(file, O_RDONLY); + if (fd < 0) + continue; + CU_TEST(read(fd, buf, 1024) > 0); + if (exist) { + CU_TEST(tep_find_event(tep, atoi(buf)) != NULL); + } else { + CU_TEST(tep_find_event(tep, atoi(buf)) == NULL); + } + + close(fd); + } + + closedir(dir); + free(edir); + +} + +static void local_events(const char *tdir) +{ + struct tep_handle *tep; + char **systems; + char *lsystems[3]; + int i; + + tep = tracefs_local_events(tdir); + CU_TEST(tep != NULL); + + systems = tracefs_event_systems(tdir); + CU_TEST(systems != NULL); + + for (i = 0; systems[i]; i++) + test_check_events(tep, systems[i], true); + tep_free(tep); + + memset(lsystems, 0, sizeof(lsystems)); + for (i = 0; systems[i]; i++) { + if (!lsystems[0]) + lsystems[0] = systems[i]; + else if (!lsystems[2]) + lsystems[2] = systems[i]; + else + break; + } + + if (lsystems[0] && lsystems[2]) { + tep = tracefs_local_events_system(tdir, + (const char * const *)lsystems); + CU_TEST(tep != NULL); + test_check_events(tep, lsystems[0], true); + test_check_events(tep, lsystems[2], false); + } + tep_free(tep); + + tep = tep_alloc(); + CU_TEST(tep != NULL); + CU_TEST(tracefs_fill_local_events(tdir, tep, NULL) == 0); + for (i = 0; systems[i]; i++) + test_check_events(tep, systems[i], true); + + tep_free(tep); + + tracefs_list_free(systems); +} + +static void test_local_events(void) +{ + const char *tdir; + + tdir = tracefs_tracing_dir(); + CU_TEST(tdir != NULL); + local_events(tdir); +} + +struct test_walk_instance { + struct tracefs_instance *instance; + bool found; +}; +#define WALK_COUNT 10 +int test_instances_walk_cb(const char *name, void *data) +{ + struct test_walk_instance *instances = (struct test_walk_instance *)data; + int i; + + CU_TEST(instances != NULL); + CU_TEST(name != NULL); + + for (i = 0; i < WALK_COUNT; i++) { + if (!strcmp(name, + tracefs_instance_get_name(instances[i].instance))) { + instances[i].found = true; + break; + } + } + + return 0; +} + +static void test_instances_walk(void) +{ + struct test_walk_instance instances[WALK_COUNT]; + int i; + + memset(instances, 0, WALK_COUNT * sizeof(struct test_walk_instance)); + for (i = 0; i < WALK_COUNT; i++) { + instances[i].instance = tracefs_instance_create(get_rand_str()); + CU_TEST(instances[i].instance != NULL); + } + + CU_TEST(tracefs_instances_walk(test_instances_walk_cb, instances) == 0); + for (i = 0; i < WALK_COUNT; i++) { + CU_TEST(instances[i].found); + tracefs_instance_destroy(instances[i].instance); + instances[i].found = false; + } + + CU_TEST(tracefs_instances_walk(test_instances_walk_cb, instances) == 0); + for (i = 0; i < WALK_COUNT; i++) { + CU_TEST(!instances[i].found); + tracefs_instance_free(instances[i].instance); + } +} + +static void current_clock_check(struct tracefs_instance *instance, const char *clock) +{ + int size = 0; + char *clocks; + char *str; + + clocks = tracefs_instance_file_read(instance, TRACE_CLOCK, &size); + CU_TEST_FATAL(clocks != NULL); + CU_TEST(size > strlen(clock)); + str = strstr(clocks, clock); + CU_TEST(str != NULL); + CU_TEST(str != clocks); + CU_TEST(*(str - 1) == '['); + CU_TEST(*(str + strlen(clock)) == ']'); + free(clocks); +} + +static void test_instance_get_clock(struct tracefs_instance *instance) +{ + const char *clock; + + clock = tracefs_get_clock(instance); + CU_TEST_FATAL(clock != NULL); + current_clock_check(instance, clock); + free((char *)clock); +} + +static void test_get_clock(void) +{ + test_instance_get_clock(test_instance); +} + +static void copy_trace_file(const char *from, char *to) +{ + int fd_from = -1; + int fd_to = -1; + char buf[512]; + int ret; + + fd_from = open(from, O_RDONLY); + if (fd_from < 0) + goto out; + fd_to = open(to, O_WRONLY | O_TRUNC | O_CREAT, S_IRWXU | S_IRWXG); + if (fd_to < 0) + goto out; + + while ((ret = read(fd_from, buf, 512)) > 0) { + if (write(fd_to, buf, ret) == -1) + break; + } + +out: + if (fd_to >= 0) + close(fd_to); + if (fd_from >= 0) + close(fd_from); +} + +static int trace_dir_base; +static char *trace_tmp_dir; +static int copy_trace_walk(const char *fpath, const struct stat *sb, + int typeflag, struct FTW *ftwbuf) +{ + char path[PATH_MAX]; + + sprintf(path, "%s%s", trace_tmp_dir, fpath + trace_dir_base); + + switch (typeflag) { + case FTW_D: + mkdir(path, 0750); + break; + case FTW_F: + copy_trace_file(fpath, path); + break; + default: + break; + } + return 0; +} + +static void dup_trace_dir(char *to, char *dir) +{ + const char *trace_dir = tracefs_tracing_dir(); + char file_from[PATH_MAX]; + char file_to[PATH_MAX]; + + sprintf(file_from, "%s/%s", trace_dir, dir); + sprintf(file_to, "%s/%s", to, dir); + trace_tmp_dir = file_to; + trace_dir_base = strlen(file_from); + nftw(file_from, copy_trace_walk, 20, 0); +} + +static void dup_trace_file(char *to, char *file) +{ + const char *trace_dir = tracefs_tracing_dir(); + char file_from[PATH_MAX]; + char file_to[PATH_MAX]; + + sprintf(file_from, "%s/%s", trace_dir, file); + sprintf(file_to, "%s/%s", to, file); + copy_trace_file(file_from, file_to); +} + +static char *copy_trace_dir(void) +{ + char template[] = TEST_TRACE_DIR; + char *dname = mkdtemp(template); + + dup_trace_dir(dname, "events"); + dup_trace_dir(dname, "options"); + dup_trace_file(dname, TRACE_ON); + dup_trace_file(dname, CUR_TRACER); + dup_trace_file(dname, TRACE_CLOCK); + dup_trace_file(dname, ALL_TRACERS); + + return strdup(dname); +} + +static int del_trace_walk(const char *fpath, const struct stat *sb, + int typeflag, struct FTW *ftwbuf) +{ + remove(fpath); + return 0; +} + +void del_trace_dir(char *dir) +{ + nftw(dir, del_trace_walk, 20, FTW_DEPTH); +} + +static void test_custom_trace_dir(void) +{ + char *tdir = "/tmp/custom_tracefs"; + struct tracefs_instance *instance; + char *dname = copy_trace_dir(); + const char *trace_dir; + char *tfile; + + instance = tracefs_instance_alloc(dname, NULL); + CU_TEST(instance != NULL); + + system_event(dname); + local_events(dname); + test_instance_tracing_options(instance); + test_instance_get_clock(instance); + test_instance_file_fd(instance); + test_instance_tracers(instance); + + tracefs_instance_free(instance); + del_trace_dir(dname); + free(dname); + + trace_dir = tracefs_tracing_dir(); + CU_TEST(trace_dir != NULL); + CU_TEST(tracefs_set_tracing_dir(tdir) == 0); + CU_TEST(strcmp(tdir, tracefs_tracing_dir()) == 0); + tfile = tracefs_get_tracing_file("trace"); + CU_TEST(tfile != NULL); + CU_TEST(strcmp(tdir, dirname(tfile)) == 0); + free(tfile); + + CU_TEST(tracefs_set_tracing_dir(NULL) == 0); + CU_TEST(strcmp(trace_dir, tracefs_tracing_dir()) == 0); + tfile = tracefs_get_tracing_file("trace"); + CU_TEST(tfile != NULL); + CU_TEST(strcmp(trace_dir, dirname(tfile)) == 0); + free(tfile); +} + +static int test_suite_destroy(void) +{ + tracefs_instance_destroy(test_instance); + tracefs_instance_free(test_instance); + tep_free(test_tep); + return 0; +} + +static int test_suite_init(void) +{ + const char *systems[] = {"ftrace", NULL}; + + test_tep = tracefs_local_events_system(NULL, systems); + if (test_tep == NULL) + return 1; + test_instance = tracefs_instance_create(TEST_INSTANCE_NAME); + if (!test_instance) + return 1; + + return 0; +} + +void test_tracefs_lib(void) +{ + CU_pSuite suite = NULL; + + suite = CU_add_suite(TRACEFS_SUITE, test_suite_init, test_suite_destroy); + if (suite == NULL) { + fprintf(stderr, "Suite \"%s\" cannot be ceated\n", TRACEFS_SUITE); + return; + } + + CU_add_test(suite, "Test tracefs/debugfs mounting", test_mounting); + CU_add_test(suite, "trace cpu read", + test_trace_cpu_read); + CU_add_test(suite, "trace cpu pipe", + test_trace_cpu_pipe); + CU_add_test(suite, "trace sql", + test_trace_sql); + CU_add_test(suite, "tracing file / directory APIs", + test_trace_file); + CU_add_test(suite, "instance file / directory APIs", + test_file_fd); + CU_add_test(suite, "instance file descriptor", + test_instance_file); + CU_add_test(suite, "systems and events APIs", + test_system_event); + CU_add_test(suite, "tracefs_iterate_raw_events API", + test_iter_raw_events); + + /* Follow events test must be after the iterate raw events above */ + CU_add_test(suite, "Follow events", test_follow_events); + + CU_add_test(suite, "tracefs_tracers API", + test_tracers); + CU_add_test(suite, "tracefs_local events API", + test_local_events); + CU_add_test(suite, "tracefs_instances_walk API", + test_instances_walk); + CU_add_test(suite, "tracefs_get_clock API", + test_get_clock); + CU_add_test(suite, "tracing on / off", + test_tracing_onoff); + CU_add_test(suite, "tracing options", + test_tracing_options); + CU_add_test(suite, "custom system directory", + test_custom_trace_dir); + CU_add_test(suite, "ftrace marker", + test_ftrace_marker); + CU_add_test(suite, "kprobes", test_kprobes); + CU_add_test(suite, "synthetic events", test_synthetic); + CU_add_test(suite, "eprobes", test_eprobes); + CU_add_test(suite, "uprobes", test_uprobes); +} |