如何使用Perfetto通过trace分析性能问题(四)——Trace Processor

本文章为官方文档译文,仅供个人日后复习使用,如有需求请移步至 https://perfetto.dev/docs/

Track events (Tracing SDK)

Custom data sources

代码中的备注:

使用此回调可基于 SetupArgs 中的 TraceConfig 将任何自定义配置应用于数据源。

此通知可用于初始化 GPU 驱动程序、启用计数器等。StartArgs 将包含可以扩展的 DataSourceDescriptor。

撤消在 OnStart 中完成的任何初始化。

数据源还可以具有每个实例的状态。

Track events

Track events 是特定应用程序的,在应用程序运行时,记录在跟踪中的时间限制事件。Track events 始终与track相关联,这是单调增加时间的时间表。Track 对应于独立的执行顺序,例如进程中的单个线程。

Track events 的主要类型有以下几种:

  • slice

    代表嵌套的、有时间限制的操作的片。例如,一个片可以覆盖从函数开始执行到返回的时间段、从网络加载文件所花的时间或者完成用户旅程所花的时间。

  • Counters

    计数器,它们是时变数值的快照。例如,跟踪事件可以记录进程在执行期间的内存使用情况。

  • Flows

    流,用于将跨不同轨道的相关片连接在一起。例如,如果图像文件首先从网络加载,然后在线程池上解码,则可以使用流事件突出显示其通过系统的路径。(尚未完全实施)。

要开始使用跟踪事件,请首先定义您的事件将属于的类别集。每个类别都可以单独启用或禁用以进行跟踪.

  • 首先,在头文件中添加类别列表 ( 如:my_app_tracing_categories.h ):

    #include <perfetto.h>
    
    PERFETTO_DEFINE_CATEGORIES(
    	perfetto::Category("rendering").SetDescription("Events from the graphics subsystem"))
        perfetto::Category("network").SetDescription("Network upload and download statistics"));
    
  • 然后,在一个.cc文件中为这些类别声明静态存储 ( 如:my_app_tracing_categories.cc )

    #include "my_app_tracing_categories.h"
    
    PERFETTO_TRACK_EVENT_STATIC_STORAGE();
    
  • 最后,在启动客户端之后初始化 track events:

    int main(int argc,char** argv){
        ...
    	perfetto::Tracing::Initialize(args);
        perfetto::TrackEvent::Register();	// Add this.
    }
    
  • 现在就可以为函数添加track events了,如下:

    #include "my_app_tracing_categories.h"
    
    void DrawPlayer(){
        TRACE_EVENT("rendering", "DrawPlayer");	// Begin "DrawPlayer" slice.
        ...
        // End "DrawPlayer" slice.
    }
    

    该事件将覆盖从遇到 TRACE _ EVENT 注释到该块结束的时间(在上面的示例中,直到函数返回)。

  • 对于不遵循函数范围的事件,使用 TRACE _ EVENT _ BEGIN 和 TRACE _ EVENT _ END:

    void LoadGame(){
        DisplayLoadingScreen();
        
        TRACE_EVENT_BEGIN("io", "Loading");	// Begin "Loading" slice.
        LoadCollectibles();
        LoadVehicles();
        LoadPlayers();
        TRACE_EVENT_END("io");	// End "Loading" slice.
        
        StartGame();
    }
    

注意,不需要为 TRACE _ EVENT _ END 指定名称,因为它会自动关闭在同一线程上开始的最新事件。换句话说,给定线程上的所有事件共享相同的堆栈。这意味着不建议在单独的函数中使用一对匹配的 TRACE _ EVENT _ BEGIN 和 TRACE _ EVENT _ END 标记,因为一个不相关的事件可能意外地终止原始事件; 对于跨函数边界的事件,最好在单独的轨道上发出它们。

您还可以提供(最多两个)与事件一起的调试注释。

int player_number = 1;
TRACE_EVENT("rendering", "DrawPlayer", "player_number", player_number);

See below for the other types of supported track event arguments. For more complex arguments, you can define your own protobuf messages and emit them as a parameter for the event.

NOTE: Currently custom protobuf messages need to be added directly to the Perfetto repository under protos/perfetto/trace, and Perfetto itself must also be rebuilt. We are working to lift this limitation.

As an example of a custom track event argument type, save the following as protos/perfetto/trace/track_event/player_info.proto:

message PlayerInfo {  optional string name = 1;  optional uint64 score = 2; }

This new file should also be added to protos/perfetto/trace/track_event/BUILD.gn:

sources = [  ...  "player_info.proto" ]

Also, a matching argument should be added to the track event message definition in protos/perfetto/trace/track_event/track_event.proto:

