硬核观察 #736 研究发现所有的网络拥堵算法都解决不了“饿死”问题
研究发现所有的网络拥塞控制算法都可能是非常不公平的尽管在过去大约 40 年中提出了数百种控制计算机网络“拥堵”延迟的关键算法，但没有明显的赢家。一项新的研究发现，每种拥塞控制算法都无法解决“饿死”的问题，即让一些用户占据了所有的带宽，而至少有一个人基本上得不到带宽。拥塞控制算法依靠数据包的丢失和延迟作为细节来推断拥塞，并决定发送数据的速度。然而，数据包可能因为网络拥堵以外的原因而丢失和延迟 —— 这被称之为网络“抖动”。而拥塞控制算法无法区分由拥塞和抖动造成的延迟的区别，由抖动引起的延迟是不可预测的，最终会导致出现饥饿的情况。消息来源：Slashdot老王点评：这就是算法的不公平，或许，限定在算法的层面上，并无一个完美的解决方案。TikTok 否认内置浏览器记录用户输入研究发现，流行 iOS 应用如 TikTok、Instagram、FB Messenger 和 Facebook 都支持用户在应用内打开浏览器访问第三方网站，期间会植入跟踪代码。以 TikTok 为例，它会记录用户的所有键盘输入，包括密码信用卡信息等敏感数据，以及屏幕上的每一次点击。从技术上说这相当于安装了一个键盘记录器。在这一报道发表之后，TikTok 发表声明，证实该功能存在，但 TikTok 并没有利用这些代码。TikTok 表示应用内浏览器旨在提供更好的用户体验，它植入第三方网站的 JS 代码是为了调试、排错和性能监视。消息来源：Solidot老王点评：植入这种第三方 SDK，即便是没启用，但是谁知道你什么时候会启用呢？美国绝大部分雇主正在跟踪员工的生产力根据《纽约时报》的调查，美国 10 个最大的私人雇主中，有 8 个正在为他们的雇员追踪生产力指标。其中一些软件测量活动时间，观察键盘停顿，甚至默默地计算击键次数。他们看到了从写电子邮件的时间到键盘活动的一切。如果员工没有达到预期的目标，就会产生影响：一个催促的通知，一个跳过的奖金，或者一个在家工作的日子被剥夺。对于在返回办公室的斗争中投降的雇主来说，这种监控是保持控制感的一种方式。消息来源：Slashdot老王点评：这可真是“工具人”，我觉得这和用代码行数来统计程序员的绩效具有“异曲同工之妙”。
这会成为放弃基于 Chromium 的浏览器并开始使用 Firefox 的一个理由吗？也许吧，决定权在你。即使你有了所有的隐私扩展和各种保护功能，别人仍然有方法可以识别你或跟踪你。请注意，并非所有浏览器都是如此，本文中，我们主要关注基于 Chromium 的浏览器，并将谷歌 Chrome 作为“主要嫌疑人”。以前，在 Chromium 浏览器上，尽管别人已经能够检测到你已安装的扩展程序，但许多扩展程序都实施了某些保护措施来防止这种检测。然而，一位名为 “z0ccc” 的安全研究人员发现了一种检测已安装 Chrome 浏览器扩展程序的新方法，该方法可进一步用于通过“浏览器指纹识别”来跟踪你。如果你还不知道的话： 浏览器指纹识别 Browser Fingerprinting 是指收集有关你的设备/浏览器的各种信息，以创建唯一的指纹 ID（哈希），从而在互联网上识别你的一种跟踪方法。“各种信息”包括：浏览器名称、版本、操作系统、已安装的扩展程序、屏幕分辨率和类似的技术数据。这听起来像是一种无害的数据收集技术，但可以使用这种跟踪方法在线跟踪你。检测谷歌 Chrome 扩展研究人员发布了一个开源项目 “Extension Fingerprints”，你可以使用它来测试你安装的 Chrome 扩展是否能被检测到。新技术涉及一种“时间差”方法，该工具比较了扩展程序获取资源的时间。与浏览器上未安装的其他扩展相比，受保护的扩展需要更多时间来获取资源。因此，这有助于从 1000 多个扩展列表中识别出一些扩展。关键是：即使有了各种新的进步和技术来防止跟踪，Chrome 网上应用店的扩展也可以被检测到。并且，在检测到已安装的扩展程序后，别人可以就使用浏览器指纹识别，对你进行在线跟踪。令人惊讶的是，即使你安装有 uBlocker、AdBlocker、或 Privacy Badger（一些流行的以隐私为重点的扩展程序）之类的扩展程序，使用了这种方法，它们也都可以被检测到。你可以在它的 GitHub 页面 上查看所有技术细节。如果你想自己测试它，请前往它的 扩展指纹识别网站 自行检查。拯救 Firefox？嗯，似乎是的，毕竟我出于各种原因，不断回到 Firefox。这个新发现的（跟踪）方法应该适用于所有基于 Chromium 的浏览器。我在 Brave 和谷歌 Chrome 上都测试了这个方法。研究人员还提到，该工具不能在使用微软应用商店中的扩展的微软 Edge 上工作。但是，相同的跟踪方法仍然有效。正如研究人员指出，Mozilla Firefox 可以避免这种情况，因为每个浏览器实例的扩展 ID 都是唯一的。via: https://news.itsfoss.com/chrome-extension-tracking/作者：Ankush Das 选题：lkxed 译者：lkxed 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
Grafana Tempo 是一个新的开源、大容量分布式跟踪后端。Grafana 的 Tempo 是出自 Grafana 实验室的一个简单易用、大规模的、分布式的跟踪后端。Tempo 集成了 Grafana、Prometheus 以及 Loki，并且它只需要对象存储进行操作，因此成本低廉，操作简单。我从一开始就参与了这个开源项目，所以我将介绍一些关于 Tempo 的基础知识，并说明为什么云原生社区会注意到它。分布式跟踪想要收集对应用程序请求的遥测数据是很常见的。但是在现在的服务器中，单个应用通常被分割为多个微服务，可能运行在几个不同的节点上。分布式跟踪是一种获得关于应用的性能细粒度信息的方式，该应用程序可能由离散的服务组成。当请求到达一个应用时，它提供了该请求的生命周期的统一视图。Tempo 的分布式跟踪可以用于单体应用或微服务应用，它提供 请求范围的信息，使其成为可观察性的第三个支柱（另外两个是度量和日志）。接下来是一个分布式跟踪系统生成应用程序甘特图的示例。它使用 Jaeger HotROD 的演示应用生成跟踪，并把它们存到 Grafana 云托管的 Tempo 上。这个图展示了按照服务和功能划分的请求处理时间。减少索引的大小在丰富且定义良好的数据模型中，跟踪包含大量信息。通常，跟踪后端有两种交互：使用元数据选择器（如服务名或者持续时间）筛选跟踪，以及筛选后的可视化跟踪。为了加强搜索，大多数的开源分布式跟踪框架会对跟踪中的许多字段进行索引，包括服务名称、操作名称、标记和持续时间。这会导致索引很大，并迫使你使用 Elasticsearch 或者 Cassandra 这样的数据库。但是，这些很难管理，而且大规模运营成本很高，所以我在 Grafana 实验室的团队开始提出一个更好的解决方案。在 Grafana 中，我们的待命调试工作流从使用指标报表开始（我们使用 Cortex 来存储我们应用中的指标，它是一个云原生基金会孵化的项目，用于扩展 Prometheus），深入研究这个问题，筛选有问题服务的日志（我们将日志存储在 Loki 中，它就像 Prometheus 一样，只不过 Loki 是存日志的），然后查看跟踪给定的请求。我们意识到，我们过滤时所需的所有索引信息都可以在 Cortex 和 Loki 中找到。但是，我们需要一个强大的集成，以通过这些工具实现跟踪的可发现性，并需要一个很赞的存储，以根据跟踪 ID 进行键值查找。这就是 Grafana Tempo 项目的开始。通过专注于给定检索跟踪 ID 的跟踪，我们将 Tempo 设计为最小依赖性、大容量、低成本的分布式跟踪后端。操作简单，性价比高Tempo 使用对象存储后端，这是它唯一的依赖。它既可以被用于单一的二进制下，也可以用于微服务模式（请参考仓库中的 例子，了解如何轻松上手）。使用对象存储还意味着你可以存储大量的应用程序的痕迹，而无需任何采样。这可以确保你永远不会丢弃那百万分之一的出错或具有较高延迟的请求的跟踪。与开源工具的强大集成Grafana 7.3 包括了 Tempo 数据源，这意味着你可以在 Grafana UI 中可视化来自Tempo 的跟踪。而且，Loki 2.0 的新查询特性 使得 Tempo 中的跟踪更简单。为了与 Prometheus 集成，该团队正在添加对 范例 exemplar 的支持，范例是可以添加到时间序列数据中的高基数元数据信息。度量存储后端不会对它们建立索引，但是你可以在 Grafana UI 中检索和显示度量值。尽管范例可以存储各种元数据，但是在这个用例中，存储跟踪 ID 是为了与 Tempo 紧密集成。这个例子展示了使用带有请求延迟直方图的范例，其中每个范例数据点都链接到 Tempo 中的一个跟踪。元数据一致性作为容器化应用程序运行的应用发出的遥测数据通常具有一些相关的元数据。这可以包括集群 ID、命名空间、吊舱 IP 等。这对于提供基于需求的信息是好的，但如果你能将元数据中包含的信息用于生产性的东西，那就更好了。 例如，你可以使用 Grafana 云代理将跟踪信息导入 Tempo 中，代理利用 Prometheus 服务发现机制轮询 Kubernetes API 以获取元数据信息，并且将这些标记添加到应用程序发出的跨域数据中。由于这些元数据也在 Loki 中也建立了索引，所以通过元数据转换为 Loki 标签选择器，可以很容易地从跟踪跳转到查看给定服务的日志。下面是一个一致元数据的示例，它可用于Tempo跟踪中查看给定范围的日志。云原生Grafana Tempo 可以作为容器化应用，你可以在如 Kubernetes、Mesos 等编排引擎上运行它。根据获取/查询路径上的工作负载，各种服务可以水平伸缩。你还可以使用云原生的对象存储，如谷歌云存储、Amazon S3 或者 Tempo Azure 博客存储。更多的信息，请阅读 Tempo 文档中的 架构部分。试一试 Tempo如果这对你和我们一样有用，可以 克隆 Tempo 仓库试一试。via: https://opensource.com/article/21/2/tempo-distributed-tracing作者：Annanay Agarwal 选题：lujun9972 译者：RiaXu 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
分布式跟踪系统的四大功能模块如何协同工作
了解分布式跟踪中的主要体系结构决策，以及各部分如何组合在一起。早在十年前，认真研究过分布式跟踪基本上只有学者和一小部分大型互联网公司中的人。对于任何采用微服务的组织来说，它如今成为一种筹码。其理由是确立的：微服务通常会发生让人意想不到的错误，而分布式跟踪则是描述和诊断那些错误的最好方法。也就是说，一旦你准备将分布式跟踪集成到你自己的应用程序中，你将很快意识到对于不同的人来说“ 分布式跟踪 Distributed Tracing ”一词意味着不同的事物。此外，跟踪生态系统里挤满了具有相似内容的重叠项目。本文介绍了分布式跟踪系统中四个（可能）独立的功能模块，并描述了它们间将如何协同工作。分布式跟踪：一种思维模型大多数用于跟踪的思维模型来源于 Google 的 Dapper 论文。OpenTracing 使用相似的术语，因此，我们从该项目借用了以下术语： 跟踪 Trace ：事物在分布式系统运行的过程描述。 跨度 Span ：一种命名的定时操作，表示工作流的一部分。跨度可接受键值对标签以及附加到特定跨度实例的细粒度的、带有时间戳的结构化日志。 跨度上下文 Span context ：携带分布式事务的跟踪信息，包括当它通过网络或消息总线将服务传递给服务时。跨度上下文包含跟踪标识符、跨度标识符以及跟踪系统所需传播到下游服务的任何其他数据。如果你想要深入研究这种思维模式的细节，请仔细参照 OpenTracing 技术规范。四大功能模块从应用层分布式跟踪系统的观点来看，现代软件系统架构如下图所示：现代软件系统的组件可分为三类：应用程序和业务逻辑：你的代码。广泛共享库：他人的代码广泛共享服务：他人的基础架构这三类组件有着不同的需求，驱动着监控应用程序的分布式跟踪系统的设计。最终的设计得到了四个重要的部分： 跟踪检测 API A tracing instrumentation API ：修饰应用程序代码 线路协议 Wire protocol ：在 RPC 请求中与应用程序数据一同发送的规定 数据协议 Data protocol ：将异步信息（带外）发送到你的分析系统的规定 分析系统 Analysis system ：用于处理跟踪数据的数据库和交互式用户界面为了更深入的解释这个概念，我们将深入研究驱动该设计的细节。如果你只需要我的一些建议，请跳转至下方的四大解决方案。需求，细节和解释应用程序代码、共享库以及共享式服务在操作上有显著的差别，这种差别严重影响了对其进行检测的请求操作。检测应用程序代码和业务逻辑在任何特定的微服务中，由微服务开发者编写的大部分代码是应用程序或者商业逻辑。这部分代码规定了特定区域的操作。通常，它包含任何特殊、独一无二的逻辑判断，这些逻辑判断首先证明了创建新型微服务的合理性。基本上按照定义，该代码通常不会在多个服务中共享或者以其他方式出现。也即是说你仍需了解它，这也意味着需要以某种方式对它进行检测。一些监控和跟踪分析系统使用 黑盒代理 black-box agents 自动检测代码，另一些系统更想使用显式的白盒检测工具。对于后者，抽象跟踪 API 提供了许多对于微服务的应用程序代码来说更为实用的优势：抽象 API 允许你在不重新编写检测代码的条件下换新的监视工具。你可能想要变更云服务提供商、供应商和监测技术，而一大堆不可移植的检测代码将会为该过程增加有意义的开销和麻烦。事实证明，除了生产监控之外，该工具还有其他有趣的用途。现有的项目使用相同的跟踪工具来驱动测试工具、分布式调试器、“混沌工程”故障注入器和其他元应用程序。但更重要的是，若将应用程序组件提取到共享库中要怎么办呢？由上述内容可得到结论：检测共享库在大多数应用程序中出现的实用程序代码（处理网络请求、数据库调用、磁盘写操作、线程、并发管理等）通常情况下是通用的，而非特别应用于某个特定应用程序。这些代码会被打包成库和框架，而后就可以被装载到许多的微服务上并且被部署到多种不同的环境中。其真正的不同是：对于共享代码，其他人则成为了使用者。大多数用户有不同的依赖关系和操作风格。如果尝试去使用该共享代码，你将会注意到几个常见的问题：你需要一个 API 来编写检测。然而，你的库并不知道你正在使用哪个分析系统。会有多种选择，并且运行在相同应用下的所有库无法做出不兼容的选择。由于这些包封装了所有网络处理代码，因此从请求报头注入和提取跨度上下文的任务往往指向 RPC 库。然而，共享库必须了解到每个应用程序正在使用哪种跟踪协议。最后，你不想强制用户使用相互冲突的依赖项。大多数用户有不同的依赖关系和操作风格。即使他们使用 gRPC，绑定的 gRPC 版本是否相同？因此任何你的库附带用于跟踪的监控 API 必定是免于依赖的。因此，一个（a）没有依赖关系、（b）与线路协议无关、（c）使用流行的供应商和分析系统的抽象 API 应该是对检测共享库代码的要求。检测共享式服务最后，有时整个服务（或微服务集合体）的通用性足以使许多独立的应用程序使用它们。这种共享式服务通常由第三方托管和管理，例如缓存服务器、消息队列以及数据库。从应用程序开发者的角度来看，理解共享式服务本质上是黑盒子是极其重要的。它不可能将你的应用程序监控注入到共享式服务。恰恰相反，托管服务通常会运行它自己的监控方案。四个方面的解决方案因此，抽象的跟踪应用程序接口将会帮助库发出数据并且注入/抽取跨度上下文。标准的线路协议将会帮助黑盒服务相互连接，而标准的数据格式将会帮助分离的分析系统合并其中的数据。让我们来看一下部分有希望解决这些问题的方案。跟踪 API：OpenTracing 项目如你所见，我们需要一个跟踪 API 来检测应用程序代码。为了将这种工具扩展到大多数进行跨度上下文注入和提取的共享库中，则必须以某种关键方式对 API 进行抽象。OpenTracing 项目主要针对解决库开发者的问题，OpenTracing 是一个与供应商无关的跟踪 API，它没有依赖关系，并且迅速得到了许多监控系统的支持。这意味着，如果库附带了内置的本地 OpenTracing 工具，当监控系统在应用程序启动连接时，跟踪将会自动启动。就个人而言，作为一个已经编写、发布和操作开源软件十多年的人，在 OpenTracing 项目上工作并最终解决这个观察性的难题令我十分满意。除了 API 之外，OpenTracing 项目还维护了一个不断增长的工具列表，其中一些可以在这里找到。如果你想参与进来，无论是通过提供一个检测插件，对你自己的 OSS 库进行本地测试，或者仅仅只想问个问题，都可以通过 Gitter 向我们打招呼。线路协议： HTTP 报头 trace-context为了监控系统能进行互操作，以及减轻从一个监控系统切换为另外一个时带来的迁移问题，需要标准的线路协议来传播跨度上下文。w3c 分布式跟踪上下文社区小组在努力制定此标准。目前的重点是制定一系列标准的 HTTP 报头。该规范的最新草案可以在此处找到。如果你对此小组有任何的疑问，邮件列表和Gitter 聊天室是很好的解惑地点。（LCTT 译注：本文原文发表于 2018 年 5 月，可能现在社区已有不同进展）数据协议 （还未出现！！）对于黑盒服务，在无法安装跟踪程序或无法与程序进行交互的情况下，需要使用数据协议从系统中导出数据。目前这种数据格式和协议的开发工作尚处在初级阶段，并且大多在 w3c 分布式跟踪上下文工作组的上下文中进行工作。需要特别关注的是在标准数据模式中定义更高级别的概念，例如 RPC 调用、数据库语句等。这将允许跟踪系统对可用数据类型做出假设。OpenTracing 项目也通过定义一套标准标签集来解决这一事务。该计划是为了使这两项努力结果相互配合。注意当前有一个中间地带。对于由应用程序开发者操作但不想编译或以其他方式执行代码修改的“网络设备”，动态链接可以帮助避免这种情况。主要的例子就是服务网格和代理，就像 Envoy 或者 NGINX。针对这种情况，可将兼容 OpenTracing 的跟踪器编译为共享对象，然后在运行时动态链接到可执行文件中。目前 C++ OpenTracing API 提供了该选项。而 JAVA 的 OpenTracing 跟踪器解析也在开发中。这些解决方案适用于支持动态链接，并由应用程序开发者部署的的服务。但从长远来看，标准的数据协议可以更广泛地解决该问题。分析系统：从跟踪数据中提取有见解的服务最后不得不提的是，现在有足够多的跟踪监视解决方案。可以在此处找到已知与 OpenTracing 兼容的监控系统列表，但除此之外仍有更多的选择。我更鼓励你研究你的解决方案，同时希望你在比较解决方案时发现本文提供的框架能派上用场。除了根据监控系统的操作特性对其进行评级外（更不用提你是否喜欢 UI 和其功能），确保你考虑到了上述三个重要方面、它们对你的相对重要性以及你感兴趣的跟踪系统如何为它们提供解决方案。结论最后，每个部分的重要性在很大程度上取决于你是谁以及正在建立什么样的系统。举个例子，开源库的作者对 OpenTracing API 非常感兴趣，而服务开发者对 trace-context 规范更感兴趣。当有人说一部分比另一部分重要时，他们的意思通常是“一部分对我来说比另一部分重要”。然而，事实是：分布式跟踪已经成为监控现代系统所必不可少的事物。在为这些系统进行构建模块时，“尽可能解耦”的老方法仍然适用。在构建像分布式监控系统一样的跨系统的系统时，干净地解耦组件是维持灵活性和前向兼容性地最佳方式。感谢你的阅读！现在当你准备好在你自己的应用程序中实现跟踪服务时，你已有一份指南来了解他们正在谈论哪部分部分以及它们之间如何相互协作。via: https://opensource.com/article/18/5/distributed-tracing作者：Ted Young 选题：lujun9972 译者：chenmu-kk 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
Google Analytics （GA）这个最流行的用户活动追踪工具我们或多或少都听说过甚至使用过，但它的用途并不仅仅限于对页面访问的追踪。作为一个既实用又流行的工具，它已经受到了广泛的欢迎，因此我们将要在下文中介绍如何在各种 Angular 和 React 单页应用中使用 Google Analytics。这篇文章源自这样一个问题：如何对单页应用中的页面访问进行跟踪？通常来说，有很多的方法可以解决这个问题，在这里我们只讨论其中的一种方法。下面我会使用 Angular 来写出对应的实现，如果你使用的是 React，相关的用法和概念也不会有太大的差别。接下来就开始吧。准备好应用程序首先需要有一个 追踪 ID tracking ID 。在开始编写业务代码之前，要先准备好一个追踪 ID，通过这个唯一的标识，Google Analytics 才能识别出某个点击或者某个页面访问是来自于哪一个应用。按照以下的步骤：访问 https://analytics.google.com；提交指定信息以完成注册，并确保可用于 Web 应用，因为我们要开发的正是一个 Web 应用；同意相关的条款，生成一个追踪 ID；保存好追踪 ID。追踪 ID 拿到了，就可以开始编写业务代码了。添加 analytics.js 脚本Google 已经帮我们做好了接入之前需要做的所有事情，接下来就是我们的工作了。不过我们要做的也很简单，只需要把下面这段脚本添加到应用的 index.html 里，就可以了： 现在我们来看一下 Google Analytics 是如何在应用程序中初始化的。创建追踪器首先创建一个应用程序的追踪器。在 app.component.ts 中执行以下两个步骤：声明一个名为 ga，类型为 any 的全局变量（在 Typescript 中需要制定变量类型）；将下面一行代码加入到 ngInInit() 中。ga('create', <你的追踪 ID>, 'auto');这样就已经成功地在应用程序中初始化了一个 Google Analytics 的追踪器了。由于追踪器的初始化是在 OnInit() 函数中执行的，因此每当应用程序启动，追踪器就会启动。在单页应用中记录页面访问情况我们需要实现的是记录 访问路由 route-visits 。如何记录用户在一个应用中不同部分的访问，这是一个难点。从功能上来看，单页应用中的路由对应了传统多页面应用中各个页面之间的跳转，因此我们需要记录访问路由。要做到这一点尽管不算简单，但仍然是可以实现的。在 app.component.ts 的 ngOnInit() 函数中添加以下的代码片段：import { Router, NavigationEnd } from '@angular/router'; ... constructor(public router: Router) {} ... this.router.events.subscribe( event => { if (event instanceof NavigationEnd) { ga('set', 'page', event.urlAfterRedirects); ga('send', { hitType: 'pageview', hitCallback: () => { this.pageViewSent = true; }}); } } );神奇的是，只需要这么几行代码，就实现了 Angular 应用中记录页面访问情况的功能。这段代码实际上做了以下几件事情：从 Angular Router 中导入了 Router、NavigationEnd；通过构造函数中将 Router 添加到组件中；然后订阅 router 事件，也就是由 Angular Router 发出的所有事件；只要产生了一个 NavigationEnd 事件实例，就将路由和目标作为一个页面访问进行记录。这样，只要使用到了页面路由，就会向 Google Analytics 发送一条页面访问记录，在 Google Analytics 的在线控制台中可以看到这些记录。类似地，我们可以用相同的方式来记录除了页面访问之外的活动，例如某个界面的查看次数或者时长等等。只要像上面的代码那样使用 hitCallBack() 就可以在有需要收集的数据的时候让应用程序作出反应，这里我们做的事情仅仅是把一个变量的值设为 true，但实际上 hitCallBack() 中可以执行任何代码。追踪用户交互活动除了页面访问记录之外，Google Analytics 还经常被用于追踪用户的交互活动，例如某个按钮的点击情况。“提交按钮被用户点击了多少次？”，“产品手册会被经常查阅吗？”这些都是 Web 应用程序的产品评审会议上的常见问题。这一节我们将会介绍如何实现这些数据的统计。按钮点击设想这样一种场景，需要统计到应用程序中某个按钮或链接被点击的次数，这是一个和注册之类的关键动作关系最密切的数据指标。下面我们来举一个例子：假设应用程序中有一个“感兴趣”按钮，用于显示即将推出的活动，你希望通过统计这个按钮被点击的次数来推测有多少用户对此感兴趣。那么我们可以使用以下的代码来实现这个功能：... params = { eventCategory: 'Button' , eventAction: 'Click' , eventLabel: 'Show interest' , eventValue: 1 }; showInterest() { ga('send', 'event', this.params); } ...现在看下这段代码实际上做了什么。正如前面说到，当我们向 Google Analytics 发送数据的时候，Google Analytics 就会记录下来。因此我们可以向 send() 方法传递不同的参数，以区分不同的事件，例如两个不同按钮的点击记录。1、首先我们定义了一个 params 对象，这个对象包含了以下几个字段：eventCategory – 交互发生的对象，这里对应的是按钮（button）eventAction – 发生的交互的类型，这里对应的是点击（click）eventLabel – 交互动作的标识，这里对应的是这个按钮的内容，也就是“感兴趣”eventValue – 与每个发生的事件实例相关联的值由于这个例子中是要统计点击了“感兴趣”按钮的用户数，因此我们把 eventValue 的值定为 1。2、对象构造完成之后，下一步就是将 params 对象作为请求负载发送到 Google Analytics，而这一步是通过事件绑定将 showInterest() 绑定在按钮上实现的。这也是使用 Google Analytics 追踪中最常用的发送事件方法。至此，Google Analytics 就可以通过记录按钮的点击次数来统计感兴趣的用户数了。追踪社交活动Google Analytics 还可以通过应用程序追踪用户在社交媒体上的互动。其中一种场景就是在应用中放置类似 Facebook 的点赞按钮，下面我们来看看如何使用 Google Analytics 来追踪这一行为。... fbLikeParams = { socialNetwork: 'Facebook', socialAction: 'Like', socialTarget: 'https://facebook.com/mypage' }; ... fbLike() { ga('send', 'social', this.fbLikeParams); }如果你觉得这段代码似曾相识，那是因为它确实跟上面统计“感兴趣”按钮点击次数的代码非常相似。下面我们继续看其中每一步的内容：1、构造发送的数据负载，其中包括以下字段：socialNetwork – 交互发生的社交媒体，例如 Facebook、Twitter 等等socialAction – 发生的交互类型，例如点赞、发表推文、分享等等socialTarget – 交互的目标 URL，一般是社交媒体账号的主页2、下一步是增加一个函数来发送整个交互记录。和统计按钮点击数量时相比，这里使用 send() 的方式有所不同。另外，我们还需要把这个函数绑定到已有的点赞按钮上。在追踪用户交互方面，Google Analytics 还可以做更多的事情，其中最重要的一种是针对异常的追踪，这让我们可以通过 Google Analytics 来追踪应用程序中出现的错误和异常。在本文中我们就不赘述这一点了，但我们鼓励读者自行探索。用户识别隐私是一项权利，而不是奢侈品Google Analytics 除了可以记录很多用户的操作和交互活动之外，这一节还将介绍一个不太常见的功能，就是可以控制是否对用户的身份进行追踪。CookiesGoogle Analytics 追踪用户活动的方式是基于 Cookies 的，因此我们可以自定义 Cookies 的名称以及一些其它的内容，请看下面这段代码：trackingID = 'UA-139883813-1' ; cookieParams = { cookieName: 'myGACookie', cookieDomain: window.location.hostname, cookieExpires: 604800 }; ... ngOnInit() { ga('create', this.trackingID, this.cookieParams); ... }在上面这段代码中，我们设置了 Google Analytics Cookies 的名称、域以及过期时间，这就让我们能够将不同网站或 Web 应用的 Cookies 区分开来。因此我们需要为我们自己的应用程序的 Google Analytics 追踪器的 Cookies 设置一个自定义的标识1，而不是一个自动生成的随机标识。IP 匿名在某些场景下，我们可能不需要知道应用程序的流量来自哪里。例如对于一个按钮点击的追踪器，我们只需要关心按钮的点击量，而不需要关心点击者的地理位置。在这种场景下，Google Analytics 允许我们只追踪用户的操作行为，而不获取到用户的 IP 地址。ipParams = { anonymizeIp: true }; ... ngOnInit() { ... ga('set', this.ipParams); ... }在上面这段代码中，我们将 Google Analytics 追踪器的 abibymizeIp 参数设置为 true。这样用户的 IP 地址就不会被 Google Analytics 所追踪，这可以让用户知道自己的隐私正在被保护。不被跟踪还有些时候用户可能不希望自己的行为受到追踪，而 Google Analytics 也允许这样的需求。因此也存在让用户不被追踪的选项。... optOut() { window['ga-disable-UA-139883813-1'] = true; } ...optOut() 是一个自定义函数，它可以禁用页面中的 Google Analytics 追踪，我们可以使用按钮或复选框上得事件绑定来使用这一个功能，这样用户就可以选择是否会被 Google Analytics 追踪。在本文中，我们讨论了 Google Analytics 集成到单页应用时的难点，并探索出了一种相关的解决方法。我们还了解到了如何在单页应用中追踪页面访问和用户交互，例如按钮点击、社交媒体活动等。最后，我们还了解到 Google Analytics 为用户提供了保护隐私的功能，尤其是用户的一些隐私数据并不需要参与到统计当中的时候。而用户也可以选择完全不受到 Google Analytics 的追踪。除此以外，Google Analytics 还可以做到很多其它的事情，这就需要我们继续不断探索了。via: https://opensourceforu.com/2019/10/all-that-you-can-do-with-google-analytics-and-more/作者：Ashwin Sathian 选题：lujun9972 译者：HankChow 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
ptrace(2)（“ 进程跟踪 process trace ”）系统调用通常都与调试有关。它是类 Unix 系统上通过原生调试器监测被调试进程的主要机制。它也是实现 strace（ 系统调用跟踪 system call trace ）的常见方法。使用 Ptrace，跟踪器可以暂停被跟踪进程，检查和设置寄存器和内存，监视系统调用，甚至可以 拦截 （ intercepting ） 系统调用。通过拦截功能，意味着跟踪器可以篡改系统调用参数，篡改系统调用的返回值，甚至阻塞某些系统调用。言外之意就是，一个跟踪器本身完全可以提供系统调用服务。这是件非常有趣的事，因为这意味着一个跟踪器可以仿真一个完整的外部操作系统，而这些都是在没有得到内核任何帮助的情况下由 Ptrace 实现的。问题是，在同一时间一个进程只能被一个跟踪器附着，因此在那个进程的调试期间，不可能再使用诸如 GDB 这样的工具去仿真一个外部操作系统。另外的问题是，仿真系统调用的开销非常高。在本文中，我们将专注于 x86-64 Linux 的 Ptrace，并将使用一些 Linux 专用的扩展。同时，在本文中，我们将忽略掉一些错误检查，但是完整的源代码仍然会包含这些错误检查。本文中的可直接运行的示例代码在这里：https://github.com/skeeto/ptrace-examplesstrace在进入到最有趣的部分之前，我们先从回顾 strace 的基本实现来开始。它不是 DTrace，但 strace 仍然非常有用。Ptrace 一直没有被标准化。它的接口在不同的操作系统上非常类似，尤其是在核心功能方面，但是在不同的系统之间仍然存在细微的差别。ptrace(2) 的原型基本上应该像下面这样，但特定的类型可能有些差别。long ptrace(int request, pid_t pid, void *addr, void *data);pid 是被跟踪进程的 ID。虽然同一个时间只有一个跟踪器可以附着到该进程上，但是一个跟踪器可以附着跟踪多个进程。request 字段选择一个具体的 Ptrace 函数，比如 ioctl(2) 接口。对于 strace，只需要两个：PTRACE_TRACEME：这个进程被它的父进程跟踪。PTRACE_SYSCALL：继续跟踪，但是在下一下系统调用入口或出口时停止。PTRACE_GETREGS：取得被跟踪进程的寄存器内容副本。另外两个字段，addr 和 data，作为所选的 Ptrace 函数的一般参数。一般情况下，可以忽略一个或全部忽略，在那种情况下，传递零个参数。strace 接口实质上是前缀到另一个命令之前。$ strace [strace options] program [arguments]最小化的 strace 不需要任何选项，因此需要做的第一件事情是 —— 假设它至少有一个参数 —— 在 argv 尾部的 fork(2) 和 exec(2) 被跟踪进程。但是在加载目标程序之前，新的进程将告知内核，目标程序将被它的父进程继续跟踪。被跟踪进程将被这个 Ptrace 系统调用暂停。pid_t pid = fork(); switch (pid) { case -1: /* error */ FATAL("%s", strerror(errno)); case 0: /* child */ ptrace(PTRACE_TRACEME, 0, 0, 0); execvp(argv[1], argv + 1); FATAL("%s", strerror(errno)); }父进程使用 wait(2) 等待子进程的 PTRACE_TRACEME，当 wait(2) 返回后，子进程将被暂停。waitpid(pid, 0, 0);在允许子进程继续运行之前，我们告诉操作系统，被跟踪进程和它的父进程应该一同被终止。一个真实的 strace 实现可能会设置其它的选择，比如： PTRACE_O_TRACEFORK。ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_EXITKILL);剩余部分就是一个简单的、无休止的循环了，每循环一次捕获一个系统调用。循环体总共有四步：等待进程进入下一个系统调用。输出系统调用的一个描述。允许系统调用去运行并等待返回。输出系统调用返回值。这个 PTRACE_SYSCALL 请求被用于等待下一个系统调用时开始，和等待那个系统调用退出。和前面一样，需要一个 wait(2) 去等待被跟踪进程进入期望的状态。ptrace(PTRACE_SYSCALL, pid, 0, 0); waitpid(pid, 0, 0);当 wait(2) 返回时，进行了系统调用的线程的寄存器中写入了该系统调用的系统调用号及其参数。尽管如此，操作系统仍然没有为这个系统调用提供服务。这个细节对后续操作很重要。接下来的一步是采集系统调用信息。这是各个系统架构不同的地方。在 x86-64 上，系统调用号是在 rax 中传递的，而参数（最多 6 个）是在 rdi、rsi、rdx、r10、r8 和 r9 中传递的。这些寄存器是由另外的 Ptrace 调用读取的，不过这里再也不需要 wait(2) 了，因为被跟踪进程的状态再也不会发生变化了。struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, 0, &regs); long syscall = regs.orig_rax; fprintf(stderr, "%ld(%ld, %ld, %ld, %ld, %ld, %ld)", syscall, (long)regs.rdi, (long)regs.rsi, (long)regs.rdx, (long)regs.r10, (long)regs.r8, (long)regs.r9);这里有一个警告。由于 内核的内部用途，系统调用号是保存在 orig_rax 中而不是 rax 中。而所有的其它系统调用参数都是非常简单明了的。接下来是它的另一个 PTRACE_SYSCALL 和 wait(2)，然后是另一个 PTRACE_GETREGS 去获取结果。结果保存在 rax 中。ptrace(PTRACE_GETREGS, pid, 0, &regs); fprintf(stderr, " = %ld\n", (long)regs.rax);这个简单程序的输出也是非常粗糙的。这里的系统调用都没有符号名，并且所有的参数都是以数字形式输出，甚至是一个指向缓冲区的指针也是如此。更完整的 strace 输出将能知道哪个参数是指针，并使用 process_vm_readv(2) 从被跟踪进程中读取哪些缓冲区，以便正确输出它们。然而，这些仅仅是系统调用拦截的基础工作。系统调用拦截假设我们想使用 Ptrace 去实现如 OpenBSD 的 pledge(2) 这样的功能，它是 一个进程 承诺 pledge 只使用一套受限的系统调用。初步想法是，许多程序一般都有一个初始化阶段，这个阶段它们都需要进行许多的系统访问（比如，打开文件、绑定套接字、等等）。初始化完成以后，它们进行一个主循环，在主循环中它们处理输入，并且仅使用所需的、很少的一套系统调用。在进入主循环之前，一个进程可以限制它自己只能运行所需要的几个操作。如果 程序有缺陷，能够通过恶意的输入去利用该缺陷，这个承诺可以有效地限制漏洞利用的实现。使用与 strace 相同的模型，但不是输出所有的系统调用，我们既能够阻塞某些系统调用，也可以在它的行为异常时简单地终止被跟踪进程。终止它很容易：只需要在跟踪器中调用 exit(2)。因此，它也可以被设置为去终止被跟踪进程。阻塞系统调用和允许子进程继续运行都只是些雕虫小技而已。最棘手的部分是当系统调用启动后没有办法去中断它。当跟踪器在入口从 wait(2) 中返回到系统调用时，从一开始停止一个系统调用的仅有方式是，终止被跟踪进程。然而，我们不仅可以“搞乱”系统调用的参数，也可以改变系统调用号本身，将它修改为一个不存在的系统调用。返回时，在 errno 中 通过正常的内部信号，我们就可以报告一个“友好的”错误信息。for (;;) { /* Enter next system call */ ptrace(PTRACE_SYSCALL, pid, 0, 0); waitpid(pid, 0, 0); struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, 0, &regs); /* Is this system call permitted? */ int blocked = 0; if (is_syscall_blocked(regs.orig_rax)) { blocked = 1; regs.orig_rax = -1; // set to invalid syscall ptrace(PTRACE_SETREGS, pid, 0, &regs); } /* Run system call and stop on exit */ ptrace(PTRACE_SYSCALL, pid, 0, 0); waitpid(pid, 0, 0); if (blocked) { /* errno = EPERM */ regs.rax = -EPERM; // Operation not permitted ptrace(PTRACE_SETREGS, pid, 0, &regs); } }这个简单的示例只是检查了系统调用是否违反白名单或黑名单。而它们在这里并没有差别，比如，允许文件以只读而不是读写方式打开（open(2)），允许匿名内存映射但不允许非匿名映射等等。但是这里仍然没有办法去动态撤销被跟踪进程的权限。跟踪器与被跟踪进程如何沟通？使用人为的系统调用！创建一个人为的系统调用对于我的这个类似于 pledge 的系统调用 —— 我可以通过调用 xpledge() 将它与真实的系统调用区分开 —— 我设置 10000 作为它的系统调用号，这是一个非常大的数字，真实的系统调用中从来不会用到它。#define SYS_xpledge 10000为演示需要，我同时构建了一个非常小的接口，这在实践中并不是个好主意。它与 OpenBSD 的 pledge(2) 稍有一些相似之处，它使用了一个 字符串接口。事实上，设计一个健壮且安全的权限集是非常复杂的，正如在 pledge(2) 的手册页面上所显示的那样。下面是对被跟踪进程的系统调用的完整接口和实现：#define _GNU_SOURCE #include <unistd.h> #define XPLEDGE_RDWR (1 << 0) #define XPLEDGE_OPEN (1 << 1) #define xpledge(arg) syscall(SYS_xpledge, arg)如果给它传递个参数 0 ，仅允许一些基本的系统调用，包括那些用于去分配内存的系统调用（比如 brk(2)）。 PLEDGE_RDWR 位允许 各种 读和写的系统调用（read(2)、readv(2)、pread(2)、preadv(2) 等等）。PLEDGE_OPEN 位允许 open(2)。为防止发生提升权限的行为，pledge() 会拦截它自己 —— 但这样也防止了权限撤销，以后再细说这方面内容。在 xpledge 跟踪器中，我需要去检查这个系统调用：/* Handle entrance */ switch (regs.orig_rax) { case SYS_pledge: register_pledge(regs.rdi); break; }操作系统将返回 ENOSYS（函数尚未实现），因为它不是一个真实的系统调用。为此在退出时我用一个 success(0) 去覆写它。/* Handle exit */ switch (regs.orig_rax) { case SYS_pledge: ptrace(PTRACE_POKEUSER, pid, RAX * 8, 0); break; }我写了一小段测试程序去打开 /dev/urandom，做一个读操作，尝试去承诺后，然后试着第二次打开 /dev/urandom，然后确认它能够读取原始的 /dev/urandom 文件描述符。在没有承诺跟踪器的情况下运行，输出如下：$ ./example fread("/dev/urandom")[1] = 0xcd2508c7 XPledging... XPledge failed: Function not implemented fread("/dev/urandom")[2] = 0x0be4a986 fread("/dev/urandom")[1] = 0x03147604做一个无效的系统调用并不会让应用程序崩溃。它只是失败，这是一个很方便的返回方式。当它在跟踪器下运行时，它的输出如下：>$ ./xpledge ./example fread("/dev/urandom")[1] = 0xb2ac39c4 XPledging... fopen("/dev/urandom")[2]: Operation not permitted fread("/dev/urandom")[1] = 0x2e1bd1c4这个承诺很成功，第二次的 fopen(3) 并没有进行，因为跟踪器用一个 EPERM 阻塞了它。可以将这种思路进一步发扬光大，比如，改变文件路径或返回一个假的结果。一个跟踪器可以很高效地 chroot 它的被跟踪进程，通过一个系统调用将任意路径传递给 root 从而实现 chroot 路径。它甚至可以对用户进行欺骗，告诉用户它以 root 运行。事实上，这些就是 Fakeroot NG 程序所做的事情。仿真外部系统假设你不满足于仅拦截一些系统调用，而是想拦截全部系统调用。你就会有了 一个打算在其它操作系统上运行的二进制程序，无需系统调用，这个二进制程序可以一直运行。使用我在前面所描述的这些内容你就可以管理这一切。跟踪器可以使用一个假冒的东西去代替系统调用号，允许它失败，以及为系统调用本身提供服务。但那样做的效率很低。其实质上是对每个系统调用做了三个上下文切换：一个是在入口上停止，一个是让系统调用总是以失败告终，还有一个是在系统调用退出时停止。从 2005 年以后，对于这个技术，PTrace 的 Linux 版本有更高效的操作：PTRACE_SYSEMU。PTrace 仅在每个系统调用发出时停止一次，在允许被跟踪进程继续运行之前，由跟踪器为系统调用提供服务。for (;;) { ptrace(PTRACE_SYSEMU, pid, 0, 0); waitpid(pid, 0, 0); struct user_regs_struct regs; ptrace(PTRACE_GETREGS, pid, 0, &regs); switch (regs.orig_rax) { case OS_read: /* ... */ case OS_write: /* ... */ case OS_open: /* ... */ case OS_exit: /* ... */ /* ... and so on ... */ } }从任何具有（足够）稳定的系统调用 ABI（LCTT 译注：应用程序二进制接口），在相同架构的机器上运行一个二进制程序时，你只需要 PTRACE_SYSEMU 跟踪器、一个加载器（用于代替 exec(2)），和这个二进制程序所需要（或仅运行静态的二进制程序）的任何系统库即可。事实上，这听起来有点像一个有趣的周末项目。参见给 Linux 内核克隆实现一个 OpenBSD 承诺via: http://nullprogram.com/blog/2018/06/23/作者：Chris Wellons 选题：lujun9972 译者：qhwdw 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在内核层面上分析事件有很多的工具：SystemTap、ktap、Sysdig、LTTNG 等等，你也可以在网络上找到关于这些工具的大量介绍文章和资料。而对于使用 Linux 原生机制去跟踪系统事件以及检索/分析故障信息的方面的资料却很少找的到。这就是 ftrace，它是添加到内核中的第一款跟踪工具，今天我们来看一下它都能做什么，让我们从它的一些重要术语开始吧。内核跟踪和分析 内核分析 Kernel profiling 可以发现性能“瓶颈”。分析能够帮我们发现在一个程序中性能损失的准确位置。特定的程序生成一个 概述 profile — 这是一个事件总结 — 它能够用于帮我们找出哪个函数占用了大量的运行时间。尽管这些程序并不能识别出为什么会损失性能。瓶颈经常发生在无法通过分析来识别的情况下。要推断出为什么会发生事件，就必须保存发生事件时的相关上下文，这就需要去 跟踪 tracing 。跟踪可以理解为在一个正常工作的系统上活动的信息收集过程。它使用特定的工具来完成这项工作，就像录音机来记录声音一样，用它来记录各种系统事件。跟踪程序能够同时跟踪应用级和操作系统级的事件。它们收集的信息能够用于诊断多种系统问题。有时候会将跟踪与日志比较。它们两者确时很相似，但是也有不同的地方。对于跟踪，记录的信息都是些低级别事件。它们的数量是成百上千的，甚至是成千上万的。对于日志，记录的信息都是些高级别事件，数量上通常少多了。这些包含用户登录系统、应用程序错误、数据库事务等等。就像日志一样，跟踪数据可以被原样读取，但是用特定的应用程序提取的信息更有用。所有的跟踪程序都能这样做。在内核跟踪和分析方面，Linux 内核有三个主要的机制： 跟踪点 tracepoint ：一种基于静态测试代码的工作机制 探针 kprobe ：一种动态跟踪机制，用于在任意时刻中断内核代码的运行，调用它自己的处理程序，在完成需要的操作之后再返回perf\_events —— 一个访问 PMU（ 性能监视单元 Performance Monitoring Unit ）的接口我并不想在这里写关于这些机制方面的内容，任何对它们感兴趣的人可以去访问 Brendan Gregg 的博客。使用 ftrace，我们可以与这些机制进行交互，并可以从用户空间直接得到调试信息。下面我们将讨论这方面的详细内容。示例中的所有命令行都是在内核版本为 3.13.0-24 的 Ubuntu 14.04 中运行的。ftrace：常用信息ftrace 是 Function Trace 的简写，但它能做的远不止这些：它可以跟踪上下文切换、测量进程阻塞时间、计算高优先级任务的活动时间等等。ftrace 是由 Steven Rostedt 开发的，从 2008 年发布的内核 2.6.27 中开始就内置了。这是为记录数据提供的一个调试 Ring 缓冲区的框架。这些数据由集成到内核中的跟踪程序来采集。ftrace 工作在 debugfs 文件系统上，在大多数现代 Linux 发行版中都默认挂载了。要开始使用 ftrace，你将进入到 sys/kernel/debug/tracing 目录（仅对 root 用户可用）：# cd /sys/kernel/debug/tracing 这个目录的内容看起来应该像这样：аvailable_filter_functions options stack_trace_filter available_tracers per_cpu trace buffer_size_kb printk_formats trace_clock buffer_total_size_kb README trace_marker current_tracer saved_cmdlines trace_options dyn_ftrace_total_info set_event trace_pipe enabled_functions set_ftrace_filter trace_stat events set_ftrace_notrace tracing_cpumask free_buffer set_ftrace_pid tracing_max_latency function_profile_enabled set_graph_function tracing_on instances set_graph_notrace tracing_thresh kprobe_events snapshot uprobe_events kprobe_profile stack_max_size uprobe_profile 我不想去描述这些文件和子目录；它们的描述在 官方文档 中已经写的很详细了。我只想去详细介绍与我们这篇文章相关的这几个文件：available\_tracers —— 可用的跟踪程序current\_tracer —— 正在运行的跟踪程序tracing\_on —— 负责启用或禁用数据写入到 Ring 缓冲区的系统文件（如果启用它，数字 1 被添加到文件中，禁用它，数字 0 被添加）trace —— 以人类友好格式保存跟踪数据的文件可用的跟踪程序我们可以使用如下的命令去查看可用的跟踪程序的一个列表：root@andrei:/sys/kernel/debug/tracing#: cat available_tracers blk mmiotrace function_graph wakeup_rt wakeup function nop 我们来快速浏览一下每个跟踪程序的特性：function —— 一个无需参数的函数调用跟踪程序function\_graph —— 一个使用子调用的函数调用跟踪程序blk —— 一个与块 I/O 跟踪相关的调用和事件跟踪程序（它是 blktrace 使用的）mmiotrace —— 一个内存映射 I/O 操作跟踪程序nop —— 最简单的跟踪程序，就像它的名字所暗示的那样，它不做任何事情（尽管在某些情况下可能会派上用场，我们将在后文中详细解释）函数跟踪程序在开始介绍函数跟踪程序 ftrace 之前，我们先看一个测试脚本：#!/bin/sh dir=/sys/kernel/debug/tracing sysctl kernel.ftrace_enabled=1 echo function > ${dir}/current_tracer echo 1 > ${dir}/tracing_on sleep 1 echo 0 > ${dir}/tracing_on less ${dir}/trace 这个脚本是非常简单的，但是还有几个需要注意的地方。命令 sysctl ftrace.enabled=1 启用了函数跟踪程序。然后我们通过写它的名字到 current_tracer 文件来启用 current tracer。接下来，我们写入一个 1 到 tracing_on，它启用了 Ring 缓冲区。这些语法都要求在 1 和 > 符号前后有一个空格；写成像 echo 1> tracing_on 这样将不能工作。一行之后我们禁用它（如果 0 写入到 tracing_on， 缓冲区不会被清除并且 ftrace 并不会被禁用）。我们为什么这样做呢？在两个 echo 命令之间，我们看到了命令 sleep 1。我们启用了缓冲区，运行了这个命令，然后禁用它。这将使跟踪程序采集了这个命令运行期间发生的所有系统调用的信息。在脚本的最后一行，我们写了一个在控制台上显示跟踪数据的命令。一旦脚本运行完成后，我们将看到下列的输出（这里只列出了一个小片断）：# tracer: function # # entries-in-buffer/entries-written: 29571/29571 #P:2 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | trace.sh-1295 [000] .... 90.502874: mutex_unlock <-rb_simple_write trace.sh-1295 [000] .... 90.502875: __fsnotify_parent <-vfs_write trace.sh-1295 [000] .... 90.502876: fsnotify <-vfs_write trace.sh-1295 [000] .... 90.502876: __srcu_read_lock <-fsnotify trace.sh-1295 [000] .... 90.502876: __srcu_read_unlock <-fsnotify trace.sh-1295 [000] .... 90.502877: __sb_end_write <-vfs_write trace.sh-1295 [000] .... 90.502877: syscall_trace_leave <-int_check_syscall_exit_work trace.sh-1295 [000] .... 90.502878: context_tracking_user_exit <-syscall_trace_leave trace.sh-1295 [000] .... 90.502878: context_tracking_user_enter <-syscall_trace_leave trace.sh-1295 [000] d... 90.502878: vtime_user_enter <-context_tracking_user_enter trace.sh-1295 [000] d... 90.502878: _raw_spin_lock <-vtime_user_enter trace.sh-1295 [000] d... 90.502878: __vtime_account_system <-vtime_user_enter trace.sh-1295 [000] d... 90.502878: get_vtime_delta <-__vtime_account_system trace.sh-1295 [000] d... 90.502879: account_system_time <-__vtime_account_system trace.sh-1295 [000] d... 90.502879: cpuacct_account_field <-account_system_time trace.sh-1295 [000] d... 90.502879: acct_account_cputime <-account_system_time trace.sh-1295 [000] d... 90.502879: __acct_update_integrals <-acct_account_cputime 这个输出以“缓冲区中的信息条目数量”和“写入的全部条目数量”开始。这两者的数据差异是缓冲区中事件的丢失数量（在我们的示例中没有发生丢失）。在这里有一个包含下列信息的函数列表：进程标识符（PID）运行这个进程的 CPU（CPU#）进程开始时间（TIMESTAMP）被跟踪函数的名字以及调用它的父级函数；例如，在我们输出的第一行，rb_simple_write 调用了 mutex-unlock 函数。function\_graph 跟踪程序function\_graph 跟踪程序的工作和函数跟踪程序一样，但是它更详细：它显示了每个函数的进入和退出点。使用这个跟踪程序，我们可以跟踪函数的子调用并且测量每个函数的运行时间。我们来编辑一下最后一个示例的脚本：#!/bin/sh dir=/sys/kernel/debug/tracing sysctl kernel.ftrace_enabled=1 echo function_graph > ${dir}/current_tracer echo 1 > ${dir}/tracing_on sleep 1 echo 0 > ${dir}/tracing_on less ${dir}/trace 运行这个脚本之后，我们将得到如下的输出：# tracer: function_graph # # CPU DURATION FUNCTION CALLS # | | | | | | | 0) 0.120 us | } /* resched_task */ 0) 1.877 us | } /* check_preempt_curr */ 0) 4.264 us | } /* ttwu_do_wakeup */ 0) + 29.053 us | } /* ttwu_do_activate.constprop.74 */ 0) 0.091 us | _raw_spin_unlock(); 0) 0.260 us | ttwu_stat(); 0) 0.133 us | _raw_spin_unlock_irqrestore(); 0) + 37.785 us | } /* try_to_wake_up */ 0) + 38.478 us | } /* default_wake_function */ 0) + 39.203 us | } /* pollwake */ 0) + 40.793 us | } /* __wake_up_common */ 0) 0.104 us | _raw_spin_unlock_irqrestore(); 0) + 42.920 us | } /* __wake_up_sync_key */ 0) + 44.160 us | } /* sock_def_readable */ 0) ! 192.850 us | } /* tcp_rcv_established */ 0) ! 197.445 us | } /* tcp_v4_do_rcv */ 0) 0.113 us | _raw_spin_unlock(); 0) ! 205.655 us | } /* tcp_v4_rcv */ 0) ! 208.154 us | } /* ip_local_deliver_finish */ 在这个图中，DURATION 展示了花费在每个运行的函数上的时间。注意使用 + 和 ! 符号标记的地方。加号（+）意思是这个函数花费的时间超过 10 毫秒；而感叹号（!）意思是这个函数花费的时间超过了 100 毫秒。在 FUNCTION_CALLS 下面，我们可以看到每个函数调用的信息。和 C 语言一样使用了花括号（{）标记每个函数的边界，它展示了每个函数的开始和结束，一个用于开始，一个用于结束；不能调用其它任何函数的叶子函数用一个分号（;）标记。函数过滤器ftrace 输出可能会很大，精确找出你所需要的内容可能会非常困难。我们可以使用过滤器去简化我们的搜索：输出中将只显示与我们感兴趣的函数相关的信息。为实现过滤，我们只需要在 set_ftrace_filter 文件中写入我们需要过滤的函数的名字即可。例如：root@andrei:/sys/kernel/debug/tracing# echo kfree > set_ftrace_filter 如果禁用过滤器，我们只需要在这个文件中添加一个空白行即可：root@andrei:/sys/kernel/debug/tracing# echo > set_ftrace_filter 通过运行这个命令：root@andrei:/sys/kernel/debug/tracing# echo kfree > set_ftrace_notrace 我们将得到相反的结果：输出将包含除了 kfree() 以外的任何函数的信息。另一个有用的选项是 set_ftrace_pid。它是为在一个特定的进程运行期间调用跟踪函数准备的。ftrace 还有很多过滤选项。对于它们更详细的介绍，你可以去查看 Steven Rostedt 在 LWN.net 上的文章。跟踪事件我们在上面提到到跟踪点机制。跟踪点是插入的触发系统事件的特定代码。跟踪点可以是动态的（意味着可能会在它们上面附加几个检查），也可以是静态的（意味着不会附加任何检查）。静态跟踪点不会对系统有任何影响；它们只是在测试的函数末尾增加几个字节的函数调用以及在一个独立的节上增加一个数据结构。当相关代码片断运行时，动态跟踪点调用一个跟踪函数。跟踪数据是写入到 Ring 缓冲区。跟踪点可以设置在代码的任何位置；事实上，它们确实可以在许多的内核函数中找到。我们来看一下 kmem_cache_alloc 函数（取自 这里）：{ void *ret = slab_alloc(cachep, flags, _RET_IP_); trace_kmem_cache_alloc(_RET_IP_, ret, cachep->object_size, cachep->size, flags); return ret; } trace_kmem_cache_alloc 它本身就是一个跟踪点。我们可以通过查看其它内核函数的源代码找到这样无数的例子。在 Linux 内核中为了从用户空间使用跟踪点，它有一个专门的 API。在 /sys/kernel/debug/tracing 目录中，这里有一个事件目录，它是为了保存系统事件。这些只是为了跟踪系统事件。在这个上下文中系统事件可以理解为包含在内核中的跟踪点。可以通过运行如下的命令来查看这个事件列表：root@andrei:/sys/kernel/debug/tracing# cat available_events 这个命令将在控制台中输出一个很长的列表。这样看起来很不方便。我们可以使用如下的命令来列出一个结构化的列表：root@andrei:/sys/kernel/debug/tracing# ls events block gpio mce random skb vsyscall btrfs header_event migrate ras sock workqueue compaction header_page module raw_syscalls spi writeback context_tracking iommu napi rcu swiotlb xen enable irq net regmap syscalls xfs exceptions irq_vectors nmi regulator task xhci-hcd ext4 jbd2 oom rpm timer filemap kmem pagemap sched udp fs kvm power scsi vfs ftrace kvmmmu printk signal vmscan 所有可能的事件都按子系统分组到子目录中。在我们开始跟踪事件之前，我们要先确保启用了 Ring 缓冲区写入：root@andrei:/sys/kernel/debug/tracing# cat tracing_on 如果在控制台中显示的是数字 0，那么，我们可以运行如下的命令来启用它：root@andrei:/sys/kernel/debug/tracing# echo 1 > tracing_on 在我们上一篇的文章中，我们写了关于 chroot() 系统调用的内容；我们来跟踪访问一下这个系统调用。对于我们的跟踪程序，我们使用 nop 因为函数跟踪程序和 function_graph 跟踪程序记录的信息太多，它包含了我们不感兴趣的事件信息。root@andrei:/sys/kernel/debug/tracing# echo nop > current_tracer 所有事件相关的系统调用都保存在系统调用目录下。在这里我们将找到一个进入和退出各种系统调用的目录。我们需要在相关的文件中通过写入数字 1 来激活跟踪点：root@andrei:/sys/kernel/debug/tracing# echo 1 > events/syscalls/sys_enter_chroot/enable 然后我们使用 chroot 来创建一个独立的文件系统（更多内容，请查看 之前这篇文章）。在我们执行完我们需要的命令之后，我们将禁用跟踪程序，以便于不需要的信息或者过量信息不会出现在输出中：root@andrei:/sys/kernel/debug/tracing# echo 0 > tracing_on 然后，我们去查看 Ring 缓冲区的内容。在输出的结束部分，我们找到了有关的系统调用信息（这里只是一个节选）。root@andrei:/sys/kernel/debug/tracing# сat trace ...... chroot-11321 [000] .... 4606.265208: sys_chroot(filename: 7fff785ae8c2) chroot-11325 [000] .... 4691.677767: sys_chroot(filename: 7fff242308cc) bash-11338 [000] .... 4746.971300: sys_chroot(filename: 7fff1efca8cc) bash-11351 [000] .... 5379.020609: sys_chroot(filename: 7fffbf9918cc) 关于配置事件跟踪的更的信息可以在 这里 找到。结束语在这篇文篇中，我们做了一个 ftrace 的功能概述。我们非常感谢你的任何意见或者补充。如果你想深入研究这个主题，我们为你推荐下列的资源：https://www.kernel.org/doc/Documentation/trace/tracepoints.txt — 一个跟踪点机制的详细描述https://www.kernel.org/doc/Documentation/trace/events.txt — 在 Linux 中跟踪系统事件的指南https://www.kernel.org/doc/Documentation/trace/ftrace.txt — ftrace 的官方文档https://lttng.org/files/thesis/desnoyers-dissertation-2009-12-v27.pdf — Mathieu Desnoyers（作者是跟踪点和 LTTNG 的创建者）的关于内核跟踪和分析的学术论文。https://lwn.net/Articles/370423/ — Steven Rostedt 的关于 ftrace 功能的文章http://alex.dzyoba.com/linux/profiling-ftrace.html — 用 ftrace 分析实际案例的一个概述via:https://blog.selectel.com/kernel-tracing-ftrace/作者：Andrej Yemelianov 译者：qhwdw 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
大家好！今天我们将去讨论一个调试工具：ftrace，之前我的博客上还没有讨论过它。还有什么能比一个新的调试工具更让人激动呢？这个非常棒的 ftrace 并不是个新的工具！它大约在 Linux 的 2.6 内核版本中就有了，时间大约是在 2008 年。这一篇是我用谷歌能找到的最早的文档。因此，如果你是一个调试系统的“老手”，可能早就已经使用它了！我知道，ftrace 已经存在了大约 2.5 年了（LCTT 译注：距本文初次写作时），但是还没有真正的去学习它。假设我明天要召开一个专题研究会，那么，关于 ftrace 应该讨论些什么？因此，今天是时间去讨论一下它了！什么是 ftrace？ftrace 是一个 Linux 内核特性，它可以让你去跟踪 Linux 内核的函数调用。为什么要这么做呢？好吧，假设你调试一个奇怪的问题，而你已经得到了你的内核版本中这个问题在源代码中的开始的位置，而你想知道这里到底发生了什么？每次在调试的时候，我并不会经常去读内核源代码，但是，极个别的情况下会去读它！例如，本周在工作中，我有一个程序在内核中卡死了。查看到底是调用了什么函数，能够帮我更好的理解在内核中发生了什么，哪些系统涉及其中！（在我的那个案例中，它是虚拟内存系统）。我认为 ftrace 是一个十分好用的工具（它肯定没有 strace 那样使用广泛，也比它难以使用），但是它还是值得你去学习。因此，让我们开始吧！使用 ftrace 的第一步不像 strace 和 perf，ftrace 并不是真正的 程序 – 你不能只运行 ftrace my_cool_function。那样太容易了！如果你去读 使用 ftrace 调试内核，它会告诉你从 cd /sys/kernel/debug/tracing 开始，然后做很多文件系统的操作。对于我来说，这种办法太麻烦——一个使用 ftrace 的简单例子像是这样：cd /sys/kernel/debug/tracing echo function > current_tracer echo do_page_fault > set_ftrace_filter cat trace 这个文件系统是跟踪系统的接口（“给这些神奇的文件赋值，然后该发生的事情就会发生”）理论上看起来似乎可用，但是它不是我的首选方式。幸运的是，ftrace 团队也考虑到这个并不友好的用户界面，因此，它有了一个更易于使用的界面，它就是 trace-cmd！！！trace-cmd 是一个带命令行参数的普通程序。我们后面将使用它！我在 LWN 上找到了一个 trace-cmd 的使用介绍：trace-cmd: Ftrace 的一个前端。开始使用 trace-cmd：让我们仅跟踪一个函数首先，我需要去使用 sudo apt-get install trace-cmd 安装 trace-cmd，这一步很容易。对于第一个 ftrace 的演示，我决定去了解我的内核如何去处理一个页面故障。当 Linux 分配内存时，它经常偷懒，（“你并不是真的计划去使用内存，对吗？”）。这意味着，当一个应用程序尝试去对分配给它的内存进行写入时，就会发生一个页面故障，而这个时候，内核才会真正的为应用程序去分配物理内存。我们开始使用 trace-cmd 并让它跟踪 do_page_fault 函数！$ sudo trace-cmd record -p function -l do_page_fault plugin 'function' Hit Ctrl^C to stop recording 我将它运行了几秒钟，然后按下了 Ctrl+C。 让我大吃一惊的是，它竟然产生了一个 2.5MB 大小的名为 trace.dat 的跟踪文件。我们来看一下这个文件的内容！$ sudo trace-cmd report chrome-15144 [000] 11446.466121: function: do_page_fault chrome-15144 [000] 11446.467910: function: do_page_fault chrome-15144 [000] 11446.469174: function: do_page_fault chrome-15144 [000] 11446.474225: function: do_page_fault chrome-15144 [000] 11446.474386: function: do_page_fault chrome-15144 [000] 11446.478768: function: do_page_fault CompositorTileW-15154 [001] 11446.480172: function: do_page_fault chrome-1830 [003] 11446.486696: function: do_page_fault CompositorTileW-15154 [001] 11446.488983: function: do_page_fault CompositorTileW-15154 [001] 11446.489034: function: do_page_fault CompositorTileW-15154 [001] 11446.489045: function: do_page_fault 看起来很整洁 – 它展示了进程名（chrome）、进程 ID（15144）、CPU ID（000），以及它跟踪的函数。通过察看整个文件，（sudo trace-cmd report | grep chrome）可以看到，我们跟踪了大约 1.5 秒，在这 1.5 秒的时间段内，Chrome 发生了大约 500 个页面故障。真是太酷了！这就是我们做的第一个 ftrace！下一个 ftrace 技巧：我们来跟踪一个进程！好吧，只看一个函数是有点无聊！假如我想知道一个程序中都发生了什么事情。我使用一个名为 Hugo 的静态站点生成器。看看内核为 Hugo 都做了些什么事情？在我的电脑上 Hugo 的 PID 现在是 25314，因此，我使用如下的命令去记录所有的内核函数：sudo trace-cmd record --help # I read the help! sudo trace-cmd record -p function -P 25314 # record for PID 25314 sudo trace-cmd report 输出了 18,000 行。如果你对这些感兴趣，你可以看 这里是所有的 18,000 行的输出。18,000 行太多了，因此，在这里仅摘录其中几行。当系统调用 clock_gettime 运行的时候，都发生了什么： compat_SyS_clock_gettime SyS_clock_gettime clockid_to_kclock posix_clock_realtime_get getnstimeofday64 __getnstimeofday64 arch_counter_read __compat_put_timespec 这是与进程调试相关的一些东西： cpufreq_sched_irq_work wake_up_process try_to_wake_up _raw_spin_lock_irqsave do_raw_spin_lock _raw_spin_lock do_raw_spin_lock walt_ktime_clock ktime_get arch_counter_read walt_update_task_ravg exiting_task 虽然你可能还不理解它们是做什么的，但是，能够看到所有的这些函数调用也是件很酷的事情。“function graph” 跟踪这里有另外一个模式，称为 function_graph。除了它既可以进入也可以退出一个函数外，其它的功能和函数跟踪器是一样的。这里是那个跟踪器的输出sudo trace-cmd record -p function_graph -P 25314 同样，这里只是一个片断（这次来自 futex 代码）： | futex_wake() { | get_futex_key() { | get_user_pages_fast() { 1.458 us | __get_user_pages_fast(); 4.375 us | } | __might_sleep() { 0.292 us | ___might_sleep(); 2.333 us | } 0.584 us | get_futex_key_refs(); | unlock_page() { 0.291 us | page_waitqueue(); 0.583 us | __wake_up_bit(); 5.250 us | } 0.583 us | put_page(); + 24.208 us | } 我们看到在这个示例中，在 futex_wake 后面调用了 get_futex_key。这是在源代码中真实发生的事情吗？我们可以检查一下！！这里是在 Linux 4.4 中 futex\_wake 的定义 (我的内核版本是 4.4）。为节省时间我直接贴出来，它的内容如下：static int futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset) { struct futex_hash_bucket *hb; struct futex_q *this, *next; union futex_key key = FUTEX_KEY_INIT; int ret; WAKE_Q(wake_q); if (!bitset) return -EINVAL; ret = get_futex_key(uaddr, flags & FLAGS_SHARED, &key, VERIFY_READ); 如你所见，在 futex_wake 中的第一个函数调用真的是 get_futex_key！ 太棒了！相比阅读内核代码，阅读函数跟踪肯定是更容易的找到结果的办法，并且让人高兴的是，还能看到所有的函数用了多长时间。如何知道哪些函数可以被跟踪如果你去运行 sudo trace-cmd list -f，你将得到一个你可以跟踪的函数的列表。它很简单但是也很重要。最后一件事：事件！现在，我们已经知道了怎么去跟踪内核中的函数，真是太酷了！还有一类我们可以跟踪的东西！有些事件与我们的函数调用并不相符。例如，你可能想知道当一个程序被调度进入或者离开 CPU 时，都发生了什么事件！你可能想通过“盯着”函数调用计算出来，但是，我告诉你，不可行！由于函数也为你提供了几种事件，因此，你可以看到当重要的事件发生时，都发生了什么事情。你可以使用 sudo cat /sys/kernel/debug/tracing/available_events 来查看这些事件的一个列表。 我查看了全部的 schedswitch 事件。我并不完全知道 schedswitch 是什么，但是，我猜测它与调度有关。sudo cat /sys/kernel/debug/tracing/available_events sudo trace-cmd record -e sched:sched_switch sudo trace-cmd report 输出如下： 16169.624862: Chrome_ChildIOT:24817 [112] S ==> chrome:15144 [120] 16169.624992: chrome:15144 [120] S ==> swapper/3:0 [120] 16169.625202: swapper/3:0 [120] R ==> Chrome_ChildIOT:24817 [112] 16169.625251: Chrome_ChildIOT:24817 [112] R ==> chrome:1561 [112] 16169.625437: chrome:1561 [112] S ==> chrome:15144 [120] 现在，可以很清楚地看到这些切换，从 PID 24817 -> 15144 -> kernel -> 24817 -> 1561 -> 15114。(所有的这些事件都发生在同一个 CPU 上）。ftrace 是如何工作的？ftrace 是一个动态跟踪系统。当我们开始 ftrace 内核函数时，函数的代码会被改变。让我们假设去跟踪 do_page_fault 函数。内核将在那个函数的汇编代码中插入一些额外的指令，以便每次该函数被调用时去提示跟踪系统。内核之所以能够添加额外的指令的原因是，Linux 将额外的几个 NOP 指令编译进每个函数中，因此，当需要的时候，这里有添加跟踪代码的地方。这是一个十分复杂的问题，因为，当不需要使用 ftrace 去跟踪我的内核时，它根本就不影响性能。而当我需要跟踪时，跟踪的函数越多，产生的开销就越大。（或许有些是不对的，但是，我认为的 ftrace 就是这样工作的）更容易地使用 ftrace：brendan gregg 的工具及 kernelshark正如我们在文件中所讨论的，你需要去考虑很多的关于单个的内核函数/事件直接使用 ftrace 都做了些什么。能够做到这一点很酷！但是也需要做大量的工作！Brendan Gregg （我们的 Linux 调试工具“大神”）有个工具仓库，它使用 ftrace 去提供关于像 I/O 延迟这样的各种事情的信息。这是它在 GitHub 上全部的 perf-tools 仓库。这里有一个权衡，那就是这些工具易于使用，但是你被限制仅能用于 Brendan Gregg 认可并做到工具里面的方面。它包括了很多方面！:)另一个工具是将 ftrace 的输出可视化，做的比较好的是 kernelshark。我还没有用过它，但是看起来似乎很有用。你可以使用 sudo apt-get install kernelshark 来安装它。一个新的超能力我很高兴能够花一些时间去学习 ftrace！对于任何内核工具，不同的内核版本有不同的功效，我希望有一天你能发现它很有用！ftrace 系列文章的一个索引最后，这里是我找到的一些 ftrace 方面的文章。它们大部分在 LWN （Linux 新闻周刊）上，它是 Linux 的一个极好的资源（你可以购买一个 订阅！）使用 Ftrace 调试内核 - part 1 (Dec 2009, Steven Rostedt)使用 Ftrace 调试内核 - part 2 (Dec 2009, Steven Rostedt)Linux 函数跟踪器的秘密 (Jan 2010, Steven Rostedt)trace-cmd：Ftrace 的一个前端 (Oct 2010, Steven Rostedt)使用 KernelShark 去分析实时调试器 (2011, Steven Rostedt)Ftrace: 神秘的开关 (2014, Brendan Gregg)内核文档：（它十分有用） Documentation/ftrace.txt你能跟踪的事件的文档 Documentation/events.txtlinux 内核开发上的一些 ftrace 设计文档 （不是有用，而是有趣！) Documentation/ftrace-design.txtvia: https://jvns.ca/blog/2017/03/19/getting-started-with-ftrace/作者：Julia Evans 译者：qhwdw 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
本文作者 Brendan Gregg 目前是 Netflix 的高级性能架构师 ，他在那里做大规模计算机性能设计、分析和调优。他是《Systems Performance》等技术书的作者，因在系统管理员方面的成绩，获得过 2013年 USENIX LISA 大奖。他之前是 SUN 公司是性能领头人和内核工程师，研究存储和网络性能。他也发明和开发过一大波性能分析工具，很多已集成到操作系统中了 。随着 BPF 追踪系统（基于时间采样）最后一个主要功能被合并至 Linux 4.9-rc1 版本的内核中，现在 Linux 内核拥有类似 DTrace 的原生追踪功能。DTrace 是 Solaris 系统中的高级追踪器。对于长期使用 DTrace 的用户和专家，这将是一个振奋人心的里程碑！现在在 Linux 系统上，你可以在生产环境中使用安全的、低负载的定制追踪系统，通过执行时间的柱状图和频率统计等信息，分析应用的性能以及内核。用于 Linux 的追踪项目有很多，但是这个最终被合并进 Linux 内核的技术从一开始就根本不是一个追踪项目：它是最开始是用于 伯克利包过滤器 （ Berkeley Packet Filter ） （BPF）的增强功能。这些补丁允许 BPF 重定向数据包，从而创建软件定义网络（SDN）。久而久之，对事件追踪的支持就被添加进来了，使得程序追踪可用于 Linux 系统。尽管目前 BPF 没有像 DTrace 一样的高级语言，但它所提供的前端已经足够让我创建很多 BPF 工具了，其中有些是基于我以前的 DTraceToolkit。这个帖子将告诉你怎么去用这些 BPF 提供的前端工具，以及畅谈这项技术将会何去何从。示例我已经将基于 BPF 的追踪工具添加到了开源的 bcc 项目里（感谢 PLUMgrid 公司的 Brenden Blanco 带领 bcc 项目的发展）。详见 bcc 安装 手册。它会在 /usr/share/bcc/tools 目录下添加一系列工具，包括接下来的那些工具。捕获新进程：# execsnoop PCOMM PID RET ARGS bash 15887 0 /usr/bin/man ls preconv 15894 0 /usr/bin/preconv -e UTF-8 man 15896 0 /usr/bin/tbl man 15897 0 /usr/bin/nroff -mandoc -rLL=169n -rLT=169n -Tutf8 man 15898 0 /usr/bin/pager -s nroff 15900 0 /usr/bin/locale charmap nroff 15901 0 /usr/bin/groff -mtty-char -Tutf8 -mandoc -rLL=169n -rLT=169n groff 15902 0 /usr/bin/troff -mtty-char -mandoc -rLL=169n -rLT=169n -Tutf8 groff 15903 0 /usr/bin/grotty 硬盘 I/O 延迟的柱状图：# biolatency -m Tracing block device I/O... Hit Ctrl-C to end. ^C msecs : count distribution 0 -> 1 : 96 |************************************ | 2 -> 3 : 25 |********* | 4 -> 7 : 29 |*********** | 8 -> 15 : 62 |*********************** | 16 -> 31 : 100 |**************************************| 32 -> 63 : 62 |*********************** | 64 -> 127 : 18 |****** | 追踪慢于 5 毫秒的 ext4 常见操作：# ext4slower 5 Tracing ext4 operations slower than 5 ms TIME COMM PID T BYTES OFF_KB LAT(ms) FILENAME 21:49:45 supervise 3570 W 18 0 5.48 status.new 21:49:48 supervise 12770 R 128 0 7.55 run 21:49:48 run 12770 R 497 0 16.46 nsswitch.conf 21:49:48 run 12770 R 1680 0 17.42 netflix_environment.sh 21:49:48 run 12770 R 1079 0 9.53 service_functions.sh 21:49:48 run 12772 R 128 0 17.74 svstat 21:49:48 svstat 12772 R 18 0 8.67 status 21:49:48 run 12774 R 128 0 15.76 stat 21:49:48 run 12777 R 128 0 7.89 grep 21:49:48 run 12776 R 128 0 8.25 ps 21:49:48 run 12780 R 128 0 11.07 xargs 21:49:48 ps 12776 R 832 0 12.02 libprocps.so.4.0.0 21:49:48 run 12779 R 128 0 13.21 cut [...] 追踪新建的 TCP 活跃连接（connect()）:# tcpconnect PID COMM IP SADDR DADDR DPORT 1479 telnet 4 127.0.0.1 127.0.0.1 23 1469 curl 4 10.201.219.236 54.245.105.25 80 1469 curl 4 10.201.219.236 54.67.101.145 80 1991 telnet 6 ::1 ::1 23 2015 ssh 6 fe80::2000:bff:fe82:3ac fe80::2000:bff:fe82:3ac 22 通过跟踪 getaddrinfo()/gethostbyname() 库的调用来追踪 DNS 延迟：# gethostlatency TIME PID COMM LATms HOST 06:10:24 28011 wget 90.00 www.iovisor.org 06:10:28 28127 wget 0.00 www.iovisor.org 06:10:41 28404 wget 9.00 www.netflix.com 06:10:48 28544 curl 35.00 www.netflix.com.au 06:11:10 29054 curl 31.00 www.plumgrid.com 06:11:16 29195 curl 3.00 www.facebook.com 06:11:25 29404 curl 72.00 foo 06:11:28 29475 curl 1.00 foo 按类别划分 VFS 操作的时间间隔统计：# vfsstat TIME READ/s WRITE/s CREATE/s OPEN/s FSYNC/s 18:35:32: 231 12 4 98 0 18:35:33: 274 13 4 106 0 18:35:34: 586 86 4 251 0 18:35:35: 241 15 4 99 0 对一个给定的 PID，通过内核和用户堆栈轨迹来追踪 CPU 处理之外的时间（由内核进行统计）：# offcputime -d -p 24347 Tracing off-CPU time (us) of PID 24347 by user + kernel stack... Hit Ctrl-C to end. ^C [...] ffffffff810a9581 finish_task_switch ffffffff8185d385 schedule ffffffff81085672 do_wait ffffffff8108687b sys_wait4 ffffffff81861bf6 entry_SYSCALL_64_fastpath -- 00007f6733a6b64a waitpid - bash (24347) 4952 ffffffff810a9581 finish_task_switch ffffffff8185d385 schedule ffffffff81860c48 schedule_timeout ffffffff810c5672 wait_woken ffffffff8150715a n_tty_read ffffffff815010f2 tty_read ffffffff8122cd67 __vfs_read ffffffff8122df65 vfs_read ffffffff8122f465 sys_read ffffffff81861bf6 entry_SYSCALL_64_fastpath -- 00007f6733a969b0 read - bash (24347) 1450908 追踪 MySQL 查询延迟（通过 USDT 探针）：# mysqld_qslower `pgrep -n mysqld` Tracing MySQL server queries for PID 14371 slower than 1 ms... TIME(s) PID MS QUERY 0.000000 18608 130.751 SELECT * FROM words WHERE word REGEXP '^bre.*n$' 2.921535 18608 130.590 SELECT * FROM words WHERE word REGEXP '^alex.*$' 4.603549 18608 24.164 SELECT COUNT(*) FROM words 9.733847 18608 130.936 SELECT count(*) AS count FROM words WHERE word REGEXP '^bre.*n$' 17.864776 18608 130.298 SELECT * FROM words WHERE word REGEXP '^bre.*n$' ORDER BY word 监测 pam 库并使用多种追踪工具观察登录请求：# trace 'pam:pam_start "%s: %s", arg1, arg2' TIME PID COMM FUNC - 17:49:45 5558 sshd pam_start sshd: root 17:49:47 5662 sudo pam_start sudo: root 17:49:49 5727 login pam_start login: bgregg bcc 项目里的很多工具都有帮助信息（-h 选项），并且都应该包含有示例的 man 页面和文本文件。必要性2014 年，Linux 追踪程序就有一些内核相关的特性（来自 ftrace 和 pref_events），但是我们仍然要转储并报告进程数据，这种几十年前的老技术有很多的限制。你不能频繁地访问进程名、函数名、堆栈轨迹或内核中的任意的其它数据。你不能在将变量保存到一个监测事件里，又在另一个事件里访问它们，这意味着你不能在你需要的地方计算延迟（或者说时间增量）。你也不能创建一个内核内部的延迟柱状图，也不能追踪 USDT 探针，甚至不能写个自定义的程序。DTrace 可以做到所有这些，但仅限于 Solaris 或 BSD 系统。在 Linux 系统中，有些不在主线内核的追踪器，比如 SystemTap 就可以满足你的这些需求，但它也有自身的不足。（理论上说，你可以写一个基于探针的内核模块来满足需求-但实际上没人这么做。）2014 年我加入了 Netflix cloud performance 团队。做了这么久的 DTrace 方面的专家，转到 Linux 对我来说简直不可思议。但我确实这么做了，而且遇到了巨大的挑战：在应用快速变化、采用微服务架构和分布式系统的情况下，调优 Netflix cloud。有时要用到系统追踪，而我之前是用的 DTrace。在 Linux 系统上可没有 DTrace，我就开始用 Linux 内核内建的 ftrace 和 perf_events 工具，构建了一个追踪工具（perf-tools）。这些工具很有用，但有些工作还是没法完成，尤其是延迟柱状图以及堆栈踪迹计数。我们需要的是内核追踪的可程序化。发生了什么？BPF 将程序化的功能添加到现有的内核追踪工具中（tracepoints、kprobes、uprobes）。在 Linux 4.x 系列的内核里，这些功能大大加强了。时间采样是最主要的部分，它被 Linux 4.9-rc1 所采用（patchset）。十分感谢 Alexei Starovoitov（在 Facebook 致力于 BPF 的开发），他是这些 BPF 增强功能的主要开发者。Linux 内核现在内建有以下这些特性（自 2.6 版本到 4.9 版本之间增加）：内核级的动态追踪（BPF 对 kprobes 的支持）用户级的动态追踪（BPF 对 uprobes 的支持）内核级的静态追踪（BPF 对 tracepoints 的支持）时间采样事件（BPF 的 pref_event_open）PMC 事件（BPF 的 pref_event_open）过滤器（通过 BPF 程序）调试输出（bpf_trace_printk()）按事件输出（bpf_perf_event_output()）基础变量（全局的和每个线程的变量，基于 BPF 映射）关联数组（通过 BPF 映射）频率计数（基于 BPF 映射）柱状图（2 的冥次方、线性及自定义，基于 BPF 映射）时间戳和时间增量（bpf_ktime_get_ns()，和 BPF 程序）内核态的堆栈轨迹（BPF 栈映射）用户态的堆栈轨迹 (BPF 栈映射)重写 ring 缓存（pref_event_attr.write_backward）我们采用的前端是 bcc，它同时提供 Python 和 lua 接口。bcc 添加了：用户级静态追踪（基于 uprobes 的 USDT 探针）调试输出（Python 中调用 BPF.trace_pipe() 和 BPF.trace_fields() 函数 ）按事件输出（BPF_PERF_OUTPUT 宏和 BPF.open_perf_buffer()）间隔输出（BPF.get_table() 和 table.clear()）打印柱状图（table.print_log2_hist()）内核级的 C 结构体导航（bcc 重写器映射到 bpf_probe_read() 函数）内核级的符号解析（ksym()、 ksymaddr()）用户级的符号解析（usymaddr()）BPF 跟踪点支持（通过 TRACEPOINT_PROBE）BPF 堆栈轨迹支持（包括针对堆栈框架的 walk 方法）其它各种辅助宏和方法例子（位于 /examples 目录）工具（位于 /tools 目录）教程（/docs/tutorial*.md）参考手册（/docs/reference_guide.md）直到最新也是最主要的特性被整合进来，我才开始写这篇文章，现在它在 4.9-rc1 内核中。我们还需要去完成一些次要的东西，还有另外一些事情要做，但是现在我们所拥有的已经值得欢呼了。现在 Linux 拥有了内建的高级追踪能力。安全性设计 BPF 及其增强功能时就考虑到生产环境级安全，它被用在大范围的生产环境里。不过你想的话，你还是可以找到一个挂起内核的方法。这种情况是偶然的，而不是必然，类似的漏洞会被快速修复，尤其是当 BPF 合并入了 Linux。因为 Linux 可是公众的焦点。在开发过程中我们碰到了一些非 BPF 的漏洞，它们需要被修复：rcu 不可重入，这可能导致内核由于 funccount 挂起，在 4.6 内核版本中这个漏洞被 “bpf: map pre-alloc” 补丁集所修复，旧版本内核的漏洞 bcc 有个临时处理方案。还有一个是 uprobe 的内存计算问题，这导致 uprobe 分配内存失败，在 4.8 内核版本这个漏洞由 “uprobes: Fix the memcg accounting” 补丁所修复，并且该补丁还将被移植到之前版本的内核中（例如，它现在被移植到了 4.4.27 和 4.4.0-45.66 版本中）。为什么 Linux 追踪用了这么久才加进来？首要任务被分到了若干追踪器中间：这些不是某个追踪器单个的事情。想要了解更多关于这个或其它方面的问题，可以看一看我在 2014 年 tracing summit 上的讲话。我忽视了部分方案的反面影响：有些公司发现其它追踪器（SystemTap 和 LTTng）能满足他们的需求，尽管他们乐于听到 BPF 的开发进程，但考虑到他们现有的解决方案，帮助 BPF 的开发就不那么重要了。BPF 仅在近两年里在追踪领域得到加强。这一过程原本可以更快的，但早期缺少全职从事于 BPF 追踪的工程师。Alexei Starovoitov (BPF 领导者)，Brenden Blanco (bcc 领导者)，我还有其它一些开发者，都有其它的事情要做。我在 Netflix 公司花了大量时间（志愿地），大概有 7% 的时间是花在 BPF 和 bcc 上。某种程度上这不是我的首要任务，因为我还有自己的工作（包括我的 perf-tools，一个可以工作在旧版本内核上的程序）。现在BPF 追踪器已经推出了，已经有科技公司开始寻找会 BPF 的人了。但我还是推荐 Netflix 公司。（如果你为了 BPF 而要聘请我，那我还是十分乐于待在 Netflix 公司的！）使用简单DTrace 和 bcc/BPF 现在的最大区别就是哪个更好使用。这取决于你要用 BPF 追踪做什么了。如果你要使用 BPF 工具/度量：应该是没什么区别的。工具的表现都差不多，图形用户界面都能取得类似度量指标。大部分用户通过这种方式使用 BPF。开发工具/度量：bcc 的开发可难多了。DTrace 有一套自己的简单语言，D 语音，和 awk 语言相似，而 bcc 使用已有的语言（C 语言，Python 和 lua）及其类库。一个用 C 和 Python 写的 bcc 工具与仅仅用 D 语言写出来的工具相比，可能要多十多倍行数的代码，或者更多。但是很多 DTrace 工具用 shell 封装来提供参数和差错检查，会让代码变得十分臃肿。编程的难处是不同的：重写 bcc 更需要巧妙性，这导致某些脚本更加难开发。（尤其是 bpf_probe_read() 这类的函数，需要了解更多 BPF 的内涵知识）。当计划改进 bcc 时，这一情形将得到改善。运行常见的命令：十分相近。通过 dtrace 命令，DTrace 能做很多事，但 bcc 有各种工具，trace、argdist、funccount、funclatency 等等。编写自定义的特殊命令：使用 DTrace 的话，这就没有必要了。允许定制消息快速传递和系统快速响应，DTrace 的高级分析很快。而 bcc 现在受限于它的多种工具以及它们的适用范围。简单来说，如果你只使用 BPF 工具的话，就不必关注这些差异了。如果你经验丰富，是个开发者（像我一样），目前 bcc 的使用更难一些。举一个 bcc 的 Python 前端的例子，下面是追踪硬盘 I/O 并打印出 I/O 大小的柱状图代码：from bcc import BPF from time import sleep # load BPF program b = BPF(text=""" #include <uapi/linux/ptrace.h> #include <linux/blkdev.h> BPF_HISTOGRAM(dist); int kprobe__blk_account_io_completion(struct pt_regs *ctx, struct request *req) { dist.increment(bpf_log2l(req->__data_len / 1024)); return 0; } """) # header print("Tracing... Hit Ctrl-C to end.") # trace until Ctrl-C try: sleep(99999999) except KeyboardInterrupt: print # output b["dist"].print_log2_hist("kbytes") 注意 Python 代码中嵌入的 C 语句（text=）。这就完成了任务，但仍有改进的空间。好在我们有时间去做：人们使用 Linux 4.9 并能用上 BPF 还得好几个月呢，所以我们有时间来制造工具和前端。高级语言前端越简单，比如高级语言，所改进的可能就越不如你所期望的。绝大多数人使用封装好的工具（和图形界面），仅有少部分人能写出这些工具。但我不反对使用高级语言，比如 SystemTap，毕竟已经开发出来了。#!/usr/bin/stap /* * opensnoop.stp Trace file open()s. Basic version of opensnoop. */ probe begin { printf("\n%6s %6s %16s %s\n", "UID", "PID", "COMM", "PATH"); } probe syscall.open { printf("%6d %6d %16s %s\n", uid(), pid(), execname(), filename); } 如果拥有整合了语言和脚本的 SystemTap 前端与高性能的内置在内核中的 BPF 后端，会不会令人满意呢？RedHat 公司的 Richard Henderson 已经在进行相关工作了，并且发布了 初代版本！这是 ply，一个完全新颖的 BPF 高级语言：#!/usr/bin/env ply kprobe:SyS_* { $syscalls[func].count() } 这也是一份承诺。尽管如此，我认为工具开发者的实际难题不是使用什么语言：而是要了解要用这些强大的工具做什么？如何帮助我们推广：BPF 追踪器目前还没有什么市场方面的进展。尽管有公司了解并在使用它（Facebook、Netflix、Github 和其它公司），但要广为人知尚需时日。你可以分享关于 BPF 的文章和资源给业内的其它公司来帮助我们。教育：你可以撰写文章，发表演讲，甚至参与 bcc 文档的编写。分享 BPF 如何解决实际问题以及为公司带来收益的实例。解决 bcc 的问题：参考 bcc issue list，这包含了错误和需要的特性。提交错误：使用 bcc/BPF，提交你发现的错误。创造工具：有很多可视化的工具需要开发，但请不要太草率，因为大家会先花几个小时学习使用你做的工具，所以请尽量把工具做的直观好用（参考我的文档）。就像 Mike Muuss 提及到他自己的 ping 程序：“要是我早知道这是我一生中最出名的成就，我就多开发一两天，添加更多选项。”高级语言：如果现有的 bcc 前端语言让你很困扰，或许你能弄门更好的语言。要是你想将这门语言内建到 bcc 里面，你需要使用 libbcc。或者你可以帮助 SystemTap BPF 或 ply 的工作。整合图形界面：除了 bcc 可以使用的 CLI 命令行工具，怎么让这些信息可视呢？延迟热点图，火焰图等等。其它追踪器那么 SystemTap、ktap、sysdig、LTTng 等追踪器怎么样呢？它们有个共同点，要么使用了 BPF，要么在自己的领域做得更好。会有单独的文章介绍它们自己。至于 DTrace ？我们公司目前还在基于 FreeBSD 系统的 CDN 中使用它。更多 bcc/BPF 的信息我已经写了一篇《bcc/BPF 工具最终用户教程》，一篇《bcc Python 开发者教程》，一篇《bcc/BPF 参考手册》，并提供了一些有用的工具，每一个工具都有一个 example.txt 文件和 man page。我之前写过的关于 bcc 和 BPF 的文章有：eBPF: One Small Step （后来就叫做 BPF）bcc: Taming Linux 4.3+ Tracing SuperpowersLinux eBPF Stack Trace Hack （现在官方支持追踪堆栈了）Linux eBPF Off-CPU Flame GraphLinux Wakeup and Off-Wake ProfilingLinux Chain Graph PrototypeLinux eBPF/bcc uprobesLinux BPF SuperpowersUbuntu Xenial bcc/BPFLinux bcc Tracing Security CapabilitiesLinux MySQL Slow Query Tracing with bcc/BPFLinux bcc ext4 Latency TracingLinux bcc/BPF Run Queue (Scheduler) LatencyLinux bcc/BPF Node.js USDT TracingLinux bcc tcptopLinux 4.9's Efficient BPF-based Profiler我在 Facebook 的 Performance@Scale Linux BPF Superpowers 大会上发表过一次演讲。十二月份，我将在 Boston 发表关于 BPF/bcc 在 USENIX LISA 方面的演讲和教程。致谢Van Jacobson 和 Steve McCanne，他们创建了最初用作过滤器的 BPF 。Barton P. Miller，Jeffrey K. Hollingsworth，and Jon Cargille，发明了动态追踪，并发表论文《Dynamic Program Instrumentation for Scalable Performance Tools》，可扩展高性能计算协议 （SHPCC），于田纳西州诺克斯维尔市，1994 年 5 月发表。kerninst (ParaDyn, UW-Madison)，展示了动态跟踪的价值的早期动态跟踪工具（上世纪 90 年代后期）Mathieu Desnoyers (在 LTTng)，内核的主要开发者，主导 tracepoints 项目。IBM 开发的作为 DProbes 一部分的 kprobes，DProbes 在 2000 年时曾与 LTT 一起提供 Linux 动态追踪，但没有整合到一起。Bryan Cantrill, Mike Shapiro, and Adam Leventhal (Sun Microsystems)，DTrace 的核心开发者，DTrace 是一款很棒的动态追踪工具，安全而且简单（2004 年）。对于动态追踪技术，DTrace 是科技的重要转折点：它很安全，默认安装在 Solaris 以及其它以可靠性著称的系统里。来自 Sun Microsystems 的各部门的许多员工，促进了 DTrace，为我们带来了高级系统追踪的意识。Roland McGrath (在 Red Hat)，utrace 项目的主要开发者，utrace 变成了后来的 uprobes。Alexei Starovoitov (PLUMgrid， 后来是 Facebook)，加强版 BPF（可编程内核部件）的主要开发者。那些帮助反馈、提交代码、测试以及针对增强版 BPF 补丁（请在 lkml 搜索 BPF）的 Linux 内核工程师： Wang Nan、 Daniel Borkmann、 David S. Miller、 Peter Zijlstra 以及其它很多人。Brenden Blanco (PLUMgrid)，bcc 的主要开发者。Sasha Goldshtein (Sela) 开发了 bcc 中的跟踪点支持，和功能最强大的 bcc 工具 trace 及 argdist，帮助 USDT 项目的开发。Vicent Martí 和其它 Github 上的工程师，为 bcc 编写了基于 lua 的前端，帮助 USDT 部分项目的开发。Allan McAleavy、 Mark Drayton，和其他的改进 bcc 的贡献者。感觉 Netflix 提供的环境和支持，让我能够编写 BPF 和 bcc 跟踪器并完成它们。我已经编写了多年的追踪工具（使用 TNF/prex、DTrace、SystemTap、ktap、ftrace、perf，现在是 bcc/BPF），并写书、博客以及评论，最后，感谢 Deirdré 编辑了另外一篇文章。总结Linux 没有 DTrace（语言），但它现在有了，或者说拥有了 DTraceTookit（工具）。通过增强内置的 BPF 引擎，Linux 4.9 内核拥有了用来支持现代化追踪的最后一项能力。内核支持这一最难的部分已经做完了。今后的任务包括更多的命令行执行工具，以及高级语言和图形用户界面。对于性能分析产品的客户，这也是一件好事：你能查看延迟柱状图和热点图，CPU 处理和 CPU 之外的火焰图，拥有更好的时延断点和更低耗的工具。在用户空间按包跟踪和处理是没有效率的方式。那么你什么时候会升级到 Linux 4.9 呢？一旦官方发布，新的性能测试工具就来了：apt-get install bcc-tools 。开始享受它吧!Brendanvia: http://www.brendangregg.com/blog/2016-10-27/dtrace-for-linux-2016.html作者：Brendan Gregg 译者：GitFuture 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
使用 Linux 的 strace 命令跟踪/调试程序的常用选项
在调试的时候，strace能帮助你追踪到一个程序所执行的系统调用。当你想知道程序和操作系统如何交互的时候，这是极其方便的，比如你想知道执行了哪些系统调用，并且以何种顺序执行。这个简单而又强大的工具几乎在所有的Linux操作系统上可用，并且可被用来调试大量的程序。命令用法让我们看看strace命令如何追踪一个程序的执行情况。最简单的形式，strace后面可以跟任何命令。它将列出许许多多的系统调用。一开始，我们并不能理解所有的输出，但是如果你正在寻找一些特殊的东西，那么你应该能从输出中发现它。让我们来看看简单命令ls的系统调用跟踪情况。raghu@raghu-Linoxide ~ $ strace ls 这是strace命令输出的前几行。其他输出被截去了。上面的输出部分展示了write系统调用，它把当前目录的列表输出到标准输出。下面的图片展示了使用ls命令列出的目录内容（没有使用strace）。raghu@raghu-Linoxide ~ $ ls 选项1 寻找被程序读取的配置文件Strace 的用法之一（除了调试某些问题以外）是你能找到被一个程序读取的配置文件。例如，raghu@raghu-Linoxide ~ $ strace php 2>&1 | grep php.ini 选项2 跟踪指定的系统调用strace命令的-e选项仅仅被用来展示特定的系统调用（例如，open，write等等）让我们跟踪一下cat命令的‘open’系统调用。raghu@raghu-Linoxide ~ $ strace -e open cat dead.letter 选项3 跟踪进程strace不但能用在命令上，而且通过使用-p选项能用在运行的进程上。raghu@raghu-Linoxide ~ $ sudo strace -p 1846 选项4 strace的统计概要它包括系统调用的概要，执行时间，错误等等。使用-c选项能够以一种整洁的方式展示：raghu@raghu-Linoxide ~ $ strace -c ls 选项5 保存输出结果通过使用-o选项可以把strace命令的输出结果保存到一个文件中。raghu@raghu-Linoxide ~ $ sudo strace -o process_strace -p 3229 之所以以sudo来运行上面的命令，是为了防止用户ID与所查看进程的所有者ID不匹配的情况。选项6 显示时间戳使用-t选项，可以在每行的输出之前添加时间戳。raghu@raghu-Linoxide ~ $ strace -t ls 选项7 更精细的时间戳-tt选项可以展示微秒级别的时间戳。raghu@raghu-Linoxide ~ $ strace -tt ls -ttt也可以向上面那样展示微秒级的时间戳，但是它并不是打印当前时间，而是显示自从epoch（译注：1970年1月1日00:00:00 UTC）以来的所经过的秒数。raghu@raghu-Linoxide ~ $ strace -ttt ls 选项8 相对时间-r选项展示系统调用之间的相对时间戳。raghu@raghu-Linoxide ~ $ strace -r ls via: http://linoxide.com/linux-command/linux-strace-command-examples/作者：Raghu 译者：guodongxiaren 校对：wxy本文由 LCTT 原创翻译，Linux中国 荣誉推出