A Light-Weight Event Tracing Toolkit

about - download - documentation - contact

About

This is the project page for the Feather-Trace toolkit as presented at the Workshop on Operating Systems Platforms for Embedded Real-Time applications (OSPERT) 2007 in our paper Feather-Trace: A Light-Weight Event Tracing Toolkit

Feather-Trace is a very light-weight static event tracing toolkit for the Intel x86 platform. It has the following notable features:

The source code is made available under the terms of the BSD license.

Download

Note: These stand-alone patches and the Feather-Trace tarball are somewhat out of date. A more recent implementation is distributed as part of the LITMUSRT project.

Feather-Trace

This is the main source code release. It contains the operating system agnostic part of Feather-Trace. Further, it also includes some glue code for user space tracing, and a very simple example that demonstrates how to use the Feather-Trace macros. Download this tarball to start embedding Feather-Trace into an existing (operating) system.

Feather-Trace: feathertrace.tar.bz2 (tested with GCC 4.1.2)

Linux

As described in the paper, we modified the {spin,rw}_trylock/_lock/_unlock macros to obtain two timestamps for each lock request: (1) immediately after the lock was acquired and (2) just before the lock is released. Further, we developed a custom character device driver to export the trace data to userspace. The complete modification is available as a patch against Linux 2.6.20.

Complete OSPERT'07 modification: ospert_complete.patch (against Linux 2.6.20)

The complete patch contains the modifications to the locking primitives. That may be not what you want if you intent to trace other aspects of the kernel yourself. The following patch, which only introduces the infrastructure, might be more appropriate as a starting point for further modifications.

Feather-Trace on Linux infrastructure: feathertrace_linux_2.6.20.patch (against Linux 2.6.20)

Note: The device driver as given in the patch is not safe for concurrent access from several user space clients because we did not want to introduce additional locks while tracing locking patterns. To avoid problems, either use only one user space client at a time or add a mutex and reference counting to the driver.

License: To comply with the Linux kernel license, these patches are made available under the terms of the GNU General Public License (GPL).

Documentation

The toolkit consists of two components: event trigger macros and single-reader/multi-writer buffers.

Event Triggers

Event trigger macros allow the user to embed conditional function calls in the source code of an existing system. Each trigger is associated with an event ID. The event trigger macros are named ft_eventX(), where X, which indicates the number of user specified parameters, ranges from 0 to 5. The first parameter is always the event ID, the second parameter must be the name of the callback function (function pointers are not supported). Example:

ft_eventX(event_id, callback, param1, param2, ..., paramX)

Callback Functions

A callback function must have the return type void. A callback function that will be used with ft_eventX() must accept X + 1 arguments. The first argument is always the event ID. Each argument, usually a pointer or an integer, must be 32bit wide. Further, a callback function must be declared to follow the feather_callback calling convention. Example:

feather_callback void foo(int id, int param1, int param2, ..., int paramX) 
{
	...
}

Event Activation

Initially, all events are disabled and none of the triggers will invoke their callback function. To enable an event, use the function ft_enable_event(event_id). Similarly, events can be disabled with the function ft_disable_event(event_id). Note, that these functions will enable and disable all triggers that share the given event id. Events may be enabled/disabled multiple times and in a nested fashion. For example, the following code defines a callback foo(), embeds two event triggers with ft_event1() and activates them.

#include "feather_userspace.h" /* user space glue code */

/* foo() takes two parameters and can be used with  ft_event1()*/
feather_callback void foo(int id, char* msg) 
{
	printf("Event %d, msg=%s\n", id, msg);
}

int main(int argc, char** argv)
{
	/* first call some user space glue code that makes 
	 * the text segment writable
         */
	INIT_FT_EVENTS();

	ft_event1(123, foo, "Hello World!");

	ft_enable_event(123);

	ft_event1(123, foo, "Goodbye World!");

	return 0;
}

This will result in the output:

Event 123, msg=Goodbye World!

Note, that there is no output from the first event trigger because the event 123 is initially disabled.

Single-Reader/Multi-Writer Buffers

Feather-Trace provides a multiprocessor-safe, wait-free single-reader/multi-writer FIFO buffer (struct ft_buffer). A method for allocating memory for the buffer must be provided by the glue code. The buffer must be initialized with the function init_ft_buffer() once memory has been set aside. The buffer manages a number of slots that are all of the same (static) size. As explained in the paper, the number of slots must divide 232.

Multiple writers and a single reader may access the buffer concurrently. Writers access the buffer by first allocating a slot with ft_buffer_start_write(). If a slot has been allocated, then the writer may store its data and finish the operation with a call to ft_buffer_finish_write(). Readers can obtain the data one slot at a time by calling ft_buffer_read(). A complete example is given below.

/* define the structure of a slot */
struct event_data
{
	int  id;
	char msg[40];	
};

/* the global buffer for callbacks*/
struct ft_buffer* buf;

/* this time around store the parameters in the buffer */
feather_callback void foo(int id, char* msg) 
{	
	struct event_data* data;				/* a local pointer that will 
								 * point into the buffer  */	
								 
	if (ft_buffer_start_write(buf, (void**) &data)) {	/* try to allocate a slot */
		data->id = id;
		strncpy(data->msg, msg, 40);
		ft_buffer_finish_write(buf, data);		/* finish operation       */
	}
}

int main(int argc, char** argv)
{
	struct event_data data;

        INIT_FT_EVENTS();

	buf = alloc_ft_buffer(4, sizeof(struct event_data));	/* glue code	          */
	if (!buf)
		exit(1);

	ft_enable_event(123);
	ft_enable_event(234);

        ft_event1(123, foo, "Hello World!");

        ft_event1(234, foo, "Nice to meet you!");

        ft_event1(123, foo, "How are you?");

        ft_event1(234, foo, "Great, thanks!");

        ft_event1(123, foo, "Goodbye World!");	


	/* dump all the stored messages */
	while (ft_buffer_read(buf, &data)) {
		printf("found in buffer: %d->%s\n", data.id, data.msg);
	}

        return 0;
}

This will result in the output:

found in buffer: 123->Hello World!
found in buffer: 234->Nice to meet you!
found in buffer: 123->How are you?
found in buffer: 234->Great, thanks!

Note, that the last message is missing. No buffer space could be allocated for it since the buffer can only hold four messages at a time.

All the examples (and a Makefile) are also contained in the source tarball.

Contact

The authors of Feather-Trace are:

Björn B. Brandenburg and James H. Anderson
The University of North Carolina at Chapel Hill
If you have any questions feel free to contact the first author, who can be reached at  bbb[AT]cs.unc.edu.

Credits

The Feather-Trace logo was designed by Jasper McChesney of Break for Sense Design.