动态追踪技术漫谈

本文最初成稿于 2016 年 5 月初,后于 2020 年 2 月中进行了较大的更新和修订,后续会持续保持更新。

什么是动态追踪

我很高兴能在这里和你们分享动态追踪技术(Dynamic Tracing)这个主题,对我我的来讲也是一个很激动人心的话题。那么,什么是动态追踪技术呢?php

动态追踪技术实际上是一种后现代的高级调试技术。它能够帮助软件工程师以很是低的成本,在很是短的时间内,回答一些很难的关于软件系统方面的问题,从而更快速地排查和解决问题。它兴起和繁荣的一个大背景是,咱们正处在一个快速增加的互联网时代,做为工程师,面临着两大方面的挑战:一是规模,无论是用户规模仍是机房的规模、机器的数量都处于快速增加的时代。第二方面的挑战就是复杂度。咱们的业务逻辑愈来愈复杂,咱们运行的软件系统也变得愈来愈复杂,咱们知道它会分红不少不少层次,包括操做系统内核而后上面是各类系统软件,像数据库和 Web 服务器,再往上有脚本语言或者其余高级语言的虚拟机、解释器及即时(JIT)编译器,顶上则是应用层面的各类业务逻辑的抽象层次和不少复杂的代码逻辑。html

这些巨大的挑战带来的最严重的后果就是,今天的软件工程师正在迅速地丧失对整个生产系统的洞察力和掌控力。在如此复杂和庞大的系统中,各类问题发生的几率大大提升了。有的问题多是致命的,好比 500 错误页,还有内存泄漏,再好比说返回错误结果之类。而另外一大类问题就是性能问题。咱们可能会发现软件在某些时候运行的很是缓慢,或者在某些机器上运行得很是缓慢,但咱们并不知道为何。如今你们都在拥抱云计算和大数据,这种大规模的生产环境中的诡异问题只会愈来愈多,很容易占据工程师大部分的时间和精力。大部分问题实际上是线上才有的问题,很难复现,或者几乎没法复现。而有些问题出现的比率又很小,只有百分之1、千分之一,甚至更低。咱们最好可以不用摘机器下线,不用修改咱们的代码或者配置,不用重启服务,在系统还在运行的时候,就把问题分析出来,定位出来,进而采起有针对性的解决办法。若是能作到这一点,那才是完美的,才能每晚都睡上一个好觉。node

动态追踪技术实际就能帮助咱们实现这种愿景,实现这种梦想,从而极大地解放咱们工程师的生产力。我至今还记得当年在雅虎中国工做的时候,有时不得不半夜打车去公司处理线上问题。这显然是很是无奈和挫败的生活和工做方式。后来我曾在美国的一家 CDN 公司工做,当时咱们的客户也会有本身的运维团队,他们没事就去翻 CDN 提供的原始日志。可能对咱们来讲是百分之一或者千分之一这样的问题,可是对他们来讲就是比较重要的问题,就会报告上来,咱们则就必须去排查,必须去找出真正的缘由,反馈给他们。这些实际存在的大量的现实问题,激发着新技术的发明和产生。linux

我以为动态追踪技术很了不得的一点就是,它是一种“活体分析”技术。就是说,咱们的某个程序或者整个软件系统仍然在运行,仍然在线上服务,还在处理真实请求的时候,咱们就能够去对它进行分析(无论它本身愿不肯意),就像查询一个数据库同样。这是很是有意思的。不少工程师容易忽略的一点是,正在运行的软件系统自己其实就包含了绝大部分的宝贵信息,就能够被直接看成是一个实时变化的数据库来进行“查询”。固然了,这种特殊的“数据库”须是只读的,不然咱们的分析和调试工做就有可能会影响到系统自己的行为,就可能会危害到在线服务。咱们能够在操做系统内核的帮助下,从外部发起一系列有针对性的查询,获取关于这个软件系统自己运行过程中的许多第一手的宝贵的细节信息,从而指导咱们的问题分析和性能分析等不少工做。nginx

动态追踪技术一般是基于操做系统内核来实现的。操做系统内核其实能够控制整个软件世界,由于它实际上是处于“造物主”这样的一个地位。它拥有绝对的权限,同时它能够确保咱们针对软件系统发出的各类“查询”不会影响到软件系统自己的正常运行。换句话说,咱们的这种查询必须是足够安全的,是能够在生产系统上大量使用的。把软件系统做为“数据库”进行查询就会涉及到一个查询方式的问题,显然咱们并非经过 SQL 这样的方式去查询这种特殊的“数据库”。git

在动态追踪里面通常是经过探针这样的机制来发起查询。咱们会在软件系统的某一个层次,或者某几个层次上面,安置一些探针,而后咱们会本身定义这些探针所关联的处理程序。这有点像中医里面的针灸,就是说若是咱们把软件系统当作是一我的,咱们能够往他的一些穴位上扎一些“针”,那么这些针头上面一般会有咱们本身定义的一些“传感器”,咱们能够自由地采集所须要的那些穴位上的关键信息,而后把这些信息汇总起来,产生可靠的病因诊断和可行的治疗方案。这里的追踪一般涉及两个纬度。一个是时间纬度,由于这个软件还一直在运行,它便有一个在时间线上的连续的变化过程。另外一个纬度则是空间纬度,由于可能它涉及到多个不一样的进程,包含内核进程,而每一个进程常常会有本身的内存空间、进程空间,那么在不一样的层次之间,以及在同一层次的内存空间里面,我能够同时沿纵向和横向,获取不少在空间上的宝贵信息。这有点儿像蛛蛛在蛛网上搜索猎物。程序员

蜘蛛在蛛网上搜索

咱们既能够在操做系统内核里面拿一些信息,也能够在用户态程序等较高的层面上采集一些信息。这些信息能够在时间线上面关联起来,构建出一幅完整的软件图景,从而有效地指导咱们作一些很复杂的分析。这里很是关键的一点是,它是非侵入式的。若是把软件系统比做一我的,那咱们显然不想把一个活人开膛破肚,却只是为了帮他诊断疾病。相反,咱们会去给他拍一张 X 光,给他作一个核磁共振,给他号号脉,或者最简单的,用听诊器听一听,诸如此类。针对一个生产系统的诊断,其实也应如此。动态追踪技术容许咱们使用非侵入式的方式,不用去修改咱们的操做系统内核,不用去修改咱们的应用程序,也不用去修改咱们的业务代码或者任何配置,就能够快速高效地精确获取咱们想要的信息,第一手的信息,从而帮助定位咱们正在排查的各类问题。github

