Skip to content

Commit 0569dc6

Browse files
committed
adds order to config fields, adds a sample config generator program, fixes #66
1 parent 2a3c810 commit 0569dc6

File tree

6 files changed

+144
-34
lines changed

6 files changed

+144
-34
lines changed

pkg/cli/cli.go

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (tf *timeFlag) Set(value string) error {
7373
}
7474

7575
// this is mostly reflection magic
76-
func populateFlagSet(obj interface{}, flagSet *flag.FlagSet) {
76+
func PopulateFlagSet(obj interface{}, flagSet *flag.FlagSet) *SortedFlags {
7777
v := reflect.ValueOf(obj).Elem()
7878
t := reflect.TypeOf(v.Interface())
7979
num := t.NumField()
@@ -142,6 +142,7 @@ func populateFlagSet(obj interface{}, flagSet *flag.FlagSet) {
142142
log.Fatalf("type %s is not supported", field.Type)
143143
}
144144
}
145+
return NewSortedFlags(obj, flagSet)
145146
}
146147

147148
// on mac pyroscope is usually installed via homebrew. homebrew installs under a prefix
@@ -173,26 +174,22 @@ func resolvePath(path string) string {
173174
return path
174175
}
175176

176-
func printUsage(c *ffcli.Command) string {
177-
return gradientBanner() + "\n" + DefaultUsageFunc(c)
178-
}
179-
180177
func Start(cfg *config.Config) error {
181178
var (
182-
rootFlagSet = flag.NewFlagSet("pyroscope", flag.ExitOnError)
183179
agentFlagSet = flag.NewFlagSet("pyroscope agent", flag.ExitOnError)
184180
serverFlagSet = flag.NewFlagSet("pyroscope server", flag.ExitOnError)
185181
convertFlagSet = flag.NewFlagSet("pyroscope convert", flag.ExitOnError)
186182
execFlagSet = flag.NewFlagSet("pyroscope exec", flag.ExitOnError)
187183
dbmanagerFlagSet = flag.NewFlagSet("pyroscope dbmanager", flag.ExitOnError)
184+
rootFlagSet = flag.NewFlagSet("pyroscope", flag.ExitOnError)
188185
)
189186

190-
populateFlagSet(cfg, rootFlagSet)
191-
populateFlagSet(&cfg.Agent, agentFlagSet)
192-
populateFlagSet(&cfg.Server, serverFlagSet)
193-
populateFlagSet(&cfg.Convert, convertFlagSet)
194-
populateFlagSet(&cfg.Exec, execFlagSet)
195-
populateFlagSet(&cfg.DbManager, dbmanagerFlagSet)
187+
agentSortedFlags := PopulateFlagSet(&cfg.Agent, agentFlagSet)
188+
serverSortedFlags := PopulateFlagSet(&cfg.Server, serverFlagSet)
189+
convertSortedFlags := PopulateFlagSet(&cfg.Convert, convertFlagSet)
190+
execSortedFlags := PopulateFlagSet(&cfg.Exec, execFlagSet)
191+
dbmanagerSortedFlags := PopulateFlagSet(&cfg.DbManager, dbmanagerFlagSet)
192+
rootSortedFlags := PopulateFlagSet(cfg, rootFlagSet)
196193

197194
options := []ff.Option{
198195
ff.WithConfigFileParser(ffyaml.Parser),
@@ -202,7 +199,7 @@ func Start(cfg *config.Config) error {
202199
}
203200

204201
agentCmd := &ffcli.Command{
205-
UsageFunc: printUsage,
202+
UsageFunc: agentSortedFlags.printUsage,
206203
Options: options,
207204
Name: "agent",
208205
ShortUsage: "pyroscope agent [flags]",
@@ -211,7 +208,7 @@ func Start(cfg *config.Config) error {
211208
}
212209

213210
serverCmd := &ffcli.Command{
214-
UsageFunc: printUsage,
211+
UsageFunc: serverSortedFlags.printUsage,
215212
Options: options,
216213
Name: "server",
217214
ShortUsage: "pyroscope server [flags]",
@@ -220,7 +217,7 @@ func Start(cfg *config.Config) error {
220217
}
221218

222219
convertCmd := &ffcli.Command{
223-
UsageFunc: printUsage,
220+
UsageFunc: convertSortedFlags.printUsage,
224221
Options: options,
225222
Name: "convert",
226223
ShortUsage: "pyroscope convert [flags] <input-file>",
@@ -229,7 +226,7 @@ func Start(cfg *config.Config) error {
229226
}
230227

231228
execCmd := &ffcli.Command{
232-
UsageFunc: printUsage,
229+
UsageFunc: execSortedFlags.printUsage,
233230
Options: options,
234231
Name: "exec",
235232
ShortUsage: "pyroscope exec [flags] <args>",
@@ -238,7 +235,7 @@ func Start(cfg *config.Config) error {
238235
}
239236

240237
dbmanagerCmd := &ffcli.Command{
241-
UsageFunc: printUsage,
238+
UsageFunc: dbmanagerSortedFlags.printUsage,
242239
Options: options,
243240
Name: "dbmanager",
244241
ShortUsage: "pyroscope dbmanager [flags] <args>",
@@ -247,7 +244,7 @@ func Start(cfg *config.Config) error {
247244
}
248245

249246
rootCmd := &ffcli.Command{
250-
UsageFunc: printUsage,
247+
UsageFunc: rootSortedFlags.printUsage,
251248
Options: options,
252249
ShortUsage: "pyroscope [flags] <subcommand>",
253250
FlagSet: rootFlagSet,
@@ -287,7 +284,7 @@ func Start(cfg *config.Config) error {
287284
}
288285
if len(args) == 0 || args[0] == "help" {
289286
fmt.Println(gradientBanner())
290-
fmt.Println(DefaultUsageFunc(execCmd))
287+
fmt.Println(DefaultUsageFunc(execSortedFlags, execCmd))
291288
return nil
292289
}
293290

@@ -306,7 +303,7 @@ func Start(cfg *config.Config) error {
306303
fmt.Println("")
307304
} else {
308305
fmt.Println(gradientBanner())
309-
fmt.Println(DefaultUsageFunc(rootCmd))
306+
fmt.Println(DefaultUsageFunc(rootSortedFlags, rootCmd))
310307
}
311308
return nil
312309
}

pkg/cli/cli_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ type FlagsStruct struct {
2222
}
2323

2424
var _ = Describe("config package", func() {
25-
Context("populateFlagSet", func() {
25+
Context("PopulateFlagSet", func() {
2626
Context("without config file", func() {
2727
It("correctly sets all types of arguments", func() {
2828
exampleFlagSet := flag.NewFlagSet("example flag set", flag.ExitOnError)
2929
cfg := FlagsStruct{}
30-
populateFlagSet(&cfg, exampleFlagSet)
30+
PopulateFlagSet(&cfg, exampleFlagSet)
3131

3232
exampleCommand := &ffcli.Command{
3333
FlagSet: exampleFlagSet,
@@ -58,7 +58,7 @@ var _ = Describe("config package", func() {
5858
It("correctly sets all types of arguments", func() {
5959
exampleFlagSet := flag.NewFlagSet("example flag set", flag.ExitOnError)
6060
cfg := FlagsStruct{}
61-
populateFlagSet(&cfg, exampleFlagSet)
61+
PopulateFlagSet(&cfg, exampleFlagSet)
6262

6363
exampleCommand := &ffcli.Command{
6464
FlagSet: exampleFlagSet,
@@ -86,7 +86,7 @@ var _ = Describe("config package", func() {
8686
It("arguments take precendence", func() {
8787
exampleFlagSet := flag.NewFlagSet("example flag set", flag.ExitOnError)
8888
cfg := FlagsStruct{}
89-
populateFlagSet(&cfg, exampleFlagSet)
89+
PopulateFlagSet(&cfg, exampleFlagSet)
9090

9191
exampleCommand := &ffcli.Command{
9292
FlagSet: exampleFlagSet,

pkg/cli/sortedflags.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package cli
2+
3+
import (
4+
"flag"
5+
"reflect"
6+
"sort"
7+
8+
"github.com/iancoleman/strcase"
9+
"github.com/peterbourgon/ff/v3/ffcli"
10+
)
11+
12+
// SortedFlags is needed because by default there's no way to provide order for flags
13+
// this is kind of an ugly workaround
14+
type SortedFlags struct {
15+
orderMap map[string]int
16+
flags []*flag.Flag
17+
}
18+
19+
func (sf *SortedFlags) Len() int {
20+
return len(sf.flags)
21+
}
22+
23+
func (sf *SortedFlags) Swap(i, j int) {
24+
sf.flags[i], sf.flags[j] = sf.flags[j], sf.flags[i]
25+
}
26+
27+
func (sf *SortedFlags) Less(i, j int) bool {
28+
return sf.orderMap[sf.flags[i].Name] < sf.orderMap[sf.flags[j].Name]
29+
}
30+
31+
func (sf *SortedFlags) VisitAll(cb func(*flag.Flag)) {
32+
for _, v := range sf.flags {
33+
cb(v)
34+
}
35+
}
36+
37+
func (sf *SortedFlags) printUsage(c *ffcli.Command) string {
38+
return gradientBanner() + "\n" + DefaultUsageFunc(sf, c)
39+
}
40+
41+
func NewSortedFlags(obj interface{}, fs *flag.FlagSet) *SortedFlags {
42+
v := reflect.ValueOf(obj).Elem()
43+
t := reflect.TypeOf(v.Interface())
44+
num := t.NumField()
45+
46+
res := SortedFlags{
47+
orderMap: make(map[string]int),
48+
flags: []*flag.Flag{},
49+
}
50+
51+
for i := 0; i < num; i++ {
52+
field := t.Field(i)
53+
nameVal := field.Tag.Get("name")
54+
if nameVal == "" {
55+
nameVal = strcase.ToKebab(field.Name)
56+
}
57+
// orderVal := field.Tag.Get("order")
58+
// order, _ := strconv.Atoi(orderVal)
59+
order := i
60+
res.orderMap[nameVal] = order
61+
}
62+
fs.VisitAll(func(f *flag.Flag) {
63+
res.flags = append(res.flags, f)
64+
})
65+
66+
sort.Sort(&res)
67+
return &res
68+
}

pkg/cli/usage.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ var hiddenCommands = []string{
3232
}
3333

3434
// This is mostly copied from ffcli package
35-
func DefaultUsageFunc(c *ffcli.Command) string {
35+
func DefaultUsageFunc(sf *SortedFlags, c *ffcli.Command) string {
3636
var b strings.Builder
3737

3838
fmt.Fprintf(&b, "continuous profiling platform\n\n")
@@ -67,7 +67,7 @@ func DefaultUsageFunc(c *ffcli.Command) string {
6767

6868
// TODO: it would be nice to sort by how often people would use these.
6969
// But for that we'd have to have a conversion from flag-set back to struct
70-
c.FlagSet.VisitAll(func(f *flag.Flag) {
70+
sf.VisitAll(func(f *flag.Flag) {
7171
def := f.DefValue
7272
// if def == "" {
7373
// def = "..."

pkg/config/config.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type Config struct {
1616

1717
type Agent struct {
1818
Config string `def:"<installPrefix>/etc/pyroscope/agent.yml" desc:"location of config file"`
19-
LogLevel string `def:"info", desc:"debug|info|warn|error"`
19+
LogLevel string `def:"info", desc:"log level: debug|info|warn|error"`
2020

2121
// AgentCMD []string
2222
AgentSpyName string `desc:"name of the spy you want to use"` // TODO: add options
@@ -29,9 +29,11 @@ type Agent struct {
2929
}
3030

3131
type Server struct {
32+
AnalyticsOptOut bool `def:"false" desc:"disables analytics"`
33+
3234
Config string `def:"<installPrefix>/etc/pyroscope/server.yml" desc:"location of config file"`
33-
LogLevel string `def:"info", desc:"debug|info|warn|error"`
34-
BadgerLogLevel string `def:"error", desc:"debug|info|warn|error"`
35+
LogLevel string `def:"info", desc:"log level: debug|info|warn|error"`
36+
BadgerLogLevel string `def:"error", desc:"log level: debug|info|warn|error"`
3537

3638
StoragePath string `def:"<installPrefix>/var/lib/pyroscope" desc:"directory where pyroscope stores profiling data"`
3739
ApiBindAddr string `def:":4040" desc:"port for the HTTP server used for data ingestion and web UI"`
@@ -55,17 +57,15 @@ type Server struct {
5557
MaxNodesRender int `def:"2048" desc:"max number of nodes used to display data on the frontend"`
5658

5759
// currently only used in our demo app
58-
HideApplications []string `def:""`
59-
60-
AnalyticsOptOut bool `def:"false" desc:"disables analytics"`
60+
HideApplications []string `def:"" desc:"please don't use, this will soon be deprecated"`
6161
}
6262

6363
type Convert struct {
6464
Format string `def:"tree"`
6565
}
6666

6767
type DbManager struct {
68-
LogLevel string `def:"error", desc:"debug|info|warn|error"`
68+
LogLevel string `def:"error", desc:"log level: debug|info|warn|error"`
6969
StoragePath string `def:"<installPrefix>/var/lib/pyroscope" desc:"directory where pyroscope stores profiling data"`
7070
DstStartTime time.Time
7171
DstEndTime time.Time
@@ -79,7 +79,7 @@ type Exec struct {
7979
SpyName string `def:"auto" desc:"name of the profiler you want to use. Supported ones are: <supportedProfilers>"`
8080
ApplicationName string `def:"" desc:"application name used when uploading profiling data"`
8181
DetectSubprocesses bool `def:"true" desc:"makes pyroscope keep track of and profile subprocesses of the main process"`
82-
LogLevel string `def:"info", desc:"debug|info|warn|error"`
82+
LogLevel string `def:"info", desc:"log level: debug|info|warn|error"`
8383
ServerAddress string `def:"http://localhost:4040" desc:"address of the pyroscope server"`
8484
AuthToken string `def:"" desc:"authorization token used to upload profiling data"`
8585
UpstreamThreads int `def:"4" desc:"number of upload threads"`

scripts/generate-sample-config.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
7+
"github.com/pyroscope-io/pyroscope/pkg/cli"
8+
"github.com/pyroscope-io/pyroscope/pkg/config"
9+
"github.com/sirupsen/logrus"
10+
)
11+
12+
// to run this program:
13+
// go run scripts/genrate-sample-config.go -format yaml
14+
// go run scripts/genrate-sample-config.go -format md
15+
16+
func main() {
17+
var format string
18+
flag.StringVar(&format, "format", "yaml", "yaml or md")
19+
flag.Parse()
20+
21+
serverFlagSet := flag.NewFlagSet("pyroscope server", flag.ExitOnError)
22+
cfg := config.Config{Server: config.Server{}}
23+
cli.PopulateFlagSet(&cfg.Server, serverFlagSet)
24+
25+
sf := cli.NewSortedFlags(&cfg.Server, serverFlagSet)
26+
27+
if format == "yaml" {
28+
fmt.Println("---")
29+
sf.VisitAll(func(f *flag.Flag) {
30+
if f.Name != "config" {
31+
fmt.Printf("# %s\n%s: %q\n\n", f.Usage, f.Name, f.DefValue)
32+
}
33+
})
34+
} else if format == "md" {
35+
fmt.Printf("| %s | %s | %s |\n", "Name", "Default Value", "Usage")
36+
fmt.Printf("| %s | %s | %s |\n", ":-", ":-", ":-")
37+
sf.VisitAll(func(f *flag.Flag) {
38+
if f.Name != "config" {
39+
fmt.Printf("| %s | %s | %q |\n", f.Name, f.DefValue, f.Usage)
40+
}
41+
})
42+
} else {
43+
logrus.Fatalf("Unknown format %q", format)
44+
}
45+
}

0 commit comments

Comments
 (0)