golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/profile/profile_test.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package profile
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"path/filepath"
    22  	"regexp"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/google/pprof/internal/proftest"
    27  )
    28  
    29  func TestParse(t *testing.T) {
    30  	const path = "testdata/"
    31  
    32  	for _, source := range []string{
    33  		"go.crc32.cpu",
    34  		"go.godoc.thread",
    35  		"gobench.cpu",
    36  		"gobench.heap",
    37  		"cppbench.cpu",
    38  		"cppbench.heap",
    39  		"cppbench.contention",
    40  		"cppbench.growth",
    41  		"cppbench.thread",
    42  		"cppbench.thread.all",
    43  		"cppbench.thread.none",
    44  		"java.cpu",
    45  		"java.heap",
    46  		"java.contention",
    47  	} {
    48  		inbytes, err := ioutil.ReadFile(filepath.Join(path, source))
    49  		if err != nil {
    50  			t.Fatal(err)
    51  		}
    52  		p, err := Parse(bytes.NewBuffer(inbytes))
    53  		if err != nil {
    54  			t.Fatalf("%s: %s", source, err)
    55  		}
    56  
    57  		js := p.String()
    58  		goldFilename := path + source + ".string"
    59  		gold, err := ioutil.ReadFile(goldFilename)
    60  		if err != nil {
    61  			t.Fatalf("%s: %v", source, err)
    62  		}
    63  
    64  		if js != string(gold) {
    65  			t.Errorf("diff %s %s", source, goldFilename)
    66  			d, err := proftest.Diff(gold, []byte(js))
    67  			if err != nil {
    68  				t.Fatalf("%s: %v", source, err)
    69  			}
    70  			t.Error(source + "\n" + string(d) + "\n" + "new profile at:\n" + leaveTempfile([]byte(js)))
    71  		}
    72  
    73  		// Reencode and decode.
    74  		bw := bytes.NewBuffer(nil)
    75  		if err := p.Write(bw); err != nil {
    76  			t.Fatalf("%s: %v", source, err)
    77  		}
    78  		if p, err = Parse(bw); err != nil {
    79  			t.Fatalf("%s: %v", source, err)
    80  		}
    81  		js2 := p.String()
    82  		if js2 != string(gold) {
    83  			d, err := proftest.Diff(gold, []byte(js2))
    84  			if err != nil {
    85  				t.Fatalf("%s: %v", source, err)
    86  			}
    87  			t.Error(source + "\n" + string(d) + "\n" + "gold:\n" + goldFilename +
    88  				"\nnew profile at:\n" + leaveTempfile([]byte(js)))
    89  		}
    90  	}
    91  }
    92  
    93  func TestParseError(t *testing.T) {
    94  
    95  	testcases := []string{
    96  		"",
    97  		"garbage text",
    98  		"\x1f\x8b", // truncated gzip header
    99  		"\x1f\x8b\x08\x08\xbe\xe9\x20\x58\x00\x03\x65\x6d\x70\x74\x79\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // empty gzipped file
   100  	}
   101  
   102  	for i, input := range testcases {
   103  		_, err := Parse(strings.NewReader(input))
   104  		if err == nil {
   105  			t.Errorf("got nil, want error for input #%d", i)
   106  		}
   107  	}
   108  }
   109  
   110  // leaveTempfile leaves |b| in a temporary file on disk and returns the
   111  // temp filename. This is useful to recover a profile when the test
   112  // fails.
   113  func leaveTempfile(b []byte) string {
   114  	f1, err := ioutil.TempFile("", "profile_test")
   115  	if err != nil {
   116  		panic(err)
   117  	}
   118  	if _, err := f1.Write(b); err != nil {
   119  		panic(err)
   120  	}
   121  	return f1.Name()
   122  }
   123  
   124  const mainBinary = "/bin/main"
   125  
   126  var cpuM = []*Mapping{
   127  	{
   128  		ID:              1,
   129  		Start:           0x10000,
   130  		Limit:           0x40000,
   131  		File:            mainBinary,
   132  		HasFunctions:    true,
   133  		HasFilenames:    true,
   134  		HasLineNumbers:  true,
   135  		HasInlineFrames: true,
   136  	},
   137  	{
   138  		ID:              2,
   139  		Start:           0x1000,
   140  		Limit:           0x4000,
   141  		File:            "/lib/lib.so",
   142  		HasFunctions:    true,
   143  		HasFilenames:    true,
   144  		HasLineNumbers:  true,
   145  		HasInlineFrames: true,
   146  	},
   147  	{
   148  		ID:              3,
   149  		Start:           0x4000,
   150  		Limit:           0x5000,
   151  		File:            "/lib/lib2_c.so.6",
   152  		HasFunctions:    true,
   153  		HasFilenames:    true,
   154  		HasLineNumbers:  true,
   155  		HasInlineFrames: true,
   156  	},
   157  	{
   158  		ID:              4,
   159  		Start:           0x5000,
   160  		Limit:           0x9000,
   161  		File:            "/lib/lib.so_6 (deleted)",
   162  		HasFunctions:    true,
   163  		HasFilenames:    true,
   164  		HasLineNumbers:  true,
   165  		HasInlineFrames: true,
   166  	},
   167  }
   168  
   169  var cpuF = []*Function{
   170  	{ID: 1, Name: "main", SystemName: "main", Filename: "main.c"},
   171  	{ID: 2, Name: "foo", SystemName: "foo", Filename: "foo.c"},
   172  	{ID: 3, Name: "foo_caller", SystemName: "foo_caller", Filename: "foo.c"},
   173  }
   174  
   175  var cpuL = []*Location{
   176  	{
   177  		ID:      1000,
   178  		Mapping: cpuM[1],
   179  		Address: 0x1000,
   180  		Line: []Line{
   181  			{Function: cpuF[0], Line: 1},
   182  		},
   183  	},
   184  	{
   185  		ID:      2000,
   186  		Mapping: cpuM[0],
   187  		Address: 0x2000,
   188  		Line: []Line{
   189  			{Function: cpuF[1], Line: 2},
   190  			{Function: cpuF[2], Line: 1},
   191  		},
   192  	},
   193  	{
   194  		ID:      3000,
   195  		Mapping: cpuM[0],
   196  		Address: 0x3000,
   197  		Line: []Line{
   198  			{Function: cpuF[1], Line: 2},
   199  			{Function: cpuF[2], Line: 1},
   200  		},
   201  	},
   202  	{
   203  		ID:      3001,
   204  		Mapping: cpuM[0],
   205  		Address: 0x3001,
   206  		Line: []Line{
   207  			{Function: cpuF[2], Line: 2},
   208  		},
   209  	},
   210  	{
   211  		ID:      3002,
   212  		Mapping: cpuM[0],
   213  		Address: 0x3002,
   214  		Line: []Line{
   215  			{Function: cpuF[2], Line: 3},
   216  		},
   217  	},
   218  }
   219  
   220  var testProfile = &Profile{
   221  	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
   222  	Period:        1,
   223  	DurationNanos: 10e9,
   224  	SampleType: []*ValueType{
   225  		{Type: "samples", Unit: "count"},
   226  		{Type: "cpu", Unit: "milliseconds"},
   227  	},
   228  	Sample: []*Sample{
   229  		{
   230  			Location: []*Location{cpuL[0]},
   231  			Value:    []int64{1000, 1000},
   232  			Label: map[string][]string{
   233  				"key1": []string{"tag1"},
   234  				"key2": []string{"tag1"},
   235  			},
   236  		},
   237  		{
   238  			Location: []*Location{cpuL[1], cpuL[0]},
   239  			Value:    []int64{100, 100},
   240  			Label: map[string][]string{
   241  				"key1": []string{"tag2"},
   242  				"key3": []string{"tag2"},
   243  			},
   244  		},
   245  		{
   246  			Location: []*Location{cpuL[2], cpuL[0]},
   247  			Value:    []int64{10, 10},
   248  			Label: map[string][]string{
   249  				"key1": []string{"tag3"},
   250  				"key2": []string{"tag2"},
   251  			},
   252  		},
   253  		{
   254  			Location: []*Location{cpuL[3], cpuL[0]},
   255  			Value:    []int64{10000, 10000},
   256  			Label: map[string][]string{
   257  				"key1": []string{"tag4"},
   258  				"key2": []string{"tag1"},
   259  			},
   260  		},
   261  		{
   262  			Location: []*Location{cpuL[4], cpuL[0]},
   263  			Value:    []int64{1, 1},
   264  			Label: map[string][]string{
   265  				"key1": []string{"tag4"},
   266  				"key2": []string{"tag1"},
   267  			},
   268  		},
   269  	},
   270  	Location: cpuL,
   271  	Function: cpuF,
   272  	Mapping:  cpuM,
   273  }
   274  
   275  var aggTests = map[string]aggTest{
   276  	"precise":         aggTest{true, true, true, true, 5},
   277  	"fileline":        aggTest{false, true, true, true, 4},
   278  	"inline_function": aggTest{false, true, false, true, 3},
   279  	"function":        aggTest{false, true, false, false, 2},
   280  }
   281  
   282  type aggTest struct {
   283  	precise, function, fileline, inlineFrame bool
   284  	rows                                     int
   285  }
   286  
   287  const totalSamples = int64(11111)
   288  
   289  func TestAggregation(t *testing.T) {
   290  	prof := testProfile.Copy()
   291  	for _, resolution := range []string{"precise", "fileline", "inline_function", "function"} {
   292  		a := aggTests[resolution]
   293  		if !a.precise {
   294  			if err := prof.Aggregate(a.inlineFrame, a.function, a.fileline, a.fileline, false); err != nil {
   295  				t.Error("aggregating to " + resolution + ":" + err.Error())
   296  			}
   297  		}
   298  		if err := checkAggregation(prof, &a); err != nil {
   299  			t.Error("failed aggregation to " + resolution + ": " + err.Error())
   300  		}
   301  	}
   302  }
   303  
   304  // checkAggregation verifies that the profile remained consistent
   305  // with its aggregation.
   306  func checkAggregation(prof *Profile, a *aggTest) error {
   307  	// Check that the total number of samples for the rows was preserved.
   308  	total := int64(0)
   309  
   310  	samples := make(map[string]bool)
   311  	for _, sample := range prof.Sample {
   312  		tb := locationHash(sample)
   313  		samples[tb] = true
   314  		total += sample.Value[0]
   315  	}
   316  
   317  	if total != totalSamples {
   318  		return fmt.Errorf("sample total %d, want %d", total, totalSamples)
   319  	}
   320  
   321  	// Check the number of unique sample locations
   322  	if a.rows != len(samples) {
   323  		return fmt.Errorf("number of samples %d, want %d", len(samples), a.rows)
   324  	}
   325  
   326  	// Check that all mappings have the right detail flags.
   327  	for _, m := range prof.Mapping {
   328  		if m.HasFunctions != a.function {
   329  			return fmt.Errorf("unexpected mapping.HasFunctions %v, want %v", m.HasFunctions, a.function)
   330  		}
   331  		if m.HasFilenames != a.fileline {
   332  			return fmt.Errorf("unexpected mapping.HasFilenames %v, want %v", m.HasFilenames, a.fileline)
   333  		}
   334  		if m.HasLineNumbers != a.fileline {
   335  			return fmt.Errorf("unexpected mapping.HasLineNumbers %v, want %v", m.HasLineNumbers, a.fileline)
   336  		}
   337  		if m.HasInlineFrames != a.inlineFrame {
   338  			return fmt.Errorf("unexpected mapping.HasInlineFrames %v, want %v", m.HasInlineFrames, a.inlineFrame)
   339  		}
   340  	}
   341  
   342  	// Check that aggregation has removed finer resolution data.
   343  	for _, l := range prof.Location {
   344  		if !a.inlineFrame && len(l.Line) > 1 {
   345  			return fmt.Errorf("found %d lines on location %d, want 1", len(l.Line), l.ID)
   346  		}
   347  
   348  		for _, ln := range l.Line {
   349  			if !a.fileline && (ln.Function.Filename != "" || ln.Line != 0) {
   350  				return fmt.Errorf("found line %s:%d on location %d, want :0",
   351  					ln.Function.Filename, ln.Line, l.ID)
   352  			}
   353  			if !a.function && (ln.Function.Name != "") {
   354  				return fmt.Errorf(`found file %s location %d, want ""`,
   355  					ln.Function.Name, l.ID)
   356  			}
   357  		}
   358  	}
   359  
   360  	return nil
   361  }
   362  
   363  // Test merge leaves the main binary in place.
   364  func TestMergeMain(t *testing.T) {
   365  	prof := testProfile.Copy()
   366  	p1, err := Merge([]*Profile{prof})
   367  	if err != nil {
   368  		t.Fatalf("merge error: %v", err)
   369  	}
   370  	if cpuM[0].File != p1.Mapping[0].File {
   371  		t.Errorf("want Mapping[0]=%s got %s", cpuM[0].File, p1.Mapping[0].File)
   372  	}
   373  }
   374  
   375  func TestMerge(t *testing.T) {
   376  	// Aggregate a profile with itself and once again with a factor of
   377  	// -2. Should end up with an empty profile (all samples for a
   378  	// location should add up to 0).
   379  
   380  	prof := testProfile.Copy()
   381  	p1, err := Merge([]*Profile{prof, prof})
   382  	if err != nil {
   383  		t.Errorf("merge error: %v", err)
   384  	}
   385  	prof.Scale(-2)
   386  	prof, err = Merge([]*Profile{p1, prof})
   387  	if err != nil {
   388  		t.Errorf("merge error: %v", err)
   389  	}
   390  
   391  	// Use aggregation to merge locations at function granularity.
   392  	if err := prof.Aggregate(false, true, false, false, false); err != nil {
   393  		t.Errorf("aggregating after merge: %v", err)
   394  	}
   395  
   396  	samples := make(map[string]int64)
   397  	for _, s := range prof.Sample {
   398  		tb := locationHash(s)
   399  		samples[tb] = samples[tb] + s.Value[0]
   400  	}
   401  	for s, v := range samples {
   402  		if v != 0 {
   403  			t.Errorf("nonzero value for sample %s: %d", s, v)
   404  		}
   405  	}
   406  }
   407  
   408  func TestMergeAll(t *testing.T) {
   409  	// Aggregate 10 copies of the profile.
   410  	profs := make([]*Profile, 10)
   411  	for i := 0; i < 10; i++ {
   412  		profs[i] = testProfile.Copy()
   413  	}
   414  	prof, err := Merge(profs)
   415  	if err != nil {
   416  		t.Errorf("merge error: %v", err)
   417  	}
   418  	samples := make(map[string]int64)
   419  	for _, s := range prof.Sample {
   420  		tb := locationHash(s)
   421  		samples[tb] = samples[tb] + s.Value[0]
   422  	}
   423  	for _, s := range testProfile.Sample {
   424  		tb := locationHash(s)
   425  		if samples[tb] != s.Value[0]*10 {
   426  			t.Errorf("merge got wrong value at %s : %d instead of %d", tb, samples[tb], s.Value[0]*10)
   427  		}
   428  	}
   429  }
   430  
   431  func TestFilter(t *testing.T) {
   432  	// Perform several forms of filtering on the test profile.
   433  
   434  	type filterTestcase struct {
   435  		focus, ignore, hide, show *regexp.Regexp
   436  		fm, im, hm, hnm           bool
   437  	}
   438  
   439  	for tx, tc := range []filterTestcase{
   440  		{nil, nil, nil, nil, true, false, false, false},
   441  		{regexp.MustCompile("notfound"), nil, nil, nil, false, false, false, false},
   442  		{nil, regexp.MustCompile("foo.c"), nil, nil, true, true, false, false},
   443  		{nil, nil, regexp.MustCompile("lib.so"), nil, true, false, true, false},
   444  	} {
   445  		prof := *testProfile.Copy()
   446  		gf, gi, gh, gnh := prof.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show)
   447  		if gf != tc.fm {
   448  			t.Errorf("Filter #%d, got fm=%v, want %v", tx, gf, tc.fm)
   449  		}
   450  		if gi != tc.im {
   451  			t.Errorf("Filter #%d, got im=%v, want %v", tx, gi, tc.im)
   452  		}
   453  		if gh != tc.hm {
   454  			t.Errorf("Filter #%d, got hm=%v, want %v", tx, gh, tc.hm)
   455  		}
   456  		if gnh != tc.hnm {
   457  			t.Errorf("Filter #%d, got hnm=%v, want %v", tx, gnh, tc.hnm)
   458  		}
   459  	}
   460  }
   461  
   462  func TestTagFilter(t *testing.T) {
   463  	// Perform several forms of tag filtering on the test profile.
   464  
   465  	type filterTestcase struct {
   466  		include, exclude *regexp.Regexp
   467  		im, em           bool
   468  		count            int
   469  	}
   470  
   471  	countTags := func(p *Profile) map[string]bool {
   472  		tags := make(map[string]bool)
   473  
   474  		for _, s := range p.Sample {
   475  			for l := range s.Label {
   476  				tags[l] = true
   477  			}
   478  			for l := range s.NumLabel {
   479  				tags[l] = true
   480  			}
   481  		}
   482  		return tags
   483  	}
   484  
   485  	for tx, tc := range []filterTestcase{
   486  		{nil, nil, true, false, 3},
   487  		{regexp.MustCompile("notfound"), nil, false, false, 0},
   488  		{regexp.MustCompile("key1"), nil, true, false, 1},
   489  		{nil, regexp.MustCompile("key[12]"), true, true, 1},
   490  	} {
   491  		prof := testProfile.Copy()
   492  		gim, gem := prof.FilterTagsByName(tc.include, tc.exclude)
   493  		if gim != tc.im {
   494  			t.Errorf("Filter #%d, got include match=%v, want %v", tx, gim, tc.im)
   495  		}
   496  		if gem != tc.em {
   497  			t.Errorf("Filter #%d, got exclude match=%v, want %v", tx, gem, tc.em)
   498  		}
   499  		if tags := countTags(prof); len(tags) != tc.count {
   500  			t.Errorf("Filter #%d, got %d tags[%v], want %d", tx, len(tags), tags, tc.count)
   501  		}
   502  	}
   503  }
   504  
   505  // locationHash constructs a string to use as a hashkey for a sample, based on its locations
   506  func locationHash(s *Sample) string {
   507  	var tb string
   508  	for _, l := range s.Location {
   509  		for _, ln := range l.Line {
   510  			tb = tb + fmt.Sprintf("%s:%d@%d ", ln.Function.Name, ln.Line, l.Address)
   511  		}
   512  	}
   513  	return tb
   514  }
   515  
   516  func TestSetMain(t *testing.T) {
   517  	testProfile.massageMappings()
   518  	if testProfile.Mapping[0].File != mainBinary {
   519  		t.Errorf("got %s for main", testProfile.Mapping[0].File)
   520  	}
   521  }