我以为大部分工程师可能特别熟悉软件构造的过程,这实际上是咱的基本功了。咱们一般会创建不一样的抽象层次,一层一层的把软件构造起来,不管是自底向上,仍是自顶向下。创建软件抽象层次的方式不少,好比经过面向对象里面的类和方法,或者直接经过函数和子例程等方式。而调试的过程,其实与软件构造的方式恰好相反,咱们偏偏是要可以很轻易地“打破”原先创建起来的这些抽象层次,可以为所欲为的拿到任意一个或者任意几个抽象层次上的任何所需的信息,而无论什么封装设计,无论什么隔离设计,无论任何软件构造时人为创建的条条框框。这是由于调试的时候老是但愿能拿到尽量多的信息,毕竟问题可能发生在任何层面上。正则表达式

正由于动态追踪技术通常是基于操做系统内核的,而内核是“造物主”,是绝对的权威,因此这种技术能够垂手可得地贯通各个软件层次的抽象和封装,所以软件构造时创建的抽象和封装层次其实并不会成为阻碍。相反,在软件构造时创建起来的设计良好的抽象与封装层次,其实反而有助于调试过程,关于这点,咱们后面还会专门提到。我在本身的工做当中常常会发现,有的工程师在线上出问题的时候,很是慌乱,会去胡乱猜想可能的缘由,但又缺少任何证据去支持或者否证他的猜想与假设。他甚至会在线上反复地试错,反复地折腾,搞得一团乱麻,毫无头绪,让本身和身边的同事都很痛苦,白白浪费了宝贵的排错时间。当咱们有了动态追踪技术以后,排查问题自己就可能会变成一个很是有趣的过程,让咱们遇到线上的诡异问题就感到兴奋,就仿佛好不容易又逮着机会,能够去解一道迷人的谜题。固然了,这一切的前提是,咱们具备趁手的强大的工具,帮助咱们进行信息采集和推理,帮助咱们快速证实或否证任何假设和推测。算法

动态追踪的优势

动态追踪技术通常是不须要目标应用来配合的。好比说,咱们在给一个哥们作体检的时候,他还在操场上奔跑,咱们就能在他还在运动的过程当中,直接给他拍一张动态的 X 光,并且他本身还不知道。仔细想一下,这实际上是一件很了不得的事情。各类基于动态追踪的分析工具的运行方式都是一种“热插拔”的方式,就是说,咱们随时能够运行这个工具,随时进行采样,随时结束采样,而不用管目标系统的当前状态。不少统计和分析,其实都是目标系统上线以后才想到的,咱们并不可能在上线前预测将来可能会遇到哪些问题,更不可能预测咱们须要采集的全部信息,用以排查那些未知的问题。动态追踪的好处就是,能够实现“随时随地,按需采集”。另外还有一个优点是它自身的性能损耗极小。仔细写出的调试工具对系统的极限性能的影响,一般在百分之五,甚至更低的比例如下,因此它通常不会对咱们的最终用户产生能够观察到的性能影响。另外,即便是这么小的性能损耗也只发生在咱们实际采样的那几十秒或者几分钟之内。一旦咱们的调试工具结束运行,在线系统又会自动恢复到原先百分之百的性能,继续向前狂奔。

被检查的小人在奔跑

DTrace 与 SystemTap

说到动态追踪就不能不提到 DTrace。DTrace 算是现代动态追踪技术的鼻祖了,它于 21 世纪初诞生于 Solaris 操做系统,是由原来的 Sun Microsystems 公司的工程师编写的。可能不少同窗都据说过 Solaris 系统和 Sun 公司的大名。

最初产生的时候,我记得有这样一个故事,当时 Solaris 操做系统的几个工程师花了几天几夜去排查一个看似很是诡异的线上问题。开始他们觉得是很高级的问题,就特别卖力,结果折腾了几天,最后发现实际上是一个很是愚蠢的、某个不起眼的地方的配置问题。自从那件事情以后,这些工程师就痛定思痛,创造了 DTrace 这样一个很是高级的调试工具,来帮助他们在将来的工做当中避免把过多精力花费在愚蠢问题上面。毕竟大部分所谓的“诡异问题”其实都是低级问题,属于那种“调不出来很郁闷,调出来了更郁闷”的类型。

应该说 DTrace 是一个很是通用的调试平台,它提供了一种很像 C 语言的脚本语言,叫作 D。基于 DTrace 的调试工具都是使用这种语言编写的。D 语言支持特殊的语法用以指定“探针”,这个“探针”一般有一个位置描述的信息。你能够把它定位在某个内核函数的入口或出口,抑或是某个用户态进程的 函数入口或出口,甚至是任意一条程序语句或机器指令上面。编写 D 语言的调试程序是须要对系统有必定的了解和知识的。这些调试程序是咱们重拾对复杂系统的洞察力的利器。Sun 公司有一位工程师叫作 Brendan Gregg,他是最初的 DTrace 的用户,甚至早于 DTrace 被开源出来。Brendan 编写了不少能够复用的基于 DTrace 的调试工具,一齐放在一个叫作 DTrace Toolkit 的开源项目中。Dtrace 是最先的动态追踪框架,也是最有名的一个。

DTrace 的优点是它采起了跟操做系统内核紧密集成的一种方式。D 语言的实现实际上是一个虚拟机(VM),有点像 Java 虚拟机(JVM)。它的一个好处在于 D 语言的运行时是常驻内核的,并且很是小巧,因此每一个调试工具的启动时间和退出时间都很短。可是我以为 DTrace 也是有明显缺点的。其中一个让我很难受的缺点是 D 语言缺少循环结构,这致使许多针对目标进程中的复杂数据结构的分析工具很难编写。虽然 DTrace 官方声称缺乏循环的缘由是为了不过热的循环,但显然 DTrace 是能够在 VM 级别上面有效限制每个循环的执行次数的。另一个较大的缺点是,DTrace 对于用户态代码的追踪支持比较弱,没有自动的加载用户态调试符号的功能,须要本身在 D 语言里面声明用到的用户态 C 语言结构体之类的类型。1

DTrace 的影响是很是大的,不少工程师把它移植到其余的操做系统。比方说苹果的 Mac OS X 操做系统上就有 DTrace 的移植。其实近些年发布的每一台苹果笔记本或者台式机上面,都有现成的 dtrace 命令行工具能够调用,你们能够去在苹果机器的命令行终端上尝试一下。这是苹果系统上面的一个 DTrace 的移植。FreeBSD 操做系统也有这样一个 DTrace 的移植。只不过它并非默认启用的。你须要经过命令去加载 FreeBSD 的 DTrace 内核模块。Oracle 也有在它本身的 Oracle Linux 操做系统发行版当中开始针对 Linux 内核进行 DTrace 移植。不过 Oracle 的移植工做好像一直没有多少转机,毕竟 Linux 内核并非 Oracle 控制的,而 DTrace 是须要和操做系统内核紧密集成的。出于相似的缘由,民间一些勇敢的工程师尝试的 DTrace 的 Linux 移植也一直距离生产级别的要求很远。

相比 Solaris 上面原生的 DTrace,这些 DTrace 移植都或多或少的缺少某些高级特性,因此从能力上来讲,还不及最本初的 DTrace。

