-
Notifications
You must be signed in to change notification settings - Fork 612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add -diff flag for better profile comparision #369
Conversation
daf6352
to
a7b32db
Compare
internal/driver/cli.go
Outdated
@@ -140,7 +142,15 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { | |||
Comment: *flagAddComment, | |||
} | |||
|
|||
for _, s := range *flagBase { | |||
if len(*flagBase) > 0 && len(*flagDiff) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compare strings for emptiness using x != ""
so that it's clear it's not a slice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Err, ignore, those are slices.
internal/driver/cli.go
Outdated
@@ -140,7 +142,15 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { | |||
Comment: *flagAddComment, | |||
} | |||
|
|||
for _, s := range *flagBase { | |||
if len(*flagBase) > 0 && len(*flagDiff) > 0 { | |||
return nil, nil, fmt.Errorf("only -base or -diff flag can be specified") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The message could be clearer - it now reads as if no other flags can be passed other than the two mentioned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adjusted the commented.
internal/report/report.go
Outdated
|
||
// Also remove pprof::diff=true string tag. | ||
if values, ok := s.Label["pprof::diff"]; ok { | ||
newVals := []string{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var newVals []string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code has been removed.
internal/report/report.go
Outdated
@@ -261,6 +261,21 @@ func (rpt *Report) newGraph(nodes graph.NodeSet) *graph.Graph { | |||
} | |||
s.NumLabel = numLabels | |||
s.NumUnit = numUnits | |||
|
|||
// Also remove pprof::diff=true string tag. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to clarify why. Also this code is not super clear. Perhaps make a separate function, and look into how to simplify it. I would also look into whether code could be simpler if the pprof::diff tag was removed in the aggregate() code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would removing the tag in aggregation() prevent it from being used in later to separate out source and base profiles?
For now, I'm separating this into a different function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the SetTag function to clear pprof::diff tag.
And adding comment to explain why this tag shouldn't be set.
internal/report/report_test.go
Outdated
@@ -285,3 +285,122 @@ func TestLegendActiveFilters(t *testing.T) { | |||
} | |||
} | |||
} | |||
|
|||
func TestComputTotal(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -1211,10 +1226,10 @@ func NewDefault(prof *profile.Profile, options Options) *Report { | |||
return New(prof, o) | |||
} | |||
|
|||
// computeTotal computes the sum of all sample values. This will be | |||
// used to compute percentages. | |||
// computeTotal computes the sum of the absolute value of all sample values. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doc should be updated when such non-trivial logic is added in the function code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
internal/report/report.go
Outdated
total += v | ||
if sample.HasTag("pprof::diff", "true") { | ||
diffTotal += v | ||
} | ||
div += d |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming that it's intentional that accumulating the div is not under the tag condition, can you add a comment on why so?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
profile/profile.go
Outdated
// profile. | ||
func (p *Profile) AddTag(key, value string) { | ||
for _, sample := range p.Sample { | ||
if sample.HasTag(key, value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling HasTag could be avoided and handled below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing AddTag to SetTag, so I think this no longer applies.
profile/profile.go
Outdated
@@ -674,6 +674,36 @@ func numLabelsToString(numLabels map[string][]int64, numUnits map[string][]strin | |||
return strings.Join(ls, " ") | |||
} | |||
|
|||
// AddTag adds a tag with the specified key and value to all samples in the | |||
// profile. | |||
func (p *Profile) AddTag(key, value string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd maybe do this as SetTag(), it would simplify the code and I don't think we really need to support multiple values per tag in this PR. Would simplify the tests as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
internal/report/report_test.go
Outdated
{ | ||
Location: []*profile.Location{testL[2], testL[1], testL[0]}, | ||
Value: []int64{-9000, 3}, | ||
Label: map[string][]string{"pprof::diff": {"true"}}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that "pprof::diff" and "true" are going to be stored as strings for each sample, I wonder if we should come up with shorter strings to save memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree -- maybe "1"?
542d764
to
c15f92c
Compare
Codecov Report
@@ Coverage Diff @@
## master #369 +/- ##
==========================================
+ Coverage 66.89% 67.08% +0.18%
==========================================
Files 36 36
Lines 7495 7532 +37
==========================================
+ Hits 5014 5053 +39
+ Misses 2079 2078 -1
+ Partials 402 401 -1
Continue to review full report at Codecov.
|
PTAL |
doc/pprof.md
Outdated
@@ -214,6 +214,10 @@ search for them in a directory pointed to by the environment variable | |||
* **-weblist= _regex_:** Generates a source/assembly combined annotated listing for | |||
functions matching *regex*, and starts a web browser to display it. | |||
|
|||
Samples in a profile may have tags. These tags have a name and a value; this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added paragraph sounds a bit out of tune in this "Annotated code" section. Why here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made an error when moving docs around. Fixed.
doc/pprof.md
Outdated
|
||
The **-base** option can be used in the place of the **-diff_base** option, and | ||
may be preferrable when comparing two cumulative profiles, for example two | ||
contention profiles collected from the same program at different times. When |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be more specific that we are talking about contention reports from Go programs. Linking to Go's documentation would be good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find much in go documentation on contention reports.
This is the main thing I found (from here):
// goroutine - stack traces of all current goroutines
// heap - a sampling of all heap allocations
// threadcreate - stack traces that led to the creation of new OS threads
// block - stack traces that led to blocking on synchronization primitives
// mutex - stack traces of holders of contended mutexes
I'm guessing that block profiles are what we would consider contention profiles?
I could look for blog posts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe link to https://golang.org/doc/diagnostics.html#profiling? It's not exclusively about contention but has something.
doc/pprof.md
Outdated
may be preferrable when comparing two cumulative profiles, for example two | ||
contention profiles collected from the same program at different times. When | ||
this flag is used and no negative values appear in the merged profile when | ||
aggregated at the address-level, percentages reported will be relative to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd somehow leave out the detail about address level or move it closer to the end of the section. Telling the whole story feels overwhelming a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried to change this.
Possibly fixed.
doc/pprof.md
Outdated
|
||
## Comparing profiles | ||
|
||
pprof can subtract a profile from another, provided the profiles are of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe start this section with a very high-level note that there are two ways to compare profiles, briefly characterize each of them, then talk about each in more detail.
doc/pprof.md
Outdated
compatible types, in order to compare them. For that, use the | ||
**-diff_base= _profile_** option, where *profile* is the filename or URL for the | ||
profile to be subtracted. This may result in some report entries having negative | ||
values. Percentages in the report are relative to the total of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: double space is a typo?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
internal/driver/cli.go
Outdated
@@ -140,12 +142,30 @@ func parseFlags(o *plugin.Options) (*source, []string, error) { | |||
Comment: *flagAddComment, | |||
} | |||
|
|||
var base []string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fragment of code of dealing with base and diffBase flags is a bit long now. Consider refactoring it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did some refactoring.
internal/report/report.go
Outdated
if sample.HasTag("pprof::base", "true") { | ||
diffTotal += v | ||
diffDiv += d | ||
} | ||
div += d |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I had this comment but it was not answered before: Assuming that it's intentional that accumulating the div is not under the tag condition, can you add a comment on why so?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
profile/profile.go
Outdated
func (p *Profile) SetTag(key string, value []string) { | ||
for _, sample := range p.Sample { | ||
if sample.Label == nil { | ||
sample.Label = make(map[string][]string) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sample.Label = map[string]string{}
is a bit cheaper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
profile/profile_test.go
Outdated
}{ | ||
{ | ||
desc: "empty label does not have tag", | ||
sample: &Sample{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could avoid repeating the Sample and its Location and Value in every test case. You only care about Label field, you could reuse the rest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
profile/profile_test.go
Outdated
wantTags []map[string][]string | ||
}{ | ||
{ | ||
desc: "some samples have tag already", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to add more test cases? E.g. what if sample doesn't have the tags.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Also did some refactoring of this test.
PTAL, except for at docs. |
Also addressed (tried to address) comments on docs. |
PTAL |
internal/driver/fetch_test.go
Outdated
}, | ||
} | ||
|
||
// Allow more test cases to run in parallel. | ||
runtime.GOMAXPROCS(len(testcases)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand why this is needed. You are not calling https://golang.org/pkg/testing/#T.Parallel here.
@nolanmar511 Please LMK when you remove the |
Removed the |
internal/driver/fetch_test.go
Outdated
base := make([]*string, len(tc.bases)) | ||
for i, s := range tc.bases { | ||
base[i] = &s | ||
} | ||
|
||
diffBase := make([]*string, len(tc.diffBases)) | ||
for i, s := range tc.diffBases { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: there is a similar loop above, maybe testFlags could be made having stringLists as slice of regular strings, not pointers and translate that to pointers when needed in its implementation. Very much optional suggestion - judge depending on how much noise in the test code this pointerization has.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
internal/driver/fetch_test.go
Outdated
if err == nil { | ||
t.Fatalf("got nil, want error %q", tc.wantErrorMsg) | ||
} | ||
gotErrMsg := err.Error() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge assignment into the if statement on next line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
internal/driver/fetch_test.go
Outdated
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if want, got := len(tc.expectedSamples), len(p.Sample); want != got { | ||
t.Fatalf("want %d samples got %d", want, got) | ||
if want, got := len(tc.wantSamples), len(p.Sample); want != got { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super-nit: I'd reverse want, got into got, want on the assignment and condition as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
internal/report/report.go
Outdated
@@ -264,6 +264,10 @@ func (rpt *Report) newGraph(nodes graph.NodeSet) *graph.Graph { | |||
s.NumUnit = numUnits | |||
} | |||
|
|||
// Remove tag marking samples from the base profiles, so it does not appear |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tag -> label
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not done?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry -- just pushed.
internal/report/report.go
Outdated
// computeTotal computes the sum of all sample values. This will be | ||
// used to compute percentages. | ||
// computeTotal computes the sum of the absolute value of all sample values. | ||
// If any samples have the tag "pprof::base" with value "true", then the total |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tag -> label
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
PTAL |
internal/driver/cli.go
Outdated
} | ||
|
||
normalize := pprofVariables["normalize"].boolValue() | ||
if normalize && len(source.Base) == 0 { | ||
return nil, nil, fmt.Errorf("Must have base profile to normalize by") | ||
return nil, nil, errors.New("Must have base profile to normalize by") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
internal/driver/cli.go
Outdated
return nil | ||
} | ||
|
||
// dropEmpty list takes a StringList flag, and outputs an array of non-empty |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not take a flag, it takes a slice of string pointers. It does not return an array, it returns a slice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
internal/driver/cli.go
Outdated
// profiles are specified. | ||
func (source *source) addBaseProfiles(flagBase, flagDiffBase []*string) error { | ||
base, diffBase := dropEmpty(flagBase), dropEmpty(flagDiffBase) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: remove empty line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
internal/driver/driver_test.go
Outdated
@@ -355,7 +355,11 @@ func (f testFlags) StringVar(p *string, s, d, c string) { | |||
|
|||
func (f testFlags) StringList(s, d, c string) *[]*string { | |||
if t, ok := f.stringLists[s]; ok { | |||
return &t | |||
tp := make([]*string, len(t)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change? It's not obvious, a comment would be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was to address a previous comment for simplifying testing.
I changed the type of the stringLists
field in the testFlags
structure from map[string][]*string
to map[string][]string
to make it easier to construct flags for tests.
Added a comment.
internal/report/report_test.go
Outdated
|
||
for _, tc := range testcases { | ||
t.Run(tc.desc, func(t *testing.T) { | ||
gotTotal := computeTotal(tc.prof, tc.value, tc.meanDiv) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge with next line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// profile. | ||
func (p *Profile) SetLabel(key string, value []string) { | ||
for _, sample := range p.Sample { | ||
if sample.Label == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could avoid lookup on the "no key" case as
if sample.Label == nil {
sample.Label = map[string][]string{key: value}
} else {
sample.Label[key] = value
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
profile/profile.go
Outdated
// samples in the profile. | ||
func (p *Profile) RemoveLabel(key string) { | ||
for _, sample := range p.Sample { | ||
if _, ok := sample.Label[key]; ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://blog.golang.org/go-maps-in-action: "The delete function doesn't return anything, and will do nothing if the specified key doesn't exist.", so I think you could drop the check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
profile/profile.go
Outdated
|
||
// HasLabel returns true if a sample has a label with indicated key and value. | ||
func (s *Sample) HasLabel(key, value string) bool { | ||
if values, ok := s.Label[key]; ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could drop the "ok" check and just do
for _, v := range s.Label[key] {
as it will return nil slice if the key is missing which will immediately terminate the loop anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
profile/profile_test.go
Outdated
for wantKey, wantValues := range wantLabels { | ||
if gotValues, ok := sample.Label[wantKey]; ok { | ||
if !reflect.DeepEqual(gotValues, wantValues) { | ||
t.Fatalf("for key %s, got values %v, want values %v", wantKey, gotValues, wantValues) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why Fatalf?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errorf works.
Done.
c0dc0bd
to
e1f7505
Compare
PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just couple nits. Please also confirm you tested the new functionality manually too.
internal/driver/fetch_test.go
Outdated
true, | ||
[][]int64{}, | ||
[]WantSample{}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and elsewhere: use nil for empty slices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
internal/driver/fetch_test.go
Outdated
if err != nil { | ||
t.Fatalf("%s: %v", tc.desc, err) | ||
t.Fatal(err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this perhaps should be t.Fatalf("got error %q, want no error", err)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
PTAL |
Did some manual testing. Looking at the graph view, the value for "Total samples" is equal to the total of the base profile. Used -proto to output the profile, then used pprof again to inspect that profile, and saw same total reported with graph view. |
* 'master' of github.com:google/pprof: (32 commits) record diff base profile label key/value constant (google#384) apply additional command overrides based on report format (google#381) profile: fix legacy format Go heap profile parsing (google#382) add -diff flag for better profile comparision (google#369) Use -u flag in pprof installation command so that it updates if needed. (google#376) Add GetBase support for ASLR kernel mappings (google#371) Add show_from profile filter. (google#372) Update README.md due to 8dff45d (google#375) Remove stale docs, move useful ones. (google#374) internal/driver: skip tests requiring tcp on js (google#373) Add "trim path" option which can be used to relocate sources. (google#366) Move update_d3flamegraph.sh script from the root directory. (google#370) Skip unsymbolizable mapping during symbolz pass. (google#368) remove -positive_percentages flag (google#365) Render icicle graph in "Flame Graph" view (google#367) Add command-line editing support for interactive pprof (google#362) Improve profile filter unit tests, fix show filtering of mappings (google#354) Fake mappings should be merged into a single one during merging (google#357) Hack the code so that both existing Go versions and current Go master format it the same (google#358) moved filter tests into their own test file which matches the implementation file, filter.go. (google#356) ...
Part of fixing #323.
Changes made to design:
-diff_base
, rather than-diff
as the flag name. It should be clearer.