Perfetto数据流架构故障分析:带你研究 trace 为何丢失

十念  论坛元老 | 2024-6-15 00:22:42 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1026|帖子 1026|积分 3078

在系统工程师的日常工作中,最苦恼的事情之一就是分析问题所依靠的可观测性数据出现了错误。“这该死的玩意儿又堕落了!” 在面临新工具出现的新问题时,工程师们在愤懑之余免不了吊唁旧时的荣光:那时的调试工具设计精巧,API 简明易用,如老伙计般地可靠。
  
然而随着新系统、新编程语言和新编程框架的不断发展,可观测性工具也在不断地推陈出新,"good old days" 早已一去不复返了。可观测性领域的技能虽并未产生大的革新,但是工程师们在观测数据的采集方式和分析方式上做了大量的工作,Perfetto 就是 Android 领域的后起之秀之一。

  
Perfetto 为自己标榜了开源、稳定且高效的跨领域系统跟踪和分析平台这一头衔,也为其自身的架构设计指出了明确的目标。在这篇文章我们临时放下“观测工具”们自身的发展汗青不谈,谈谈其数据编码与传输(后文简称 Data Flow)的架构设计,并从这个角度表明其可能存在数据丢失的诸多缘故起因,并提出相应的解决(或者规避)建议。

  
Note:对 Perfetto 架构不感爱好的伙伴可以直接跳转至 PART 4 章节,得到减少 Perfetto 使用故障的具体建议。

  前言 - 数据传输系统的设计理念  

  如何将数据从一侧搬至另一侧的根本设计理念从来都不是秘密,这就如同我们在现实生活中订购产物送抵家的过程。
  
通常我们在使用这类服务的时候只需要考虑 3 方面的因素:

  

  • WHAT:订阅什么产物

  • WHEN:什么时候收到

  • WHERE:在什么地点收货

  除此之外的诸多细节我们统统都不关心,我们提供了必要的信息来描述需求,供应商、物流公司设法为我们解决过程中需要处理的诸多麻烦事儿。
  
大多数时候机制都是运转精良的:商品总是供应富足,快递总能按时抵家,很难遇到意外的情况发生,我们不需要费心整个过程是如何完成的。但是当错误的事情开始发生时(比方产物没有送抵家,或是收到了错误的产物),我们就会陷入一种沮丧的情绪中:我们知道有问题,但是不知道该找谁的麻烦。这种沮丧的情绪就如同工程师遇到了不太合作的系统可观测工具,此时人类的情感是共同的。

  
为什么 trace 会遗漏了我们关注的时间发生的系统状态?为什么 Perfetto UI 上的 trace event 会层层叠叠地延伸至屏幕的两端?造成这统统的根源都来自于不同水平的“权衡利弊”。    

  
毕竟可使用的资源总归是有限的,而在系统里有不止一个生产者、物流和消费者,任何一方都有可能成为瓶颈。“物流公司”的运输可能会延迟后丢失,“产物供应商”也可能会爆单,来自消费者的需求也可能不敷。只要我们设法触达了这套系统的中的诸多瓶颈,那么权衡利弊的策略就会在各个环节发生。

  

  在生产者 & 运输者 & 消费者 构成的一个供销系统下,每一方都会为了自身效率的最大化而添加很多非必要的中心环节。比方:物流公司可能会为货物增设中转站,以提前备货来缓解消费者的需求旺盛使得某些货品的物流压力骤增。
  PART 1 - 根本理念:生产者,消费者,以及 IPC 通讯  

  如今让我们将目光拉回到 Perfetto 自己。我们已经知道了 Perfetto 希望成为一个拥抱开源、支持跨平台跨领域的系统跟踪和分析框架,那么在这个框架下生产者、消费者和 “物流公司” 分别有哪些?从官方提供的示意图中我们可以窥知一二:    
  

  

  •  数据生产者:如图绿色框所示。生产者可以是多个进程,且每个进程可以同时供给不同的数据类型。

  • 数据消费者:如图黄色框所示。在一个数据跟踪会话中只存在一个消费者进程。对于 Perfetto 来说,Traced 只能算是一个署理消费者。署理消费者仅仅将数据放置在自己的 Buffer 中,并可以与真正的消费者协商数据的处理方式:是定期读取 buffer 数据做持久化存储?照旧将数据重定向至新的 IPC 通道?

  • 数据传输:如图蓝色框所示。数据传输分为信号转达和数据交换两个过程,发生在生产者进程和消费者进程之间。

  Perfetto 的跨平台数据跟踪本领,是以 Data source 的 ABI/API 协议来定义的。无论是 Chrome 浏览器内核,照旧 Android 或 Chrome OS 操纵系统,都可以在遵循这套协议的基础大将自己注册为 Perfetto 的数据生产者。
  
