前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >打造一款支持线上抓 systrace 的框架

打造一款支持线上抓 systrace 的框架

作者头像
codelang
发布2022-11-30 14:52:46
7030
发布2022-11-30 14:52:46
举报
文章被收录于专栏:codelang

缘起

缘起是看到网易云音乐技术团队发布的一篇《systrace 统计方法耗时》的文章,文章讲解了如何通过 hook 的方式将 trace 开关打开,并实现不依赖 PC 开启 Trace 功能,实现线上抓取。文章思路与流程非常清晰,是一个不错的实践(造轮子)机会,我将此文转发给正在研究 xcrash 和 xhook 的同事,我俩一拍即合,打算利用这个机会撸一个框架,并且,同事还给我发了一篇更全面的实践文章《抖音 Android 性能优化系列:新一代全能型性能分析工具 Rhea》。

我们非常建议,在阅读这款框架源码的时候,一定要看下上述提到的两篇文章。

为什么我们也想做一个:

  • 脱离 PC 抓取 trace 可以帮助我们更好的定位问题,有很多 bug 我们是无法从自己的机型和环境进行复现的,往往需要客户配合来抓取一些日志来实现问题定位,并且,相比较我们自己埋点的日志来说,systrace 采集到的信息更丰富

能学到什么?

  • Gradle Transfrom 被废弃,学习使用最新的 TransfromAction
  • 利用 AsmClassVisitorFactory 实现方法插桩
  • 学会使用 xhook 来 hook native 函数
  • 使用 mmap 来优化 IO 性能问题

开发的过程中遇到了哪些问题?

插桩方法未正确闭合:

  • try catch 中的 catch 块,需要插桩方法结束标记
  • 在参考字节的 Reha 第一版时,Reha 有限制调用栈深度,所以,我们直接使用了 Stack 集合 size 为 6 来限制层级,以此来提高性能,但忘了考虑多线程调用的问题,故修复后使用 ThreadLocal 来为每个线程保存一份 Stack 来记录方法调用栈深度问题。但在 Rhea 的第三次改造中,Rhea 为了获得更丰富的信息,已取消层级限制。

还有哪些不足

  • 插桩方法使用的 class name 和 method name,可以考虑像 Matrix 那样,通过 method Id 来做,然后生成一份 method 映射表。但这么做的话,对于生成的 systrace 可能不太直观, 因为倒入 perfetto 的时候看到的都是 methid Id,可以做一个脚本,读取 systrace 内容,将 method id 拿到,然后通过映射表,恢复方法名,然后再重新写入 systrace,导入 perfetto 查看。Reha 考虑线下用户的易用性,在线下模式打包阶段直接插入的方法名
  • 目前仅在线下使用,理由与 Rhea 一致:

目前 Trace 工具更多的还是在线下使用,由于插桩过多影响了包大小,使得我们线上部分只能对小规模的用户群体定向打开,没法全量上线定位线上大规模用户的性能问题

使用

由于放置在 github 的源码没有对外发布依赖,故这里就介绍下如何使用,我相信,如果有同学对这方面有兴趣的话,这一定难不倒他(主要是我们太懒了,笑哭)。

由于 github 项目使用的是复合构建引入的插件模块,如果想接入到自己项目的话,可以参考 setting.gradle,通过 includebuild 来引入插件模块。

在 app 模块的 build.gradle 中使用 tracePlugin 插件:

代码语言:javascript
复制
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'tracePlugin'
}

trace {
    pkg = "com.bomber.strace"
    ignoreClass = [
        "com.bomber.strace.*.bean.*",
        "com.bomber.*.R",
        "com.bomber.*.R\\\$.*",
        "com.bomber.*.BuildConfig",
    ]
}
  • tracePlugin 插件支持 pkg 配置要插桩的包路径,这非常友好,对于一些三方模块,我们暂时没必要对其进行插桩,我们只定位业务模块发生的问题
  • tracePlugin 插件还支持 ignoreClass 来忽略掉一些不需要对其插桩的类,例如 javaBean 和 R 类等,类路径支持正则匹配

在 Application 中对其进行初始化:

代码语言:javascript
复制
class LancerApp : Application() {

    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        installLancer()
    }

    private fun installLancer() {
        val traceDir = filesDir.absolutePath + File.separator + "trace"
        val r = Lancer.getInstance().initialize(
            Build.VERSION.SDK_INT,
            traceDir,
            BuildConfig.DEBUG
        )
        Log.d("TraceApp", "r = $r")
    }
}

initialize 方法需要传入三个参数:

  • 获取 Android SDK 版本
  • trace 文件存储目录,存储 trace 文件时使用
  • 是否为 debug 环境,打印日志时使用

在需要记录 trace 日志和关闭 trace 日志的地方进行记录:

代码语言:javascript
复制
Lancer.getInstance().start()


Lancer.getInstance().stop()

示例 demo 中演示的是从 application 的 attachBaseContext 就开启 start 日志记录,对于想记录业务模块的话,可自由搭配记录位置。

运行 demo 时,需要在 local.properties 下配置下 ndk 路径,如:

ndk.dir=/Users/mac/Library/Android/sdk/ndk/25.1.8937393

release 包效果:

  • 打出 release 包,install 安装,并启动应用
  • 结束应用,导出存储在 sd 卡外置存储目录的 trace 文件

perfetto 效果:

  • 打开 perfetto ui [1]网址,将导出的 trace 文件直接拖进即可查看

参考资料

[1]

perfetto ui : https://ui.perfetto.dev/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-09-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 扣浪 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 缘起
  • 能学到什么?
  • 开发的过程中遇到了哪些问题?
  • 还有哪些不足
  • 使用
  • 参考资料
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档