资源采集API特性指导

0 评论 121 浏览 0 收藏 17 分钟

资源调优和资源泄漏是应用开发中常见且难以快速解决的两类棘手问题。在鸿蒙应用代码开发过程中,这些问题通常难以利用常规调试手段(如流水日志、/proc/[pid]/smaps、/proc/meminfo、/proc/memview、hidumper --mem)直接定位和处理。

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:

资源采集API特性指导-华为开发者话题 | 华为开发者联盟

1、概述

资源调优和资源泄漏是应用开发中常见且难以快速解决的两类棘手问题。在鸿蒙应用代码开发过程中,这些问题通常难以利用常规调试手段(如流水日志、/proc/[pid]/smaps、/proc/meminfo、/proc/memview、hidumper –mem)直接定位和处理。

表1 资源类型说明

资源类型 描述
文件描述符 通过open/fopen、epoll、eventfd、socket、pipe、dup等创建的fd。
线程 通过线程函数创建的线程(如pthread_create)。
Native

内存(包含堆内存和映射区内存)

通过malloc/calloc/realloc/new分配的堆内存(NativeHeap);

通过mmap映射的映射区内存(包含so等文件映射以及匿名映射)

GPU内存 通过图形标准API(如opengles、vlukan、opencl)创建的纹理(texture)、缓冲区(buffer)等内存。
全局句柄

(Global Handle)

在Native侧通过napi_create_reference创建的ArkTS对象全局引用(napi_ref)。

 为快速解决上述两类痛点问题,鸿蒙系统通过Performance Analysis Kit开放的HiDebug资源采集接口(OH_HiDebug_StartProfiler/ OH_HiDebug_StopProfiler),提供线上资源分配栈采集功能,赋能开发者高效实现问题的自诊断与自闭环。

该资源采集接口性能功耗开销较大,盲目或高频调用易触发应用卡顿、发热。集成时,请务必谨慎评估开销,建立严格的条件触发策略和配置合理的采集参数,兼顾线上功能正常运行和性能开销的平衡。

2、HiDebug资源分配栈采集接口介绍

 6.1版本,资源采集接口(API 24.0)原型如下:

// 资源类型
typedef enum OH_HiDebug_ResourceType {
  OH_RES_TYPE_FD,
  OH_RES_TYPE_THREAD,
  OH_RES_TYPE_NATIVE,
  OH_RES_TYPE_GPU,
  OH_RES_TYPE_GLOBAL_HANDLE
} OH_HiDebug_ResourceType;


// 采集参数
typedef struct OH_HiDebug_ResProfilerConfig {
  uint32_t maxDuration;
  uint32_t filterSize;
  uint32_t maxStackDepth;
  uint32_t statisticsInterval;
  uint32_t sampleInterval;
} OH_HiDebug_ResProfilerConfig;


// 采集结果
typedef struct OH_HiDebug_ProfilingResult {
  OH_HiDebug_ResourceType resourceType;
  const char* filePath;
} OH_HiDebug_ProfilingResult;



// 采集结果回调函数
typedef void (*OH_HiDebug_ProfilingCallback)(OH_HiDebug_ProfilingResult* result);
// 启动采集
HiDebug_ErrorCode OH_HiDebug_StartProfiler(
OH_HiDebug_ResourceType type,
OH_HiDebug_ResProfilerConfig* config,
OH_HiDebug_ProfilingCallback callback);
// 停止采集
HiDebug_ErrorCode OH_HiDebug_StopProfiler(void);

2.1 资源分配栈采集接口资源类型参数简介

type参数支持五类资源类型,包括:文件描述符(FD)、线程(Thread)、NativeHeap内存、GPU内存和全局句柄(Global Handle)。

2.2 资源分配栈采集接口采集参数简介

config采集参数支持五种配置项,包括:最大采集时间、过滤大小、最大栈深、统计间隔和采样大小。这些参数开发者可按需灵活配置(调整),实现资源分配栈采集的性能开销和应用体验之间的平衡。采样率参数仅针对Native内存生效。

表2-2-1 采集参数说明

采集参数 描述 说明
maxDuration 最大采集时间,单位:s。最大支持1小时。 采集参数可根据实际采集开销情况调整,达成性能损耗与应用体验的自适应平衡。

默认值仅供参考。

 

 

 

 