DTrace 对 Linux 操做系统的另外一个影响反映在 SystemTap 这个开源项目。这是由 Red Hat 公司的工程师建立的较为独立的动态追踪框架。SystemTap 提供了本身的一种小语言,和 D 语言并不相同。显然,Red Hat 本身服务于很是多的企业级用户,他们的工程师天天须要处理的各类线上的“诡异问题”天然也是极多的。这种技术的产生必然是现实需求激发的。我以为 SystemTap 是目前 Linux 世界功能最强大,同时也是最实用的动态追踪框架。我在本身的工做当中已经成功使用多年。SystemTap 的做者 Frank Ch. Eigler 和 Josh Stone 等人,都是很是热情、同时很是聪明的工程师。我在 IRC 或者邮件列表里的提问,他们通常都会很是快且很是详尽地进行解答。值得一提的是,我也曾给 SystemTap 贡献过一个较为重要的新特性,使其能在任意的探针上下文中访问用户态的全局变量的取值。我当时合并到 SystemTap 主线的这个 C++ 补丁的规模达到了约一千行,多亏了 SystemTap 做者们的热心帮助2。这个新特性在我基于 SystemTap 实现的动态脚本语言(好比 Perl 和 Lua)的火焰图工具中扮演了关键角色。

SystemTap 的优势是它有很是成熟的用户态调试符号的自动加载,同时也有循环这样的语言结构能够去编写比较复杂的探针处理程序,能够支持不少很复杂的分析处理。因为 SystemTap 早些年在实现上的不成熟,致使互联网上充斥着不少针对它的已通过时了的诟病和批评。最近几年 SystemTap 已然有了长足的进步。我在 2017 年创办的 OpenResty Inc. 公司也对 SystemTap 作了很是大的加强和优化。

固然,SystemTap 也是有缺点的。首先,它并非 Linux 内核的一部分,就是说它并无与内核紧密集成,因此它须要一直不停地追赶主线内核的变化。另外一个缺点是,它一般是把它的“小语言”脚本(有点像 D 语言哦)动态编译成一个 Linux 内核模块的 C 源码,所以常常须要在线部署 C 编译器工具链和 Linux 内核的头文件。出于这些缘由,SystemTap 脚本的启动相比 DTrace 要慢得多,和 JVM 的启动时间倒有几分相似。虽然存在这些缺点3,但总的来讲,SystemTap 仍是一个很是成熟的动态追踪框架。

SystemTap 工做原理图

不管是 DTrace 仍是 SystemTap,其实都不支持编写完整的调试工具,由于它们都缺乏方便的命令行交互的原语。因此咱们才看到现实世界中许多基于它们的工具,其实最外面都有一个 Perl、Python 或者 Shell 脚本编写的包裹。为了便于使用一种干净的语言编写完整的调试工具,我曾经给 SystemTap 语言进行了扩展,实现了一个更高层的“宏语言”,叫作 stap++4。我本身用 Perl 实现的 stap++ 解释器能够直接解释执行 stap++ 源码,并在内部调用 SystemTap 命令行工具。有兴趣的朋友能够查看我开源在 GitHub 上面的 stapxx 这个代码仓库。这个仓库里面也包含了不少直接使用个人 stap++ 宏语言实现的完整的调试工具。

SystemTap 在生产上的应用

DTrace 有今天这么大的影响离不开著名的 DTrace 布道士 Brendan Gregg 老师。前面咱们也提到了他的名字。他最初是在 Sun Microsystems 公司,工做在 Solaris 的文件系统优化团队,是最先的 DTrace 用户。他写过好几本有关 DTrace 和性能优化方面的书,也写过不少动态追踪方面的博客文章。

2011 年我离开淘宝之后,曾经在福州过了一年所谓的“田园生活”。在田园生活的最后几个月当中,我经过 Brendan 的公开博客较为系统地学习了 DTrace 和动态追踪技术。其实最先据说 DTrace 是由于一位微博好友的评论,他只提到了 DTrace 这个名字。因而我便想了解一下这到底是什么东西。谁知,不了解不知道,一了解吓一跳。这居然是一个全新的世界,完全改变了我对整个计算世界的见解。因而我就花了很是多的时间,一篇一篇地仔细精读 Brendan 的我的博客。后来终于有一天,我有了一种大彻大悟的感受,终于能够融会贯通,掌握到了动态追踪技术的精妙。

2012 年我结束了在福州的“田园生活”,来到美国加入前面提到的那家 CDN 公司。而后我就当即开始着手把 SystemTap 以及我已领悟到的动态追踪的一整套方法,应用到这家 CDN 公司的全球网络当中去,用于解决那些很是诡异很是奇怪的线上问题。我在这家公司观察到其实不少工程师在排查线上问题的时候,常常会本身在软件系统里面埋点。这主要是在业务代码里,乃至于像 Nginx 这样的系统软件的代码基(code base)里,本身去作修改,添加一些计数器,或者去埋下一些记录日志的点。经过这种方式,大量的日志会在线上被实时地采集起来,进入专门的数据库,而后再进行离线分析。显然这种作法的成本是巨大的,不只涉及业务系统自己的修改和维护成本的陡然提升,并且全量采集和存储大量的埋点信息的在线开销,也是很是可观的。并且常常出现的状况是,张三今天在业务代码里面埋了一个采集点,李四明天又埋下另外一个类似的点,过后可能这些点又都被遗忘在了代码基里面,而没有人再去理会。最后这种点会愈来愈多,把代码基搞得愈来愈凌乱。这种侵入式的修改,会致使相应的软件,不管是系统软件仍是业务代码,变得愈来愈难以维护。

埋点的方式主要存在两大问题,一个是“太多”的问题,一个是“太少”的问题。“太多”是指咱们每每会采集一些根本不须要的信息,只是一时贪多贪全,从而形成没必要要的采集和存储开销。不少时候咱们经过采样就能进行分析的问题,可能会习惯性的进行全网全量的采集,这种代价累积起来显然是很是昂贵的。那“太少”的问题是指,咱们每每很难在一开始就规划好所需的全部信息采集点,毕竟没有人是先知,能够预知将来须要排查的问题。因此当咱们遇到新问题时,现有的采集点搜集到的信息几乎老是不够用的。这就致使频繁地修改软件系统,频繁地进行上线操做,大大增长了开发工程师和运维工程师的工做量,同时增长了线上发生更大故障的风险。

另一种暴力调试的作法也是咱们某些运维工程师常常采用的,即把机器拉下线,而后设置一系列临时的防火墙规则,以屏蔽用户流量或者本身的监控流量,而后在生产机上各类折腾。这是很繁琐影响很大的过程。首先它会让机器不能再继续服务,下降了整个在线系统的总的吞吐能力。同时有些只有真实流量才能复现的问题,此时再也没法复现了。能够想象这些粗暴的作法有多么让人头疼。

