一、了解CPU架构
市场上,我们比较熟悉的CPU架构有ARM(arm64
)和Intel(x86
)等等,目前,市场上大部分的iPhone都是基于arm64
架构。因为arm
架构有着功耗低的特点,因此广泛应用在移动设备领域。(intel虽然性能好,但功耗高。因此失去了移动端领域的市场份额。)。
CPU指令集架构:
>i386是针对intel通用微处理器32位处理器
>x86_64是针对x86架构的64位处理器
>模拟器32位处理器测试需要i386架构,
>模拟器64位处理器测试需要x86_64架构,
>真机32位处理器需要armv6、armv7或者armv7s架构,
>真机64位处理器需要arm64架构
二、iOS如何监控CPU功耗
- 首先,获取当前的任务
task
。从任务task
中获得当前所有存活的线程信息。 这时,我们就拿到了当前任务所有存活的 “线程信息”(threads) 和 “存活的线程个数”(threadCount) 。 - 然后,设置一个预定的
CPU
使用阈值。 遍历所有线程的信息,查看是否有线程的CPU使用率cpu_usage
“超过” 预定的阈值(例如CPU
使用率超过80%
)。 - 如果有线程的CPU使用率
cpu_usage
超过预定阈值,就 “存储” 当前线程的调用的堆栈信息。
三、具体实现
1、thread_basic_info 结构体的定义
struct thread_basic_info {
time_value_t user_time; // 用户运行时长
time_value_t system_time; // 系统运行时长
integer_t cpu_usage; // CPU 使用率
policy_t policy; // 调度策略
integer_t run_state; // 运行状态
integer_t flags; // 各种标记
integer_t suspend_count; // 暂停线程的计数
integer_t sleep_time; // 休眠时间
};
名称 | 介绍 |
user_time | 用户运行时间(精确到微妙)。 |
system_time | 系统运行时(精确到微妙)。 |
cpu_usage | cpu使用率(理论上限1000)。 |
policy | 调度策略。 |
run_state | 五种 “运行状态”: 1> running 运行中 2> stopped 已停止 3> waiting 等待中 4> uninterruptible 不可中断 5> halted 被阻塞 |
flags | 三种 “线程标志”: 1> swapped 换出 2> idle 空闲 3> global forced idle 全局强制空闲。 |
suspend_count | 线程已经被挂起的计数。 |
sleep_time | 线程已经挂起的时间(精确到秒)。 |
每个线程都有这个结构体,所以我们只需要定时去遍历每个线程,累加每个线程的 cpu_usage
字段的值,就可以得到当前 App 所在进程的 CPU 使用率。
CPU 占用率实现
- (CGFloat)cpuUsageForApp {
//引入的头文件
// #import <mach/mach.h>
kern_return_t kr;
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;
// 根据当前 task 获取所有线程 mach_task_self():表示获取当前的 Mach task
kr = task_threads(mach_task_self(), &thread_list, &thread_count);
//检查是否成功,KERN_SUCCESS = 0 代表成功,其他有对应的错误码有52种。
if (kr != KERN_SUCCESS)
return -1;
float total_cpu_usage = 0;
// 遍历当前任务内存活的所有线程
for (int i = 0; i < thread_count; i++) {
thread_info_count = THREAD_INFO_MAX;
// 获取每一个线程信息
kr = thread_info(thread_list[i], 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)) {
// cpu_usage : Scaled cpu usage percentage. The scale factor is TH_USAGE_SCALE.
// 宏定义 TH_USAGE_SCALE 返回 CPU 处理总频率:
total_cpu_usage += basic_info_th->cpu_usage / (float)TH_USAGE_SCALE;
}
}
// 注意方法最后要调用 vm_deallocate,防止出现内存泄漏
kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
assert(kr == KERN_SUCCESS);
return total_cpu_usage;
}
监控的同时,又不影响App性能,故这个判断用一个定时器,每3秒刷新一次即可。
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(cpuUsageForApp) userInfo:nil repeats:YES];
代码中使用 task_threads
API 调用获取指定的 task 的线程列表。task_threads
将 target_task
任务中的所有线程保存在 act_list
数组中,数组包含 act_listCnt
个条目。上述源码中,在调用 task_threads
API 时,target_task
参数传入的是 mach_task_self()
,表示获取当前的 Mach task。
在获取到线程列表后,代码中使用 thread_info
API 调用获取指定线程的线程信息。thread_info
查询 flavor
指定的线程信息,将信息返回到长度为 thread_info_outCnt
字节的 thread_info_out
缓存区中。上述源码,在调用 thread_info
API 时,flavor
参数传入的是 THREAD_BASIC_INFO
,使用这个类型会返回线程的基本信息,即 thread_basic_info_t
结构体。