/ core / commands / profile.go
profile.go
  1  package commands
  2  
  3  import (
  4  	"archive/zip"
  5  	"fmt"
  6  	"io"
  7  	"os"
  8  	"strings"
  9  	"time"
 10  
 11  	cmds "github.com/ipfs/go-ipfs-cmds"
 12  	"github.com/ipfs/kubo/core/commands/e"
 13  	"github.com/ipfs/kubo/profile"
 14  )
 15  
 16  // time format that works in filenames on windows.
 17  var timeFormat = strings.ReplaceAll(time.RFC3339, ":", "_")
 18  
 19  type profileResult struct {
 20  	File string
 21  }
 22  
 23  const (
 24  	collectorsOptionName       = "collectors"
 25  	profileTimeOption          = "profile-time"
 26  	mutexProfileFractionOption = "mutex-profile-fraction"
 27  	blockProfileRateOption     = "block-profile-rate"
 28  )
 29  
 30  var sysProfileCmd = &cmds.Command{
 31  	Helptext: cmds.HelpText{
 32  		Tagline: "Collect a performance profile for debugging.",
 33  		ShortDescription: `
 34  Collects profiles from a running go-ipfs daemon into a single zip file.
 35  To aid in debugging, this command also attempts to include a copy of
 36  the running go-ipfs binary.
 37  `,
 38  		LongDescription: `
 39  Collects profiles from a running go-ipfs daemon into a single zipfile.
 40  To aid in debugging, this command also attempts to include a copy of
 41  the running go-ipfs binary.
 42  
 43  Profiles can be examined using 'go tool pprof', some tips can be found at
 44  https://github.com/ipfs/kubo/blob/master/docs/debug-guide.md.
 45  
 46  Privacy Notice:
 47  
 48  The output file includes:
 49  
 50  - A list of running goroutines.
 51  - A CPU profile.
 52  - A heap inuse profile.
 53  - A heap allocation profile.
 54  - A mutex profile.
 55  - A block profile.
 56  - Your copy of go-ipfs.
 57  - The output of 'ipfs version --all'.
 58  
 59  It does not include:
 60  
 61  - Any of your IPFS data or metadata.
 62  - Your config or private key.
 63  - Your IP address.
 64  - The contents of your computer's memory, filesystem, etc.
 65  
 66  However, it could reveal:
 67  
 68  - Your build path, if you built go-ipfs yourself.
 69  - If and how a command/feature is being used (inferred from running functions).
 70  - Memory offsets of various data structures.
 71  - Any modifications you've made to go-ipfs.
 72  `,
 73  		HTTP: &cmds.HTTPHelpText{
 74  			ResponseContentType: "application/zip",
 75  		},
 76  	},
 77  	NoLocal: true,
 78  	Options: []cmds.Option{
 79  		cmds.StringOption(outputOptionName, "o", "The path where the output .zip should be stored. Default: ./ipfs-profile-[timestamp].zip"),
 80  		cmds.DelimitedStringsOption(",", collectorsOptionName, "The list of collectors to use for collecting diagnostic data.").
 81  			WithDefault([]string{
 82  				profile.CollectorGoroutinesStack,
 83  				profile.CollectorGoroutinesPprof,
 84  				profile.CollectorVersion,
 85  				profile.CollectorHeap,
 86  				profile.CollectorAllocs,
 87  				profile.CollectorBin,
 88  				profile.CollectorCPU,
 89  				profile.CollectorMutex,
 90  				profile.CollectorBlock,
 91  				profile.CollectorTrace,
 92  			}),
 93  		cmds.StringOption(profileTimeOption, "The amount of time spent profiling. If this is set to 0, then sampling profiles are skipped.").WithDefault("30s"),
 94  		cmds.IntOption(mutexProfileFractionOption, "The fraction 1/n of mutex contention events that are reported in the mutex profile.").WithDefault(4),
 95  		cmds.StringOption(blockProfileRateOption, "The duration to wait between sampling goroutine-blocking events for the blocking profile.").WithDefault("1ms"),
 96  	},
 97  	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
 98  		collectors := req.Options[collectorsOptionName].([]string)
 99  
100  		profileTimeStr, _ := req.Options[profileTimeOption].(string)
101  		profileTime, err := time.ParseDuration(profileTimeStr)
102  		if err != nil {
103  			return fmt.Errorf("failed to parse profile duration %q: %w", profileTimeStr, err)
104  		}
105  
106  		blockProfileRateStr, _ := req.Options[blockProfileRateOption].(string)
107  		blockProfileRate, err := time.ParseDuration(blockProfileRateStr)
108  		if err != nil {
109  			return fmt.Errorf("failed to parse block profile rate %q: %w", blockProfileRateStr, err)
110  		}
111  
112  		mutexProfileFraction, _ := req.Options[mutexProfileFractionOption].(int)
113  
114  		r, w := io.Pipe()
115  
116  		go func() {
117  			archive := zip.NewWriter(w)
118  			err = profile.WriteProfiles(req.Context, archive, profile.Options{
119  				Collectors:           collectors,
120  				ProfileDuration:      profileTime,
121  				MutexProfileFraction: mutexProfileFraction,
122  				BlockProfileRate:     blockProfileRate,
123  			})
124  			archive.Close()
125  			_ = w.CloseWithError(err)
126  		}()
127  		res.SetEncodingType(cmds.OctetStream)
128  		res.SetContentType("application/zip")
129  		return res.Emit(r)
130  	},
131  	PostRun: cmds.PostRunMap{
132  		cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error {
133  			v, err := res.Next()
134  			if err != nil {
135  				return err
136  			}
137  
138  			outReader, ok := v.(io.Reader)
139  			if !ok {
140  				return e.New(e.TypeErr(outReader, v))
141  			}
142  
143  			outPath, _ := res.Request().Options[outputOptionName].(string)
144  			if outPath == "" {
145  				outPath = "ipfs-profile-" + time.Now().Format(timeFormat) + ".zip"
146  			}
147  			fi, err := os.Create(outPath)
148  			if err != nil {
149  				return err
150  			}
151  			defer fi.Close()
152  
153  			_, err = io.Copy(fi, outReader)
154  			if err != nil {
155  				return err
156  			}
157  			return re.Emit(&profileResult{File: outPath})
158  		},
159  	},
160  	Encoders: cmds.EncoderMap{
161  		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *profileResult) error {
162  			fmt.Fprintf(w, "Wrote profiles to: %s\n", out.File)
163  			return nil
164  		}),
165  	},
166  }