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

Extend k6 inspect command with additional flag #2279

Merged
merged 2 commits into from
Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions cmd/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ import (
"github.com/spf13/pflag"

"go.k6.io/k6/lib/metrics"
"go.k6.io/k6/loader"
)

var archiveOut = "archive.tar"

func getArchiveCmd(logger *logrus.Logger) *cobra.Command {
// archiveCmd represents the pause command
// archiveCmd represents the archive command
archiveCmd := &cobra.Command{
Use: "archive",
Short: "Create an archive",
Expand All @@ -50,14 +49,7 @@ An archive is a fully self-contained test run, and can be executed identically e
k6 run myarchive.tar`[1:],
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// Runner.
pwd, err := os.Getwd()
if err != nil {
return err
}
filename := args[0]
filesystems := loader.CreateFilesystems()
src, err := loader.ReadSource(logger, filename, pwd, filesystems, os.Stdin)
src, filesystems, err := readSource(args[0], logger)
if err != nil {
return err
}
Expand All @@ -78,7 +70,7 @@ An archive is a fully self-contained test run, and can be executed identically e
if err != nil {
return err
}
conf, err := getConsolidatedConfig(afero.NewOsFs(), Config{Options: cliOpts}, r)
conf, err := getConsolidatedConfig(afero.NewOsFs(), Config{Options: cliOpts}, r.GetOptions())
if err != nil {
return err
}
Expand Down
11 changes: 2 additions & 9 deletions cmd/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import (
"go.k6.io/k6/lib"
"go.k6.io/k6/lib/consts"
"go.k6.io/k6/lib/metrics"
"go.k6.io/k6/loader"
"go.k6.io/k6/ui/pb"
)

Expand Down Expand Up @@ -90,14 +89,8 @@ This will execute the test on the k6 cloud service. Use "k6 login cloud" to auth
printBar(progressBar)

// Runner
pwd, err := os.Getwd()
if err != nil {
return err
}

filename := args[0]
filesystems := loader.CreateFilesystems()
src, err := loader.ReadSource(logger, filename, pwd, filesystems, os.Stdin)
src, filesystems, err := readSource(filename, logger)
if err != nil {
return err
}
Expand All @@ -121,7 +114,7 @@ This will execute the test on the k6 cloud service. Use "k6 login cloud" to auth
if err != nil {
return err
}
conf, err := getConsolidatedConfig(afero.NewOsFs(), Config{Options: cliOpts}, r)
conf, err := getConsolidatedConfig(afero.NewOsFs(), Config{Options: cliOpts}, r.GetOptions())
if err != nil {
return err
}
Expand Down
42 changes: 42 additions & 0 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,20 @@
package cmd

import (
"archive/tar"
"bytes"
"fmt"
"io"
"os"

"github.com/sirupsen/logrus"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"gopkg.in/guregu/null.v3"

"go.k6.io/k6/lib/types"
"go.k6.io/k6/loader"
)

// Use these when interacting with fs and writing to terminal, makes a command testable
Expand Down Expand Up @@ -88,3 +92,41 @@ func exactArgsWithMsg(n int, msg string) cobra.PositionalArgs {
return nil
}
}

// readSource is a small wrapper around loader.ReadSource returning
// result of the load and filesystems map
func readSource(filename string, logger *logrus.Logger) (*loader.SourceData, map[string]afero.Fs, error) {
pwd, err := os.Getwd()
if err != nil {
return nil, nil, err
}

filesystems := loader.CreateFilesystems()
src, err := loader.ReadSource(logger, filename, pwd, filesystems, os.Stdin)
return src, filesystems, err
}

// TODO: consider moving this out as a method of SourceData ?
func getRunType(src *loader.SourceData) string {
typ := runType
if typ == "" {
typ = detectType(src.Data)
}
return typ
}

func detectType(data []byte) string {
if _, err := tar.NewReader(bytes.NewReader(data)).Next(); err == nil {
return typeArchive
}
return typeJS
}

// fprintf panics when where's an error writing to the supplied io.Writer
func fprintf(w io.Writer, format string, a ...interface{}) (n int) {
n, err := fmt.Fprintf(w, format, a...)
if err != nil {
panic(err.Error())
}
return n
}
10 changes: 5 additions & 5 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,13 @@ func readEnvConfig() (Config, error) {
// Assemble the final consolidated configuration from all of the different sources:
// - start with the CLI-provided options to get shadowed (non-Valid) defaults in there
// - add the global file config options
// - if supplied, add the Runner-provided options
// - add the Runner-provided options (they may come from Bundle too if applicable)
// - add the environment variables
// - merge the user-supplied CLI flags back in on top, to give them the greatest priority
// - set some defaults if they weren't previously specified
// TODO: add better validation, more explicit default values and improve consistency between formats
// TODO: accumulate all errors and differentiate between the layers?
func getConsolidatedConfig(fs afero.Fs, cliConf Config, runner lib.Runner) (conf Config, err error) {
func getConsolidatedConfig(fs afero.Fs, cliConf Config, runnerOpts lib.Options) (conf Config, err error) {
// TODO: use errext.WithExitCodeIfNone(err, exitcodes.InvalidConfig) where it makes sense?

fileConf, _, err := readDiskConfig(fs)
Expand All @@ -183,9 +183,9 @@ func getConsolidatedConfig(fs afero.Fs, cliConf Config, runner lib.Runner) (conf
}

conf = cliConf.Apply(fileConf)
if runner != nil {
conf = conf.Apply(Config{Options: runner.GetOptions()})
}

conf = conf.Apply(Config{Options: runnerOpts})

conf = conf.Apply(envConf).Apply(cliConf)
conf = applyDefault(conf)

Expand Down
8 changes: 5 additions & 3 deletions cmd/config_consolidation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,16 +569,18 @@ func runTestCase(
}
require.NoError(t, cliErr)

var runner lib.Runner
var runnerOpts lib.Options
if testCase.options.runner != nil {
runner = &minirunner.MiniRunner{Options: *testCase.options.runner}
runnerOpts = minirunner.MiniRunner{Options: *testCase.options.runner}.GetOptions()
}
// without runner creation, values in runnerOpts will simply be invalid

if testCase.options.fs == nil {
t.Logf("Creating an empty FS for this test")
testCase.options.fs = afero.NewMemMapFs() // create an empty FS if it wasn't supplied
}

consolidatedConfig, err := getConsolidatedConfig(testCase.options.fs, cliConf, runner)
consolidatedConfig, err := getConsolidatedConfig(testCase.options.fs, cliConf, runnerOpts)
if testCase.expected.consolidationError {
require.Error(t, err)
return
Expand Down
96 changes: 71 additions & 25 deletions cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,80 +27,126 @@ import (
"os"

"github.com/sirupsen/logrus"
"github.com/spf13/afero"
"github.com/spf13/cobra"

"go.k6.io/k6/core/local"
"go.k6.io/k6/js"
"go.k6.io/k6/lib"
"go.k6.io/k6/lib/metrics"
"go.k6.io/k6/loader"
"go.k6.io/k6/lib/types"
)

func getInspectCmd(logger logrus.FieldLogger) *cobra.Command {
func getInspectCmd(logger *logrus.Logger) *cobra.Command {
var addExecReqs bool

// inspectCmd represents the inspect command
inspectCmd := &cobra.Command{
Use: "inspect [file]",
Short: "Inspect a script or archive",
Long: `Inspect a script or archive.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
pwd, err := os.Getwd()
if err != nil {
return err
}
filesystems := loader.CreateFilesystems()
src, err := loader.ReadSource(logger, args[0], pwd, filesystems, os.Stdin)
src, filesystems, err := readSource(args[0], logger)
if err != nil {
return err
}

typ := runType
if typ == "" {
typ = detectType(src.Data)
}

runtimeOptions, err := getRuntimeOptions(cmd.Flags(), buildEnvMap(os.Environ()))
if err != nil {
return err
}
registry := metrics.NewRegistry()
_ = metrics.RegisterBuiltinMetrics(registry)
builtinMetrics := metrics.RegisterBuiltinMetrics(registry)

var (
opts lib.Options
b *js.Bundle
)
switch typ {
var b *js.Bundle
switch getRunType(src) {
// this is an exhaustive list
case typeArchive:
var arc *lib.Archive
arc, err = lib.ReadArchive(bytes.NewBuffer(src.Data))
if err != nil {
return err
}
b, err = js.NewBundleFromArchive(logger, arc, runtimeOptions, registry)
if err != nil {
return err
}
opts = b.Options

case typeJS:
b, err = js.NewBundle(logger, src, filesystems, runtimeOptions, registry)
}
if err != nil {
return err
}

// ATM, output can take 2 forms: standard (equal to lib.Options struct) and extended, with additional fields.
inspectOutput := interface{}(b.Options)

if addExecReqs {
inspectOutput, err = addExecRequirements(b, builtinMetrics, registry, logger)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm missing something but could we reuse the newRunner function from cmd/run.go and inject the runner here?

k6/cmd/run.go

Line 390 in 5c14dae

func newRunner(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I started with that approach but newRunner creates a runner from loader.SourceData so it duplicates what was already done in inspect cmd, i.e. js.New / lib.ReadArchive calls. So primarily I didn't use newRunner to avoid duplication. Additionally, it just makes sense to me for NewFromBundle to be public too, even if it isn't used anywhere else right now.

if err != nil {
return err
}
opts = b.Options
}

data, err := json.MarshalIndent(opts, "", " ")
data, err := json.MarshalIndent(inspectOutput, "", " ")
if err != nil {
return err
}
fmt.Println(string(data))

return nil
},
}

inspectCmd.Flags().SortFlags = false
inspectCmd.Flags().AddFlagSet(runtimeOptionFlagSet(false))
inspectCmd.Flags().StringVarP(&runType, "type", "t", runType, "override file `type`, \"js\" or \"archive\"")
inspectCmd.Flags().BoolVar(&addExecReqs,
"execution-requirements",
false,
"include calculations of execution requirements for the test")

return inspectCmd
}

func addExecRequirements(b *js.Bundle,
builtinMetrics *metrics.BuiltinMetrics, registry *metrics.Registry,
logger *logrus.Logger) (interface{}, error) {
// TODO: after #1048 issue, consider rewriting this without a Runner:
// just creating ExecutionPlan directly from validated options

runner, err := js.NewFromBundle(logger, b, builtinMetrics, registry)
if err != nil {
return nil, err
}

conf, err := getConsolidatedConfig(afero.NewOsFs(), Config{}, runner.GetOptions())
if err != nil {
return nil, err
}

conf, err = deriveAndValidateConfig(conf, runner.IsExecutable)
if err != nil {
return nil, err
}

if err = runner.SetOptions(conf.Options); err != nil {
return nil, err
}
execScheduler, err := local.NewExecutionScheduler(runner, logger)
if err != nil {
return nil, err
}

executionPlan := execScheduler.GetExecutionPlan()
duration, _ := lib.GetEndOffset(executionPlan)

return struct {
lib.Options
TotalDuration types.NullDuration `json:"totalDuration"`
MaxVUs uint64 `json:"maxVUs"`
}{
conf.Options,
types.NewNullDuration(duration, true),
lib.GetMaxPossibleVUs(executionPlan),
}, nil
}
10 changes: 0 additions & 10 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"context"
"errors"
"fmt"
"io"
"io/ioutil"
stdlog "log"
"os"
Expand Down Expand Up @@ -242,15 +241,6 @@ func (c *rootCommand) rootCmdPersistentFlagSet() *pflag.FlagSet {
return flags
}

// fprintf panics when where's an error writing to the supplied io.Writer
func fprintf(w io.Writer, format string, a ...interface{}) (n int) {
n, err := fmt.Fprintf(w, format, a...)
if err != nil {
panic(err.Error())
}
return n
}

// RawFormatter it does nothing with the message just prints it
type RawFormatter struct{}

Expand Down
Loading