forked from grafana/k6
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
shareable array implementation (grafana#1739)
This is part of grafana#532 This only implements a shareable array that is generated through a callback. This way any additional processing can be done by any js code once and then the result will be shared between VUs in a readonly fashion.
- Loading branch information
Showing
8 changed files
with
458 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* | ||
* | ||
* k6 - a next-generation load testing tool | ||
* Copyright (C) 2020 Load Impact | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as | ||
* published by the Free Software Foundation, either version 3 of the | ||
* License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package data | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/dop251/goja" | ||
"github.com/loadimpact/k6/js/common" | ||
"github.com/loadimpact/k6/js/internal/modules" | ||
"github.com/loadimpact/k6/lib" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type data struct{} | ||
|
||
func init() { | ||
modules.Register("k6/data", new(data)) | ||
} | ||
|
||
const sharedArrayNamePrefix = "k6/data/SharedArray." | ||
|
||
// XSharedArray is a constructor returning a shareable read-only array | ||
// indentified by the name and having their contents be whatever the call returns | ||
func (d *data) XSharedArray(ctx context.Context, name string, call goja.Callable) (goja.Value, error) { | ||
if lib.GetState(ctx) != nil { | ||
return nil, errors.New("new SharedArray must be called in the init context") | ||
} | ||
|
||
initEnv := common.GetInitEnv(ctx) | ||
if initEnv == nil { | ||
return nil, errors.New("missing init environment") | ||
} | ||
if len(name) == 0 { | ||
return nil, errors.New("empty name provided to SharedArray's constructor") | ||
} | ||
|
||
name = sharedArrayNamePrefix + name | ||
value := initEnv.SharedObjects.GetOrCreateShare(name, func() interface{} { | ||
return getShareArrayFromCall(common.GetRuntime(ctx), call) | ||
}) | ||
array, ok := value.(sharedArray) | ||
if !ok { // TODO more info in the error? | ||
return nil, errors.New("wrong type of shared object") | ||
} | ||
|
||
return array.wrap(&ctx, common.GetRuntime(ctx)), nil | ||
} | ||
|
||
func getShareArrayFromCall(rt *goja.Runtime, call goja.Callable) sharedArray { | ||
gojaValue, err := call(goja.Undefined()) | ||
if err != nil { | ||
common.Throw(rt, err) | ||
} | ||
obj := gojaValue.ToObject(rt) | ||
if obj.ClassName() != "Array" { | ||
common.Throw(rt, errors.New("only arrays can be made into SharedArray")) // TODO better error | ||
} | ||
arr := make([]string, obj.Get("length").ToInteger()) | ||
|
||
// We specifically use JSON.stringify here as we need to use JSON.parse on the way out | ||
// it also has the benefit of needing only one loop and being more JS then using golang's json | ||
cal, err := rt.RunString(`(function(input, output) { | ||
for (var i = 0; i < input.length; i++) { | ||
output[i] = JSON.stringify(input[i]) | ||
} | ||
})`) | ||
if err != nil { | ||
common.Throw(rt, err) | ||
} | ||
newCall, _ := goja.AssertFunction(cal) | ||
_, err = newCall(goja.Undefined(), gojaValue, rt.ToValue(arr)) | ||
if err != nil { | ||
common.Throw(rt, err) | ||
} | ||
return sharedArray{arr: arr} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/* | ||
* | ||
* k6 - a next-generation load testing tool | ||
* Copyright (C) 2020 Load Impact | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as | ||
* published by the Free Software Foundation, either version 3 of the | ||
* License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package data | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/dop251/goja" | ||
"github.com/loadimpact/k6/js/common" | ||
) | ||
|
||
// TODO fix it not working really well with setupData or just make it more broken | ||
// TODO fix it working with console.log | ||
type sharedArray struct { | ||
arr []string | ||
} | ||
|
||
func (s sharedArray) wrap(ctxPtr *context.Context, rt *goja.Runtime) goja.Value { | ||
cal, err := rt.RunString(arrayWrapperCode) | ||
if err != nil { | ||
common.Throw(rt, err) | ||
} | ||
call, _ := goja.AssertFunction(cal) | ||
wrapped, err := call(goja.Undefined(), rt.ToValue(common.Bind(rt, s, ctxPtr))) | ||
if err != nil { | ||
common.Throw(rt, err) | ||
} | ||
|
||
return wrapped | ||
} | ||
|
||
func (s sharedArray) Get(index int) (interface{}, error) { | ||
if index < 0 || index >= len(s.arr) { | ||
return goja.Undefined(), nil | ||
} | ||
|
||
// we specifically use JSON.parse to get the json to an object inside as otherwise we won't be | ||
// able to freeze it as goja doesn't let us unless it is a pure goja object and this is the | ||
// easiest way to get one. | ||
return s.arr[index], nil | ||
} | ||
|
||
func (s sharedArray) Length() int { | ||
return len(s.arr) | ||
} | ||
|
||
/* This implementation is commented as with it - it is harder to deepFreeze it with this implementation. | ||
type sharedArrayIterator struct { | ||
a *sharedArray | ||
index int | ||
} | ||
func (sai *sharedArrayIterator) Next() (interface{}, error) { | ||
if sai.index == len(sai.a.arr)-1 { | ||
return map[string]bool{"done": true}, nil | ||
} | ||
sai.index++ | ||
var tmp interface{} | ||
if err := json.Unmarshal(sai.a.arr[sai.index], &tmp); err != nil { | ||
return goja.Undefined(), err | ||
} | ||
return map[string]interface{}{"value": tmp}, nil | ||
} | ||
func (s sharedArray) Iterator() *sharedArrayIterator { | ||
return &sharedArrayIterator{a: &s, index: -1} | ||
} | ||
*/ | ||
|
||
const arrayWrapperCode = `(function(val) { | ||
function deepFreeze(o) { | ||
Object.freeze(o); | ||
if (o === undefined) { | ||
return o; | ||
} | ||
Object.getOwnPropertyNames(o).forEach(function (prop) { | ||
if (o[prop] !== null | ||
&& (typeof o[prop] === "object" || typeof o[prop] === "function") | ||
&& !Object.isFrozen(o[prop])) { | ||
deepFreeze(o[prop]); | ||
} | ||
}); | ||
return o; | ||
}; | ||
var arrayHandler = { | ||
get: function(target, property, receiver) { | ||
switch (property){ | ||
case "length": | ||
return target.length(); | ||
case Symbol.iterator: | ||
return function(){ | ||
var index = 0; | ||
return { | ||
"next": function() { | ||
if (index >= target.length()) { | ||
return {done: true} | ||
} | ||
var result = {value: deepFreeze(JSON.parse(target.get(index)))}; | ||
index++; | ||
return result; | ||
} | ||
} | ||
} | ||
} | ||
var i = parseInt(property); | ||
if (isNaN(i)) { | ||
return undefined; | ||
} | ||
return deepFreeze(JSON.parse(target.get(i))); | ||
} | ||
}; | ||
return new Proxy(val, arrayHandler); | ||
})` |
Oops, something went wrong.