Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support to collect Usage dynamically #3917

Merged
merged 10 commits into from
Sep 10, 2024
2 changes: 2 additions & 0 deletions api/v1/group_routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"go.k6.io/k6/lib/testutils/minirunner"
"go.k6.io/k6/metrics"
"go.k6.io/k6/metrics/engine"
"go.k6.io/k6/usage"
)

func getTestPreInitState(tb testing.TB) *lib.TestPreInitState {
Expand All @@ -27,6 +28,7 @@ func getTestPreInitState(tb testing.TB) *lib.TestPreInitState {
RuntimeOptions: lib.RuntimeOptions{},
Registry: reg,
BuiltinMetrics: metrics.RegisterBuiltinMetrics(reg),
Usage: usage.New(),
}
}

Expand Down
7 changes: 7 additions & 0 deletions cmd/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func createOutputs(
ScriptOptions: test.derivedConfig.Options,
RuntimeOptions: test.preInitState.RuntimeOptions,
ExecutionPlan: executionPlan,
Usage: test.preInitState.Usage,
}

outputs := test.derivedConfig.Out
Expand All @@ -136,6 +137,12 @@ func createOutputs(
outputType, getPossibleIDList(outputConstructors),
)
}
if _, builtinErr := builtinOutputString(outputType); builtinErr == nil {
err := test.preInitState.Usage.Strings("outputs", outputType)
if err != nil {
gs.Logger.WithError(err).Warnf("Couldn't report usage for output %q", outputType)
}
}

params := baseParams
params.OutputType = outputType
Expand Down
88 changes: 15 additions & 73 deletions cmd/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,92 +6,34 @@ import (
"encoding/json"
"net/http"
"runtime"
"strings"

"go.k6.io/k6/execution"
"go.k6.io/k6/lib/consts"
"go.k6.io/k6/usage"
)

type report struct {
Version string `json:"k6_version"`
Executors map[string]int `json:"executors"`
VUsMax int64 `json:"vus_max"`
Iterations uint64 `json:"iterations"`
Duration string `json:"duration"`
GoOS string `json:"goos"`
GoArch string `json:"goarch"`
Modules []string `json:"modules"`
Outputs []string `json:"outputs"`
}

