btrace 开源!基于 Systrace 高性能 Trace 工具

介绍

btrace(又名 RheaTrace) 是抖音基础技术团队自研的一款高性能 Android Trace 工具,它基于 Systrace 实现,并针对 Systrace 不足之处加以改进,核心改进点如下。

  1. 效率提升:编译期间为 App 方法自动注入自定义事件,并提供高效、灵活配置规则。

  2. 性能提升:改进 Systrace 文件实时写 atrace 数据方式,性能提升最大 400 % 以上。

  3. 实用性提升:额外提供更详细 IO 等数据,大幅提升方法耗时归因效率;使用独创方案彻底来解决方法因执行异常引起 trace 数据不闭合问题。

项目地址:

https://github.com/bytedance/btrace

目前字节跳动已有多款 App 接入,包括抖音、TikTok、今日头条、幸福里等均已接入 RheaTrace,并为其体验优化提供强有力支持。借助 RheaTrace 将为您的 App 带来极致流畅体验,RheaTrace 使用效果如下(因保密原则,每个方法用 ID 表示)。

2cfc201c282254c499ffd9b489c92ed2.png

Systrace 简介

如果我们使用过 Systrace 分析应用性能,我们都知道 Systrace 提供 Category 配置让用户决定采集哪些系统 atrace 数据,如下命令,从 sched 开始后续是不同类别的 atrace 数据。

python systrace.py -o mynewtrace.html sched freq idle am wm gfx view \
    binder_driver hal dalvik camera input res

atrace 的数据类型见下图:

3316268529430a7a614ce029e17e17c3.png

其中,用户空间 atrace 类型包括应用层自定义 Trace 事件、系统层 gfx 渲染相关 Trace、系统层锁相关 Trace 信息等,其最终都是通过调用 Android SDK 提供 Trace.beginSection 或者 ATRACE_BEGIN 记录到同一个文件 /sys/kernel/debug/tracing/trace_marker 中。此节点允许用户层写入数据,ftrace 会记录该写入操作时间戳。当用户层发生函数调用时,ftrace 可以记录被跟踪函数的运行时间。atrace 若需记录用户层某一 trace 类型,只需激活对应 TAG 类型即可。如选择 gfx,则会激活 ATRACE_TAG_GRAPHICS,并将渲染事件记录到 trace_marker 文件中。

内核空间的数据主要是一些补充分析数据,如 freq、sched、binder 等,常用 CPU 调度相关信息包括:

  1. CPU 频率变化情况。

  2. 任务执行情况。

  3. 大小核调度情况。

  4. CPU Boost 调度情况。

c9027fc2459d60a053724770df81021d.png

关于图中一些标签释义。

  1. CPU 使用率:右边柱状图越高,表明使用率越高。

  2. CPU 序号:标识 CPU 核心序号,表示该设备有 8 个核心,编号 0 -7。

  3. CPU 频率:右边对应的粉色柱状图表示其频率变化趋势。

  4. 调度任务:标识在该 CPU 核心上正在运行的任务,点击任务可查看其 ID、优先级等信息。

这些信息 App 可以直接读取 /sys/devices/system/cpu 节点下相关信息获得,而另外一部分标识线程状态信息则只能通过系统或者 adb 才能获取,且这些信息不是统一节点控制,需要激活各自对应的事件节点,让 ftrace 记录下不同事件的 tracepoint。内核在运行时,根据节点的使能状态,会往 ftrace 缓冲中记录事件。

例如,激活线程调度状态信息记录,需要激活类似如下相关节点。

events/sched/sched_switch/enable
events/sched/sched_wakeup/enable

激活后,则可以获取到线程调度状态相关的信息,比如:

  1. Running: 线程在正常执行代码逻辑。

  2. Runnable: 可执行状态,等待调度,如果长时间调度不到,说明 CPU 繁忙。

  3. Sleeping: 休眠,一般是在等待事件驱动。

  4. Uninterruptible Sleep: 不可中断的休眠,需要看 Args 描述来确定当时状态。

  5. Uninterruptible Sleep - Block I/O: IO 阻塞。

4b3dd88a9e29255617a3e6fc692879ce.png

最终,上述两大类事件记录都汇集到内核态同一缓冲中, Systrace 工具是通过指定抓取 trace 类别等参数,然后触发手机端 /system/bin/atrace 开启对应文件节点信息,接着 atrace 会读取 ftrace 缓存,生成只包含 ftrace 信息的 atrace_raw 信息,最终通过脚本转换成可视化 HTML 文件,大致流程如下。

a88fef6bbe209ca2f94a0a924ad6ef00.png

RheaTrace 揭秘

本章节将从 RheaTrace 重点优势一一介绍。

Systrace 源码分析

Systrace 提供 Trace#beginSection(String) 和 Trace.endSection() 采集 atrace 数据,首先,我们大致了解下 atrace 工作原理,以 android.os.Trace#beginSection 作为分析入口。

public static void beginSection(@NonNull String sectionName) {
    if (isTagEnabled(TRACE_TAG_APP)) {
        if (sectionName.length() > MAX_SECTION_NAME_LEN) {
            throw new IllegalArgumentException("sectionName is too long");
        }
        nativeTraceBegin(TRACE_TAG_APP, sectionName);
    }
}

android.os.Trace#beginSection 会调用 nativeTraceBegin 方法,该方法实现参考 frameworks/base/core/jni/android_os_Trace.cpp。

static void android_os_Trace_nativeTraceBegin(JNIEnv* env, jclass,
        jlong tag, jstring nameStr) {
    withString(env, nameStr, [tag](char* str) {
        atrace_begin(tag, str);
    });
}

atrace_begin 方法实现参考 system/core/libcutils/include/cutils/trace.h。

#define ATRACE_BEGIN(name) atrace_begin(ATRACE_TAG, name)
static inline void atrace_begin(uint64_t tag, const char* name)
{
    if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
        void atrace_begin_body(const char*);
        atrace_begin_body(name);
    }
}

