docs: refine

This commit is contained in:
LiuBinPeng
2022-04-19 17:15:49 +08:00
parent e2871d54e4
commit 7241220c8e
92 changed files with 10837 additions and 1963 deletions

View File

@@ -58,7 +58,7 @@ _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(p_di
![卡顿原因](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-02-04-ios_frame_drop.png)
VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 AppApp 主线程开始在 CPU 中计算显示内容(视图创建、布局计算、图片解码、文本绘制等)。然后将计算的内容提交到 GPUGPU 经过图层的变换、合成、渲染,随后 GPU 把渲染结果提交到帧缓冲区,等待下一次 VSync 信号到来再显示之前渲染好的结果。在垂直同步机制的情况下,如果在一个 VSync 时间周期内CPU 或者 GPU 没有完成内容的提交,就会造成该帧的丢弃,等待下一次机会再显示,这时候屏幕上还是之前渲染的图像,所以这就是 CPU、GPU 层面界面卡顿的原因。
VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 AppApp 主线程开始在 CPU 中计算显示内容(视图创建、布局计算、图片解码、文本绘制等)。然后将计算的内容提交到 GPUGPU 经过图层的变换、合成、渲染,随后 GPU 把渲染结果提交到帧缓冲区,等待下一次 VSync 信号到来再显示之前渲染好的结果。在垂直同步机制的情况下,如果在一个 VSync 时间周期内CPU 或者 GPU 没有完成内容的提交,就会造成该帧的丢弃(也叫掉帧),等待下一次机会再显示,这时候屏幕上还是之前渲染的图像,所以这就是 CPU、GPU 层面界面卡顿的原因。
目前 iOS 设备有双缓存机制也有三缓冲机制Android 现在主流是三缓冲机制,在早期是单缓冲机制。
[iOS 三缓冲机制例子](https://ios.developreference.com/article/12261072/Metal+newBufferWithBytes+usage)
@@ -553,8 +553,6 @@ curNode.costTime > (parentNode.costTime * 1.1/n) && curNode.costTime > parentNod
这样情况下,业务方可以针对特定的业务流程做性能统计分析
## 二、 App 启动时间监控
### 1. App 启动时间的监控
@@ -773,6 +771,8 @@ for (int i = 0; i < threadCount; i++) {
## 四、 OOM 问题
大多数情况下 OOM 问题比 crash 问题更严重,线上稳定性问题主要是由于 OOM 造成的,因为线下可以利用 Xcode 的一些工具解决并定位 crash。线上也有类似 KSCrash 这样的优秀监控工具。但是 OOM 方面线下只有一些三方的工具,这些工具大多是基于 OC 对象引用关心实现的,所以只能判断 OC 对象。另外比较耗费性能,所以线上 OOM 问题还是比较多的。
### 1. 基础知识准备
硬盘:也叫做磁盘,用于存储数据。你存储的歌曲、图片、视频都是在硬盘里。
@@ -1624,6 +1624,51 @@ for 循环打印出每个进程(也就是 App的 pid、Priority、User Data
### 4. 如何判定发生了 OOM
首先我们无法通过正常手段监控 OOM因为 XNU 源码显示发生 OOM 发送的信号为 SIGKILL该信号无法监控。
```c
/*
* The jetsam no frills kill call
* Return: 0 on success
* error code on failure (EINVAL...)
*/
static int jetsam_do_kill(proc_t p, int jetsam_flags, os_reason_t jetsam_reason) {
int error = 0;
error = exit_with_reason(p, W_EXITCODE(0, SIGKILL), (int *)NULL, FALSE, FALSE, jetsam_flags, jetsam_reason);
return error;
}
```
FacekBook 提出排除法监控 OOM。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/Facebook-OOM.jpeg)
- App 更新了版本
- App 发生了崩溃
- 用户手动退出
- 操作系统更新了版本
- App 切换到后台之后进程终止
其实不够全,存在误判,增加下面几种 case
- 覆盖安装
- WatchDog 崩溃
- 后台启动
- XCTest/UITest 等自动化测试框架驱动
- 应用 exit 主动退出
OOM 导致 crash 前app 一定会收到低内存警告吗?
做 2 组对比实验:
@@ -2793,49 +2838,47 @@ CFNetwork 使用 CFReadStreamRef 来传递数据,使用回调函数的形式
}
@end
```
@implementation NetworkDelegateProxy
#pragma mark - life cycle
+ (instancetype)sharedInstance {
static NetworkDelegateProxy *_sharedInstance = nil;
```
- (instancetype)sharedInstance {
static NetworkDelegateProxy *_sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [NetworkDelegateProxy alloc];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [NetworkDelegateProxy alloc];
});
return _sharedInstance;
}
#pragma mark - public Method
+ (instancetype)setProxyForObject:(id)originalTarget withNewDelegate:(id)newDelegate
{
NetworkDelegateProxy *instance = [NetworkDelegateProxy sharedInstance];
instance->_originalTarget = originalTarget;
instance->_NewDelegate = newDelegate;
return instance;
- (instancetype)setProxyForObject:(id)originalTarget withNewDelegate:(id)newDelegate {
NetworkDelegateProxy *instance = [NetworkDelegateProxy sharedInstance];
instance->_originalTarget = originalTarget;
instance->_NewDelegate = newDelegate;
return instance;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
if ([_originalTarget respondsToSelector:invocation.selector]) {
- (void)forwardInvocation:(NSInvocation *)invocation {
if ([_originalTarget respondsToSelector:invocation.selector]) {
[invocation invokeWithTarget:_originalTarget];
[((NSURLSessionAndConnectionImplementor *)_NewDelegate) invoke:invocation];
[((NSURLSessionAndConnectionImplementor *)_NewDelegate) invoke:invocation];
}
}
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [_originalTarget methodSignatureForSelector:sel];
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [_originalTarget methodSignatureForSelector:sel];
}
@end
```
@@ -6139,7 +6182,7 @@ file_names[ 4]:
<起始地址> <结束地址> <函数> [<文件名:行号>]
```
#### 4.4 **如何获取地址**
#### 4.4 如何获取地址
image 加载的时候会进行相对基地址进行重定位,并且每次加载的基地址都不一样,函数栈 frame 的地址是重定位后的绝对地址,我们要的是重定位前的相对地址。
@@ -6358,21 +6401,21 @@ Crash log 统一入库 Kibana 时是没有符号化的,所以需要符号化
Weex 由于历史原因,不做性能监控了,现有业务代码继续跑着,只业务问题和稳定性问题。
## Weex 异常监控
### Weex 异常监控
### 背景
#### 背景
Weex 由于历史原因,不能很好的统计异常。比如在页面的模版代码中绑定了 data 中的一个对象,此时对象可能并没有值,而是依赖后续的网络请求完成,对象才有了具体的值 data 改变,数据驱动,页面再次 render。所以监控代码会认为第一次 render 的时候访问对象不存在的属性。
真正有问题的代码和不影响业务的异常信息,都会被 Vue 官方认为是一场。基于这样的背景,我们无法 pick 出真正异常或者是开发者判空代码没写好的问题。
### 解决思路
#### 解决思路
按照异常的等级,可以划分为影响业务和不影响业务。问题来了,什么叫做“影响业务”?这是我们自己定义的词,也就是影响用户是否正常操作 App。比如说页面白屏、点击某个按钮无响应等等。定义为 Error。其他不影响业务的定义为 Warning。
### 技术实现
#### 技术实现
#### Warning 异常
##### Warning 异常
采用主流方案React、Vue 都提供了框架自己的异常监控方案,由于 Weex 是在 Vue 基础上实现。包括 Native 和 Vue 的 UI 双线程机制、事件机制等等。其余我们不关心Weex 开发中,开发和都是写 Vue 去实现页面和逻辑,所以我们监控 Vue 的异常就满足了。
@@ -6417,7 +6460,7 @@ Weex 由于历史原因,不能很好的统计异常。比如在页面的模版
componentName = this.fetchComponentName(vm)
componentPath = this.fetchComponentPath(vm)
}
let errorInfo = {
name: err.name,
reason: err.message,
@@ -6442,13 +6485,13 @@ export default APM;
由于 Weex 代码是单独可运行和部署的,因此前端没有统一的入口,所以在发布阶段监控代码需要配合打包机,利用脚本动态插入到页面代码中。
#### Error 监控
##### Error 监控
根据我们对“影响业务”的定义Error 包括:页面白屏 + 事件无法响应。
所以我们来拆解下问题。
##### 页面白屏
###### 页面白屏
根据 Weex SDK 整个完整流程得出,白屏包括以下几个可能:
@@ -6465,14 +6508,13 @@ export default APM;
return;
}
```
- Weex 资源网络请求成功,但是数据内容为空
```objectivec
_mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
// ...
if (!data) {
// Weex 资源网络请求成功,但是数据内容为空。也就是下载下来的资源无法消费,页面无法正常渲染
NSString *errorMessage = [NSString stringWithFormat:@"Request to %@ With no data return", WeexSafeString(request.URL)];
@@ -6484,14 +6526,13 @@ export default APM;
}
}
```
- Weex 资源网络请求成功,但是数据 JSON 序列化失败
```objectivec
_mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
// ...
NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (!jsBundleString) {
// Weex 资源网络请求成功,但是网络数据 JSON 序列化失败,也就是下载下来的资源无法消费,页面无法正常渲染
@@ -6503,8 +6544,7 @@ export default APM;
}
}
```
- Weex 资源网络请求异常(网络请求 _mainBundleLoader.onFailed
```objectivec
@@ -6518,10 +6558,8 @@ export default APM;
// ...
};
```
##### 点击事件无响应
###### 点击事件无响应
调试 WeexSDK 发现 SDK 内部通过给 JSContext 注册了 `callNativeModule` 方法来实现调用 Native Module 的 Method。
@@ -6555,7 +6593,7 @@ export default APM;
};
[[WeexAPM sharedInstance] reportError:errorDict];
} @finally {
}
}
}
@@ -6604,7 +6642,7 @@ export default APM;
}
```
#### JS ExceptionHandler
##### JS ExceptionHandler
JS 引擎中异常回调中上报错误
@@ -6636,6 +6674,7 @@ typedef NS_ENUM(NSUInteger, WeexAPMType) {
统计线上数据发现Weex 页面渲染失败率为4.09%,所以看上去页面渲染失败率还是蛮高的。目前发现失败率最高的场景为 App 启动时候按照功能模块组织的配置清单。
类似于下面的配置:
```
"//goods/detail": {
"configParams": "",
@@ -6648,18 +6687,15 @@ typedef NS_ENUM(NSUInteger, WeexAPMType) {
因此,针对于这样的场景。我们希望针对 Weex 资源的拉取和访问机制做一些优化。
目前有几个流程不太合理:
1. 当前启动时的js 预载入流程里的JS下载失败 和 进入Weex页面后的JS拉取失败 两处的上报失败未做区分没法实际统计加载页面时有多少JS获取失败的情况。
2. 随着业务迭代, Weex 页面越来越多,需要做 JS 拉取相关优化,避免白屏问题
3. 目前 JS 缓存逻辑为,每个 Weex 模块单独维护一个 LRUCache并且会做大量的预加载但可能大多数页面根本不会访问到。浪费了内存资源去做了一些不会被调用到的页面预加载可能还会造成 OOM 问题
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WeexResourcePull.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WeexResourcePull.png)
// todo? 参考前端资源模块化,如何实现资源预加载(全局 LRU或者基于用户行为路径的预热功能比如某个收银员角色固定的情况下他日常的操作行为是固定的商品扫码、开单
### 2. Flutter 异常监控
#### 2. Flutter 异常监控
## 九、子线程 UI 监控
@@ -6697,8 +6733,6 @@ typedef NS_ENUM(NSUInteger, WeexAPMType) {
具体可以参考这个 [Demo](https://github.com/FantasticLBP/MainThreadChecker)
## 十、页面渲染时长统计
当我们的产品经理、TL、领导或者任何关心我们产品质量的某个角色问你你们的 App 看上去好像比较卡,很难用,这时候我们心里一阵空虚,卡吗?
@@ -6747,10 +6781,8 @@ typedef NS_ENUM(NSUInteger, WeexAPMType) {
另外随着问题的暴露或者技术研究的渗入,监控方案本身是可以迭代演进的。
1. 8060 会有特例比如8060满足了且此时主线程空了。因为某个 ImageView 在子线程上根据 URL 异步请求资源,之后会再次触发渲染。所以这个情况下,方案还是存在问题的
## 十一、 APM 小结
1. 通常来说各个端的监控能力是不太一致的,技术实现细节也不统一。所以在技术方案评审的时候需要将监控能力对齐统一。每个能力在各个端的数据字段必须对齐(字段个数、名称、数据类型和精度),因为 APM 本身是一个闭环,监控了之后需符号化解析、数据整理,进行产品化开发、最后需要监控大盘展示等