import "protos/perfetto/trace/track_event/player_info.proto"; ... message TrackEvent {  ...  // New argument types go here.  optional PlayerInfo player_info = 1000; }

The corresponding trace point could look like this:

Player my_player; 
TRACE_EVENT("category", "MyEvent", [&](perfetto::EventContext ctx) {
	auto player = ctx.event()->set_player_info();  
	player->set_name(my_player.name());  
	player->set_player_score(my_player.score()); 
});

The lambda function passed to the macro is only called if tracing is enabled for the given category. It is always called synchronously and possibly multiple times if multiple concurrent tracing sessions are active.

Now that you have instrumented your app with track events, you are ready to start recording traces.

Category configuration ( 类型配置 )

所有Track Event 都被分配到另一个 Trace 类别,例如:

TRACE-EVENT("rendering", ...); // Event in the "rendering" category.

默认情况下,启用了所有 non-debug 和 non-slow track event 类别用于跟踪。debug 和 slow 类别是具有特殊标签的类别:

  • “debug” categories 可以为特定子系统提供更详细的调试输出。
  • “slow” categories 记录足够的数据,会影响应用程序的交互性能。

分类标签可以这样定义:

perfetto::Category("rendering.debug")
    .SetDescription("Debug events from the graphics subsystem")
    .SetTags("debug", "my_custom_tag")

一个简单的trace event也可以属于多个类别:

// Event in the "rendering" and "benchmark" categories
TRACE_EVENT("rendering,benchmark", ...);

必须向类别注册表添加相应的类别组条目

perfetto::Category::Group("rendering,benchmark")

还可以有效地查询是否为跟踪启用了给定的类别

if(TRACE_EVENT_CATEGORY_ENABLED("rendering")){
    // ...
}

Track ( 轨道 )

每个轨道事件都与轨道关联,该轨道指定事件所属的时间表。在大多数情况下,轨道对应于Perfetto UI中的视觉水平轨道。

描述并行序列的事件(例如,单独的线程)应该使用单独的轨道,而序列事件(例如,嵌套的函数调用)通常属于同一轨道。

  • Track
  • ProcessTrack
  • ThreadTrack

轨道可以具有父轨道,该轨道用于将相关曲目分组在一起。例如,ThreadTrack的父轨道是线程所属进程的ProcessTrack。默认情况下,轨道的分组在当前进程的ProcessTrack中。

轨道由 uuid 标识,该 uuid 在整个记录的轨道中必须是唯一的。为了尽量减少意外冲突的机会,将子轨道的 uuid 与其父轨道的 uuid 组合在一起,每个 ProcessTrack 都有一个随机的、每个进程的 uuid。

默认情况下,跟踪事件(例如 TRACE _ EVENT)使用 ThreadTrack 来调用线程。这可以被重写,例如,标记在不同线程上开始和结束的事件:

void OnNewRequest(size_t request_id){
    // Open a slice when the request came in.
    TRACE_EVENT_BEGIN("category", "HandleRequest", perfetto::Track(request_id));
    
    // Start a thread to handle the request.
    std::thread worker_thread([=]{
        // ... produce response ...
        
        // Close the slice for the request now that we finished handling it.
        TRACE_EVENT_END("category", perfetto::Track(request_id));
    });
}

Thread and process identifiers ( 线程和进程标识符 )

在跟踪环境中,线程和进程的处理需要特别注意; 线程和进程的标识符(例如 Android/macOS/Linux 中的 pid/tgid 和 tid)可以在一个trace中被操作系统重用。这意味着在跟踪处理器中查询表时,它们不能作为唯一标识符。

为了解决此问题,Trace Processor使用utid(唯一的tid)和upid(唯一pid)。所有对线程和进程的引用(例如,在 CPU 调度数据中,线程跟踪)都使用 utid 和 upid 代替系统标识符。

面向对象数据表

多类型对象建模是跟踪处理器中的一个常见问题。例如,轨道可以有很多种类(线程轨道、进程轨道、计数器轨道等)。每个类型都有一段与之关联的数据,这段数据对于该类型是独一无二的; 例如,线程轨道具有线程的 utid,计数器轨道具有计数器的 unit

为了在面向对象的语言中解决这个问题,可以创建一个 Track 类,并为所有子类使用继承(例如,ThreadTrack 和 CounterTrack 是 Track 的子类,ProcessCounterTrack 是 CounterTrack 的子类等)。

在 trace processor 中,通过为每种类型的对象提供不同的表,可以复制这种“面向对象”的方法。例如,我们有一个跟踪表作为层次结构的“根”,其中 thread_track 和 counter_track 表“继承自” track 表。

写查询语句 ( Writing Queries )

在跟踪处理器中查询表时常见的问题是: “如何获取片的进程或线程?”.更一般地说,问题是“我如何得到一个事件的上下文?”.

在 trace processor 中,与 track 中所有事件相关联的任何上下文都可以在相关联的 track 表中找到。

For example, to obtain the utid of any thread which emitted a measure slice

SELECT utid
FROM slice
JOIN thread_track ON thread_track.id = slice.track_id
WHERE slice.name = 'measure'

同样,要获得具有 mem.swap 计数器大于1000的任何过程的upid:

SELECT upid
FROM counter
JOIN process_counter_track ON process_counter_track.id = counter.track_id
WHERE process_counter_track.name = 'mem.swap' AND value > 1000

如果事先知道事件的源和类型(通常是这种情况) ,则可以使用以下内容查找要连接的 track

Event typeAssociated withTrack tableConstraint in WHERE clause
sliceN/A (global scope)tracktype = 'track'
slicethreadthread_trackN/A
sliceprocessprocess_trackN/A
counterN/A (global scope)counter_tracktype = 'counter_track'
counterthreadthread_counter_trackN/A
counterprocessprocess_counter_trackN/A
countercpucpu_counter_trackN/A

另一方面,有时源是未知的。在这种情况下,与 track 表连接并查找类型列将提供要与之连接的精确跟踪表。

例如,要查找 ‘measure’ 事件的轨道类型,可以使用以下查询。

select track.type
from slice join track on slice.track_id=track.id
where slice.name ='measure'

Thread and process tables

如果想查询 tid, pid 或者进程/线程的名称,应该首先获取utid

select tid, name
from thread
where utid = 10

Thread和Process表还可以与相关联的Track表直接联接,从slice或counter直接跳转到有关进程和线程的信息。

例如,要获取发出 ‘measure’ slice的所有thread的列表

select thread.name AS thread_name
from thread_track
join slice on slice.track_id = thread_track.id
join thread using(utid)
where slice.name = 'measure'
GROUP BY thread_name
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值