• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# JSVM-API调试&定位
2<!--Kit: NDK Development-->
3<!--Subsystem: arkcompiler-->
4<!--Owner: @yuanxiaogou; @string_sz-->
5<!--Designer: @knightaoko-->
6<!--Tester: @test_lzz-->
7<!--Adviser: @fang-jinxu-->
8
9JSVM,即标准JS引擎,是严格遵守ECMAScript规范的JavaScript代码执行引擎。详情参考:[JSVM](../reference/common/capi-jsvm.md)。
10基于JSVM的JS代码调试调优能力包括:Debugger、CPU Profiler、Heap Snapshot、Heap Statistics。涉及以下接口:
11| 接口名  |  接口功能 |
12|---|---|
13| OH_JSVM_GetVM  |  获取给定环境的虚拟机实例。 |
14| OH_JSVM_GetHeapStatistics  |  返回一组虚拟机堆的统计数据。 |
15| OH_JSVM_StartCpuProfiler  |  创建并启动一个CPU profiler。 |
16| OH_JSVM_StopCpuProfiler  |  停止CPU profiler并将结果输出到流。 |
17| OH_JSVM_TakeHeapSnapshot  |  获取当前堆快照并将其输出到流。 |
18| OH_JSVM_OpenInspector  |  在指定的主机和端口上激活inspector,将用来调试JS代码。 |
19| OH_JSVM_OpenInspectorWithName | 基于传入的 pid 和 name 激活 inspector。 |
20| OH_JSVM_CloseInspector  |  尝试关闭剩余的所有inspector连接。 |
21| OH_JSVM_WaitForDebugger  |  等待主机与inspector建立socket连接,连接建立后程序将继续运行。执行Runtime.runIfWaitingForDebugger命令。 |
22
23
24本文将介绍调试方法、CPU Profiler使用方法和Heap Snapshot使用方法。
25
26## 调试能力使用方法
27
28### 使用 OH_JSVM_OpenInspector
29
301. 在应用工程配置文件module.json中配置网络权限:
31
32```
33"requestPermissions": [{
34  "name": "ohos.permission.INTERNET",
35  "reason": "$string:app_name",
36  "usedScene": {
37    "abilities": [
38      "FromAbility"
39    ],
40    "when": "inuse"
41  }
42}]
43```
44
452. 为避免debugger过程中的暂停被误报为无响应异常,可以开启DevEco Studio的Debug模式,参考[debug启动调试](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-debug-arkts-debug-V5)(无需设置断点),或者可以在非主线程的其它线程中运行JSVM。
46```cpp
47// 在非主线程的其他线程中运行JSVM示例代码
48static napi_value RunTest(napi_env env, napi_callback_info info)
49{
50    std::thread testJSVMThread(TestJSVM);
51    testJSVMThread.detach();
52    return  nullptr;
53}
54```
553. 在执行JS代码之前,调用OH_JSVM_OpenInspector在指定的主机和端口上激活inspector,创建socket。例如OH_JSVM_OpenInspector(env, "localhost", 9225),在端侧本机端口9225创建socket。
564. 调用OH_JSVM_WaitForDebugger,等待建立socket连接。
575. 检查端侧端口是否打开成功。hdc shell "netstat -anp | grep 9225"。结果为9225端口状态为“LISTEN"即可。
586. 转发端口。hdc fport tcp:9229 tcp:9225。转发开发者个人计算机侧端口9229到端侧端口9225。结果为"Forwardport result:OK"即可。
597. 在chrome浏览器地址栏输入"localhost:9229/json",回车。获取端口连接信息。拷贝"devtoolsFrontendUrl"字段url内容到地址栏,回车,进入DevTools源码页,将看到在应用中通过OH_JSVM_RunScript执行的JS源码,此时暂停在第一行JS源码处。(注:"devtoolsFrontendUrl"字段url只支持使用Chrome、Edge浏览器打开,不支持使用Firefox、Safari等浏览器打开。)
608. 用户可在源码页打断点,通过按钮发出各种调试命令控制JS代码执行,并查看变量。
619. 调用OH_JSVM_CloseInspector关闭inspector,结束socket连接。
62
63**示例代码**
64JSVM-API接口开发流程参考[使用JSVM-API实现JS与C/C++语言交互开发流程](use-jsvm-process.md),本文仅对接口对应C++相关代码进行展示。
65```cpp
66#include "ark_runtime/jsvm.h"
67
68#include <string>
69
70using namespace std;
71
72// 待调试的JS源码
73static string srcDebugger = R"JS(
74const concat = (...args) => args.reduce((a, b) => a + b);
75var dialogue = concat('"What ', 'is ', 'your ', 'name ', '?"');
76dialogue = concat(dialogue, ' --', '"My ', 'name ', 'is ', 'Bob ', '."');
77)JS";
78
79// 开启debugger
80static void EnableInspector(JSVM_Env env) {
81    // 在指定的主机和端口上激活inspector,创建socket。
82    OH_JSVM_OpenInspector(env, "localhost", 9225);
83    // 等待建立socket连接。
84    OH_JSVM_WaitForDebugger(env, true);
85}
86
87// 关闭debugger
88static void CloseInspector(JSVM_Env env) {
89    // 关闭inspector,结束socket连接。
90    OH_JSVM_CloseInspector(env);
91}
92
93static void RunScript(JSVM_Env env) {
94    JSVM_HandleScope handleScope;
95    OH_JSVM_OpenHandleScope(env, &handleScope);
96
97    JSVM_Value jsSrc;
98    OH_JSVM_CreateStringUtf8(env, srcDebugger.c_str(), srcDebugger.size(), &jsSrc);
99
100    JSVM_Script script;
101    OH_JSVM_CompileScript(env, jsSrc, nullptr, 0, true, nullptr, &script);
102
103    JSVM_Value result;
104    OH_JSVM_RunScript(env, script, &result);
105
106    OH_JSVM_CloseHandleScope(env, handleScope);
107}
108
109void TestJSVM() {
110    JSVM_InitOptions initOptions{};
111    OH_JSVM_Init(&initOptions);
112
113    JSVM_VM vm;
114    OH_JSVM_CreateVM(nullptr, &vm);
115    JSVM_VMScope vmScope;
116    OH_JSVM_OpenVMScope(vm, &vmScope);
117
118    JSVM_Env env;
119    OH_JSVM_CreateEnv(vm, 0, nullptr, &env);
120    // 执行JS代码之前打开debugger。
121    EnableInspector(env);
122    JSVM_EnvScope envScope;
123    OH_JSVM_OpenEnvScope(env, &envScope);
124
125    // 执行JS代码。
126    RunScript(env);
127
128    OH_JSVM_CloseEnvScope(env, envScope);
129    // 执行JS代码之后关闭debugger。
130    CloseInspector(env);
131    OH_JSVM_DestroyEnv(env);
132    OH_JSVM_CloseVMScope(vm, vmScope);
133    OH_JSVM_DestroyVM(vm);
134}
135
136```
137
138### 使用 OH_JSVM_OpenInspectorWithName
139
1401. 在应用工程配置文件module.json中配置网络权限:
141
142```
143"requestPermissions": [{
144  "name": "ohos.permission.INTERNET",
145  "reason": "$string:app_name",
146  "usedScene": {
147    "abilities": [
148      "FromAbility"
149    ],
150    "when": "inuse"
151  }
152}]
153```
154
1552. 为避免debugger过程中的暂停被误报为无响应异常,可以[开启DevEco Studio的Debug模式](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-debug-arkts-debug-V5)(无需设置断点),或者可以在非主线程的其他线程中运行JSVM。
1563. 打开 inspector 端口,连接 devtools 用于调试,其流程如下:  在执行JS代码之前,调用OH_JSVM_OpenInspector在指定的主机和端口上激活inspector,创建socket。例如OH_JSVM_OpenInspectorWithName(env, 123, "test"),创建 tcp socket 及其对应的 unixdomain 端口。
1574. 调用OH_JSVM_WaitForDebugger,等待建立socket连接。
1585. 检查端侧端口是否打开成功。hdc shell "cat /proc/net/unix | grep jsvm"。结果出现可用的 unix 端口即可,如: jsvm_devtools_remote_9229_123,其中 9229 为 tcp 端口号,123 为对应的 pid。
1596. 转发端口。hdc fport tcp:9229 tcp:9229。转发开发者个人计算机侧端口9229到端侧端口9229。结果为"Forwardport result:OK"即可。
1607. 在 chrome 浏览器地址栏输入 "localhost:9229/json",回车。获取端口连接信息。打开Chrome开发者工具,拷贝"devtoolsFrontendUrl"字段url内容到地址栏,回车,进入DevTools源码页,将看到在应用中通过OH_JSVM_RunScript执行的JS源码,此时暂停在第一行JS源码处。(注:"devtoolsFrontendUrl"字段url只支持使用Chrome、Edge浏览器打开,不支持使用Firefox、Safari等浏览器打开。)
1618. 用户可在源码页打断点,通过按钮发出各种调试命令控制JS代码执行,并查看变量。
1629. 调用OH_JSVM_CloseInspector关闭inspector,结束socket连接。
163
164**代码示例**
165
166对应的 enable inspector 替换为下面的即可
167```cpp
168// 开启debugger
169static void EnableInspector(JSVM_Env env) {
170    // 在指定的主机和端口上激活inspector,创建socket。
171    OH_JSVM_OpenInspectorWithName(env, 123, "test");
172    // 等待建立socket连接。
173    OH_JSVM_WaitForDebugger(env, true);
174}
175```
176
177### 使用 Chrome inspect 页面进行调试
178除了使用上述打开"devtoolsFrontendUrl"字段url的方法调试代码之外,也可以直接通过Chrome浏览器的 chrome://inspect/#devices 页面进行调试。方法如下:
1791. Chrome浏览器中打开 chrome://inspect/#devices,勾选以下内容:
180   <div align=left><img src="figures/jsvm-debugger-cpuprofiler-heapsnapshot_1.png"/></div>
1812. 执行端口转发命令:hdc fport [开发者个人计算机侧端口号] [端侧端口号]
182例如:hdc fport tcp:9227 tcp:9226
1831. 点击Port forwarding按钮,左侧输入开发者个人计算机侧端口,右侧输入端侧端口号,点击done。如下图所示:
184   <div align=left><img src="figures/jsvm-debugger-cpuprofiler-heapsnapshot_2.png"/></div>
1852. 点击Configure按钮,输入开发者个人计算机侧的端口号,如localhost:9227。如下图所示:
186   <div align=left><img src="figures/jsvm-debugger-cpuprofiler-heapsnapshot_3.png"/></div>
1873. 稍等片刻,会在target下出现调试的内容,点击inspect即可调试。如下图所示:
188   <div align=left><img src="figures/jsvm-debugger-cpuprofiler-heapsnapshot_4.png"/></div>
189
190### 使用 websocket 端口进行调试
191除了使用上述打开 "devtoolsFrontendUrl" 字段url的方法通过网页端 chrome devtools 调试代码之外,如果读者了解如何使用 CDP 协议代替网页端 devtools 功能,也可以通过连接 inspector 提供的 websocket 端口进行调试。
192
193其中连接 websocket 的方法为,根据前面提供的网页端调试步骤,在做完端口映射之后(如映射到 9229 端口),在 chrome 浏览器地址栏输入 "localhost:9229/json",回车,获取"webSocketDebuggerUrl" 字段所对应的 url,然后使用标准的 websocket 客户端连接这个 url 即可发送 CDP 调试协议进行调试。需要注意的是,当前版本 inspector 提供的websocket 端口仅支持接收 Text Frame, Ping Frame 和 Connection Close Frame,所有其他类型的帧都会被视为错误帧而导致 websocket 连接中断。
194
195CDP 协议可以参考 chrome 的[官方文档](https://chromedevtools.github.io/devtools-protocol/)
196
197## CPU Profiler及Heap Snapshot使用方法
198
199### CPU Profiler接口使用方法
200
2011. 在执行JS代码之前,调用OH_JSVM_StartCpuProfiler开始采样并返回JSVM_CpuProfiler。
2022. 在执行JS代码后,调用OH_JSVM_StopCpuProfiler,传入1中返回的JSVM_CpuProfiler,传入输出流回调及输出流指针。数据将会写入指定的输出流中。
2033. 输出数据为JSON字符串。可存入.cpuprofile文件中。该文件类型可导入Chrome浏览器-DevTools-JavaScript Profiler工具中解析成性能分析视图。
204
205### Heap Snapshot接口使用方法
206
2071. 为分析某段JS代码的堆对象创建情况,可在执行JS代码前后,分别调用一次OH_JSVM_TakeHeapSnapshot。传入输出流回调及输出流指针。数据将会写入指定的输出流中。
2082. 输出数据可存入.heapsnapshot文件中。该文件类型可导入Chrome浏览器-DevTools-Memory工具中解析成内存分析视图。
209
210### 示例代码
211JSVM-API接口开发流程参考[使用JSVM-API实现JS与C/C++语言交互开发流程](use-jsvm-process.md),本文仅对接口对应C++相关代码进行展示。
212
213```cpp
214#include "ark_runtime/jsvm.h"
215
216#include <fstream>
217#include <iostream>
218
219using namespace std;
220
221// 待调优的JS代码。
222static string srcProf = R"JS(
223function sleep(delay) {
224    var start = (new Date()).getTime();
225    while ((new Date()).getTime() - start < delay) {
226        continue;
227    }
228}
229
230function work3() {
231    sleep(300);
232}
233
234function work2() {
235    work3();
236    sleep(200);
237}
238
239function work1() {
240    work2();
241    sleep(100);
242}
243
244work1();
245)JS";
246
247// 数据输出流回调,用户自定义,处理返回的调优数据,此处以写入文件为例。
248static bool OutputStream(const char *data, int size, void *streamData) {
249    auto &os = *reinterpret_cast<ofstream *>(streamData);
250    if (data) {
251        os.write(data, size);
252    } else {
253        os.close();
254    }
255    return true;
256}
257
258static JSVM_CpuProfiler ProfilingBegin(JSVM_VM vm) {
259    // 文件输出流,保存调优数据,/data/storage/el2/base/files为沙箱路径。以包名为com.example.helloworld为例。
260    // 实际文件会保存到/data/app/el2/100/base/com.example.helloworld/files/heap-snapshot-begin.heapsnapshot261    ofstream heapSnapshot("/data/storage/el2/base/files/heap-snapshot-begin.heapsnapshot",
262                          ios::out | ios::binary | ios::trunc);
263    // 执行JS前获取一次Heap Snapshot数据。
264    OH_JSVM_TakeHeapSnapshot(vm, OutputStream, &heapSnapshot);
265    JSVM_CpuProfiler cpuProfiler;
266    // 开启CPU Profiler。
267    OH_JSVM_StartCpuProfiler(vm, &cpuProfiler);
268    return cpuProfiler;
269}
270
271// 关闭调优数据采集工具
272static void ProfilingEnd(JSVM_VM vm, JSVM_CpuProfiler cpuProfiler) {
273    // 文件输出流,保存调优数据,/data/storage/el2/base/files为沙箱路径。以包名为com.example.helloworld为例。
274    // 实际文件会保存到/data/app/el2/100/base/com.example.helloworld/files/cpu-profile.cpuprofile275    ofstream cpuProfile("/data/storage/el2/base/files/cpu-profile.cpuprofile",
276                        ios::out | ios::binary | ios::trunc);
277    // 关闭CPU Profiler,获取数据。
278    OH_JSVM_StopCpuProfiler(vm, cpuProfiler, OutputStream, &cpuProfile);
279    ofstream heapSnapshot("/data/storage/el2/base/files/heap-snapshot-end.heapsnapshot",
280                              ios::out | ios::binary | ios::trunc);
281    // 执行JS后再获取一次Heap Snapshot数据,与执行前数据作对比,以分析内存问题或者进行内存调优。
282    OH_JSVM_TakeHeapSnapshot(vm, OutputStream, &heapSnapshot);
283}
284
285static JSVM_Value RunScriptWithStatistics(JSVM_Env env, JSVM_CallbackInfo info) {
286    JSVM_VM vm;
287    OH_JSVM_GetVM(env, &vm);
288
289    // 开始调优。
290    auto cpuProfiler = ProfilingBegin(vm);
291
292    JSVM_HandleScope handleScope;
293    OH_JSVM_OpenHandleScope(env, &handleScope);
294
295    JSVM_Value jsSrc;
296    OH_JSVM_CreateStringUtf8(env, srcProf.c_str(), srcProf.size(), &jsSrc);
297
298    JSVM_Script script;
299    OH_JSVM_CompileScript(env, jsSrc, nullptr, 0, true, nullptr, &script);
300
301    JSVM_Value result;
302    // 执行JS代码。
303    OH_JSVM_RunScript(env, script, &result);
304
305    OH_JSVM_CloseHandleScope(env, handleScope);
306
307    // 结束调优。
308    ProfilingEnd(vm, cpuProfiler);
309    return nullptr;
310}
311static JSVM_CallbackStruct param[] = {
312    {.data = nullptr, .callback = RunScriptWithStatistics},
313};
314static JSVM_CallbackStruct *method = param;
315// runScriptWithStatistics方法别名,供JS调用
316static JSVM_PropertyDescriptor descriptor[] = {
317    {"runScriptWithStatistics", nullptr, method++, nullptr, nullptr, nullptr, JSVM_DEFAULT},
318};
319```
320样例测试JS
321```cpp
322const char *srcCallNative = R"JS(runScriptWithStatistics();)JS";
323```
324预计的输出结果:
325```
326在对应鸿蒙设备内生成两个文件用于后续调优:
327heap-snapshot-end.heapsnapshot,
328cpu-profile.cpuprofile
329文件功能见上文接口使用方法介绍
330```
331