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 }