实际上运用 SystemTap 动态追踪技术能够很好地解决这样的问题,有“润物细无声”之妙。首先咱们不须要去修改咱们的软件栈(software stack)自己,无论是系统软件仍是业务软件。我常常会编写一些有针对性的工具,而后在一些关键的系统「穴位」上面放置一些通过仔细安排的探针。这些探针会采集各自的信息,同时调试工具会把这些信息汇总起来输出到终端。用这种方式我能够在某一台机器或某几台机器上面,经过采样的方式,很快地获得我想要的关键信息,从而快速地回答一些很是基本的问题,给后续的调试工做指明方向。

正如我前面提到的,与其在生产系统里面人工去埋点去记日志,再搜集日志入库,还不如把整个生产系统自己当作是一个能够直接查询的“数据库”,咱们直接从这个“数据库”里安全快捷地获得咱们想要的信息,并且毫不留痕迹,毫不去采集咱们不须要的信息。利用这种思想,我编写了不少调试工具,绝大部分已经开源在了 GitHub 上面,不少是针对像 Nginx、LuaJIT 和操做系统内核这样的系统软件,也有一些是针对更高层面的像 OpenResty 这样的 Web 框架。有兴趣的朋友能够查看 GitHub 上面的 nginx-systemtap-toolkitperl-systemtap-toolkitstappxx 这几个代码仓库。

个人 SystemTap 工具云

利用这些工具,我成功地定位了数不清的线上问题,有些问题甚至是我意外发现的。下面就随便举几个例子吧。

第一个例子是,我使用基于 SystemTap 的火焰图工具分析咱们线上的 Nginx 进程,结果发现有至关一部分 CPU 时间花费在了一条很是奇怪的代码路径上面。这实际上是我一位同事在好久以前调试一个老问题时遗留下来的临时的调试代码,有点儿像咱们前面提到的“埋点代码”。结果它就这样被遗忘在了线上,遗忘在了公司代码仓库里,虽然当时那个问题其实早已解决。因为这个代价高昂的“埋点代码”一直没有去除,因此一直都产生着较大的性能损耗,而一直都没有人注意到。因此可谓是我意外的发现。当时我就是经过采样的方式,让工具自动绘制出一张火焰图。我一看这张图就能够发现问题并能采起措施。这是很是很是有效的方式。

第二个例子是,不多量的请求存在延时较长的问题,即所谓的“长尾请求”。这些请求数目很低,但可能达到「秒级」这样的延时。当时有同事乱猜说是个人 OpenResty 有 bug,我不服气,因而当即编写了一个 SystemTap 工具去在线进行采样,对那些超过一秒总延时的请求进行分析。该工具会直接测试这些问题请求内部的时间分布,包括请求处理过程当中各个典型 I/O 操做的延时以及纯 CPU 计算延时。结果很快定位到是 OpenResty 在访问 Go 编写的 DNS 服务器时,出现延时缓慢。而后我再让个人工具输出这些长尾 DNS 查询的具体内容,发现都是涉及 CNAME 展开。显然,这与OpenResty 无关了,而进一步的排查和优化也有了明确的方向。

第三个例子是,咱们曾注意到某一个机房的机器存在比例明显高于其余机房的网络超时的问题,但也只有 1% 的比例。一开始咱们很天然的去怀疑网络协议栈方面的细节。但后来我经过一系列专门的 SystemTap 工具直接分析那些超时请求的内部细节,便定位到了是硬盘 配置方面的问题。从网络到硬盘,这种调试是很是有趣的。第一手的数据让咱们快速走上正确的轨道。

还有一个例子是,咱们曾经经过火焰图在 Nginx 进程里观察到文件的打开和关闭操做占用了较多的 CPU 时间,因而咱们很天然地启用了 Nginx 自身的文件句柄缓存配置,可是优化效果并不明显。因而再作出一张新的火焰图,便发现由于这回轮到 Nginx 的文件句柄缓存的元数据所使用的“自旋锁”占用不少 CPU 时间了。这是由于咱们虽然启用了缓存,但把缓存的大小设置得过大,因此致使元数据的自旋锁的开销抵消掉了缓存带来的好处。这一切都能在火焰图上面一目了然地看出来。假设咱们没有火焰图,而只是盲目地试验,极可能会得出 Nginx 的文件句柄缓存没用的错误结论,而不会去想到去调整缓存的参数。

最后一个例子是,咱们在某一次上线操做以后,在线上最新的火焰图中观察到正则表达式的编译操做占用了不少 CPU 时间,但其实咱们已经在线上启用了正则编译结果的缓存。很显然,咱们业务系统中用到的正则表达式的数量,已然超出了咱们最初设置的缓存大小,因而很天然地想到把线上的正则缓存调的更大一些。而后,咱们在线上的火焰图中便再看不到正则编译操做了。

经过这些例子咱们其实能够看到,不一样的数据中心,不一样的机器,乃至同一台机器的不一样时段,都会产生本身特有的一些新问题。咱们须要的是直接对问题自己进行分析,进行采样,而不是胡乱去猜想去试错。有了强大的工具,排错实际上是一个事半功倍的事情。

自从咱们成立了 OpenResty Inc. 公司之后,咱们研发了 OpenResty XRay 这一款全新一代的动态追踪平台。咱们已经再也不手动去使用 SystemTap 这样的开源解决方案了。

火焰图

前面咱们已经屡次提到了火焰图(Flame Graph)这种东西,那么火焰图是什么呢?它实际上是一个很是了不得的可视化方法,是由前面已经反复提到的 Brendan Gregg 同窗发明的。

火焰图就像是给一个软件系统拍的 X 光照片,能够很天然地把时间和空间两个维度上的信息融合在一张图上,以很是直观的形式展示出来,从而反映系统在性能方面的不少定量的统计规律。

Nginx 的 C 级别 on-CPU 火焰图示例

比方说,最经典的火焰图是统计某一个软件的全部代码路径在 CPU 上面的时间分布。经过这张分布图咱们就能够直观地看出哪些代码路径花费的 CPU 时间较多,而哪些则是可有可无的。进一步地,咱们能够在不一样的软件层面上生成火焰图,好比说能够在系统软件的 C/C++ 语言层面上画出一张图,而后再在更高的——好比说——动态脚本语言的层面,例如 Lua 和 Perl 代码的层面,画出火焰图。不一样层面的火焰图经常会提供不一样的视角,从而反映出不一样层面上的代码热点。

由于我本身维护着 OpenResty 这样的开源软件的社区,咱们有本身的邮件列表,我常常会鼓励报告问题的用户主动提供本身绘制的火焰图,这样咱们就能够惬意地看图说话,帮助用户快速地进行性能问题的定位,而不至于反复地试错,和用户一块儿去胡乱猜想,从而节约彼此大量的时间,皆大欢喜。