以 Linux ftrace 为例:Perfetto 在 Android 操纵系统中如何将 ftrace 作为其数据源?从上图灰色框部门可以看到 ftrace 在每个 CPU core 中已有对应的 ring buffer,在兼容已有 ftrace 框架的基础上,Android 系统启动了 traced_probes 进程来定期读取 ftrace buffer 数据并将其序列化为 perfetto 支持的二进制格式。由于 traced_probes 在启动阶段已经注册为了 Perfetto 的 Data source,因此消费端在启动一个跟踪会话时只需要告知 Perfetto 订阅 linux.ftrace 这个数据源即可。    

  PART 2 - 权衡利弊:观测开销 vs 传输可靠性  

  可观测工具的使用是有成本的。如下表展示了在前台随机启动应用 60 秒的过程中,相关可观测工具进程的 CPU task 运行时间的统计:
  
process_name
pid
uid
cpu_time_ms
cpu_time_perccent
/system/bin/traced_probes
2376
9999
11013.04
2.294383
/system/bin/logd
1034
1036
6801.802
1.417042
logcat
3756
0
4545.653
0.947011
/system/bin/traced
2386
9999
2064.368
0.430077
/apex/com.android.os.statsd/bin/statsd
1506
1066
1124.588
0.234289
logcat
7330
[NULL]
11.8526
0.002469
  
从上表数据可知,无论是 trace 相关的进程(tracd, tracd_probes)照旧 log 的进程(logd, logcat)或是 metrics 采集进程(statsd)都会引入一定的性能开销。随着需要记载或存储的数据流量越来越大,工具自己可能引入的开销也徐徐膨胀。

  
我们希望观测工具所带来的 “观察者效应” 能够尽可能地消除。除了束缚数据的生产者生产数据的速度,观测工具还会绞劲脑汁地优化观测数据的 data flow 中所有可能引入系统负载的代码流程。

  
Perfetto 采用了共享内存 Buffer 的方式来减少跨进程的数据拷贝所带来开销,并采用 buffer 的分区写入方案实现了生产者在并行生产数据时的数据同步开销。

  Central Buffer 映射  

  如 PART 1 所述,Perfetto 为 trace 会话设置了一个署理的数据消费者,这个署理消费者位于 Traced 进程内,负责将生产者产生的数据拷贝至独立的 Central Buffer 中。Central Buffer 需要设置符合的大小,以缓冲特定生产者发送的数据包。    
  
buffer 与 生产者可以创建明确的映射关系:               

  
权衡利弊 1:生产者生产数据的速度是有差异的,因此依照数据生产的吞吐率来为其映射符合的 Central Buffer 可以包管 buffer 的添补速率相对同等:生产较快的数据源使用更大的 Central Buffer,而生产较慢的数据源应当映射到一个更小的 Central Buffer。同时对于更 “重要” 的生产者生产的数据,最好也为其设置独立的 Central Buffer,以免受到其它生产者的干扰。

  生产速度不同等的数据生产者如果不举行 buffer 隔离,可能会出现相互挤兑现象。倘使我们仅仅分配一个 Buffer 供 A,B,C 三个生产者共用,在预期的状态下,Buffer 按照 RING BUFFER 模式举行数据的存储和覆写,如下图所示:               
               
此时三位生产者生产数据的频率和数据的大小近似,在 Buffer Size 的限定窗口内我们可以观测到来自三个生产者的数据。               
当生产者 A 的数据包大小或生产速度忽然加速时,RING BUFFER 模式将会挤占 buffer 中记载的来自其它生产者的数据包并使其快速丢失,如下图:               
               
当新的数据包写入 Buffer 后,来自生产者 B 的数据将失效并无法被观测到。    
  Shared Memory Buffer 的数据搬运(读写)  

  Shared Memory Buffer 与 IPC 通道是 Perfetto 中数据搬运的实现基础,生产者与署理消费者遵循相应的协议有序地使用 Shared Memory Buffer 来完成高效的数据传输过程。下面这张图展示了此中的一些细节:               

  
