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 }