By
被删
更新日期:
我们在上一篇《前端性能优化–卡顿心跳检测》一文中介绍过基于requestAnimationFrame
的卡顿的检测方案实现,这一篇文章我们将会介绍基于该心跳检测方案,要怎么实现链路追踪,来找到产生卡顿的地方。
卡顿监控实现
上一篇我们提到的心跳检测,实现的功能很简单,就是卡顿和心跳事件、开始和停止,那么我们卡顿监控使用的时候也比较简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class JankMonitor { private heartBeatMonitor: HeartbeatMonitor;
constructor() { this.heartBeatMonitor = new HeartbeatMonitor(); this.heartBeatMonitor.addEventListener("jank", this.handleJank); this.heartBeatMonitor.addEventListener("heartbeat", this.handleHeartBeat);
this.heartBeatMonitor.start(); }
private handleJank() {}
private handleHeartBeat() {} }
|
这时候可以检测到卡顿了,接下来便是在卡顿发生的时候找到问题并上报了。前面《前端性能优化–卡顿的监控和定位》中有大致介绍堆栈的方法,这里我们来介绍下具体要怎么实现吧~
堆栈追踪卡顿
同样的,假设我们通过打堆栈的方式来追踪,堆栈信息包括:
1 2 3 4 5
| interface IJankLog { module: string; action: string; logTime: number; }
|
那么,我们的卡顿检测还需要对外提供log
打堆栈的能力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class JankMonitor { private jankLogStack: IJankLog[] = [];
log(logPosition: { module: string; action: string }) { this.jankLogStack.push({ ...logPosition, logTime: Date.now(), }); }
private handleHeartBeat() { this.jankLogStack = [];
this.jankLogStack.push({ module: "jank", action: "heartbeat", logTime: Date.now(), }); }
}
|
当卡顿发生时,我们可以根据堆栈计算出卡顿产生的位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class JankMonitor { private jankLogStack: IJankLog[] = [];
private handleJank() { const jankPosition = this.calculateJankPosition(); reportJank(jankPosition); console.error("产生了卡顿,位置信息为:", jankPosition);
this.jankLogStack = []; }
}
|
下面我们来详细看一下,要怎么计算出卡顿产生的位置。
卡顿位置定位
我们在代码中,使用log
方法来打关键链路日志,那么我们拿到的堆栈信息大概会长这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| jankLogStack = [ { module: "数据模块", action: "拉取数据", logTime: logTime1, }, { module: "数据模块", action: "加载数据", logTime: logTime2, }, { module: "Feature 模块", action: "处理数据", logTime: logTime3, }, { module: "渲染模块", action: "渲染数据", logTime: logTime4, }, ];
|
当卡顿发生的时候,我们可以将堆栈取出来计算最大耗时的位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class JankMonitor { private jankLogStack: IJankLog[] = [];
private calculateJankPosition() { let jankPosition; let maxCostTime = 0;
for (let i = 1; i < this.jankLogStack.length; i++) { const previousPosition = this.jankLogStack[i - 1]; const currentPosition = this.jankLogStack[i]; const costTime = currentPosition.logTime - previousPosition.logTime;
console.log( `${previousPosition.module}-${previousPosition.action} -> ${currentPosition.module}-${currentPosition.action}, 耗时 ${costTime} ms` );
if (costTime > maxCostTime) { maxCostTime = costTime; jankPosition = { ...currentPosition, costTime, }; } }
return jankPosition; }
}
|
这样我们就可以计算出产生卡顿时,代码执行的整个链路(需要使用log
记录堆栈),同时可找到耗时最大的位置并进行上报。当然,有时候卡顿产生并不只是一个地方,这里也可以调整为将执行超过一定时间的链路全部进行上报。
现在,我们可以拿到产生卡顿的有效位置,当然前提是需要使用log
方法记录关键的链路信息。为了方便,我们可以将其做成一个装饰器来使用。
@jankTrace 装饰器
该装饰器功能很简单,就是调用JankMonitor.log
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
export const JankTrace: MethodDecorator | PropertyDecorator = ( target, propertyKey, descriptor ) => { const className = target.constructor.name; const methodName = propertyKey.toString(); const isProperty = !descriptor; const originalMethod = isProperty ? (target as any)[propertyKey] : descriptor.value; if (typeof originalMethod !== "function") { throw new Error("JankTrace decorator can only be applied to methods"); }
const newFunction = function (...args: any[]) { jankMonitor.log({ moduleValue: className, actionValue: methodName, }); const syncResult = originalMethod.apply(this, args); return syncResult; };
if (isProperty) { (target as any)[propertyKey] = newFunction; } else { descriptor!.value = newFunction as any; } };
|
至此,我们可以直接在一些类方法上去添加装饰器,来实现自动跟踪卡顿链路:
1 2 3 4 5 6 7
| class DataLoader { @JankLog getData() {}
@JankLog loadData() {} }
|
结束语
本文简单介绍了卡顿检测的一个实现思路,实际上在项目中还有很多其他问题需要考虑,比如需要设置堆栈上限、状态管理等等。
技术方案在项目中落地时,都需要因地制宜做些调整,来更好地适配自己的项目滴~
查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢
如果你想要关注日常生活中的我,欢迎关注“牧羊的猪”公众号噢