← Back to LTTng's blog

Announcing the release of LTTng 2.7

Comments

We're happy to announce the release of LTTng 2.7 "Herbe à Détourne". Following on the coattails of a conservative 2.6 release, LTTng 2.7 introduces a number of long-requested features.

It is also our first release since we have started pouring considerable efforts into our Continuous Integration setup to test the complete toolchain on every build configuration and platform we support. We are not done yet, but we're getting there fast!

While we have always been diligent about robustness, we have, in the past, mostly relied on our users to report problems occurring on non-Intel platforms or under non-default build scenarios. Now, with this setup in place at EfficiOS, it has become very easy for us to ensure new features and fixes work reliably and can be deployed safely for most of our user base.

Testing tracers—especially kernel tracers—poses a number of interesting challenges which we'll cover in a follow-up post. For now, let's talk features!

Kernel filtering

While LTTng has supported filtering of user space events for a long time, LTTng 2.7 now makes it possible to attach a filter to a kernel event when it is first enabled.

Filters are specified by C-like expressions which are dynamically evaluated against events to reduce the amount of events being recorded to a trace. For instance, it is now possible to trace every invocation of the write() system call with a payload larger than 100 bytes.

This can be achieved by enabling the write() system call and providing a filter expression.

lttng enable-event --kernel --syscall write --filter "count > 100"

This is a handy tool to quickly reduce the amount of data to sift through when investigating a problem.

[15:02:28.557877153] (+0.003936173) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "ssh" }, { fd = 5, buf = 94097319473858, count = 4095 }
[15:02:28.558899341] (+0.001022188) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "ssh" }, { fd = 5, buf = 94097319468416, count = 3148 }
[15:02:31.323397195] (+2.764497854) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_IOThread" }, { fd = 146, buf = 15269810037264, count = 176 }
[15:02:31.323687516] (+0.000290321) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_IOThread" }, { fd = 146, buf = 15269790656752, count = 169 }
[15:02:31.325460834] (+0.001773318) XThink syscall_entry_write: { cpu_id = 0 }, { procname = "BrowserBlocking" }, { fd = 59, buf = 15269860282392, count = 520192 }
[15:02:31.325564719] (+0.000103885) XThink syscall_entry_write: { cpu_id = 0 }, { procname = "BrowserBlocking" }, { fd = 59, buf = 140309699067904, count = 3976 }
[15:02:31.333541760] (+0.007977041) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_FileThre" }, { fd = 292, buf = 15269789575704, count = 279 }
[15:02:31.334523566] (+0.000981806) XThink syscall_entry_write: { cpu_id = 3 }, { procname = "Chrome_FileThre" }, { fd = 292, buf = 15269789575704, count = 279 }
[15:02:31.335080492] (+0.000556926) XThink syscall_entry_write: { cpu_id = 3 }, { procname = "Chrome_FileThre" }, { fd = 292, buf = 15269789575704, count = 279 }
[15:02:31.342076188] (+0.006995696) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_IOThread" }, { fd = 124, buf = 15269798800512, count = 300 }
[15:02:31.414602941] (+0.072526753) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_IOThread" }, { fd = 124, buf = 15269821225824, count = 203 }
[15:02:31.414715714] (+0.000112773) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_IOThread" }, { fd = 124, buf = 15269807877216, count = 263 }
[15:02:31.427803794] (+0.013088080) XThink syscall_entry_write: { cpu_id = 0 }, { procname = "Chrome_IOThread" }, { fd = 124, buf = 15269818173632, count = 394 }
[15:02:31.917912677] (+0.400593753) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_HistoryT" }, { fd = 147, buf = 15269714267400, count = 512 }
[15:02:31.917931214] (+0.000018537) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_HistoryT" }, { fd = 61, buf = 15269741980936, count = 2048 }
[15:02:31.918080718] (+0.000149504) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_HistoryT" }, { fd = 181, buf = 15269720687112, count = 4096 }
[15:02:31.918105831] (+0.000025113) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_HistoryT" }, { fd = 181, buf = 15269782003720, count = 4096 }
[15:02:31.918118110] (+0.000012279) XThink syscall_entry_write: { cpu_id = 2 }, { procname = "Chrome_HistoryT" }, { fd = 181, buf = 15269791514632, count = 4096 }

Process (PID) tracker

Filter expressions are a great tool to significantly reduce the amount of events being traced on a given system. However, users have often let us know they wished they could only track a given process, as other tools—such as strace—do.

To this end, LTTng 2.7 introduces the notion of trackers, which are used to follow events associated to a given resource across multiple domains.

To track events associated with a given Process Identifier (PID), a PID tracker can be assigned to a tracing session. Such a tracker acts as a top-level filter which is applied to all events in a session's domain.

Let's run through a small example to track the handling of an HTTP request by Node.js across the kernel and Node.js itself.

$ lttng create node-demo
Session node-demo created.
Traces will be written in /home/jgalar/lttng-traces/node-demo-20151005-180235

$ pidof node
956

$ lttng track --kernel --pid 956
PID 956 tracked in session node-demo