这里值得注意的是,即便是遇到咱们并不了解的陌生程序,经过看火焰图,也能够大体推出性能问题的所在,即便从未阅读过它的一行源码。这是一件很是了不得的事情。由于大部分程序实际上是编写良好的,也就是说它每每在软件构造的时候就使用了抽象层次,好比经过函数。这些函数的名称一般会包含语义上的信息,并在火焰图上面直接显示出来。经过这些函数名,咱们能够大体推测出对应的函数,乃至对应的某一条代码路径,大体是作什么事情的,从而推断出这个程序所存在的性能问题。因此,又回到那句老话,程序代码中的命名很是重要,不只有助于阅读源码,也有助于调试问题。而反过来,火焰图也为咱们提供了一条学习陌生的软件系统的捷径。毕竟重要的代码路径,几乎老是花费时间较多的那些,因此值得咱们重点研究;不然的话,这个软件的构造方式必然存在很大的问题。

火焰图其实能够拓展到其余维度,好比刚才咱们讲的火焰图是看程序运行在 CPU 上的时间在全部代码路径上的分布,这是 on-CPU 时间这个维度。相似地,某一个进程不运行在任何 CPU 上的时间其实也是很是有趣的,咱们称之为 off-CPU 时间。off-CPU 时间通常是这个进程由于某种缘由处于休眠状态,好比说在等待某一个系统级别的锁,或者被一个很是繁忙的进程调度器(scheduler)强行剥夺 CPU 时间片。这些状况都会致使这个进程没法运行在 CPU 上,可是仍然花费不少的挂钟时间。经过这个维度的火焰图咱们能够获得另外一幅很不同的图景。经过这个维度上的信息,咱们能够分析系统锁方面的开销(好比 sem_wait 这样的系统调用),某些阻塞的 I/O 操做(例如 openread 之类),还能够分析进程或线程之间争用 CPU 的问题。经过 off-CPU 火焰图,都一目了然。

应该说 off-CPU 火焰图也算是我本身的一个大胆尝试。记得最初我在加州和内华达州之间的一个叫作 Tahoe 的湖泊边,阅读 Brendan 关于 off-CPU 时间的一篇博客文章。我固然就想到,或许能够把 off-CPU 时间代替 on-CPU 时间应用到火焰图这种展示方式上去。因而回来后我就在公司的生产系统中作了这样一个尝试,使用 SystemTap 绘制出了 Nginx 进程的 off-CPU 火焰图。我在推特上公布了这个成功尝试以后,Brendan 还专门联系到我,说他本身以前也尝试过这种方式,但效果并不理想。我估计这是由于他当时将之应用于多线程的程序,好比 MySQL,而多线程的程序由于线程同步方面的缘由,off-CPU 图上会有不少噪音,容易掩盖真正有趣的那些部分。而我应用 off-CPU 火焰图的场景是像 Nginx 这样的单线程程序,因此 off-CPU 火焰图里每每会当即指示出那些阻塞 Nginx 事件循环的系统调用,抑或是 sem_wait 之类的锁操做,又或者是抢占式的进程调度器的强行介入,因而能够很是好地帮助分析一大类的性能问题。在这样的 off-CPU 火焰图中,惟一的“噪音”其实就是 Nginx 事件循环自己的 epoll_wait 这样的系统调用,很容易识别并忽略掉。

off-CPU 时间

相似地,咱们能够把火焰图拓展到其余的系统指标维度,好比内存泄漏的字节数。有一回我就使用“内存泄漏火焰图”快速定位了 Nginx 核心中的一处很微妙的泄漏问题。因为该泄漏发生在 Nginx 本身的内存池中,因此使用 ValgrindAddressSanitizer 这样的传统工具是没法捕捉到的。还有一次也是使用“内存泄漏火焰图”轻松定位了一位欧洲开发者本身编写的 Nginx C 模块中的泄漏。那处泄漏很是细微和缓慢,困挠了他好久,而我帮他定位前都不须要阅读他的源代码。细想起来我本身都会以为有些神奇。固然,咱们也能够将火焰图拓展到文件 I/O 的延时和数据量等其余系统指标。因此这真是一种了不得的可视化方法,能够用于不少彻底不一样的问题类别。

咱们的 OpenResty XRay 产品支持自动采样各类类型的火焰图,包括 C/C++ 级别火焰图、Lua 级别火焰图、off-CPU 和 on-CPU 火焰图、内存动态分配火焰图、内存对象引用关系火焰图、文件 IO 火焰图、等等。

方法论

前面咱们介绍到火焰图这样的基于采样的可视化方法,它其实算是很是通用的方法了。无论是什么系统,是用什么语言编写的,咱们通常均可以获得一张某种性能维度上的火焰图,而后轻松进行分析。但更多的时候,咱们可能须要对一些更深层次的更特殊的问题进行分析和排查,此时就须要编写一系列专门化的动态追踪工具,有计划有步骤地去逼近真正的问题。

在这个过程中,咱们推荐的策略是一种所谓的小步推动、连续求问的方式。也就是说咱们并不期望一下编写一个很庞大很复杂的调试工具,一会儿采集到全部可能须要的信息,从而一会儿解决掉最终的问题。相反,咱们会把最终问题的假设,分解成一系列的小假设,而后逐步求索,逐步验证,不断肯定会修正咱们的方向,不断地调整咱们的轨迹和咱们的假设,以接近最终的问题。这样作有一个好处是,每个步骤每个阶段的工具均可以足够的简单,那么这些工具自己犯错的可能性就大大下降。Brendan 也注意到他若是尝试编写多用途的复杂工具,这种复杂工具自己引入 bug 的可能性也大大提升了。而错误的工具会给出错误的信息,从而误导咱们得出错误的结论。这是很是危险的。简单工具的另外一大好处是,在采样过程中对生产系统产生的开销也会相对较小,毕竟引入的探针数目较少,每一个探针的处理程序也不会有太多太复杂的计算。这里的每个调试工具都有本身的针对性,均可以单独使用,那么这些工具在将来获得复用的机会也大大提升。因此总的来讲,这种调试策略是很是有益的。

值得一提的是,这里咱们拒绝所谓的“大数据”的调试作法。即咱们并不会去尝试一会儿采集尽量全的信息和数据。相反,咱们在每个阶段每个步骤上只采集咱们当前步骤真正须要的信息。在每一步上,基于咱们已经采集到的信息,去支持或者修正咱们原来的方案和原来的方向,而后去指导编写下一步更细化的分析工具。

另外,对于很是小频率发生的线上事件,咱们一般会采用“守株待兔”的作法,也就是说咱们会设一个阈值或其余筛选条件,坐等有趣的事件被咱们的探针捕获到。好比在追踪小频率的大延时请求的时候,咱们会在调试工具里,首先筛选出那些延时超过必定阈值的请求,而后针对这些请求,采集尽量多的实际须要的细节信息。这种策略其实跟咱们传统的尽量多的采集全量统计数据的作法彻底相反,正由于咱们是有针对性地、有具体策略地进行采样分析,咱们才能把损耗和开销降到最低点,避免无谓的资源浪费。

