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.heapsnapshot。 239 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.cpuprofile。 253 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