$ lttng track --userspace --pid 956
PID 956 tracked in session node-demo

$ lttng enable-event --kernel --all
All Kernel events are enabled in channel channel0

$ lttng enable-event --userspace --all
All UST events are enabled in channel channel0

$ lttng start
[...]

After a while, we can stop the session and have a look at the resulting trace. First, we can see the kernel returning from the epoll(), which node uses to wait for a new connection.

[18:03:36.847046439] (+0.000007750) XThink syscall_exit_epoll_wait: { cpu_id = 3 }, { ret = 1, events = 140728832813824 }

node then performs a read() on the socket and emits the http_server_request event, signalling the start of its processing.

[18:03:36.847059675] (+0.000013236) XThink syscall_entry_read: { cpu_id = 3 }, { fd = 26, count = 65536 }
[18:03:36.847064285] (+0.000004610) XThink skb_copy_datagram_iovec: { cpu_id = 3 }, { skbaddr = 18446612148662969576, len = 310 }
[18:03:36.847068539] (+0.000004254) XThink syscall_exit_read: { cpu_id = 3 }, { ret = 310, buf = 49358448 }
[18:03:36.847144549] (+0.000076010) XThink kmem_mm_page_alloc: { cpu_id = 3 }, { page = 0xFFFFEA0000D0D940, order = 0, gfp_flags = 164058, migratetype = 2 }

[18:03:36.847326206] (+0.000181657) XThink node:http_server_request: { cpu_id = 3 }, { url = "/", method = "GET", forwardedFor = "" }

Since the Node.js instrumentation is still relatively terse, it is hard to have an idea of exactly what goes on during the processing of that request. However, we see that 32 page allocation events occur in kernel space before node writes the result of the request using the writev() system call.

[18:03:36.847402792] (+0.000076586) XThink kmem_mm_page_alloc_zone_locked: { cpu_id = 3 }, { page = 0xFFFFEA0000DA1800, order = 0, migratetype = 2 }
[18:03:36.847403846] (+0.000001054) XThink kmem_mm_page_alloc_zone_locked: { cpu_id = 3 }, { page = 0xFFFFEA0000DA1840, order = 0, migratetype = 2 }
[18:03:36.847404279] (+0.000000433) XThink kmem_mm_page_alloc_zone_locked: { cpu_id = 3 }, { page = 0xFFFFEA0000DA1880, order = 0, migratetype = 2 }
[18:03:36.847404652] (+0.000000373) XThink kmem_mm_page_alloc_zone_locked: { cpu_id = 3 }, { page = 0xFFFFEA0000DA18C0, order = 0, migratetype = 2 }
[18:03:36.847405132] (+0.000000480) XThink kmem_mm_page_alloc_zone_locked: { cpu_id = 3 }, { page = 0xFFFFEA0000DA1900, order = 0, migratetype = 2 }
[18:03:36.847405562] (+0.000000430) XThink kmem_mm_page_alloc_zone_locked: { cpu_id = 3 }, { page = 0xFFFFEA0000DA1940, order = 0, migratetype = 2 }
[18:03:36.847405962] (+0.000000400) XThink kmem_mm_page_alloc_zone_locked: { cpu_id = 3 }, { page = 0xFFFFEA0000DA1980, order = 0, migratetype = 2 }
[...]
[18:03:36.847416719] (+0.000001500) XThink kmem_mm_page_alloc: { cpu_id = 3 }, { page = 0xFFFFEA0000DA1800, order = 0, gfp_flags = 164058, migratetype = 2 }
[18:03:36.847509646] (+0.000092927) XThink syscall_entry_writev: { cpu_id = 3 }, { fd = 26, vec = 49184128, vlen = 2 }

We can then follow the path of this request in the network stack and see node return to a quiescent state, waiting for the next event on its connection socket.

[18:03:36.847516326] (+0.000006680) XThink kmem_cache_alloc_node: { cpu_id = 3 }, { call_site = 0xFFFFFFFF81460ACB, ptr = 0xFFFF8803CE762600, bytes_req = 512, bytes_alloc = 512, gfp_flags = 208, node = -1 }
[18:03:36.847517752] (+0.000001426) XThink kmem_kmalloc_node: { cpu_id = 3 }, { call_site = 0xFFFFFFFF81460AF7, ptr = 0xFFFF88043AFD3800, bytes_req = 2048, bytes_alloc = 2048, gfp_flags = 66256, node = -1 }
[18:03:36.847524762] (+0.000007010) XThink net_dev_queue: { cpu_id = 3 }, { skbaddr = 18446612148662970088, len = 222, name = "lo" }
[18:03:36.847527269] (+0.000002507) XThink net_if_rx: { cpu_id = 3 }, { skbaddr = 18446612148662970088, len = 208, name = "lo" }
[18:03:36.847528352] (+0.000001083) XThink irq_softirq_raise: { cpu_id = 3 }, { vec = 3 }
[18:03:36.847529186] (+0.000000834) XThink net_dev_xmit: { cpu_id = 3 }, { skbaddr = 18446612148662970088, rc = 0, len = 222, name = "lo" }
[18:03:36.847530379] (+0.000001193) XThink irq_softirq_entry: { cpu_id = 3 }, { vec = 3 }
[18:03:36.847531372] (+0.000000993) XThink net_if_receive_skb: { cpu_id = 3 }, { skbaddr = 18446612148662970088, len = 208, name = "lo" }
[18:03:36.847536179] (+0.000004807) XThink napi_poll: { cpu_id = 3 }, { napi = 18446612151359333392, dev_name = "(no_device)" }
[18:03:36.847536799] (+0.000000620) XThink irq_softirq_exit: { cpu_id = 3 }, { vec = 3 }
[18:03:36.847539112] (+0.000002313) XThink timer_start: { cpu_id = 3 }, { timer = 18446612134541520144, function = 18446744071583888432, expires = 4296212450, now = 4296212389, flags = 3 }
[18:03:36.847541446] (+0.000002334) XThink kmem_kfree: { cpu_id = 3 }, { call_site = 0xFFFFFFFF811D0DBF, ptr = 0x0 }
[18:03:36.847543466] (+0.000002020) XThink syscall_exit_writev: { cpu_id = 3 }, { ret = 156, vec = 49184128 }