咱们的 OpenResty XRay 产品经过知识库和推理引擎,能够自动化应用各类动态追踪方面的方法论,能够自动使用系统性的方法,逐步缩小问题范围,直至定位问题根源,再报告给用户,并向用户建议优化或修复方法。

知识就是力量

我以为动态追踪技术很好地诠释了一句老话,那就是“知识就是力量”。

经过动态追踪工具,咱们能够把咱们原先对系统的一些认识和知识,转变成能够解决实际问题的很是实用的工具。咱们原先在计算机专业教育当中,经过课本了解到的那些本来抽象的概念,好比说虚拟文件系统、虚拟内存系统、进程调度器等等,如今均可以变得很是鲜活和具体。咱们第一次能够在实际的生产系统当中,真切地观察它们具体的运做,它们的统计规律,而不用把操做系统内核或者系统软件的源码改得面目全非。这些非侵入式的实时观测的能力,都得益于动态追踪技术。

这项技术就像是金庸小说里杨过使的那把玄铁重剑,彻底不懂武功的人天然是使不动的。但只要会一些武功,就能够越使越好,不断进步,直至木剑也能横行天下的境界。因此但凡你有一些系统方面的知识,就能够把这把“剑”挥动起来,就能够解决一些虽然基本但原先都没法想象的问题。并且你积累的系统方面的知识越多,这把“剑”就能够使得越好。并且,还有一点颇有意思的是,你每多知道一点,就立马能多解决一些新的问题。反过来,因为咱们能够经过这些调试工具解决不少问题,能够测量和学习到生产系统里面不少有趣的微观或宏观方面的统计规律,这些看得见的成果也会成为咱们学习更多的系统知识的强大动力。因而很天然地,这也就成为有追求的工程师的“练级神器”。

记得我在微博上面曾经说过,“鼓励工程师不断的深刻学习的工具才是有前途的好工具”。这实际上是一个良性的相互促进的过程。

开源与调试符号

前面咱们提到,动态追踪技术能够把正在运行的软件系统变成一个能够查询的实时只读数据库,可是作到这一点一般是有条件的,那就是这个软件系统得有比较完整的调试符号。那么调试符号是什么呢?调试符号通常是软件在编译的时候,由编译器生成的供调试使用的元信息。这些信息能够把编译后的二进制程序里面的不少细节信息,好比说函数和变量的地址、数据结构的内存布局等等,映射回源代码里面的那些抽象实体的名称,好比说函数名、变量名、类型名之类。Linux 世界常见的调试符号的格式称为 DWARF(与英文单词“矮人”相同)。正是由于有了这些调试符号,咱们在冰冷黑暗的二进制世界里面才有了一张地图,才有了一座灯塔,才可能去解释和还原这个底层世界里每个细微方面的语义,重建出高层次的抽象概念和关系。

“矮人”为咱们指明道路

一般也只有开源软件才容易生成调试符号,由于绝大多数闭源软件出于保密方面的缘由,并不会提供任何调试符号,以增长逆向工程和破解的难度。其中有一个例子是 Intel 公司的 IPP 这个程序库。IPP 针对 Intel 的芯片提供了不少常见算法的优化实现。咱们也曾经尝试过在生产系统上面去使用基于 IPP 的 gzip 压缩库,但不幸的是咱们遇到了问题—— IPP 会时不时的在线上崩溃。显然,没有调试符号的闭源软件在调试的时候会很是痛苦。咱们曾经跟 Intel 的工程师远程沟通了好屡次都没有办法定位和解决问题,最后只好放弃。若是有源代码,或者有调试符号,那么这个调试过程极可能会变的简单许多。

关于开源与动态追踪技术之间这种水乳相容的关系,Brendan Gregg 在他以前的一次分享当中也有说起。特别是当咱们的整个软件栈(software stack)都是开源的时候,动态追踪的威力才有可能获得最大限度的发挥。软件栈一般包括操做系统内核、各类系统软件以及更上层的高级语言程序。当整个栈所有开源的时候,咱们就能够垂手可得的从各个软件层面获得想要的信息,并将之转化为知识,转化为行动方案。

软件栈的典型构成

因为较复杂的动态追踪都会依赖于调试符号,而有些 C 编译器生成的调试符号是有问题的。这些含有错误的调试信息会致使动态追踪的效果打上很大的折扣,甚至直接阻碍咱们的分析。比方说使用很是普遍的 GCC 这个 C 编译器,在 4.5 这个版本以前生成的调试符号质量是不好的,而 4.5 以后则有了长足的进步,尤为是在开启编译器优化的状况下。

咱们的 OpenResty XRay 动态追踪平台会实时抓取公网上常见的开源软件的调试符号包和二进制包,并进行分析和索引。目前这个数据库已经索引了接近 10 TB 的数据了。

Linux 内核的支持

前面提到,动态追踪技术通常是基于操做系统内核的,而对于咱们平时使用很是普遍的 Linux 操做系统内核来讲,其动态追踪的支持之路是一个漫长而艰辛的过程。其中一个主要缘由或许是由于 Linux 的老大 Linus 一直以为这种技术没有必要。

最初 Red Hat 公司的工程师为 Linux 内核准备了一个所谓的 utrace 的补丁,用来支持用户态的动态追踪技术。这是 SystemTap 这样的框架最初仰赖的基础。在漫长的岁月里,Red Hat 家族的 Linux 发行版都默认包含了这个 utrace 补丁,好比 RHEL、CentOS 和 Fedora 之类。在那段 utrace 主导的日子里,SystemTap 只在 Red Hat 系的操做系统中有意义。这个 utrace 补丁最终也未能合并到主线版本的 Linux 内核中,它被另外一种折衷的方案所取代。

Linux 主线版本很早就拥有了 kprobes 这种机制,能够动态地在指定的内核函数的入口和出口等位置上放置探针,并定义本身的探针处理程序。

用户态的动态追踪支持姗姗来迟,经历了无数次的讨论和反复修改。从官方 Linux 内核的 3.5 这个版本开始,引入了基于 inode 的 uprobes 内核机制,能够安全地在用户态函数的入口等位置设置动态探针,并执行本身的探针处理程序。再后来,从 3.10 的内核开始,又融合了所谓的 uretprobes 这个机制5,能够进一步地在用户态函数的返回地址上设置动态探针。uprobes 和 uretprobes 加在一块儿,终于能够取代 utrace 的主要功能。utrace 补丁今后也完成了它的历史使命。而 SystemTap 如今也能在较新的内核上面,自动使用 uprobes 和 uretprobes 这些机制,而再也不依赖于 utrace 补丁。