Shared Memory Buffer 并不是整块地被使用,而是被分别为不同的地区,我们临时将每个地区称作一个 Page,Page 是进程间共享数据的最小单元。而 Page 在进程内部还会被分别为以 chunk 为最小单元的 buffer 块。每个 chunk 内的数据写入是 lock free 的,这意味着 chunk 内的数据写入是顺序的,与生产数据的线程相映射。

  
Chunk 的状态可以分别为 3 个阶段,每个阶段都只能被一个独立的实体来访问。简言之,chunk 是生产者与消费者之间交互的最小粒度,对每个 chunk 的访问必须是独占的。Chunk 的状态切换如下图:    

  

  

  •  Free: Chunk 是空闲的,消费者不会去拷贝处于 Free 状态的 chunk,而生产者则需要申请该状态的 Chunk 来写入数据;

  • BeingWritten: 此时 Chunk 正在被数据生产者使用,数据的写入正在举行并且还未完成。注:即使此时 chunk 还未填满,消费者仍然可能在竣事 Trace 会话时拷贝此中的数据。

  • BeingRead: Chunk 已经写满了数据,此时生产者不能再去修改此中的数据,生产者已经开始拷贝此中的数据。当数据拷贝完成后,Chunk 将被重置为 Free 状态。

  权衡利弊 2:在 Producer 与 Consumer 之间共享的 Page 大小和数量不可能是无穷的。在设定 Shared Memory Buffer 的总大小后,Page 的大小和 chunk 的大小需要在多个因素之间权衡利弊。
  

  • Page size 越大,则 Page 交换的 IPC 信号就发送得更不频仍,反之亦然;
  • Page size 和 chunk size 越大,chunk 的过程就更不经常发生,而 chunk 的状态切换需要申请同步锁;    
  • Page 或 chunk 的 size 越大,则数据更不易被填满,处于 BeingWritten 状态的 chunk 数量越来越多而 Free 状态的 chunk 不敷乃至降为 0,这可能使得数据生产者无法得到可用 chunk 来写入数据;
  通过上文的分析,我们知道可以通过设定更大的 Shared Memory Buffer 和更大的 Central Buffer 来进步数据传输的可靠性,而这将以更大的内存空间使用为代价。Perfetto 依据生产者的生产速度与消费者的搬运速度为 Buffer 的大小设置了相应的履历值,这可能在 Pixel 的机器上运转精良,可以在可控的观测开销下得到可靠的数据传输本领,然而在其它的设备上可能并不能很好地工作。
  PART 3 - 数据编码协议中的权衡利弊  

  下面我们来谈谈 Perfetto 中的数据编码协议。Perfetto 采用了 protobuf 对 trace 的数据举行序列化,且煞费苦心地为其专门开发了 ProtoZero 库来进步 protobuf 的序列化性能以降低 perfetto trace 数据的实时序列化开销。
  
protobuf 存在很多优良的特性,包括空间友好的可变长编码,可通过 .proto 文件预定义的数据结构等等。这些内容与本文的主题无关,因此不再睁开。我们重点谈谈 Perfetto trace 中的原子数据结构:TracePacket 以及此中存在的权衡利弊。

  数据原子性  

  TracePacket 是 Perfetto Trace 中的最小数据单元,trace 数据是由一系列大大小小的 TracePacket 的序列化数据构成的。风趣的是,固然 Shared Memory Buffer 被分别为 Page 和更小单元的 Chunk,但这并不会限定 TracePacket 的数据大小。TracePacket 是可以跨越多个 chunk 存储的。详情可如下图所示:               
   
  
Packet 2 是一个尺寸巨大的 TracePacket,因而我们跨越了 3 个 chunk 来存储这份数据,分别是 chunk 1 -> chunk 3 -> chunk 4。为了包管后端数据能够按正确的方式还原 TracePacket ,chunk header 中会记载与之关联的前一份或后一份 chunk 的 ID

  
权衡利弊 3:TracePacket Size 如果太大,可能会使得 TracePacket 的原子写入还未完成时,与之关联的 chunk 已经写入了 Central Buffer,乃至已经从 Central Buffer 中递交到了真正的数据消费者(被写入文件或在 Ring Buffer 中被覆写)。

  TracePacket 数据写回  

  我们已经相识了:TracePacket 是 Perfetto Trace 中的最小数据单元,其二进制数据是顺序写入的。如果数据的格式损毁,则 TracePacket 中的数据将无法还原。
  
