golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/driver/commands.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package driver
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"os/exec"
    23  	"runtime"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/google/pprof/internal/plugin"
    30  	"github.com/google/pprof/internal/report"
    31  	"github.com/google/pprof/third_party/svg"
    32  )
    33  
    34  // commands describes the commands accepted by pprof.
    35  type commands map[string]*command
    36  
    37  // command describes the actions for a pprof command. Includes a
    38  // function for command-line completion, the report format to use
    39  // during report generation, any postprocessing functions, and whether
    40  // the command expects a regexp parameter (typically a function name).
    41  type command struct {
    42  	format      int           // report format to generate
    43  	postProcess PostProcessor // postprocessing to run on report
    44  	visualizer  PostProcessor // display output using some callback
    45  	hasParam    bool          // collect a parameter from the CLI
    46  	description string        // single-line description text saying what the command does
    47  	usage       string        // multi-line help text saying how the command is used
    48  }
    49  
    50  // help returns a help string for a command.
    51  func (c *command) help(name string) string {
    52  	message := c.description + "\n"
    53  	if c.usage != "" {
    54  		message += "  Usage:\n"
    55  		lines := strings.Split(c.usage, "\n")
    56  		for _, line := range lines {
    57  			message += fmt.Sprintf("    %s\n", line)
    58  		}
    59  	}
    60  	return message + "\n"
    61  }
    62  
    63  // AddCommand adds an additional command to the set of commands
    64  // accepted by pprof. This enables extensions to add new commands for
    65  // specialized visualization formats. If the command specified already
    66  // exists, it is overwritten.
    67  func AddCommand(cmd string, format int, post PostProcessor, desc, usage string) {
    68  	pprofCommands[cmd] = &command{format, post, nil, false, desc, usage}
    69  }
    70  
    71  // SetVariableDefault sets the default value for a pprof
    72  // variable. This enables extensions to set their own defaults.
    73  func SetVariableDefault(variable, value string) {
    74  	if v := pprofVariables[variable]; v != nil {
    75  		v.value = value
    76  	}
    77  }
    78  
    79  // PostProcessor is a function that applies post-processing to the report output
    80  type PostProcessor func(input io.Reader, output io.Writer, ui plugin.UI) error
    81  
    82  // interactiveMode is true if pprof is running on interactive mode, reading
    83  // commands from its shell.
    84  var interactiveMode = false
    85  
    86  // pprofCommands are the report generation commands recognized by pprof.
    87  var pprofCommands = commands{
    88  	// Commands that require no post-processing.
    89  	"comments": {report.Comments, nil, nil, false, "Output all profile comments", ""},
    90  	"disasm":   {report.Dis, nil, nil, true, "Output assembly listings annotated with samples", listHelp("disasm", true)},
    91  	"dot":      {report.Dot, nil, nil, false, "Outputs a graph in DOT format", reportHelp("dot", false, true)},
    92  	"list":     {report.List, nil, nil, true, "Output annotated source for functions matching regexp", listHelp("list", false)},
    93  	"peek":     {report.Tree, nil, nil, true, "Output callers/callees of functions matching regexp", "peek func_regex\nDisplay callers and callees of functions matching func_regex."},
    94  	"raw":      {report.Raw, nil, nil, false, "Outputs a text representation of the raw profile", ""},
    95  	"tags":     {report.Tags, nil, nil, false, "Outputs all tags in the profile", "tags [tag_regex]* [-ignore_regex]* [>file]\nList tags with key:value matching tag_regex and exclude ignore_regex."},
    96  	"text":     {report.Text, nil, nil, false, "Outputs top entries in text form", reportHelp("text", true, true)},
    97  	"top":      {report.Text, nil, nil, false, "Outputs top entries in text form", reportHelp("top", true, true)},
    98  	"traces":   {report.Traces, nil, nil, false, "Outputs all profile samples in text form", ""},
    99  	"tree":     {report.Tree, nil, nil, false, "Outputs a text rendering of call graph", reportHelp("tree", true, true)},
   100  
   101  	// Save binary formats to a file
   102  	"callgrind": {report.Callgrind, nil, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format", reportHelp("callgrind", false, true)},
   103  	"proto":     {report.Proto, nil, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format", ""},
   104  	"topproto":  {report.TopProto, nil, awayFromTTY("pb.gz"), false, "Outputs top entries in compressed protobuf format", ""},
   105  
   106  	// Generate report in DOT format and postprocess with dot
   107  	"gif": {report.Dot, invokeDot("gif"), awayFromTTY("gif"), false, "Outputs a graph image in GIF format", reportHelp("gif", false, true)},
   108  	"pdf": {report.Dot, invokeDot("pdf"), awayFromTTY("pdf"), false, "Outputs a graph in PDF format", reportHelp("pdf", false, true)},
   109  	"png": {report.Dot, invokeDot("png"), awayFromTTY("png"), false, "Outputs a graph image in PNG format", reportHelp("png", false, true)},
   110  	"ps":  {report.Dot, invokeDot("ps"), awayFromTTY("ps"), false, "Outputs a graph in PS format", reportHelp("ps", false, true)},
   111  
   112  	// Save SVG output into a file
   113  	"svg": {report.Dot, massageDotSVG(), awayFromTTY("svg"), false, "Outputs a graph in SVG format", reportHelp("svg", false, true)},
   114  
   115  	// Visualize postprocessed dot output
   116  	"eog":    {report.Dot, invokeDot("svg"), invokeVisualizer("svg", []string{"eog"}), false, "Visualize graph through eog", reportHelp("eog", false, false)},
   117  	"evince": {report.Dot, invokeDot("pdf"), invokeVisualizer("pdf", []string{"evince"}), false, "Visualize graph through evince", reportHelp("evince", false, false)},
   118  	"gv":     {report.Dot, invokeDot("ps"), invokeVisualizer("ps", []string{"gv --noantialias"}), false, "Visualize graph through gv", reportHelp("gv", false, false)},
   119  	"web":    {report.Dot, massageDotSVG(), invokeVisualizer("svg", browsers()), false, "Visualize graph through web browser", reportHelp("web", false, false)},
   120  
   121  	// Visualize callgrind output
   122  	"kcachegrind": {report.Callgrind, nil, invokeVisualizer("grind", kcachegrind), false, "Visualize report in KCachegrind", reportHelp("kcachegrind", false, false)},
   123  
   124  	// Visualize HTML directly generated by report.
   125  	"weblist": {report.WebList, nil, invokeVisualizer("html", browsers()), true, "Display annotated source in a web browser", listHelp("weblist", false)},
   126  }
   127  
   128  // pprofVariables are the configuration parameters that affect the
   129  // reported generated by pprof.
   130  var pprofVariables = variables{
   131  	// Filename for file-based output formats, stdout by default.
   132  	"output": &variable{stringKind, "", "", helpText("Output filename for file-based outputs")},
   133  
   134  	// Comparisons.
   135  	"drop_negative": &variable{boolKind, "f", "", helpText(
   136  		"Ignore negative differences",
   137  		"Do not show any locations with values <0.")},
   138  
   139  	// Comparisons.
   140  	"positive_percentages": &variable{boolKind, "f", "", helpText(
   141  		"Ignore negative samples when computing percentages",
   142  		" Do not count negative samples when computing the total value",
   143  		" of the profile, used to compute percentages. If set, and the -base",
   144  		" option is used, percentages reported will be computed against the",
   145  		" main profile, ignoring the base profile.")},
   146  
   147  	// Graph handling options.
   148  	"call_tree": &variable{boolKind, "f", "", helpText(
   149  		"Create a context-sensitive call tree",
   150  		"Treat locations reached through different paths as separate.")},
   151  
   152  	// Display options.
   153  	"relative_percentages": &variable{boolKind, "f", "", helpText(
   154  		"Show percentages relative to focused subgraph",
   155  		"If unset, percentages are relative to full graph before focusing",
   156  		"to facilitate comparison with original graph.")},
   157  	"unit": &variable{stringKind, "minimum", "", helpText(
   158  		"Measurement units to display",
   159  		"Scale the sample values to this unit.",
   160  		" For time-based profiles, use seconds, milliseconds, nanoseconds, etc.",
   161  		" For memory profiles, use megabytes, kilobytes, bytes, etc.",
   162  		" auto will scale each value independently to the most natural unit.")},
   163  	"compact_labels": &variable{boolKind, "f", "", "Show minimal headers"},
   164  	"source_path":    &variable{stringKind, "", "", "Search path for source files"},
   165  
   166  	// Filtering options
   167  	"nodecount": &variable{intKind, "-1", "", helpText(
   168  		"Max number of nodes to show",
   169  		"Uses heuristics to limit the number of locations to be displayed.",
   170  		"On graphs, dotted edges represent paths through nodes that have been removed.")},
   171  	"nodefraction": &variable{floatKind, "0.005", "", "Hide nodes below <f>*total"},
   172  	"edgefraction": &variable{floatKind, "0.001", "", "Hide edges below <f>*total"},
   173  	"trim": &variable{boolKind, "t", "", helpText(
   174  		"Honor nodefraction/edgefraction/nodecount defaults",
   175  		"Set to false to get the full profile, without any trimming.")},
   176  	"focus": &variable{stringKind, "", "", helpText(
   177  		"Restricts to samples going through a node matching regexp",
   178  		"Discard samples that do not include a node matching this regexp.",
   179  		"Matching includes the function name, filename or object name.")},
   180  	"ignore": &variable{stringKind, "", "", helpText(
   181  		"Skips paths going through any nodes matching regexp",
   182  		"If set, discard samples that include a node matching this regexp.",
   183  		"Matching includes the function name, filename or object name.")},
   184  	"prune_from": &variable{stringKind, "", "", helpText(
   185  		"Drops any functions below the matched frame.",
   186  		"If set, any frames matching the specified regexp and any frames",
   187  		"below it will be dropped from each sample.")},
   188  	"hide": &variable{stringKind, "", "", helpText(
   189  		"Skips nodes matching regexp",
   190  		"Discard nodes that match this location.",
   191  		"Other nodes from samples that include this location will be shown.",
   192  		"Matching includes the function name, filename or object name.")},
   193  	"show": &variable{stringKind, "", "", helpText(
   194  		"Only show nodes matching regexp",
   195  		"If set, only show nodes that match this location.",
   196  		"Matching includes the function name, filename or object name.")},
   197  	"tagfocus": &variable{stringKind, "", "", helpText(
   198  		"Restrict to samples with tags in range or matched by regexp",
   199  		"Discard samples that do not include a node with a tag matching this regexp.")},
   200  	"tagignore": &variable{stringKind, "", "", helpText(
   201  		"Discard samples with tags in range or matched by regexp",
   202  		"Discard samples that do include a node with a tag matching this regexp.")},
   203  	"tagshow": &variable{stringKind, "", "", helpText(
   204  		"Only consider tags matching this regexp",
   205  		"Discard tags that do not match this regexp")},
   206  	"taghide": &variable{stringKind, "", "", helpText(
   207  		"Skip tags matching this regexp",
   208  		"Discard tags that match this regexp")},
   209  	// Heap profile options
   210  	"divide_by": &variable{floatKind, "1", "", helpText(
   211  		"Ratio to divide all samples before visualization",
   212  		"Divide all samples values by a constant, eg the number of processors or jobs.")},
   213  	"mean": &variable{boolKind, "f", "", helpText(
   214  		"Average sample value over first value (count)",
   215  		"For memory profiles, report average memory per allocation.",
   216  		"For time-based profiles, report average time per event.")},
   217  	"sample_index": &variable{stringKind, "", "", helpText(
   218  		"Sample value to report (0-based index or name)",
   219  		"Profiles contain multiple values per sample.",
   220  		"Use sample_index=i to select the ith value (starting at 0).")},
   221  
   222  	// Data sorting criteria
   223  	"flat": &variable{boolKind, "t", "cumulative", helpText("Sort entries based on own weight")},
   224  	"cum":  &variable{boolKind, "f", "cumulative", helpText("Sort entries based on cumulative weight")},
   225  
   226  	// Output granularity
   227  	"functions": &variable{boolKind, "t", "granularity", helpText(
   228  		"Aggregate at the function level.",
   229  		"Takes into account the filename/lineno where the function was defined.")},
   230  	"functionnameonly": &variable{boolKind, "f", "granularity", helpText(
   231  		"Aggregate at the function level.",
   232  		"Ignores the filename/lineno where the function was defined.")},
   233  	"files": &variable{boolKind, "f", "granularity", "Aggregate at the file level."},
   234  	"lines": &variable{boolKind, "f", "granularity", "Aggregate at the source code line level."},
   235  	"addresses": &variable{boolKind, "f", "granularity", helpText(
   236  		"Aggregate at the function level.",
   237  		"Includes functions' addresses in the output.")},
   238  	"noinlines": &variable{boolKind, "f", "granularity", helpText(
   239  		"Aggregate at the function level.",
   240  		"Attributes inlined functions to their first out-of-line caller.")},
   241  	"addressnoinlines": &variable{boolKind, "f", "granularity", helpText(
   242  		"Aggregate at the function level, including functions' addresses in the output.",
   243  		"Attributes inlined functions to their first out-of-line caller.")},
   244  }
   245  
   246  func helpText(s ...string) string {
   247  	return strings.Join(s, "\n") + "\n"
   248  }
   249  
   250  // usage returns a string describing the pprof commands and variables.
   251  // if commandLine is set, the output reflect cli usage.
   252  func usage(commandLine bool) string {
   253  	var prefix string
   254  	if commandLine {
   255  		prefix = "-"
   256  	}
   257  	fmtHelp := func(c, d string) string {
   258  		return fmt.Sprintf("    %-16s %s", c, strings.SplitN(d, "\n", 2)[0])
   259  	}
   260  
   261  	var commands []string
   262  	for name, cmd := range pprofCommands {
   263  		commands = append(commands, fmtHelp(prefix+name, cmd.description))
   264  	}
   265  	sort.Strings(commands)
   266  
   267  	var help string
   268  	if commandLine {
   269  		help = "  Output formats (select only one):\n"
   270  	} else {
   271  		help = "  Commands:\n"
   272  		commands = append(commands, fmtHelp("o/options", "List options and their current values"))
   273  		commands = append(commands, fmtHelp("quit/exit/^D", "Exit pprof"))
   274  	}
   275  
   276  	help = help + strings.Join(commands, "\n") + "\n\n" +
   277  		"  Options:\n"
   278  
   279  	// Print help for variables after sorting them.
   280  	// Collect radio variables by their group name to print them together.
   281  	radioOptions := make(map[string][]string)
   282  	var variables []string
   283  	for name, vr := range pprofVariables {
   284  		if vr.group != "" {
   285  			radioOptions[vr.group] = append(radioOptions[vr.group], name)
   286  			continue
   287  		}
   288  		variables = append(variables, fmtHelp(prefix+name, vr.help))
   289  	}
   290  	sort.Strings(variables)
   291  
   292  	help = help + strings.Join(variables, "\n") + "\n\n" +
   293  		"  Option groups (only set one per group):\n"
   294  
   295  	var radioStrings []string
   296  	for radio, ops := range radioOptions {
   297  		sort.Strings(ops)
   298  		s := []string{fmtHelp(radio, "")}
   299  		for _, op := range ops {
   300  			s = append(s, "  "+fmtHelp(prefix+op, pprofVariables[op].help))
   301  		}
   302  
   303  		radioStrings = append(radioStrings, strings.Join(s, "\n"))
   304  	}
   305  	sort.Strings(radioStrings)
   306  	return help + strings.Join(radioStrings, "\n")
   307  }
   308  
   309  func reportHelp(c string, cum, redirect bool) string {
   310  	h := []string{
   311  		c + " [n] [focus_regex]* [-ignore_regex]*",
   312  		"Include up to n samples",
   313  		"Include samples matching focus_regex, and exclude ignore_regex.",
   314  	}
   315  	if cum {
   316  		h[0] += " [-cum]"
   317  		h = append(h, "-cum sorts the output by cumulative weight")
   318  	}
   319  	if redirect {
   320  		h[0] += " >f"
   321  		h = append(h, "Optionally save the report on the file f")
   322  	}
   323  	return strings.Join(h, "\n")
   324  }
   325  
   326  func listHelp(c string, redirect bool) string {
   327  	h := []string{
   328  		c + "<func_regex|address> [-focus_regex]* [-ignore_regex]*",
   329  		"Include functions matching func_regex, or including the address specified.",
   330  		"Include samples matching focus_regex, and exclude ignore_regex.",
   331  	}
   332  	if redirect {
   333  		h[0] += " >f"
   334  		h = append(h, "Optionally save the report on the file f")
   335  	}
   336  	return strings.Join(h, "\n")
   337  }
   338  
   339  // browsers returns a list of commands to attempt for web visualization.
   340  func browsers() []string {
   341  	cmds := []string{"chrome", "google-chrome", "firefox"}
   342  	switch runtime.GOOS {
   343  	case "darwin":
   344  		return append(cmds, "/usr/bin/open")
   345  	case "windows":
   346  		return append(cmds, "cmd /c start")
   347  	default:
   348  		userBrowser := os.Getenv("BROWSER")
   349  		if userBrowser != "" {
   350  			cmds = append([]string{userBrowser, "sensible-browser"}, cmds...)
   351  		} else {
   352  			cmds = append([]string{"sensible-browser"}, cmds...)
   353  		}
   354  		return append(cmds, "xdg-open")
   355  	}
   356  }
   357  
   358  var kcachegrind = []string{"kcachegrind"}
   359  
   360  // awayFromTTY saves the output in a file if it would otherwise go to
   361  // the terminal screen. This is used to avoid dumping binary data on
   362  // the screen.
   363  func awayFromTTY(format string) PostProcessor {
   364  	return func(input io.Reader, output io.Writer, ui plugin.UI) error {
   365  		if output == os.Stdout && (ui.IsTerminal() || interactiveMode) {
   366  			tempFile, err := newTempFile("", "profile", "."+format)
   367  			if err != nil {
   368  				return err
   369  			}
   370  			ui.PrintErr("Generating report in ", tempFile.Name())
   371  			output = tempFile
   372  		}
   373  		_, err := io.Copy(output, input)
   374  		return err
   375  	}
   376  }
   377  
   378  func invokeDot(format string) PostProcessor {
   379  	return func(input io.Reader, output io.Writer, ui plugin.UI) error {
   380  		cmd := exec.Command("dot", "-T"+format)
   381  		cmd.Stdin, cmd.Stdout, cmd.Stderr = input, output, os.Stderr
   382  		if err := cmd.Run(); err != nil {
   383  			return fmt.Errorf("Failed to execute dot. Is Graphviz installed? Error: %v", err)
   384  		}
   385  		return nil
   386  	}
   387  }
   388  
   389  // massageDotSVG invokes the dot tool to generate an SVG image and alters
   390  // the image to have panning capabilities when viewed in a browser.
   391  func massageDotSVG() PostProcessor {
   392  	generateSVG := invokeDot("svg")
   393  	return func(input io.Reader, output io.Writer, ui plugin.UI) error {
   394  		baseSVG := new(bytes.Buffer)
   395  		if err := generateSVG(input, baseSVG, ui); err != nil {
   396  			return err
   397  		}
   398  		_, err := output.Write([]byte(svg.Massage(baseSVG.String())))
   399  		return err
   400  	}
   401  }
   402  
   403  func invokeVisualizer(suffix string, visualizers []string) PostProcessor {
   404  	return func(input io.Reader, output io.Writer, ui plugin.UI) error {
   405  		tempFile, err := newTempFile(os.TempDir(), "pprof", "."+suffix)
   406  		if err != nil {
   407  			return err
   408  		}
   409  		deferDeleteTempFile(tempFile.Name())
   410  		if _, err := io.Copy(tempFile, input); err != nil {
   411  			return err
   412  		}
   413  		tempFile.Close()
   414  		// Try visualizers until one is successful
   415  		for _, v := range visualizers {
   416  			// Separate command and arguments for exec.Command.
   417  			args := strings.Split(v, " ")
   418  			if len(args) == 0 {
   419  				continue
   420  			}
   421  			viewer := exec.Command(args[0], append(args[1:], tempFile.Name())...)
   422  			viewer.Stderr = os.Stderr
   423  			if err = viewer.Start(); err == nil {
   424  				// Wait for a second so that the visualizer has a chance to
   425  				// open the input file. This needs to be done even if we're
   426  				// waiting for the visualizer as it can be just a wrapper that
   427  				// spawns a browser tab and returns right away.
   428  				defer func(t <-chan time.Time) {
   429  					<-t
   430  				}(time.After(time.Second))
   431  				// On interactive mode, let the visualizer run in the background
   432  				// so other commands can be issued.
   433  				if !interactiveMode {
   434  					return viewer.Wait()
   435  				}
   436  				return nil
   437  			}
   438  		}
   439  		return err
   440  	}
   441  }
   442  
   443  // variables describe the configuration parameters recognized by pprof.
   444  type variables map[string]*variable
   445  
   446  // variable is a single configuration parameter.
   447  type variable struct {
   448  	kind  int    // How to interpret the value, must be one of the enums below.
   449  	value string // Effective value. Only values appropriate for the Kind should be set.
   450  	group string // boolKind variables with the same Group != "" cannot be set simultaneously.
   451  	help  string // Text describing the variable, in multiple lines separated by newline.
   452  }
   453  
   454  const (
   455  	// variable.kind must be one of these variables.
   456  	boolKind = iota
   457  	intKind
   458  	floatKind
   459  	stringKind
   460  )
   461  
   462  // set updates the value of a variable, checking that the value is
   463  // suitable for the variable Kind.
   464  func (vars variables) set(name, value string) error {
   465  	v := vars[name]
   466  	if v == nil {
   467  		return fmt.Errorf("no variable %s", name)
   468  	}
   469  	var err error
   470  	switch v.kind {
   471  	case boolKind:
   472  		var b bool
   473  		if b, err = stringToBool(value); err == nil {
   474  			if v.group != "" && b == false {
   475  				err = fmt.Errorf("%q can only be set to true", name)
   476  			}
   477  		}
   478  	case intKind:
   479  		_, err = strconv.Atoi(value)
   480  	case floatKind:
   481  		_, err = strconv.ParseFloat(value, 64)
   482  	case stringKind:
   483  		// Remove quotes, particularly useful for empty values.
   484  		if len(value) > 1 && strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
   485  			value = value[1 : len(value)-1]
   486  		}
   487  	}
   488  	if err != nil {
   489  		return err
   490  	}
   491  	vars[name].value = value
   492  	if group := vars[name].group; group != "" {
   493  		for vname, vvar := range vars {
   494  			if vvar.group == group && vname != name {
   495  				vvar.value = "f"
   496  			}
   497  		}
   498  	}
   499  	return err
   500  }
   501  
   502  // boolValue returns the value of a boolean variable.
   503  func (v *variable) boolValue() bool {
   504  	b, err := stringToBool(v.value)
   505  	if err != nil {
   506  		panic("unexpected value " + v.value + " for bool ")
   507  	}
   508  	return b
   509  }
   510  
   511  // intValue returns the value of an intKind variable.
   512  func (v *variable) intValue() int {
   513  	i, err := strconv.Atoi(v.value)
   514  	if err != nil {
   515  		panic("unexpected value " + v.value + " for int ")
   516  	}
   517  	return i
   518  }
   519  
   520  // floatValue returns the value of a Float variable.
   521  func (v *variable) floatValue() float64 {
   522  	f, err := strconv.ParseFloat(v.value, 64)
   523  	if err != nil {
   524  		panic("unexpected value " + v.value + " for float ")
   525  	}
   526  	return f
   527  }
   528  
   529  // stringValue returns a canonical representation for a variable.
   530  func (v *variable) stringValue() string {
   531  	switch v.kind {
   532  	case boolKind:
   533  		return fmt.Sprint(v.boolValue())
   534  	case intKind:
   535  		return fmt.Sprint(v.intValue())
   536  	case floatKind:
   537  		return fmt.Sprint(v.floatValue())
   538  	}
   539  	return v.value
   540  }
   541  
   542  func stringToBool(s string) (bool, error) {
   543  	switch strings.ToLower(s) {
   544  	case "true", "t", "yes", "y", "1", "":
   545  		return true, nil
   546  	case "false", "f", "no", "n", "0":
   547  		return false, nil
   548  	default:
   549  		return false, fmt.Errorf(`illegal value "%s" for bool variable`, s)
   550  	}
   551  }
   552  
   553  // makeCopy returns a duplicate of a set of shell variables.
   554  func (vars variables) makeCopy() variables {
   555  	varscopy := make(variables, len(vars))
   556  	for n, v := range vars {
   557  		vcopy := *v
   558  		varscopy[n] = &vcopy
   559  	}
   560  	return varscopy
   561  }