atrace_begin_body 方法实现参考 system/core/libcutils/trace-dev.cpp。

void atrace_begin_body(const char* name)
{
    WRITE_MSG("B|%d|", "%s", name, "");
}

atrace_begin_body 最终实现在宏 WRITE_MSG 实现,代码如下:

#define WRITE_MSG(format_begin, format_end, name, value) { \
    ...
    write(atrace_marker_fd, buf, len); \
}

通过 WRITE_MSG 实现,可知,atrace 数据是实时写入 fd 为 atrace_marker_fd 的文件中,如果多线程同时写入,则会出现锁问题,导致性能损耗加大。

RheaTrace 核心优势

效率提升

RheaTrace 会在 App 编译期间自动插入 Trace 跟踪函数,大大提高效率。针对不同 Android Gradle Plugin 版本,我们支持 Proguard 之后插桩,这样可以减少 App 方法插桩量,同时也过滤 Empty、Set/Get 等简单方法。

思路基于 matrix-gradle-plugin 大量改造实现。

rheaTrace {

   compilation {
      //为每个方法生成唯一 ID,若为 true,Trace#beginSection(String) 传入的是方法 ID。
      traceWithMethodID = true
      //决定哪些包名下的类您不需要做性能跟踪。
      traceFilterFilePath = "${project.rootDir}/rhea-trace/traceFilter.txt"
      //一些特定方法保持 ID 值固定不变。
      applyMethodMappingFilePath = "${project.rootDir}/rhea-trace/keep-method-id.txt"
   }
   runtime {
        ......
   }
}
  1. 为减少包体积、性能影响,我们也借鉴 matrix 慢函数思路,支持为每个函数生成唯一 ID,traceWithMethodID 为 true,Trace#beginSection(String)传入的是方法 ID,不再是方法名。有时候我们想某些方法 ID 固定不变,同样借鉴 matrix 慢函数思路,我们提供 applyMethodMappingFilePath 配置规则文件路径。

  2. 为进一步减少 App 方法插桩量,我们提供 traceFilterFilePath 文件配置让您决定哪些包、类、方法不做自定义事件跟踪,关于其用法请参考 RheaTrace Gradle Config。

性能提升

在 Systrace 概述中,我们了解到 atrace 数据是实时写入文件,且存在多线程同时写入带来的锁问题。因此,我们采取策略是拿到 atrace 文件 fd,在 atrace 数据写入前,先将其写至 LockFreeRingBuffer内存中,然后再将循环读取内存中 atrace 数据,写入我们定义的文件中。

首先我们通过 dlopen 获取 libcutils.so 对应句柄,通过对应 symbol 从中找到 atrace_enabled_tags 和 atrace_marker_fd 对应指针,设置 atrace_enabled_tags 用以打开 atrace,代码实现片段如下。