如今我们要深入到另一个细节:TracePacket 的原子数据写入顺序。要说明这个问题首先需要相识 TracePacket 在内存中的布局,其细节如下图所示:    

  

  
由于 protobuf 是可变字长编码,因此 TracePacket 会在其编码数据的头部预留空间用于标记数据段的字长。在通过 ProtoZero 库来举行快速的 protobuf 序列化编码时,序列化程序不会提前计算 TracePacket 的序列化字长,而是通过预留 size 字段,待 payload 部门的编码写入完毕后再返回 size 字段写回字段的长度。

  
现在为止统统看上去都运作精良,不外新的问题很快就会显现出来。让我们同时考虑数据写回与 数据原子性 小节中提到的权衡利弊的问题:

  

  • 当 TracePacket size > chunk size 时,TracePacket 的编码需要跨越多个 chunk;

  • 当最后一个 chunk 完成 TracePacket 的写入时,记载 TracePacket 的 size 字段的 chunk 可能已经写入了 Central Buffer;

  • 记载 TracePacket 头部数据的 chunk 大概已经在 Central Buffer 中消费掉了。

  Perfetto 的应对策略:我们还可以通过 IPC 信号通道来通知 Trace Service 举行过后补救,只需要告知它:“请修复目标编号为XXX,来自 XX Producer 的 chunk” 便可以在问题 3 发生之前完成数据的补救步调。关于 chunk 数据的补救涉及的具体信号内容,可以参考 CommitDataRequest.ChunkToPatch 中的协议字段。    
  增量编码  

  各人可能相识过视频编解码中的 “帧间编码” 的概念,这是一个利用邻近帧之间的时域相关性来举行预测,去除相邻帧之间的冗余信息的编码过程。简单来视频由一帧一帧的图像编码构成,帧间编码在视频数据中确定了一些 关键帧,并通过算法来比较关键帧之间的差异,通过只记载 “变化地区的数据” 来实现数据压缩的效果。相对地,完整记载每一帧信息的编码方式称为“帧内编码”,关于两种编码方式的形象化说明,参见下图 帧内编码 vs 帧间编码:               

  
Perfetto 也奇妙地利用了这一理念,通过增量编码的方式来节省数据记载的开销,这是另一个 “权衡利弊” 的例子:

  

  • 使用 Trace Event SDK 的 DataSource 会尽可能减少 TracePacket 中的 string 类字段记载,因为 string 字段通常不能得到很好的压缩。大多数的 string 类字段都只记载一次并创建 id -> string 的映射信息(雷同于关键帧信息),后续的其它 TracePacket 在使用这些信息时就无需记载 string 而是其 id 以减少总体数据编码的大小,Trace Processor 当然可以在解码 Trace 数据的过程中还原这种映射关系。

  • 联合前文可知,这些关键帧信息可能会丢失,在这种情况下与之关联的其它 TracePacket 就无法正确地解析效果。Trace Processor 将会检测到这些错误的发生,并跳过关键帧波及的所有 TracePacket 信息。在 Central Buffer 处于 RING BUFFER 的记载模式时,这将会更经常地发生。    

  权衡利弊 4:为了降低数据丢失的风险,关键帧数据不应该关联太多的 TracePacket,我们都明白不要把鸡蛋全都放在同一个篮子里。定期地让增量编码失效并重新记载关键帧数据是很有必要的。               
包罗关键帧数据的 TracePacket 或许要存储在独立的 Central Buffer 地区,以在 RING BUFFER 模式下与关联的其它 Buffer 的添补速度相匹配。显然这也不是最优雅的做法,不外总能缓解问题发生的严峻水平。
  PART 4 - 应对之法  

  通过 PART 1 ~ PART 3 章节的内容,我们已经深入探讨了关于 Perfetto Trace 的 DataFlow 中涉及的相关信息,此中不乏大量的 “权衡利弊”,很多时候都需要在性能开销、可靠性、易用性之间做出艰难的选择,最糟糕的是在某些极端的场景下数据丢失总会发生。
  
