master
go 166 lines 4.94 KB
Raw
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 }