Skip to content

Commit c283c2c

Browse files
nolanmar511aalexand
authored andcommitted
add -diff flag for better profile comparision (#369)
1 parent 4d67f66 commit c283c2c

File tree

9 files changed

+726
-59
lines changed

9 files changed

+726
-59
lines changed

doc/README.md

+38-6
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,44 @@ search for them in a directory pointed to by the environment variable
216216
* **-weblist= _regex_:** Generates a source/assembly combined annotated listing for
217217
functions matching *regex*, and starts a web browser to display it.
218218

219+
## Comparing profiles
220+
221+
pprof can subtract one profile from another, provided the profiles are of
222+
compatible types (i.e. two heap profiles). pprof has two options which can be
223+
used to specify the filename or URL for a profile to be subtracted from the
224+
source profile:
225+
226+
* **-diff_base= _profile_:** useful for comparing two profiles. Percentages in
227+
the output are relative to the total of samples in the diff base profile.
228+
229+
* **-base= _profile_:** useful for subtracting a cumulative profile, like a
230+
[golang block profile](https://golang.org/doc/diagnostics.html#profiling),
231+
from another cumulative profile collected from the same program at a later time.
232+
When comparing cumulative profiles collected on the same program, percentages in
233+
the output are relative to the difference between the total for the source
234+
profile and the total for the base profile.
235+
236+
The **-normalize** flag can be used when a base profile is specified with either
237+
the `-diff_base` or the `-base` option. This flag scales the source profile so
238+
that the total of samples in the source profile is equal to the total of samples
239+
in the base profile prior to subtracting the base profile from the source
240+
profile. Useful for determining the relative differences between profiles, for
241+
example, which profile has a larger percentage of CPU time used in a particular
242+
function.
243+
244+
When using the **-diff_base** option, some report entries may have negative
245+
values. If the merged profile is output as a protocol buffer, all samples in the
246+
diff base profile will have a label with the key "pprof::base" and a value of
247+
"true". If pprof is then used to look at the merged profile, it will behave as
248+
if separate source and base profiles were passed in.
249+
250+
When using the **-base** option to subtract one cumulative profile from another
251+
collected on the same program at a later time, percentages will be relative to
252+
the difference between the total for the source profile and the total for
253+
the base profile, and all values will be positive. In the general case, some
254+
report entries may have negative values and percentages will be relative to the
255+
total of the absolute value of all samples when aggregated at the address level.
256+
219257
# Fetching profiles
220258

221259
pprof can read profiles from a file or directly from a URL over http. Its native
@@ -237,11 +275,6 @@ them. This is useful to combine profiles from multiple processes of a
237275
distributed job. The profiles may be from different programs but must be
238276
compatible (for example, CPU profiles cannot be combined with heap profiles).
239277

240-
pprof can subtract a profile from another in order to compare them. For that,
241-
use the **-base= _profile_** option, where *profile* is the filename or URL for the
242-
profile to be subtracted. This may result on some report entries having negative
243-
values.
244-
245278
## Symbolization
246279

247280
pprof can add symbol information to a profile that was collected only with
@@ -286,4 +319,3 @@ the symbolization handler.
286319

287320
* **-symbolize=demangle=templates:** Demangle, and trim function parameters, but
288321
not template parameters.
289-

internal/driver/cli.go

+39-10
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package driver
1616

1717
import (
18+
"errors"
1819
"fmt"
1920
"os"
2021
"strings"
@@ -28,6 +29,7 @@ type source struct {
2829
ExecName string
2930
BuildID string
3031
Base []string
32+
DiffBase bool
3133
Normalize bool
3234

3335
Seconds int
@@ -43,7 +45,8 @@ type source struct {
4345
func parseFlags(o *plugin.Options) (*source, []string, error) {
4446
flag := o.Flagset
4547
// Comparisons.
46-
flagBase := flag.StringList("base", "", "Source for base profile for comparison")
48+
flagBase := flag.StringList("base", "", "Source for base profile for profile subtraction")
49+
flagDiffBase := flag.StringList("diff_base", "", "Source for diff base profile for comparison")
4750
// Source options.
4851
flagSymbolize := flag.String("symbolize", "", "Options for profile symbolization")
4952
flagBuildID := flag.String("buildid", "", "Override build id for first mapping")
@@ -85,7 +88,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
8588
usageMsgVars)
8689
})
8790
if len(args) == 0 {
88-
return nil, nil, fmt.Errorf("no profile source specified")
91+
return nil, nil, errors.New("no profile source specified")
8992
}
9093

9194
var execName string
@@ -112,7 +115,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
112115
return nil, nil, err
113116
}
114117
if cmd != nil && *flagHTTP != "" {
115-
return nil, nil, fmt.Errorf("-http is not compatible with an output format on the command line")
118+
return nil, nil, errors.New("-http is not compatible with an output format on the command line")
116119
}
117120

118121
si := pprofVariables["sample_index"].value
@@ -140,15 +143,13 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
140143
Comment: *flagAddComment,
141144
}
142145

143-
for _, s := range *flagBase {
144-
if *s != "" {
145-
source.Base = append(source.Base, *s)
146-
}
146+
if err := source.addBaseProfiles(*flagBase, *flagDiffBase); err != nil {
147+
return nil, nil, err
147148
}
148149

149150
normalize := pprofVariables["normalize"].boolValue()
150151
if normalize && len(source.Base) == 0 {
151-
return nil, nil, fmt.Errorf("Must have base profile to normalize by")
152+
return nil, nil, errors.New("must have base profile to normalize by")
152153
}
153154
source.Normalize = normalize
154155

@@ -158,6 +159,34 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
158159
return source, cmd, nil
159160
}
160161

162+
// addBaseProfiles adds the list of base profiles or diff base profiles to
163+
// the source. This function will return an error if both base and diff base
164+
// profiles are specified.
165+
func (source *source) addBaseProfiles(flagBase, flagDiffBase []*string) error {
166+
base, diffBase := dropEmpty(flagBase), dropEmpty(flagDiffBase)
167+
if len(base) > 0 && len(diffBase) > 0 {
168+
return errors.New("-base and -diff_base flags cannot both be specified")
169+
}
170+
171+
source.Base = base
172+
if len(diffBase) > 0 {
173+
source.Base, source.DiffBase = diffBase, true
174+
}
175+
return nil
176+
}
177+
178+
// dropEmpty list takes a slice of string pointers, and outputs a slice of
179+
// non-empty strings associated with the flag.
180+
func dropEmpty(list []*string) []string {
181+
var l []string
182+
for _, s := range list {
183+
if *s != "" {
184+
l = append(l, *s)
185+
}
186+
}
187+
return l
188+
}
189+
161190
// installFlags creates command line flags for pprof variables.
162191
func installFlags(flag plugin.FlagSet) flagsInstalled {
163192
f := flagsInstalled{
@@ -240,15 +269,15 @@ func outputFormat(bcmd map[string]*bool, acmd map[string]*string) (cmd []string,
240269
for n, b := range bcmd {
241270
if *b {
242271
if cmd != nil {
243-
return nil, fmt.Errorf("must set at most one output format")
272+
return nil, errors.New("must set at most one output format")
244273
}
245274
cmd = []string{n}
246275
}
247276
}
248277
for n, s := range acmd {
249278
if *s != "" {
250279
if cmd != nil {
251-
return nil, fmt.Errorf("must set at most one output format")
280+
return nil, errors.New("must set at most one output format")
252281
}
253282
cmd = []string{n, *s}
254283
}

internal/driver/driver_test.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ type testFlags struct {
288288
floats map[string]float64
289289
strings map[string]string
290290
args []string
291-
stringLists map[string][]*string
291+
stringLists map[string][]string
292292
}
293293

294294
func (testFlags) ExtraUsage() string { return "" }
@@ -355,7 +355,12 @@ func (f testFlags) StringVar(p *string, s, d, c string) {
355355

356356
func (f testFlags) StringList(s, d, c string) *[]*string {
357357
if t, ok := f.stringLists[s]; ok {
358-
return &t
358+
// convert slice of strings to slice of string pointers before returning.
359+
tp := make([]*string, len(t))
360+
for i, v := range t {
361+
tp[i] = &v
362+
}
363+
return &tp
359364
}
360365
return &[]*string{}
361366
}

internal/driver/fetch.go

+3
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
6363
}
6464

6565
if pbase != nil {
66+
if s.DiffBase {
67+
pbase.SetLabel("pprof::base", []string{"true"})
68+
}
6669
if s.Normalize {
6770
err := p.Normalize(pbase)
6871
if err != nil {

0 commit comments

Comments
 (0)