| 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 | } |