func createReport(execScheduler *execution.Scheduler, importedModules []string, outputs []string) report {
func createReport(u *usage.Usage, execScheduler *execution.Scheduler) map[string]any {
execState := execScheduler.GetState()
m := u.Map()

m["k6_version"] = consts.Version
m["duration"] = execState.GetCurrentTestRunDuration().String()
m["goos"] = runtime.GOOS
m["goarch"] = runtime.GOARCH
m["vus_max"] = uint64(execState.GetInitializedVUsCount())
m["iterations"] = execState.GetFullIterationCount()
executors := make(map[string]int)
for _, ec := range execScheduler.GetExecutorConfigs() {
executors[ec.GetType()]++
}
m["executors"] = executors

// collect the report only with k6 public modules
publicModules := make([]string, 0, len(importedModules))
for _, module := range importedModules {
// Exclude JS modules extensions to prevent to leak
// any user's custom extensions
if strings.HasPrefix(module, "k6/x") {
continue
}
// Exclude any import not starting with the k6 prefix
// that identifies a k6 built-in stable or experimental module.
// For example, it doesn't include any modules imported from the file system.
if !strings.HasPrefix(module, "k6") {
continue
}
publicModules = append(publicModules, module)
}

builtinOutputs := builtinOutputStrings()

// TODO: migrate to slices.Contains as soon as the k6 support
// for Go1.20 will be over.
builtinOutputsIndex := make(map[string]bool, len(builtinOutputs))
for _, bo := range builtinOutputs {
builtinOutputsIndex[bo] = true
}

// collect only the used outputs that are builtin
publicOutputs := make([]string, 0, len(builtinOutputs))
for _, o := range outputs {
// TODO:
// if !slices.Contains(builtinOutputs, o) {
// continue
// }
if !builtinOutputsIndex[o] {
continue
}
publicOutputs = append(publicOutputs, o)
}

execState := execScheduler.GetState()
return report{
Version: consts.Version,
Executors: executors,
VUsMax: execState.GetInitializedVUsCount(),
Iterations: execState.GetFullIterationCount(),
Duration: execState.GetCurrentTestRunDuration().String(),
GoOS: runtime.GOOS,
GoArch: runtime.GOARCH,
Modules: publicModules,
Outputs: publicOutputs,
}
return m
}

func reportUsage(ctx context.Context, execScheduler *execution.Scheduler, test *loadedAndConfiguredTest) error {
outputs := make([]string, 0, len(test.derivedConfig.Out))
for _, o := range test.derivedConfig.Out {
outputName, _ := parseOutputArgument(o)
outputs = append(outputs, outputName)
}

r := createReport(execScheduler, test.moduleResolver.Imported(), outputs)
body, err := json.Marshal(r)
m := createReport(test.preInitState.Usage, execScheduler)
body, err := json.Marshal(m)
if err != nil {
return err
}
Expand Down
31 changes: 9 additions & 22 deletions cmd/report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,12 @@ import (
"go.k6.io/k6/lib/consts"
"go.k6.io/k6/lib/executor"
"go.k6.io/k6/lib/testutils"
"go.k6.io/k6/usage"
"gopkg.in/guregu/null.v3"
)

func TestCreateReport(t *testing.T) {
t.Parallel()
importedModules := []string{
"k6/http",
"my-custom-module",
"k6/experimental/webcrypto",
"file:custom-from-file-system",
"k6",
"k6/x/custom-extension",
}

outputs := []string{
"json",
"xk6-output-custom-example",
}

logger := testutils.NewLogger(t)
opts, err := executor.DeriveScenariosFromShortcuts(lib.Options{
VUs: null.IntFrom(10),
Expand All @@ -51,12 +38,12 @@ func TestCreateReport(t *testing.T) {
time.Sleep(10 * time.Millisecond)
s.GetState().MarkEnded()

r := createReport(s, importedModules, outputs)
assert.Equal(t, consts.Version, r.Version)
assert.Equal(t, map[string]int{"shared-iterations": 1}, r.Executors)
assert.Equal(t, 6, int(r.VUsMax))
assert.Equal(t, 170, int(r.Iterations))
assert.NotEqual(t, "0s", r.Duration)
assert.ElementsMatch(t, []string{"k6", "k6/http", "k6/experimental/webcrypto"}, r.Modules)
assert.ElementsMatch(t, []string{"json"}, r.Outputs)
m := createReport(usage.New(), s)
require.NoError(t, err)

assert.Equal(t, consts.Version, m["k6_version"])
assert.EqualValues(t, map[string]int{"shared-iterations": 1}, m["executors"])
assert.EqualValues(t, 6, m["vus_max"])
assert.EqualValues(t, 170, m["iterations"])
assert.NotEqual(t, "0s", m["duration"])
}
3 changes: 3 additions & 0 deletions cmd/runtime_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"go.k6.io/k6/lib/fsext"
"go.k6.io/k6/loader"
"go.k6.io/k6/metrics"
"go.k6.io/k6/usage"
)

type runtimeOptionsTestCase struct {
Expand Down Expand Up @@ -70,6 +71,7 @@ func testRuntimeOptionsCase(t *testing.T, tc runtimeOptionsTestCase) {
RuntimeOptions: rtOpts,
Registry: registry,
BuiltinMetrics: metrics.RegisterBuiltinMetrics(registry),
Usage: usage.New(),
},
}

Expand All @@ -89,6 +91,7 @@ func testRuntimeOptionsCase(t *testing.T, tc runtimeOptionsTestCase) {
RuntimeOptions: rtOpts,
Registry: registry,
BuiltinMetrics: metrics.RegisterBuiltinMetrics(registry),
Usage: usage.New(),
},
}
}
Expand Down
2 changes: 2 additions & 0 deletions cmd/test_load.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"go.k6.io/k6/lib/fsext"
"go.k6.io/k6/loader"
"go.k6.io/k6/metrics"
"go.k6.io/k6/usage"
)

const (
Expand Down Expand Up @@ -77,6 +78,7 @@ func loadLocalTest(gs *state.GlobalState, cmd *cobra.Command, args []string) (*l
val, ok := gs.Env[key]
return val, ok
},
Usage: usage.New(),
}

test := &loadedTest{
Expand Down
5 changes: 5 additions & 0 deletions execution/scheduler_ext_exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"go.k6.io/k6/lib/testutils"
"go.k6.io/k6/loader"
"go.k6.io/k6/metrics"
"go.k6.io/k6/usage"
)

// TODO: rewrite and/or move these as integration tests to reduce boilerplate
Expand Down Expand Up @@ -74,6 +75,7 @@ func TestExecutionInfoVUSharing(t *testing.T) {
Logger: logger,
BuiltinMetrics: builtinMetrics,
Registry: registry,
Usage: usage.New(),
},
&loader.SourceData{
URL: &url.URL{Path: "/script.js"},
Expand Down Expand Up @@ -187,6 +189,7 @@ func TestExecutionInfoScenarioIter(t *testing.T) {
Logger: logger,
BuiltinMetrics: builtinMetrics,
Registry: registry,
Usage: usage.New(),
},
&loader.SourceData{
URL: &url.URL{Path: "/script.js"},
Expand Down Expand Up @@ -269,6 +272,7 @@ func TestSharedIterationsStable(t *testing.T) {
Logger: logger,
BuiltinMetrics: builtinMetrics,
Registry: registry,
Usage: usage.New(),
},
&loader.SourceData{
URL: &url.URL{Path: "/script.js"},
Expand Down Expand Up @@ -404,6 +408,7 @@ func TestExecutionInfoAll(t *testing.T) {
Logger: logger,
BuiltinMetrics: builtinMetrics,
Registry: registry,
Usage: usage.New(),
},
&loader.SourceData{
URL: &url.URL{Path: "/script.js"},
Expand Down
4 changes: 4 additions & 0 deletions execution/scheduler_ext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"go.k6.io/k6/lib/types"
"go.k6.io/k6/loader"
"go.k6.io/k6/metrics"
"go.k6.io/k6/usage"
)

func getTestPreInitState(tb testing.TB) *lib.TestPreInitState {
Expand All @@ -41,6 +42,7 @@ func getTestPreInitState(tb testing.TB) *lib.TestPreInitState {
RuntimeOptions: lib.RuntimeOptions{},
Registry: reg,
BuiltinMetrics: metrics.RegisterBuiltinMetrics(reg),
Usage: usage.New(),
}
}

Expand Down Expand Up @@ -1112,6 +1114,7 @@ func TestDNSResolverCache(t *testing.T) {
Logger: logger,
BuiltinMetrics: builtinMetrics,
Registry: registry,
Usage: usage.New(),
},
&loader.SourceData{
URL: &url.URL{Path: "/script.js"}, Data: []byte(script),
Expand Down Expand Up @@ -1399,6 +1402,7 @@ func TestNewSchedulerHasWork(t *testing.T) {
Logger: logger,
Registry: registry,
BuiltinMetrics: metrics.RegisterBuiltinMetrics(registry),
Usage: usage.New(),
}
runner, err := js.New(piState, &loader.SourceData{URL: &url.URL{Path: "/script.js"}, Data: script}, nil)
require.NoError(t, err)
Expand Down
3 changes: 2 additions & 1 deletion js/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ func newBundle(
}

c := bundle.newCompiler(piState.Logger)
bundle.ModuleResolver = modules.NewModuleResolver(getJSModules(), generateFileLoad(bundle), c, bundle.pwd)
bundle.ModuleResolver = modules.NewModuleResolver(
getJSModules(), generateFileLoad(bundle), c, bundle.pwd, piState.Usage, piState.Logger)

// Instantiate the bundle into a new VM using a bound init context. This uses a context with a
// runtime, but no state, to allow module-provided types to function within the init context.
Expand Down
2 changes: 2 additions & 0 deletions js/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"go.k6.io/k6/lib/types"
"go.k6.io/k6/loader"
"go.k6.io/k6/metrics"
"go.k6.io/k6/usage"
)

const isWindows = runtime.GOOS == "windows"
Expand All @@ -43,6 +44,7 @@ func getTestPreInitState(tb testing.TB, logger logrus.FieldLogger, rtOpts *lib.R
RuntimeOptions: *rtOpts,
Registry: reg,
BuiltinMetrics: metrics.RegisterBuiltinMetrics(reg),
Usage: usage.New(),
}
}

Expand Down
3 changes: 3 additions & 0 deletions js/console_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"go.k6.io/k6/lib/testutils"
"go.k6.io/k6/loader"
"go.k6.io/k6/metrics"
"go.k6.io/k6/usage"
)

func TestConsoleContext(t *testing.T) {
Expand Down Expand Up @@ -73,6 +74,7 @@ func getSimpleRunner(tb testing.TB, filename, data string, opts ...interface{})
BuiltinMetrics: builtinMetrics,
Registry: registry,
LookupEnv: func(_ string) (val string, ok bool) { return "", false },
Usage: usage.New(),
},
&loader.SourceData{
URL: &url.URL{Path: filename, Scheme: "file"},
Expand Down Expand Up @@ -110,6 +112,7 @@ func getSimpleArchiveRunner(tb testing.TB, arc *lib.Archive, opts ...interface{}
RuntimeOptions: rtOpts,
BuiltinMetrics: builtinMetrics,
Registry: registry,
Usage: usage.New(),
}, arc)
}

Expand Down
3 changes: 3 additions & 0 deletions js/init_and_modules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"go.k6.io/k6/lib/testutils"
"go.k6.io/k6/loader"
"go.k6.io/k6/metrics"
"go.k6.io/k6/usage"
)

