字节跳动开源神器:btrace 2.0 技术原理大揭秘

您所在的位置:网站首页 动态跟踪技术原理 字节跳动开源神器:btrace 2.0 技术原理大揭秘

字节跳动开源神器:btrace 2.0 技术原理大揭秘

2024-06-28 03:15| 来源: 网络整理| 查看: 265

项目 GitHub 地址:https://github.com/bytedance/btrace

1 背景介绍

在一年多前,我们对外正式开源了 btrace(AKA RheaTrace),它是基于 Systrace 的高性能 Trace 工具,目前字节跳动已经有接近 10+ 产品团队使用 btrace 做日常性能优化工作。在这一年期间,我们收到很多社区以及公司内部反馈,包括使用体验、性能体验、监控数据等上都收到众多反馈,我们汇总了大家反馈的内容,主要包括以下三类:

使用体验:Windows 有着大量用户群体,但 btrace 1.0 未支持;桌面脚本依赖 Systrace 和 Python 2.7 环境,导致环境搭建十分复杂,此外手机端还依赖外部存储访问权限,在初次使用时很容易导致打断。同时产物体积庞大,网页打开速度很慢。

性能体验:大型应用插桩数量达到百万级别,性能损耗接近 100%,对性能优化工作产生一定困扰。

监控数据:在 Trace 分析过程中,有些信息是缺失的,并不知道耗时原因,比如目前 Trace 中仅包含 synchronized 锁信息,缺少 ReentrantLock 等其他锁信息,同时渲染监控只有部分系统关键路径信息,缺少业务层信息。

同时,随着 Android 系统的不断发展,Google 逐渐废弃了 Systrace 工具,并开始大力推广 Perfetto 工具。此外,由于系统的 sdcard 权限限制变得更加严格,btrace 在高版本 Android 系统中已经出现兼容性问题。

在此背景下,我们决定大幅改造 btrace,解决用户反馈最多、最集中的问题,同时适应 Google 发布的新特性,并修复兼容性问题,以便更好地满足开发者的需求,目前 btrace 2.0 在使用体验、性能体验、监控数据等方面均做出大量改进,重点改进如下。

使用体验:支持 Windows 啦!此外将脚本实现从 Python 切至 Java 并去除各种权限要求,因脚本工具可用性问题引起的用户使用打断次数几乎降为 0,同时还将 Trace 产物切至 PB 协议,产物体积减小 70%,网页打开速度提升 7 倍!

性能体验:通过大规模改造方法 Trace 逻辑,将 App 方法 Trace 底层结构由字符串切换为整数,实现内存占用减少 80%,存储改为 mmap 方式、优化无锁队列逻辑、提供精准插桩策略等,全插桩场景下性能损耗进一步降低至 15%!

监控数据:新增 4 项数据监控能力,重磅推出渲染详情采集能力!同时还新增 Binder、线程启动、Wait/Notify/Park/Unpark 等详情数据!

接下来,我们将详细介绍上述三个改进方向的具体机制与实现原理,以帮助您深入了解 btrace 2.0 的重要升级。

2 原理揭秘

Perfetto 简介

Perfetto 和 Systrace 都是用于 Android 系统的性能分析和调试的工具,但它们有所不同:

Systrace:是 Android SDK 中的一个工具,可用于捕获和分析不同系统进程的时序事件,并提供了用于分析系统性能瓶颈的图形界面。Systrace 能够捕获的事件包括 CPU、内存、网络、磁盘I/O、渲染等等。Systrace 的工作原理是在内核和用户空间捕获和解析时序事件,并将其记录到 HTML 文件中,开发者可以使用 Chrome 浏览器来分析这些事件。Systrace 能够很好地帮助开发者找出系统瓶颈,但它在性能方面的表现并不理想,尤其是在处理大量数据时。

Perfetto:是一个全新、低开销的 Trace 采集工具,旨在优化 Systrace 的性能表现。Perfetto 的目标是提供比 Systrace 更快、更细粒度的 Trace 采集,并支持与其他跨平台工具集成。Perfetto 采用二进制格式记录 Trace 数据,并使用基于 ProtoBuf 的数据交换格式进行数据导出,可与 Grafana、SQLite、BigQuery 等其他分析和可视化工具集成。Perfetto 采集的数据种类非常广泛,包括 CPU 使用情况、网络字节流、触摸输入、渲染等等。与 Systrace 相比,Perfetto 在性能和可定制性方面更为出色。

