Open
Implement OOM heap dump #190
lizzyhpAdd on 17 days ago
Implement OOM heap dump #190
Open
Total: 7 files changed+186-7
| @@ -211,6 +211,7 @@ action("build_libjsvm") { | |||
| 211 | "samgr:samgr_proxy", | 211 | "samgr:samgr_proxy", |
| 212 | "faultloggerd:dfx_signalhandler", | 212 | "faultloggerd:dfx_signalhandler", |
| 213 | "faultloggerd:libfaultloggerd", | 213 | "faultloggerd:libfaultloggerd", |
| 214 | + "hiview:libucollection_client" | ||
| 214 | ] | 215 | ] |
| 215 | if (dependency_tag != "default") { | 216 | if (dependency_tag != "default") { |
| 216 | external_deps += [ "zlib:libz" ] | 217 | external_deps += [ "zlib:libz" ] |
| @@ -46,6 +46,7 @@ add_compile_definitions( | |||
| 46 | -DHAVE_OPENSSL=1 | 46 | -DHAVE_OPENSSL=1 |
| 47 | -DTARGET_OHOS | 47 | -DTARGET_OHOS |
| 48 | # -DENABLE_HISYSEVENT | 48 | # -DENABLE_HISYSEVENT |
| 49 | + -DENABLE_UCOLLECTION | ||
| 49 | -DJSVM_ENABLE_API_ACCESS_SCOPE=${JSVM_ENABLE_API_ACCESS_SCOPE} | 50 | -DJSVM_ENABLE_API_ACCESS_SCOPE=${JSVM_ENABLE_API_ACCESS_SCOPE} |
| 50 | -DJSVM_ENABLE_API_TRACE=${JSVM_ENABLE_API_TRACE} | 51 | -DJSVM_ENABLE_API_TRACE=${JSVM_ENABLE_API_TRACE} |
| 51 | -DJSVM_ENABLE_API_ACCESS_AUDIT=${JSVM_ENABLE_API_ACCESS_AUDIT} | 52 | -DJSVM_ENABLE_API_ACCESS_AUDIT=${JSVM_ENABLE_API_ACCESS_AUDIT} |
| @@ -57,6 +58,12 @@ include_directories(jsvm | |||
| 57 | "interface/kits" | 58 | "interface/kits" |
| 58 | ) | 59 | ) |
| 59 | 60 | ||
| 61 | +# hiview inner_api:unified_collection (MemoryCollector::IsolateSubProcess) | ||
| 62 | +include_directories(jsvm | ||
| 63 | + "../../base/hiviewdfx/hiview/interfaces/inner_api/unified_collection" | ||
| 64 | + "../../base/hiviewdfx/hiview/interfaces/inner_api/unified_collection/client" | ||
| 65 | +) | ||
| 66 | + | ||
| 60 | # samgr:samgr_proxy header, need by res_sched_client.h (SystemAbilityStatusChangesStub) | 67 | # samgr:samgr_proxy header, need by res_sched_client.h (SystemAbilityStatusChangesStub) |
| 61 | include_directories(jsvm | 68 | include_directories(jsvm |
| 62 | "../../foundation/systemabilitymgr/samgr/interfaces/innerkits/samgr_proxy/include" | 69 | "../../foundation/systemabilitymgr/samgr/interfaces/innerkits/samgr_proxy/include" |
| @@ -102,6 +109,7 @@ set (LIB_DIRS | |||
| 102 | "${LIB_ROOT_DIR}/thirdparty/openssl/" | 109 | "${LIB_ROOT_DIR}/thirdparty/openssl/" |
| 103 | "${LIB_ROOT_DIR}/obj/vendor/${DEPENDENCY_TAG}/foundation/bundlemanager/zlib_override/" | 110 | "${LIB_ROOT_DIR}/obj/vendor/${DEPENDENCY_TAG}/foundation/bundlemanager/zlib_override/" |
| 104 | "${LIB_ROOT_DIR}/hiviewdfx/faultloggerd" | 111 | "${LIB_ROOT_DIR}/hiviewdfx/faultloggerd" |
| 112 | + "${LIB_ROOT_DIR}/hiviewdfx/hiview" | ||
| 105 | ) | 113 | ) |
| 106 | set(CMAKE_SKIP_RPATH TRUE) | 114 | set(CMAKE_SKIP_RPATH TRUE) |
| 107 | link_directories(${LIB_DIRS}) | 115 | link_directories(${LIB_DIRS}) |
| @@ -139,6 +147,7 @@ target_link_libraries(jsvm | |||
| 139 | c++_static | 147 | c++_static |
| 140 | libdfx_signalhandler.z.so | 148 | libdfx_signalhandler.z.so |
| 141 | libfaultloggerd.z.so | 149 | libfaultloggerd.z.so |
| 150 | + libucollection_client.z.so | ||
| 142 | ) | 151 | ) |
| 143 | else() | 152 | else() |
| 144 | target_link_libraries(jsvm | 153 | target_link_libraries(jsvm |
| @@ -158,5 +167,6 @@ target_link_libraries(jsvm | |||
| 158 | c++_static | 167 | c++_static |
| 159 | libdfx_signalhandler.z.so | 168 | libdfx_signalhandler.z.so |
| 160 | libfaultloggerd.z.so | 169 | libfaultloggerd.z.so |
| 170 | + libucollection_client.z.so | ||
| 161 | ) | 171 | ) |
| 162 | endif() | 172 | endif() |
| @@ -16,15 +16,32 @@ | |||
| 16 | #include "jsvm_hidump.h" | 16 | #include "jsvm_hidump.h" |
| 17 | 17 | ||
| 18 | #include <unistd.h> | 18 | #include <unistd.h> |
| 19 | +#include <atomic> | ||
| 19 | #include <chrono> | 20 | #include <chrono> |
| 21 | +#include <cstdint> | ||
| 22 | +#include <dlfcn.h> | ||
| 20 | #include <memory> | 23 | #include <memory> |
| 24 | +#include <sys/types.h> | ||
| 25 | +#include <string> | ||
| 21 | #include <sys/wait.h> | 26 | #include <sys/wait.h> |
| 22 | 27 | ||
| 23 | #include "jsvm_dfx.h" | 28 | #include "jsvm_dfx.h" |
| 24 | #include "jsvm_log.h" | 29 | #include "jsvm_log.h" |
| 25 | #include "platform/platform.h" | 30 | #include "platform/platform.h" |
| 31 | +#include "platform/platform_ohos.h" | ||
| 26 | #include "v8-profiler.h" | 32 | #include "v8-profiler.h" |
| 27 | #include "faultloggerd_client.h" | 33 | #include "faultloggerd_client.h" |
| 34 | +#ifdef ENABLE_UCOLLECTION | ||
| 35 | +#include "memory_collector_client.h" | ||
| 36 | +#endif | ||
| 37 | + | ||
| 38 | +namespace v8 { | ||
| 39 | +namespace dfx { | ||
| 40 | +using OOMDumpCallback = void (*)(Isolate* isolate, char* eventConfig, int32_t eventConfigBufSize); | ||
| 41 | +void SetOOMDumpCallback(OOMDumpCallback callback); | ||
| 42 | +pid_t DumpHeapWithFork(Isolate* isolate, int fd); | ||
| 43 | +} // namespace dfx | ||
| 44 | +} // namespace v8 | ||
| 28 | 45 | ||
| 29 | namespace jsvm { | 46 | namespace jsvm { |
| 30 | 47 | ||
| @@ -303,6 +320,102 @@ bool IsolateRegistry::RequestDumpInterrupt(uint32_t tid, DumpFormat format, int | |||
| 303 | return true; | 320 | return true; |
| 304 | } | 321 | } |
| 305 | 322 | ||
| 323 | +// ─── OOM dump decision callback (appkit, fetched via dlsym) ──────────────── | ||
| 324 | + | ||
| 325 | +namespace { | ||
| 326 | +// Contract with the appkit side: appkit exports InvokeAppFreezeFilterCallback | ||
| 327 | +// directly (no getter indirection); calling it performs the appfreeze-filter | ||
| 328 | +// decision. Caller-owned buffer (char* + size) keeps the dlsym boundary pure | ||
| 329 | +// C ABI: no std::string layout or allocator crossing. The key is input only. | ||
| 330 | +constexpr const char* INVOKE_SYMBOL_NAME = "InvokeAppFreezeFilterCallback"; | ||
| 331 | + | ||
| 332 | +// Input key identifying the jsvm heap dump feature. If appkit does not write | ||
| 333 | +// config data back into eventConfig, the value stays equal to this key and | ||
| 334 | +// is reported as empty (no config data). | ||
| 335 | +constexpr const char* EVENT_CONFIG_KEY = "user.event_config.jsvm_heap_log_enable"; | ||
| 336 | + | ||
| 337 | +// Atomic for cross-thread visibility: registered on the OH_JSVM_Init caller | ||
| 338 | +// thread, consumed later on the isolate's JS thread at OOM time. | ||
| 339 | +std::atomic<bool (*)(int32_t, bool, char*, int32_t)> g_oomDumpDecisionCallback{nullptr}; | ||
| 340 | +} // namespace | ||
| 341 | + | ||
| 342 | +// ─── OOM heap dump (fatal, fork-based) ────────────────────────────────────── | ||
| 343 | + | ||
| 344 | +static void DoOomHeapDump(v8::Isolate* isolate, char* eventConfig, int32_t eventConfigBufSize) | ||
| 345 | +{ | ||
| 346 | + auto invoke = g_oomDumpDecisionCallback.load(); | ||
| 347 | + if (invoke == nullptr || eventConfig == nullptr || eventConfigBufSize <= 0) { | ||
| 348 | + LOG(Error) << "OOMDump: no decision callback, skip dump"; | ||
| 349 | + return; | ||
| 350 | + } | ||
| 351 | + | ||
| 352 | + snprintf(eventConfig, static_cast<size_t>(eventConfigBufSize), "%s", EVENT_CONFIG_KEY); | ||
| 353 | + bool allowed = invoke(static_cast<int32_t>(getpid()), true, eventConfig, eventConfigBufSize); | ||
| 354 | + eventConfig[eventConfigBufSize - 1] = '\0'; | ||
| 355 | + // If appkit didn't modify eventConfig (still equals the input key), there | ||
| 356 | + // is no config data to report — write empty instead of the raw key. Runs | ||
| 357 | + // on the denied path too: the OOM report chain reads the buffer regardless. | ||
| 358 | + if (strcmp(eventConfig, EVENT_CONFIG_KEY) == 0) { | ||
| 359 | + eventConfig[0] = '\0'; | ||
| 360 | + } | ||
| 361 | + if (!allowed) { | ||
| 362 | + LOG(Info) << "OOMDump: dump denied (no dump quota)"; | ||
| 363 | + return; | ||
| 364 | + } | ||
| 365 | + LOG(Info) << "OOMDump: dump permitted, triggering"; | ||
| 366 | + | ||
| 367 | + int fd = RequestOutputFd(static_cast<uint32_t>(platform::OS::GetTid())); | ||
| 368 | + if (fd < 0) { | ||
| 369 | + LOG(Error) << "OOMDump: fd request failed, fd=" << fd; | ||
| 370 | + return; | ||
| 371 | + } | ||
| 372 | + pid_t childPid = v8::dfx::DumpHeapWithFork(isolate, fd); | ||
| 373 | + if (childPid > 0) { | ||
| 374 | + LOG(Info) << "OOMDump: fork success, childPid=" << childPid; | ||
| 375 | +#ifdef ENABLE_UCOLLECTION | ||
| 376 | + auto collector = OHOS::HiviewDFX::UCollectClient::MemoryCollector::Create(); | ||
| 377 | + std::string bundleName = platform::ohos::GetBundleName(); | ||
| 378 | + auto result = collector->IsolateSubProcess(bundleName, static_cast<int32_t>(getpid()), | ||
| 379 | + static_cast<int32_t>(childPid)); | ||
| 380 | + if (result.retCode == OHOS::HiviewDFX::UCollect::UcError::SUCCESS) { | ||
| 381 | + LOG(Info) << "OOMDump: IsolateSubProcess success, childPid=" << childPid; | ||
| 382 | + } else { | ||
| 383 | + LOG(Error) << "OOMDump: IsolateSubProcess failed, retCode=" << result.retCode; | ||
| 384 | + } | ||
| 385 | +#endif | ||
| 386 | + } else { | ||
| 387 | + LOG(Error) << "OOMDump: fork failed, childPid=" << childPid; | ||
| 388 | + close(fd); | ||
| 389 | + } | ||
| 390 | +} | ||
| 391 | + | ||
| 392 | +void InitOOMDump() | ||
| 393 | +{ | ||
| 394 | + // Dump slot: always registered; the decision callback inside is optional | ||
| 395 | + // (dlsym below) — dump is skipped when it is absent or denies. | ||
| 396 | + v8::dfx::SetOOMDumpCallback(&DoOomHeapDump); | ||
| 397 | + | ||
| 398 | + using OOMDumpDecisionCallback = bool (*)(int32_t, bool, char*, int32_t); | ||
| 399 | + void* hdl = dlopen("libappkit_native.z.so", RTLD_LAZY); | ||
| 400 | + if (hdl == nullptr) { | ||
| 401 | + LOG(Error) << "OOMDump: dlopen libappkit_native.z.so failed: " << dlerror(); | ||
| 402 | + return; | ||
| 403 | + } | ||
| 404 | + // One dlsym: the symbol is the invoke function itself. | ||
| 405 | + OOMDumpDecisionCallback invoke = reinterpret_cast<OOMDumpDecisionCallback>( | ||
| 406 | + dlsym(hdl, INVOKE_SYMBOL_NAME)); | ||
| 407 | + if (invoke == nullptr) { | ||
| 408 | + // dlerror must be read before dlclose resets it. | ||
| 409 | + LOG(Error) << "OOMDump: dlsym " << INVOKE_SYMBOL_NAME | ||
| 410 | + << " failed: " << dlerror(); | ||
| 411 | + dlclose(hdl); | ||
| 412 | + return; | ||
| 413 | + } | ||
| 414 | + dlclose(hdl); | ||
| 415 | + g_oomDumpDecisionCallback.store(invoke); | ||
| 416 | + LOG(Info) << "OOMDump: decision callback registered"; | ||
| 417 | +} | ||
| 418 | + | ||
| 306 | } // namespace jsvm | 419 | } // namespace jsvm |
| 307 | 420 | ||
| 308 | // ─── C API ──────────────────────────────────────────────────────────── | 421 | // ─── C API ──────────────────────────────────────────────────────────── |
| @@ -81,6 +81,13 @@ private: | |||
| 81 | std::unordered_map<uint32_t, std::stack<v8::Isolate*>> isolatesInThreads_; | 81 | std::unordered_map<uint32_t, std::stack<v8::Isolate*>> isolatesInThreads_; |
| 82 | }; | 82 | }; |
| 83 | 83 | ||
| 84 | +// OOM heap dump (fatal): registers the V8 dump slot (DoOomHeapDump) and | ||
| 85 | +// resolves the appkit decision callback (InvokeAppFreezeFilterCallback via | ||
| 86 | +// dlsym). Called from OH_JSVM_Init once per process. The decision callback | ||
| 87 | +// is later invoked on the isolate's JS thread at OOM time; the appkit | ||
| 88 | +// implementation must be thread-safe and return quickly. | ||
| 89 | +void InitOOMDump(); | ||
| 90 | + | ||
| 84 | } // namespace jsvm | 91 | } // namespace jsvm |
| 85 | 92 | ||
| 86 | #endif // JSVM_HIDUMP_H | 93 | #endif // JSVM_HIDUMP_H |
| @@ -1168,6 +1168,8 @@ JSVM_Status OH_JSVM_Init(const JSVM_InitOptions* options) | |||
| 1168 | v8::V8::SetFlagsFromCommandLine(options->argc, options->argv, options->removeFlags); | 1168 | v8::V8::SetFlagsFromCommandLine(options->argc, options->argv, options->removeFlags); |
| 1169 | } | 1169 | } |
| 1170 | OHOS_CALL(platform::ohos::SetSecurityMode()); | 1170 | OHOS_CALL(platform::ohos::SetSecurityMode()); |
| 1171 | + LOG(Info) << "OOMDump: OH_JSVM_Init running (jsvm initialized)"; | ||
| 1172 | + jsvm::InitOOMDump(); | ||
| 1171 | 1173 | ||
| 1172 | constexpr size_t secArgCnt = 2; | 1174 | constexpr size_t secArgCnt = 2; |
| 1173 | constexpr bool removeFlag = false; | 1175 | constexpr bool removeFlag = false; |
| @@ -1199,10 +1201,15 @@ JSVM_Status OH_JSVM_GetVM(JSVM_Env env, JSVM_VM* result) | |||
| 1199 | } | 1201 | } |
| 1200 | 1202 | ||
| 1201 | static void OnJSVMOOMError(v8::Isolate* isolate, const char* location, | 1203 | static void OnJSVMOOMError(v8::Isolate* isolate, const char* location, |
| 1202 | - const v8::OOMDetails& details, const char* heap_stat) | 1204 | + const v8::OOMDetails& details, const char* heap_stat, |
| 1205 | + const char* eventConfig) | ||
| 1203 | { | 1206 | { |
| 1204 | - // Report OOM error to HiSysEvent | 1207 | + // Fatal-thread pid/tid: matches the dump file name segments. |
| 1205 | - platform::ohos::WriteOOMErrorToHisysevent(location, details.detail, details.is_heap_oom, heap_stat); | 1208 | + int32_t pid = static_cast<int32_t>(platform::OS::GetPid()); |
| 1209 | + int32_t tid = static_cast<int32_t>(platform::OS::GetTid()); | ||
| 1210 | + // Report OOM error to HiSysEvent, including the dump eventConfig if present. | ||
| 1211 | + platform::ohos::WriteOOMErrorToHisysevent(location, details.detail, details.is_heap_oom, | ||
| 1212 | + heap_stat, pid, tid, eventConfig); | ||
| 1206 | } | 1213 | } |
| 1207 | 1214 | ||
| 1208 | JSVM_Status OH_JSVM_CreateVM(const JSVM_CreateVMOptions* options, JSVM_VM* result) | 1215 | JSVM_Status OH_JSVM_CreateVM(const JSVM_CreateVMOptions* options, JSVM_VM* result) |
| @@ -296,6 +296,22 @@ std::unique_ptr<char[]> ProcessBundleNameParam() | |||
| 296 | return std::move(name); | 296 | return std::move(name); |
| 297 | } | 297 | } |
| 298 | 298 | ||
| 299 | +std::string GetBundleName() | ||
| 300 | +{ | ||
| 301 | + int pid = getprocpid(); | ||
| 302 | + std::string filePath = "/proc/" + std::to_string(pid) + "/cmdline"; | ||
| 303 | + std::string bundleName; | ||
| 304 | + if (LoadStringFromFile(filePath, bundleName) && !bundleName.empty()) { | ||
| 305 | + auto pos = bundleName.find(":"); | ||
| 306 | + if (pos != std::string::npos) { | ||
| 307 | + bundleName = bundleName.substr(0, pos); | ||
| 308 | + } | ||
| 309 | + } else { | ||
| 310 | + bundleName = "INVALID_BUNDLE_NAME"; | ||
| 311 | + } | ||
| 312 | + return bundleName; | ||
| 313 | +} | ||
| 314 | + | ||
| 299 | void WriteHisysevent(HiSysEventParam* params, int size) | 315 | void WriteHisysevent(HiSysEventParam* params, int size) |
| 300 | { | 316 | { |
| 301 | OH_HiSysEvent_Write(OHOS::HiviewDFX::HiSysEvent::Domain::JSVM_RUNTIME, "APP_STATS", | 317 | OH_HiSysEvent_Write(OHOS::HiviewDFX::HiSysEvent::Domain::JSVM_RUNTIME, "APP_STATS", |
| @@ -352,7 +368,9 @@ void WriteHisyseventJSVMOOM(HiSysEventParam* params, int size) | |||
| 352 | params, size); | 368 | params, size); |
| 353 | } | 369 | } |
| 354 | 370 | ||
| 355 | -void WriteOOMErrorToHisysevent(const char* location, const char* detail, bool isHeapOOM, const char* heapStat) | 371 | +void WriteOOMErrorToHisysevent(const char* location, const char* detail, bool isHeapOOM, |
| 372 | + const char* heapStat, int32_t pid, int32_t tid, | ||
| 373 | + const char* eventConfig) | ||
| 356 | { | 374 | { |
| 357 | #ifdef ENABLE_HISYSEVENT | 375 | #ifdef ENABLE_HISYSEVENT |
| 358 | std::unique_ptr<char[]> name = ProcessBundleNameParam(); | 376 | std::unique_ptr<char[]> name = ProcessBundleNameParam(); |
| @@ -375,6 +393,18 @@ void WriteOOMErrorToHisysevent(const char* location, const char* detail, bool is | |||
| 375 | .v = { .s = const_cast<char*>(id) }, | 393 | .v = { .s = const_cast<char*>(id) }, |
| 376 | .arraySize = 0, | 394 | .arraySize = 0, |
| 377 | }; | 395 | }; |
| 396 | + HiSysEventParam pidParam = { | ||
| 397 | + .name = "PID", | ||
| 398 | + .t = HISYSEVENT_INT32, | ||
| 399 | + .v = { .i32 = pid }, | ||
| 400 | + .arraySize = 0, | ||
| 401 | + }; | ||
| 402 | + HiSysEventParam tidParam = { | ||
| 403 | + .name = "TID", | ||
| 404 | + .t = HISYSEVENT_INT32, | ||
| 405 | + .v = { .i32 = tid }, | ||
| 406 | + .arraySize = 0, | ||
| 407 | + }; | ||
| 378 | HiSysEventParam locationParam = { | 408 | HiSysEventParam locationParam = { |
| 379 | .name = "OOM_LOCATION", | 409 | .name = "OOM_LOCATION", |
| 380 | .t = HISYSEVENT_STRING, | 410 | .t = HISYSEVENT_STRING, |
| @@ -399,8 +429,14 @@ void WriteOOMErrorToHisysevent(const char* location, const char* detail, bool is | |||
| 399 | .v = { .s = const_cast<char*>(heapStat != nullptr ? heapStat : "") }, | 429 | .v = { .s = const_cast<char*>(heapStat != nullptr ? heapStat : "") }, |
| 400 | .arraySize = 0, | 430 | .arraySize = 0, |
| 401 | }; | 431 | }; |
| 402 | - HiSysEventParam params[] = { modeuleName, processName, appRunningId, locationParam, | 432 | + HiSysEventParam eventConfigParam = { |
| 403 | - detailParam, heapOOMParam, heapStatParam }; | 433 | + .name = "EVENT_CONFIG", |
| 434 | + .t = HISYSEVENT_STRING, | ||
| 435 | + .v = { .s = const_cast<char*>(eventConfig != nullptr ? eventConfig : "") }, | ||
| 436 | + .arraySize = 0, | ||
| 437 | + }; | ||
| 438 | + HiSysEventParam params[] = { modeuleName, processName, appRunningId, pidParam, tidParam, | ||
| 439 | + locationParam, detailParam, heapOOMParam, heapStatParam, eventConfigParam }; | ||
| 404 | WriteHisyseventJSVMOOM(params, sizeof(params) / sizeof(params[0])); | 440 | WriteHisyseventJSVMOOM(params, sizeof(params) / sizeof(params[0])); |
| 405 | #endif | 441 | #endif |
| 406 | } | 442 | } |
| @@ -20,6 +20,7 @@ | |||
| 20 | #error "PLATFORM OHOS is required" | 20 | #error "PLATFORM OHOS is required" |
| 21 | #endif | 21 | #endif |
| 22 | 22 | ||
| 23 | +#include <cstdint> | ||
| 23 | #include <string> | 24 | #include <string> |
| 24 | #include <unordered_map> | 25 | #include <unordered_map> |
| 25 | 26 | ||
| @@ -38,11 +39,15 @@ void SetSecurityMode(); | |||
| 38 | 39 | ||
| 39 | void WriteJSVMInitToHisysevent(); | 40 | void WriteJSVMInitToHisysevent(); |
| 40 | 41 | ||
| 41 | -void WriteOOMErrorToHisysevent(const char* location, const char* detail, bool isHeapOOM, const char* heapStat); | 42 | +void WriteOOMErrorToHisysevent(const char* location, const char* detail, bool isHeapOOM, |
| 43 | + const char* heapStat, int32_t pid, int32_t tid, | ||
| 44 | + const char* eventConfig); | ||
| 42 | 45 | ||
| 43 | void WriteAPIUseToHisysevent(const char *apiName, uint32_t misuseKind = 0); | 46 | void WriteAPIUseToHisysevent(const char *apiName, uint32_t misuseKind = 0); |
| 44 | 47 | ||
| 45 | bool InJitMode(); | 48 | bool InJitMode(); |
| 49 | + | ||
| 50 | +std::string GetBundleName(); | ||
| 46 | } // namespace ohos | 51 | } // namespace ohos |
| 47 | } // namespace platform | 52 | } // namespace platform |
| 48 | #endif | 53 | #endif |