简介:
LTTng: (Linux Trace Toolkit Next Generation),它是用于跟踪 Linux 内核、应用程序以及库的系统软件包
LTTng 主要由内核模块和动态链接库(用于应用程序和动态链接库的跟踪)组成
由一个会话守护进程控制,该守护进程接受来自命令行接口的命令
babeltrace 项目允许将追踪信息翻译成用户可读的日志,并提供一个读追踪库,即 libbabletrace。
- LTTng 不仅使用了 Linux 内核中的追踪点(tracepoint)手段,而且可以使用其他各种信息来源,
- 比如kprobes 和 Perf(Linux 中的性能监检测工具)。
- 这对于调试大范围内的bug 是非常有用的,否则这种调试工作将极具挑战性。
- 比如,包括并行系统和实时系统中的性能问题。
- 另外,用户自己定制的工具也可以加入到其中。
- LTTng 的设计目标是将性能影响最小化,而且在没有跟踪的情况下,对系统的影响应该几乎为零。
LTTng如今已支持多个发行版(Ubuntu/Dibian、Fedora、OpenSUSE、Arch etc.)和多种架构(x86 and x86-64 、ARM 、PowerPC, Sparc, Mips etc.),此外官方还说支持Android和FreeBSD系统。
实战:
- 首先我们得确认你是否已经安装了lttng-modules 和lttng-tools。
- 列出所有的可追踪内核事件:
# lttng list -k
Kernel events:
-------------
timer_init (loglevel: TRACE_EMERG (0)) (type: tracepoint)
timer_start (loglevel: TRACE_EMERG (0)) (type: tracepoint)
timer_expire_entry (loglevel: TRACE_EMERG (0)) (type: tracepoint)
……
-
创建一个追踪会话(session),这个命令会创建一个目录用以存放追踪结果:
# lttng create mysession
Session mysession created.
Traces will be written in /home/dslab/lttng-traces/mysession-20131010-145153- 假如你当前已经有了很多的会话,我们可以设置当前追踪会话:
# lttng set-session myothersession
Session set to myothersession
-
创建追踪规则(探测点/系统调用 etc.)
- 追踪内核所有的探测点和所有的系统调用事件(-k/--kernel):
- # lttng enable-event -a -k
- 追踪探测点事件,这里我们追踪 sched_switch和sched_wakeup为例 (-k/--kernel) ;或者追踪所有的探测点事件:
- # lttng enable-event sched_switch,sched_wakeup -k
- # lttng enable-event -a -k --tracepoint
- 追踪所有的系统调用:
- # lttng enable-event -a -k --syscall
- 使用 kprobes 以及 (或) 其他追踪器作为lttng的源:
- 这是一个LTTng2.0内核追踪器的一个新特性,你可以使用一个动态probe作为源,probe的追踪结果会显示在lttng的追踪结果中。
- 可以为probe制定一个准确的地址0xffff7260695或者 symbol+offset。
- lttng enable-event aname -k --probe 0xffff7260695
- lttng enable-event aname -k --probe symbol+0x0
- 也可以使用功能追踪(使用的Ftrace API),追踪结果也会显示在lttng的追踪结果中
- lttng enable-event aname -k --function <symbol_name>
- 打开一个事件的上下文信息:
- 这也是一个新特性,可以让你添加一个事件的上下文信息。
- 比如说你可以添加PID:# lttng add-context -k -e sched_switch -t pid
- 也可以使用多个上下文信息:
- # lttng add-context -k -e sched_switch -t pid -t nice -t tid
- 可以使用' lttng add-context --help ' 学习所有的上下文格式的用法。
- 这也是一个新特性,可以让你添加一个事件的上下文信息。
- 打开事件的Perf计数器:
- 这也是一个新的很强大的特性,为每个追踪的事件添加Perf计数器数据(使用Perf的API)。
- 下面实例为为每个事件添加CPU周期:# lttng add-context -k -e sched_switch -t perf:cpu-cycles
- 注: 你需要使用 add-context 的help学习所有的perf计数器值的含义
- 这也是一个新的很强大的特性,为每个追踪的事件添加Perf计数器数据(使用Perf的API)。
- 追踪内核所有的探测点和所有的系统调用事件(-k/--kernel):
- 开始追踪
- # lttng start
- 追踪结果会写到上面创建会话时创建的文件夹中。
- 比如上面的 :/home/dslab/lttng-traces/mysession-20131010-145153
- 注意:这个命令会打开所有的追踪,如果你想同时追踪用户空间和内核空间,你在使用这个之前需要设置好所有的追踪规则。
- 停止追踪
- # lttng stop
- 注:在这时候,你可一使用 lttng start 重新追踪,也可以打开/关闭某个事件或者隔段时间再来追踪。当然你也可以查看追踪信息。
- 关闭追踪(你的追踪工作已经结束)
- # lttng destroy
用户空间追踪:
追踪数据分析:
- 分析工具 -- babeltrace
- babeltrace是lttng tools中自带的分析工具,很强大。
- 可以直接使用babeltrace打开追踪数据
- 比如上面提到的 /home/dslab/lttng-traces/mysession-20131010-145153。
- 先查看下这个追踪结果下的结构:
# tree /home/dslab/lttng-traces/mysession-20131010-145153 /home/dslab/lttng-traces/mysession-20131010-145153
└── kernel
├── channel0_0
├── channel0_1
├── channel0_2
├── channel0_3
└── metadata- 可以看出来追踪目录下只有一个目录,叫做kernel;如果追踪前也打开了用户追踪,那么这里面还会多个追踪目录(用户空间的)
- kernel目录下分几个文件保存追踪数据。但是我们使用babeltrace查看追踪结果时不能指定到具体的文件,需要指定到kernel。
- 如果我们将babeltrace的输出信息输出到一个文件中,我们就可以使用shell脚本获取/tmp/trace中的追踪数据并进一步分析。
- 比如# babeltrace /home/dslab/lttng-traces/mysession-20131010-145153 > /tmp/trace 。
LTT:(Linux Trace Toolkit)是linux下一种用于跟踪系统详细运行状态和流程的重要工具,它可以跟踪记录系统中的特定事件。
这些事件包括:
* 系统调用的进入和退出
* 陷阱/中断(Trap / Irq)的进入和退出
* 进程调度事件
* 内核定时器
* 进程管理相关事件:创建 ,唤醒,信号处理等等
* 文件系统相关事件:Open / Read / Write / Seek / Ioctl 等等
* 内存管理相关事件:内存分配/释放等
* 其他事件:IPC / Socket/ 网络 等等
此外 Ltt还提供了自定义和记录需要跟踪的事件类型的函数接口。
LTTng: (Linux Trace Toolkit Next Generation),它是用于跟踪 Linux 内核、应用程序以及库的系统软件包。
LTTng 主要由内核模块和动态链接库(用于应用程序和动态链接库的跟踪)组成。
它由一个会话守护进程控制,该守护进程接受来自命令行接口的命令。
babeltrace 项目允许将追踪信息翻译成用户可读的日志,并提供一个读追踪库,即 libbabletrace。
LTTng 不仅使用了 Linux 内核中的追踪点(tracepoint)手段,而且使用了其他各种信息来源,比如kprobes 和 Perf(Linux 中的性能监检测工具)。
这对于调试大范围内的bug 是非常有用的,否则这种调试工作将极具挑战性。比如,包括并行系统和实时系统中的性能问题。
另外,用户自己定制的工具也可以加入到其中。LTTng 的设计目标是将性能影响最小化,而且在没有跟踪的情况下,对系统的影响应该几乎为零。
LTTng2.0:使用简单的用户接口--lttng命令来整合用户空间和内核空间追踪。
还有其他一些特性:
- 从支持多个发行版 & 多个架构
- 有通用的追踪格式
- 支持探测点、详细的系统调用追踪、函数追踪器、CPU性能监控以及kprobe
- 提供对用户空间和内核的追踪接口
- 可以在追踪中的事件附加上下文信息(比如PMU 性能监控单元counter, pid, ppid, tid, comm name等)。所有的额外信息都被收集起来放在一个追踪session的部件里。
研究背景
现如今,多核、集群以及嵌入式系统等让Linux系统变得越来越复杂,而开发者就面临着越来越复杂的bug,这些bug可能只在产品中出现而在调试时就不见了或者很少发生而且只让系统变得有点慢,这些特性都让传统的调试内核工具变得无效。所以需要有调试工具来应对这些问题。这些工具对系统的性能影响必要要很小,从而可以把他们用在产品上。
内核追踪就是一款这样的工具,它提供了一种很有效的理解内核行为以及调试用户空间程序和内核故障的方法。但是在一些情况下,内核追踪也是不足以应对的。比如,有个程序会发出大量的请求或者它有很多很多个线程从而导致只从内核空间的角度来追踪是非常难的。所以在这种情况下,应用空间的程序也需要追踪。而且要求追踪的用户空间事件与内核事件是相关的。
本文提到的是LTTng的一个用户空间追踪器,可以实现上述功能 -- 同时追踪用户空间应用程序事件和内核事件。
What is tracing?
追踪是一门技术用来理解或者监控系统里面是如何运行的。追踪器(tracer)是用来追踪的软件。追踪可以用来调试很多的bug,这些可能包括复杂的并行系统或者实时系统中的性能问题。
追踪其实和记录日志很像:它主要就是记录系统中的事件。但是,与日志相比,它记录的是系统中更为底层以及更频繁的事件。所以追踪其必须被优化从而可以处理大量的数据而不对系统产生很大的影响。追踪其通常一秒钟产生数以千计的事件。它们经常处理数以百万计的事件以及数M到数十G的数据。
追踪可能包括操作系统内核的各种事件(比如中断请求处理函数的进入/退出,系统调用的进入/退出,调度情况以及网络情况等),他们还可能包括应用程序的事件。
事件追踪的列表可能被手动的像读取日志文件那样读取,但是因为工作量太大,所以追踪分析器或者阅读器可以根据这些庞大的数据生成图型或者统计数据。这些分析软件需要特殊设计以便快读处理这些庞大的数据。
相关研究
* strace :是一种提供原始的用户空间追踪的工具,它可以追踪系统调用和信号的情况。但是它会引发较大的性能损失。但是它限制到追踪一些系统调用和信号,可以以较小的损失来获得追踪信息。
* DTrace :具有静态定义的跟踪(Statically Defined Tracing,SDT)可以用在用户空间应用中。它用一种在运行时链接中的特殊支持实现的。【6】
* SystemTap :如同DTrace实现了SDT。【2】
* LTTng : 这些年提供了几种不同的用户空间追踪技术:
1)systemcall assisted -- 这个技术声明了两个新的系统调用,第一个用来注册一个事件类型并返回事件ID,第二种用来记录事件,他需要事件ID和负荷
2) userspace tracing -- 它不需要内核的支持。程序在他们自己的地址空间缓存上写事件,追踪器为每个线程穿件一个共享缓存的同伴进程,同伴进程使用一个无锁算法来使用这个缓存。
---- 这两个方法都被遗弃了,现在有个新的替代方案被提出,它是一个简单的使用字符串做参数的系统调用。调用它会产生一个参数就是这个字符串的事件,…… 。这个特性是来自于DebugFS(/debug/ltt/write_event),向这个文件写入一个字符就会创建一个叫做user-space_event的事件,事件的参数是这个字符串。
* Ftrace :有类似的特性,叫做trace_marker.
实现
主要介绍UST(LTTng Userspace Tracer,LTTng用户空间追踪器)的结构,首先我们看看UST的重要设计目标:
* 需要与内核空间的追踪器完全独立,用户空间追踪和内核空间追踪的结果应该在分析的时候再关联
* 需要是可重入的,并支持多线程&追踪信号处理的事件
* 在快速路径中不能有系统调用
* 追踪结果不能被拷贝
* 需要能追踪共享库中的代码
* 探测点需要支持无穷多的参数
* 对于连接器和编译器不需要特殊的支持
* 追踪格式需要是紧凑的
追踪库
* libust - 追踪库
* liburcu - 用户空间读/拷贝/更新库,用来在数据结构之间无锁操控【4】
* libkcompat - Linux内核提供用户空间API的库(如院子看哦乃至,连接表机制,kref类型机制等)【5】
时间
LTTng的用户空间追踪器和内核没有依赖关系,但是为了把内核追踪和用户空间追踪合并分析,所有的追踪器的时间戳必须是一致的(必须来自一样的时钟源)。
一个合适的时钟源必须是高精度的这样才能保证在核之间都是一致的;还要保证用户空间和内核空间访问时的开销要小(因为系统调用的代价以及很高了)。
UST中使用的是内核提供的时钟源,而且直接使用rdtsc指令读取。这个时钟源也是内核追踪器使用的,读取和多核之间同步都很快。
探测点
探测点是由内核LTTng使用的两个端口组成:marker和tracepoint,它们的使用也与内核中一样。
插入marker就像在事件需要记录的地方插入一行代码一样简单,下面的代码中我们可以看到,mark的使用与printf很类似:
trace_mark(main,myevent,"firstarg %d secondarg %s",v,st);
tracepoint的设计更佳,从下面代码中可以看到,它基本不包括一个格式字符串,但需要一些声明,一般分开放在C文件和头文件中。所以,它更合适放在永久的代码中(需要时,只需要修改头文件中声明即可)。而marker更合适用在调试时添加。(用来调试中打印信息)
trace_main_myevent(v,st);
marker和tracepoint在可执行或者动态对象中的一个特殊字段列举他们自身信息,所有包含探测点的库和可执行文件因此需要通过调用追踪库来注册他们的marker/tracepoint。这是通过唤醒一个添加构造函数的宏来实现的。
缓存机制
缓存机制是无锁LTTng算法的一个端口,它的设计很多源自于K42操作系统以及Linux内核中的Relay系统【8】事件在一个圆形的、每个进程一个的缓冲区中被写入,这个缓冲区被划分成多个sub-buffer。默认情况下,当一个sub-buffer写满,就会有一个清空守护进程清空它。在另外一种操作模式下(flight recorder),圆形的缓冲区被不断的重写直到这个缓冲区被清除,这对于等待一些罕见的bug出现是很有帮助的。
每个事件都与一个channel(通道?)相关联,对于每个channel每个进程都有个明显的缓冲区。有几个channel允许选择每个channel的sub-buffer大小。在flight recorder模式下,它通过将一些时间放在低速率的缓冲区中实现允许保持这些事件的缓冲区很长的时间。
缓冲区被分配在System V的共享内存字段中,所以清空守护进程可以将它们映射到它自己的地址空间。
写缓冲区时使用了一种已形式化验证过其安全性的无锁算法,所以可以保证可重入、多线程、安全性。
追踪控制
用户空间追踪器肯定需要一个控制方法,比如我们需要开始/停止追踪、使能/关闭/列出探测点以及控制追踪参数(比如sub-buffer的大小以及数量)
一个叫做ust的辅助应用被设计用来做这项工作,它与被追踪的程序通过UNIX socket通信,被追踪哦进程中一个特殊的线程来处理这个通信。这个线程并不是当这个应用程序开始时就启动,这使得UST看起来不是那么有攻击性。而是,当追踪库构造函数为一个特别的信号注册一个信号控制器,当那个信号被接收到,这个监听的线程才会启动。这个线程会在一个预先定义好的目录下创建一个socket,socket的名为进程ID。
数据收集
有一个进程收集系统中所有被追踪的进程的追踪数据,这个进程叫做ustd。收集数据使用一个命名socket,socket会调用ustd 并定位到与被追踪的程序的socket同样的目录下。通过这个socket,ustd才能接受指令并收集给定PID对应的缓存的追踪数据。
收到指令后,ustd会创建一个线程用来链接socket和被追踪的进程。首先,假如socket还没准备好它会发送一个SIGIO信号,然后,它会请求共享内存以求映射缓存。
直到能够使用被追踪应用的socket后,这个线程会发一个请求来获取下一个sub-buffer的许可。当下一个sub-buffer满了后,会发送一个回复。收到回复后,这个线程会将他的数据写到追踪文件中(从共享内存段中读取的)。
过早和过晚地追踪
UST使用一种特殊的机制用来保证在程序执行时开始追踪:用两个环境变量运行这个程序,这些环境变量被追踪库构造函数解析,设定这些环境变量是为了保证在程序进入他的main()函数时追踪已然开始。
但对于程序结束时如何结束跟踪难度比较大:可能程序崩溃了然后就没办法通知ustd来获取最后一个sub-buffer的信息,更坏的可能是在追踪还没映射它的缓存程序就结束了。对于前面的情况,追踪的最后一部分就丢失了;对于后面的情况,整个追踪就丢失了,因为内核会在使用者与共享内存段断开连接时解除分配这段共享内存。
当程序崩溃时,内核会关闭socket连接,但是ustd能够检测到socket的断开,并在缓存上运行一个崩溃恢复程序,这个程序会辨别哪个sub-buffer有还没记录的数据以及有多少数据可以被恢复,这些数据会被恢复程序追加到追踪文件后。对于如何辨别sub-buffer中的数据是不是有效的,使用的是一些在映射缓存与共享内存段的原子算法中用到的计数器。
当程序的生命周期对于ustd来说太短了,来不及映射内存,新的难题出现了。(目前UST还没支持这种情况下的追踪)但是计划是使用析构函数来处理这个问题,假如追踪库的析构函数检测到一个追踪被记录但是他的缓冲区还未被映射,它会扩展程序的生命周期来给ustd足够的时间映射。