因此,可以看出 Perfetto 是 Systrace 的一种更为先进和优秀的替代工具,它提供了更强大的数据采集和分析功能,更好的性能以及更好的可定制性,为开发人员提供更全面和深入的性能分析和调试工具。

3 整体流程

首先我们了解下 btrace 采集的整体流程:

整个流程分为以下三个阶段:

App 编译时:在应用程序编译阶段,我们提供了两种插桩模式:方法数字标识插桩和方法字符串标识插桩。方法 数字标识插桩适用于只需要记录方法名称的场景,而方法字符串标识插桩可以同时记录方法参数的值。此外,我们还支持精准插桩引擎,自动识别可疑耗时代码并进行插桩。

App 运行时:在应用程序运行期间,主要工作是采集应用的 apptrace 信息,对于方法数字标识类型的信息,通过 mmap 无锁队列方式采集;对于字符串标识类型的信息,直接通过系统函数写入 atrace,同时代理 atrace 写入逻辑,将其替换为 LFRB 高性能写入方案。

桌面脚本:桌面脚本主要用于控制应用程序的运行和开启/关闭 Trace 采集功能。此外,桌面脚本还负责对采集到的 apptrace 与 atrace 数据进行编码,并将它们与 ftrace 进行合并。

4 技术揭秘

1. 使用体验

使用体验问题在用户反馈中最多,分析下来基本是存储权限、Systrace 环境、Python 环境、Trace 产物体积过大、Perffetto 网页打开过慢等问题,这些体验问题我们完成了针对性的优化:

权限优化

为进行数据处理,桌面脚本需要访问到 App 数据。在 App 层面,最方便的方式是将数据存储到公共 SDCard 中。但从 Android Q 开始,Google 收紧对外置存储完全访问权限。尽管 requestLegacyExternalStorage 可以临时解决这个问题,但从长远来看,SDCard 将无法完全访问。

为解决此问题,我们搭建 Http Server 来通过端口对外访问数据,但访问该 Server 仍需要确定服务地址,为此,我们使用 adb forward 功能,它可以建立一个转发,将 PC 端数据转发到手机端口,并且可以获取从手机端口返回的数据。这样,我们就可以使用 localhost 访问数据。

以上解决了脚本读取 App 数据的问题,我们还面临 App 读取脚本参数问题,比如 maxAppTraceBufferSize、 mainThreadOnly,在 btrace 1.0 支持运行时通过 push 配置文件到指定目录进行动态调整,但这也需要 SDCard 访问权限,为彻底去除权限依赖,我们需要引入新方案。

首先想到的是 adb forward 反向方案:adb reverse,它可以将手机端口数据转发给 PC,实现了从手机到 PC 的访问,同样我们可以在脚本启动 HttpServer 来实现数据接收。但是,因为是网络请求意味着 App 读取参数只能在子线程进行,会有一定的不便,尤其在需要参数实时生效时。

我们又研究了新方案,在桌面脚本通过 adb setprop 给手机设置参数,App 通过 __system_property_get 来读取参数,只要是参数 property 名称以 debug. 开头,就无需任何权限。

// 桌面脚本设置参数 Adb.call("shell", "setprop", "debug.rhea.startWhenAppLaunch", "1"); // 手机运行时读取参数 static jboolean JNI_startWhenAppLaunch(JNIEnv *env, jobject thiz) {     char value[PROP_VALUE_MAX];     __system_property_get("debug.rhea.startWhenAppLaunch", value);     return value[0] == '1'; } 环境优化

btrace 1.0 基于 Systrace 开发,对 Python 2.7 有强依赖,而 Python 2.7 已被官方废弃,同时大多数 Android 工程师对 Python 不太熟悉,浪费了大量时间解决环境问题。对此,我们计划将 Systrace 切换到 Perfetto ,并选择 Android 工程师更熟悉的 Java 语言重写脚本,用户只需有可用的 Java 和 adb 环境,即可轻松使用 btrace 2.0。

产物优化

