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

View as plain text