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