btrace 1.0 产物是基于 Systrace 的 HTML 文本数据,常常遇到文本内容太大、加载速度过慢、甚至需要单独搭建服务来支持 Trace 显示的问题。Perfetto 是 Google 新推出的性能分析平台,支持多种数据格式解析,Systrace 格式是其中一种,同时 Perfetto 还支持 Protocol Buffer 格式,pb 是一种轻量级、高效的数据序列化格式,用于结构化数据存储和传输。Perfetto 使用 pb 作为其事件记录格式,保证记录系统事件数据的同时,保持数据的高效性和可伸缩性。pb 因为其结构化数据存储可以实现更小体积占用与更快解析速度。因此,btrace 2.0 也将数据格式由 HTML 切换到 pb,在减小产物文件体积的同时,还大幅提升 Trace 在网页上的加载速度。

我们先简单介绍下 Perfetto 的 pb 数据格式,然后再介绍如何将采集到的 apptrace 与 atrace 编码为 pb 格式,以及如何将其与系统 ftrace 进行融合。

Perfetto pb 是由一系列 TracePacket 组成,官方文档可以参考:https://perfetto.dev/docs/reference/trace-packet-proto,这里将介绍 btrace 使用到的一种 TracePacket:FtraceEventBundle:

FtraceEventBundle 是 Android 用于收集系统 Trace 数据的一种机制。它由大量 FtraceEvent 组成,可以被用来记录各种系统行为,如调度、中断、内存管理和文件系统等。btrace 主要利用其中 PrintFtraceEvent 来记录方法 Trace 信息,具体使用方式可以参考下面简单示例:

int threadId = 10011; FtraceEventBundle.Builder bundle = FtraceEventBundle.newBuilder()         .addEvent(                 FtraceEvent.newBuilder()                         .setPid(threadId) // 线程内核 pid,就是 tid                         .setTimestamp(System.nanoTime())                         .setPrint(                                 Ftrace.PrintFtraceEvent.newBuilder()                                         // buf 格式是 B|$pid|$msg\n 这里 pid 是实际                                         // 进程 ID,`\n` 是必须项                                         .setBuf("B|10010|someEvent\n")))          .addEvent(                 FtraceEvent.newBuilder()                         .setPid(threadId)                         .setTimestamp(System.nanoTime() + TimeUnit.SECONDS.toNanos(2))                         .setPrint(                                 Ftrace.PrintFtraceEvent.newBuilder()                                         .setBuf("E|10010|\n")))         .setCpu(0); Trace trace = Trace.newBuilder()         .addPacket(                 TracePacketOuterClass.TracePacket.newBuilder()                         .setFtraceEvents(bundle)).build(); try (FileOutputStream out = new FileOutputStream("demo.pb")) {     trace.writeTo(out); }

上面示例将得到下面这个 Trace:

下面再介绍如何将运行时采集到的 apptrace 信息转换成 pb 格式的,这部分操作在桌面脚本进行。

首先脚本通过 adb http 方式获取到手机上 mmap 映射文件,然后再解析文件内容:

// 读取 mapping,我们将 mapping 内置到了 apk 的 assets 目录 Map mapping = Mapping.get(); // 开始解码并保存解码后的结果 List result = new ArrayList(); byte[] bytes = FileUtils.readFileToByteArray(traceFile); ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); while (buffer.hasRemaining()) {     long a = buffer.getLong();     long b = buffer.getLong();     // 分别解析出 startTime / duration / tid / methodId     long startTime = a >>> 19;     long dur0 = a & 0x7FFFF;     long dur1 = (b >>> 38) & 0x3FFFFFF;     long dur = (dur0 > 23) & 0x7FFF);     int mid = (int) (b & 0x7FFFFF);     // 记录相应的开始与结束 Trace     result.add(new Frame(Frame.B, startTime, dur, pid, tid, mid, mapping));     result.add(new Frame(Frame.E, startTime, dur, pid, tid, mid, mapping)); } // 排序 result.sort(Comparator.comparingLong(frame -> frame.time));

之后再利用上文介绍的 FtraceEventBundle 对 List 进行编码即可,这里不再展开。atrace 的处理方式也是类似的,也不再阐述。

再介绍下如何将采集到的 apptrace、atrace 与系统 ftrace 进行合并。

前文介绍过,Perfetto pb 是由一系列 TracePacket 组成,一般而言,我们只要将业务采集到的 Trace 分别封装成 TracePacket,然后加入到系统 TracePacket 集合中就完成了 Trace 的合并。

Trace.Builder systemTrace = Trace.parseFrom(systraceStream).toBuilder(); FtraceEventBundle.Builder bundle = ...; for (int i = 0; i 


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3