Skip to content

Commit 5f418c2

Browse files
committed
add CLI flag to select specific diag collectors
1 parent defc2ca commit 5f418c2

File tree

3 files changed

+78
-30
lines changed

3 files changed

+78
-30
lines changed

core/commands/profile.go

+15
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type profileResult struct {
2121
}
2222

2323
const (
24+
collectorsOptionName = "collectors"
2425
profileTimeOption = "profile-time"
2526
mutexProfileFractionOption = "mutex-profile-fraction"
2627
blockProfileRateOption = "block-profile-rate"
@@ -72,11 +73,24 @@ However, it could reveal:
7273
NoLocal: true,
7374
Options: []cmds.Option{
7475
cmds.StringOption(outputOptionName, "o", "The path where the output .zip should be stored. Default: ./ipfs-profile-[timestamp].zip"),
76+
cmds.DelimitedStringsOption(",", collectorsOptionName, "The list of collectors to use for collecting diagnostic data.").
77+
WithDefault([]string{
78+
profile.CollectorGoroutinesStack,
79+
profile.CollectorGoroutinesPprof,
80+
profile.CollectorVersion,
81+
profile.CollectorHeap,
82+
profile.CollectorBin,
83+
profile.CollectorCPU,
84+
profile.CollectorMutex,
85+
profile.CollectorBlock,
86+
}),
7587
cmds.StringOption(profileTimeOption, "The amount of time spent profiling. If this is set to 0, then sampling profiles are skipped.").WithDefault("30s"),
7688
cmds.IntOption(mutexProfileFractionOption, "The fraction 1/n of mutex contention events that are reported in the mutex profile.").WithDefault(4),
7789
cmds.StringOption(blockProfileRateOption, "The duration to wait between sampling goroutine-blocking events for the blocking profile.").WithDefault("1ms"),
7890
},
7991
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
92+
collectors := req.Options[collectorsOptionName].([]string)
93+
8094
profileTimeStr, _ := req.Options[profileTimeOption].(string)
8195
profileTime, err := time.ParseDuration(profileTimeStr)
8296
if err != nil {
@@ -96,6 +110,7 @@ However, it could reveal:
96110
go func() {
97111
archive := zip.NewWriter(w)
98112
err = profile.WriteProfiles(req.Context, archive, profile.Options{
113+
Collectors: collectors,
99114
ProfileDuration: profileTime,
100115
MutexProfileFraction: mutexProfileFraction,
101116
BlockProfileRate: blockProfileRate,

profile/profile.go

+49-28
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,30 @@ import (
1717
"github.com/ipfs/go-log"
1818
)
1919

20+
const (
21+
CollectorGoroutinesStack = "goroutines-stack"
22+
CollectorGoroutinesPprof = "goroutines-pprof"
23+
CollectorVersion = "version"
24+
CollectorHeap = "heap"
25+
CollectorBin = "bin"
26+
CollectorCPU = "cpu"
27+
CollectorMutex = "mutex"
28+
CollectorBlock = "block"
29+
)
30+
2031
var (
2132
logger = log.Logger("profile")
2233
goos = runtime.GOOS
2334
)
2435

25-
type profile struct {
36+
type collector struct {
2637
outputFile string
2738
isExecutable bool
28-
profileFunc func(ctx context.Context, opts Options, writer io.Writer) error
39+
collectFunc func(ctx context.Context, opts Options, writer io.Writer) error
2940
enabledFunc func(opts Options) bool
3041
}
3142

32-
func (p *profile) fileName() string {
43+
func (p *collector) outputFileName() string {
3344
fName := p.outputFile
3445
if p.isExecutable {
3546
if goos == "windows" {
@@ -39,51 +50,52 @@ func (p *profile) fileName() string {
3950
return fName
4051
}
4152

42-
var profiles = []profile{
43-
{
53+
var collectors = map[string]collector{
54+
CollectorGoroutinesStack: {
4455
outputFile: "goroutines.stacks",
45-
profileFunc: goroutineStacksText,
56+
collectFunc: goroutineStacksText,
4657
enabledFunc: func(opts Options) bool { return true },
4758
},
48-
{
59+
CollectorGoroutinesPprof: {
4960
outputFile: "goroutines.pprof",
50-
profileFunc: goroutineStacksProto,
61+
collectFunc: goroutineStacksProto,
5162
enabledFunc: func(opts Options) bool { return true },
5263
},
53-
{
64+
CollectorVersion: {
5465
outputFile: "version.json",
55-
profileFunc: versionInfo,
66+
collectFunc: versionInfo,
5667
enabledFunc: func(opts Options) bool { return true },
5768
},
58-
{
69+
CollectorHeap: {
5970
outputFile: "heap.pprof",
60-
profileFunc: heapProfile,
71+
collectFunc: heapProfile,
6172
enabledFunc: func(opts Options) bool { return true },
6273
},
63-
{
74+
CollectorBin: {
6475
outputFile: "ipfs",
6576
isExecutable: true,
66-
profileFunc: binary,
77+
collectFunc: binary,
6778
enabledFunc: func(opts Options) bool { return true },
6879
},
69-
{
80+
CollectorCPU: {
7081
outputFile: "cpu.pprof",
71-
profileFunc: profileCPU,
82+
collectFunc: profileCPU,
7283
enabledFunc: func(opts Options) bool { return opts.ProfileDuration > 0 },
7384
},
74-
{
85+
CollectorMutex: {
7586
outputFile: "mutex.pprof",
76-
profileFunc: mutexProfile,
87+
collectFunc: mutexProfile,
7788
enabledFunc: func(opts Options) bool { return opts.ProfileDuration > 0 && opts.MutexProfileFraction > 0 },
7889
},
79-
{
90+
CollectorBlock: {
8091
outputFile: "block.pprof",
81-
profileFunc: blockProfile,
92+
collectFunc: blockProfile,
8293
enabledFunc: func(opts Options) bool { return opts.ProfileDuration > 0 && opts.BlockProfileRate > 0 },
8394
},
8495
}
8596

8697
type Options struct {
98+
Collectors []string
8799
ProfileDuration time.Duration
88100
MutexProfileFraction int
89101
BlockProfileRate time.Duration
@@ -97,7 +109,7 @@ func WriteProfiles(ctx context.Context, archive *zip.Writer, opts Options) error
97109
return p.runProfile(ctx)
98110
}
99111

100-
// profiler runs the profiles concurrently and writes the results to the zip archive.
112+
// profiler runs the collectors concurrently and writes the results to the zip archive.
101113
type profiler struct {
102114
archive *zip.Writer
103115
opts Options
@@ -113,22 +125,31 @@ func (p *profiler) runProfile(ctx context.Context) error {
113125
ctx, cancelFn := context.WithCancel(ctx)
114126
defer cancelFn()
115127

116-
results := make(chan profileResult, len(profiles))
128+
var collectorsToRun []collector
129+
for _, name := range p.opts.Collectors {
130+
c, ok := collectors[name]
131+
if !ok {
132+
return fmt.Errorf("unknown collector '%s'", name)
133+
}
134+
collectorsToRun = append(collectorsToRun, c)
135+
}
136+
137+
results := make(chan profileResult, len(p.opts.Collectors))
117138
wg := sync.WaitGroup{}
118-
for _, prof := range profiles {
119-
if !prof.enabledFunc(p.opts) {
139+
for _, c := range collectorsToRun {
140+
if !c.enabledFunc(p.opts) {
120141
continue
121142
}
122143

123-
fName := prof.fileName()
144+
fName := c.outputFileName()
124145

125146
wg.Add(1)
126-
go func(prof profile) {
147+
go func(c collector) {
127148
defer wg.Done()
128149
logger.Infow("collecting profile", "File", fName)
129150
defer logger.Infow("profile done", "File", fName)
130151
b := bytes.Buffer{}
131-
err := prof.profileFunc(ctx, p.opts, &b)
152+
err := c.collectFunc(ctx, p.opts, &b)
132153
if err != nil {
133154
select {
134155
case results <- profileResult{err: fmt.Errorf("generating profile data for %q: %w", fName, err)}:
@@ -140,7 +161,7 @@ func (p *profiler) runProfile(ctx context.Context) error {
140161
case results <- profileResult{buf: &b, fName: fName}:
141162
case <-ctx.Done():
142163
}
143-
}(prof)
164+
}(c)
144165
}
145166
go func() {
146167
wg.Wait()

test/sharness/t0152-profile.sh

+14-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ test_expect_success "profiling requires a running daemon" '
1616

1717
test_launch_ipfs_daemon
1818

19-
test_expect_success "test profiling (without samplin)" '
19+
test_expect_success "test profiling (without sampling)" '
2020
ipfs diag profile --profile-time=0 > cmd_out
2121
'
2222

@@ -35,14 +35,20 @@ test_expect_success "test profiling with -o" '
3535
test_expect_success "test that test-profile.zip exists" '
3636
test -e test-profile.zip
3737
'
38+
39+
test_expect_success "test profiling with specific collectors" '
40+
ipfs diag profile --collectors version,goroutines-stack -o test-profile-small.zip
41+
'
42+
3843
test_kill_ipfs_daemon
3944

4045
if ! test_have_prereq UNZIP; then
4146
test_done
4247
fi
4348

4449
test_expect_success "unpack profiles" '
45-
unzip -d profiles test-profile.zip
50+
unzip -d profiles test-profile.zip &&
51+
unzip -d profiles-small test-profile-small.zip
4652
'
4753

4854
test_expect_success "cpu profile is valid" '
@@ -69,4 +75,10 @@ test_expect_success "goroutines stacktrace is valid" '
6975
grep -q "goroutine" "profiles/goroutines.stacks"
7076
'
7177

78+
test_expect_success "the small profile only contains the requested data" '
79+
find profiles-small -type f > actual &&
80+
echo -e "profiles-small/goroutines.stacks\nprofiles-small/version.json" > expected &&
81+
test_cmp expected actual
82+
'
83+
7284
test_done

0 commit comments

Comments
 (0)