[18:03:36.847595932] (+0.000052466) XThink node:http_server_response: { cpu_id = 3 }, { port = 58200, remote = "::ffff:127.0.0.1", fd = 26 }

[18:03:36.847678026] (+0.000082094) XThink kmem_mm_page_alloc: { cpu_id = 3 }, { page = 0xFFFFEA0000DA1840, order = 0, gfp_flags = 164058, migratetype = 2 }
[18:03:36.847734886] (+0.000056860) XThink syscall_entry_epoll_ctl: { cpu_id = 3 }, { epfd = 7, op = 3, fd = 26, event = 140728832813680 }

This demonstrates how trackers improve the signal-to-noise ratio of traces. Of course, this is only the start as we hope to make it even easier to hone-in on select components, processes or resources as we implement more trackers.

Kernel tracer wildcard support

Another feature we added, bringing the kernel tracer to parity with the user space tracer, is the addition of support for event name wildcards.

This little addition makes it possible to enable every event of a given kernel subsystem. For instance, enabling events matching power* will result in a trace containing all power management events.

$ lttng enable-event --kernel 'power*'

On Linux 4.2.2, this results in a trace containing the following events.

power_cpu_idle
power_cpu_frequency
power_machine_suspend
power_wakeup_source_activate
power_wakeup_source_deactivate
power_clock_enable
power_clock_disable
power_clock_set_rate
power_domain_target

Python logging support

Joining the ranks of java.util.logging and log4j, the Herbe à Détourne release adds support for the Python logging module for both Python 2.7 and 3.x.

In short, this means that importing the lttngust module makes it possible to funnel all Python logs through LTTng, automatically correlating them with events emitted by the kernel and other user space applications.

A complete guide to Python tracing has also been added to the official documentation.

File-backed user space buffers

This new LTTng-UST feature allows the mapping of ring buffers onto filesystems. This proves useful in combination with PRAMFS or DAX+pmem (Linux kernels 4.1+) to store tracer ring buffers in persistent memory upon an unexpected system reboot.

These buffers can be salvaged and converted to regular CTF traces using the newly introduced lttng-crash utility to see the events that led to a crash.

Support for custom clock sources and CPU identifier plugins

In the interest of keeping LTTng as unintrusive as possible, LTTng 2.7 introduces support for platform-specific plugins providing custom implementations of getcpu() and clock sources.

getcpu()/sched_getcpu() override plugins can prove useful in cases where direct access to architecture-specific registers provides a more efficient way of accessing the current CPU number than using the Linux's sched_getcpu() vDSO/system call.

A code sample demonstrating the implementation of such a plugin is available here.

Clock override plugins, which may be implemented as shared objects, useful in cases where direct access to architecture-specific clocks is available, and when they should be used in place of the Linux kernel monotonic clock.

We also have a code sample showing how to implement such a clock source here.

LTTng 2.8 will introduce support for custom kernel space clock sources. This feature is already available as part of LTTng-modules's master branch.

tracelog() instrumentation support

Multiple developers have made it clear that we needed to provide a quick and easy way to convert existing logging instrumentation to LTTng tracepoints.

To this end, we have introduced the tracelog() instrumentation facility with the goal of helping out C/C++ developers who wish to move away from existing text-based logging frameworks or need to accommodate multiple tracing backends.

tracelog() offers a very straightforward API. It accepts a log level, a format string and a variable number of arguments, not unlike printf().

Tracing text statements still has a number of drawbacks (runtime inefficiency, bloating the resulting trace, etc.). Nonetheless, tracelog() allows developers to take advantage of the complete LTTng feature set from the get-go.

Try it out!

We're very excited to have these new additions see the light of day. LTTng 2.7 "Herbe à Détourne" should be rolling out to your distributions soon. Until then, feel free to give it a try by building from source.

We wish to thank everyone who has contributed to this release!