最近几年 Linux 的主线开发者们,把原来用于防火墙的 netfilter 里所使用的动态编译器,即 BPF,扩展了一下,获得了一个所谓的 eBPF,能够做为某种更加通用的内核虚拟机。经过这种机制,咱们其实能够在 Linux 中构建相似 DTrace 那种常驻内核的动态追踪虚拟机。而事实上,最近已经有了一些这方面的尝试,好比说像 BPF 编译器(BCC)这样的工具,使用 LLVM 工具链来把 C 代码编译为 eBPF 虚拟机所接受的字节码。总的来讲,Linux 的动态追踪支持是变得愈来愈好的。特别是从 3.15 版本的内核开始,动态追踪相关的内核机制终于变得比较健壮和稳定了。惋惜的是,eBPF 在设计上一直有严重的限制,使得那些基于 eBPF 开发的动态追踪工具始终停留在较为简单的水平上,用个人话来讲,还停留在“石器时代”。虽然 SystemTap 最近也开始支持 eBPF 这个运行时,但这个运行时支持的 stap 语言特性也是极为有限的,即便 SystemTap 的老大 Frank 也表达了这方面的担忧。

硬件追踪

咱们看到动态追踪技术在软件系统的分析当中能够扮演很是关键的角色,那么很天然地会想到,是否也能够用相似的方法和思想去追踪硬件。

咱们知道其实操做系统是直接和硬件打交道的,那么经过追踪操做系统的某些驱动程序或者其余方面,咱们也能够间接地去分析与之相接的硬件设备的一些行为和问题。同时,现代硬件,好比说像 Intel 的 CPU,通常会内置一些性能统计方面的寄存器(Hardware Performance Counter),经过软件读取这些特殊寄存器里的信息,咱们也能够获得不少有趣的直接关于硬件的信息。好比说 Linux 世界里的 perf 工具最初就是为了这个目的。甚至包括 VMWare 这样的虚拟机软件也会去模拟这样特殊的硬件寄存器。基于这种特殊寄存器,也产生了像 Mozilla rr 这样有趣的调试工具,能够高效地进行进程执行过程的录制与回放。

直接对硬件内部设置动态探针并实施动态追踪,或许目前还存在于科幻层面,欢迎有兴趣的同窗可以贡献更多的灵感和信息。

死亡进程的遗骸分析

咱们前面看到的其实都是对活着的进程进行分析,或者说正在运行的程序。那么死的进程呢?对于死掉的进程,其实最多见的形式就是进程发生了异常崩溃,产生了所谓的 core dump 文件。其实对于这样死掉的进程剩下的“遗骸”,咱们也能够进行不少深刻的分析,从而有可能肯定它的死亡缘由。从这个意义上来说,咱们做为程序员扮演着「法医」这样的角色。

最经典的针对死进程遗骸进行分析的工具即是鼎鼎大名的 GNU Debugger(GDB)。那么 LLVM 世界也有一个相似的工具叫作 LLDB。显然,GDB 原生的命令语言是很是有局限的,咱们若是手工逐条命令地对 core dump 进行分析其实能获得地信息也很是有限。其实大部分工程师分析 core dump 也只是用 bt full 命令查看一下当前的 C 调用栈轨迹,抑或是利用 info reg 命令查看一下各个 CPU 寄存器的当前取值,又或者查看一下崩溃位置的机器代码序列,等等。而其实更多的信息深藏于在堆(heap)中分配的各类复杂的二进制数据结构之中。对堆里的复杂数据结构进行扫描和分析,显然须要自动化,咱们须要一种可编程的方式来编写复杂的 core dump 的分析工具。

顺应此需求,GDB 在较新的版本当中(我记得好像是从 7.0 开始的),内置了对 Python 脚本的支持。咱们如今能够用 Python 来实现较复杂的 GDB 命令,从而对 core dump 这样的东西进行深度分析。事实上我也用 Python 写了不少这样的基于 GDB 的高级调试工具,甚至不少工具是和分析活体进程的 SystemTap 工具一一对应起来的。与动态追踪相似,借助于调试符号,咱们能够在黑暗的“死亡世界”中找到光明之路。

黑暗世界里的光明之路

不过这种作法带来的一个问题是,工具的开发和移植变成了一个很大的负担。用 Python 这样的脚本语言来对 C 风格的数据结构进行遍历并非一件有趣的事情。这种奇怪的 Python 代码写多了真的会让人抓狂。另外,同一个工具,咱们既要用 SystemTap 的脚本语言写一遍,而后又要用 GDB 的 Python 代码来写一遍:无疑这是一个很大的负担,两种实现都须要仔细地进行开发和测试。它们虽然作的是相似的事情,但实现代码和相应的 API 都彻底不一样(这里值得一提的是,LLVM 世界的 LLDB 工具也提供了相似的 Python 编程支持,而那里的 Python API 又和 GDB 的不相兼容)。

咱们固然也能够用 GDB 对活体程序进行分析,但和 SystemTap 相比,GDB 最明显的就是性能问题。我曾经比较过一个较复杂工具的 SystemTap 版和 GDB Python 版。它们的性能相差有一个数量级。GDB 显然不是为这种在线分析来设计的,相反,更多地考虑了交互性的使用方式。虽然它也能以批处理的方式运行,可是内部的实现方式决定了它存在很是严重的性能方面的限制。其中最让我抓狂的莫过于 GDB 内部滥用 longjmp 来作常规的错误处理,从而带来了严重的性能损耗,这在 SystemTap 生成的 GDB 火焰图上是很是明显的。幸运地是,对死进程的分析老是能够离线进行,咱们不必在线去作这样的事情,因此时间上的考虑倒并非那么重要了。然而不幸的是,咱们的一些很复杂的 GDB Python 工具,须要运行好几分钟,即便是离线来作,也是让人感到很挫败的。

我本身曾经使用 SystemTap 对 GDB + Python 进行性能分析,并根据火焰图定位到了 GDB 内部最大的两处执行热点。而后,我给 GDB 官方提了两个 C 补丁,一是针对 Python 字符串操做,一是针对 GDB 的错误处理方式。它们使得咱们最复杂的 GDB Python 工具的总体运行速度提升了 100%。GDB 官方目前已经合并了其中一个补丁。使用动态追踪技术来分析和改进传统的调试工具,也是很是有趣的。

我已经把不少从前在本身的工做当中编写的 GDB Python 的调试工具开源到了 GitHub 上面,有兴趣的同窗能够去看一下。通常是放在 nginx-gdb-utils 这样的 GitHub 仓库里面,主要针对 Nginx 和 LuaJIT。我曾经利用这些工具协助 LuaJIT 的做者 Mike Pall 定位到了十多个 LuaJIT 内部的 bug。这些 bug 大多隐藏多年,都是 Just-in-Time (JIT) 编译器中的很微妙的问题。

因为死掉的进程不存在随时间变化的可能性,咱们姑且把这种针对 core dump 的分析称之为“静态追踪”吧。

我编写的 GDB 调试命令

