aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/vendor/github.com/google/pprof/internal/driver/settings.go
blob: f72314b1857ddbcb09dd8e81675288f3a575e3e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package driver

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
)

// settings holds pprof settings.
type settings struct {
	// Configs holds a list of named UI configurations.
	Configs []namedConfig `json:"configs"`
}

// namedConfig associates a name with a config.
type namedConfig struct {
	Name string `json:"name"`
	config
}

// settingsFileName returns the name of the file where settings should be saved.
func settingsFileName() (string, error) {
	// Return "pprof/settings.json" under os.UserConfigDir().
	dir, err := os.UserConfigDir()
	if err != nil {
		return "", err
	}
	return filepath.Join(dir, "pprof", "settings.json"), nil
}

// readSettings reads settings from fname.
func readSettings(fname string) (*settings, error) {
	data, err := ioutil.ReadFile(fname)
	if err != nil {
		if os.IsNotExist(err) {
			return &settings{}, nil
		}
		return nil, fmt.Errorf("could not read settings: %w", err)
	}
	settings := &settings{}
	if err := json.Unmarshal(data, settings); err != nil {
		return nil, fmt.Errorf("could not parse settings: %w", err)
	}
	for i := range settings.Configs {
		settings.Configs[i].resetTransient()
	}
	return settings, nil
}

// writeSettings saves settings to fname.
func writeSettings(fname string, settings *settings) error {
	data, err := json.MarshalIndent(settings, "", "  ")
	if err != nil {
		return fmt.Errorf("could not encode settings: %w", err)
	}

	// create the settings directory if it does not exist
	// XDG specifies permissions 0700 when creating settings dirs:
	// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
	if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
		return fmt.Errorf("failed to create settings directory: %w", err)
	}

	if err := ioutil.WriteFile(fname, data, 0644); err != nil {
		return fmt.Errorf("failed to write settings: %w", err)
	}
	return nil
}

// configMenuEntry holds information for a single config menu entry.
type configMenuEntry struct {
	Name       string
	URL        string
	Current    bool // Is this the currently selected config?
	UserConfig bool // Is this a user-provided config?
}

// configMenu returns a list of items to add to a menu in the web UI.
func configMenu(fname string, url url.URL) []configMenuEntry {
	// Start with system configs.
	configs := []namedConfig{{Name: "Default", config: defaultConfig()}}
	if settings, err := readSettings(fname); err == nil {
		// Add user configs.
		configs = append(configs, settings.Configs...)
	}

	// Convert to menu entries.
	result := make([]configMenuEntry, len(configs))
	lastMatch := -1
	for i, cfg := range configs {
		dst, changed := cfg.config.makeURL(url)
		if !changed {
			lastMatch = i
		}
		result[i] = configMenuEntry{
			Name:       cfg.Name,
			URL:        dst.String(),
			UserConfig: (i != 0),
		}
	}
	// Mark the last matching config as currennt
	if lastMatch >= 0 {
		result[lastMatch].Current = true
	}
	return result
}

// editSettings edits settings by applying fn to them.
func editSettings(fname string, fn func(s *settings) error) error {
	settings, err := readSettings(fname)
	if err != nil {
		return err
	}
	if err := fn(settings); err != nil {
		return err
	}
	return writeSettings(fname, settings)
}

// setConfig saves the config specified in request to fname.
func setConfig(fname string, request url.URL) error {
	q := request.Query()
	name := q.Get("config")
	if name == "" {
		return fmt.Errorf("invalid config name")
	}
	cfg := currentConfig()
	if err := cfg.applyURL(q); err != nil {
		return err
	}
	return editSettings(fname, func(s *settings) error {
		for i, c := range s.Configs {
			if c.Name == name {
				s.Configs[i].config = cfg
				return nil
			}
		}
		s.Configs = append(s.Configs, namedConfig{Name: name, config: cfg})
		return nil
	})
}

// removeConfig removes config from fname.
func removeConfig(fname, config string) error {
	return editSettings(fname, func(s *settings) error {
		for i, c := range s.Configs {
			if c.Name == config {
				s.Configs = append(s.Configs[:i], s.Configs[i+1:]...)
				return nil
			}
		}
		return fmt.Errorf("config %s not found", config)
	})
}