nodejs native 插件调研

背景

nodejs的native插件是典型的混合语言开发场景(js + C)。为了辅助评估多语言运行时的价值,希望获取nodejs社区中native插件的总代码量。

方法

nodejs社区有中心分发系统,可以认为绝大部分的native插件都会进入npmjs这个中心网站。我们可以利用npmjs提供的查询功能来统计native 插件的数量,获取插件的源码,然后使用cloc等工具来最终得到代码行数。

获取native插件总量

npmjs中没有特异性的标签用于确定哪一个npm包是native插件,获取插件总量的确切数量非常困难。
一个比较取巧的方案是利用npmjs中的包依赖信息。nodejs主要的native插件都依赖两个接口机制NAN(Native Abstractions for Node.js 其较为成熟)和NAPI(较新的接口层,功能更强更简洁)。这两个机制分别对应https://www.npmjs.com/package/nanhttps://www.npmjs.com/package/node-addon-api 这两个npm包。因此,只需要查看这两个npm包被依赖的数量,就可以估算出npmjs中的native插件数量了。在本文档写作时,NAN有5564个依赖,NAPI有986个依赖。
较为粗略的估算,当前nodejs的native插件数量大致是6500个

获取native插件的代码并统计代码行数

使用cloc等工具统计代码较为简单,核心问题是需要从nan和napi包的依赖信息中抽取出各native插件的源代码。
调研了各种方案后,当前的唯一可行的方案是(需要说明的是,即使是使用这个方案,也无法完整获取所有的信息。因为在超过396个依赖后,npmjs就拒绝再展示后面的依赖条目了):
基于 https://www.npmjs.com/browse/depended/nan 这样的页面信息分析nan的依赖包 (npmjs rest api接口中已经无法直接获取依赖信息了,可能是由于负载较重的原因)
取出包名后,通过 npmjs 的 restapi 可以查询到该 native 插件的源代码地址
使用git 取出源代码后,使用cloc 统计代码行数

第一条的依赖分析可使用现成的npm包,如下所示,可以将依赖信息导出到文本文件中。

1
2
3
npm install npm-dependants
npx npm-dependants node-addon-api &> napi_dep.txt
npx npm-dependants nan &> nan_dep.txt

使用curl可以访问内npmjs的restapi获取插件的代码位置,示例脚本如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#set -x
#dep_file是前面获得的依赖包文件
dep_file=./napi_dep.txt
result_dir="./results_${dep_file##*/}"
rm ${result_dir} -rf; mkdir ${result_dir}
for pkg in `cat ${dep_file}`
do
#pkg="node-stringprep"
output=`curl https://registry.npmjs.org/${pkg}/latest`
echo "now ##$pkg##"
for field in ${output}
do
#"url":"https://
#,"url":"git+https:
used_line=`echo ${field}|grep '"repository":{"type":"git","url":"'`
if [ $? -eq 0 ] ; then
used_line=${used_line##*\"repository\"\:\{\"type\"\:\"git\"\,\"url\"\:\"}
used_line=${used_line%%\"\}\,\"*}

used_line=${used_line##*github.com\/}

echo "getting git://github.com/${used_line}"
bash ./cloc-git.sh git://github.com/${used_line} > ${result_dir}/${used_line##*/}.log
fi
done
done

上面文件依赖的cloc-git.sh 是一个对cloc工具的简单封装,如下代码所示.

1
2
3
4
5
#!/usr/bin/env bash
git clone --depth 1 "$1" temp-linecount-repo &&
printf "\nnow $1\n\n" &&
cloc temp-linecount-repo &&
rm -rf temp-linecount-repo

上面步骤执行完成后,可以得到一系列cloc的输出文件,再用如下脚本抽取所需的代码行号信息。
注意我们不能直接提取SUM的结果,因为native 插件中可能包含原始的工程项目(如C代码和其相关的文档说明)。
为了减小干扰,我们只提取c++和js的信息(插件中包含原c++工程的,后续通过代码行数量来过滤)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#set -x
dep_file=./napi_dep.txt
result_dir="./results_${dep_file##*/}"
for file in `ls ${result_dir}/*.log`
do
cpp_header_comment=`cat ${file} |grep 'C++ Header' -w | awk '{print $5}'`
cpp_header_code=`cat ${file}|grep 'C++ Header' -w | awk '{print $6}'`
cpp_comment=`cat ${file} |grep '^C++ ' -w| awk '{print $4}'`
cpp_code=`cat ${file} |grep '^C++ ' -w | awk '{print $5}'`
js_comment=`cat ${file} |grep '^JavaScript ' -w | awk '{print $4}'`
js_code=`cat ${file} |grep '^JavaScript ' -w | awk '{print $5}'`
#echo ${file}
echo "${file}:${cpp_header_comment}:${cpp_header_code}:${cpp_comment}:${cpp_code}:${js_comment}:${js_code}"
done

这里可以得到如下所示的输出。

1
2
3
results/64.git.log:63:1941:13:45:0:119
results/ApplicationInsights-node.js-native-metrics.git.log:2:189:9:139:3:45
results/BridJS.git.log:1241:1475:708:3247:654:1426

这样的数据就便于分析了,可以按需进行数据处理。

估算总代码行数

在得到上面的分析数据后,通过抽样观察,过滤掉满足如下条件之一的项目(以屏蔽native插件中包含原工程的场景):
1 c++代码行超过10000行
2 c++头文件代码超过5000行
3 js代码超过5000行

对nan总共得到270条数据,数值如下。

c++_header_comment cpp_header_code cpp_comment cpp_code js_comment js_code SUM
286.678392 584.8241206 96.65055762 848.3197026 147.6666667 1026.724138 2990.863577

对napi总共得到225条数据,数值如下。

c++_header_comment cpp_header_code cpp_comment cpp_code js_comment js_code SUM
237.8641975 450.3703704 66.64516129 536.4193548 56.06542056 460.5 1807.864505

根据上述数据,估算native插件总代码数量如下:
2000 行/每个插件 * 6500 个插件 = 6.5k * 2k = 13m = 一千三百万行
较为粗略的估算,当前nodejs的native插件总代码行为 一千三百万行 。

需要注意到,整个分析过程中出现了很多干扰因素,上面的数据可能产生较大偏差。

可能导致数据低估的因素:
有一些由于行数过多被过滤掉的项目,其实并没有包含原工程(如nodegui)。
NAPI中出现了对接Object C++和RUST的情况,这些语言的代码行没有被统计

可能导致数据高估的因素:
过滤后仍然有部分项目含有了原工程

可能导致数据不定向偏差的因素:
由于npmjs的限制,只能获得396个插件的抽样统计数据,抽样导致的偏差不可控

结论

依据上面的分析,nodejs的native插件生态维护的代码行数应该在数百万行到数千万行之间。