%poky; ] > Basic Usage (with examples) for each of the Yocto Tracing Tools This chapter presents basic usage examples for each of the tracing tools.
perf The 'perf' tool is the profiling and tracing tool that comes bundled with the Linux kernel. Don't let the fact that it's part of the kernel fool you into thinking that it's only for tracing and profiling the kernel - you can indeed use it to trace and profile just the kernel , but you can also use it to profile specific applications separately (with or without kernel context), and you can also use it to trace and profile the kernel and all applications on the system simultaneously to gain a system-wide view of what's going on. In many ways, it aims to be a superset of all the tracing and profiling tools available in Linux today, including all the other tools covered in this HOWTO. The past couple of years have seen perf subsume a lot of the functionality of those other tools, and at the same time those other tools have removed large portions of their previous functionality and replaced it with calls to the equivalent functionality now implemented by the perf subsystem. Extrapolation suggests that at some point those other tools will simply become completely redundant and go away; until then, we'll cover those other tools in these pages and in many cases show how the same things can be accomplished in perf and the other tools when it seems useful to do so. The coverage below details some of the most common ways you'll likely want to apply the tool; full documentation can be found either within the tool itself or in the man pages at perf(1).
Setup For this section, we'll assume you've already performed the basic setup outlined in the General Setup section. In particular, you'll get the most mileage out of perf if you profile an image built with INHIBIT_PACKAGE_STRIP = "1" in your local.conf. perf runs on the target system for the most part. You can archive profile data and copy it to the host for analysis, but for the rest of this document we assume you've ssh'ed to the host and will be running the perf commands on the target.
Basic Usage The perf tool is pretty much self-documenting. To remind yourself of the available commands, simply type 'perf', which will show you basic usage along with the available perf subcommands: root@crownbay:~# perf usage: perf [--version] [--help] COMMAND [ARGS] The most commonly used perf commands are: annotate Read perf.data (created by perf record) and display annotated code archive Create archive with object files with build-ids found in perf.data file bench General framework for benchmark suites buildid-cache Manage build-id cache. buildid-list List the buildids in a perf.data file diff Read two perf.data files and display the differential profile evlist List the event names in a perf.data file inject Filter to augment the events stream with additional information kmem Tool to trace/measure kernel memory(slab) properties kvm Tool to trace/measure kvm guest os list List all symbolic event types lock Analyze lock events probe Define new dynamic tracepoints record Run a command and record its profile into perf.data report Read perf.data (created by perf record) and display the profile sched Tool to trace/measure scheduler properties (latencies) script Read perf.data (created by perf record) and display trace output stat Run a command and gather performance counter statistics test Runs sanity tests. timechart Tool to visualize total system behavior during a workload top System profiling tool. See 'perf help COMMAND' for more information on a specific command.
Using perf to do Basic Profiling As a simple test case, we'll profile the 'wget' of a fairly large file, which is a minimally interesting case because it has both file and network I/O aspects, and at least in the case of standard Yocto images, it's implemented as part of busybox, so the methods we use to analyze it can be used in a very similar way to the whole host of supported busybox applets in Yocto. root@crownbay:~# rm linux-2.6.19.2.tar.bz2; \ wget http://downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2 The quickest and easiest way to get some basic overall data about what's going on for a particular workload it to profile it using 'perf stat'. 'perf stat' basically profiles using a few default counters and displays the summed counts at the end of the run: root@crownbay:~# perf stat wget http://downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2 Connecting to downloads.yoctoproject.org (140.211.169.59:80) linux-2.6.19.2.tar.b 100% |***************************************************| 41727k 0:00:00 ETA Performance counter stats for 'wget http://downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2': 4597.223902 task-clock # 0.077 CPUs utilized 23568 context-switches # 0.005 M/sec 68 CPU-migrations # 0.015 K/sec 241 page-faults # 0.052 K/sec 3045817293 cycles # 0.663 GHz <not supported> stalled-cycles-frontend <not supported> stalled-cycles-backend 858909167 instructions # 0.28 insns per cycle 165441165 branches # 35.987 M/sec 19550329 branch-misses # 11.82% of all branches 59.836627620 seconds time elapsed Many times such a simple-minded test doesn't yield much of interest, but sometimes it does (see Real-world Yocto bug (slow loop-mounted write speed)). Also, note that 'perf stat' isn't restricted to a fixed set of counters - basically any event listed in the output of 'perf list' can be tallied by 'perf stat'. For example, suppose we wanted to see a summary of all the events related to kernel memory allocation/freeing along with cache hits and misses: root@crownbay:~# perf stat -e kmem:* -e cache-references -e cache-misses wget http:// downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2 Connecting to downloads.yoctoproject.org (140.211.169.59:80) linux-2.6.19.2.tar.b 100% |***************************************************| 41727k 0:00:00 ETA Performance counter stats for 'wget http://downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2': 5566 kmem:kmalloc 125517 kmem:kmem_cache_alloc 0 kmem:kmalloc_node 0 kmem:kmem_cache_alloc_node 34401 kmem:kfree 69920 kmem:kmem_cache_free 133 kmem:mm_page_free 41 kmem:mm_page_free_batched 11502 kmem:mm_page_alloc 11375 kmem:mm_page_alloc_zone_locked 0 kmem:mm_page_pcpu_drain 0 kmem:mm_page_alloc_extfrag 66848602 cache-references 2917740 cache-misses # 4.365 % of all cache refs 44.831023415 seconds time elapsed So 'perf stat' gives us a nice easy way to get a quick overview of what might be happening for a set of events, but normally we'd need a little more detail in order to understand what's going on in a way that we can act on in a useful way. To dive down into a next level of detail, we can use 'perf record'/'perf report' which will collect profiling data and present it to use using an interactive text-based UI (or simply as text if we specify --stdio to 'perf report'). As our first attempt at profiling this workload, we'll simply run 'perf record', handing it the workload we want to profile (everything after 'perf record' and any perf options we hand it - here none - will be executedin a new shell). perf collects samples until the process exits and records them in a file named 'perf.data' in the current working directory. root@crownbay:~# perf record wget http://downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2 Connecting to downloads.yoctoproject.org (140.211.169.59:80) linux-2.6.19.2.tar.b 100% |************************************************| 41727k 0:00:00 ETA [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.176 MB perf.data (~7700 samples) ] To see the results in a 'text-based UI' (tui), simply run 'perf report', which will read the perf.data file in the current working directory and display the results in an interactive UI: root@crownbay:~# perf report The above screenshot displays a 'flat' profile, one entry for each 'bucket' corresponding to the functions that were profiled during the profiling run, ordered from the most popular to the least (perf has options to sort in various orders and keys as well as display entries only above a certain threshold and so on - see the perf documentation for details). Note that this includes both userspace functions (entries containing a [.]) and kernel functions accounted to the process (entries containing a [k]). (perf has command-line modifiers that can be used to restrict the profiling to kernel or userspace, among others). Notice also that the above report shows an entry for 'busybox', which is the executable that implements 'wget' in Yocto, but that instead of a useful function name in that entry, it displays an not-so-friendly hex value instead. The steps below will show how to fix that problem. Before we do that, however, let's try running a different profile, one which shows something a little more interesting. The only difference between the new profile and the previous one is that we'll add the -g option, which will record not just the address of a sampled function, but the entire callchain to the sampled function as well: root@crownbay:~# perf record -g wget http://downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2 Connecting to downloads.yoctoproject.org (140.211.169.59:80) linux-2.6.19.2.tar.b 100% |************************************************| 41727k 0:00:00 ETA [ perf record: Woken up 3 times to write data ] [ perf record: Captured and wrote 0.652 MB perf.data (~28476 samples) ] root@crownbay:~# perf report Using the callgraph view, we can actually see not only which functions took the most time, but we can also see a summary of how those functions were called and learn something about how the program interacts with the kernel in the process. Notice that each entry in the above screenshot now contains a '+' on the left-hand side. This means that we can expand the entry and drill down into the callchains that feed into that entry. Pressing 'enter' on any one of them will expand the callchain (you can also press 'E' to expand them all at the same time or 'C' to collapse them all). In the screenshot above, we've toggled the __copy_to_user_ll() entry and several subnodes all the way down. This lets us see which callchains contributed to the profiled __copy_to_user_ll() function which contributed 1.77% to the total profile. As a bit of background explanation for these callchains, think about what happens at a high level when you run wget to get a file out on the network. Basically what happens is that the data comes into the kernel via the network connection (socket) and is passed to the userspace program 'wget' (which is actually a part of busybox, but that's not important for now), which takes the buffers the kernel passes to it and writes it to a disk file to save it. The part of this process that we're looking at in the above call stacks is the part where the kernel passes the data it's read from the socket down to wget i.e. a copy-to-user. Notice also that here there's also a case where the a hex value is displayed in the callstack, here in the expanded sys_clock_gettime() function. Later we'll see it resolve to a userspace function call in busybox. The above screenshot shows the other half of the journey for the data - from the wget program's userspace buffers to disk. To get the buffers to disk, the wget program issues a write(2), which does a copy-from-user to the kernel, which then takes care via some circuitous path (probably also present somewhere in the profile data), to get it safely to disk. Now that we've seen the basic layout of the profile data and the basics of how to extract useful information out of it, let's get back to the task at hand and see if we can get some basic idea about where the time is spent in the program we're profiling, wget. Remember that wget is actually implemented as an applet in busybox, so while the process name is 'wget', the executable we're actually interested in is busybox. So let's expand the first entry containing busybox: Again, before we expanded we saw that the function was labeled with a hex value instead of a symbol as with most of the kernel entries. Expanding the busybox entry doesn't make it any better. The problem is that perf can't find the symbol information for the busybox binary, which is actually stripped out by the Yocto build system. One way around that is to put the following in your local.conf when you build the image: INHIBIT_PACKAGE_STRIP = "1" However, we already have an image with the binaries stripped, so what can we do to get perf to resolve the symbols? Basically we need to install the debuginfo for the busybox package. To generate the debug info for the packages in the image, we can to add dbg-pkgs to EXTRA_IMAGE_FEATURES in local.conf. For example: EXTRA_IMAGE_FEATURES = "debug-tweaks tools-profile dbg-pkgs" Additionally, in order to generate the type of debuginfo that perf understands, we also need to add the following to local.conf: PACKAGE_DEBUG_SPLIT_STYLE = 'debug-file-directory' Once we've done that, we can install the debuginfo for busybox. The debug packages once built can be found in build/tmp/deploy/rpm/* on the host system. Find the busybox-dbg-...rpm file and copy it to the target. For example: [trz@empanada core2]$ scp /home/trz/yocto/crownbay-tracing-dbg/build/tmp/deploy/rpm/core2/busybox-dbg-1.20.2-r2.core2.rpm root@192.168.1.31: root@192.168.1.31's password: busybox-dbg-1.20.2-r2.core2.rpm 100% 1826KB 1.8MB/s 00:01 Now install the debug rpm on the target: root@crownbay:~# rpm -i busybox-dbg-1.20.2-r2.core2.rpm Now that the debuginfo is installed, we see that the busybox entries now display their functions symbolically: If we expand one of the entries and press 'enter' on a leaf node, we're presented with a menu of actions we can take to get more information related to that entry: One of these actions allows us to show a view that displays a busybox-centric view of the profiled functions (in this case we've also expanded all the nodes using the 'E' key): Finally, we can see that now that the busybox debuginfo is installed, the previously unresolved symbol in the sys_clock_gettime() entry mentioned previously is now resolved, and shows that the sys_clock_gettime system call that was the source of 6.75% of the copy-to-user overhead was initiated by the handle_input() busybox function: At the lowest level of detail, we can dive down to the assembly level and see which instructions caused the most overhead in a function. Pressing 'enter' on the 'udhcpc_main' function, we're again presented with a menu: Selecting 'Annotate udhcpc_main', we get a detailed listing of percentages by instruction for the udhcpc_main function. From the display, we can see that over 50% of the time spent in this function is taken up by a couple tests and the move of a constant (1) to a register: As a segue into tracing, let's try another profile using a different counter, something other than the default 'cycles'. The tracing and profiling infrastructure in Linux has become unified in a way that allows us to use the same tool with a completely different set of counters, not just the standard hardware counters that traditionally tools have had to restrict themselves to (of course the traditional tools can also make use of the expanded possibilities now available to them, and in some cases have, as mentioned previously). We can get a list of the available events that can be used to profile a workload via 'perf list': root@crownbay:~# perf list List of pre-defined events (to be used in -e): cpu-cycles OR cycles [Hardware event] stalled-cycles-frontend OR idle-cycles-frontend [Hardware event] stalled-cycles-backend OR idle-cycles-backend [Hardware event] instructions [Hardware event] cache-references [Hardware event] cache-misses [Hardware event] branch-instructions OR branches [Hardware event] branch-misses [Hardware event] bus-cycles [Hardware event] ref-cycles [Hardware event] cpu-clock [Software event] task-clock [Software event] page-faults OR faults [Software event] minor-faults [Software event] major-faults [Software event] context-switches OR cs [Software event] cpu-migrations OR migrations [Software event] alignment-faults [Software event] emulation-faults [Software event] L1-dcache-loads [Hardware cache event] L1-dcache-load-misses [Hardware cache event] L1-dcache-prefetch-misses [Hardware cache event] L1-icache-loads [Hardware cache event] L1-icache-load-misses [Hardware cache event] . . . rNNN [Raw hardware event descriptor] cpu/t1=v1[,t2=v2,t3 ...]/modifier [Raw hardware event descriptor] (see 'perf list --help' on how to encode it) mem:<addr>[:access] [Hardware breakpoint] sunrpc:rpc_call_status [Tracepoint event] sunrpc:rpc_bind_status [Tracepoint event] sunrpc:rpc_connect_status [Tracepoint event] sunrpc:rpc_task_begin [Tracepoint event] skb:kfree_skb [Tracepoint event] skb:consume_skb [Tracepoint event] skb:skb_copy_datagram_iovec [Tracepoint event] net:net_dev_xmit [Tracepoint event] net:net_dev_queue [Tracepoint event] net:netif_receive_skb [Tracepoint event] net:netif_rx [Tracepoint event] napi:napi_poll [Tracepoint event] sock:sock_rcvqueue_full [Tracepoint event] sock:sock_exceed_buf_limit [Tracepoint event] udp:udp_fail_queue_rcv_skb [Tracepoint event] hda:hda_send_cmd [Tracepoint event] hda:hda_get_response [Tracepoint event] hda:hda_bus_reset [Tracepoint event] scsi:scsi_dispatch_cmd_start [Tracepoint event] scsi:scsi_dispatch_cmd_error [Tracepoint event] scsi:scsi_eh_wakeup [Tracepoint event] drm:drm_vblank_event [Tracepoint event] drm:drm_vblank_event_queued [Tracepoint event] drm:drm_vblank_event_delivered [Tracepoint event] random:mix_pool_bytes [Tracepoint event] random:mix_pool_bytes_nolock [Tracepoint event] random:credit_entropy_bits [Tracepoint event] gpio:gpio_direction [Tracepoint event] gpio:gpio_value [Tracepoint event] block:block_rq_abort [Tracepoint event] block:block_rq_requeue [Tracepoint event] block:block_rq_issue [Tracepoint event] block:block_bio_bounce [Tracepoint event] block:block_bio_complete [Tracepoint event] block:block_bio_backmerge [Tracepoint event] . . writeback:writeback_wake_thread [Tracepoint event] writeback:writeback_wake_forker_thread [Tracepoint event] writeback:writeback_bdi_register [Tracepoint event] . . writeback:writeback_single_inode_requeue [Tracepoint event] writeback:writeback_single_inode [Tracepoint event] kmem:kmalloc [Tracepoint event] kmem:kmem_cache_alloc [Tracepoint event] kmem:mm_page_alloc [Tracepoint event] kmem:mm_page_alloc_zone_locked [Tracepoint event] kmem:mm_page_pcpu_drain [Tracepoint event] kmem:mm_page_alloc_extfrag [Tracepoint event] vmscan:mm_vmscan_kswapd_sleep [Tracepoint event] vmscan:mm_vmscan_kswapd_wake [Tracepoint event] vmscan:mm_vmscan_wakeup_kswapd [Tracepoint event] vmscan:mm_vmscan_direct_reclaim_begin [Tracepoint event] . . module:module_get [Tracepoint event] module:module_put [Tracepoint event] module:module_request [Tracepoint event] sched:sched_kthread_stop [Tracepoint event] sched:sched_wakeup [Tracepoint event] sched:sched_wakeup_new [Tracepoint event] sched:sched_process_fork [Tracepoint event] sched:sched_process_exec [Tracepoint event] sched:sched_stat_runtime [Tracepoint event] rcu:rcu_utilization [Tracepoint event] workqueue:workqueue_queue_work [Tracepoint event] workqueue:workqueue_execute_end [Tracepoint event] signal:signal_generate [Tracepoint event] signal:signal_deliver [Tracepoint event] timer:timer_init [Tracepoint event] timer:timer_start [Tracepoint event] timer:hrtimer_cancel [Tracepoint event] timer:itimer_state [Tracepoint event] timer:itimer_expire [Tracepoint event] irq:irq_handler_entry [Tracepoint event] irq:irq_handler_exit [Tracepoint event] irq:softirq_entry [Tracepoint event] irq:softirq_exit [Tracepoint event] irq:softirq_raise [Tracepoint event] printk:console [Tracepoint event] task:task_newtask [Tracepoint event] task:task_rename [Tracepoint event] syscalls:sys_enter_socketcall [Tracepoint event] syscalls:sys_exit_socketcall [Tracepoint event] . . . syscalls:sys_enter_unshare [Tracepoint event] syscalls:sys_exit_unshare [Tracepoint event] raw_syscalls:sys_enter [Tracepoint event] raw_syscalls:sys_exit [Tracepoint event] Tying It Together: These are exactly the same set of events defined by the trace event subsystem and exposed by ftrace/tracecmd/kernelshark as files in /sys/kernel/debug/tracing/events, by SystemTap as kernel.trace("tracepoint_name") and (partially) accessed by LTTng. Only a subset of these would be of interest to us when looking at this workload, so let's choose the most likely subsystems (identified by the string before the colon in the Tracepoint events) and do a 'perf stat' run using only those wildcarded subsystems: root@crownbay:~# perf stat -e skb:* -e net:* -e napi:* -e sched:* -e workqueue:* -e irq:* -e syscalls:* wget http://downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2 Performance counter stats for 'wget http://downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2': 23323 skb:kfree_skb 0 skb:consume_skb 49897 skb:skb_copy_datagram_iovec 6217 net:net_dev_xmit 6217 net:net_dev_queue 7962 net:netif_receive_skb 2 net:netif_rx 8340 napi:napi_poll 0 sched:sched_kthread_stop 0 sched:sched_kthread_stop_ret 3749 sched:sched_wakeup 0 sched:sched_wakeup_new 0 sched:sched_switch 29 sched:sched_migrate_task 0 sched:sched_process_free 1 sched:sched_process_exit 0 sched:sched_wait_task 0 sched:sched_process_wait 0 sched:sched_process_fork 1 sched:sched_process_exec 0 sched:sched_stat_wait 2106519415641 sched:sched_stat_sleep 0 sched:sched_stat_iowait 147453613 sched:sched_stat_blocked 12903026955 sched:sched_stat_runtime 0 sched:sched_pi_setprio 3574 workqueue:workqueue_queue_work 3574 workqueue:workqueue_activate_work 0 workqueue:workqueue_execute_start 0 workqueue:workqueue_execute_end 16631 irq:irq_handler_entry 16631 irq:irq_handler_exit 28521 irq:softirq_entry 28521 irq:softirq_exit 28728 irq:softirq_raise 1 syscalls:sys_enter_sendmmsg 1 syscalls:sys_exit_sendmmsg 0 syscalls:sys_enter_recvmmsg 0 syscalls:sys_exit_recvmmsg 14 syscalls:sys_enter_socketcall 14 syscalls:sys_exit_socketcall . . . 16965 syscalls:sys_enter_read 16965 syscalls:sys_exit_read 12854 syscalls:sys_enter_write 12854 syscalls:sys_exit_write . . . 58.029710972 seconds time elapsed Let's pick one of these tracepoints and tell perf to do a profile using it as the sampling event: root@crownbay:~# perf record -g -e sched:sched_wakeup wget http://downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2 The screenshot above shows the results of running a profile using sched:sched_switch tracepoint, which shows the relative costs of various paths to sched_wakeup (note that sched_wakeup is the name of the tracepoint - it's actually defined just inside ttwu_do_wakeup(), which accounts for the function name actually displayed in the profile: /* * Mark the task runnable and perform wakeup-preemption. */ static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags) { trace_sched_wakeup(p, true); . . . } A couple of the more interesting callchains are expanded and displayed above, basically some network receive paths that presumably end up waking up wget (busybox) when network data is ready. Note that because tracepoints are normally used for tracing, the default sampling period for tracepoints is 1 i.e. for tracepoints perf will sample on every event occurrence (this can be changed using the -c option). This is in contrast to hardware counters such as for example the default 'cycles' hardware counter used for normal profiling, where sampling periods are much higher (in the thousands) because profiling should have as low an overhead as possible and sampling on every cycle w ould be prohibitively expensive.
Using perf to do Basic Tracing Profiling is a great tool for solving many problems or for getting a high-level view of what's going on with a workload or across the system. It is however by definition an approximation, as suggested by the most prominent word associated with it, 'sampling'. On the one hand, it allows a representative picture of what's going on in the system to be cheaply taken, but on the other hand, that cheapness limits its utility when that data suggests a need to 'dive down' more deeply to discover what's really going on. In such cases, the only way to see what's really going on is to be able to look at (or summarize more intelligently) the individual steps that go into the higher-level behavior exposed by the coarse-grained profiling data. As a concrete example, we can trace all the events we think might be applicable to our workload: root@crownbay:~# perf record -g -e skb:* -e net:* -e napi:* -e sched:sched_switch -e sched:sched_wakeup -e irq:* -e syscalls:sys_enter_read -e syscalls:sys_exit_read -e syscalls:sys_enter_write -e syscalls:sys_exit_write wget http://downloads.yoctoproject.org/mirror/sources/linux-2.6.19.2.tar.bz2 We can look at the raw trace output using 'perf script' with no arguments: root@crownbay:~# perf script perf 1262 [000] 11624.857082: sys_exit_read: 0x0 perf 1262 [000] 11624.857193: sched_wakeup: comm=migration/0 pid=6 prio=0 success=1 target_cpu=000 wget 1262 [001] 11624.858021: softirq_raise: vec=1 [action=TIMER] wget 1262 [001] 11624.858074: softirq_entry: vec=1 [action=TIMER] wget 1262 [001] 11624.858081: softirq_exit: vec=1 [action=TIMER] wget 1262 [001] 11624.858166: sys_enter_read: fd: 0x0003, buf: 0xbf82c940, count: 0x0200 wget 1262 [001] 11624.858177: sys_exit_read: 0x200 wget 1262 [001] 11624.858878: kfree_skb: skbaddr=0xeb248d80 protocol=0 location=0xc15a5308 wget 1262 [001] 11624.858945: kfree_skb: skbaddr=0xeb248000 protocol=0 location=0xc15a5308 wget 1262 [001] 11624.859020: softirq_raise: vec=1 [action=TIMER] wget 1262 [001] 11624.859076: softirq_entry: vec=1 [action=TIMER] wget 1262 [001] 11624.859083: softirq_exit: vec=1 [action=TIMER] wget 1262 [001] 11624.859167: sys_enter_read: fd: 0x0003, buf: 0xb7720000, count: 0x0400 wget 1262 [001] 11624.859192: sys_exit_read: 0x1d7 wget 1262 [001] 11624.859228: sys_enter_read: fd: 0x0003, buf: 0xb7720000, count: 0x0400 wget 1262 [001] 11624.859233: sys_exit_read: 0x0 wget 1262 [001] 11624.859573: sys_enter_read: fd: 0x0003, buf: 0xbf82c580, count: 0x0200 wget 1262 [001] 11624.859584: sys_exit_read: 0x200 wget 1262 [001] 11624.859864: sys_enter_read: fd: 0x0003, buf: 0xb7720000, count: 0x0400 wget 1262 [001] 11624.859888: sys_exit_read: 0x400 wget 1262 [001] 11624.859935: sys_enter_read: fd: 0x0003, buf: 0xb7720000, count: 0x0400 wget 1262 [001] 11624.859944: sys_exit_read: 0x400 This gives us a detailed timestamped sequence of events that occurred within the workload with respect to those events. In many ways, profiling can be viewed as a subset of tracing - theoretically, if you have a set of trace events that's sufficient to capture all the important aspects of a workload, you can derive any of the results or views that a profiling run can. Another aspect of traditional profiling is that while powerful in many ways, it's limited by the granularity of the underlying data. Profiling tools offer various ways of sorting and presenting the sample data, which make it much more useful and amenable to user experimentation, but in the end it can't be used in an open-ended way to extract data that just isn't present as a consequence of the fact that conceptually, most of it has been thrown away. Full-blown detailed tracing data does however offer the opportunity to manipulate and present the information collected during a tracing run in an infinite variety of ways. Another way to look at it is that there are only so many ways that the 'primitive' counters can be used on their own to generate interesting output; to get anything more complicated than simple counts requires some amount of additional logic, which is typically very specific to the problem at hand. For example, if we wanted to make use of a 'counter' that maps to the value of the time difference between when a process was scheduled to run on a processor and the time it actually ran, we wouldn't expect such a counter to exist on its own, but we could derive one called say 'wakeup_latency' and use it to extract a useful view of that metric from trace data. Likewise, we really can't figure out from standard profiling tools how much data every process on the system reads and writes, along with how many of those reads and writes fail completely. If we have sufficient trace data, however, we could with the right tools easily extract and present that information, but we'd need something other than pre-canned profiling tools to do that. Luckily, there is general-purpose way to handle such needs, called 'programming languages'. Making programming languages easily available to apply to such problems given the specific format of data is called a 'programming language binding' for that data and language. Perf supports two programming language bindings, one for Python and one for Perl. Tying It Together: Language bindings for manipulating and aggregating trace data are of course not a new idea. One of the first projects to do this was IBM's DProbes dpcc compiler, an ANSI C compiler which targeted a low-level assembly language running on an in-kernel interpreter on the target system. This is exactly analagous to what Sun's DTrace did, except that DTrace invented its own language for the purpose. Systemtap, heavily inspired by DTrace, also created its own one-off language, but rather than running the product on an in-kernel interpreter, created an elaborate compiler-based machinery to translate its language into kernel modules written in C. Now that we have the trace data in perf.data, we can use 'perf script -g' to generate a skeleton script with handlers for the read/write entry/exit events we recorded: root@crownbay:~# perf script -g python generated Python script: perf-script.py The skeleton script simply creates a python function for each event type in the perf.data file. The body of each function simply prints the event name along with its parameters. For example: def net__netif_rx(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, skbaddr, len, name): print_header(event_name, common_cpu, common_secs, common_nsecs, common_pid, common_comm) print "skbaddr=%u, len=%u, name=%s\n" % (skbaddr, len, name), We can run that script directly to print all of the events contained in the perf.data file: root@crownbay:~# perf script -s perf-script.py in trace_begin syscalls__sys_exit_read 0 11624.857082795 1262 perf nr=3, ret=0 sched__sched_wakeup 0 11624.857193498 1262 perf comm=migration/0, pid=6, prio=0, success=1, target_cpu=0 irq__softirq_raise 1 11624.858021635 1262 wget vec=TIMER irq__softirq_entry 1 11624.858074075 1262 wget vec=TIMER irq__softirq_exit 1 11624.858081389 1262 wget vec=TIMER syscalls__sys_enter_read 1 11624.858166434 1262 wget nr=3, fd=3, buf=3213019456, count=512 syscalls__sys_exit_read 1 11624.858177924 1262 wget nr=3, ret=512 skb__kfree_skb 1 11624.858878188 1262 wget skbaddr=3945041280, location=3243922184, protocol=0 skb__kfree_skb 1 11624.858945608 1262 wget skbaddr=3945037824, location=3243922184, protocol=0 irq__softirq_raise 1 11624.859020942 1262 wget vec=TIMER irq__softirq_entry 1 11624.859076935 1262 wget vec=TIMER irq__softirq_exit 1 11624.859083469 1262 wget vec=TIMER syscalls__sys_enter_read 1 11624.859167565 1262 wget nr=3, fd=3, buf=3077701632, count=1024 syscalls__sys_exit_read 1 11624.859192533 1262 wget nr=3, ret=471 syscalls__sys_enter_read 1 11624.859228072 1262 wget nr=3, fd=3, buf=3077701632, count=1024 syscalls__sys_exit_read 1 11624.859233707 1262 wget nr=3, ret=0 syscalls__sys_enter_read 1 11624.859573008 1262 wget nr=3, fd=3, buf=3213018496, count=512 syscalls__sys_exit_read 1 11624.859584818 1262 wget nr=3, ret=512 syscalls__sys_enter_read 1 11624.859864562 1262 wget nr=3, fd=3, buf=3077701632, count=1024 syscalls__sys_exit_read 1 11624.859888770 1262 wget nr=3, ret=1024 syscalls__sys_enter_read 1 11624.859935140 1262 wget nr=3, fd=3, buf=3077701632, count=1024 syscalls__sys_exit_read 1 11624.859944032 1262 wget nr=3, ret=1024 That in itself isn't very useful; after all, we can accomplish pretty much the same thing by simply running 'perf script' without arguments in the same directory as the perf.data file. We can however replace the print statements in the generated function bodies with whatever we want, and thereby make it infinitely more useful. As a simple example, let's just replace the print statements in the function bodies with a simple function that does nothing but increment a per-event count. When the program is run against a perf.data file, each time a particular event is encountered, a tally is incremented for that event. For example: def net__netif_rx(event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, skbaddr, len, name): inc_counts(event_name) Each event handler function in the generated code is modified to do this. For convenience, we define a common function called inc_counts() that each handler calls; inc_counts simply tallies a count for each event using the 'counts' hash, which is a specialized has function that does Perl-like autovivification, a capability that's extremely useful for kinds of multi-level aggregation commonly used in processing traces (see perf's documentation on the Python language binding for details): counts = autodict() def inc_counts(event_name): try: counts[event_name] += 1 except TypeError: counts[event_name] = 1 Finally, at the end of the trace processing run, we want to print the result of all the per-event tallies. For that, we use the special 'trace_end()' function: def trace_end(): for event_name, count in counts.iteritems(): print "%-40s %10s\n" % (event_name, count) The end result is a summary of all the events recorded in the trace: skb__skb_copy_datagram_iovec 13148 irq__softirq_entry 4796 irq__irq_handler_exit 3805 irq__softirq_exit 4795 syscalls__sys_enter_write 8990 net__net_dev_xmit 652 skb__kfree_skb 4047 sched__sched_wakeup 1155 irq__irq_handler_entry 3804 irq__softirq_raise 4799 net__net_dev_queue 652 syscalls__sys_enter_read 17599 net__netif_receive_skb 1743 syscalls__sys_exit_read 17598 net__netif_rx 2 napi__napi_poll 1877 syscalls__sys_exit_write 8990 Note that this is pretty much exactly the same information we get from 'perf stat', which goes a little way to support the idea mentioned previously that given the right kind of trace data, higher-level profiling-type summaries can be derived from it. Documentation on using the 'perf script' python binding.