一、了解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_threadstarget_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 结构体。