Skip to content

Commit

Permalink
Rename modules.InstanceCore to modules.VU and don't embed it
Browse files Browse the repository at this point in the history
As well as remove GenerateExports and make module authors to actually
specify exactly what they will export.
  • Loading branch information
mstoykov committed Nov 11, 2021
1 parent 840b905 commit 5efd660
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 126 deletions.
13 changes: 7 additions & 6 deletions js/initcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,24 +151,25 @@ func (i *InitContext) Require(arg string) goja.Value {
}
}

type moduleInstanceCoreImpl struct {
// TODO this likely should just be part of the initialized VU or at least to take stuff directly from it.
type moduleVUImpl struct {
ctxPtr *context.Context
// we can technically put lib.State here as well as anything else
}

func (m *moduleInstanceCoreImpl) GetContext() context.Context {
func (m *moduleVUImpl) Context() context.Context {
return *m.ctxPtr
}

func (m *moduleInstanceCoreImpl) GetInitEnv() *common.InitEnvironment {
func (m *moduleVUImpl) InitEnv() *common.InitEnvironment {
return common.GetInitEnv(*m.ctxPtr) // TODO thread it correctly instead
}

func (m *moduleInstanceCoreImpl) GetState() *lib.State {
func (m *moduleVUImpl) State() *lib.State {
return lib.GetState(*m.ctxPtr) // TODO thread it correctly instead
}

func (m *moduleInstanceCoreImpl) GetRuntime() *goja.Runtime {
func (m *moduleVUImpl) Runtime() *goja.Runtime {
return common.GetRuntime(*m.ctxPtr) // TODO thread it correctly instead
}

Expand Down Expand Up @@ -201,7 +202,7 @@ func (i *InitContext) requireModule(name string) (goja.Value, error) {
return nil, fmt.Errorf("unknown module: %s", name)
}
if m, ok := mod.(modules.Module); ok {
instance := m.NewModuleInstance(&moduleInstanceCoreImpl{ctxPtr: i.ctxPtr})
instance := m.NewModuleInstance(&moduleVUImpl{ctxPtr: i.ctxPtr})
return i.runtime.ToValue(toESModuleExports(instance.Exports())), nil
}
if perInstance, ok := mod.(modules.HasModuleInstancePerVU); ok {
Expand Down
18 changes: 9 additions & 9 deletions js/modules/k6/execution/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type (

// ModuleInstance represents an instance of the execution module.
ModuleInstance struct {
modules.InstanceCore
vu modules.VU
obj *goja.Object
}
)
Expand All @@ -57,9 +57,9 @@ func New() *RootModule {

// NewModuleInstance implements the modules.Module interface to return
// a new instance for each VU.
func (*RootModule) NewModuleInstance(m modules.InstanceCore) modules.Instance {
mi := &ModuleInstance{InstanceCore: m}
rt := m.GetRuntime()
func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
mi := &ModuleInstance{vu: vu}
rt := vu.Runtime()
o := rt.NewObject()
defProp := func(name string, newInfo func() (*goja.Object, error)) {
err := o.DefineAccessorProperty(name, rt.ToValue(func() goja.Value {
Expand Down Expand Up @@ -90,17 +90,17 @@ func (mi *ModuleInstance) Exports() modules.Exports {
// newScenarioInfo returns a goja.Object with property accessors to retrieve
// information about the scenario the current VU is running in.
func (mi *ModuleInstance) newScenarioInfo() (*goja.Object, error) {
ctx := mi.GetContext()
ctx := mi.vu.Context()
rt := common.GetRuntime(ctx)
vuState := mi.GetState()
vuState := mi.vu.State()
if vuState == nil {
return nil, errors.New("getting scenario information in the init context is not supported")
}
if rt == nil {
return nil, errors.New("goja runtime is nil in context")
}
getScenarioState := func() *lib.ScenarioState {
ss := lib.GetScenarioState(mi.GetContext())
ss := lib.GetScenarioState(mi.vu.Context())
if ss == nil {
common.Throw(rt, errors.New("getting scenario information in the init context is not supported"))
}
Expand Down Expand Up @@ -140,7 +140,7 @@ func (mi *ModuleInstance) newScenarioInfo() (*goja.Object, error) {
// newInstanceInfo returns a goja.Object with property accessors to retrieve
// information about the local instance stats.
func (mi *ModuleInstance) newInstanceInfo() (*goja.Object, error) {
ctx := mi.GetContext()
ctx := mi.vu.Context()
es := lib.GetExecutionState(ctx)
if es == nil {
return nil, errors.New("getting instance information in the init context is not supported")
Expand Down Expand Up @@ -175,7 +175,7 @@ func (mi *ModuleInstance) newInstanceInfo() (*goja.Object, error) {
// newVUInfo returns a goja.Object with property accessors to retrieve
// information about the currently executing VU.
func (mi *ModuleInstance) newVUInfo() (*goja.Object, error) {
ctx := mi.GetContext()
ctx := mi.vu.Context()
vuState := lib.GetState(ctx)
if vuState == nil {
return nil, errors.New("getting VU information in the init context is not supported")
Expand Down
14 changes: 7 additions & 7 deletions js/modules/k6/execution/execution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ func setupTagsExecEnv(t *testing.T) execEnv {
ctx := common.WithRuntime(context.Background(), rt)
ctx = lib.WithState(ctx, state)
m, ok := New().NewModuleInstance(
&modulestest.InstanceCore{
Runtime: rt,
InitEnv: &common.InitEnvironment{},
Ctx: ctx,
State: state,
&modulestest.VU{
RuntimeField: rt,
InitEnvField: &common.InitEnvironment{},
CtxField: ctx,
StateField: state,
},
).(*ModuleInstance)
require.True(t, ok)
Expand Down Expand Up @@ -102,7 +102,7 @@ func TestVUTags(t *testing.T) {
t.Parallel()

tenv := setupTagsExecEnv(t)
state := tenv.Module.GetState()
state := tenv.Module.vu.State()
state.Tags.Set("custom-tag", "mytag1")

encoded, err := tenv.Runtime.RunString(`JSON.stringify(exec.vu.tags)`)
Expand Down Expand Up @@ -157,7 +157,7 @@ func TestVUTags(t *testing.T) {
t.Parallel()

tenv := setupTagsExecEnv(t)
state := tenv.Module.GetState()
state := tenv.Module.vu.State()
state.Options.Throw = null.BoolFrom(true)
require.NotNil(t, state)

Expand Down
27 changes: 17 additions & 10 deletions js/modules/k6/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@ import (

type Metric struct {
metric *stats.Metric
core modules.InstanceCore
vu modules.VU
}

// ErrMetricsAddInInitContext is error returned when adding to metric is done in the init context
var ErrMetricsAddInInitContext = common.NewInitContextError("Adding to metrics in the init context is not supported")

func (mi *ModuleInstance) newMetric(call goja.ConstructorCall, t stats.MetricType) (*goja.Object, error) {
initEnv := mi.GetInitEnv()
initEnv := mi.vu.InitEnv()
if initEnv == nil {
return nil, errors.New("metrics must be declared in the init context")
}
rt := mi.GetRuntime()
rt := mi.vu.Runtime()
c, _ := goja.AssertFunction(rt.ToValue(func(name string, isTime ...bool) (*goja.Object, error) {
valueType := stats.Default
if len(isTime) > 0 && isTime[0] {
Expand All @@ -58,7 +58,7 @@ func (mi *ModuleInstance) newMetric(call goja.ConstructorCall, t stats.MetricTyp
if err != nil {
return nil, err
}
metric := &Metric{metric: m, core: mi.InstanceCore}
metric := &Metric{metric: m, vu: mi.vu}
o := rt.NewObject()
err = o.DefineDataProperty("name", rt.ToValue(name), goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_TRUE)
if err != nil {
Expand Down Expand Up @@ -93,7 +93,7 @@ func limitValue(v string) string {
}

func (m Metric) add(v goja.Value, addTags ...map[string]string) (bool, error) {
state := m.core.GetState()
state := m.vu.State()
if state == nil {
return false, ErrMetricsAddInInitContext
}
Expand Down Expand Up @@ -130,7 +130,7 @@ func (m Metric) add(v goja.Value, addTags ...map[string]string) (bool, error) {
}

sample := stats.Sample{Time: time.Now(), Metric: m.metric, Value: vfloat, Tags: stats.IntoSampleTags(&tags)}
stats.PushIfNotDone(m.core.GetContext(), state.Samples, sample)
stats.PushIfNotDone(m.vu.Context(), state.Samples, sample)
return true, nil
}

Expand All @@ -139,7 +139,7 @@ type (
RootModule struct{}
// ModuleInstance represents an instance of the metrics module
ModuleInstance struct {
modules.InstanceCore
vu modules.VU
}
)

Expand All @@ -149,8 +149,8 @@ var (
)

// NewModuleInstance implements modules.Module interface
func (*RootModule) NewModuleInstance(m modules.InstanceCore) modules.Instance {
return &ModuleInstance{InstanceCore: m}
func (*RootModule) NewModuleInstance(m modules.VU) modules.Instance {
return &ModuleInstance{vu: m}
}

// New returns a new RootModule.
Expand All @@ -160,7 +160,14 @@ func New() *RootModule {

// Exports returns the exports of the metrics module
func (mi *ModuleInstance) Exports() modules.Exports {
return modules.GenerateExports(mi)
return modules.Exports{
Named: map[string]interface{}{
"Counter": mi.XCounter,
"Gauge": mi.XGauge,
"Trend": mi.XTrend,
"Rate": mi.XRate,
},
}
}

// XCounter is a counter constructor
Expand Down
30 changes: 15 additions & 15 deletions js/modules/k6/metrics/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ func TestMetrics(t *testing.T) {
}
test.rt = goja.New()
test.rt.SetFieldNameMapper(common.FieldNameMapper{})
mii := &modulestest.InstanceCore{
Runtime: test.rt,
InitEnv: &common.InitEnvironment{Registry: metrics.NewRegistry()},
Ctx: context.Background(),
mii := &modulestest.VU{
RuntimeField: test.rt,
InitEnvField: &common.InitEnvironment{Registry: metrics.NewRegistry()},
CtxField: context.Background(),
}
m, ok := New().NewModuleInstance(mii).(*ModuleInstance)
require.True(t, ok)
Expand All @@ -146,12 +146,12 @@ func TestMetrics(t *testing.T) {
require.NoError(t, err)

t.Run("ExitInit", func(t *testing.T) {
mii.State = state
mii.InitEnv = nil
mii.StateField = state
mii.InitEnvField = nil
_, err := test.rt.RunString(fmt.Sprintf(`new metrics.%s("my_metric")`, fn))
assert.Contains(t, err.Error(), "metrics must be declared in the init context")
})
mii.State = state
mii.StateField = state
logger := logrus.New()
logger.Out = ioutil.Discard
test.hook = &testutils.SimpleLogrusHook{HookedLevels: logrus.AllLevels}
Expand Down Expand Up @@ -186,10 +186,10 @@ func TestMetricGetName(t *testing.T) {
rt := goja.New()
rt.SetFieldNameMapper(common.FieldNameMapper{})

mii := &modulestest.InstanceCore{
Runtime: rt,
InitEnv: &common.InitEnvironment{Registry: metrics.NewRegistry()},
Ctx: context.Background(),
mii := &modulestest.VU{
RuntimeField: rt,
InitEnvField: &common.InitEnvironment{Registry: metrics.NewRegistry()},
CtxField: context.Background(),
}
m, ok := New().NewModuleInstance(mii).(*ModuleInstance)
require.True(t, ok)
Expand All @@ -214,10 +214,10 @@ func TestMetricDuplicates(t *testing.T) {
rt := goja.New()
rt.SetFieldNameMapper(common.FieldNameMapper{})

mii := &modulestest.InstanceCore{
Runtime: rt,
InitEnv: &common.InitEnvironment{Registry: metrics.NewRegistry()},
Ctx: context.Background(),
mii := &modulestest.VU{
RuntimeField: rt,
InitEnvField: &common.InitEnvironment{Registry: metrics.NewRegistry()},
CtxField: context.Background(),
}
m, ok := New().NewModuleInstance(mii).(*ModuleInstance)
require.True(t, ok)
Expand Down
73 changes: 14 additions & 59 deletions js/modules/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,11 @@ type HasModuleInstancePerVU interface {
NewModuleInstancePerVU() interface{}
}

// Module is the interface js modules should implement in order to get access to the InstanceCore
// Module is the interface js modules should implement in order to get access to the VU
type Module interface {
// NewModuleInstance will get InstanceCore that should provide the module with *everything* it needs and return an
// Instance implementation (embedding the InstanceCore).
// This method will be called for *each* require/import and return an object for VUs.
NewModuleInstance(InstanceCore) Instance
// NewModuleInstance will get modules.VU that should provide the module with a way to interact with the VU
// This method will be called for *each* require/import and should return an unique instance for each call
NewModuleInstance(VU) Instance
}

// checks that modules implement HasModuleInstancePerVU
Expand All @@ -92,7 +91,6 @@ func GetJSModules() map[string]interface{} {

// Instance is what a module needs to return
type Instance interface {
InstanceCore
Exports() Exports
}

Expand All @@ -108,21 +106,19 @@ func getInterfaceMethods() []string {
return result
}

// InstanceCore is something that will be provided to modules and they need to embed it in ModuleInstance
type InstanceCore interface {
GetContext() context.Context
// VU gives access to the currently executing VU to a module Instance
type VU interface {
// Context return the context.Context about the current VU
Context() context.Context

// GetInitEnv returns common.InitEnvironment instance if present
GetInitEnv() *common.InitEnvironment
// InitEnv returns common.InitEnvironment instance if present
InitEnv() *common.InitEnvironment

// GetState returns lib.State if any is present
GetState() *lib.State
// State returns lib.State if any is present
State() *lib.State

// GetRuntime returns the goja.Runtime for the current VU
GetRuntime() *goja.Runtime

// sealing field will help probably with pointing users that they just need to embed this in their Instance
// implementations
// Runtime returns the goja.Runtime for the current VU
Runtime() *goja.Runtime
}

// Exports is representation of ESM exports of a module
Expand All @@ -132,44 +128,3 @@ type Exports struct {
// Named is the named exports of a module
Named map[string]interface{}
}

// GenerateExports generates an Exports from a module akin to how common.Bind does now.
// it also skips anything that is expected will not want to be exported such as methods and fields coming from
// interfaces defined in this package.
func GenerateExports(v interface{}) Exports {
exports := make(map[string]interface{})
val := reflect.ValueOf(v)
typ := val.Type()
badNames := getInterfaceMethods()
outer:
for i := 0; i < typ.NumMethod(); i++ {
meth := typ.Method(i)
for _, badname := range badNames {
if meth.Name == badname {
continue outer
}
}
name := common.MethodName(typ, meth)

fn := val.Method(i)
exports[name] = fn.Interface()
}

// If v is a pointer, we need to indirect it to access its fields.
if typ.Kind() == reflect.Ptr {
val = val.Elem()
typ = val.Type()
}
var mic InstanceCore // TODO move this out
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if field.Type == reflect.TypeOf(&mic).Elem() {
continue
}
name := common.FieldName(typ, field)
if name != "" {
exports[name] = val.Field(i).Interface()
}
}
return Exports{Default: exports, Named: exports}
}
Loading

0 comments on commit 5efd660

Please sign in to comment.