int32_t ATrace::InstallAtraceProbe() {
  ......
  {
    std::string lib_name("libcutils.so");
    std::string enabled_tags_sym("atrace_enabled_tags");
    std::string marker_fd_sym("atrace_marker_fd");

    ...
    ...
    atrace_marker_fd_ = reinterpret_cast<int*>(
        dlsym(handle, marker_fd_sym.c_str()));

    if (atrace_marker_fd_ == nullptr) {
      ALOGE("'atrace_marker_fd' is not defined");
      dlclose(handle);
      return INSTALL_ATRACE_FAILED;
    }
    if (*atrace_marker_fd_ == -1) {
      *atrace_marker_fd_ = kTracerMagicFd;
    }
  dlclose(handle);
  return OK;
  }

思路参考 profilo#installSystraceSnooper,本文不做过多介绍。

接着,通过 PLT Hook libcutils.so 中 write、write_chk 方法,判定该方法传入 fd 是否与 atrace_marker_fd 一致,若一致,则将 atrace 数据写入我们定义的文件中。

ssize_t proxy_write(int fd, const void* buf, size_t count) {
  BYTEHOOK_STACK_SCOPE();

  if (ATrace::Get().IsATrace(fd, count)) {
    ATrace::Get().LogTrace(buf, count);
    return count;
  }
  ...

  ATRACE_END();
  return ret;
}

有时候,我们可能仅需要关注主线程 atrace 数据,如果能将子线程 atrace 数据过滤掉,那么整体性能将进一步提升。一种很简单的思路,就是将 Trace#beginSection(String) 包装一层,如下代码片段。

static void t(String methodId) {
    if (!isMainProcess) {
        return;
    }
    if (mainThreadOnly) {
        if (Thread.currentThread() == sMainThread) {
            Trace.beginSection(methodId);
        }
    } else {
        Trace.beginSection(methodId);
    }
}

该方法仅能过滤我们为 App 方法插桩的 atrace 数据,系统层 atrace 数据无法过滤。为更彻底实现仅采集主线程数据,我们通过 PLT Hook 代理 atrace_begin_body 和 atrace_end_body 实现,在该方法进入前,判断当前线程 id 是否为主线程,如果不是,则不记录该条数据,代码实现片段如下。

void proxy_atrace_begin_body(const char *name) {
    BYTEHOOK_STACK_SCOPE();
    if (gettid() == TraceProvider::Get().GetMainThreadId()) {
        BYTEHOOK_CALL_PREV(proxy_atrace_begin_body, name);
    }
}

void proxy_atrace_end_body() {
    BYTEHOOK_STACK_SCOPE();
    if (gettid() == TraceProvider::Get().GetMainThreadId()) {
        BYTEHOOK_CALL_PREV(proxy_atrace_end_body);
    }
}

针对降低性能损耗,RheaTrace 提供编译配置供用户选择,针对不同使用场景配置合理参数。

rheaTrace {
    ......

    runtime {
        mainThreadOnly false
        startWhenAppLaunch true
        atraceBufferSize "500000"
    }
}

上述配置释义如下。

  1. mainThreadOnly:为 true 表示仅采集主线程 trace 数据。

  2. startWhenAppLaunch:是否 App 启动开始就采集 trace 数据。

  3. atraceBufferSize:指定内存存储 atrace 数据 ring buffer 的大小,如果其值过小会导致 trace 数据写入不完整,若您抓取多线程 trace 数据,建议将值设为百万左右量级;最小值为 1 万,最大值为 5 百万。

实用性提升

针对已有的 atrace 数据,额外拓展 IO 等信息;另外为通过 Python 脚本彻底解决方法因执行异常导致 trace 数据闭合异常问题,保证每个方法 trace 数据的准确性。

目前我们基于 JVMTI 方案,在 Android 8.0 及以上设备可以获取类加载以及内存访问相关 trace 数据,目前仅支持编译类型为 debuggable 的 App,目前处于实验功能,本文暂先不过多介绍。

IO 数据拓展

背景简介

在抖音启动性能优化时,我们曾统计冷启动的耗时,其中占比最长的是进程处于 D 状态(不可中断睡眠态,Uninterruptible Sleep ,通常我们用 PS 查看进程状态显示 D,因此俗称 D 状态)时间。此部分耗时占总启动耗时约 40%,进程为什么会被置于 D 状态呢?处于 uninterruptible sleep 状态的进程通常是在等待 IO,比如磁盘 IO,其他外设 IO,正是因为得不到 IO 响应,进程才进入 uninterruptible sleep 状态,所以要想使进程从 uninterruptible sleep 状态恢复,就得使进程等待 IO 恢复,类似如下。

c125eb238e001f2baa9b99e57414c35c.png

但在使用 Systrace 进行优化时仅能得到如上内核态的调用状态,却无法得知具体的 IO 操作是什么。

方案介绍

因此,我们专门设计一套获取 IO 耗时信息方案,其包括用户空间和内核空间两部分。

一是在用户空间,为采集所需 IO 耗时信息,我们通过 Hook IO 操作标准函数簇,包括 open,write,read,fsync,fdatasync 等,插入对应 atrace 埋点用于统计对应的 IO 耗时,以 fsync 为例。

0528c96745ee5e4c5ffcc9fe11c5c2d5.png

其对应 hook 代码逻辑如下:

int proxy_fsync(int fd) {
  BYTEHOOK_STACK_SCOPE();
  ATRACE_BEGIN_VALUE("fsync:", FileInfo(fd).c_str());

  int ret = BYTEHOOK_CALL_PREV(proxy_fsync, fd);

  ATRACE_END();
  return ret;
}

二是在内核空间,除 systrace 或 atrace 可直接支持启用功能外,ftrace 还提供其他功能,并包含对调试性能问题至关重要的一些高级功能(需要 root 访问权限,通常也可能需要新内核)。我们基于此添加显示定制 IO 信息等功能,开启/sys/kernel/debug/tracing/events/android_fs节点下 ftrace 信息,用于收集 IO 相关的信息。内核空间 IO 信息是通过 python 脚本开启,详见 io_extender.py。

解决方法闭合错误问题

背景介绍

RheaTrace 会自动在每个方法入口、出口处分别插入 Trace#beginSection(String) 和 Trace#endSection() ,一个方法有且只有一个入口,但会有多个出口,方法出口对应的结束字节码指令有 return 和 throw 等。

public static void testCrash() {
        try {
            testA();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void testA() {
        testB();
        testC();
    }

    public static void testB() {
        int ret = 2 / 0; // <----- crash event
        testD(ret);
    }

    public static void testC() {
        Log.d("btrace", "do some things.");
    }

    public static void testD(int num) {
        Log.d("btrace", "box size: " + num);
    }

上面的代码很简单,即 testCrash -> testA -> testB,其中 testB 出现异常,最终是在 testCrash 中捕获。通过本示例可知,testA、testB 方法出口均未正常执行完成,这也就导致 trace 数据不闭合,生成的 trace 数据如下,从中可以看出,B 和 E 数量上并不匹配,且仅从 trace 上看,我们也无法知道 E 属于哪个方法。

5108949.231989: B|28045| TestCrash:a
5108949.232055: B|28045| TestCrash:b
5108949.232554: B|28045| TestCrash:c
5108949.232580: E|28045

方案介绍

为解决该问题,RheaTrace 做了取巧处理,方法的出口由插入 Trace#endSection()改为 Trace#beginSection(String)。那我们如何知道哪条 trace 是开始,哪条是结束?我们看如下示例。

5108949.231989: B|28045|B:TestCrash:a
5108949.232055: B|28045|B:TestCrash:b
5108949.232554: B|28045|B:TestCrash:c
5108949.232580: B|28045|E:TestCrash:a

如上 trace 数据,每个方法描述前都会加上 B: 或 E、T, B: 表示方法开始,E 表示方法 retrun 结束,T: 表示方法 throw 结束。然后通过 Python 脚本处理并还原正常 trace 数据。如此做以后,我们就可以明确知道方法开始和结束,同时针对异常结束方法,我们会做补全处理,处理后的 trace 数据如下。

5108949.231989: B|28045|TestCrash:a
5108949.232055: B|28045|TestCrash:b
5108949.232554: B|28045|TestCrash:c
5108949.232554: E|28045|TestCrash:c
5108949.232554: E|28045|TestCrash:b
5108949.232580: E|28045|TestCrash:a

关于 Python 脚本的处理过程,本文不做过多介绍,大家可以阅读相关源码即可。

RheaTrace 工作流程

流程概述

RheaTrace 作为线下性能分析利器,我们首先看下其整体工作流程。

a681d9fb78f317485bc2993dbd363d8c.png

如上文介绍,我们将 Systrace 中 atrace 数据做拦截,将其转存至我们自定义的文件中。

Systrace 格式

首先我们 Systrace 生成的 trace.html 中 atrace 数据格式。

<idle>-0     (-----) [001] d.h4 1308823.803921: sched_waking: comm=TimerDispatch pid=704 prio=97 target_cpu=001
          <idle>-0     (-----) [001] dnh5 1308823.803929: sched_wakeup: comm=TimerDispatch pid=704 prio=97 target_cpu=001
          <idle>-0     (-----) [001] d..2 1308823.803943: sched_switch: prev_comm=swapper/1 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=TimerDispatch next_pid=704 next_prio=97
          <idle>-0     (-----) [003] d.s3 1308823.803980: sched_waking: comm=kworker/3:0 pid=11120 prio=120 target_cpu=003
          <idle>-0     (-----) [003] d.s4 1308823.803986: sched_blocked_reason: pid=11120 iowait=0 caller=worker_thread+0x4fc/0x804
   TimerDispatch-704   (  643) [001] .... 1308823.803988: tracing_mark_write: B|643|TimerIteration #9392
          <idle>-0     (-----) [003] dns4 1308823.803988: sched_wakeup: comm=kworker/3:0 pid=11120 prio=120 target_cpu=003
   TimerDispatch-704   (  643) [001] .... 1308823.803992: tracing_mark_write: E|643
          <idle>-0     (-----) [003] d..2 1308823.803997: sched_switch: prev_comm=swapper/3 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=kworker/3:0 next_pid=11120 next_prio=120
   TimerDispatch-704   (  643) [001] .... 1308823.804011: tracing_mark_write: C|643|VSP-mode|0
   TimerDispatch-704   (  643) [001] .... 1308823.804014: tracing_mark_write: C|643|VSP-timePoint|405332069786762
   TimerDispatch-704   (  643) [001] .... 1308823.804016: tracing_mark_write: C|643|VSP-prediction|405332075389317
   TimerDispatch-704   (  643) [001] .... 1308823.804022: tracing_mark_write: B|643|app-alarm in:5602555 for vs:15880333
   TimerDispatch-704   (  643) [001] .... 1308823.804024: tracing_mark_write: E|643

文本形式打开 trace.html,在其底部是填充的 trace 数据 ,如上所示数据片段,带有 tracing_mark_write 标签的即包含 atrace 数据。在 trace.html 文件中有关于 trace 格式介绍,如下数据片段。

# tracer: nop
#
# entries-in-buffer/entries-written: 178063/178063   #P:8
#
#                                      _-----=> irqs-off
#                                     / _----=> need-resched
#                                    | / _---=> hardirq/softirq
#                                    || / _--=> preempt-depth
#                                    ||| /     delay
#           TASK-PID    TGID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |        |      |   ||||       |         |
          <idle>-0     (-----) [003] d.s2 1308814.493991: sched_waking: comm=rcu_preempt pid=9 prio=120 target_cpu=003
          <idle>-0     (-----) [000] d.s2 1308814.493997: sched_waking: comm=rcu_sched pid=10 prio=120 target_cpu=000

在 trace.html 中,一条完整的 atrace 数据为:

.sample.android-19452 (19452) [005] .... 1308823.801863: tracing_mark_write: B|19452|activityStart
......
.sample.android-19452 (19452) [005] .... 1308824.801753: tracing_mark_write: E|19452

在上文介绍 Systrace 时候,我们提到,Trace#beginSection(String) 和 Trace.endSection() 最终是调用如下宏。

#define WRITE_MSG(format_begin, format_end, name, value) { \
    ...
    write(atrace_marker_fd, buf, len); \
}

其中,write 函数传入的 trace 数据为:

B|19452|activityStart
......
E|19452

B 表示 Section 进入,E 表示 Section 退出,从以上数据片段可以看出,相较于 trace.html 中 atrace 数据少了很多信息,缺少的信息是内核补全。

Systrace 工具中 --from-file 是可以将原始 atrace 数据转化为可视化的 html 文件。因此,针对 atrace 数据我们需要补全缺少的信息。结合前面介绍的 trace 格式说明及多次验证,可被 Systrace 工具识别的 atrace 文件格式满足如下:

<ThreadName>-<TheadID> [001] ...1 <Timestamp>: trace_mark_write:<B|E>|<ProcessID>|<TAG>

格式说明:

  1. <ThreadName>:线程名,若为主线程,可指定为包名。

  2. <ThreadID>:线程 ID。

  3. <Time seconds>:方法开始或者结束时间戳。

  4. <B|E>:标记该条记录为方法开始(B)还是结束(E)。

  5. <ProcessID>:所在进程 ID。

  6. <TAG>:方法标记,字符长度不可超过 127。

[001] 和 ...1 对应的数据用户层是无法获取,因此硬编码写死。

RheaTrace 格式

Systrace 中相关 atrace 数据格式有很多冗余信息,冗余信息是可以通过脚本来进行补充,这样在 atrace 存储过程中可以减少一定数据量的存储。

仅采集主线程 atrace 数据,其对应格式如下:

1306401.857369: B|16667|VerifyClass com.bytedance.rheatrace.atrace.TraceEnableTagsHelper
1306401.857498: E|16667
1306401.857560: B|16667|VerifyClass com.bytedance.rheatrace.common.ReflectUtil
1306401.857825: E|16667
1306401.857876: B|16667|VerifyClass kotlin.jvm.internal.Intrinsics
1306401.858241: E|16667
1306401.858523: B|16667|VerifyClass com.bytedance.rheatrace.core.RheaNoticeManager
1306401.858633: E|16667

因为 RheaTrace 仅支持采集主进程 trace 数据,因此,进程 ID 信息无需写入,另外主线程名为包名也无需写入,主线程 ID 与进程 ID 一致也无需写入,剩余信息均为格式模板信息也无需写入,唯一需要记录的是时间戳。

采集所有线程 atrace 数据,其对应格式如下。

1306401.859162 16667: B|16667|RheaApplication#onCreate
1306401.859173 16667: E|16667
1306401.859756 16667: E|16667
1306401.859877 16667: B|16667|activityStart
1306401.862738 16680: B|16667|JIT compiling int sun.util.locale.StringTokenIterator.nextDelimiter(int) (baseline=0, osr=0)
1306401.862772 16680: B|16667|Compiling
1306401.863154 16680: B|16667|ScopedCodeCacheWrite
1306401.863172 16680: B|16667|mprotect all
1306401.863207 16680: E|16667

当采集多线程数据时,我们需要获取对应的线程 ID,线程名我们没有通过在 App 期间获取,而是读取 Systrace 工具生成 trace.html 中读取。如下数据片段,我们可以获取进程 ID 为 16667 对应的所有线程 ID 及名称。当然也会存在线程 ID 如下数据片段找不到的情况,我们暂时用 <...>代替。

USER            PID   TID CMD
root              1     1 init
root              1   548 init
root              2     2 kthreadd
root              3     3 rcu_gp
root              5     5 kworker/0:0H
root              7     7 mm_percpu_wq
root              8     8 ksoftirqd/0
root              9     9 rcu_preempt
root             10    10 rcu_sched
root             11    11 rcu_bh
.....
u0_a168       16667 16684 FinalizerWatchd
u0_a168       16667 16685 Binder:16667_1
u0_a168       16667 16686 Binder:16667_2
u0_a168       16667 16687 Binder:16667_3
u0_a168       16667 16688 Profile Saver
u0_a168       16667 16689 async-writer
u0_a168       16667 16690 RenderThread
u0_a168       16667 16693 HWC release
u0_a168       16667 16694 GPU completion

通过 RheaTrace 提供的脚本,我们就可以将原始 atrace 数据加工为标准 atrace 格式,如下数据片段。

.sample.android-16667 [001] ...1 1306401.857369: tracing_mark_write: B|16667|VerifyClass com.bytedance.rheatrace.atrace.TraceEnableTagsHelper
 .sample.android-16667 [001] ...1 1306401.857498: tracing_mark_write: E|16667
 .sample.android-16667 [001] ...1 1306401.857560: tracing_mark_write: B|16667|VerifyClass com.bytedance.rheatrace.common.ReflectUtil
 .sample.android-16667 [001] ...1 1306401.857825: tracing_mark_write: E|16667
 .sample.android-16667 [001] ...1 1306401.857876: tracing_mark_write: B|16667|VerifyClass kotlin.jvm.internal.Intrinsics
 .sample.android-16667 [001] ...1 1306401.858241: tracing_mark_write: E|16667
 .sample.android-16667 [001] ...1 1306401.858523: tracing_mark_write: B|16667|VerifyClass com.bytedance.rheatrace.core.RheaNoticeManager
 .sample.android-16667 [001] ...1 1306401.858633: tracing_mark_write: E|16667

当通过 Systrace 命令获取 trace.html 后,trace.html 中的 atrace 因为被 RheaTrace 拦截写入自定义文件中,因此生成的 trace.html 文件中是不包含 atrace 数据。

如果不包含 atrace,那么 trace.html 的作用将非常小,因此,我们需要将 atrace 数据填充进入 trace.html 中,经过验证 atrace 数据满足如下格式,能够被 trace.html 识别。

<ThreadName>-<TheadID> (ProcessID) [001] ...1 <Timestamp>: trace_mark_write:<B|E>|<ProcessID>|<TAG>

相较于标准 atrace 文件,它多了 (ProcessID)数据。

有了上述介绍,我们介绍下 RheaTrace 生成的 systrace.html 中间会生成哪些文件。

python rheatrace.py -a rhea.sample.android -t 3 -o ./output/systrace.html

在 systrace.html 同级目录下会生成 .build 目录,其中包括上述中间产物文件。

├── .build
│   ├── atrace-standard          //标准 atrace 格式,可直接用 perfetto 打开。
│   ├── rhea-atrace              //从设备中拉取的 rhea-atrace.gz 文件解压得到,原始 RheaTrace 格式的 atrace 数据。
│   ├── systrace-fs-origin.html  //如果设备 root,其中会包括内核 IO 事件,前文有提到。
│   └── systrace-origin.html     //通过 Systrace 工具抓取的可视化 trace 文件,不包含 atrace 数据。
└── systrace.html                //atrace-standard 与 systrace-fs-origin.html 或 systrace-origin.html 合并得到。

未来规划

  1. 支持 App 独立抓取 atrace 数据,无需依赖 Systrace 环境。

  2. 提供稳定、高效的 trace 采集环境,适配更多手机机型。

  3. 支持更多维度的 trace 信息,比如渲染、内存等,更方便定位函数耗时原因。

  4. 进一步降低性能损耗,到达线上使用要求。

相关推荐

2024 抖音欢笑中国年(五):Wasm、WebGL 在互动技术中的创新应用

前言随着 Web 前端技术的不断发展,越来越多的新兴技术方案被引入到 Web 开发中,其中 Wasm 和 WebGL 作为前端领域的两大利器,为开发者带来了更多的可能性。本文将结合2024 年抖音欢笑中国年的部分项目,重点介绍如何利用 Wasm 和 WebGL 对目前流行的一些前端互动技术(比如 Lottie、渲染引擎、动画图片等)进行创新和实践,利用 Wasm 和 WebGL 等新技术方案的特性...

字节跳动技术团队官方博客 2万+

2024 抖音欢笑中国年(四):渲染技术实践与探索

作者:陈瑞、欧阳浩铸、王武俊、倪梵云前言抖音在2024年春节期间推出了欢笑中国年系列活动,为用户带来了全新的体验和乐趣。而SAR Creator则为该项目研发工作提供了重要的技术支持。SAR Creator是一款基于 Typescript 的高性能、轻量化的互动解决方案,目前支持了浏览器和跨端框架平台,服务于字节内部的各种互动业务。这些绚烂多彩的互动场景当然也离不开实时渲染技术的支持,因此本文将专...

字节跳动技术团队官方博客 2万+

2024 抖音欢笑中国年(三):编辑器技巧与实践

前言本次春节活动中,我们大部分场景使用内部的 SAR Creator互动方案来实现。SAR Creator 是一款基于 TypeScript 的高性能、轻量化的互动解决方案,目前支持了Web和字节内部跨端框架平台,服务于字节内部的各种互动业务,包括但不限于抖音春节、抖音直播礼物、抖音UG活动等。SAR Creator 编辑器支持了图形化界面,提供了各类完善的系统(光照、动画、脚本等)供用户快速便捷...

字节跳动技术团队官方博客 1万+

2024 抖音欢笑中国年(二):AnnieX互动容器创新玩法解析

本文基于24年抖音春节活动业务背景,介绍了字节跨端容器AnnieX在游戏互动套件上的探索,致力于提升容器在游戏互动场景的优化能力。业务背景AnnieX作为字节一方游戏统一容器,服务字节内部电商、直播、UG等跨端场景业务。在字节一方游戏互动场景,有大量的一方游戏业务对容器有特定的流量、端能力和游戏优化的诉求。因此我们不断深入互动游戏业务特点,为字节游戏提供完善游戏端能力和流量运营能力,同时提供游戏互...

字节跳动技术团队官方博客 1万+

2024 抖音欢笑中国年(一):招财神龙互动技术揭秘

字节跳动旗下的抖音等 App 在 2024 年春节期间推出了欢笑中国年系列活动,在实现增长业务目标的同时,为用户带来了全新的体验和乐趣。「招财神龙」是其中的一个重要玩法。前言本次春节活动,使用到了字节内的主要前端、跨端、互动技术产品。主要涉及:跨端框架提供了首屏直出的方案使其具有较短的首屏时间,能够大大提升业务加载成功率。跨端框架也提供了 Canvas 作为 SAR Creator 等渲染引擎的...

字节跳动技术团队官方博客 1万+

Monorepo 解决方案 — 基于 Bazel 的 Xcode 性能优化实践

背景介绍书接上回《Monorepo 解决方案 — Bazel 在头条 iOS 的实践》,在头条工程切换至 Bazel 构建系统后,为了支持用户使用 Xcode 开发的习惯,我们使用了开源项目 Tulsi 作为生成工具,用于将 Bazel 工程转换为 Xcode 工程。但是在使用的过程中,我们发现了一些问题,其中影响较大的是,Xcode 工程卡顿:对于头条这种大型项目来说,Xcode 卡顿一直是本地...

字节跳动技术团队官方博客 1万+

抖音 ANR 自动归因平台建设实践

背景介绍本文在 2024 年初最新一期『抖音客户端基础技术大揭秘』技术沙龙活动中已做过专题分享,本次将内容重新整理文章进行分享。公众号后台回复技术沙龙可查看沙龙回放及 PPT~抖音作为一个超大型的应用,我们在 ANR 问题治理上面临着很大的挑战。首先对于存量问题的优化,由于缺少有效的归因手段,一些长期的疑难问题一直难以突破解决,例如长期位于 Top 1 的 nativePollOnce 问题。同时...

字节跳动技术团队官方博客 2万+

CVPR 2024 | CAMixerSR 动态注意力分配的超分辨率加速框架

背景随着相关技术和应用的发展,比如超高清屏幕、虚拟现实(VR)等沉浸式体验的增加,用户对超高分辨率图像和视频的需求变得越来越强烈。在这些场景中,图像的质量和清晰度对于提供最佳的用户体验至关重要。超高分辨率不仅能提供更清晰、更真实的视觉效果,还能在一定程度上增强人们的互动和沉浸感,在一些VR场景中我们需要8K甚至16K的才可以满足需求。然而要生成或者处理这些超高分辨率的内容,对算力的要求也是与日增长...

字节跳动技术团队官方博客 1万+

CVPR 2024 | Modular Blind Video Quality Assessment:模块化无参视频质量评估

无参视频质量评估 (Blind Video Quality Assessment,BVQA) 在评估和改善各种视频平台并服务用户的观看体验方面发挥着关键作用。当前基于深度学习的模型主要以下采样/局部块采样的形式分析视频内容,而忽视了实际空域分辨率和时域帧率对视频质量的影响,随着高分辨率和高帧率视频投稿逐渐普及,特别是跨分辨率/帧率视频转码档位画质评估场景中,这种影响变得更加不可忽视。在本文中,我们...

字节跳动技术团队官方博客 1万+

2024 AI & 前端:回首展望,光芒未至,破晓之前!

前言回望 2023 年,ChatGPT 的突然爆火,让 AI 无疑成为最为值得注目的新兴领域之一,我们也一起见证了生成式 AI 的寒武纪大爆发。这一年来,国内外的生成式 AI 、大模型和相关产品以令人眼花缭乱的速度更新迭代,新的创业浪潮风起云涌。在这 AI 浪潮下,也让我们有了新的开发思考,探索着在各个环节中“前端 & AI”的应用场景。勇于探索的前端开发者们已经开始挥舞着 AI 的“魔法...

字节跳动技术团队官方博客 1万+

Kotlin 云端差分缓存技术

本文由字节跳动 Buildinfra 团队出品。在我们的工程上线 Monorepo 全源码后,Kotlin 编译成了整个编译中最耗时的步骤,全源码过程中大量的 BuildCache Miss 导致我们的编译数据落后原来多仓二进制时代很多,且业界没有相关的解决方案。本篇文章我们来具体阐述下 BuildInfra 团队自研的解决方案 - Kotlin 云端差分方案的原理和技术实现。一、Monorepo...

字节跳动技术团队官方博客 1万+

字节跳动基础架构SRE-Copilot获得2023 CCF国际AIOps挑战赛冠军

近日,2023 CCF国际AIOps挑战赛决赛暨“大模型时代的AIOps”研讨会在北京成功举办,活动吸引了来自互联网、运营商、科研院所、高校、软硬件厂商等领域多名专家学者参与,为智能运维的前沿学术研究、落地生产实践打开了新思路。决赛中,从初赛两百多支队伍中脱颖而出的十支入围队伍分别展示了各自的方案,并进行了现场答辩,评审专家从选题方向、创新性、实用性、完整度和实验复现结果等多角度进行了综合评定,最...

字节跳动技术团队官方博客 2万+

字节跳动百万级Metrics Agent性能优化的探索与实践

背景metricserver2 (以下简称Agent)是与字节内场时序数据库 ByteTSD 配套使用的用户指标打点 Agent,用于在物理机粒度收集用户的指标打点数据,在字节内几乎所有的服务节点上均有部署集成,装机量达到百万以上。此外Agent需要负责打点数据的解析、聚合、压缩、协议转换和发送,属于CPU和Mem密集的服务。两者结合,使得Agent在监控全链路服务成本中占比达到70%以上,对Ag...

字节跳动技术团队官方博客 1万+

西瓜视频RenderThread引起的闪退问题攻坚历程

背景影响西瓜之前存在过一类RenderThread闪退,从堆栈上看,全部都是系统so调用,给人的第一印象像是一个系统bug,无从下手。闪退集中在Android 5~6上,表现为打开直播间立即闪退。该问题在2022年占据Native Crash Top5,2023年更是上升到到Top1。因此有必要投入时间和精力再重新审视一下这个问题。在历经多周的源码分析和排查后,逐步明确了问题根因并修复,最终取得了...

字节跳动技术团队官方博客 2万+

字节电商双11 大促容量保障是如何做的?

前言Rhino 简介Rhino是字节自研全链路容量评估产品,致力于构建完整的全链路容量评估解决方案(覆盖:容量预估->资源准备->数据准备->容量验证->监控->分析->决策->处理反馈);围绕容量在稳定性、成本、效率 三方面提供业务全方位基础支撑。Rhino 目前已经成为字节各业务容量评估主流解决方案,并且历年来在业务大型活动稳定性保障中(抖音春节项目、...

字节跳动技术团队官方博客 10万+

使用火山引擎 APMPlus 解决抖音Top 1 Java 崩溃的通用优化方案

背景近3个月,抖音 Android 版面临一个多次触发线上报警的崩溃问题,全量版本和灰度版本的异常数据激增,该问题不仅容易触发报警,更成为了 Java Top 1 崩溃问题,带来巨大困扰,急需攻坚解决。本文展现了具体的分析过程、优化思路和解决方案,同时提供了已集成该方案的实用工具。初步分析多维特征我们以某发版期间数据为例进行分析:机型方面:比较分散,有聚集部分samsung sm-s9180 占比...

字节跳动技术团队官方博客 2万+

用 Addon 增强 Node.js 和 Electron 应用的原生能力

前言Node.js Addon 是 Node.js 中为 JavaScript 环境提供 C/C++ 交互能力的机制。其形态十分类似 Java 的 JNI,都是通过提供一套 C/C++ SDK,用于在 C/C++ 中创建函数方法、进行数据转换,以便 JavaScript / Java 等语言进行调用。这样编写的代码通常叫做 Bindings。此外还有基于 C ABI Calling Convent...

字节跳动技术团队官方博客 2万+

火山引擎 ByteHouse 的增强型数据导入技术实践

作为企业数字化建设的必备要素,易用的数据引擎能帮助企业提升数据使用效率,更好提升数据应用价值,夯实数字化建设基础。数据导入是衡量OLAP引擎性能及易用性的重要标准之一,高效的数据导入能力能够加速数据实时处理和分析的效率。作为一款OLAP引擎,火山引擎云原生数据仓库ByteHouse源于开源ClickHouse,在字节跳动多年打磨下,提供更丰富的能力和更强性能,能为用户带来极速分析体验,支撑实时数据...

字节跳动技术团队官方博客 1万+

打造企业级智能问答系统的秘密:如何使用云数据库 PostgreSQL 版实现向量检索

本文就如何利用火山引擎云数据库 PostgreSQL 版和大语言模型技术(Large Language Model,简称 LLM),实现企业级智能交互式问答系统进行介绍。通过本文,你将会了解交互式问答系统的原理,学习 PostgreSQL 的向量化存储和检索技术,以及大语言模型交互技术等。背景在大数据的浪潮下,众多企业建立了自己的知识库,以便于信息检索和知识查询。然而,随着知识库内容的膨胀,传统的...

字节跳动技术团队官方博客 2万+

抖音大型直播的画质优化实践

面临挑战随着抖音内容生态的不断丰富,越来越多的大型赛事在抖音平台进行直播,世界杯/春晚/亚运会等各项赛事节目引来大量用户观看。卡塔尔世界杯期间,抖音提供的稳定高质直播画面为观众带来了完美的观赛体验,决赛的 PCU 高达 3700W+。不同赛事节目涉及链路众多,且不同赛事之间存在差异,如何保障各链路的画质稳定并进一步提升画质,是一个巨大的挑战。如何应对挑战?画质优化链路大型赛事直播涉及链路较长,不同...

字节跳动技术团队官方博客 2万+

抖音直播新一代BVC编码器正式亮相

面临挑战在直播行业发展如火如荼的今天,用户对视频体验的要求也水涨船高。视频基础体验的关键要素包括清晰度、流畅度、低延迟等,而这些要素的“第一性原理”,就是视频本身的编码效率,也就是压缩率。视频编码是整个技术体系的基座,编码效率的显著提升,能够在同等码率下极大提高画质,从而改善用户体验。视频编码效率的重要性不言而喻,但进一步地提升也并非易事,尤其在直播场景中,对编码速度、延迟、码率控制等方面都有很高...

字节跳动技术团队官方博客 7356

Go Metrics SDK Tag 校验性能优化实践

背景Metrics SDK 是与字节内场时序数据库 ByteTSD 配套的用户指标打点 SDK,在字节内数十万服务中集成,应用广泛,因此 SDK 的性能优化是个重要和持续性的话题。本文主要以 Go Metrics SDK 为例,讲述对打点 API 的 hot-path 优化的实践。用户在使用 SDK API 进行打点时,需要传入指标对应的 Tag:tags:=[]m.T{{Name:"foo...

字节跳动技术团队官方博客 1万+

云上智能驾驶三维重建最佳实践

智能驾驶技术的不断发展,正在改变着我们的出行方式和交通系统。作为其中的一个关键技术,三维重建在智能驾驶系统中起着重要的作用。除去车端本身的感知、重建算法,自动驾驶技术的落地与发展需要庞大的云端重建能力支撑,火山引擎多媒体实验室通过行业领先的自研三维重建技术,结合强大的云平台资源与能力,助力相关技术在云端大规模重建、自动标注、真实感仿真等场景的落地与应用。本文重点介绍火山引擎多媒体实验室三维重建技术...

字节跳动技术团队官方博客 6304

火山引擎实时、低延时拥塞控制算法的优化实践

摘要火山引擎智能拥塞控制算法 VICC(Volcano Intelligent Congestion Control)是一种自适应的拥塞控制算法,旨在解决全球不同网络环境下,不同音视频应用对带宽利用率和延时的差异化要求。它结合了传统拥塞控制算法(如 GCC 和 BBR)的优点,并且能够根据不同的网络条件、业务偏好和码率特征进行自适应调整,包括自适应拥塞响应速度、自适应带宽探测幅度、自适应丢包检测策...

字节跳动技术团队官方博客 6735

veImageX 演进之路:Web 图片加载提速50%

背景说明火山引擎veImageX演进之路主要介绍了veImageX在字节内部从2012年随着字节成长过程中逐步演进的过程,演进中包括V1、V2、V3版本并最终面向行业输出;整个演进过程中包括服务端、客户端、网络库、业务场景与优化等多个角度介绍在图像处理压缩、省成本与体验优化的经验与方案;本篇文章重点介绍在web端演进和提供的能力,图片是 Web 站点中的重要元素,图片体积、格式、分辨率以及渲染方式...

字节跳动技术团队官方博客 6589

自研多模态追踪算法 PICO 为「手柄小型化」找到新思路

作者:张韬、林泽一 、闻超 、赵洋研发背景作为头戴的追踪配件,VR手柄可以通过HMD(头戴显示设备)的inside-out光学追踪定位原理,计算出手柄的空间运动轨迹,同时结合6轴传感器实现6DoF空间定位。与此同时,结合手柄控制器的物理按键、马达反馈、摇杆等,用户还能获得逼真、细腻的触觉反馈,进一步增强虚拟现实人机交互的能力以及沉浸感,这也是目前无手柄方案所难以实现的。目前主流VR手柄的追踪技术方...

字节跳动技术团队官方博客 7812

如何利用播放器节省20%点播成本

点播成本节省的点其实涉及诸多部分,例如:CDN、转码、存储等,而利用播放器降本却是很多客户比较陌生的部分。火山引擎基于内部支撑抖音集团相关业务的实践,播放器恰恰是成本优化中最重要和最为依赖的部分。火山引擎的视频团队做了份数据统计,在一个很经典的视频业务中,我们在2022年至2023年大约1年半的时间里,针对这个业务进行了33次成本优化点,其中13次是播放器主导的优化,其余的有12次也是需要播放器强...

字节跳动技术团队官方博客 6176

火山引擎 ByteHouse:ClickHouse 如何保证海量数据一致性

背景ClickHouse是一个开源的OLAP引擎,不仅被全球开发者广泛使用,在字节各个应用场景中也可以看到它的身影。基于高性能、分布式特点,ClickHouse可以满足大规模数据的分析和查询需求,因此字节研发团队以开源ClickHouse为基础,推出火山引擎云原生数据仓库ByteHouse。在日常工作中,研发人员经常会遇到业务链路过长,导致流程稳定性和数据一致性难保障的问题,这在分布式、跨服务的场...

字节跳动技术团队官方博客 6900

抖音集团都在用的画质评估工具,确定不试试吗?

导读本文从抖音集团内部画质评估体系的建设历程着笔,主要分享了画质评测对于业务的重要性、主要应用场景和内部产品的一些典型实践案例。通过分享业务视角遇到的一些问题和我们的解决思路,希望能抛砖引玉,为遇到类似困扰的伙伴们提供有价值的参考。画质评估体系建设历程为何评测画质如此重要?我们通过线上业务大量实验发现,图片画质优劣对点击率、 停留时长等消费类指标有正相关影响,间接影响用户收益指标。因此,建设一套行...

字节跳动技术团队官方博客 7937

VLDB 2023 | CDSBen: 字节跳动 veDB 数据库存储系统性能测试模型

背景随着业务爆炸式增长与云原生技术的日渐成熟,大量云原生分布式数据库产品如雨后春笋般涌现,其中一部分主打 OLTP 场景的分布式数据库强调的是从计算-存储分离架构获得弹性收益;对于业界各种计算-存储分离架构的数据库而言,怎么用真实的端到端数据库 workload 去 benchmark 其底层存储系统一直存在以下难题:对于数据库专用存储系统,不存在如 fio 一样的“事实标准” benchmark...

字节跳动技术团队官方博客 5895
上一篇: 赵海平与张宏波谈编程语言
下一篇: 业界首家720p/1080p移动端实时超分,打造抖音极致画质体验
 字节跳动技术团队
字节跳动技术团队 企业官方账号 企业官方账号
博客等级 码龄7年 8152粉丝 316原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值