如今可以定论了:Perfetto 不是一个 100% 可靠的 tracing 平台,它只是一个拥抱开源(机制不完善)、稳定(不是 100% 稳定)且高效(并未追求极致性能)的跨领域系统跟踪和分析平台,我们可以接受现实了。如何才气正确地拥抱这个平台,并使用好如许的工具呢?

  “我的剑留给能够挥动它的人!” ————By 查理-芒格
  完整性检查  

  即使无法避免错误,至少要能记载下错误发生的时候。显然 Perfetto 的开发者们也深刻地明白这儿原理,于是他们特意为 Perfetto Trace 数据创建了一张独立的 SQLite 表,用于记载这些 “失败时候” 的发生。我们可以在 Perfetto UI 的 Query(SQL) 功能栏中,通过以下 SQL 查询来得到这些信息:
  
select*from stats where severity ='data_loss';

  如果数据完好,则 SQL 的查询效果通常如下表所示:
  
name
idx
severity
source
value
description
ftrace_cpu_overrun_delta
0
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
1
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
2
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
3
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
4
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
5
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
6
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
7
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
traced_buf_abi_violations
0
data_loss
trace
0

traced_buf_abi_violations
1
data_loss
trace
0

traced_buf_patches_failed
0
data_loss
trace
0

traced_buf_patches_failed
1
data_loss
trace
0

traced_buf_trace_writer_packet_loss
0
data_loss
trace
0

traced_buf_trace_writer_packet_loss
1
data_loss
trace
0

traced_final_flush_failed
NULL
data_loss
trace
0

traced_flushes_failed
NULL
data_loss
trace
0

misplaced_end_event
NULL
data_loss
analysis
0

truncated_sys_write_duration
NULL
data_loss
analysis
0
Count of sys_write slices that have a truncated duration to resolve nesting incompatibilities with atrace slices. Real durations can be recovered via the |raw| table.
perf_samples_skipped_dataloss
NULL
data_loss
trace
0

  
name
idx
severity
source
value
description
ftrace_cpu_overrun_delta
0
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
1
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
2
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
3
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
4
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
5
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
6
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
ftrace_cpu_overrun_delta
7
data_loss
trace
0
The kernel ftrace buffer cannot keep up with the rate of events produced. Indexed by CPU. This is likely a misconfiguration.
traced_buf_abi_violations
0
data_loss
trace
0

traced_buf_abi_violations
1
data_loss
trace
0

traced_buf_patches_failed
0
data_loss
trace
0

traced_buf_patches_failed
1
data_loss
trace
0

traced_buf_trace_writer_packet_loss
0
data_loss
trace
0

traced_buf_trace_writer_packet_loss
1
data_loss
trace
0

traced_final_flush_failed
NULL
data_loss
trace
0

traced_flushes_failed
NULL
data_loss
trace
0

misplaced_end_event
NULL
data_loss
analysis
0

truncated_sys_write_duration
NULL
data_loss
analysis
0
Count of sys_write slices that have a truncated duration to resolve nesting incompatibilities with atrace slices. Real durations can be recovered via the |raw| table.
perf_samples_skipped_dataloss
NULL
data_loss
trace
0

  stats 表中记载了各式各样用于描述 Trace 状态的元数据,这些元数据来源于 Trace 自身记载的数据以及 trace_processor 在处理 Trace 的过程中分析得到的数据。               
通过 severity 字段,我们可以分辨不同品级的元数据记载:
  

  • info: 常规的统计信息,通常不意味着 Trace 的内容错误或数据丢失,仅仅用于相识 Trace 自身的数据分布特性。
  • error: trace 采集过程中发生的系统状态异常。此类异常通常标识了在正常情况下本不该发生的变乱,比方:在 trace 会话过程中发生了时钟的校准;ATRACE 的 begin 与 end 变乱无法正确地配对等等;
  • data_loss:在数据传输的过程中发生了一些 “权衡利弊” 的决议,导致 trace 中的部门数据丢失。
  针对一些重要的字段 description 中也做了详细的说明,可以作为我们举行错误分析的重要依据。    
  关于 stats 表中每个字段的详细说明,建议查察 Perfetto Document 中 stats 表的 Reference,本文将不再赘述。
  降低 Trace 丢失的概率  

  如今是时候梳理 Perfetto Trace 的 DataFlow 架构中可能引起数据神秘失踪的诸多因素了,参见下图:               

  
