Source file src/cmd/vendor/github.com/google/pprof/internal/driver/settings.go

     1  package driver
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/url"
     7  	"os"
     8  	"path/filepath"
     9  )
    10  
    11  // settings holds pprof settings.
    12  type settings struct {
    13  	// Configs holds a list of named UI configurations.
    14  	Configs []namedConfig `json:"configs"`
    15  }
    16  
    17  // namedConfig associates a name with a config.
    18  type namedConfig struct {
    19  	Name string `json:"name"`
    20  	config
    21  }
    22  
    23  // settingsFileName returns the name of the file where settings should be saved.
    24  func settingsFileName() (string, error) {
    25  	// Return "pprof/settings.json" under os.UserConfigDir().
    26  	dir, err := os.UserConfigDir()
    27  	if err != nil {
    28  		return "", err
    29  	}
    30  	return filepath.Join(dir, "pprof", "settings.json"), nil
    31  }
    32  
    33  // readSettings reads settings from fname.
    34  func readSettings(fname string) (*settings, error) {
    35  	data, err := os.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  
    52  // writeSettings saves settings to fname.
    53  func writeSettings(fname string, settings *settings) error {
    54  	data, err := json.MarshalIndent(settings, "", "  ")
    55  	if err != nil {
    56  		return fmt.Errorf("could not encode settings: %w", err)
    57  	}
    58  
    59  	// create the settings directory if it does not exist
    60  	// XDG specifies permissions 0700 when creating settings dirs:
    61  	// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
    62  	if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
    63  		return fmt.Errorf("failed to create settings directory: %w", err)
    64  	}
    65  
    66  	if err := os.WriteFile(fname, data, 0644); err != nil {
    67  		return fmt.Errorf("failed to write settings: %w", err)
    68  	}
    69  	return nil
    70  }
    71  
    72  // configMenuEntry holds information for a single config menu entry.
    73  type configMenuEntry struct {
    74  	Name       string
    75  	URL        string
    76  	Current    bool // Is this the currently selected config?
    77  	UserConfig bool // Is this a user-provided config?
    78  }
    79  
    80  // configMenu returns a list of items to add to a menu in the web UI.
    81  func configMenu(fname string, u url.URL) []configMenuEntry {
    82  	// Start with system configs.
    83  	configs := []namedConfig{{Name: "Default", config: defaultConfig()}}
    84  	if settings, err := readSettings(fname); err == nil {
    85  		// Add user configs.
    86  		configs = append(configs, settings.Configs...)
    87  	}
    88  
    89  	// Convert to menu entries.
    90  	result := make([]configMenuEntry, len(configs))
    91  	lastMatch := -1
    92  	for i, cfg := range configs {
    93  		dst, changed := cfg.config.makeURL(u)
    94  		if !changed {
    95  			lastMatch = i
    96  		}
    97  		// Use a relative URL to work in presence of stripping/redirects in webui.go.
    98  		rel := &url.URL{RawQuery: dst.RawQuery, ForceQuery: true}
    99  		result[i] = configMenuEntry{
   100  			Name:       cfg.Name,
   101  			URL:        rel.String(),
   102  			UserConfig: (i != 0),
   103  		}
   104  	}
   105  	// Mark the last matching config as currennt
   106  	if lastMatch >= 0 {
   107  		result[lastMatch].Current = true
   108  	}
   109  	return result
   110  }
   111  
   112  // editSettings edits settings by applying fn to them.
   113  func editSettings(fname string, fn func(s *settings) error) error {
   114  	settings, err := readSettings(fname)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	if err := fn(settings); err != nil {
   119  		return err
   120  	}
   121  	return writeSettings(fname, settings)
   122  }
   123  
   124  // setConfig saves the config specified in request to fname.
   125  func setConfig(fname string, request url.URL) error {
   126  	q := request.Query()
   127  	name := q.Get("config")
   128  	if name == "" {
   129  		return fmt.Errorf("invalid config name")
   130  	}
   131  	cfg := currentConfig()
   132  	if err := cfg.applyURL(q); err != nil {
   133  		return err
   134  	}
   135  	return editSettings(fname, func(s *settings) error {
   136  		for i, c := range s.Configs {
   137  			if c.Name == name {
   138  				s.Configs[i].config = cfg
   139  				return nil
   140  			}
   141  		}
   142  		s.Configs = append(s.Configs, namedConfig{Name: name, config: cfg})
   143  		return nil
   144  	})
   145  }
   146  
   147  // removeConfig removes config from fname.
   148  func removeConfig(fname, config string) error {
   149  	return editSettings(fname, func(s *settings) error {
   150  		for i, c := range s.Configs {
   151  			if c.Name == config {
   152  				s.Configs = append(s.Configs[:i], s.Configs[i+1:]...)
   153  				return nil
   154  			}
   155  		}
   156  		return fmt.Errorf("config %s not found", config)
   157  	})
   158  }
   159  

View as plain text