一、启动时间规划
启动时间包括两部分:Launch Time = Pre-main Time + Loading Time
-
Pre-main Time 指 main 函数执行之前的加载时间,包括 dylib 动态库加载,Mach-O 文件加载,Rebase/Binding,Objective-C Runtime 加载等;
-
Loading Time 指 main 函数开始执行到 AppDelegate 的 applicationDidBecomeActive: 回调方法执行(App 被激活)的时间间隔,这个时间包含了的 App 启动时各初始化项的执行时间(一般写在 application:didFinishLaunchingWithOptions: 方法里)
1.冷启动统计方案:
t1(创建进程 - main) + t2(main - didFinishLaunching) + t3(自定义时间)
SDK的设计上,进程创建时间戳的获取:
@implementation LTLoadTimeManager
+ (NSTimeInterval)processStartTime {
struct kinfo_proc kProcInfo;
if ([self processInfoForPID:[[NSProcessInfo processInfo] processIdentifier] procInfo:&kProcInfo]) {
return kProcInfo.kp_proc.p_un.__p_starttime.tv_sec * 1000.0 + kProcInfo.kp_proc.p_un.__p_starttime.tv_usec / 1000.0;
} else {
NSAssert(NO, @"无法取得进程的信息");
return 0;
}
}
+ (BOOL)processInfoForPID:(int)pid procInfo:(struct kinfo_proc*)procInfo {
int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
size_t size = sizeof(*procInfo);
return sysctl(cmd, sizeof(cmd)/sizeof(*cmd), procInfo, &size, NULL, 0) == 0;
}
@end
didFinishLaunching时间戳获取可以做到业务无感,常规获取方案如下:
+ (void)load {
NSLog(@"时间(load 执行时间)%f",[[NSDate date] timeIntervalSince1970] * 1000);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
__block id<NSObject> obs;
obs = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
NSTimeInterval didFinishLaunching = [[NSDate date] timeIntervalSince1970] * 1000;//毫秒
NSTimeInterval app_start_time = [LTLoadTimeManager processStartTime];
NSLog(@"时间(创建进程 毫秒)%f",app_start_time);
NSLog(@"时间(main start毫秒)%f",main_start_time);
NSLog(@"时间(didFinishLaunching 毫秒):%f",didFinishLaunching);
NSLog(@"时间间隔(创建进程->main)%f",main_start_time - app_start_time);
NSLog(@"时间间隔(main->didFinishLaunching)%f",didFinishLaunching - main_start_time);
NSLog(@"时间间隔(创建进程->didFinishLaunching 毫秒):%f",didFinishLaunching - app_start_time);
///MARK:记录冷启动时间
k_lucky_cold_start_time_interval = didFinishLaunching - app_start_time;
//移除观察者
[[NSNotificationCenter defaultCenter] removeObserver:obs];
}];
[self recordHotStart];///MARK:记录热启动
}
});
}
以上 k_lucky_cold_start_time_interval 即为创建进程到didFinishLaunching的时长 即t1 + t2
对于t3的定义,我们决定暴露给外部。你可以在首页加载完成时调用,也可以在首页网络请求完成刷新UI调用,这都取决于你的选择:
///MARK:自定义启动结束节点
+ (NSTimeInterval)recordCustomLuanchedTime {
NSTimeInterval user_custom_time = [[NSDate date] timeIntervalSince1970] * 1000;//毫秒
NSTimeInterval start_time = [LTLoadTimeManager __processStartTime];
k_lucky_cold_start_time_interval = user_custom_time - start_time;
return k_lucky_cold_start_time_interval;
}
2.热启动统计方案:
从applicationWillEnterForeground 到 applicationDidBecomeActive之间的时间
///MARK:热启动
+ (void)recordHotStart {
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
k_lucky_hot_begin_time = [[NSDate date] timeIntervalSince1970] * 1000;
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
k_lucky_hot_end_time = [[NSDate date] timeIntervalSince1970] * 1000;
if (k_lucky_hot_begin_time > 0) {
k_lucky_hot_start_time_interval = k_lucky_hot_end_time - k_lucky_hot_begin_time;
NSLog(@"热启动时长:%f 毫秒",k_lucky_hot_start_time_interval);
}
}];
}
以上k_lucky_hot_end_time即为热启动时长
二.页面加载时间:
页面加载时长,这里采取的方案是viewWillAppear - viewDidAppear的时长。可以在viewWillappear方法和viweDidAppear中获取对应的时间戳计算时长。也可以通过hook UIViewController做到无痕获取。方法比较简单,具体代码就不贴出来了。
三.CPU使用率:
与 Mac OS X 类似,iOS 的线程技术也是基于 Mach 线程技术实现的,在 Mach 层中 thread_basic_info 结构体提供了线程的基本信息。
struct thread_basic_info {
time_value_t user_time; /* user run time */
time_value_t system_time; /* system run time */
integer_t cpu_usage; /* scaled cpu usage percentage */
policy_t policy; /* scheduling policy in effect */
integer_t run_state; /* run state (see below) */
integer_t flags; /* various flags (see below) */
integer_t suspend_count; /* suspend count for thread */
integer_t sleep_time; /* number of seconds that thread
has been sleeping */
};
一个 task 包含它的线程列表。内核提供了 task_threads API 调用获取指定 task 的线程列表,然后可以通过 thread_info API 调用来查询指定线程的信息,thread_info API 在 thread_act.h 中定义。
kern_return_t task_threads
(
task_t target_task,
thread_act_array_t *act_list,
mach_msg_type_number_t *act_listCnt
);
task_threads 将 target_task 任务中的所有线程保存在 act_list 数组中,数组中包含 act_listCnt 个条目。
kern_return_t thread_info
(
thread_act_t target_act,
thread_flavor_t flavor,
thread_info_t thread_info_out,
mach_msg_type_number_t *thread_info_outCnt
);
thread_info 查询 flavor 指定的 thread 信息,将信息返回到长度为 thread_info_outCnt 字节的 thread_info_out 缓存区中。
具体实现如下:
+ (double)getCpuUsage {
kern_return_t kr;
thread_array_t threadList; // 保存当前Mach task的线程列表
mach_msg_type_number_t threadCount; // 保存当前Mach task的线程个数
thread_info_data_t threadInfo; // 保存单个线程的信息列表
mach_msg_type_number_t threadInfoCount; // 保存当前线程的信息列表大小
thread_basic_info_t threadBasicInfo; // 线程的基本信息
// 通过“task_threads”API调用获取指定 task 的线程列表
// mach_task_self_,表示获取当前的 Mach task
kr = task_threads(mach_task_self(), &threadList, &threadCount);
if (kr != KERN_SUCCESS) {
return -1;
}
double cpuUsage = 0;
for (int i = 0; i < threadCount; i++) {
threadInfoCount = THREAD_INFO_MAX;
// 通过“thread_info”API调用来查询指定线程的信息
// flavor参数传的是THREAD_BASIC_INFO,使用这个类型会返回线程的基本信息,
// 定义在 thread_basic_info_t 结构体,包含了用户和系统的运行时间、运行状态和调度优先级等
kr = thread_info(threadList[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount);
if (kr != KERN_SUCCESS) {
return -1;
}
threadBasicInfo = (thread_basic_info_t)threadInfo;
if (!(threadBasicInfo->flags & TH_FLAGS_IDLE)) {
cpuUsage += threadBasicInfo->cpu_usage;
}
}
// 回收内存,防止内存泄漏
vm_deallocate(mach_task_self(), (vm_offset_t)threadList, threadCount * sizeof(thread_t));
return cpuUsage / (double)TH_USAGE_SCALE * 100.0;
}
其它性能数据待续...
网友评论