我们为可能存在故障的环节都做了红色标记,这使得 Perfetto DataFlow 看上去相当不可靠。下面让我们来逐一梳理每个故障环节可以接纳的步调:

  故障 1:ftrace buffer losses  

  ftrace 的 buffer 大小是有限的,其填满的速度通常取决于来自 kernel 和 ATRACE 发送的数据量大小。这可能在不同设备上都有差异。为了在 ftrace buffer 出现数据丢失前拿走数据,我们需要 traced_probes 进程能够尽快地举行数据的读取和编码,并将其发送至一个充足维持一段时间的 Central Buffer。这里有两个建议的步调:
  

  • 为 linux.ftrace 的数据源映射充足大的 Central Buffer,在基于 RING BUFFER 策略的采集模式中更应云云。对于 Android 系统来说,ftrace 的数据乃是 Perfetto Trace 中最大的数据来源;
  • 包管 traced_probes 进程有符合的优先级来完成其工作。否则它可能会因为获取不到充足的 CPU 算力资源而无法及时地搬运 ftrace 中产生的数据。    
  故障 2:Shared Memory Buffer Limit  

  Shared Memory Buffer(后文简称 SMB)为数据生产者和消费者之间提供的共享内存 buffer 通常限定为 128-512 KB 大小,而单个 Page 的大小通常为 4KB~32KB。如果 SMB 中缺乏充足的 Free 状态的 chunk 来供数据生产者使用,则数据丢失将会发生。此处的建议步调:
  

  • 确保 traced 或其它 Trace 数据的消费者的消费速率能够大于或等于数据生产的速率。如果 traced 在一段时间内无法被 CPU 调理而不再搬运 SMB 中写满的 chunk,则数据丢失会开始发生;

  • 调整 SMB 和 Page 的大小以满意需求。然而大多数时候这需要我们在使用 Perfetto Client 库时通过设置 TracingInitArgs.shmem_size_hint_kb 与 TracingInitArgs.shmem_page_size_hint_kb 来实现。然而对于 Android 系统而言,基于 android.os.Trace (SDK) / ATrace_* (NDK) 的跟踪方式恐怕难以调整这两个值的大小。

  故障 3:Central Buffer  

  Central Buffer 位于 Traced 进程中,是消费者处理 Trace 数据的数据中转站。根据 Central Buffer 的使用方式不同,其数据丢失的可能也不雷同:
  

  • RING BUFFER:Buffer 按序写入 Buffer,当 Buffer size 不敷时最早写入的数据将被覆盖而丢失;
  • STREAM LONG TRACE:Buffer data 将以流式的方式被读取和发送至其它消费者。Buffer 仍然可能在两次读取的隔断时段被填满并发生数据溢出;
  • STOP WHEN FULL:Central Buffer 填满时停止 trace 会话。
  应对故障的可行步调:
  1.提供充足大的 Central Buffer 大小。无论是 RING BUFFER 模式照旧其它模式这总是有效,在设备的 RAM SIZE 日益膨胀的今日,我们好像不必吝啬 Buffer size 的扩张;    
  2.如果采用 STREAM LONG TRACE 的采集模式,可以通过降低两次 STREAM 读取之间的时间隔断来降低 buffer 数据溢出的风险;
  3.为不同的 Data Source 映射符合的 Central Buffer,以减少不同数据源的数据写入速率不同而发生相互挤占的风险;
  4.为了应对增量编码可能带来的数据集丢失,也可以实验调整 “增量重置” 的隔断时间。
  故障 4:Trace file 存储  

  数据只有真正被消费才不算丢失。无论是将其持久化至何处,包管数据落盘的实时性也是很重要的。如果 IO Block 的时间太久,Central Buffer 仍然可能发生溢出。这通常在低端机上是更容易出现的,尤其是发热发烫的机器。
  总结  

  此处我提供了一份标准的 TraceConfig 示例,在大多数情况下这份 trace 可以连续记载 60 秒的 system trace 数据并且不会出现数据的丢失或错乱。在表明中详细说明白不同的字段如何控制各个故障点的可靠性:
  
