No Description

settings.go 3.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. package driver
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net/url"
  7. "os"
  8. "path/filepath"
  9. )
  10. // settings holds pprof settings.
  11. type settings struct {
  12. // Configs holds a list of named UI configurations.
  13. Configs []namedConfig `json:"configs"`
  14. }
  15. // namedConfig associates a name with a config.
  16. type namedConfig struct {
  17. Name string `json:"name"`
  18. config
  19. }
  20. // settingsFileName returns the name of the file where settings should be saved.
  21. func settingsFileName() (string, error) {
  22. // Return "pprof/settings.json" under os.UserConfigDir().
  23. dir, err := os.UserConfigDir()
  24. if err != nil {
  25. return "", err
  26. }
  27. dir = filepath.Join(dir, "pprof")
  28. if err := os.MkdirAll(dir, 0755); err != nil {
  29. return "", err
  30. }
  31. return filepath.Join(dir, "settings.json"), nil
  32. }
  33. // readSettings reads settings from fname.
  34. func readSettings(fname string) (*settings, error) {
  35. data, err := ioutil.ReadFile(fname)
  36. if err != nil {
  37. if os.IsNotExist(err) {
  38. return &settings{}, nil
  39. }
  40. return nil, fmt.Errorf("could not read settings: %w", err)
  41. }
  42. settings := &settings{}
  43. if err := json.Unmarshal(data, settings); err != nil {
  44. return nil, fmt.Errorf("could not parse settings: %w", err)
  45. }
  46. for i := range settings.Configs {
  47. settings.Configs[i].resetTransient()
  48. }
  49. return settings, nil
  50. }
  51. // writeSettings saves settings to fname.
  52. func writeSettings(fname string, settings *settings) error {
  53. data, err := json.MarshalIndent(settings, "", " ")
  54. if err != nil {
  55. return fmt.Errorf("could not encode settings: %w", err)
  56. }
  57. if err := ioutil.WriteFile(fname, data, 0644); err != nil {
  58. return fmt.Errorf("failed to write settings: %w", err)
  59. }
  60. return nil
  61. }
  62. // configMenuEntry holds information for a single config menu entry.
  63. type configMenuEntry struct {
  64. Name string
  65. URL string
  66. Current bool // Is this the currently selected config?
  67. UserConfig bool // Is this a user-provided config?
  68. }
  69. // configMenu returns a list of items to add to a menu in the web UI.
  70. func configMenu(fname string, url url.URL) []configMenuEntry {
  71. // Start with system configs.
  72. configs := []namedConfig{{Name: "Default", config: defaultConfig()}}
  73. if settings, err := readSettings(fname); err == nil {
  74. // Add user configs.
  75. configs = append(configs, settings.Configs...)
  76. }
  77. // Convert to menu entries.
  78. result := make([]configMenuEntry, len(configs))
  79. lastMatch := -1
  80. for i, cfg := range configs {
  81. dst, changed := cfg.config.makeURL(url)
  82. if !changed {
  83. lastMatch = i
  84. }
  85. result[i] = configMenuEntry{
  86. Name: cfg.Name,
  87. URL: dst.String(),
  88. UserConfig: (i != 0),
  89. }
  90. }
  91. // Mark the last matching config as currennt
  92. if lastMatch >= 0 {
  93. result[lastMatch].Current = true
  94. }
  95. return result
  96. }
  97. // editSettings edits settings by applying fn to them.
  98. func editSettings(fname string, fn func(s *settings) error) error {
  99. settings, err := readSettings(fname)
  100. if err != nil {
  101. return err
  102. }
  103. if err := fn(settings); err != nil {
  104. return err
  105. }
  106. return writeSettings(fname, settings)
  107. }
  108. // setConfig saves the config specified in request to fname.
  109. func setConfig(fname string, request url.URL) error {
  110. q := request.Query()
  111. name := q.Get("config")
  112. if name == "" {
  113. return fmt.Errorf("invalid config name")
  114. }
  115. cfg := currentConfig()
  116. if err := cfg.applyURL(q); err != nil {
  117. return err
  118. }
  119. return editSettings(fname, func(s *settings) error {
  120. for i, c := range s.Configs {
  121. if c.Name == name {
  122. s.Configs[i].config = cfg
  123. return nil
  124. }
  125. }
  126. s.Configs = append(s.Configs, namedConfig{Name: name, config: cfg})
  127. return nil
  128. })
  129. }
  130. // removeConfig removes config from fname.
  131. func removeConfig(fname, config string) error {
  132. return editSettings(fname, func(s *settings) error {
  133. for i, c := range s.Configs {
  134. if c.Name == config {
  135. s.Configs = append(s.Configs[:i], s.Configs[i+1:]...)
  136. return nil
  137. }
  138. }
  139. return fmt.Errorf("config %s not found", config)
  140. })
  141. }