Open
Implement OOM heap dump #190
Implement OOM heap dump #190
Open
lizzyhpAdd on 17 days ago
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=146 -DHAVE_OPENSSL=1
47 -DTARGET_OHOS47 -DTARGET_OHOS
48 # -DENABLE_HISYSEVENT48 # -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)
61include_directories(jsvm68include_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)
106set(CMAKE_SKIP_RPATH TRUE)114set(CMAKE_SKIP_RPATH TRUE)
107link_directories(${LIB_DIRS})115link_directories(${LIB_DIRS})
@@ -139,6 +147,7 @@ target_link_libraries(jsvm
139 c++_static147 c++_static
140 libdfx_signalhandler.z.so148 libdfx_signalhandler.z.so
141 libfaultloggerd.z.so149 libfaultloggerd.z.so
150+ libucollection_client.z.so
142)151)
143else()152else()
144target_link_libraries(jsvm153target_link_libraries(jsvm
@@ -158,5 +167,6 @@ target_link_libraries(jsvm
158 c++_static167 c++_static
159 libdfx_signalhandler.z.so168 libdfx_signalhandler.z.so
160 libfaultloggerd.z.so169 libfaultloggerd.z.so
170+ libucollection_client.z.so
161)171)
162endif()172endif()
@@ -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 
29namespace jsvm {46namespace 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 jsvm419} // 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 jsvm91} // namespace jsvm
85 92 
86#endif // JSVM_HIDUMP_H93#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 
1201static void OnJSVMOOMError(v8::Isolate* isolate, const char* location,1203static 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 HiSysEvent1207+ // 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 
1208JSVM_Status OH_JSVM_CreateVM(const JSVM_CreateVMOptions* options, JSVM_VM* result)1215JSVM_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+ 
299void WriteHisysevent(HiSysEventParam* params, int size)315void 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_HISYSEVENT375#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#endif441#endif
406}442}
@@ -20,6 +20,7 @@
20#error "PLATFORM OHOS is required"20#error "PLATFORM OHOS is required"
21#endif21#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 
39void WriteJSVMInitToHisysevent();40void 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 
43void WriteAPIUseToHisysevent(const char *apiName, uint32_t misuseKind = 0);46void WriteAPIUseToHisysevent(const char *apiName, uint32_t misuseKind = 0);
44 47 
45bool InJitMode();48bool InJitMode();
49+ 
50+std::string GetBundleName();
46} // namespace ohos51} // namespace ohos
47} // namespace platform52} // namespace platform
48#endif53#endif