buffers: {                  
    size_kb: 260096                  
    fill_policy: RING_BUFFER                  
}                  
buffers: {                  
    size_kb: 2048                  
    fill_policy: RING_BUFFER                  
}                  
data_sources: {                  
    config {                  
        name: "android.packages_list"                  
        target_buffer: 1                  
    }                  
}                  
data_sources: {                  
    config {                  
        name: "linux.process_stats"                  
        target_buffer: 1                  
        process_stats_config {                  
            scan_all_processes_on_start: true                  
        }                  
    }                  
}                  
data_sources: {                  
    config {                  
        name: "android.log"                  
        android_log_config {                  
            log_ids: LID_SYSTEM                  
        }                  
    }                  
}                  
data_sources: {                  
    config {                  
        name: "android.surfaceflinger.frametimeline"                  
    }                  
}                  
data_sources: {                  
    config {                  
        name: "linux.sys_stats"                  
        sys_stats_config {                  
            stat_period_ms: 1000                  
            stat_counters: STAT_CPU_TIMES                  
            stat_counters: STAT_FORK_COUNT                  
            cpufreq_period_ms: 1000                  
        }                  
    }                  
}                  
data_sources: {                  
    config {                  
        name: "linux.ftrace"                  
        ftrace_config {                  
            ftrace_events: "sched/sched_switch"                  
            ftrace_events: "power/suspend_resume"                  
            ftrace_events: "sched/sched_wakeup"                  
            ftrace_events: "sched/sched_wakeup_new"                  
            ftrace_events: "sched/sched_waking"                  
            ftrace_events: "power/cpu_frequency"                  
            ftrace_events: "power/cpu_idle"                  
            ftrace_events: "sched/sched_process_exit"                  
            ftrace_events: "sched/sched_process_free"                  
            ftrace_events: "task/task_newtask"                  
            ftrace_events: "task/task_rename"                  
            atrace_categories: "am"                  
            atrace_categories: "aidl"                  
            atrace_categories: "dalvik"                  
            atrace_categories: "binder_driver"                  
            atrace_categories: "gfx"                  
            atrace_categories: "input"                  
            atrace_categories: "pm"                  
            atrace_categories: "power"                  
            atrace_categories: "rs"                  
            atrace_categories: "res"                  
            atrace_categories: "ss"                  
            atrace_categories: "view"                  
            atrace_categories: "wm"                  
            atrace_apps: "*"                  
        }                  
    }                  
}                  
# trace 会话最长持续时间,在 LONG_TRACE 模式下有效                  
duration_ms: 60000                  
                 
# 设置此字段后 STREAM 模式将启用,数据将定期从 Central Buffer 中读出                  
write_into_file: true                   
                 
# 在 write_info_File = true 时有效,控制两次数据读出之间的期待隔断时间                  
file_write_period_ms: 2500                  
max_file_size_bytes: 1500000000                  
                 
# 要求数据源定期将数据提交至 Central Buffer,即使 SMB 中的 chunk 并未写满。常用于解决部门 Page 填满花费时间太久而无法即使刷入 Central Buffer 的情形                  
flush_period_ms: 30000                  
                 
incremental_state_config {                  
    # 触发关键帧数据失效的隔断时间                  
    clear_period_ms: 5000                  
}    

  无论如何,权衡利弊(trade-off)的策略都在可观测工具的各处发生着。Google 无法为所有的场景都调试出一份通用的 “甜点” 参数,所以将调整的余地开放给了使用者。
  结语  

  Perfetto 并不是一个十全十美的工具,如果想要“挥动好这把宝剑”,我们就需要对此中的曲折原委有所相识。
  
希望这篇文章能够帮助到各位读者,让各人对于 Perfetto 的 Data Flow 架构有更深入的明白。当我们在下一次遇到Perfetto 无法配合工作时不会感到那么手足无措,而是能够试着通过调整一些参数来解决遇到的问题。

  参考文献&资料  

  部门参考资料、图片来源于如下网站链接,在此致谢~~
  
1. [Perfetto Documents] - https://perfetto.dev/docs/

  
2. [Long GOP vs All instra] - https://www.sonystyle.com.cn/content/dam/sonystyle/products/ilc/e-body/ilce_7m4/feature/ilce_7m4_d04_6857b92c.jpg

  往
  期
  推
  荐
  Android分区挂载原理介绍(上)

  Android分区挂载原理介绍(下)

  深入明白Linux内核共享内存机制- shmem&tmpfs

  

  长按关注内核工匠微信
  Linux内核黑科技| 技能文章| 精选教程

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

十念

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表