cmd/cmd.go (116 lines of code) (raw):
/*
Copyright (c) Facebook, Inc. and its affiliates.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
*/
package cmd
import (
"fmt"
"os"
"time"
"github.com/facebookincubator/fbender/cmd/core"
"github.com/facebookincubator/fbender/cmd/dhcpv4"
"github.com/facebookincubator/fbender/cmd/dhcpv6"
"github.com/facebookincubator/fbender/cmd/dns"
"github.com/facebookincubator/fbender/cmd/http"
"github.com/facebookincubator/fbender/cmd/tftp"
"github.com/facebookincubator/fbender/cmd/udp"
"github.com/facebookincubator/fbender/flags"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
// Subcommands are the protocol subcommands.
//nolint:gochecknoglobals
var Subcommands = []*cobra.Command{
dhcpv4.Command,
dhcpv6.Command,
dns.Command,
http.Command,
tftp.Command,
udp.Command,
}
// Command is the root command for the CLI.
//nolint:gochecknoglobals
var Command = &cobra.Command{
Use: "fbender",
Long: `FBender is a load tester tool for various protocols. It provides two different
approaches to load testing: Throughput and Concurrency and each of them can have
either fixed or constraints based test values. Throughput tests give the tester
control over the throughput (QPS), but not over concurrency. The second gives
the user control over the concurrency but not over the throughput.
* fixed - runs a single test for each of the specified values.
* constraint - runs tests adjusting load based on the growth and constraints.
Target:
Target format may vary depending on the protocol, however most of them accept
ipv4, ipv6, hostname with an optional port. Use "fbender protocol --help" to get
the documentation on the target format for a specific protocol.
Input:
Unless explicitly stated in the command documentation one request is generated
per input line, skipping the lines with improper format. Use "fbender
protocol help" to get the documentation on the input format for a specific
protocol. The generated requests are then reused in a round-robin manner.
Output:
All important information is printed to stdout. Test logs can be redirected
using the output flag. They can also be filtered based on the message verbosity
level. Note that this filters/redirect only test logs and not the summary and
other output. Available levels (both numbers and literals are accepted):
* panic/0
* fatal/1
* error/2
* warning/3 - log when an *error response* is received
* info/4 - log when a *successful response* is received
* debug/5 - log when a *request* is sent
`,
Example: ` fbender dns throughput fixed -t $TARGET 100
fbender tftp concurrency fixed -t $TARGET -o /dev/null 10
fbender udp throughput fixed -t $TARGET -d 5m 100 200 300
fbender http concurrency constraints -t $TARGET 20 -c "MAX(errors)<5"
fbender dhcpv6 throughput constraints -t $TARGET 50 -c "MIN(latency)<20"
fbender dns throughput constraints -t $TARGET 40 -c -g ^10 "MAX(errors)<5"`,
}
func initIOFlags() {
// Input
Command.PersistentFlags().StringP("input", "i", "", "load test input data from a file (default <stdin>)")
if err := Command.MarkPersistentFlagFilename("input"); err != nil {
panic(err)
}
// Output
logOutput := flags.NewLogOutput(logrus.StandardLogger())
Command.PersistentFlags().VarP(logOutput, "output", "o", "log test output to a file")
if err := Command.MarkPersistentFlagFilename("output"); err != nil {
panic(err)
}
// Log Level
logLevel := &flags.LogLevel{Logger: logrus.StandardLogger()}
logLevelChoices := flags.ChoicesString(flags.LogLevelChoices())
Command.PersistentFlags().VarP(logLevel, "verbosity", "v", fmt.Sprintf("verbosity level %s", logLevelChoices))
if err := flags.BashCompletionLogLevel(Command, Command.PersistentFlags(), "verbosity"); err != nil {
panic(err)
}
// Log format
logFormat := &flags.LogFormat{Logger: logrus.StandardLogger(), Format: "json"}
logFormatChoices := flags.ChoicesString(flags.LogFormatChoices())
Command.PersistentFlags().VarP(logFormat, "format", "f", fmt.Sprintf("output format %s", logFormatChoices))
if err := flags.BashCompletionLogFormat(Command, Command.PersistentFlags(), "format"); err != nil {
panic(err)
}
}
func initExecutionFlags() {
// Test duration
Command.PersistentFlags().DurationP("duration", "d", 1*time.Minute, "single test duration")
// Requests distribution
distribution := flags.NewDefaultDistribution()
distributionChoices := flags.ChoicesString(flags.DistributionChoices())
Command.PersistentFlags().VarP(distribution, "dist", "D", fmt.Sprintf("requests distribution %s", distributionChoices))
if err := flags.BashCompletionDistribution(Command, Command.PersistentFlags(), "dist"); err != nil {
panic(err)
}
// Other settings
Command.PersistentFlags().IntP("buffer", "b", 2048, "buffer size of the requests generator channel")
Command.PersistentFlags().DurationP("timeout", "w", 1*time.Second, "wait timeout on requests")
Command.PersistentFlags().DurationP("unit", "u", 1*time.Millisecond, "histogram scaling unit")
Command.PersistentFlags().Bool("nostats", false, "disable statistics")
}
//nolint:gochecknoinits
func init() {
cobra.EnablePrefixMatching = true
initIOFlags()
initExecutionFlags()
for _, subcommand := range Subcommands {
Command.AddCommand(subcommand)
subcommand.PersistentFlags().StringP("target", "t", "", "endpoint to load test")
if err := subcommand.MarkPersistentFlagRequired("target"); err != nil {
panic(err)
}
}
Command.AddCommand(completionCmd)
core.StartPostInit()
}
// Execute runs the Command.
func Execute() {
if err := Command.Execute(); err != nil {
os.Exit(1)
}
}