github.com/grafana/pyroscope@v1.18.0/pkg/usage/usage.go (about)

     1  //nolint:goconst
     2  package usage
     3  
     4  import (
     5  	"flag"
     6  	"fmt"
     7  	"os"
     8  	"reflect"
     9  	"strings"
    10  
    11  	"github.com/grafana/dskit/flagext"
    12  
    13  	"github.com/grafana/pyroscope/pkg/util/fieldcategory"
    14  )
    15  
    16  // Usage prints command-line usage.
    17  // printAll controls whether only basic flags or all flags are included.
    18  // configs are expected to be pointers to structs.
    19  func Usage(printAll bool, configs ...interface{}) error {
    20  	fields := map[uintptr]reflect.StructField{}
    21  	for _, c := range configs {
    22  		if err := parseStructure(c, fields); err != nil {
    23  			return err
    24  		}
    25  	}
    26  
    27  	fs := flag.CommandLine
    28  	fmt.Fprintf(fs.Output(), "Usage of %s:\n", os.Args[0])
    29  	fs.VisitAll(func(fl *flag.Flag) {
    30  		v := reflect.ValueOf(fl.Value)
    31  		fieldCat := fieldcategory.Basic
    32  		var field reflect.StructField
    33  
    34  		// Do not print usage for deprecated flags.
    35  		if fl.Value.String() == "deprecated" {
    36  			return
    37  		}
    38  
    39  		// Do not print usage for vmodule flag which is not exported see glog@v1.1.0/glog.go
    40  		switch fl.Name {
    41  		case "vmodule", "v", "log_backtrace_at", "logtostderr", "alsologtostderr", "stderrthreshold", "log_dir", "log_link", "logbuflevel":
    42  			return
    43  		}
    44  
    45  		if override, ok := fieldcategory.GetOverride(fl.Name); ok {
    46  			fieldCat = override
    47  		} else if v.Kind() == reflect.Ptr {
    48  			ptr := v.Pointer()
    49  			field, ok = fields[ptr]
    50  			if ok {
    51  				catStr := field.Tag.Get("category")
    52  				switch catStr {
    53  				case "advanced":
    54  					fieldCat = fieldcategory.Advanced
    55  				case "experimental":
    56  					fieldCat = fieldcategory.Experimental
    57  				}
    58  			}
    59  		}
    60  
    61  		if fieldCat != fieldcategory.Basic && !printAll {
    62  			// Don't print help for this flag since we're supposed to print only basic flags
    63  			return
    64  		}
    65  
    66  		var b strings.Builder
    67  		// Two spaces before -; see next two comments.
    68  		fmt.Fprintf(&b, "  -%s", fl.Name)
    69  		name := getFlagName(fl)
    70  		if len(name) > 0 {
    71  			b.WriteString(" ")
    72  			b.WriteString(strings.ReplaceAll(name, " ", "-"))
    73  		}
    74  		// Four spaces before the tab triggers good alignment
    75  		// for both 4- and 8-space tab stops.
    76  		b.WriteString("\n    \t")
    77  		if fieldCat == fieldcategory.Experimental {
    78  			b.WriteString("[experimental] ")
    79  		}
    80  		b.WriteString(strings.ReplaceAll(fl.Usage, "\n", "\n    \t"))
    81  
    82  		if defValue := getFlagDefault(fl, field); !isZeroValue(fl, defValue) {
    83  			v := reflect.ValueOf(fl.Value)
    84  			if v.Kind() == reflect.Ptr {
    85  				v = v.Elem()
    86  			}
    87  			if v.Kind() == reflect.String {
    88  				// put quotes on the value
    89  				fmt.Fprintf(&b, " (default %q)", defValue)
    90  			} else {
    91  				fmt.Fprintf(&b, " (default %v)", defValue)
    92  			}
    93  		}
    94  		fmt.Fprint(fs.Output(), b.String(), "\n")
    95  	})
    96  
    97  	if !printAll {
    98  		fmt.Fprintf(fs.Output(), "\nTo see all flags, use -help-all\n")
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  // isZeroValue determines whether the string represents the zero
   105  // value for a flag.
   106  func isZeroValue(fl *flag.Flag, value string) bool {
   107  	// Build a zero value of the flag's Value type, and see if the
   108  	// result of calling its String method equals the value passed in.
   109  	// This works unless the Value type is itself an interface type.
   110  	typ := reflect.TypeOf(fl.Value)
   111  	var z reflect.Value
   112  	if typ.Kind() == reflect.Ptr {
   113  		z = reflect.New(typ.Elem())
   114  	} else {
   115  		z = reflect.Zero(typ)
   116  	}
   117  	return value == z.Interface().(flag.Value).String()
   118  }
   119  
   120  // parseStructure parses a struct and populates fields.
   121  func parseStructure(structure interface{}, fields map[uintptr]reflect.StructField) error {
   122  	// structure is expected to be a pointer to a struct
   123  	if reflect.TypeOf(structure).Kind() != reflect.Ptr {
   124  		t := reflect.TypeOf(structure)
   125  		return fmt.Errorf("%s is a %s while a %s is expected", t, t.Kind(), reflect.Ptr)
   126  	}
   127  	v := reflect.ValueOf(structure).Elem()
   128  	if v.Kind() != reflect.Struct {
   129  		return fmt.Errorf("%s is a %s while a %s is expected", v, v.Kind(), reflect.Struct)
   130  	}
   131  
   132  	t := v.Type()
   133  
   134  	for i := 0; i < t.NumField(); i++ {
   135  		field := t.Field(i)
   136  		if field.Type.Kind() == reflect.Func {
   137  			continue
   138  		}
   139  
   140  		fieldValue := v.FieldByIndex(field.Index)
   141  
   142  		// Take address of field value and map it to field
   143  		fields[fieldValue.Addr().Pointer()] = field
   144  
   145  		// Recurse if a struct
   146  		if field.Type.Kind() != reflect.Struct || ignoreStructType(field.Type) || !field.IsExported() {
   147  			continue
   148  		}
   149  
   150  		if err := parseStructure(fieldValue.Addr().Interface(), fields); err != nil {
   151  			return err
   152  		}
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  // Descending into some structs breaks check for "advanced" category for some fields (eg. flagext.Secret),
   159  // because field itself is at the same memory address as the internal field in the struct, and advanced-category-check
   160  // then gets confused.
   161  var ignoredStructTypes = []reflect.Type{
   162  	reflect.TypeOf(flagext.Secret{}),
   163  }
   164  
   165  func ignoreStructType(fieldType reflect.Type) bool {
   166  	for _, t := range ignoredStructTypes {
   167  		if fieldType == t {
   168  			return true
   169  		}
   170  	}
   171  	return false
   172  }
   173  
   174  func getFlagName(fl *flag.Flag) string {
   175  	if getter, ok := fl.Value.(flag.Getter); ok {
   176  		if v := reflect.ValueOf(getter.Get()); v.IsValid() {
   177  			t := v.Type()
   178  			switch t.Name() {
   179  			case "bool":
   180  				return ""
   181  			case "Duration":
   182  				return "duration"
   183  			case "float64":
   184  				return "float"
   185  			case "int", "int64":
   186  				return "int"
   187  			case "string":
   188  				return "string"
   189  			case "uint", "uint64":
   190  				return "uint"
   191  			case "Secret":
   192  				return "string"
   193  			default:
   194  				return "value"
   195  			}
   196  		}
   197  	}
   198  
   199  	// Check custom types.
   200  	if v := reflect.ValueOf(fl.Value); v.IsValid() {
   201  		switch v.Type().String() {
   202  		case "*flagext.Secret":
   203  			return "string"
   204  		case "*flagext.StringSlice":
   205  			return "string"
   206  		case "*flagext.StringSliceCSV":
   207  			return "comma-separated list of strings"
   208  		case "*flagext.CIDRSliceCSV":
   209  			return "comma-separated list of strings"
   210  		case "*flagext.URLValue":
   211  			return "string"
   212  		case "*url.URL":
   213  			return "string"
   214  		case "*model.Duration":
   215  			return "duration"
   216  		case "*tsdb.DurationList":
   217  			return "comma-separated list of durations"
   218  		}
   219  	}
   220  
   221  	return "value"
   222  }
   223  
   224  func getFlagDefault(fl *flag.Flag, field reflect.StructField) string {
   225  	if docDefault := parseDocTag(field)["default"]; docDefault != "" {
   226  		return docDefault
   227  	}
   228  	return fl.DefValue
   229  }
   230  
   231  func parseDocTag(f reflect.StructField) map[string]string {
   232  	cfg := map[string]string{}
   233  	tag := f.Tag.Get("doc")
   234  
   235  	if tag == "" {
   236  		return cfg
   237  	}
   238  
   239  	for _, entry := range strings.Split(tag, "|") {
   240  		parts := strings.SplitN(entry, "=", 2)
   241  
   242  		switch len(parts) {
   243  		case 1:
   244  			cfg[parts[0]] = ""
   245  		case 2:
   246  			cfg[parts[0]] = parts[1]
   247  		}
   248  	}
   249  
   250  	return cfg
   251  }