1# JSVM-API调试&定位
2
3JSVM,既标准JS引擎,是严格遵守Ecmascript规范的JavaScript代码执行引擎。 详情参考:[JSVM](../reference/common/_j_s_v_m.md)。
4基于JSVM的JS代码调试调优能力包括:Debugger、CPU Profiler、Heap Snapshot、Heap Statistics。涉及以下接口:
5| 接口名  |  接口功能 |
6|---|---|
7| OH_JSVM_GetVM  |  将检索给定环境的虚拟机实例。 |
8| OH_JSVM_GetHeapStatistics  |  返回一组虚拟机堆的统计数据。 |
9| OH_JSVM_StartCpuProfiler  |  创建并启动一个CPU profiler。 |
10| OH_JSVM_StopCpuProfiler  |  停止CPU profiler并将结果输出到流。 |
11| OH_JSVM_TakeHeapSnapshot  |  获取当前堆快照并将其输出到流。 |
12| OH_JSVM_OpenInspector  |  在指定的主机和端口上激活inspector,将用来调试JS代码。 |
13| OH_JSVM_OpenInspectorWithName | 基于传入的 pid 和 name 激活 inspector。 |
14| OH_JSVM_CloseInspector  |  尝试关闭剩余的所有inspector连接。 |
15| OH_JSVM_WaitForDebugger  |  等待主机与inspector建立socket连接,连接建立后程序将继续运行。发送Runtime.runIfWaitingForDebugger命令。 |
16
17
18本文将介绍调试、CPU Profiler、Heap Snapshot的使用方法。
19
20## 调试能力使用方法
21
22### 使用 OH_JSVM_OpenInspector
23
241. 在应用工程配置文件module.json中配置网络权限:
25
26```
27"requestPermissions": [{
28  "name": "ohos.permission.INTERNET",
29  "reason": "$string:app_name",
30  "usedScene": {
31    "abilities": [
32      "FromAbility"
33    ],
34    "when": "inuse"
35  }
36}]
37```
38
392. 为避免debugger过程中的暂停被误报为无响应异常,可以[开启DevEco Studio的Debug模式](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-debug-arkts-debug-V5)(无需设置断点),或者可以在非主线程的其他线程中运行JSVM。
403. 在执行JS代码之前,调用OH_JSVM_OpenInspector在指定的主机和端口上激活inspector,创建socket。例如OH_JSVM_OpenInspector(env, "localhost", 9225),在端侧本机端口9225创建socket。
414. 调用OH_JSVM_WaitForDebugger,等待建立socket连接。
425. 检查端侧端口是否打开成功。hdc shell "netstat -anp | grep 9225"。结果为9225端口状态为“LISTEN"即可。
436. 转发端口。hdc fport tcp:9229 tcp:9225。转发PC侧端口9229到端侧端口9225。结果为"Forwardport result:OK"即可。
447. 在chrome浏览器地址栏输入"localhost:9229/json",回车。获取端口连接信息。拷贝"devtoolsFrontendUrl"字段url内容到地址栏,回车,进入DevTools源码页,将看到在应用中通过OH_JSVM_RunScript执行的JS源码,此时暂停在第一行JS源码处。(注:"devtoolsFrontendUrl"字段url只支持使用Chrome、Edge浏览器打开,不支持使用Firefox、Safari等浏览器打开。)
458. 用户可在源码页打断点,通过按钮发出各种调试命令控制JS代码执行,并查看变量。
469. 调用OH_JSVM_CloseInspector关闭inspector,结束socket连接。
47
48#### 示例代码
49JSVM-API接口开发流程参考[使用JSVM-API实现JS与C/C++语言交互开发流程](use-jsvm-process.md),本文仅对接口对应C++相关代码进行展示。
50```cpp
51#include "ark_runtime/jsvm.h"
52
53#include <string>
54
55using namespace std;
56
57// 待调试的JS源码
58static string srcDebugger = R"JS(
59const concat = (...args) => args.reduce((a, b) => a + b);
60var dialogue = concat('"What ', 'is ', 'your ', 'name ', '?"');
61dialogue = concat(dialogue, ' --', '"My ', 'name ', 'is ', 'Bob ', '."');
62)JS";
63
64// 开启debugger
65static void EnableInspector(JSVM_Env env) {
66    // 在指定的主机和端口上激活inspector,创建socket。
67    OH_JSVM_OpenInspector(env, "localhost", 9225);
68    // 等待建立socket连接。
69    OH_JSVM_WaitForDebugger(env, true);
70}
71
72// 关闭debugger
73static void CloseInspector(JSVM_Env env) {
74    // 关闭inspector,结束socket连接。
75    OH_JSVM_CloseInspector(env);
76}
77
78static void RunScript(JSVM_Env env) {
79    JSVM_HandleScope handleScope;
80    OH_JSVM_OpenHandleScope(env, &handleScope);
81
82    JSVM_Value jsSrc;
83    OH_JSVM_CreateStringUtf8(env, srcDebugger.c_str(), srcDebugger.size(), &jsSrc);
84
85    JSVM_Script script;
86    OH_JSVM_CompileScript(env, jsSrc, nullptr, 0, true, nullptr, &script);
87
88    JSVM_Value result;
89    OH_JSVM_RunScript(env, script, &result);
90
91    OH_JSVM_CloseHandleScope(env, handleScope);
92}
93
94void TestJSVM() {
95    JSVM_InitOptions initOptions{};
96    OH_JSVM_Init(&initOptions);
97
98    JSVM_VM vm;
99    OH_JSVM_CreateVM(nullptr, &vm);
100    JSVM_VMScope vmScope;
101    OH_JSVM_OpenVMScope(vm, &vmScope);
102
103    JSVM_Env env;
104    OH_JSVM_CreateEnv(vm, 0, nullptr, &env);
105    // 执行JS代码之前打开debugger。
106    EnableInspector(env);
107    JSVM_EnvScope envScope;
108    OH_JSVM_OpenEnvScope(env, &envScope);
109
110    // 执行JS代码。
111    RunScript(env);
112
113    OH_JSVM_CloseEnvScope(env, envScope);
114    // 执行JS代码之后关闭debugger。
115    CloseInspector(env);
116    OH_JSVM_DestroyEnv(env);
117    OH_JSVM_CloseVMScope(vm, vmScope);
118    OH_JSVM_DestroyVM(vm);
119}
120
121```
122
123### 使用 OH_JSVM_OpenInspectorWithName
124
1251. 在应用工程配置文件module.json中配置网络权限:
126
127```
128"requestPermissions": [{
129  "name": "ohos.permission.INTERNET",
130  "reason": "$string:app_name",
131  "usedScene": {
132    "abilities": [
133      "FromAbility"
134    ],
135    "when": "inuse"
136  }
137}]
138```
139
1402. 为避免debugger过程中的暂停被误报为无响应异常,可以[开启DevEco Studio的Debug模式](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-debug-arkts-debug-V5)(无需设置断点),或者可以在非主线程的其他线程中运行JSVM。
1413. 打开 inspector 端口, 链接 devtools 用于调试, 其流程如下:  在执行JS代码之前,调用OH_JSVM_OpenInspector在指定的主机和端口上激活inspector,创建socket。例如OH_JSVM_OpenInspectorWithName(env, 123, “test”),创建 tcp socket 及其对应的 unixdomain 端口。
1424. 调用OH_JSVM_WaitForDebugger,等待建立socket连接。
1435. 检查端侧端口是否打开成功。hdc shell "cat /proc/net/unix | grep jsvm"。结果出现可用的 unix 端口即可, 如: jsvm_devtools_remote_9229_123, 其中 9229 为 tcp 端口号, 123 为对应的 pid。
1446. 转发端口。hdc fport tcp:9229 tcp:9229。转发PC侧端口9229到端侧端口9229。结果为"Forwardport result:OK"即可。
1457. 在 chrome 浏览器地址栏输入 "localhost:9229/json",回车。获取端口连接信息。打开Chrome开发者工具,拷贝"devtoolsFrontendUrl"字段url内容到地址栏,回车,进入DevTools源码页,将看到在应用中通过OH_JSVM_RunScript执行的JS源码,此时暂停在第一行JS源码处。(注:"devtoolsFrontendUrl"字段url只支持使用Chrome、Edge浏览器打开,不支持使用Firefox、Safari等浏览器打开。)
1468. 用户可在源码页打断点,通过按钮发出各种调试命令控制JS代码执行,并查看变量。
1479. 调用OH_JSVM_CloseInspector关闭inspector,结束socket连接。
148
149#### 代码示例
150
151对应的 enable inspector 替换为下面的即可
152```cpp
153// 开启debugger
154static void EnableInspector(JSVM_Env env) {
155    // 在指定的主机和端口上激活inspector,创建socket。
156    OH_JSVM_OpenInspectorWithName(env, 123, "test");
157    // 等待建立socket连接。
158    OH_JSVM_WaitForDebugger(env, true);
159}
160```
161
162### 使用 Chrome inspect 页面进行调试
163除了使用上述打开"devtoolsFrontendUrl"字段url的方法调试代码之外,也可以直接通过Chrome浏览器的 chrome://inspect/#devices 页面进行调试。方法如下:
1641. Chrome浏览器中打开 chrome://inspect/#devices,勾选以下内容:
165   <div align=left><img src="figures/jsvm-debugger-cpuprofiler-heapsnapshot_1.png"/></div>
1662. 执行端口转发命令:hdc fport [pc侧端口号] [端侧端口号]
167例如:hdc fport tcp:9227 tcp:9226
1681. 点击Port forwarding按钮,左侧输入pc侧端口,右侧输入端侧端口号,点击done。如下图所示:
169   <div align=left><img src="figures/jsvm-debugger-cpuprofiler-heapsnapshot_2.png"/></div>
1702. 点击Configure按钮,输入pc侧的端口号,如localhost:9227。如下图所示:
171   <div align=left><img src="figures/jsvm-debugger-cpuprofiler-heapsnapshot_3.png"/></div>
1723. 稍等片刻,会在target下出现调试的内容,点击inspect即可调试。如下图所示:
173   <div align=left><img src="figures/jsvm-debugger-cpuprofiler-heapsnapshot_4.png"/></div>
174
175## CPU Profiler及Heap Snapshot使用方法
176
177### CPU Profiler接口使用方法
178
1791. 在执行JS代码之前,调用OH_JSVM_StartCpuProfiler开始采样并返回JSVM_CpuProfiler。
1802. 在执行JS代码后,调用OH_JSVM_StopCpuProfiler,传入1中返回的JSVM_CpuProfiler,传入输出流回调及输出流指针。数据将会写入指定的输出流中。
1813. 输出数据为JSON字符串。可存入.cpuprofile文件中。该文件类型可导入Chrome浏览器-DevTools-JavaScript Profiler工具中解析成性能分析视图。
182
183### Heap Snapshot接口使用方法
184
1851.为分析某段JS代码的堆对象创建情况。可在执行JS代码前后,分别调用一次OH_JSVM_TakeHeapSnapshot。传入输出流回调及输出流指针。数据将会写入指定的输出流中。
1862.输出数据可存入.heapsnapshot文件中。该文件类型可导入Chrome浏览器-DevTools-Memory工具中解析成内存分析视图。
187
188### 示例代码
189JSVM-API接口开发流程参考[使用JSVM-API实现JS与C/C++语言交互开发流程](use-jsvm-process.md),本文仅对接口对应C++相关代码进行展示。
190
191```cpp
192#include "ark_runtime/jsvm.h"
193
194#include <fstream>
195#include <iostream>
196
197using namespace std;
198
199// 待调优的JS代码。
200static string srcProf = R"JS(
201function sleep(delay) {
202    var start = (new Date()).getTime();
203    while ((new Date()).getTime() - start < delay) {
204        continue;
205    }
206}
207
208function work3() {
209    sleep(300);
210}
211
212function work2() {
213    work3();
214    sleep(200);
215}
216
217function work1() {
218    work2();
219    sleep(100);
220}
221
222work1();
223)JS";
224
225// 数据输出流回调,用户自定义,处理返回的调优数据,此处以写入文件为例。
226static bool OutputStream(const char *data, int size, void *streamData) {
227    auto &os = *reinterpret_cast<ofstream *>(streamData);
228    if (data) {
229        os.write(data, size);
230    } else {
231        os.close();
232    }
233    return true;
234}
235
236static JSVM_CpuProfiler ProfilingBegin(JSVM_VM vm) {
237    // 文件输出流,保存调优数据,/data/storage/el2/base/files为沙箱路径。以包名为com.example.helloworld为例。
238    // 实际文件会保存到/data/app/el2/100/base/com.example.helloworld/files/heap-snapshot-begin.heapsnapshot239    ofstream heapSnapshot("/data/storage/el2/base/files/heap-snapshot-begin.heapsnapshot",
240                          ios::out | ios:: binary | ios::trunc);
241    // 执行JS前获取一次Heap Snapshot数据。
242    OH_JSVM_TakeHeapSnapshot(vm, OutputStream, &heapSnapshot);
243    JSVM_CpuProfiler cpuProfiler;
244    // 开启CPU Profiler。
245    OH_JSVM_StartCpuProfiler(vm, &cpuProfiler);
246    return cpuProfiler;
247}
248
249// 关闭调优数据采集工具
250static void ProfilingEnd(JSVM_VM vm, JSVM_CpuProfiler cpuProfiler) {
251    // 文件输出流,保存调优数据,/data/storage/el2/base/files为沙箱路径。以包名为com.example.helloworld为例。
252    // 实际文件会保存到/data/app/el2/100/base/com.example.helloworld/files/cpu-profile.cpuprofile253    ofstream cpuProfile("/data/storage/el2/base/files/cpu-profile.cpuprofile",
254                        ios::out | ios:: binary | ios::trunc);
255    // 关闭CPU Profiler,获取数据。
256    OH_JSVM_StopCpuProfiler(vm, cpuProfiler, OutputStream, &cpuProfile);
257    ofstream heapSnapshot("/data/storage/el2/base/files/heap-snapshot-end.heapsnapshot",
258                              ios::out | ios:: binary | ios::trunc);
259    // 执行JS后再获取一次Heap Snapshot数据,与执行前数据作对比,以分析内存问题或者进行内存调优。
260    OH_JSVM_TakeHeapSnapshot(vm, OutputStream, &heapSnapshot);
261}
262
263static JSVM_Value RunScriptWithStatistics(JSVM_Env env, JSVM_CallbackInfo info) {
264    JSVM_VM vm;
265    OH_JSVM_GetVM(env, &vm);
266
267    // 开始调优。
268    auto cpuProfiler = ProfilingBegin(vm);
269
270    JSVM_HandleScope handleScope;
271    OH_JSVM_OpenHandleScope(env, &handleScope);
272
273    JSVM_Value jsSrc;
274    OH_JSVM_CreateStringUtf8(env, srcProf.c_str(), srcProf.size(), &jsSrc);
275
276    JSVM_Script script;
277    OH_JSVM_CompileScript(env, jsSrc, nullptr, 0, true, nullptr, &script);
278
279    JSVM_Value result;
280    // 执行JS代码。
281    OH_JSVM_RunScript(env, script, &result);
282
283    OH_JSVM_CloseHandleScope(env, handleScope);
284
285    // 结束调优。
286    ProfilingEnd(vm, cpuProfiler);
287    return nullptr;
288}
289static JSVM_CallbackStruct param[] = {
290    {.data = nullptr, .callback = RunScriptWithStatistics},
291};
292static JSVM_CallbackStruct *method = param;
293// runScriptWithStatistics方法别名,供JS调用
294static JSVM_PropertyDescriptor descriptor[] = {
295    {"runScriptWithStatistics", nullptr, method++, nullptr, nullptr, nullptr, JSVM_DEFAULT},
296};
297```
298
299
300// 样例测试JS
301```cpp
302const char *srcCallNative = R"JS(runScriptWithStatistics();)JS";
303```
304