type CheckModule struct {
Expand Down Expand Up @@ -64,6 +65,7 @@ func TestNewJSRunnerWithCustomModule(t *testing.T) {
BuiltinMetrics: builtinMetrics,
Registry: registry,
RuntimeOptions: rtOptions,
Usage: usage.New(),
},
&loader.SourceData{
URL: &url.URL{Path: "blah", Scheme: "file"},
Expand Down Expand Up @@ -101,6 +103,7 @@ func TestNewJSRunnerWithCustomModule(t *testing.T) {
BuiltinMetrics: builtinMetrics,
Registry: registry,
RuntimeOptions: rtOptions,
Usage: usage.New(),
}, arc)
require.NoError(t, err)
assert.Equal(t, checkModule.initCtxCalled, 3) // changes because we need to get the exported functions
Expand Down
2 changes: 2 additions & 0 deletions js/modules/k6/marshalling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"go.k6.io/k6/lib/types"
"go.k6.io/k6/loader"
"go.k6.io/k6/metrics"
"go.k6.io/k6/usage"
)

func TestSetupDataMarshalling(t *testing.T) {
Expand Down Expand Up @@ -103,6 +104,7 @@ func TestSetupDataMarshalling(t *testing.T) {
Logger: testutils.NewLogger(t),
BuiltinMetrics: builtinMetrics,
Registry: registry,
Usage: usage.New(),
},

&loader.SourceData{URL: &url.URL{Path: "/script.js"}, Data: script},
Expand Down
Loading