package commands import ( "archive/zip" "fmt" "io" "os" "strings" "time" cmds "github.com/ipfs/go-ipfs-cmds" "github.com/ipfs/kubo/core/commands/e" "github.com/ipfs/kubo/profile" ) // time format that works in filenames on windows. var timeFormat = strings.ReplaceAll(time.RFC3339, ":", "_") type profileResult struct { File string } const ( collectorsOptionName = "collectors" profileTimeOption = "profile-time" mutexProfileFractionOption = "mutex-profile-fraction" blockProfileRateOption = "block-profile-rate" ) var sysProfileCmd = &cmds.Command{ Helptext: cmds.HelpText{ Tagline: "Collect a performance profile for debugging.", ShortDescription: ` Collects profiles from a running go-ipfs daemon into a single zip file. To aid in debugging, this command also attempts to include a copy of the running go-ipfs binary. `, LongDescription: ` Collects profiles from a running go-ipfs daemon into a single zipfile. To aid in debugging, this command also attempts to include a copy of the running go-ipfs binary. Profiles can be examined using 'go tool pprof', some tips can be found at https://github.com/ipfs/kubo/blob/master/docs/debug-guide.md. Privacy Notice: The output file includes: - A list of running goroutines. - A CPU profile. - A heap inuse profile. - A heap allocation profile. - A mutex profile. - A block profile. - Your copy of go-ipfs. - The output of 'ipfs version --all'. It does not include: - Any of your IPFS data or metadata. - Your config or private key. - Your IP address. - The contents of your computer's memory, filesystem, etc. However, it could reveal: - Your build path, if you built go-ipfs yourself. - If and how a command/feature is being used (inferred from running functions). - Memory offsets of various data structures. - Any modifications you've made to go-ipfs. `, }, NoLocal: true, Options: []cmds.Option{ cmds.StringOption(outputOptionName, "o", "The path where the output .zip should be stored. Default: ./ipfs-profile-[timestamp].zip"), cmds.DelimitedStringsOption(",", collectorsOptionName, "The list of collectors to use for collecting diagnostic data."). WithDefault([]string{ profile.CollectorGoroutinesStack, profile.CollectorGoroutinesPprof, profile.CollectorVersion, profile.CollectorHeap, profile.CollectorAllocs, profile.CollectorBin, profile.CollectorCPU, profile.CollectorMutex, profile.CollectorBlock, }), cmds.StringOption(profileTimeOption, "The amount of time spent profiling. If this is set to 0, then sampling profiles are skipped.").WithDefault("30s"), cmds.IntOption(mutexProfileFractionOption, "The fraction 1/n of mutex contention events that are reported in the mutex profile.").WithDefault(4), cmds.StringOption(blockProfileRateOption, "The duration to wait between sampling goroutine-blocking events for the blocking profile.").WithDefault("1ms"), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { collectors := req.Options[collectorsOptionName].([]string) profileTimeStr, _ := req.Options[profileTimeOption].(string) profileTime, err := time.ParseDuration(profileTimeStr) if err != nil { return fmt.Errorf("failed to parse profile duration %q: %w", profileTimeStr, err) } blockProfileRateStr, _ := req.Options[blockProfileRateOption].(string) blockProfileRate, err := time.ParseDuration(blockProfileRateStr) if err != nil { return fmt.Errorf("failed to parse block profile rate %q: %w", blockProfileRateStr, err) } mutexProfileFraction, _ := req.Options[mutexProfileFractionOption].(int) r, w := io.Pipe() go func() { archive := zip.NewWriter(w) err = profile.WriteProfiles(req.Context, archive, profile.Options{ Collectors: collectors, ProfileDuration: profileTime, MutexProfileFraction: mutexProfileFraction, BlockProfileRate: blockProfileRate, }) archive.Close() _ = w.CloseWithError(err) }() return res.Emit(r) }, PostRun: cmds.PostRunMap{ cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error { v, err := res.Next() if err != nil { return err } outReader, ok := v.(io.Reader) if !ok { return e.New(e.TypeErr(outReader, v)) } outPath, _ := res.Request().Options[outputOptionName].(string) if outPath == "" { outPath = "ipfs-profile-" + time.Now().Format(timeFormat) + ".zip" } fi, err := os.Create(outPath) if err != nil { return err } defer fi.Close() _, err = io.Copy(fi, outReader) if err != nil { return err } return re.Emit(&profileResult{File: outPath}) }, }, Encoders: cmds.EncoderMap{ cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *profileResult) error { fmt.Fprintf(w, "Wrote profiles to: %s\n", out.File) return nil }), }, }