-
背景
近期有例上报框架导致的视频号卡顿问题,本篇文章记录针对该类反馈强化监控的方法。
反馈如上,即:在视频号单流,刚开始刷视频号没问题,越刷越卡。
检测出的fps有什么特征?
和常规卡顿不同,该bug导致的fps特征曲线是:刷了足够多的feed后,才开始出现卡顿,所以收集到的fps曲线走向是:
之前fps检测之后数据是按取平均的方式处理的,所以这种前大段正常,后面掉帧的情况,会被取平均掩盖低fps的情况。
当采集100条feed fps信息求均值后,后期数据比较差的fps就被平均了,导致整个fps数据波动看起来不明显。
卡顿指标的进一步优化
我们知道,卡顿从分类上大致可以分为三种类型:
现有 fps报警
计算场景值主要覆盖了类型1
,所以需要指标对类型2
和类型3
也进行监测。
这里我们引入Google提出的Jank指标,这个指标已被多方验证有效,关于Jank指标的描述可以移步我之前写的文章:FPS与Jank卡顿指标。
简单来说,可以这么一句话描述Jank指标:
1
| 如果连续N (N>=3?)帧帧间隔 > 40ms(对应25fps)
|
但只要使用过Jank卡顿
指标的同学就会知道,Jank卡顿
出现的情况非常频繁,有一些系统接口的回调也可能会导致出现Jank卡顿
,以至于后果就变成Jank卡顿一直在上报,但开发人员并不关心。
为了避免这种情况,我们对Jank指标进行分级处理 :
- 一级Jank告警:N = 9
- 二级Jank告警:N = 6
- 三级Jank告警:N = 3
逻辑实现
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| @interface WCFinderCommonFPSMonitor () @property (nonatomic, strong) CADisplayLink *link; @property (nonatomic, assign) NSUInteger count; @property (nonatomic, assign) NSTimeInterval lastTime; @property (nonatomic, assign) NSTimeInterval lastFrameTime;
@property (nonatomic, assign) CGFloat duration; @property (nonatomic, strong) NSMutableDictionary<NSString *, YYWeakProxy *> *tickObservers;
@end
@implementation WCFinderCommonFPSMonitor
- (instancetype)init { if (self = [super init]) { _tickObservers = [NSMutableDictionary dictionary]; _duration = 1; } return self; }
- (void)dealloc { [_link invalidate]; }
- (NSMutableArray<NSString *> *)fpsInfoArray { if (!_fpsInfoArray) { _fpsInfoArray = [NSMutableArray array]; } return _fpsInfoArray; }
- (NSMutableArray<NSString *> *)dropFrameInfoArray { if (!_dropFrameInfoArray) { _dropFrameInfoArray = [NSMutableArray array]; } return _dropFrameInfoArray; }
- (void)start { [self stop]; _link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)]; [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; }
- (void)startWithDuration:(CGFloat)duration { [self start]; self.duration = duration; }
- (void)stop { [_link invalidate]; _lastTime = 0; _lastFrameTime = 0; _count = 0; }
- (void)addObserver:(id<WCFinderCommonFPSMonitorDelegate>)observer { NSString *identifier = observer.observerIdentifier; if (!identifier.length || [_tickObservers objectForKey:identifier] != nil) { return; } [_tickObservers safeSetObject:[YYWeakProxy proxyWithTarget:observer] forKey:identifier]; }
- (void)tick:(CADisplayLink *)link { if (_lastTime == 0) { _lastTime = link.timestamp; return; } _count++; NSTimeInterval delta = link.timestamp - _lastTime; NSTimeInterval frameInterval = link.timestamp - _lastFrameTime;
// 掉帧统计 NSInteger limitFrame = 25; // fps低于25计为掉帧 CGFloat limitDuration = 1.0 / limitFrame; WCFinderDebug(@"finder fps frameInterval:%f", frameInterval); if (frameInterval > limitDuration) { NSMutableString *content = [NSMutableString string]; [content safeAppendString:[NSString stringWithFormat:@"%lu", (unsigned long)_count]]; [content safeAppendString:@";"]; [content safeAppendString:[NSString stringWithFormat:@"%u", (unsigned int)[CUtility genServerCurrentTime]]]; [self.dropFrameInfoArray safeAddObject:content]; } _lastFrameTime = link.timestamp;
// fps计算 if (delta < self.duration) { return; }
_lastTime = link.timestamp; float fps = _count / delta; _count = 0; self.curFps = fps;
NSMutableString *content = [NSMutableString string]; [content safeAppendString:[NSString stringWithFormat:@"%f", fps]]; [content safeAppendString:@";"]; [content safeAppendString:[NSString stringWithFormat:@"%u", (unsigned int)[CUtility genServerCurrentTime]]]; [self.fpsInfoArray safeAddObject:content];
[_tickObservers.allValues enumerateObjectsUsingBlock:^(YYWeakProxy *proxy, NSUInteger idx, BOOL *stop) { [proxy.target onTick:fps]; }]; }
|