filterSize 过滤≤filter_size的内存分配栈,减少采集数据量。
maxStackDepth 用于设置最大资源分配调用栈回栈深度,建议默认值30
statisticsInterval 统计间隔,表示将一个统计周期内的栈进行汇总,单位:秒。建议默认值10s。
sampleInterval 采样大小,单位:字节。采样率=1/采样大小。建议默认值384 bytes。

注:内存分配384字节,按照1/384采样率采栈;384字节的,全采。

2.3 资源分配栈采集接口回调参数简介

callback回调参数包含资源分配栈.htrace文件沙箱路径(filePath),开发者可直接获取栈trace文件并上传云侧做泄漏根因分析、聚类。

表2-3-1 回调参数说明

回调参数 描述
resourceType 采集资源类型。
filePath 资源分配栈.htrace文件沙箱路径。

2.4 资源分配栈采集接口实现逻辑

本质是对不同资源的分配/释放函数跟踪,然后记录调用栈、去重、符号化,并最终以.htrace(protobuf格式)文件形式落盘至应用沙箱,供IDE加载、解析和查看(Call Trees),辅助资源调优和资源泄漏问题快速定位。

(1)文件描述符:hook文件描述符打开和关闭函数,并记录调用栈;

(2)线程:hook线程的创建和销毁函数,并记录调用栈;

(3)NativeHeap内存:hook C库(musl库) malloc/calloc/realloc/free、mmap/munmap函数,并记录调用栈。

(4)GPU内存:hook opengles、vlukan、opencl的纹理(texture)、缓冲区(buffer)创建和释放函数,并记录调用栈。

(5)Global Handle:hook global handle对象的创建和释放函数,并记录调用栈。

2.5 资源分配栈采集接口触发条件

表2-5-1 资源分配栈采集接口OH_HiDebug_StartProfiler触发条件

资源类型 触发采集条件

(推荐值)

检测间隔

推荐值

参考建议
文件描述符

FD

超过 5000 个 60s 1、开发者可通过读取/proc/self/fd_num节点获取被采集应用的FD总数。

2、1个检测周期内(每60s),若发现应用的FD总数超过阈值(5000个),则认为可能存在FD泄漏,此时可调用资源分配栈采集接口采栈。

线程 超过 700 个 60s 1、开发者可通过读取/proc/self/status节点的Threads字段获取被采集应用的线程总数。

2、1个检测周期内(每60s),发现线程总数超过700个,则认为可能存在线程泄漏,此时可调用资源分配栈采集接口采栈。

Native

