diff options
author | Sadaf Ebrahimi <sadafebrahimi@google.com> | 2023-01-19 17:02:46 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-01-19 17:02:46 +0000 |
commit | 287e80b3a36113050663245e7f2c00d274188f18 (patch) | |
tree | 21be18ffc6c7cbb651dcc56278ff076a3f297bcf | |
parent | c6b58cef25e7847f5b31acab1502b034bfee302e (diff) | |
parent | e2afc71443010991595eac6d1d41c971e0fdbcff (diff) | |
download | libtracefs-main.tar.gz |
Upgrade libtracefs to libtracefs-1.6.4 am: eee6e409c7 am: 1da5104322 am: e2afc71443HEADandroid-14.0.0_r45android-14.0.0_r44android-14.0.0_r43android-14.0.0_r42android-14.0.0_r41android-14.0.0_r40android-14.0.0_r39android-14.0.0_r38android-14.0.0_r37android-14.0.0_r36android-14.0.0_r35android-14.0.0_r34android-14.0.0_r33android-14.0.0_r32android-14.0.0_r31android-14.0.0_r30android-14.0.0_r29android-14.0.0_r27android-14.0.0_r26android-14.0.0_r25android-14.0.0_r24android-14.0.0_r23android-14.0.0_r22android-14.0.0_r21android-14.0.0_r20android-14.0.0_r19android-14.0.0_r18android-14.0.0_r17android-14.0.0_r16aml_rkp_341510000aml_rkp_341311000aml_rkp_341114000aml_rkp_341015010aml_rkp_341012000aml_hef_341613000aml_hef_341512030aml_hef_341415040aml_hef_341311010aml_hef_341114030aml_cfg_341510000mastermainandroid14-qpr2-s5-releaseandroid14-qpr2-s4-releaseandroid14-qpr2-s3-releaseandroid14-qpr2-s2-releaseandroid14-qpr2-s1-releaseandroid14-qpr2-releaseandroid14-qpr1-s2-releaseandroid14-qpr1-releaseandroid14-mainline-healthfitness-releaseandroid14-devandroid14-d2-s5-releaseandroid14-d2-s4-releaseandroid14-d2-s3-releaseandroid14-d2-s2-releaseandroid14-d2-s1-releaseandroid14-d2-release
Original change: https://android-review.googlesource.com/c/platform/external/libtracefs/+/2394014
Change-Id: Ic954afaa5a2045e71b266bc1c6f86b54058bc4a6
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
33 files changed, 2991 insertions, 254 deletions
@@ -11,5 +11,5 @@ build_prefix build_uninstall tfs_version.h *.o -*.d +.*.d sqlhist 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-events.txt b/Documentation/libtracefs-events.txt index f998c79..90c54b8 100644 --- a/Documentation/libtracefs-events.txt +++ b/Documentation/libtracefs-events.txt @@ -4,7 +4,7 @@ libtracefs(3) NAME ---- tracefs_event_systems, tracefs_system_events, tracefs_event_enable, tracefs_event_disable, -tracefs_iterate_raw_events, tracefs_iterate_stop - Work with trace systems and events. +tracefs_event_is_enabled - Work with trace systems and events. SYNOPSIS -------- @@ -12,18 +12,21 @@ SYNOPSIS -- *#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_); -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_); - +enum tracefs_enable_state *tracefs_event_is_enabled*(struct tracefs_instance pass:[*]_instance_, + const char pass:[*]_system_, const char pass:[*]_event_); -- DESCRIPTION @@ -61,25 +64,23 @@ 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_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_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 ------------ @@ -95,8 +96,8 @@ found, it will return -1 and errno will be set. If no errors occur, but no event are found that match the _system_ and _event_ parameters, then -1 is returned and errno is not set. -The *tracefs_iterate_raw_events()* function returns -1 in case of an error or -0 otherwise. +The *tracefs_event_is_enabled()* returns the enabled status of the matching events +or TRACEFS_ERROR on error. EXAMPLE ------- diff --git a/Documentation/libtracefs-files.txt b/Documentation/libtracefs-files.txt index 13e251d..d22e759 100644 --- a/Documentation/libtracefs-files.txt +++ b/Documentation/libtracefs-files.txt @@ -3,8 +3,8 @@ libtracefs(3) NAME ---- -tracefs_get_tracing_file, tracefs_put_tracing_file, tracefs_tracing_dir - -Find locations of trace directory and files. +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 -------- @@ -15,6 +15,9 @@ SYNOPSIS 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 @@ -22,6 +25,13 @@ 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 @@ -38,14 +48,33 @@ 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] diff --git a/Documentation/libtracefs-hist.txt b/Documentation/libtracefs-hist.txt index 3ac21ce..7503fd0 100644 --- a/Documentation/libtracefs-hist.txt +++ b/Documentation/libtracefs-hist.txt @@ -3,8 +3,8 @@ libtracefs(3) NAME ---- -tracefs_hist_alloc, tracefs_hist_alloc_2d, tracefs_hist_alloc_nd, tracefs_hist_free, -tracefs_hist_add_key, tracefs_hist_add_value - Create and destroy event histograms +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 -------- @@ -39,10 +39,15 @@ struct tracefs_hist pass:[*]*tracefs_hist_alloc_2d*(struct tracefs_tep pass:[*] 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_); -- @@ -78,6 +83,12 @@ _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. @@ -90,6 +101,14 @@ 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. @@ -140,6 +159,7 @@ EXAMPLE [source,c] -- #include <stdlib.h> +#include <ctype.h> #include <unistd.h> #include <tracefs.h> @@ -162,14 +182,14 @@ static void parse_system_event(char *group, char **system, char **event) } } -static int parse_keys(char *keys, struct tracefs_hist_axis **axes) +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 *ax; + struct tracefs_hist_axis_cnt *ax; char *att; ax = realloc(*axes, sizeof(*ax) * (cnt + 2)); @@ -201,7 +221,14 @@ static int parse_keys(char *keys, struct tracefs_hist_axis **axes) ax[cnt].type = TRACEFS_HIST_KEY_LOG; else if (strcmp(att, "usecs") == 0) ax[cnt].type = TRACEFS_HIST_KEY_USECS; - else { + 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"); @@ -220,7 +247,7 @@ static void process_hist(enum commands cmd, const char *instance_name, struct tracefs_instance *instance = NULL; struct tracefs_hist *hist; struct tep_handle *tep; - struct tracefs_hist_axis *axes = NULL; + struct tracefs_hist_axis_cnt *axes = NULL; char *system; char *event; char *sav; @@ -268,6 +295,17 @@ static void process_hist(enum commands cmd, const char *instance_name, 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: @@ -281,7 +319,7 @@ static void process_hist(enum commands cmd, const char *instance_name, break; default: /* Really, 1 and 2 could use this too */ - hist = tracefs_hist_alloc_nd(tep, system, event, axes); + hist = tracefs_hist_alloc_nd_cnt(tep, system, event, axes); } if (!hist) { fprintf(stderr, "Failed hist create\n"); @@ -445,6 +483,7 @@ int main (int argc, char **argv, char **env) } process_hist(cmd, instance, event, keys, vals, sort, ascend, desc); } + -- FILES diff --git a/Documentation/libtracefs-instances-utils.txt b/Documentation/libtracefs-instances-utils.txt index a79cc34..bc8c9a7 100644 --- a/Documentation/libtracefs-instances-utils.txt +++ b/Documentation/libtracefs-instances-utils.txt @@ -3,9 +3,8 @@ libtracefs(3) NAME ---- -tracefs_instance_get_name, tracefs_instance_get_trace_dir, -tracefs_instances_walk, tracefs_instance_exists - -Helper functions for working with tracing instances. +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 -------- @@ -17,7 +16,8 @@ const char pass:[*]*tracefs_instance_get_name*(struct tracefs_instance pass:[*]_ 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 @@ -39,6 +39,15 @@ 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 @@ -53,6 +62,11 @@ 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] 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-sql.txt b/Documentation/libtracefs-sql.txt index ba4271e..6d606db 100644 --- a/Documentation/libtracefs-sql.txt +++ b/Documentation/libtracefs-sql.txt @@ -195,7 +195,7 @@ That is: [source,c] -- - select CAST(common_pid AS comm, CAST(id AS syscall) FROM sys_enter + select CAST(common_pid AS comm), CAST(id AS syscall) FROM sys_enter -- Which produces: @@ -407,8 +407,13 @@ static int do_sql(const char *instance_name, } } tracefs_synth_echo_cmd(&seq, synth); - if (execute) - tracefs_synth_create(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; @@ -430,8 +435,13 @@ static int do_sql(const char *instance_name, } } tracefs_hist_echo_cmd(&seq, instance, hist, 0); - if (execute) - tracefs_hist_start(instance, hist); + 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); diff --git a/Documentation/libtracefs-traceon.txt b/Documentation/libtracefs-traceon.txt index 8f64928..28c79ed 100644 --- a/Documentation/libtracefs-traceon.txt +++ b/Documentation/libtracefs-traceon.txt @@ -62,7 +62,7 @@ EXAMPLE ret = tracefs_trace_is_on(NULL); if (ret == 0) { /* Tracing is disabled in the top instance */ - } else if (ret == 1) {" + } else if (ret == 1) { /* Tracing is enabled in the top instance */ } else { /* Error getting tracing state of the top instance */ diff --git a/Documentation/libtracefs-utils.txt b/Documentation/libtracefs-utils.txt index ddbd675..ab16cc6 100644 --- a/Documentation/libtracefs-utils.txt +++ b/Documentation/libtracefs-utils.txt @@ -3,8 +3,8 @@ libtracefs(3) NAME ---- -tracefs_tracers, tracefs_get_clock, tracefs_list_free, tracefs_list_add, -tracefs_list_size - Helper functions for working with trace file system. +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 -------- @@ -13,6 +13,7 @@ SYNOPSIS *#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_); @@ -30,6 +31,9 @@ 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(). @@ -56,6 +60,9 @@ 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. diff --git a/Documentation/libtracefs.txt b/Documentation/libtracefs.txt index 82cb574..c3f448d 100644 --- a/Documentation/libtracefs.txt +++ b/Documentation/libtracefs.txt @@ -15,6 +15,9 @@ 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_); @@ -43,6 +46,8 @@ Trace instances: 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_); @@ -51,8 +56,21 @@ Trace events: 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_); @@ -112,6 +130,7 @@ Trace options: 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_); @@ -144,11 +163,15 @@ Dynamic event generic APIs: 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_); -Kprobes and Kretprobes: +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_, @@ -223,9 +246,14 @@ Histograms: 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_); @@ -255,6 +283,21 @@ Histograms: 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 @@ -1,14 +1,19 @@ -name: "libtracefs" -description: - "libtracefs is a library that provides APIs to access to the Linux kernel " - "tracefs file system" +# This project was upgraded with external_updater. +# Usage: tools/external_updater/updater.sh update libtracefs +# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md +name: "libtracefs" +description: "libtracefs is a library that provides APIs to access to the Linux kernel tracefs file system" third_party { url { type: GIT value: "https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git" } - version: "libtracefs-1.3.1" - last_upgrade_date { year: 2022 month: 5 day: 02 } + version: "libtracefs-1.6.4" license_type: RESTRICTED + last_upgrade_date { + year: 2023 + month: 1 + day: 18 + } } @@ -1,8 +1,8 @@ # SPDX-License-Identifier: LGPL-2.1 # libtracefs version TFS_VERSION = 1 -TFS_PATCHLEVEL = 4 -TFS_EXTRAVERSION = dev +TFS_PATCHLEVEL = 6 +TFS_EXTRAVERSION = 4 TRACEFS_VERSION = $(TFS_VERSION).$(TFS_PATCHLEVEL).$(TFS_EXTRAVERSION) export TFS_VERSION @@ -52,12 +52,12 @@ endif libdir_relative ?= $(libdir_relative_temp) prefix ?= /usr/local -man_dir = $(prefix)/share/man +man_dir ?= $(prefix)/share/man man_dir_SQ = '$(subst ','\'',$(man_dir))' -libdir = $(prefix)/$(libdir_relative) +libdir ?= $(prefix)/$(libdir_relative) libdir_SQ = '$(subst ','\'',$(libdir))' includedir_relative ?= include/tracefs -includedir = $(prefix)/$(includedir_relative) +includedir ?= $(prefix)/$(includedir_relative) includedir_SQ = '$(subst ','\'',$(includedir))' pkgconfig_dir ?= $(word 1,$(shell $(PKG_CONFIG) \ --variable pc_path pkg-config | tr ":" " ")) @@ -132,7 +132,8 @@ LIBTRACEFS_SHARED_VERSION = $(bdir)/libtracefs.so.$(TFS_VERSION) PKG_CONFIG_SOURCE_FILE = libtracefs.pc PKG_CONFIG_FILE := $(addprefix $(obj)/,$(PKG_CONFIG_SOURCE_FILE)) -LIBS = $(LIBTRACEEVENT_LIBS) -lpthread +LPTHREAD ?= -lpthread +LIBS = $(LIBTRACEEVENT_LIBS) $(LPTHREAD) export LIBS export LIBTRACEFS_STATIC LIBTRACEFS_SHARED @@ -163,6 +164,9 @@ 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) @@ -330,7 +334,6 @@ OBJS += tracefs-instance.o OBJS += tracefs-events.o OBJS := $(OBJS:%.o=$(bdir)/%.o) -DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d) all: $(DEFAULT_TARGET) @@ -388,3 +391,7 @@ clean: $(BUILD_PREFIX)) .PHONY: clean + +# libtracefs.a and libtracefs.so would concurrently enter the same directory - +# a recipe for collisions. +.NOTPARALLEL: diff --git a/check-manpages.sh b/check-manpages.sh index d3ec6af..776365c 100755 --- a/check-manpages.sh +++ b/check-manpages.sh @@ -15,12 +15,19 @@ 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 - sed -ne '/^NAME/,/^SYNOP/{/^[a-z]/{s/, *$//;s/,/\n/g;s/ //g;s/-.*$/-/;/-/{s/-//p;q};p}}' $man | while read a; 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 diff --git a/include/tracefs-local.h b/include/tracefs-local.h index 926fd02..2007d26 100644 --- a/include/tracefs-local.h +++ b/include/tracefs-local.h @@ -6,6 +6,8 @@ #ifndef _TRACE_FS_LOCAL_H #define _TRACE_FS_LOCAL_H +#include <pthread.h> + #define __hidden __attribute__((visibility ("hidden"))) #define __weak __attribute__((weak)) @@ -15,13 +17,25 @@ #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; @@ -31,6 +45,8 @@ struct tracefs_instance { 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; }; @@ -50,7 +66,7 @@ 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(void); +char *trace_find_tracing_dir(bool debugfs); #ifndef ACCESSPERMS #define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO) /* 0777 */ @@ -94,7 +110,7 @@ struct tracefs_synth *synth_init_from(struct tep_handle *tep, int synth_add_start_field(struct tracefs_synth *synth, const char *start_field, const char *name, - enum tracefs_hist_key_type type); + enum tracefs_hist_key_type type, int cnt); /* Internal interface for ftrace dynamic events */ @@ -120,4 +136,6 @@ int trace_rescan_events(struct tep_handle *tep, 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 index 27954a4..3547b5a 100644 --- a/include/tracefs.h +++ b/include/tracefs.h @@ -15,6 +15,9 @@ 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; @@ -53,6 +56,8 @@ 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); @@ -65,8 +70,17 @@ 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); @@ -76,6 +90,8 @@ 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 @@ -110,6 +126,17 @@ int tracefs_iterate_raw_events(struct tep_handle *tep, 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, @@ -306,6 +333,7 @@ enum tracefs_hist_key_type { TRACEFS_HIST_KEY_EXECNAME, TRACEFS_HIST_KEY_LOG, TRACEFS_HIST_KEY_USECS, + TRACEFS_HIST_KEY_BUCKETS, TRACEFS_HIST_KEY_MAX }; @@ -368,15 +396,27 @@ struct tracefs_hist_axis { 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); @@ -578,4 +618,20 @@ struct tracefs_synth *tracefs_sql(struct tep_handle *tep, const char *name, struct tep_event * tracefs_synth_get_event(struct tep_handle *tep, struct tracefs_synth *synth); +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/samples/Makefile b/samples/Makefile index 97a570d..743bddb 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -21,6 +21,7 @@ EXAMPLES += hist-cont EXAMPLES += tracer EXAMPLES += stream EXAMPLES += instances-affinity +EXAMPLES += cpu TARGETS := TARGETS += sqlhist @@ -46,7 +47,7 @@ sqlhist: $(sdir)/sqlhist $(TARGETS): $(sdir) # sqlhist is unique and stands on its own -$(sdir)/sqlhist: $(bdir)/sqlhist.c +$(sdir)/sqlhist: $(bdir)/sqlhist.c $(LIBTRACEFS_STATIC) $(call do_sample_build,$@,$<) $(sdir)/%: $(bdir)/%.o @@ -65,5 +66,10 @@ $(EXAMPLES): $(patsubst %,$(sdir)/%,$(TARGETS)) $(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/utils.mk b/scripts/utils.mk index 5f43de1..4d0f8bc 100644 --- a/scripts/utils.mk +++ b/scripts/utils.mk @@ -53,12 +53,12 @@ endif do_fpic_compile = \ ($(print_fpic_compile) \ - $(CC) -c $(CPPFLAGS) $(CFLAGS) $(EXT) -fPIC $< -o $@) + $(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) -c $(CPPFLAGS) $(CFLAGS) $(EXT) $< -o $@)) + $(CC) -Wp,-MMD,$(@D)/.$(@F).d,-MT,$@ -MP -c $(CPPFLAGS) $(CFLAGS) $(EXT) $< -o $@)) do_app_build = \ ($(print_app_build) \ @@ -101,7 +101,7 @@ extract_example = \ do_sample_build = \ $(Q)($(print_sample_build) \ - $(CC) -o $1 $2 $(CFLAGS) $(LIBTRACEFS_STATIC) $(LIBTRACEEVENT_LIBS)) + $(CC) -o $1 $2 $(CFLAGS) $(LIBTRACEFS_STATIC) $(LIBTRACEEVENT_LIBS) -lpthread) do_sample_obj = \ $(Q)($(print_sample_obj) \ diff --git a/src/Makefile b/src/Makefile index 645d518..e2965bc 100644 --- a/src/Makefile +++ b/src/Makefile @@ -14,6 +14,7 @@ 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 @@ -21,7 +22,6 @@ OBJS += sqlhist.tab.o OBJS += tracefs-sqlhist.o OBJS := $(OBJS:%.o=$(bdir)/%.o) -DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d) $(LIBTRACEFS_STATIC): $(OBJS) $(Q)$(call do_build_static_lib) @@ -48,24 +48,14 @@ sqlhist-lex.c: sqlhist.l sqlhist.tab.c $(bdir)/%.o: %.c $(Q)$(call do_fpic_compile) -$(DEPS): $(bdir)/.%.d: %.c - $(Q)$(CC) -M -MT $(bdir)/$*.o $(CPPFLAGS) $(CFLAGS) $< > $@ - -$(OBJS): $(bdir)/%.o : $(bdir)/.%.d - tracefs-sqlhist.o: sqlhist.tab.h $(OBJS): | $(bdir) -$(DEPS): | $(bdir) clean: $(Q)$(call do_clean,$(OBJS) .*.d) -dep_includes := $(wildcard $(DEPS)) - -ifneq ($(dep_includes),) - include $(dep_includes) -endif +-include .*.d $(bdir)/tracefs-sqlhist.o tracefs-sqlhist.o: sqlhist.tab.h diff --git a/src/sqlhist-parse.h b/src/sqlhist-parse.h index d5b86ca..7bd2a63 100644 --- a/src/sqlhist-parse.h +++ b/src/sqlhist-parse.h @@ -4,8 +4,9 @@ #include <stdarg.h> #include <tracefs.h> +#include <tracefs-local.h> + struct str_hash; -#define HASH_BITS 10 struct sql_table; diff --git a/src/tracefs-dynevents.c b/src/tracefs-dynevents.c index 48bb26a..7a3c45c 100644 --- a/src/tracefs-dynevents.c +++ b/src/tracefs-dynevents.c @@ -713,9 +713,6 @@ dynevent_info(struct tracefs_dynevent *dynevent, char **system, &dynevent->address, &dynevent->format }; int i; - if (!dynevent) - return TRACEFS_DYNEVENT_UNKNOWN; - for (i = 0; i < ARRAY_SIZE(lv); i++) { if (lv[i]) { if (*rv[i]) { diff --git a/src/tracefs-events.c b/src/tracefs-events.c index 65d5707..c2adf41 100644 --- a/src/tracefs-events.c +++ b/src/tracefs-events.c @@ -20,15 +20,20 @@ #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 rsize; int cpu; - int fd; }; static int read_kbuf_record(struct cpu_iterate *cpu) @@ -46,6 +51,7 @@ static int read_kbuf_record(struct cpu_iterate *cpu) 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; @@ -59,9 +65,21 @@ int read_next_page(struct tep_handle *tep, struct cpu_iterate *cpu) { enum kbuffer_long_size long_size; enum kbuffer_endian endian; + int r; - cpu->rsize = read(cpu->fd, cpu->page, cpu->psize); - if (cpu->rsize <= 0) + 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) { @@ -81,8 +99,8 @@ int read_next_page(struct tep_handle *tep, struct cpu_iterate *cpu) } kbuffer_load_subbuffer(cpu->kbuf, cpu->page); - if (kbuffer_subbuffer_size(cpu->kbuf) > cpu->rsize) { - tracefs_warning("%s: page_size > %d", __func__, cpu->rsize); + if (kbuffer_subbuffer_size(cpu->kbuf) > r) { + tracefs_warning("%s: page_size > %d", __func__, r); return -1; } @@ -105,7 +123,122 @@ int read_next_record(struct tep_handle *tep, struct cpu_iterate *cpu) return -1; } -static int read_cpu_pages(struct tep_handle *tep, struct cpu_iterate *cpus, int count, +/** + * 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 *), @@ -131,7 +264,10 @@ static int read_cpu_pages(struct tep_handle *tep, struct cpu_iterate *cpus, int j = i; } if (j < count) { - if (callback(cpus[j].event, &cpus[j].record, cpus[j].cpu, callback_context)) + 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); @@ -146,64 +282,114 @@ static int read_cpu_pages(struct tep_handle *tep, struct cpu_iterate *cpus, int 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; - unsigned int p_size; - struct dirent *dent; - char file[PATH_MAX]; - struct stat st; - int ret = -1; - int fd = -1; - char *path; - DIR *dir; + int nr_cpus; int cpu; int i = 0; - path = tracefs_instance_get_file(instance, "per_cpu"); - if (!path) - return -1; - dir = opendir(path); - if (!dir) - goto out; - p_size = getpagesize(); - while ((dent = readdir(dir))) { - const char *name = dent->d_name; + *all_cpus = NULL; - if (strlen(name) < 4 || strncmp(name, "cpu", 3) != 0) - continue; - cpu = atoi(name + 3); + nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + for (cpu = 0; cpu < nr_cpus; cpu++) { if (cpus && !CPU_ISSET_S(cpu, cpu_size, cpus)) continue; - sprintf(file, "%s/%s", path, name); - if (stat(file, &st) < 0 || !S_ISDIR(st.st_mode)) - continue; - - sprintf(file, "%s/%s/trace_pipe_raw", path, name); - fd = open(file, O_RDONLY | O_NONBLOCK); - if (fd < 0) - continue; - tmp = realloc(*all_cpus, (i + 1) * sizeof(struct cpu_iterate)); + tcpu = tracefs_cpu_open(instance, cpu, true); + tmp = realloc(*all_cpus, (i + 1) * sizeof(*tmp)); if (!tmp) { - close(fd); - goto out; + i--; + goto error; } - memset(tmp + i, 0, sizeof(struct cpu_iterate)); - tmp[i].fd = fd; - tmp[i].cpu = cpu; - tmp[i].page = malloc(p_size); - tmp[i].psize = p_size; + *all_cpus = tmp; - *count = i + 1; + + 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 out; + 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; +} - ret = 0; +/** + * 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; -out: - if (dir) - closedir(dir); - tracefs_put_tracing_file(path); - return ret; + 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; @@ -233,22 +419,30 @@ int tracefs_iterate_raw_events(struct tep_handle *tep, int, void *), void *callback_context) { - bool *keep_going = instance ? &instance->pipe_keep_going : + bool *keep_going = instance ? &instance->iterate_keep_going : &top_iterate_keep_going; - struct cpu_iterate *all_cpus = NULL; + struct follow_event *followers; + struct cpu_iterate *all_cpus; int count = 0; int ret; int i; (*(volatile bool *)keep_going) = true; - if (!tep || !callback) + 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, all_cpus, count, + ret = read_cpu_pages(tep, instance, all_cpus, count, callback, callback_context, keep_going); @@ -256,7 +450,7 @@ out: if (all_cpus) { for (i = 0; i < count; i++) { kbuffer_free(all_cpus[i].kbuf); - close(all_cpus[i].fd); + tracefs_cpu_close(all_cpus[i].tcpu); free(all_cpus[i].page); } free(all_cpus); @@ -945,6 +1139,40 @@ out: 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. * @@ -969,6 +1197,11 @@ struct tep_handle *tracefs_local_events_system(const char *tracing_dir, 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; } @@ -1004,9 +1237,68 @@ 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) + bool enable, enum event_state *state) { const char *str = enable ? "1" : "0"; char *system_event; @@ -1016,14 +1308,18 @@ static int enable_disable_event(struct tracefs_instance *instance, if (ret < 0) return ret; - ret = tracefs_instance_file_write(instance, system_event, str); + 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) + const char *system, bool enable, + enum event_state *state) { const char *str = enable ? "1" : "0"; char *system_path; @@ -1033,7 +1329,10 @@ static int enable_disable_system(struct tracefs_instance *instance, if (ret < 0) return ret; - ret = tracefs_instance_file_write(instance, system_path, str); + 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; @@ -1071,7 +1370,7 @@ static int make_regex(regex_t *re, const char *match) static int event_enable_disable(struct tracefs_instance *instance, const char *system, const char *event, - bool enable) + bool enable, enum event_state *state) { regex_t system_re, event_re; char **systems; @@ -1108,7 +1407,7 @@ static int event_enable_disable(struct tracefs_instance *instance, /* Check for the short cut first */ if (!event) { - ret = enable_disable_system(instance, systems[s], enable); + ret = enable_disable_system(instance, systems[s], enable, state); if (ret < 0) break; ret = 0; @@ -1123,7 +1422,7 @@ static int event_enable_disable(struct tracefs_instance *instance, if (!match(events[e], &event_re)) continue; ret = enable_disable_event(instance, systems[s], - events[e], enable); + events[e], enable, state); if (ret < 0) break; ret = 0; @@ -1162,11 +1461,55 @@ static int event_enable_disable(struct tracefs_instance *instance, int tracefs_event_enable(struct tracefs_instance *instance, const char *system, const char *event) { - return event_enable_disable(instance, system, event, true); + 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); + 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 index 85d0be1..a3dd77b 100644 --- a/src/tracefs-filter.c +++ b/src/tracefs-filter.c @@ -35,6 +35,12 @@ static const struct tep_format_field 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. @@ -42,13 +48,19 @@ static const struct tep_format_field common_timestamp_usecs = { 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; - return tep_find_any_field(event, field_name); + 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 @@ -423,7 +435,8 @@ int tracefs_filter_string_append(struct tep_event *event, char **filter, free(*filter); *filter = str; } - return 0; + + return ret; } static int error_msg(char **err, char *str, diff --git a/src/tracefs-hist.c b/src/tracefs-hist.c index e688eb3..fb6231e 100644 --- a/src/tracefs-hist.c +++ b/src/tracefs-hist.c @@ -280,25 +280,11 @@ tracefs_hist_alloc_2d(struct tep_handle *tep, return tracefs_hist_alloc_nd(tep, system, event_name, axis); } -/** - * 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) +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; @@ -326,25 +312,75 @@ tracefs_hist_alloc_nd(struct tep_handle *tep, 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(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) { bool use_key = false; char *key_type = NULL; @@ -377,6 +413,9 @@ int tracefs_hist_add_key(struct tracefs_hist *hist, const char *key, 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; @@ -396,6 +435,22 @@ int tracefs_hist_add_key(struct tracefs_hist *hist, const char *key, } /** + * 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. @@ -654,6 +709,12 @@ struct action { 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 @@ -684,6 +745,7 @@ struct tracefs_synth { 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; @@ -724,6 +786,21 @@ static void action_free(struct action *action) 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 @@ -750,6 +827,7 @@ void tracefs_synth_free(struct tracefs_synth *synth) 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); @@ -773,6 +851,7 @@ static bool verify_event_fields(struct tep_event *start_event, { 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)) @@ -783,7 +862,11 @@ static bool verify_event_fields(struct tep_event *start_event, &end_field)) return false; - if (start_field->flags != end_field->flags || + /* 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; @@ -1068,9 +1151,8 @@ struct tracefs_synth *tracefs_synth_alloc(struct tep_handle *tep, if (!synth->name || !synth->start_keys || !synth->end_keys || ret) { tracefs_synth_free(synth); synth = NULL; - } - - synth->new_format = has_new_format(); + } else + synth->new_format = has_new_format(); return synth; } @@ -1096,7 +1178,7 @@ static int add_synth_fields(struct tracefs_synth *synth, return -1; synth->synthetic_fields = list; - ret = asprintf(&str, "$%s", name); + ret = asprintf(&str, "$%s", var ? : name); if (ret < 0) { trace_list_pop(synth->synthetic_fields); return -1; @@ -1196,7 +1278,7 @@ static unsigned int make_rand(void) return((unsigned)(seed/65536) % 32768); } -static char *new_arg(struct tracefs_synth *synth) +static char *new_name(struct tracefs_synth *synth, const char *name) { int cnt = synth->arg_cnt + 1; char *arg; @@ -1205,9 +1287,9 @@ static char *new_arg(struct tracefs_synth *synth) /* 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, "__arg_%u_", make_rand()); + sprintf(synth->arg_name, "%u", make_rand()); } - ret = asprintf(&arg, "%s%d", synth->arg_name, cnt); + ret = asprintf(&arg, "__%s_%s_%d", name, synth->arg_name, cnt); if (ret < 0) return NULL; @@ -1215,6 +1297,55 @@ static char *new_arg(struct tracefs_synth *synth) 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 @@ -1245,6 +1376,7 @@ int tracefs_synth_add_compare_field(struct tracefs_synth *synth, const char *name) { const struct tep_format_field *start_field; + const char *hname; char *start_arg; char *compare; int ret; @@ -1296,11 +1428,12 @@ int tracefs_synth_add_compare_field(struct tracefs_synth *synth, if (ret < 0) return -1; - ret = add_var(&synth->end_vars, name, compare, false); + 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, NULL); + ret = add_synth_fields(synth, start_field, name, hname); if (ret < 0) goto out_free; @@ -1313,9 +1446,10 @@ int tracefs_synth_add_compare_field(struct tracefs_synth *synth, __hidden int synth_add_start_field(struct tracefs_synth *synth, const char *start_field, const char *name, - enum tracefs_hist_key_type type) + enum tracefs_hist_key_type type, int count) { const struct tep_format_field *field; + const char *var; char *start_arg; char **tmp; int *types; @@ -1330,6 +1464,8 @@ __hidden int synth_add_start_field(struct tracefs_synth *synth, if (!name) name = start_field; + var = hash_name(synth, name); + if (!trace_verify_event_field(synth->start_event, start_field, &field)) return -1; @@ -1341,11 +1477,11 @@ __hidden int synth_add_start_field(struct tracefs_synth *synth, if (ret) goto out_free; - ret = add_var(&synth->end_vars, name, start_arg, true); + ret = add_var(&synth->end_vars, var, start_arg, true); if (ret) goto out_free; - ret = add_synth_fields(synth, field, name, NULL); + ret = add_synth_fields(synth, field, name, var); if (ret) goto out_free; @@ -1394,7 +1530,7 @@ 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); + return synth_add_start_field(synth, start_field, name, 0, 0); } /** @@ -1417,6 +1553,7 @@ int tracefs_synth_add_end_field(struct tracefs_synth *synth, const char *name) { const struct tep_format_field *field; + const char *hname = NULL; char *tmp_var = NULL; int ret; @@ -1425,17 +1562,24 @@ int tracefs_synth_add_end_field(struct tracefs_synth *synth, 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 ? : tmp_var, end_field, false); + ret = add_var(&synth->end_vars, name ? hname : tmp_var, end_field, false); if (ret) goto out; - ret = add_synth_fields(synth, field, name, tmp_var); + ret = add_synth_fields(synth, field, name, hname ? : tmp_var); free(tmp_var); out: return ret; @@ -2194,7 +2338,7 @@ int tracefs_synth_echo_cmd(struct trace_seq *seq, new_event = true; } - path = trace_find_tracing_dir(); + path = trace_find_tracing_dir(false); if (!path) goto out_free; @@ -2227,11 +2371,6 @@ int tracefs_synth_echo_cmd(struct trace_seq *seq, hist, path, synth->end_event->system, synth->end_event->name); - if (new_event) { - tracefs_dynevent_free(synth->dyn_event); - synth->dyn_event = NULL; - } - ret = 0; out_free: free(hist); diff --git a/src/tracefs-instance.c b/src/tracefs-instance.c index 1493938..57f5c7f 100644 --- a/src/tracefs-instance.c +++ b/src/tracefs-instance.c @@ -334,7 +334,7 @@ char *tracefs_instance_get_dir(struct tracefs_instance *instance) int ret; if (!instance) /* Top instance of default system trace directory */ - return trace_find_tracing_dir(); + return trace_find_tracing_dir(false); if (!instance->name) return strdup(instance->trace_dir); @@ -364,6 +364,70 @@ const char *tracefs_instance_get_name(struct tracefs_instance *instance) } /** + * 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 * 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 index 9811362..3f571b7 100644 --- a/src/tracefs-sqlhist.c +++ b/src/tracefs-sqlhist.c @@ -30,6 +30,12 @@ enum alias_type { 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) @@ -42,6 +48,7 @@ struct field { const char *label; const char *field; const char *type; + enum field_type ftype; }; struct filter { @@ -167,7 +174,7 @@ static void parse_error(struct sqlhist_bison *sb, const char *text, va_end(ap); } -static inline unsigned int quick_hash(const char *str) +__hidden unsigned int quick_hash(const char *str) { unsigned int val = 0; int len = strlen(str); @@ -253,6 +260,7 @@ __hidden int add_selection(struct sqlhist_bison *sb, void *select, switch (expr->type) { case EXPR_FIELD: + expr->field.label = name; break; case EXPR_COMPARE: expr->compare.name = name; @@ -285,6 +293,8 @@ static struct expr *find_field(struct sqlhist_bison *sb, if (!strcmp(field->raw, raw)) { if (label && !field->label) field->label = label; + if (label && strcmp(label, field->label) != 0) + continue; return expr; } @@ -562,6 +572,9 @@ static int test_field_exists(struct tep_handle *tep, 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; @@ -580,6 +593,7 @@ static int update_vars(struct tep_handle *tep, { 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; @@ -589,6 +603,11 @@ static int update_vars(struct tep_handle *tep, 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; @@ -670,6 +689,7 @@ static int update_vars(struct tep_handle *tep, 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); @@ -887,20 +907,21 @@ static int verify_filter_error(struct sqlhist_bison *sb, struct expr *expr, } static int do_verify_filter(struct sqlhist_bison *sb, struct filter *filter, - const char **system, const char **event) + 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); + 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); + 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); + return do_verify_filter(sb, &filter->lval->filter, system, event, ftype); } /* @@ -910,6 +931,7 @@ static int do_verify_filter(struct sqlhist_bison *sb, struct filter *filter, if (!*system && !*event) { *system = filter->lval->field.system; *event = filter->lval->field.event_name; + *ftype = filter->lval->field.ftype; return 0; } @@ -921,7 +943,8 @@ static int do_verify_filter(struct sqlhist_bison *sb, struct filter *filter, } static int verify_filter(struct sqlhist_bison *sb, struct filter *filter, - const char **system, const char **event) + const char **system, const char **event, + enum field_type *ftype) { int ret; @@ -932,17 +955,17 @@ static int verify_filter(struct sqlhist_bison *sb, struct filter *filter, case FILTER_NOT_GROUP: break; default: - return do_verify_filter(sb, filter, system, event); + return do_verify_filter(sb, filter, system, event, ftype); } - ret = do_verify_filter(sb, &filter->lval->filter, system, event); + 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); + return do_verify_filter(sb, &filter->rval->filter, system, event, ftype); default: return 0; } @@ -1260,7 +1283,7 @@ static void where_no_to_error(struct sqlhist_bison *sb, struct expr *expr, static int verify_field_type(struct tep_handle *tep, struct sqlhist_bison *sb, - struct expr *expr) + struct expr *expr, int *cnt) { struct field *field = &expr->field; struct tep_event *event; @@ -1339,6 +1362,17 @@ static int verify_field_type(struct tep_handle *tep, 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", @@ -1449,17 +1483,19 @@ static struct tracefs_synth *build_synth(struct tep_handle *tep, if (expr->type == EXPR_FIELD) { ret = -1; field = &expr->field; - if (field->system == start_system && + if (field->ftype != FIELD_TO && + field->system == start_system && field->event_name == start_event) { int type; - type = verify_field_type(tep, table->sb, expr); + 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); + type, cnt); } else if (table->to) { ret = tracefs_synth_add_end_field(synth, field->field, field->label); @@ -1498,16 +1534,18 @@ static struct tracefs_synth *build_synth(struct tep_handle *tep, 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); + &filter_event, &ftype); if (ret < 0) goto free; start = filter_system == start_system && - filter_event == start_event; + filter_event == start_event && + ftype != FIELD_TO; if (start) started = &started_start; diff --git a/src/tracefs-tools.c b/src/tracefs-tools.c index 489be28..8e7b46d 100644 --- a/src/tracefs-tools.c +++ b/src/tracefs-tools.c @@ -1055,7 +1055,7 @@ int tracefs_tracer_set(struct tracefs_instance *instance, out: tracefs_put_tracing_file(tracer_path); close(fd); - return ret; + return ret > 0 ? 0 : ret; } int tracefs_tracer_clear(struct tracefs_instance *instance) diff --git a/src/tracefs-utils.c b/src/tracefs-utils.c index 7b1f816..9acf2ad 100644 --- a/src/tracefs-utils.c +++ b/src/tracefs-utils.c @@ -29,6 +29,7 @@ #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 @@ -84,13 +85,8 @@ static int mount_debugfs(void) return ret; } -/** - * trace_find_tracing_dir - Find tracing 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(void) +/* Exported for testing purpose only */ +__hidden char *find_tracing_dir(bool debugfs, bool mount) { char *debug_str = NULL; char fspath[PATH_MAX+1]; @@ -109,9 +105,11 @@ __hidden char *trace_find_tracing_dir(void) STR(PATH_MAX) "s %99s %*s %*d %*d\n", fspath, type) == 2) { - if (strcmp(type, "tracefs") == 0) + 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); @@ -121,14 +119,21 @@ __hidden char *trace_find_tracing_dir(void) } fclose(fp); - if (strcmp(type, "tracefs") != 0) { - if (mount_tracefs() < 0) { + 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_debugfs() < 0) { - tracefs_warning("debugfs not mounted, please mount"); + if (!mount || mount_debugfs() < 0) { + if (mount) + tracefs_warning("debugfs not mounted, please mount"); free(debug_str); return NULL; } @@ -156,6 +161,86 @@ __hidden char *trace_find_tracing_dir(void) } /** + * 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. @@ -165,14 +250,36 @@ const char *tracefs_tracing_dir(void) { static const char *tracing_dir; - if (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 = trace_find_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 * @@ -487,3 +594,31 @@ int tracefs_list_size(char **list) 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; +} diff --git a/utest/Makefile b/utest/Makefile index 74bf7e6..f10b709 100644 --- a/utest/Makefile +++ b/utest/Makefile @@ -15,27 +15,19 @@ LIBS += -lcunit \ $(obj)/lib/libtracefs.a OBJS := $(OBJS:%.o=$(bdir)/%.o) -DEPS := $(OBJS:$(bdir)/%.o=$(bdir)/.%.d) $(bdir): @mkdir -p $(bdir) $(OBJS): | $(bdir) -$(DEPS): | $(bdir) -$(bdir)/trace-utest: $(OBJS) +$(bdir)/trace-utest: $(OBJS) $(obj)/lib/libtracefs.a $(Q)$(do_app_build) $(bdir)/%.o: %.c $(Q)$(call do_fpic_compile) -$(DEPS): $(bdir)/.%.d: %.c - $(Q)$(CC) -M $(CPPFLAGS) $(CFLAGS) $< > $@ - $(Q)$(CC) -M -MT $(bdir)/$*.o $(CPPFLAGS) $(CFLAGS) $< > $@ - -$(OBJS): $(bdir)/%.o : $(bdir)/.%.d - -dep_includes := $(wildcard $(DEPS)) +-include .*.d test: $(TARGETS) diff --git a/utest/README b/utest/README index ed92042..647e460 100644 --- a/utest/README +++ b/utest/README @@ -13,3 +13,6 @@ using a precompiled distro package: libcunit1 libcunit1-doc libcunit1-dev + + openSUSE and SLE: + cunit-devel diff --git a/utest/tracefs-utest.c b/utest/tracefs-utest.c index 3f63837..e0e3c07 100644 --- a/utest/tracefs-utest.c +++ b/utest/tracefs-utest.c @@ -11,13 +11,18 @@ #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 "trasefs library" +#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 @@ -44,6 +49,10 @@ #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 { @@ -86,20 +95,51 @@ static int test_callback(struct tep_event *event, struct tep_record *record, 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) { - int cpus = sysconf(_SC_NPROCESSORS_CONF); - cpu_set_t *cpuset, *cpusave; - int cpu_size; char *path; int i, fd; + int cpus; int ret; - cpuset = CPU_ALLOC(cpus); - cpusave = CPU_ALLOC(cpus); - cpu_size = CPU_ALLOC_SIZE(cpus); - CPU_ZERO_S(cpu_size, cpuset); - sched_getaffinity(0, cpu_size, cpusave); + cpus = sysconf(_SC_NPROCESSORS_CONF); + save_affinity(); path = tracefs_instance_get_file(instance, "trace_marker"); CU_TEST(path != NULL); @@ -113,17 +153,13 @@ static void test_iter_write(struct tracefs_instance *instance) if (!test_array[i].value) test_array[i].value++; CU_TEST(test_array[i].cpu < cpus); - CPU_ZERO_S(cpu_size, cpuset); - CPU_SET(test_array[i].cpu, cpuset); - sched_setaffinity(0, cpu_size, cpuset); + set_affinity(test_array[i].cpu); ret = write(fd, test_array + i, sizeof(struct test_sample)); CU_TEST(ret == sizeof(struct test_sample)); } - sched_setaffinity(0, cpu_size, cpusave); + reset_affinity(); close(fd); - CPU_FREE(cpuset); - CPU_FREE(cpusave); } @@ -382,6 +418,538 @@ 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; @@ -1404,6 +1972,9 @@ static void test_instance_tracers(struct tracefs_instance *instance) 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, " "); @@ -1701,8 +2272,11 @@ void del_trace_dir(char *dir) 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); @@ -1717,6 +2291,22 @@ static void test_custom_trace_dir(void) 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) @@ -1750,6 +2340,12 @@ void test_tracefs_lib(void) 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", @@ -1762,6 +2358,10 @@ void test_tracefs_lib(void) 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", |