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.heapsnapshot。 261 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.cpuprofile。 275 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