咱们的 OpenResty XRay 产品经过 Y 语言 编译器,可让各类用 Y 语言编写的分析工具也能同时支持 GDB 这样的平台,从而自动化对 core dump 文件的深刻分析。

传统的调试技术

说到 GDB,咱们就不得不说一说动态追踪与传统的调试方法之间的区别与联系。细心的有经验的工程师应该会发现,其实动态追踪的“前身”就是在 GDB 里面设置断点,而后在断点处进行一系列检查的这种方式。只不过不一样的是,动态追踪老是强调非交互式的批处理,强调尽量低的性能损耗。而 GDB 这样的工具自然就是为交互操做而生的,因此实现并不考虑生产安全性,也不怎么在意性能损耗。通常它的性能损耗是极大的。同时 GDB 所基于的 ptrace 这种很古老的系统调用,其中的坑和问题也很是多。好比 ptrace 须要改变目标调试进程的父亲,还不容许多个调试者同时分析同一个进程。因此,从某种意义上来说,使用 GDB 能够模拟一种所谓的“穷人的动态追踪”。

不少编程初学者喜欢使用 GDB 进行“单步执行”,而在真实的工业界的生产开发当中,这种方式常常是很是缺少效率的。这是由于单步执行的时候每每会产生程序执行时序上的变化,致使不少与时序相关的问题没法再复现。另外,对于复杂的软件系统,单步执行很容易让人迷失在纷繁的代码路径当中,或者说迷失在所谓的“花园小径”当中,只见树木,不见森林。

因此,对于平常的开发过程中的调试,其实咱们仍是推荐最简单也是看起来最笨的方法,即在关键代码路径上打印输出语句。这样咱们经过查看日志等输出获得一个有很完整的上下文,从而可以有效进行分析的程序执行结果。当这种作法与测试驱动的开发方式结合起来的时候,尤其高效。显然,这种加日志和埋点的方式对于在线调试是不切合实际的,关于这一点,前面已经充分地讨论了。而另外一方面,传统的性能分析工具,像 Perl 的 DProf、C 世界里的 gprof、以及其余语言和环境的性能分析器(profiler),每每须要用特殊的选项从新编译程序,或者以特殊的方式从新运行程序。这种须要特别处理和配合的性能分析工具,显然并不适用在线的实时活体分析。

凌乱的调试世界

当今的调试世界是很凌乱的,正如咱们前面看到的有 DTrace、SystemTap、ePBF/BCC、GDB、LLDB 这些,还有不少不少咱们没有提到的,你们均可以在网络上查到。或许这从一个侧面反映出了咱们所处的这个真实世界的混乱。

以前有不少年我都在想,咱们能够设计并实现一种大一统的调试语言。后来我在 OpenResty Inc. 公司终于实现了这样的一种语言,叫作 Y 语言。它的编译器可以自动生成各类不一样的调试框架和技术所接受的输入代码。好比说生成 DTrace 接受的 D 语言代码,生成 SystemTap 接受的 stap 脚本,还有 GDB 接受的 Python 脚本,以及 LLDB 的另外一种不兼容 API 的 Python 脚本,抑或是 eBPF 接受的字节码,乃至 BCC 接受的某种 C 和 Python 代码的混合物。

若是咱们设计的一个调试工具须要移植到多个不一样的调试框架,那么显然人工移植的工做量是很是大的,正如我前面所提到的。而若是有这样一个大一统的 Y 语言,其编译器可以自动把同一份 Y 代码转换为针对各类不一样调试平台的输入代码,并针对那些平台进行自动优化,那么每一种调试工具咱们就只须要用 Y 语言写一遍就能够了。这将是巨大的解脱。而做为调试者本人,也没有必要亲自去学习全部那些具体的调试技术的凌乱的细节,亲自去踩每一种调试技术的“坑”。

Y 语言目前已经做为 OpenResty XRay 产品的一部分,提供给广大用户。

有朋友可能要问为何要叫作 Y 呢? 这是由于个人名字叫亦春,而亦字的汉语拼音的第一个字母就是 Y……固然了,还有更重要的缘由,那就是它是用来回答以「为何」开头的问题的语言,而「为何」在英语里面就是「why」,而 why 与 Y 谐音。

OpenResty XRay

OpenResty XRay 是由 OpenResty Inc. 公司提供的商业产品。OpenResty XRay 能够在无需目标程序任何配合的状况下,帮助用户深刻洞察其线上或者线下的各类软件系统的行为细节,有效地分析和定位各类性能问题、可靠性问题和安全问题。在技术上,它拥有比 SystemTap 更强的追踪功能,同时在性能上和易用性上也比 SystemTap 提升不少。它同时也能够支持自动分析 core dump 文件这样的程序遗骸。

有兴趣的朋友欢迎联系咱们,申请免费试用。

OpenResty XRay Console Dashboard

了解更多

若是你还想了解更多关于动态追踪的技术、工具、方法论和案例,能够关注咱们 OpenResty Inc. 公司的博客网站 。也欢迎扫码关注咱们的微信公众号:

咱们的微信公众号

同时很是欢迎你们试用咱们的 OpenResty XRay 商业产品。

动态追踪方面的先驱,Brendan Gregg 老师的博客也有不少精采内容。

鸣谢

本文获得了个人不少朋友和家人的帮助。首先要感谢师蕊辛苦的听写笔录工做;本文其实源自一次长达一小时的语音分享。而后要感谢不少朋友认真的审稿和意见反馈。同时也感谢个人父亲和妻子在文字上的耐心帮助。

关于做者

章亦春是开源项目 OpenResty® 的创始人,同时也是 OpenResty Inc. 公司的创始人和 CEO。他贡献了许多 Nginx 的第三方模块,至关多 Nginx 和 LuaJIT 核心补丁,而且设计了 OpenResty XRay 等产品。

译文

咱们目前只提供了中文原文(本文) 。咱们欢迎读者提供其余语言的翻译版本,只要是全文翻译不带省略,咱们都将会考虑采用,很是感谢!

咱们也在 OpenResty Inc. 官方博客 同步维护本文。


  1. SystemTap 和 OpenResty XRay 都没有这些限制和缺点。
  2. 在我成立 OpenResty Inc. 公司以后,咱们团队也给开源 SystemTap 项目贡献过数量众多的补丁,重新功能到 bug 修复。
  3. OpenResty XRay 的动态追踪平台并不存在 SystemTap 的这些缺点。
  4. stap++ 项目再也不继续维护,已被使用全新一代动态追踪技术的 OpenResty XRay 平台和工具集所取代。
  5. uretprobes 其实在实现上有很大的问题,由于它会直接修改目标程序的系统运行时栈,从而会破坏不少重要的功能,好比 stack unwinding。咱们的 OpenResty XRay 本身从新实现了相似 uretprobes 的效果,但却没有它的这些缺点。
相关文章
相关标签/搜索