内存(包含堆内存和映射区内存

超过 3 GB 200s 1、开发者可通过读取/proc/self/status节点,将VmRss+VmSwap字段之和,作为被采集应用占用的Native内存总量。

注:应用Native内存超4G且整机内存压力过大时可能触发系统管控,建议超3G或更小时就启动采集。

2、连续2个检测周期(每200s一个周期),Native内存均超过阈值(3GB),则认为可能存在Native内存泄漏,此时可调用资源分配栈采集接口采栈。

GPU内存 超过 2300 MB 60s 1、开发者可通过调用OH_HiDebug_GetGraphicsMemory获取被采集应用的GPU内存总量。

2、连续2个检测周期(每60s一个周期),GPU内存均超过阈值(12G手机:2.3GB),则认为可能存在GPU内存泄漏,此时可调用资源分配栈采集接口采栈。

全局句柄

Global Handle

VM 堆内存使用率超过 70% 60s 1、开发者可通过hidebug.getAppVMObjectUsedSize() /

hidebug.getAppVMMemoryInfo().totalHeap > 0.7作为触发采集条件。

2、1个检测周期内(每60s),VM堆内存使用率超过70%,则认为可能存在Global Handle泄漏,此时可调用资源分配栈采集接口采栈。

3、API资源采集规格约束

3.1 采集配额限制:

       • 整机(所有应用共享):每日最多可采集4次;同一时刻,最高支持4个不同应用并行采集。

       • 应用(单应用独享):每日最多可采集2次;同一时刻,最高支持应用内2个进程并行采集。

3.2 系统负载熔断机制:

当系统触发以下任一负载保护条件时,API采集请求将被拒绝,并返回相应错误码:

• CPU限制:整机系统CPU占用率超过70%;

• 内存与存储限制:整机系统剩余可用内存(RAM)或 剩余可用存储空间(ROM)低于影响应用体验和整机读写性能阈值。

负载保护条件未来可能会随系统版本迭代持续优化,具体以实际返回错误码为准。

3.3 并发冲突约束:

本API与命令行工具或系统采集任务存在排他性。当与命令行工具或系统采集任务的请求发生冲突时,API采集请求将被拒绝并返回相应错误码。

3.4 采集结果交付:

       • 存储路径:采集到的调用栈日志文件(.htrace)将自动保存在应用沙箱目中,路径为:/data/storage/el2/base/files/;

       • 日志文件名规则:“资源采集类型-进程名-进程号-时间戳.htrace”。

3.5 注意事项

       • 受上述采集规格与约束限制,本API不保证采集请求一定成功;

       • 调用本API会对当前应用进程及整机性能功耗产生不可忽略的影响(包括但不限于:CPU、内存占用升高,丢帧卡顿,发热)。开发者在集成时,必须谨慎评估其性能功耗开销,并建立严格的条件触发策略(如仅在特定灰度范围内、应用资源超基线阈值时触发,同时须配置合理的采集参数以兼顾性能功耗开销平衡),再决策是否开启。严禁在线上环境下盲目或高频次触发。

3.6 老化管控(每种资源类型均受以下条件约束):

       • 按文件个数管控:只保存最新5份文件(滚动老化)

       • 按文件大小管控:单文件上限1G采集达上限即停止,不做覆盖。避免单个文件太大撑爆磁盘且无法被IDE加载。

       • 按目录空间管控:沙箱下采集文件存储空间超过4G上限,删除最早的采集文件。

       • 按存储时效管控:单个日志存储最大时长24小时。24小时到期后清理超时的采集文件。

3.7 系统采集模式优先级规格:

       • 命令行模式hdc shell hiprofiler_cmd)优先,可抢占其它采集模式(API采集、系统采集、应用灰度采集);

       • API采集(OH_HiDebug_StartProfiler/StopProfiler)和 系统采集/应用灰度采集模式同时并发时,按照FIFO原则,哪种模式先发起采集请求,服务端就优先响应该模式,其它模式拒绝访问。

4、接口说明

官方文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-hidebug-h#oh_hidebug_startprofiler

 

附1 Global Handle常见泄漏场景

场景 全局引用忘记 delete(最常见)

#include <napi.h>

// 全局引用(泄漏重灾区)
static napi_ref g_my_ref = nullptr;

napi_value LeakRef(napi_env env, napi_callback_info info){
  napi_value obj;
  napi_get_cb_info(env, info, nullptr, nullptr, &obj, nullptr);
  
  // 创建强引用(初始计数=1)
  napi_create_reference(env, obj, 1, &g_my_ref); // ❌ 只创建不释放
  return nullptr;
}

// 缺少清理函数:
// void Cleanup(napi_env env) {
  // if (g_my_ref) {
    // napi_delete_reference(env, g_my_ref);
    // g_my_ref = nullptr;
  // }
// }

场景 循环 / 重复创建不释放(叠加泄漏)

napi_value CreateAndLeak(napi_env env, napi_callback_info info) {
  napi_value obj;
  napi_get_cb_info(env, info, nullptr, nullptr, &obj, nullptr);
  
  napi_ref ref;
  // 每次调用都新建引用
  napi_create_reference(env, obj, 1, &ref); // ❌ 无delete
  // 错误:覆盖旧ref,旧ref句柄永久丢失
  // g_ref = ref;
  return nullptr;
}

场景  / 实例持有引用、析构不清理

class NativeHolder {
  public:
  napi_ref m_ref;
  NativeHolder(napi_env env, napi_value obj) {
    napi_create_reference(env, obj, 1, &m_ref);
  }

  // ❌ 析构不delete
  ~NativeHolder() {
    // 缺少napi_delete_reference(env, m_ref)配对调用
  }
};

napi_value CreateHolder(napi_env env, napi_callback_info info) {
  napi_value obj;
  napi_get_cb_info(env, info, nullptr, nullptr, &obj, nullptr);
  NativeHolder* holder = new NativeHolder(env, obj);
  // 若不主动清理:holder泄漏 + m_ref泄漏
  return nullptr;
}

本文由 @华为开发者联盟 授权发布于人人都是产品经理。未经作者许可,禁止转载

题图来自Unsplash,基于CC0协议

该文观点仅代表作者本人,人人都是产品经理平台仅提供信息存储空间服务

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
评论
评论请登录
  1. 目前还没评论,等你发挥!