• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 使用HiCollie检测业务线程卡死卡顿问题(C/C++)
2<!--Kit: Performance Analysis Kit-->
3<!--Subsystem: HiviewDFX-->
4<!--Owner: @rr_cn-->
5<!--Designer: @peterhuangyu-->
6<!--Tester: @gcw_KuLfPSbe-->
7<!--Adviser: @foryourself-->
8
9## 简介
10
11用户在使用应用时,如果出现点击无反应或应用无响应等情况,并且持续时间超过一定限制,就会被定义为[应用冻屏](appfreeze-guidelines.md)。本文面向开发者介绍HiCollie模块对外提供检测业务线程卡死、卡顿,以及上报卡死事件的能力。
12
13## 接口说明
14
15| 接口名 | 描述 |
16| -------- | -------- |
17| OH_HiCollie_Init_StuckDetection | 注册应用业务线程卡死的周期性检测任务。用户实现回调函数, 用于定时检测业务线程卡死情况。<br/>默认检测时间:3s上报BUSSINESS_THREAD_BLOCK_3S告警事件,6s上报BUSSINESS_THREAD_BLOCK_6S卡死事件。 |
18| OH_HiCollie_Init_StuckDetectionWithTimeout | 注册应用业务线程卡死的周期性检测任务。用户实现回调函数, 用于定时检测业务线程卡死情况。<br/>开发者可以设置卡死检测时间,可设置的时间范围:[3, 15],单位:s。<br/>说明:从API version 18开始,支持该接口。 |
19| OH_HiCollie_Init_JankDetection | 注册应用业务线程卡顿检测的回调函数。<br/>线程卡顿监控功能需要开发者实现两个卡顿检测回调函数,分别放在业务线程处理事件的前后。作为插桩函数,监控业务线程处理事件执行情况。 |
20| OH_HiCollie_Report | 上报应用业务线程卡死事件,生成卡死故障日志,辅助定位应用卡死问题。<br/>先调用OH_HiCollie_Init_StuckDetection或OH_HiCollie_Init_StuckDetectionWithTimeout接口,初始化检测的task;<br/>如果task任务超时,结合业务逻辑,调用OH_HiCollie_Report接口上报卡死事件。 |
21
22API接口的具体使用说明(参数使用限制、具体取值范围等)请参考[HiCollie](../reference/apis-performance-analysis-kit/capi-hicollie-h.md)。
23
24## 检测原理
25
261. 业务线程卡顿OH_HiCollie_Init_JankDetection故障规格,请参考[主线程超时事件检测原理](hiappevent-watcher-mainthreadjank-events.md#检测原理)。
27
282. 业务线程卡死故障:
29   (1)OH_HiCollie_Init_StuckDetection检测原理:应用的watchdog线程会周期性进行业务线程判活检测。当判活检测超过3s没有被执行,上报BUSSINESS_THREAD_BLOCK_3S线程告警事件;超过6s依然没有被执行,会上报BUSSINESS_THREAD_BLOCK_6S线程卡死事件。两个事件根据系统匹配规则生成appfreeze故障日志。
30
31   (2)OH_HiCollie_Init_StuckDetectionWithTimeout检测原理:应用的watchdog线程会周期性进行业务线程判活检测。当判活检测超过stuckTimeout时间没有被执行,上报BUSSINESS_THREAD_BLOCK_3S告警事件;超过stuckTimeout \* 2时间,依然没有被执行,会上报BUSSINESS_THREAD_BLOCK_6S线程卡死事件。两个事件匹配生成appfreeze故障日志。
32
33## 日志规格
34
351. 业务线程卡死故障日志以appfreeze-开头,生成在“设备/data/log/faultlog/faultlogger/”路径下。该日志文件名格式为“appfreeze-应用包名-应用UID-秒级时间”。具体规格可参考[应用冻屏(AppFreeze)日志规格](appfreeze-guidelines.md#日志规格)。
36
372. OH_HiCollie_Init_StuckDetection日志规格,请参考[主线程超时事件日志规格](hiappevent-watcher-mainthreadjank-events.md)。
38
39## 开发步骤
40
41下文将展示如何在应用内增加一个按钮,并单击该按钮以调用HiCollie Ndk接口。
42
431. 新建Native C++工程,目录结构如下:
44
45   ```yml
46   entry:
47     src:
48       main:
49         cpp:
50           types:
51             libentry:
52               - index.d.ts
53           - CMakeLists.txt
54           - napi_init.cpp
55         ets:
56           entryability:
57             - EntryAbility.ts
58           pages:
59             - Index.ets
60   ```
61
622. 编辑“CMakeLists.txt”文件,添加源文件及动态库:
63
64   ```cmake
65   # 新增动态库依赖libhilog_ndk.z.so(日志输出)
66   target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so libohhicollie.so)
67   ```
68
693. 编辑“napi_init.cpp”文件,导入依赖的文件,定义LOG_TAG,下述代码步骤用于模拟卡死卡顿场景,具体使用请结合业务需要。示例代码如下:
70
71   (1)**应用线程卡顿检测**: OH_HiCollie_Init_JankDetection,示例代码如下:
72
73   ```c++
74   #include <thread>
75   #include <string>
76   #include <unistd.h>
77   #include <atomic>
78   #include "napi/native_api.h"
79   #include "hilog/log.h"
80   #include "hicollie/hicollie.h"
81
82   #undef LOG_TAG
83   #define LOG_TAG "JankTest"
84
85   //定义两个回调函数对象
86   static OH_HiCollie_BeginFunc beginFunc_;
87   static OH_HiCollie_EndFunc endFunc_;
88
89   //定义监控应用显示开始、结束的回调函数
90   void InitBeginFunc(const char* eventName)
91   {
92       std::string str(eventName);
93       OH_LOG_INFO(LogType::LOG_APP, "InitBeginFunc eventName: %{public}s", str.c_str());
94   }
95   void InitEndFunc(const char* eventName)
96   {
97       std::string str(eventName);
98       OH_LOG_INFO(LogType::LOG_APP, "OH_HiCollie_EndFunc eventName: %{public}s", str.c_str());
99   }
100
101   void StartDelayTimer()
102   {
103     //等待1s
104     std::chrono::seconds delay(1);
105     OH_LOG_INFO(LogType::LOG_APP, "OH_HiCollie_Init_JankDetection delay before");
106     std::this_thread::sleep_for(delay);
107     OH_LOG_INFO(LogType::LOG_APP, "OH_HiCollie_Init_JankDetection delay after");
108   }
109
110   //定义子线程回调函数
111   void TestJankDetection()
112   {
113       // 初始化回调函数参数
114       beginFunc_ = InitBeginFunc;
115       endFunc_ = InitEndFunc;
116       HiCollie_DetectionParam param {0};
117       // 初始化线程卡顿监控函数
118       int initResult = OH_HiCollie_Init_JankDetection(&beginFunc_, &endFunc_, param);
119       // 线程启动1s内,不进行检测
120       StartDelayTimer();
121       // 成功结果:0
122       OH_LOG_INFO(LogType::LOG_APP, "OH_HiCollie_Init_JankDetection: %{public}d", initResult);
123       int count = 0;
124       while (count < 3) {
125           // 设置处理开始回调函数,监控线程任务执行开始时长
126           beginFunc_("TestBegin");
127           // 休眠350ms,模拟任务线程处理事件卡顿场景
128           usleep(350 * 1000);
129           // 设置处理结束回调函数,监控线程任务执行结束时长
130           endFunc_("TestEnd");
131           count++;
132       }
133   }
134
135   static napi_value TestHiCollieJankNdk(napi_env env, napi_callback_info info)
136   {
137       // 创建子线程
138       std::thread threadObj(TestJankDetection);
139       // 执行TestJankDetection任务
140       threadObj.join();
141       return 0;
142   }
143
144   EXTERN_C_START
145   static napi_value Init(napi_env env, napi_value exports)
146   {
147       napi_property_descriptor desc[] = {
148           { "testHiCollieJankNdk", nullptr, TestHiCollieJankNdk, nullptr, nullptr, nullptr, napi_default, nullptr },
149       };
150       napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
151       return exports;
152   }
153   EXTERN_C_END
154
155   static napi_module demoModule = {
156       .nm_version = 1,
157       .nm_flags = 0,
158       .nm_filename = nullptr,
159       .nm_register_func = Init,
160       .nm_modname = "entry",
161       .nm_priv = ((void*)0),
162       .reserved = { 0 },
163   };
164
165   extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
166   {
167       napi_module_register(&demoModule);
168   }
169   ```
170
171   (2)**应用线程卡死检测**: OH_HiCollie_Init_StuckDetection, 示例代码如下:
172
173   ```c++
174   #include "napi/native_api.h"
175   #include "hilog/log.h"
176   #include "hicollie/hicollie.h"
177   #include <thread>
178   #include <string>
179   #include <unistd.h>
180
181   #undef LOG_TAG
182   #define LOG_TAG "StruckTest"
183
184   // 自定义休眠时间,模拟卡死场景
185   const int64_t BLOCK_TIME = 3;
186   // 设置应用线程执行任务情况标志位, true-正常,false-卡死
187   std::shared_ptr<std::atomic<bool>> appThreadIsAlive_ = std::make_shared<std::atomic<bool>>(true);
188   // 设置上报应用线程卡死事件标志位
189   std::shared_ptr<std::atomic<bool>> isSixSecondEvent_ = std::make_shared<std::atomic<bool>>(false);
190
191   void ReportEvent() {
192       bool temp = isSixSecondEvent_->load();
193       int reportResult = OH_HiCollie_Report(&temp);
194       // 成功:0
195       OH_LOG_INFO(LogType::LOG_APP, "OH_HiCollie_Report: %{public}d, isSixSecondEvent: %{public}d", reportResult, isSixSecondEvent_->load());
196       isSixSecondEvent_->store(temp);
197   }
198
199   void SetTimeout()
200   {
201     int64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::
202       system_clock::now().time_since_epoch()).count();
203     sleep(BLOCK_TIME);
204     int64_t currentTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::
205       system_clock::now().time_since_epoch()).count();
206     if (currentTime - now < BLOCK_TIME) {
207       appThreadIsAlive_->store(true);
208       return;
209     }
210     appThreadIsAlive_->store(false);
211   }
212
213   // 开发者可自定义周期性检测任务
214   void Timer()
215   {
216     // 每隔3s检查应用是否正常执行任务
217     if (appThreadIsAlive_->load()) {
218       OH_LOG_INFO(LogType::LOG_APP, "Check appThread isAlive.");
219       // 更新appThreadIsAlive_,正常执行下次检测时为true
220       appThreadIsAlive_->store(false);
221       // 模拟超时场景
222       SetTimeout();
223       return;
224     }
225     ReportEvent();
226   }
227
228   //定义子线程回调函数
229   void InitStuckDetection()
230   {
231     // 初始化线程卡死监控函数
232     int initResult = OH_HiCollie_Init_StuckDetection(Timer);
233     // 成功结果:0
234     OH_LOG_INFO(LogType::LOG_APP, "OH_HiCollie_Init_StuckDetection: %{public}d", initResult);
235   }
236
237   static napi_value TestHiCollieStuckNdk(napi_env env, napi_callback_info info)
238   {
239     // 创建子线程
240     std::thread threadObj(InitStuckDetection);
241     // 执行任务
242     threadObj.join();
243     return 0;
244   }
245
246   EXTERN_C_START
247   static napi_value Init(napi_env env, napi_value exports)
248   {
249       napi_property_descriptor desc[] = {
250           { "testHiCollieStuckNdk", nullptr, TestHiCollieStuckNdk, nullptr, nullptr, nullptr, napi_default, nullptr },
251       };
252       napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
253       return exports;
254   }
255   EXTERN_C_END
256
257   static napi_module demoModule = {
258       .nm_version = 1,
259       .nm_flags = 0,
260       .nm_filename = nullptr,
261       .nm_register_func = Init,
262       .nm_modname = "entry",
263       .nm_priv = ((void*)0),
264       .reserved = { 0 },
265   };
266
267   extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
268   {
269       napi_module_register(&demoModule);
270   }
271   ```
272
273   (3)**应用线程卡死检测,自定义检测时间**: OH_HiCollie_Init_StuckDetectionWithTimeout,示例代码如下:
274
275   ```c++
276   #include "napi/native_api.h"
277   #include "hilog/log.h"
278   #include "hicollie/hicollie.h"
279   #include <thread>
280   #include <string>
281   #include <unistd.h>
282
283   #undef LOG_TAG
284   #define LOG_TAG "StruckTest"
285
286   // 自定义休眠时间,模拟卡死场景
287   const int64_t BLOCK_TIME = 5;
288   // 设置应用线程执行任务情况标志位, true-正常, false-卡死
289   std::shared_ptr<std::atomic<bool>> appThreadIsAlive_ = std::make_shared<std::atomic<bool>>(true);
290   // 设置上报应用线程卡死事件标志位
291   std::shared_ptr<std::atomic<bool>> isSixSecondEvent_ = std::make_shared<std::atomic<bool>>(false);
292
293   void ReportEvent() {
294       bool temp = isSixSecondEvent_->load();
295       int reportResult = OH_HiCollie_Report(&temp);
296       // 成功:0
297       OH_LOG_INFO(LogType::LOG_APP, "OH_HiCollie_Report: %{public}d, isSixSecondEvent: %{public}d", reportResult, isSixSecondEvent_->load());
298       isSixSecondEvent_->store(temp);
299   }
300
301   void SetTimeout()
302   {
303     int64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::
304       system_clock::now().time_since_epoch()).count();
305     sleep(BLOCK_TIME);
306     int64_t currentTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::
307       system_clock::now().time_since_epoch()).count();
308     if (currentTime - now < BLOCK_TIME) {
309       appThreadIsAlive_->store(true);
310       return;
311     }
312     appThreadIsAlive_->store(false);
313   }
314
315   // 开发者可自定义周期性检测任务
316   void Timer()
317   {
318     // 每隔5s检查应用是否正常执行任务
319     if (appThreadIsAlive_->load()) {
320       OH_LOG_INFO(LogType::LOG_APP, "Check appThread isAlive.");
321       // 更新appThreadIsAlive_,正常执行下次检测时为true
322       appThreadIsAlive_->store(false);
323       // 模拟超时场景
324       SetTimeout();
325       return;
326     }
327     ReportEvent();
328   }
329
330   //定义子线程回调函数
331   void InitStuckDetectionWithTimeout()
332   {
333     // 初始化线程卡死监控函数
334     int initResult = OH_HiCollie_Init_StuckDetectionWithTimeout(Timer, BLOCK_TIME);
335     // 成功结果:0
336     OH_LOG_INFO(LogType::LOG_APP, "OH_HiCollie_Init_StuckDetection: %{public}d", initResult);
337   }
338
339   static napi_value TestHiCollieStuckWithTimeoutNdk(napi_env env, napi_callback_info info)
340   {
341     // 创建子线程
342     std::thread threadObj(InitStuckDetectionWithTimeout);
343     // 执行任务
344     threadObj.join();
345     return 0;
346   }
347
348   EXTERN_C_START
349   static napi_value Init(napi_env env, napi_value exports)
350   {
351       napi_property_descriptor desc[] = {
352           { "testHiCollieStuckWithTimeoutNdk", nullptr, TestHiCollieStuckWithTimeoutNdk, nullptr, nullptr, nullptr, napi_default, nullptr },
353       };
354       napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
355       return exports;
356   }
357   EXTERN_C_END
358
359   static napi_module demoModule = {
360       .nm_version = 1,
361       .nm_flags = 0,
362       .nm_filename = nullptr,
363       .nm_register_func = Init,
364       .nm_modname = "entry",
365       .nm_priv = ((void*)0),
366       .reserved = { 0 },
367   };
368
369   extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
370   {
371       napi_module_register(&demoModule);
372   }
373   ```
374
3754. 将TestHiCollieNdk注册为ArkTS接口。
376   (1)OH_HiCollie_Init_JankDetection示例,编辑“index.d.ts”文件,定义ArkTS接口:
377
378   ```typescript
379   export const testHiCollieJankNdk: () => void;
380   ```
381
382   (2)OH_HiCollie_Init_StuckDetection示例,编辑“index.d.ts”文件,定义ArkTS接口:
383
384   ```typescript
385   export const testHiCollieStuckNdk: () => void;
386   ```
387
388   (3)OH_HiCollie_Init_StuckDetectionWithTimeout示例,编辑“index.d.ts”文件,定义ArkTS接口:
389
390   ```typescript
391   export const testHiCollieStuckWithTimeoutNdk: () => void;
392   ```
393
3945. 编辑“Index.ets”文件:
395
396   ```ts
397   import testNapi from 'libentry.so'
398
399   @Entry
400   @Component
401   struct Index {
402     build() {
403       RelativeContainer() {
404         Column() {
405           //选择下方对应的功能,可在此处添加不同的点击事件
406
407         }
408         .width('100%')
409       }
410       .height('100%')
411       .width('100%')
412     }
413   }
414   ```
415
416   (1)添加点击事件,触发OH_HiCollie_Init_JankDetection方法。
417
418   ```ts
419   Column() {
420     Button("testHiCollieJankNdk", { stateEffect:true, type: ButtonType.Capsule})
421       .width('75%')
422       .height(50)
423       .margin(15)
424       .fontSize(20)
425       .fontWeight(FontWeight.Bold)
426       .onClick(testNapi.testHiCollieJankNdk);
427   }
428   ```
429
430   (2)添加点击事件,触发OH_HiCollie_Init_StuckDetection方法。
431
432   ```ts
433   Column() {
434     Button("testHiCollieStuckNdk", { stateEffect:true, type: ButtonType.Capsule})
435       .width('75%')
436       .height(50)
437       .margin(15)
438       .fontSize(20)
439       .fontWeight(FontWeight.Bold)
440       .onClick(testNapi.testHiCollieStuckNdk);
441   }
442   ```
443
444   (3)添加点击事件,触发OH_HiCollie_Init_StuckDetectionWithTimeout方法。
445
446   ```ts
447   Column() {
448     Button("testHiCollieStuckWithTimeoutNdk", { stateEffect:true, type: ButtonType.Capsule})
449       .width('75%')
450       .height(50)
451       .margin(15)
452       .fontSize(20)
453       .fontWeight(FontWeight.Bold)
454       .onClick(testNapi.testHiCollieStuckWithTimeoutNdk);
455   }
456   ```
457
4586. 点击DevEco Studio界面中的运行按钮,运行应用工程。
459
4607. 在DevEco Studio的底部,切换到“Log”窗口,过滤自定义的LOG_TAG。
461   (1)点击“testHiCollieJankNdk”按钮。
462
463   此时窗口将显示通过OH_HiCollie_Init_JankDetection接口获取的应用业务线程采样栈的超时信息。可以通过订阅hiappevent获取对应的事件,参见[订阅主线程超时事件](hiappevent-watcher-mainthreadjank-events-arkts.md)。
464
465   (2)点击“testHiCollieStuckNdk”按钮。
466
467   此时窗口将显示通过OH_HiCollie_Init_StuckDetection接口,初始化卡死检测回调函数。可以根据实际业务场景,自行定义卡死检测函数。
468
469   (3)点击“testHiCollieStuckWithTimeoutNdk”按钮。
470
471   此时窗口将显示通过OH_HiCollie_Init_StuckDetectionWithTimeout接口,初始化卡死检测回调函数。可以根据实际业务场景,自行定义卡死检测函数,及卡死检测时间。
472