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