1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <string>
17 #include "event_handler.h"
18 #include "event_runner.h"
19 #include "native_engine/native_engine.h"
20 #include "ui/view/ui_context.h"
21 #include "window_manager.h"
22
23 using namespace OHOS::Ace;
24 using namespace OHOS::AppExecFwk;
25 using namespace OHOS::Rosen;
26 using ArkUIRuntimeCallInfo = panda::JsiRuntimeCallInfo;
27
28 constexpr uint32_t DUMP_EVENT_ID = 0;
29 constexpr uint32_t GC_EVENT_ID = 1;
30 constexpr uint32_t DUMP_DELAY_TIME = 30000; // 30s
31 constexpr uint32_t GC_DELAY_TIME = 27000; // 27s
32 constexpr uint32_t ONE_VALUE_LIMIT = 1;
33
34
35 class LeakWatcherEventHandler : public EventHandler {
36 public:
LeakWatcherEventHandler(const std::shared_ptr<EventRunner> & runner)37 explicit LeakWatcherEventHandler(const std::shared_ptr<EventRunner> &runner) : EventHandler(runner) {}
38
ProcessEvent(const InnerEvent::Pointer & event)39 void ProcessEvent(const InnerEvent::Pointer &event) override
40 {
41 if (!isRunning_) {
42 return;
43 }
44 auto eventId = event->GetInnerEventId();
45 if (eventId == DUMP_EVENT_ID) {
46 ExecuteJsFunc(dumpFuncRef_);
47 SendEvent(DUMP_EVENT_ID, DUMP_DELAY_TIME, Priority::IDLE);
48 } else if (eventId == GC_EVENT_ID) {
49 ExecuteJsFunc(gcFuncRef_);
50 SendEvent(GC_EVENT_ID, GC_DELAY_TIME, Priority::IDLE);
51 }
52 }
53
SetEnv(napi_env env)54 void SetEnv(napi_env env)
55 {
56 env_ = env;
57 }
SetDumpFuncRef(napi_ref ref)58 void SetDumpFuncRef(napi_ref ref)
59 {
60 dumpFuncRef_ = ref;
61 }
SetGcFuncRef(napi_ref ref)62 void SetGcFuncRef(napi_ref ref)
63 {
64 gcFuncRef_ = ref;
65 }
SetShutdownFuncRef(napi_ref ref)66 void SetShutdownFuncRef(napi_ref ref)
67 {
68 shutdownFuncRef_ = ref;
69 }
SetJsLeakWatcherStatus(bool isRunning)70 void SetJsLeakWatcherStatus(bool isRunning)
71 {
72 isRunning_ = isRunning;
73 if (!isRunning) {
74 Reset();
75 }
76 }
77
78 private:
ExecuteJsFunc(napi_ref callbackRef)79 void ExecuteJsFunc(napi_ref callbackRef)
80 {
81 napi_value global = nullptr;
82 napi_get_global(env_, &global);
83 napi_value callback = nullptr;
84 napi_get_reference_value(env_, callbackRef, &callback);
85 napi_value argv[1] = {nullptr};
86 napi_call_function(env_, global, callback, 1, argv, nullptr);
87 }
Reset()88 void Reset()
89 {
90 napi_delete_reference(env_, dumpFuncRef_);
91 napi_delete_reference(env_, gcFuncRef_);
92 napi_delete_reference(env_, shutdownFuncRef_);
93 dumpFuncRef_ = nullptr;
94 gcFuncRef_ = nullptr;
95 shutdownFuncRef_ = nullptr;
96 }
97 napi_env env_ = nullptr;
98 napi_ref dumpFuncRef_ = nullptr;
99 napi_ref gcFuncRef_ = nullptr;
100 napi_ref shutdownFuncRef_ = nullptr;
101 bool isRunning_ = false;
102 };
103
104 class WindowLifeCycleListener : public IWindowLifeCycleListener {
105 public:
OnWindowDestroyed(const WindowLifeCycleInfo & info,void * jsWindowNapiValue)106 void OnWindowDestroyed(const WindowLifeCycleInfo& info, void* jsWindowNapiValue) override
107 {
108 napi_value global = nullptr;
109 napi_get_global(env_, &global);
110 napi_value callback = nullptr;
111 napi_get_reference_value(env_, callbackRef_, &callback);
112 napi_value param = reinterpret_cast<napi_value>(jsWindowNapiValue);
113 napi_value argv[1] = {param};
114 napi_call_function(env_, global, callback, 1, argv, nullptr);
115 }
116
WindowLifeCycleListener()117 WindowLifeCycleListener() {}
SetEnvAndCallback(napi_env env,napi_ref callbackRef)118 void SetEnvAndCallback(napi_env env, napi_ref callbackRef)
119 {
120 env_ = env;
121 callbackRef_ = callbackRef;
122 }
Reset()123 void Reset()
124 {
125 napi_delete_reference(env_, callbackRef_);
126 env_ = nullptr;
127 callbackRef_ = nullptr;
128 }
129 private:
130 napi_env env_ = nullptr;
131 napi_ref callbackRef_ = nullptr;
132 };
133
134 auto g_runner = EventRunner::Current();
135 auto g_handler = std::make_shared<LeakWatcherEventHandler>(g_runner);
136 auto g_listener = OHOS::sptr<WindowLifeCycleListener>::MakeSptr();
137 napi_ref g_callbackRef = nullptr;
138
CreateFile(const std::string & filePath)139 static bool CreateFile(const std::string& filePath)
140 {
141 if (access(filePath.c_str(), F_OK) == 0) {
142 return access(filePath.c_str(), W_OK) == 0;
143 }
144 const mode_t defaultMode = S_IRUSR | S_IWUSR | S_IRGRP; // -rw-r-----
145 int fd = creat(filePath.c_str(), defaultMode);
146 if (fd == -1) {
147 return false;
148 }
149 close(fd);
150 return true;
151 }
152
GetNapiStringValue(napi_env env,napi_value value,std::string & str)153 static bool GetNapiStringValue(napi_env env, napi_value value, std::string& str)
154 {
155 size_t bufLen = 0;
156 napi_status status = napi_get_value_string_utf8(env, value, nullptr, 0, &bufLen);
157 if (status != napi_ok) {
158 return false;
159 }
160 str.reserve(bufLen + 1);
161 str.resize(bufLen);
162 status = napi_get_value_string_utf8(env, value, str.data(), bufLen + 1, &bufLen);
163 if (status != napi_ok) {
164 return false;
165 }
166 return true;
167 }
168
GetFileSize(const std::string & filePath)169 static uint64_t GetFileSize(const std::string& filePath)
170 {
171 struct stat st;
172 if (stat(filePath.c_str(), &st) != 0) {
173 return 0;
174 }
175 return st.st_size;
176 }
177
AppendMetaData(const std::string & filePath)178 static bool AppendMetaData(const std::string& filePath)
179 {
180 const char* metaDataPath = "/system/lib64/module/arkcompiler/metadata.json";
181 auto rawHeapFileSize = static_cast<uint32_t>(GetFileSize(filePath));
182 auto metaDataFileSize = static_cast<uint32_t>(GetFileSize(metaDataPath));
183 FILE* targetFile = fopen(filePath.c_str(), "ab");
184 if (targetFile == nullptr) {
185 return false;
186 }
187 FILE* metaDataFile = fopen(metaDataPath, "rb");
188 if (metaDataFile == nullptr) {
189 fclose(targetFile);
190 return false;
191 }
192 constexpr auto buffSize = 1024;
193 char buff[buffSize] = {0};
194 size_t readSize = 0;
195 while ((readSize = fread(buff, 1, buffSize, metaDataFile)) > 0) {
196 if (fwrite(buff, 1, readSize, targetFile) != readSize) {
197 fclose(targetFile);
198 fclose(metaDataFile);
199 return false;
200 }
201 }
202 if (fwrite(&rawHeapFileSize, sizeof(rawHeapFileSize), 1, targetFile) != 1) {
203 fclose(targetFile);
204 fclose(metaDataFile);
205 return false;
206 }
207 if (fwrite(&metaDataFileSize, sizeof(metaDataFileSize), 1, targetFile) != 1) {
208 fclose(targetFile);
209 fclose(metaDataFile);
210 return false;
211 }
212 fclose(targetFile);
213 fclose(metaDataFile);
214 return true;
215 }
216
CreateUndefined(napi_env env)217 static napi_value CreateUndefined(napi_env env)
218 {
219 napi_value result = nullptr;
220 napi_get_undefined(env, &result);
221 return result;
222 }
223
GetCallbackRef(napi_env env,napi_callback_info info,napi_ref * ref)224 static bool GetCallbackRef(napi_env env, napi_callback_info info, napi_ref* ref)
225 {
226 size_t argc = ONE_VALUE_LIMIT;
227 napi_value argv[ONE_VALUE_LIMIT] = {nullptr};
228 napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
229 if (argc != ONE_VALUE_LIMIT) {
230 return false;
231 }
232 napi_create_reference(env, argv[0], 1, ref);
233 return true;
234 }
235
RegisterArkUIObjectLifeCycleCallback(napi_env env,napi_callback_info info)236 static napi_value RegisterArkUIObjectLifeCycleCallback(napi_env env, napi_callback_info info)
237 {
238 if (!GetCallbackRef(env, info, &g_callbackRef)) {
239 return nullptr;
240 }
241
242 RefPtr<Kit::UIContext> uiContext = Kit::UIContext::Current();
243 if (uiContext == nullptr) {
244 return nullptr;
245 }
246 uiContext->RegisterArkUIObjectLifecycleCallback([env](void* obj) {
247 ArkUIRuntimeCallInfo* arkUIRuntimeCallInfo = reinterpret_cast<ArkUIRuntimeCallInfo*>(obj);
248 panda::Local<panda::JSValueRef> firstArg = arkUIRuntimeCallInfo->GetCallArgRef(0);
249 napi_value param = reinterpret_cast<napi_value>(*firstArg);
250 napi_value global = nullptr;
251 napi_get_global(env, &global);
252 napi_value callback = nullptr;
253 napi_get_reference_value(env, g_callbackRef, &callback);
254 napi_value argv[1] = {param};
255 napi_call_function(env, global, callback, 1, argv, nullptr);
256 });
257
258 return CreateUndefined(env);
259 }
260
UnregisterArkUIObjectLifeCycleCallback(napi_env env,napi_callback_info info)261 static napi_value UnregisterArkUIObjectLifeCycleCallback(napi_env env, napi_callback_info info)
262 {
263 RefPtr<Kit::UIContext> uiContext = Kit::UIContext::Current();
264 if (uiContext == nullptr) {
265 return nullptr;
266 }
267 uiContext->UnregisterArkUIObjectLifecycleCallback();
268 napi_delete_reference(env, g_callbackRef);
269 g_callbackRef = nullptr;
270 return CreateUndefined(env);
271 }
272
RegisterWindowLifeCycleCallback(napi_env env,napi_callback_info info)273 static napi_value RegisterWindowLifeCycleCallback(napi_env env, napi_callback_info info)
274 {
275 napi_ref ref = nullptr;
276 if (!GetCallbackRef(env, info, &ref)) {
277 return nullptr;
278 }
279 g_listener->SetEnvAndCallback(env, ref);
280 WindowManager::GetInstance().RegisterWindowLifeCycleCallback(g_listener);
281 return CreateUndefined(env);
282 }
283
UnregisterWindowLifeCycleCallback(napi_env env,napi_callback_info info)284 static napi_value UnregisterWindowLifeCycleCallback(napi_env env, napi_callback_info info)
285 {
286 WindowManager::GetInstance().UnregisterWindowLifeCycleCallback(g_listener);
287 g_listener->Reset();
288 return CreateUndefined(env);
289 }
290
HandleDumpTask(napi_env env,napi_callback_info info)291 static napi_value HandleDumpTask(napi_env env, napi_callback_info info)
292 {
293 napi_ref ref = nullptr;
294 if (!GetCallbackRef(env, info, &ref)) {
295 return nullptr;
296 }
297 g_handler->SetEnv(env);
298 g_handler->SetDumpFuncRef(ref);
299 g_handler->SetJsLeakWatcherStatus(true);
300 g_handler->SendEvent(DUMP_EVENT_ID, DUMP_DELAY_TIME, LeakWatcherEventHandler::Priority::IDLE);
301 return CreateUndefined(env);
302 }
303
HandleGCTask(napi_env env,napi_callback_info info)304 static napi_value HandleGCTask(napi_env env, napi_callback_info info)
305 {
306 napi_ref ref = nullptr;
307 if (!GetCallbackRef(env, info, &ref)) {
308 return nullptr;
309 }
310 g_handler->SetEnv(env);
311 g_handler->SetGcFuncRef(ref);
312 g_handler->SendEvent(GC_EVENT_ID, GC_DELAY_TIME, LeakWatcherEventHandler::Priority::IDLE);
313 return CreateUndefined(env);
314 }
315
HandleShutdownTask(napi_env env,napi_callback_info info)316 static napi_value HandleShutdownTask(napi_env env, napi_callback_info info)
317 {
318 napi_ref ref = nullptr;
319 if (!GetCallbackRef(env, info, &ref)) {
320 return nullptr;
321 }
322 g_handler->SetEnv(env);
323 g_handler->SetShutdownFuncRef(ref);
324 return CreateUndefined(env);
325 }
326
RemoveTask(napi_env env,napi_callback_info info)327 static napi_value RemoveTask(napi_env env, napi_callback_info info)
328 {
329 g_handler->SetJsLeakWatcherStatus(false);
330 return CreateUndefined(env);
331 }
332
DumpRawHeap(napi_env env,napi_callback_info info)333 static napi_value DumpRawHeap(napi_env env, napi_callback_info info)
334 {
335 size_t argc = ONE_VALUE_LIMIT;
336 napi_value argv[ONE_VALUE_LIMIT] = {nullptr};
337 napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
338 if (argc != ONE_VALUE_LIMIT) {
339 return nullptr;
340 }
341 std::string filePath = "";
342 if (!GetNapiStringValue(env, argv[0], filePath)) {
343 return nullptr;
344 }
345 if (!CreateFile(filePath)) {
346 return nullptr;
347 }
348 NativeEngine *engine = reinterpret_cast<NativeEngine*>(env);
349 engine->DumpHeapSnapshot(filePath, true, DumpFormat::BINARY, false, true, true);
350 AppendMetaData(filePath);
351 return CreateUndefined(env);
352 }
353
DeclareJsLeakWatcherInterface(napi_env env,napi_value exports)354 napi_value DeclareJsLeakWatcherInterface(napi_env env, napi_value exports)
355 {
356 napi_property_descriptor desc[] = {
357 DECLARE_NAPI_FUNCTION("registerArkUIObjectLifeCycleCallback", RegisterArkUIObjectLifeCycleCallback),
358 DECLARE_NAPI_FUNCTION("unregisterArkUIObjectLifeCycleCallback", UnregisterArkUIObjectLifeCycleCallback),
359 DECLARE_NAPI_FUNCTION("registerWindowLifeCycleCallback", RegisterWindowLifeCycleCallback),
360 DECLARE_NAPI_FUNCTION("unregisterWindowLifeCycleCallback", UnregisterWindowLifeCycleCallback),
361 DECLARE_NAPI_FUNCTION("removeTask", RemoveTask),
362 DECLARE_NAPI_FUNCTION("handleDumpTask", HandleDumpTask),
363 DECLARE_NAPI_FUNCTION("handleGCTask", HandleGCTask),
364 DECLARE_NAPI_FUNCTION("handleShutdownTask", HandleShutdownTask),
365 DECLARE_NAPI_FUNCTION("dumpRawHeap", DumpRawHeap),
366 };
367 NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
368 return exports;
369 }
370
371 static napi_module _module = {
372 .nm_version = 0,
373 .nm_filename = nullptr,
374 .nm_register_func = DeclareJsLeakWatcherInterface,
375 .nm_modname = "jsLeakWatcherNative",
376 };
NAPI_hiviewdfx_jsLeakWatcher_AutoRegister()377 extern "C" __attribute__((constructor)) void NAPI_hiviewdfx_jsLeakWatcher_AutoRegister()
378 {
379 napi_module_register(&_module);
380 }
381