• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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