ClickHouse/tools/clickhouse-diagnostics/cmd/collect.go
2022-04-27 13:22:20 +01:00

159 lines
5.8 KiB
Go

package cmd
import (
"fmt"
"github.com/ClickHouse/clickhouse-diagnostics/cmd/params"
"github.com/ClickHouse/clickhouse-diagnostics/internal"
"github.com/ClickHouse/clickhouse-diagnostics/internal/collectors"
_ "github.com/ClickHouse/clickhouse-diagnostics/internal/collectors/clickhouse"
_ "github.com/ClickHouse/clickhouse-diagnostics/internal/collectors/system"
"github.com/ClickHouse/clickhouse-diagnostics/internal/outputs"
_ "github.com/ClickHouse/clickhouse-diagnostics/internal/outputs/file"
_ "github.com/ClickHouse/clickhouse-diagnostics/internal/outputs/terminal"
"github.com/ClickHouse/clickhouse-diagnostics/internal/platform/utils"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"os"
"strings"
)
var id string
var output = params.StringOptionsVar{
Options: outputs.GetOutputNames(),
Value: "simple",
}
// access credentials
var host string
var port uint16
var username string
var password string
var collectorNames = params.StringSliceOptionsVar{
Options: collectors.GetCollectorNames(false),
Values: collectors.GetCollectorNames(true),
}
// holds the collector params passed by the cli
var collectorParams params.ParamMap
// holds the output params passed by the cli
var outputParams params.ParamMap
const collectHelpTemplate = `Usage:{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}
Additional help topics:
Use "{{.CommandPath}} [command] --help" for more information about a command.
Use "{{.Parent.Name}} help --collector [collector]" for more information about a specific collector.
Use "{{.Parent.Name}} help --output [output]" for more information about a specific output.
`
func init() {
collectCmd.Flags().StringVar(&id, "id", getHostName(), "Id of diagnostic bundle")
// access credentials
collectCmd.Flags().StringVar(&host, "host", "localhost", "ClickHouse host")
collectCmd.Flags().Uint16VarP(&port, "port", "p", 9000, "ClickHouse native port")
collectCmd.Flags().StringVarP(&username, "username", "u", "", "ClickHouse username")
collectCmd.Flags().StringVar(&password, "password", "", "ClickHouse password")
// collectors and outputs
collectCmd.Flags().VarP(&output, "output", "o", fmt.Sprintf("Output Format for the diagnostic Bundle, options: [%s]\n", strings.Join(output.Options, ",")))
collectCmd.Flags().VarP(&collectorNames, "collectors", "c", fmt.Sprintf("Collectors to use, options: [%s]\n", strings.Join(collectorNames.Options, ",")))
collectorConfigs, err := collectors.BuildConfigurationOptions()
if err != nil {
log.Fatal().Err(err).Msg("Unable to build collector configurations")
}
collectorParams = params.NewParamMap(collectorConfigs)
outputConfigs, err := outputs.BuildConfigurationOptions()
if err != nil {
log.Fatal().Err(err).Msg("Unable to build output configurations")
}
params.AddParamMapToCmd(collectorParams, collectCmd, "collector", true)
outputParams = params.NewParamMap(outputConfigs)
params.AddParamMapToCmd(outputParams, collectCmd, "output", true)
collectCmd.SetFlagErrorFunc(handleFlagErrors)
collectCmd.SetHelpTemplate(collectHelpTemplate)
rootCmd.AddCommand(collectCmd)
}
var collectCmd = &cobra.Command{
Use: "collect",
Short: "Collect a diagnostic bundle",
Long: `Collect a ClickHouse diagnostic bundle for a specified ClickHouse instance`,
PreRun: func(cmd *cobra.Command, args []string) {
bindFlagsToConfig(cmd)
},
Example: fmt.Sprintf(`%s collect --username default --collector=%s --output=simple`, rootCmd.Name(), strings.Join(collectorNames.Options[:2], ",")),
Run: func(cmd *cobra.Command, args []string) {
log.Info().Msgf("executing collect command with %v collectors and %s output", collectorNames.Values, output.Value)
outputConfig := params.ConvertParamsToConfig(outputParams)[output.Value]
runConfig := internal.NewRunConfiguration(id, host, port, username, password, output.Value, outputConfig, collectorNames.Values, params.ConvertParamsToConfig(collectorParams))
internal.Capture(runConfig)
os.Exit(0)
},
}
func getHostName() string {
name, err := os.Hostname()
if err != nil {
name = "clickhouse-diagnostics"
}
return name
}
// these flags are nested under the cmd name in the config file to prevent collisions
var flagsToNest = []string{"output", "collectors"}
// this saves us binding each command manually to viper
func bindFlagsToConfig(cmd *cobra.Command) {
cmd.Flags().VisitAll(func(f *pflag.Flag) {
err := viper.BindEnv(f.Name, fmt.Sprintf("%s_%s", envPrefix,
strings.ToUpper(strings.Replace(f.Name, ".", "_", -1))))
if err != nil {
log.Error().Msgf("Unable to bind %s to config", f.Name)
}
configFlagName := f.Name
if utils.Contains(flagsToNest, f.Name) {
configFlagName = fmt.Sprintf("%s.%s", cmd.Use, configFlagName)
}
err = viper.BindPFlag(configFlagName, f)
if err != nil {
log.Error().Msgf("Unable to bind %s to config", f.Name)
}
// here we prefer the config value when the param is not set on the cmd line
if !f.Changed && viper.IsSet(configFlagName) {
val := viper.Get(configFlagName)
log.Debug().Msgf("Setting parameter %s from configuration file", f.Name)
err = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
if err != nil {
log.Error().Msgf("Unable to read \"%s\" value from config", f.Name)
} else {
log.Debug().Msgf("Set parameter \"%s\" from configuration", f.Name)
}
}
})
}