性能 Trace
在 Android 和 iOS 中,用嵌套 Span 和属性记录应用里的任意操作。
Trace 用来展示一个操作耗时多久,以及时间具体花在了哪些步骤上。
一个 Span 表示一段独立工作的耗时,例如 HTTP 请求、数据库查询或函数调用。一个 Trace 会把相关 Span 组织成一次完整操作,小到应用启动,大到完整的新用户引导流程都可以记录。Span 可以在 Trace 中形成父子层级,用来展示工作的结构。
本页介绍常见用法。完整 API 请查看记录性能 Trace。
记录操作耗时
用一个 Span 包住要计时的操作:工作开始时启动 Span,工作完成时结束 Span。例如,可以记录 Feed 刷新、图片解码或数据库查询的耗时。结束 Span 前,可以设置 status 来标记工作成功(Ok)或失败(Error)。
import sh.measure.android.Measure
import sh.measure.android.tracing.SpanStatus
val span = Measure.startSpan("refresh-feed")
val refreshed = feedRepository.refresh()
span.setStatus(if (refreshed) SpanStatus.Ok else SpanStatus.Error).end()import Measure
let span = Measure.startSpan(name: "refresh-feed")
let refreshed = await feedRepository.refresh()
span.setStatus(refreshed ? .ok : .error).end()将流程分解为步骤
单个 Span 会记录整个流程的耗时。很多时候你还需要继续下钻,找出到底是哪个步骤拖慢了流程。可以把一个 Span 设置为另一个 Span 的 parent 来形成嵌套关系。例如,结账流程可以有一个覆盖全流程的父 Span,再为“校验购物车”“处理支付”等步骤分别创建子 Span。
import sh.measure.android.Measure
val checkout = Measure.startSpan("checkout")
val validate = Measure.startSpan("validate-cart").setParent(checkout)
cartService.validate(cart)
validate.end()
val payment = Measure.startSpan("process-payment").setParent(checkout)
paymentGateway.charge(cart)
payment.end()
checkout.end()import Measure
let checkout = Measure.startSpan(name: "checkout")
let validate = Measure.startSpan(name: "validate-cart").setParent(checkout)
await cartService.validate(cart)
validate.end()
let payment = Measure.startSpan(name: "process-payment").setParent(checkout)
await paymentGateway.charge(cart)
payment.end()
checkout.end()添加带有属性的上下文
给 Span 添加属性,可以在耗时之外记录更多上下文。例如在 Feed 加载的 Span 中,属性可以是屏幕名称、Feed 中的条目数量,或数据是否来自缓存。允许的 key 和 value 请查看属性限制。
import sh.measure.android.Measure
val span = Measure.startSpan("load-feed")
val items = feedApi.fetchFeed()
span.setAttribute("screen", "Home")
span.setAttribute("item_count", items.size)
span.end()import Measure
let span = Measure.startSpan(name: "load-feed")
let items = await feedApi.fetchFeed()
span.setAttribute("screen", value: "Home")
span.setAttribute("item_count", value: items.count)
span.end()标记检查点
在 Span 内标记 checkpoint,用来记录关键时刻。比如在屏幕加载的 Span 中,可以在网络请求返回、列表渲染完成时分别设置 checkpoint。这样可以看出加载过程的哪一段更慢,而不需要为每个阶段单独创建子 Span。
import sh.measure.android.Measure
val span = Measure.startSpan("load-screen")
val products = productApi.fetchProducts()
span.setCheckpoint("network_done")
renderList(products)
span.setCheckpoint("list_rendered")
span.end()import Measure
let span = Measure.startSpan(name: "load-screen")
let products = await productApi.fetchProducts()
span.setCheckpoint("network_done")
renderList(products)
span.setCheckpoint("list_rendered")
span.end()延后或回填 Span
有些 Span 对应的操作在你真正方便创建 Span 之前就已经开始了。比如冷启动从进程启动时就开始计时,但那时 SDK 可能还没有初始化。遇到这种情况,可以创建一个带历史时间戳的 Span。
import sh.measure.android.Measure
import sh.measure.android.tracing.SpanStatus
// in Application.onCreate
val launchStart = Measure.getCurrentTime()
// when the first screen is drawn
val span = Measure.startSpan("cold-launch", timestamp = launchStart)
span.setStatus(SpanStatus.Ok).end()import Measure
// in application(_:didFinishLaunchingWithOptions:)
let launchStart = Measure.getCurrentTime()
// when the first screen appears
let span = Measure.startSpan(name: "cold-launch", timestamp: launchStart)
span.setStatus(.ok).end()如果想先配置 Span,稍后再在正确的时刻启动它,可以提前通过 createSpanBuilder 创建 builder。
import sh.measure.android.Measure
val builder = Measure.createSpanBuilder("checkout")
// later, when the work begins
val span = builder?.startSpan()import Measure
let builder = Measure.createSpanBuilder(name: "checkout")
// later, when the work begins
let span = builder?.startSpan()控制采样率
默认情况下,Measure 会上报每一个 Trace。要减少采集量,可以在 Dashboard 中调整 Trace 采样率。