在go中执行 Javascript 代码

filebeat的javascript插件是怎么run起来的?
filebeat 是由golang 实现的,其间接引用了https://github.com/dop251/goja包,该包使用纯golang语言实现了 ECMAScript 5.1.

内嵌脚本语言,其实并不少见。比如常用的Lua,就被内嵌在redis中。javascript 相对于lua,是非常灵活的,学习成本也相对较低。

经过调研,实现javascript运行时的包有如下几个:

  • github.com/robertkrimen/otto [Star 7.1K]
    • 最初使的项目
    • 性能较低
    • go 1.18
    • ECMA 不支持严格模式
    • 正则不完全兼容
    • es6 特性不支持
  • github.com/dop251/goja [Star 3.5K]
    • filebeat使用
    • 思想来源于otto
    • go 1.16
    • ECMAScript 5.1 引擎
    • 支持 regex and strict mode
    • 在频繁执行较简单的js情况下,性能与otto基本持平,高于v8go
  • github.com/rogchap/v8go [Star 2.5K]
    • 基于google的V8引擎实现(chrome)
    • cgo实现,性能最高(在需要执行较长的js代码的情况下),与系统兼容性略差.(windows可能需要自己编译v8)
    • javascript兼容性比较好
    • 调试困难

otto case

 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
27
28
29
30
31
32
33
34
35
36
37
package main

import (
        "fmt"

        "github.com/robertkrimen/otto"
)

func main() {
        vm := otto.New()

        // 注入变量
        vm.Set("def", map[string]interface{}{"abc": 123})
        // 注入方法
        vm.Set("Add", func(call otto.FunctionCall) otto.Value {
                var a, b int64
                a, _ = call.Argument(0).ToInteger()
                b, _ = call.Argument(1).ToInteger()

                val, _ := vm.ToValue(a + b)
                return val
        })

        // 执行
        vm.Run(`
                abc = Add(1,2);
                console.log("The value of abc is " + abc);
                console.log("The value of def is " , def.abc);
        `)

        // 变量取值
        if value, err := vm.Get("abc"); err == nil {
                if intVal, err := value.ToInteger(); err == nil {
                        fmt.Println(intVal)
                }
        }
}

goja case

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
        "fmt"

        "github.com/dop251/goja"
)

func main() {
        vm := goja.New()
        // 返回值
        v, _ := vm.RunString("2+2")
        fmt.Println(v.Export().(int64))

        // 注入方法
        vm.Set("add", func(call goja.FunctionCall) goja.Value {
                var a, b int64
                a = call.Argument(0).ToInteger()
                b = call.Argument(1).ToInteger()

                val := vm.ToValue(a + b)
                return val
        })

        v, _ = vm.RunString(`add(1,2)`)
        fmt.Println(v.Export().(int64))

        // 导出方法
        vm.RunString(`
function sub(a,b) {
        return a - b
}
        `)

        sub, _ := goja.AssertFunction(vm.Get("sub"))

        v, _ = sub(goja.Undefined(), vm.ToValue(10), vm.ToValue(1))

        fmt.Println(v.Export().(int64))
        
}

v8go

 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
27
28
29
30
31
32
33
34
35
36
37
38
39

package main

import (
        "fmt"

        v8 "rogchap.com/v8go"
)

func main() {
        iso := v8.NewIsolate()
        global := v8.NewObjectTemplate(iso)

        // 注入方法
        fn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value {
                for _, v := range info.Args() {
                        fmt.Println(v)
                }
                val, _ := v8.NewValue(iso, "something")
                return val
        })

        // 注入变量
        abc, _ := v8.NewValue(iso, int32(456))

        global.Set("abc", abc, v8.ReadOnly)
        global.Set("print", fn, v8.ReadOnly)
        ctx := v8.NewContext(iso, global)
        defer ctx.Close()
        ctx.RunScript(`
print("abc", 123, {"abc":123});
print(abc)
        `, "")

        //获取返回结果

        resp, err := ctx.RunScript(`abc + 321`, "")
        fmt.Println(resp.Int32(), err)
}

性能测试

主要两个方面做性能测试:

  1. 测试简单的加法操作,主要测试高频的启动js引擎的操作
  2. 测试js的高频计算,测试在不同引擎中,js源码的执行效率

压测源码

对比如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
goos: darwin
goarch: arm64
pkg: javascripttest
BenchmarkOttoAdd-10        19362             61799 ns/op
BenchmarkGojaAdd-10        15794             75839 ns/op
BenchmarkV8Add-10           4783            260458 ns/op
BenchmarkSumOtto-10           15          70687100 ns/op
BenchmarkSumGoja-10           67          17269518 ns/op
BenchmarkSumV8-10            489           2203052 ns/op
PASS
ok      javascripttest  9.329s
0%