-
Notifications
You must be signed in to change notification settings - Fork 4
/
README.md
289 lines (229 loc) · 9.42 KB
/
README.md
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
Walle can monitor the core data of iOS application performance.
# Features
Real-time monitoring of the following data
- Memory
- FPS
- CPU
- launch time
- UIViewController appear time
- monitor main thread whether blocked
#Environment
IOS 8 or later , XCode 7 or later
#How To Use
```Objc
#import "WTPerformanceMonitor.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
[[WTPerformanceMonitor sharedInstance] startMonitorWithBar:YES];
}
```
#Installation
pod 'Walle'
---
# iOS 应用,性能监控
> 背景,目前正在优化项目,首先要对项目内的性能指标进行分析,这个可以通过Instrument 进行debug 分析。这样做只适用于开发人员。性能指标作为一项衡量App的重要指标无法量化。为了每次发布前能有一个性能报告,需要开发一个组件,对性能数据进行记录,之后通过脚本生成报表。
##报表中重点关注的指标有以下几点:
- 启动时间
- 内存
- FPS(页面刷新帧率)
- CPU
- 页面渲染时间
## debug模式
- 主线程阻塞时,输出MainThread 栈信息。
##分别介绍如何实现这些数据的采集。
### 内存
```Objc
vm_size_t usedMemory(void) {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
return (kerr == KERN_SUCCESS) ? info.resident_size : 0; // size in bytes
}
```
### FPS
- 使用CADisplayLink 进行获取
```Objc
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(envokeDisplayLink:)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
- (void)envokeDisplayLink:(CADisplayLink *)displayLink
{
if (_lastTime == 0) {
_lastTime = displayLink.timestamp;
return;
}
_count ++;
NSTimeInterval interval = displayLink.timestamp - _lastTime;
if (interval < 1) {
return;
}
_lastTime = displayLink.timestamp;
CGFloat fps = _count / interval;
_count = 0;
NSInteger shownFPS = round(fps);
CGFloat memory = [WTPerformanceUtility usedMemoryInMB];
CGFloat cpu = [WTPerformanceUtility cpuUsage];
DDLogInfo(@"FPS:%ld,MEM:%.2f,CPU:%.2f", (long)shownFPS, memory, cpu);
[self.performanceView setPerformanceViewData:cpu memory:memory FPS:shownFPS];
}
```
### CPU
```Objc
float cpu_usage()
{
kern_return_t kr;
task_info_data_t tinfo;
mach_msg_type_number_t task_info_count;
task_info_count = TASK_INFO_MAX;
kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);
if (kr != KERN_SUCCESS) {
return -1;
}
task_basic_info_t basic_info;
thread_array_t thread_list;
mach_msg_type_number_t thread_count;
thread_info_data_t thinfo;
mach_msg_type_number_t thread_info_count;
thread_basic_info_t basic_info_th;
uint32_t stat_thread = 0; // Mach threads
basic_info = (task_basic_info_t)tinfo;
// get threads in the task
kr = task_threads(mach_task_self(), &thread_list, &thread_count);
if (kr != KERN_SUCCESS) {
return -1;
}
if (thread_count > 0)
stat_thread += thread_count;
long tot_sec = 0;
long tot_usec = 0;
float tot_cpu = 0;
int j;
for (j = 0; j < thread_count; j++)
{
thread_info_count = THREAD_INFO_MAX;
kr = thread_info(thread_list[j], THREAD_BASIC_INFO,
(thread_info_t)thinfo, &thread_info_count);
if (kr != KERN_SUCCESS) {
return -1;
}
basic_info_th = (thread_basic_info_t)thinfo;
if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;
tot_usec = tot_usec + basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds;
tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;
}
} // for each thread
kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
assert(kr == KERN_SUCCESS);
return tot_cpu;
}
```
### 启动时间
```Objc
+ (void)load
{
loadTime = mach_absolute_time();
mach_timebase_info(&timebaseInfo);
@autoreleasepool {
__block id obs;
obs = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
object:nil queue:nil
usingBlock:^(NSNotification *note) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
applicationRespondedTime = mach_absolute_time();
DDLogInfo(@"VivaVedio_IOS_Start_Time: %.f", MachTimeToSeconds(applicationRespondedTime - loadTime));
});
[[NSNotificationCenter defaultCenter] removeObserver:obs];
}];
}
}
```
### 页面渲染耗时
利用runtime, 将UIViewController 的viewWillAppear, viewDidAppear 进行hook。输出调用的时间间隔。
```Objc
@interface UIViewController()
@property (nonatomic, assign) CFTimeInterval viewControllerAppearDuration;
@end
@implementation UIViewController (Performance)
+ (void)load{
[self walle_swizzlingViewWillAppear];
[self walle_swizzlingViewDidAppear];
}
+ (void)walle_swizzlingViewWillAppear
{
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(walle_viewWillAppear:);
[self swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
}
+ (void)walle_swizzlingViewDidAppear
{
SEL originalSelector = @selector(viewDidAppear:);
SEL swizzledSelector = @selector(walle_viewDidAppear:);
[self swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
}
- (void)walle_viewWillAppear:(BOOL)animated
{
self.viewControllerAppearDuration = CACurrentMediaTime();
[self walle_viewWillAppear:animated];
}
- (void)walle_viewDidAppear:(BOOL)animated
{
[self walle_viewDidAppear:animated];
self.viewControllerAppearDuration = CACurrentMediaTime() - self.viewControllerAppearDuration;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *name = NSStringFromClass(self.class);
DDLogInfo(@"View Controller :%@ show time : %g s", name, self.viewControllerAppearDuration);
});
}
+ (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector
{
Class clz = cls;
Method originalMethod = class_getInstanceMethod(clz, originalSelector);
Method swizzledMethod = class_getInstanceMethod(clz, swizzledSelector);
BOOL didAddMethod = class_addMethod(clz, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(clz, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- (void)setViewControllerAppearDuration:(CFTimeInterval)viewControllerAppearDuration
{
objc_setAssociatedObject(self, @selector(viewControllerAppearDuration), @(viewControllerAppearDuration), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (CFTimeInterval )viewControllerAppearDuration
{
return [objc_getAssociatedObject(self, @selector(viewControllerAppearDuration)) doubleValue];
}
```
## debug 主线程阻塞
- 通过监控Runloop的回调进行监控
```Objc
_observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
self->_activity = activity;
dispatch_semaphore_t semaphore = self->_semaphore;
dispatch_semaphore_signal(semaphore);
});
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
// 创建信号
_semaphore = dispatch_semaphore_create(0);
// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (YES)
{
// 超时250ms 认为卡顿
long st = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
if (st != 0)
{
if (_activity == kCFRunLoopBeforeSources || _activity == kCFRunLoopAfterWaiting)
{
if (++_countTime < 5)
continue;
NSString *track = [BSBacktraceLogger bs_backtraceOfMainThread];
NSLog(@"############### Main thread is blocked ###############");
NSLog(@"%@", track);
NSLog(@"############### Main thread is blocked ###############");
}
}
_countTime = 0;
}
});
```