From 2a2c91aa3e8ecf14b785609e587b3f4586bc4477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Wed, 23 Jun 2021 18:05:11 +0200 Subject: [PATCH] Replace getter with property based API Based on internal design discussion. Access by property is more idiomatic for JS. Also, s/stats/info/ based on feedback in https://github.com/k6io/k6/pull/1863. --- go.mod | 2 +- go.sum | 18 +++ pkg/execution/execution.go | 254 +++++++++++++++++++++----------- pkg/execution/execution_test.go | 66 ++++----- 4 files changed, 219 insertions(+), 121 deletions(-) diff --git a/go.mod b/go.mod index 3266b25..acaf823 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,6 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/spf13/afero v1.1.2 github.com/stretchr/testify v1.7.0 - go.k6.io/k6 v0.32.1-0.20210622082042-2039c5691bbe + go.k6.io/k6 v0.32.1-0.20210624122905-f24bd9a86806 gopkg.in/guregu/null.v3 v3.3.0 ) diff --git a/go.sum b/go.sum index d52b342..87fe3dc 100644 --- a/go.sum +++ b/go.sum @@ -16,12 +16,14 @@ github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzU github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v0.0.0-20180330214955-e67964b4021a h1:zpQSzEApXM0qkXcpdjeJ4OpnBWhD/X8zT/iT1wYLiVU= github.com/DataDog/datadog-go v0.0.0-20180330214955-e67964b4021a/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da/go.mod h1:DgrzXonpdQbfN3uYaGz1EG4Sbhyum/MMIn6Cphlh2bw= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.3.0/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA= github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk= github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/Shopify/sarama v1.16.0 h1:9pI5+ZN06jB3bu5kHXqzzaErMC5rimcIZBQL9IOiEQ0= github.com/Shopify/sarama v1.16.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Soontao/goHttpDigestClient v0.0.0-20170320082612-6d28bb1415c5 h1:k+1+doEm31k0rRjCjLnGG3YRkuO9ljaEyS2ajZd6GK8= @@ -67,8 +69,11 @@ github.com/dop251/goja v0.0.0-20210427212725-462d53687b0d h1:enuVjS1vVnToj/GuGZ7 github.com/dop251/goja v0.0.0-20210427212725-462d53687b0d/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20160609142408-bb955e01b934 h1:oGLoaVIefp3tiOgi7+KInR/nNPvEpPM6GFo+El7fd14= github.com/eapache/go-xerial-snappy v0.0.0-20160609142408-bb955e01b934/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -84,6 +89,7 @@ github.com/fatih/color v1.11.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 h1:Uc+IZ7gYqAf/rSGFplbWBSHaGolEQlNLgMgSE3ccnIQ= github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9yhSRvsmYyZsshflcR6ePWYLql6UU1amW13IM= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -165,12 +171,15 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20190402204710-8ff2fc3824fc h1:KpMgaYJRieDkHZJWY3LMafvtqS/U8xX6+lUN+OKpl/Y= github.com/influxdata/influxdb1-client v0.0.0-20190402204710-8ff2fc3824fc/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jhump/protoreflect v1.7.0/go.mod h1:RZkzh7Hi9J7qT/sPlWnJ/UwZqCJvciFxKDA0UCeltSM= github.com/jhump/protoreflect v1.8.2 h1:k2xE7wcUomeqwY0LDCYA16y4WWfyTcMx5mKhk0d4ua0= github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -179,6 +188,7 @@ github.com/julienschmidt/httprouter v1.1.1-0.20180222160526-d18983907793/go.mod github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k6io/xk6-output-kafka v0.1.1/go.mod h1:FEnBddwavknsQ3/tGFW0CP9ABAVMyimlJdLgyT1BDPE= github.com/k6io/xk6-output-kafka v0.1.2-0.20210510135110-a159d7c8c171/go.mod h1:fgsOfxm/erON/jKuOBL8/TceTnAZPQ1OS8A7cPhrso8= +github.com/k6io/xk6-output-kafka v0.2.0 h1:BCWOFFDhjD2ETLMD1HdEO84xXkA6QgJ/LG9HxrsqqIo= github.com/k6io/xk6-output-kafka v0.2.0/go.mod h1:yoiecpPwfa3F/UVl8k2scJ0xkZqRYu6Ht1XqohXnlXA= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= @@ -209,6 +219,7 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/mailru/easyjson v0.7.4-0.20200812114229-8ab5ff9cd8e4/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/manyminds/api2go v0.0.0-20180125085803-95be7bd0455e h1:Jg9dDZCJYqtv9GHQ34yDBFgEJLl4Hi4VEkKUyzHXCV8= github.com/manyminds/api2go v0.0.0-20180125085803-95be7bd0455e/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= @@ -252,7 +263,9 @@ github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgF github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pierrec/lz4 v1.0.2-0.20171218195038-2fcda4cb7018 h1:z+gewaCBdXQRy5LXNOOKQU0gU93ro44uK92zqkB9KCc= github.com/pierrec/lz4 v1.0.2-0.20171218195038-2fcda4cb7018/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/xxHash v0.1.1 h1:KP4NrV9023xp3M4FkTYfcXqWigsOCImL1ANJ7sh5vg4= github.com/pierrec/xxHash v0.1.1/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -269,6 +282,7 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -292,10 +306,12 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.4-0.20180629152535-a114f312e075/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -331,6 +347,8 @@ go.k6.io/k6 v0.31.2-0.20210510132435-c2958278a362/go.mod h1:WmqQqrJOnATU2o+vmmfP go.k6.io/k6 v0.31.2-0.20210511090412-61f464b99a2d/go.mod h1:5BTMcTH7K+IEoBUPBRM15M9c97nBqeKzQfop868FMiw= go.k6.io/k6 v0.32.1-0.20210622082042-2039c5691bbe h1:k3IcFb/gUSo/XQPLdaaw7YJXBrPY/Y+AHEh3HtfXNy0= go.k6.io/k6 v0.32.1-0.20210622082042-2039c5691bbe/go.mod h1:SNG6/ZknLfIqNGbYUfORZT6sNdUuBJ15ntzt8jZloc0= +go.k6.io/k6 v0.32.1-0.20210624122905-f24bd9a86806 h1:8DkI2JZ289voqprAvvDYPDTO49qpXsi4Pxeac+9zVH8= +go.k6.io/k6 v0.32.1-0.20210624122905-f24bd9a86806/go.mod h1:SNG6/ZknLfIqNGbYUfORZT6sNdUuBJ15ntzt8jZloc0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= diff --git a/pkg/execution/execution.go b/pkg/execution/execution.go index 1e36408..5507363 100644 --- a/pkg/execution/execution.go +++ b/pkg/execution/execution.go @@ -23,6 +23,7 @@ package execution import ( "context" "errors" + "sort" "time" "github.com/dop251/goja" @@ -31,137 +32,218 @@ import ( "go.k6.io/k6/lib" ) -// Execution is a JS module to return information about the execution in progress. -type Execution struct{} +type ( + // RootExecution is the global module instance that will create module + // instances for each VU. + RootExecution struct{} + // Execution is a JS module that returns information about the currently + // executing test run. + Execution struct{ *goja.Proxy } +) -// New returns a pointer to a new Execution. -func New() *Execution { +// NewModuleInstancePerVU fulfills the k6 modules.HasModuleInstancePerVU +// interface so that each VU will get a separate copy of the module. +func (*RootExecution) NewModuleInstancePerVU() interface{} { return &Execution{} } -// GetVUStats returns information about the currently executing VU. -func (e *Execution) GetVUStats(ctx context.Context) (goja.Value, error) { - vuState := lib.GetState(ctx) - if vuState == nil { - return nil, errors.New("getting VU information in the init context is not supported") +// New returns a pointer to a new RootExecution instance. +func New() *RootExecution { + return &RootExecution{} +} + +// WithContext fulfills the k6 modules.HasWithContext interface to allow +// retrieving the VU, scenario and test state from the context used by each VU. +// It initializes a goja.Proxy object for the per-VU module instance, which in +// turn retrieves goja.DynamicObject instances for each property (scenario, vu, +// test). +func (e *Execution) WithContext(getCtx func() context.Context) { + keys := []string{"scenario", "vu", "test"} + + pcfg := goja.ProxyTrapConfig{ + OwnKeys: func(target *goja.Object) *goja.Object { + ctx := getCtx() + rt := common.GetRuntime(ctx) + return rt.ToValue(keys).ToObject(rt) + }, + Has: func(target *goja.Object, prop string) (available bool) { + return sort.SearchStrings(keys, prop) != -1 + }, + Get: func(target *goja.Object, prop string, r goja.Value) goja.Value { + return dynObjValue(getCtx, target, prop) + }, + GetOwnPropertyDescriptor: func(target *goja.Object, prop string) (desc goja.PropertyDescriptor) { + desc.Enumerable, desc.Configurable = goja.FLAG_TRUE, goja.FLAG_TRUE + desc.Value = dynObjValue(getCtx, target, prop) + return desc + }, } + ctx := getCtx() rt := common.GetRuntime(ctx) - if rt == nil { - return nil, errors.New("goja runtime is nil in context") + proxy := rt.NewProxy(rt.NewObject(), &pcfg) + e.Proxy = &proxy +} + +// dynObjValue returns a goja.Value for a specific prop on target. +func dynObjValue(getCtx func() context.Context, target *goja.Object, prop string) goja.Value { + v := target.Get(prop) + if v != nil { + return v } + // fmt.Printf(">>> creating DynamicObject at %s\n", time.Now()) - stats := map[string]interface{}{ - "id": vuState.VUID, - "idGlobal": vuState.VUIDGlobal, - "iteration": vuState.Iteration, - "iterationScenario": func() goja.Value { - return rt.ToValue(vuState.GetScenarioVUIter()) - }, + ctx := getCtx() + rt := common.GetRuntime(ctx) + var ( + dobj *execInfo + err error + ) + switch prop { + case "scenario": + dobj, err = newScenarioInfo(getCtx) + case "test": + dobj, err = newTestInfo(getCtx) + case "vu": + vuState := lib.GetState(ctx) + dobj, err = newVUInfo(rt, vuState) } - obj, err := newLazyJSObject(rt, stats) if err != nil { - return nil, err + // TODO: Something less drastic? + common.Throw(rt, err) } - return obj, nil + if dobj != nil { + v = rt.NewDynamicObject(dobj) + } + target.Set(prop, v) + return v } -// GetScenarioStats returns information about the currently executing scenario. -func (e *Execution) GetScenarioStats(ctx context.Context) (goja.Value, error) { - ss := lib.GetScenarioState(ctx) +// newScenarioInfo returns a goja.DynamicObject implementation to retrieve +// information about the scenario the current VU is running in. +func newScenarioInfo(getCtx func() context.Context) (*execInfo, error) { + ctx := getCtx() vuState := lib.GetState(ctx) + ss := lib.GetScenarioState(ctx) if ss == nil || vuState == nil { return nil, errors.New("getting scenario information in the init context is not supported") } rt := common.GetRuntime(ctx) - if rt == nil { - return nil, errors.New("goja runtime is nil in context") - } - - var iterGlobal interface{} - if vuState.GetScenarioGlobalVUIter != nil { - iterGlobal = vuState.GetScenarioGlobalVUIter() - } else { - iterGlobal = goja.Null() - } - - stats := map[string]interface{}{ - "name": ss.Name, - "executor": ss.Executor, - "startTime": float64(ss.StartTime.UnixNano()) / 1e9, - "progress": func() goja.Value { + si := map[string]func() interface{}{ + "name": func() interface{} { + ctx := getCtx() + ss := lib.GetScenarioState(ctx) + return ss.Name + }, + "executor": func() interface{} { + ctx := getCtx() + ss := lib.GetScenarioState(ctx) + return ss.Executor + }, + "startTime": func() interface{} { return float64(ss.StartTime.UnixNano()) / 1e9 }, + "progress": func() interface{} { p, _ := ss.ProgressFn() - return rt.ToValue(p) + return p + }, + "iteration": func() interface{} { + return vuState.GetScenarioLocalVUIter() + }, + "iterationGlobal": func() interface{} { + if vuState.GetScenarioGlobalVUIter != nil { + return vuState.GetScenarioGlobalVUIter() + } + return goja.Null() }, - "iteration": vuState.GetScenarioLocalVUIter(), - "iterationGlobal": iterGlobal, - } - - obj, err := newLazyJSObject(rt, stats) - if err != nil { - return nil, err } - return obj, nil + return newExecInfo(rt, si), nil } -// GetTestInstanceStats returns test information for the current k6 instance. -func (e *Execution) GetTestInstanceStats(ctx context.Context) (goja.Value, error) { +// newTestInfo returns a goja.DynamicObject implementation to retrieve +// information about the overall test run (local instance). +func newTestInfo(getCtx func() context.Context) (*execInfo, error) { + ctx := getCtx() + rt := common.GetRuntime(ctx) es := lib.GetExecutionState(ctx) if es == nil { return nil, errors.New("getting test information in the init context is not supported") } - rt := common.GetRuntime(ctx) - if rt == nil { - return nil, errors.New("goja runtime is nil in context") - } - - stats := map[string]interface{}{ - "duration": func() goja.Value { - return rt.ToValue(float64(es.GetCurrentTestRunDuration()) / float64(time.Millisecond)) + ti := map[string]func() interface{}{ + "duration": func() interface{} { + return float64(es.GetCurrentTestRunDuration()) / float64(time.Millisecond) }, - "iterationsCompleted": func() goja.Value { - return rt.ToValue(es.GetFullIterationCount()) + "iterationsCompleted": func() interface{} { + return es.GetFullIterationCount() }, - "iterationsInterrupted": func() goja.Value { - return rt.ToValue(es.GetPartialIterationCount()) + "iterationsInterrupted": func() interface{} { + return es.GetPartialIterationCount() }, - "vusActive": func() goja.Value { - return rt.ToValue(es.GetCurrentlyActiveVUsCount()) + "vusActive": func() interface{} { + return es.GetCurrentlyActiveVUsCount() }, - "vusMax": func() goja.Value { - return rt.ToValue(es.GetInitializedVUsCount()) + "vusMax": func() interface{} { + return es.GetInitializedVUsCount() }, } - obj, err := newLazyJSObject(rt, stats) - if err != nil { - return nil, err + return newExecInfo(rt, ti), nil +} + +// newVUInfo returns a goja.DynamicObject implementation to retrieve +// information about the currently executing VU. +func newVUInfo(rt *goja.Runtime, vuState *lib.State) (*execInfo, error) { + if vuState == nil { + return nil, errors.New("getting VU information in the init context is not supported") + } + + vi := map[string]func() interface{}{ + "id": func() interface{} { return vuState.VUID }, + "idGlobal": func() interface{} { return vuState.VUIDGlobal }, + "iteration": func() interface{} { return vuState.Iteration }, + "iterationScenario": func() interface{} { + return vuState.GetScenarioVUIter() + }, } - return obj, nil + return newExecInfo(rt, vi), nil } -func newLazyJSObject(rt *goja.Runtime, data map[string]interface{}) (goja.Value, error) { - obj := rt.NewObject() +// execInfo is a goja.DynamicObject implementation to lazily return data only +// on property access. +type execInfo struct { + rt *goja.Runtime + obj map[string]func() interface{} + keys []string +} - for k, v := range data { - if val, ok := v.(func() goja.Value); ok { - if err := obj.DefineAccessorProperty(k, rt.ToValue(val), - nil, goja.FLAG_FALSE, goja.FLAG_TRUE); err != nil { - return nil, err - } - } else { - if err := obj.DefineDataProperty(k, rt.ToValue(v), - goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_TRUE); err != nil { - return nil, err - } - } +var _ goja.DynamicObject = &execInfo{} + +func newExecInfo(rt *goja.Runtime, obj map[string]func() interface{}) *execInfo { + keys := make([]string, 0, len(obj)) + for k := range obj { + keys = append(keys, k) } + return &execInfo{obj: obj, keys: keys, rt: rt} +} - return obj, nil +func (ei *execInfo) Get(key string) goja.Value { + if fn, ok := ei.obj[key]; ok { + return ei.rt.ToValue(fn()) + } + return goja.Undefined() +} + +func (ei *execInfo) Set(key string, val goja.Value) bool { return false } + +func (ei *execInfo) Has(key string) bool { + _, has := ei.obj[key] + return has } + +func (ei *execInfo) Delete(key string) bool { return false } + +func (ei *execInfo) Keys() []string { return ei.keys } diff --git a/pkg/execution/execution_test.go b/pkg/execution/execution_test.go index e8d66a3..fe315d0 100644 --- a/pkg/execution/execution_test.go +++ b/pkg/execution/execution_test.go @@ -27,7 +27,7 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func TestExecutionStatsVUSharing(t *testing.T) { +func TestExecutionInfoVUSharing(t *testing.T) { t.Parallel() script := []byte(` import exec from 'k6/x/execution'; @@ -58,14 +58,14 @@ func TestExecutionStatsVUSharing(t *testing.T) { }; export function cvus() { - const stats = Object.assign({scenario: 'cvus'}, exec.getVUStats()); - console.log(JSON.stringify(stats)); + const info = Object.assign({scenario: 'cvus'}, exec.vu); + console.log(JSON.stringify(info)); sleep(0.2); }; export function carr() { - const stats = Object.assign({scenario: 'carr'}, exec.getVUStats()); - console.log(JSON.stringify(stats)); + const info = Object.assign({scenario: 'carr'}, exec.vu); + console.log(JSON.stringify(info)); }; `) @@ -136,7 +136,7 @@ func TestExecutionStatsVUSharing(t *testing.T) { } } -func TestExecutionStatsScenarioIter(t *testing.T) { +func TestExecutionInfoScenarioIter(t *testing.T) { t.Parallel() script := []byte(` import exec from 'k6/x/execution'; @@ -166,13 +166,13 @@ func TestExecutionStatsScenarioIter(t *testing.T) { }; export function pvu() { - const stats = Object.assign({VUID: __VU}, exec.getScenarioStats()); - console.log(JSON.stringify(stats)); + const info = Object.assign({VUID: __VU}, exec.scenario); + console.log(JSON.stringify(info)); } export function carr() { - const stats = Object.assign({VUID: __VU}, exec.getScenarioStats()); - console.log(JSON.stringify(stats)); + const info = Object.assign({VUID: __VU}, exec.scenario); + console.log(JSON.stringify(info)); }; `) @@ -248,9 +248,8 @@ func TestSharedIterationsStable(t *testing.T) { }, }; export default function () { - const stats = exec.getScenarioStats(); sleep(1); - console.log(JSON.stringify(Object.assign({VUID: __VU}, stats))); + console.log(JSON.stringify(Object.assign({VUID: __VU}, exec.scenario))); } `) @@ -305,7 +304,7 @@ func TestSharedIterationsStable(t *testing.T) { } } -func TestExecutionStats(t *testing.T) { +func TestExecutionInfo(t *testing.T) { t.Parallel() testCases := []struct { @@ -315,48 +314,47 @@ func TestExecutionStats(t *testing.T) { var exec = require('k6/x/execution'); exports.default = function() { - var vuStats = exec.getVUStats(); - if (vuStats.id !== 1) throw new Error('unexpected VU ID: '+vuStats.id); - if (vuStats.idGlobal !== 10) throw new Error('unexpected global VU ID: '+vuStats.idGlobal); - if (vuStats.iteration !== 0) throw new Error('unexpected VU iteration: '+vuStats.iteration); - if (vuStats.iterationScenario !== 0) throw new Error('unexpected scenario iteration: '+vuStats.iterationScenario); + if (exec.vu.id !== 1) throw new Error('unexpected VU ID: '+exec.vu.id); + if (exec.vu.idGlobal !== 10) throw new Error('unexpected global VU ID: '+exec.vu.idGlobal); + if (exec.vu.iteration !== 0) throw new Error('unexpected VU iteration: '+exec.vu.iteration); + if (exec.vu.iterationScenario !== 0) throw new Error('unexpected scenario iteration: '+exec.vu.iterationScenario); }`}, {name: "vu_err", script: ` var exec = require('k6/x/execution'); - exec.getVUStats(); + exec.vu; `, expErr: "getting VU information in the init context is not supported"}, {name: "scenario_ok", script: ` var exec = require('k6/x/execution'); var sleep = require('k6').sleep; exports.default = function() { - var ss = exec.getScenarioStats(); + var si = exec.scenario; sleep(0.1); - if (ss.name !== 'default') throw new Error('unexpected scenario name: '+ss.name); - if (ss.executor !== 'test-exec') throw new Error('unexpected executor: '+ss.executor); - if (ss.startTime > new Date().getTime()) throw new Error('unexpected startTime: '+ss.startTime); - if (ss.progress !== 0.1) throw new Error('unexpected progress: '+ss.progress); - if (ss.iteration !== 3) throw new Error('unexpected scenario local iteration: '+ss.iteration); - if (ss.iterationGlobal !== 4) throw new Error('unexpected scenario local iteration: '+ss.iterationGlobal); + if (si.name !== 'default') throw new Error('unexpected scenario name: '+si.name); + if (si.executor !== 'test-exec') throw new Error('unexpected executor: '+si.executor); + if (si.startTime > new Date().getTime()) throw new Error('unexpected startTime: '+si.startTime); + if (si.progress !== 0.1) throw new Error('unexpected progress: '+si.progress); + if (si.iteration !== 3) throw new Error('unexpected scenario local iteration: '+si.iteration); + if (si.iterationGlobal !== 4) throw new Error('unexpected scenario local iteration: '+si.iterationGlobal); }`}, {name: "scenario_err", script: ` var exec = require('k6/x/execution'); - exec.getScenarioStats(); + exec.scenario; `, expErr: "getting scenario information in the init context is not supported"}, {name: "test_ok", script: ` var exec = require('k6/x/execution'); exports.default = function() { - var ts = exec.getTestInstanceStats(); - if (ts.duration !== 0) throw new Error('unexpected test duration: '+ts.duration); - if (ts.vusActive !== 1) throw new Error('unexpected vusActive: '+ts.vusActive); - if (ts.vusMax !== 0) throw new Error('unexpected vusMax: '+ts.vusMax); - if (ts.iterationsCompleted !== 0) throw new Error('unexpected iterationsCompleted: '+ts.iterationsCompleted); - if (ts.iterationsInterrupted !== 0) throw new Error('unexpected iterationsInterrupted: '+ts.iterationsInterrupted); + var ti = exec.test; + if (ti.duration !== 0) throw new Error('unexpected test duration: '+ti.duration); + if (ti.vusActive !== 1) throw new Error('unexpected vusActive: '+ti.vusActive); + if (ti.vusMax !== 0) throw new Error('unexpected vusMax: '+ti.vusMax); + if (ti.iterationsCompleted !== 0) throw new Error('unexpected iterationsCompleted: '+ti.iterationsCompleted); + if (ti.iterationsInterrupted !== 0) throw new Error('unexpected iterationsInterrupted: '+ti.iterationsInterrupted); }`}, {name: "test_err", script: ` var exec = require('k6/x/execution'); - exec.getTestInstanceStats(); + exec.test; `, expErr: "getting test information in the init context is not supported"}, }