golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_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 driver
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"regexp"
    23  	"runtime"
    24  	"strconv"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/google/pprof/internal/plugin"
    30  	"github.com/google/pprof/internal/proftest"
    31  	"github.com/google/pprof/internal/symbolz"
    32  	"github.com/google/pprof/profile"
    33  )
    34  
    35  func TestParse(t *testing.T) {
    36  	// Override weblist command to collect output in buffer
    37  	pprofCommands["weblist"].postProcess = nil
    38  
    39  	// Our mockObjTool.Open will always return success, causing
    40  	// driver.locateBinaries to "find" the binaries below in a non-existant
    41  	// directory. As a workaround, point the search path to the fake
    42  	// directory containing out fake binaries.
    43  	savePath := os.Getenv("PPROF_BINARY_PATH")
    44  	os.Setenv("PPROF_BINARY_PATH", "/path/to")
    45  	defer os.Setenv("PPROF_BINARY_PATH", savePath)
    46  
    47  	testcase := []struct {
    48  		flags, source string
    49  	}{
    50  		{"text,functions,flat", "cpu"},
    51  		{"tree,addresses,flat,nodecount=4", "cpusmall"},
    52  		{"text,functions,flat", "unknown"},
    53  		{"text,alloc_objects,flat", "heap_alloc"},
    54  		{"text,files,flat", "heap"},
    55  		{"text,inuse_objects,flat", "heap"},
    56  		{"text,lines,cum,hide=line[X3]0", "cpu"},
    57  		{"text,lines,cum,show=[12]00", "cpu"},
    58  		{"topproto,lines,cum,hide=mangled[X3]0", "cpu"},
    59  		{"tree,lines,cum,focus=[24]00", "heap"},
    60  		{"tree,relative_percentages,cum,focus=[24]00", "heap"},
    61  		{"callgrind", "cpu"},
    62  		{"callgrind", "heap"},
    63  		{"dot,functions,flat", "cpu"},
    64  		{"dot,lines,flat,focus=[12]00", "heap"},
    65  		{"dot,addresses,flat,ignore=[X3]002,focus=[X1]000", "contention"},
    66  		{"dot,files,cum", "contention"},
    67  		{"comments", "cpu"},
    68  		{"comments", "heap"},
    69  		{"tags", "cpu"},
    70  		{"tags,tagignore=tag[13],tagfocus=key[12]", "cpu"},
    71  		{"tags", "heap"},
    72  		{"tags,unit=bytes", "heap"},
    73  		{"traces", "cpu"},
    74  		{"dot,alloc_space,flat,focus=[234]00", "heap_alloc"},
    75  		{"dot,alloc_space,flat,hide=line.*1?23?", "heap_alloc"},
    76  		{"dot,inuse_space,flat,tagfocus=1mb:2gb", "heap"},
    77  		{"dot,inuse_space,flat,tagfocus=30kb:,tagignore=1mb:2mb", "heap"},
    78  		{"disasm=line[13],addresses,flat", "cpu"},
    79  		{"peek=line.*01", "cpu"},
    80  		{"weblist=line[13],addresses,flat", "cpu"},
    81  	}
    82  
    83  	baseVars := pprofVariables
    84  	defer func() { pprofVariables = baseVars }()
    85  	for _, tc := range testcase {
    86  		// Reset the pprof variables before processing
    87  		pprofVariables = baseVars.makeCopy()
    88  
    89  		f := baseFlags()
    90  		f.args = []string{tc.source}
    91  
    92  		flags := strings.Split(tc.flags, ",")
    93  
    94  		// Skip the output format in the first flag, to output to a proto
    95  		addFlags(&f, flags[1:])
    96  
    97  		// Encode profile into a protobuf and decode it again.
    98  		protoTempFile, err := ioutil.TempFile("", "profile_proto")
    99  		if err != nil {
   100  			t.Errorf("cannot create tempfile: %v", err)
   101  		}
   102  		defer protoTempFile.Close()
   103  		f.strings["output"] = protoTempFile.Name()
   104  
   105  		if flags[0] == "topproto" {
   106  			f.bools["proto"] = false
   107  			f.bools["topproto"] = true
   108  		}
   109  
   110  		// First pprof invocation to save the profile into a profile.proto.
   111  		o1 := setDefaults(nil)
   112  		o1.Flagset = f
   113  		o1.Fetch = testFetcher{}
   114  		o1.Sym = testSymbolizer{}
   115  		if err := PProf(o1); err != nil {
   116  			t.Errorf("%s %q:  %v", tc.source, tc.flags, err)
   117  			continue
   118  		}
   119  		// Reset the pprof variables after the proto invocation
   120  		pprofVariables = baseVars.makeCopy()
   121  
   122  		// Read the profile from the encoded protobuf
   123  		outputTempFile, err := ioutil.TempFile("", "profile_output")
   124  		if err != nil {
   125  			t.Errorf("cannot create tempfile: %v", err)
   126  		}
   127  		defer outputTempFile.Close()
   128  		f.strings["output"] = outputTempFile.Name()
   129  		f.args = []string{protoTempFile.Name()}
   130  
   131  		var solution string
   132  		// Apply the flags for the second pprof run, and identify name of
   133  		// the file containing expected results
   134  		if flags[0] == "topproto" {
   135  			solution = solutionFilename(tc.source, &f)
   136  			delete(f.bools, "topproto")
   137  			f.bools["text"] = true
   138  		} else {
   139  			delete(f.bools, "proto")
   140  			addFlags(&f, flags[:1])
   141  			solution = solutionFilename(tc.source, &f)
   142  		}
   143  
   144  		// Second pprof invocation to read the profile from profile.proto
   145  		// and generate a report.
   146  		o2 := setDefaults(nil)
   147  		o2.Flagset = f
   148  		o2.Sym = testSymbolizeDemangler{}
   149  		o2.Obj = new(mockObjTool)
   150  
   151  		if err := PProf(o2); err != nil {
   152  			t.Errorf("%s: %v", tc.source, err)
   153  		}
   154  		b, err := ioutil.ReadFile(outputTempFile.Name())
   155  		if err != nil {
   156  			t.Errorf("Failed to read profile %s: %v", outputTempFile.Name(), err)
   157  		}
   158  
   159  		// Read data file with expected solution
   160  		solution = "testdata/" + solution
   161  		sbuf, err := ioutil.ReadFile(solution)
   162  		if err != nil {
   163  			t.Errorf("reading solution file %s: %v", solution, err)
   164  			continue
   165  		}
   166  		if runtime.GOOS == "windows" {
   167  			sbuf = bytes.Replace(sbuf, []byte("testdata/"), []byte("testdata\\"), -1)
   168  			sbuf = bytes.Replace(sbuf, []byte("/path/to/"), []byte("\\path\\to\\"), -1)
   169  		}
   170  
   171  		if flags[0] == "svg" {
   172  			b = removeScripts(b)
   173  			sbuf = removeScripts(sbuf)
   174  		}
   175  
   176  		if string(b) != string(sbuf) {
   177  			t.Errorf("diff %s %s", solution, tc.source)
   178  			d, err := proftest.Diff(sbuf, b)
   179  			if err != nil {
   180  				t.Fatalf("diff %s %v", solution, err)
   181  			}
   182  			t.Errorf("%s\n%s\n", solution, d)
   183  		}
   184  	}
   185  }
   186  
   187  // removeScripts removes <script > .. </script> pairs from its input
   188  func removeScripts(in []byte) []byte {
   189  	beginMarker := []byte("<script")
   190  	endMarker := []byte("</script>")
   191  
   192  	if begin := bytes.Index(in, beginMarker); begin > 0 {
   193  		if end := bytes.Index(in[begin:], endMarker); end > 0 {
   194  			in = append(in[:begin], removeScripts(in[begin+end+len(endMarker):])...)
   195  		}
   196  	}
   197  	return in
   198  }
   199  
   200  // addFlags parses flag descriptions and adds them to the testFlags
   201  func addFlags(f *testFlags, flags []string) {
   202  	for _, flag := range flags {
   203  		fields := strings.SplitN(flag, "=", 2)
   204  		switch len(fields) {
   205  		case 1:
   206  			f.bools[fields[0]] = true
   207  		case 2:
   208  			if i, err := strconv.Atoi(fields[1]); err == nil {
   209  				f.ints[fields[0]] = i
   210  			} else {
   211  				f.strings[fields[0]] = fields[1]
   212  			}
   213  		}
   214  	}
   215  }
   216  
   217  // solutionFilename returns the name of the solution file for the test
   218  func solutionFilename(source string, f *testFlags) string {
   219  	name := []string{"pprof", strings.TrimPrefix(source, "http://host:8000/")}
   220  	name = addString(name, f, []string{"flat", "cum"})
   221  	name = addString(name, f, []string{"functions", "files", "lines", "addresses"})
   222  	name = addString(name, f, []string{"inuse_space", "inuse_objects", "alloc_space", "alloc_objects"})
   223  	name = addString(name, f, []string{"relative_percentages"})
   224  	name = addString(name, f, []string{"seconds"})
   225  	name = addString(name, f, []string{"text", "tree", "callgrind", "dot", "svg", "tags", "dot", "traces", "disasm", "peek", "weblist", "topproto", "comments"})
   226  	if f.strings["focus"] != "" || f.strings["tagfocus"] != "" {
   227  		name = append(name, "focus")
   228  	}
   229  	if f.strings["ignore"] != "" || f.strings["tagignore"] != "" {
   230  		name = append(name, "ignore")
   231  	}
   232  	name = addString(name, f, []string{"hide", "show"})
   233  	if f.strings["unit"] != "minimum" {
   234  		name = addString(name, f, []string{"unit"})
   235  	}
   236  	return strings.Join(name, ".")
   237  }
   238  
   239  func addString(name []string, f *testFlags, components []string) []string {
   240  	for _, c := range components {
   241  		if f.bools[c] || f.strings[c] != "" || f.ints[c] != 0 {
   242  			return append(name, c)
   243  		}
   244  	}
   245  	return name
   246  }
   247  
   248  // testFlags implements the plugin.FlagSet interface.
   249  type testFlags struct {
   250  	bools   map[string]bool
   251  	ints    map[string]int
   252  	floats  map[string]float64
   253  	strings map[string]string
   254  	args    []string
   255  }
   256  
   257  func (testFlags) ExtraUsage() string { return "" }
   258  
   259  func (f testFlags) Bool(s string, d bool, c string) *bool {
   260  	if b, ok := f.bools[s]; ok {
   261  		return &b
   262  	}
   263  	return &d
   264  }
   265  
   266  func (f testFlags) Int(s string, d int, c string) *int {
   267  	if i, ok := f.ints[s]; ok {
   268  		return &i
   269  	}
   270  	return &d
   271  }
   272  
   273  func (f testFlags) Float64(s string, d float64, c string) *float64 {
   274  	if g, ok := f.floats[s]; ok {
   275  		return &g
   276  	}
   277  	return &d
   278  }
   279  
   280  func (f testFlags) String(s, d, c string) *string {
   281  	if t, ok := f.strings[s]; ok {
   282  		return &t
   283  	}
   284  	return &d
   285  }
   286  
   287  func (f testFlags) BoolVar(p *bool, s string, d bool, c string) {
   288  	if b, ok := f.bools[s]; ok {
   289  		*p = b
   290  	} else {
   291  		*p = d
   292  	}
   293  }
   294  
   295  func (f testFlags) IntVar(p *int, s string, d int, c string) {
   296  	if i, ok := f.ints[s]; ok {
   297  		*p = i
   298  	} else {
   299  		*p = d
   300  	}
   301  }
   302  
   303  func (f testFlags) Float64Var(p *float64, s string, d float64, c string) {
   304  	if g, ok := f.floats[s]; ok {
   305  		*p = g
   306  	} else {
   307  		*p = d
   308  	}
   309  }
   310  
   311  func (f testFlags) StringVar(p *string, s, d, c string) {
   312  	if t, ok := f.strings[s]; ok {
   313  		*p = t
   314  	} else {
   315  		*p = d
   316  	}
   317  }
   318  
   319  func (f testFlags) StringList(s, d, c string) *[]*string {
   320  	return &[]*string{}
   321  }
   322  
   323  func (f testFlags) Parse(func()) []string {
   324  	return f.args
   325  }
   326  
   327  func baseFlags() testFlags {
   328  	return testFlags{
   329  		bools: map[string]bool{
   330  			"proto":          true,
   331  			"trim":           true,
   332  			"compact_labels": true,
   333  		},
   334  		ints: map[string]int{
   335  			"nodecount": 20,
   336  		},
   337  		floats: map[string]float64{
   338  			"nodefraction": 0.05,
   339  			"edgefraction": 0.01,
   340  			"divide_by":    1.0,
   341  		},
   342  		strings: map[string]string{
   343  			"unit": "minimum",
   344  		},
   345  	}
   346  }
   347  
   348  type testProfile struct {
   349  }
   350  
   351  const testStart = 0x1000
   352  const testOffset = 0x5000
   353  
   354  type testFetcher struct{}
   355  
   356  func (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) {
   357  	var p *profile.Profile
   358  	s = strings.TrimPrefix(s, "http://host:8000/")
   359  	switch s {
   360  	case "cpu", "unknown":
   361  		p = cpuProfile()
   362  	case "cpusmall":
   363  		p = cpuProfileSmall()
   364  	case "heap":
   365  		p = heapProfile()
   366  	case "heap_alloc":
   367  		p = heapProfile()
   368  		p.SampleType = []*profile.ValueType{
   369  			{Type: "alloc_objects", Unit: "count"},
   370  			{Type: "alloc_space", Unit: "bytes"},
   371  		}
   372  	case "contention":
   373  		p = contentionProfile()
   374  	case "symbolz":
   375  		p = symzProfile()
   376  	case "http://host2/symbolz":
   377  		p = symzProfile()
   378  		p.Mapping[0].Start += testOffset
   379  		p.Mapping[0].Limit += testOffset
   380  		for i := range p.Location {
   381  			p.Location[i].Address += testOffset
   382  		}
   383  	default:
   384  		return nil, "", fmt.Errorf("unexpected source: %s", s)
   385  	}
   386  	return p, s, nil
   387  }
   388  
   389  type testSymbolizer struct{}
   390  
   391  func (testSymbolizer) Symbolize(_ string, _ plugin.MappingSources, _ *profile.Profile) error {
   392  	return nil
   393  }
   394  
   395  type testSymbolizeDemangler struct{}
   396  
   397  func (testSymbolizeDemangler) Symbolize(_ string, _ plugin.MappingSources, p *profile.Profile) error {
   398  	for _, fn := range p.Function {
   399  		if fn.Name == "" || fn.SystemName == fn.Name {
   400  			fn.Name = fakeDemangler(fn.SystemName)
   401  		}
   402  	}
   403  	return nil
   404  }
   405  
   406  func testFetchSymbols(source, post string) ([]byte, error) {
   407  	var buf bytes.Buffer
   408  
   409  	if source == "http://host2/symbolz" {
   410  		for _, address := range strings.Split(post, "+") {
   411  			a, _ := strconv.ParseInt(address, 0, 64)
   412  			fmt.Fprintf(&buf, "%v\t", address)
   413  			if a-testStart < testOffset {
   414  				fmt.Fprintf(&buf, "wrong_source_%v_", address)
   415  				continue
   416  			}
   417  			fmt.Fprintf(&buf, "%#x\n", a-testStart-testOffset)
   418  		}
   419  		return buf.Bytes(), nil
   420  	}
   421  	for _, address := range strings.Split(post, "+") {
   422  		a, _ := strconv.ParseInt(address, 0, 64)
   423  		fmt.Fprintf(&buf, "%v\t", address)
   424  		if a-testStart > testOffset {
   425  			fmt.Fprintf(&buf, "wrong_source_%v_", address)
   426  			continue
   427  		}
   428  		fmt.Fprintf(&buf, "%#x\n", a-testStart)
   429  	}
   430  	return buf.Bytes(), nil
   431  }
   432  
   433  type testSymbolzSymbolizer struct{}
   434  
   435  func (testSymbolzSymbolizer) Symbolize(variables string, sources plugin.MappingSources, p *profile.Profile) error {
   436  	return symbolz.Symbolize(sources, testFetchSymbols, p, nil)
   437  }
   438  
   439  func fakeDemangler(name string) string {
   440  	switch name {
   441  	case "mangled1000":
   442  		return "line1000"
   443  	case "mangled2000":
   444  		return "line2000"
   445  	case "mangled2001":
   446  		return "line2001"
   447  	case "mangled3000":
   448  		return "line3000"
   449  	case "mangled3001":
   450  		return "line3001"
   451  	case "mangled3002":
   452  		return "line3002"
   453  	case "mangledNEW":
   454  		return "operator new"
   455  	case "mangledMALLOC":
   456  		return "malloc"
   457  	default:
   458  		return name
   459  	}
   460  }
   461  
   462  func cpuProfile() *profile.Profile {
   463  	var cpuM = []*profile.Mapping{
   464  		{
   465  			ID:              1,
   466  			Start:           0x1000,
   467  			Limit:           0x4000,
   468  			File:            "/path/to/testbinary",
   469  			HasFunctions:    true,
   470  			HasFilenames:    true,
   471  			HasLineNumbers:  true,
   472  			HasInlineFrames: true,
   473  		},
   474  	}
   475  
   476  	var cpuF = []*profile.Function{
   477  		{ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
   478  		{ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
   479  		{ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
   480  		{ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
   481  		{ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
   482  		{ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
   483  	}
   484  
   485  	var cpuL = []*profile.Location{
   486  		{
   487  			ID:      1000,
   488  			Mapping: cpuM[0],
   489  			Address: 0x1000,
   490  			Line: []profile.Line{
   491  				{Function: cpuF[0], Line: 1},
   492  			},
   493  		},
   494  		{
   495  			ID:      2000,
   496  			Mapping: cpuM[0],
   497  			Address: 0x2000,
   498  			Line: []profile.Line{
   499  				{Function: cpuF[2], Line: 9},
   500  				{Function: cpuF[1], Line: 4},
   501  			},
   502  		},
   503  		{
   504  			ID:      3000,
   505  			Mapping: cpuM[0],
   506  			Address: 0x3000,
   507  			Line: []profile.Line{
   508  				{Function: cpuF[5], Line: 2},
   509  				{Function: cpuF[4], Line: 5},
   510  				{Function: cpuF[3], Line: 6},
   511  			},
   512  		},
   513  		{
   514  			ID:      3001,
   515  			Mapping: cpuM[0],
   516  			Address: 0x3001,
   517  			Line: []profile.Line{
   518  				{Function: cpuF[4], Line: 8},
   519  				{Function: cpuF[3], Line: 9},
   520  			},
   521  		},
   522  		{
   523  			ID:      3002,
   524  			Mapping: cpuM[0],
   525  			Address: 0x3002,
   526  			Line: []profile.Line{
   527  				{Function: cpuF[5], Line: 5},
   528  				{Function: cpuF[3], Line: 9},
   529  			},
   530  		},
   531  	}
   532  
   533  	return &profile.Profile{
   534  		PeriodType:    &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
   535  		Period:        1,
   536  		DurationNanos: 10e9,
   537  		SampleType: []*profile.ValueType{
   538  			{Type: "samples", Unit: "count"},
   539  			{Type: "cpu", Unit: "milliseconds"},
   540  		},
   541  		Sample: []*profile.Sample{
   542  			{
   543  				Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]},
   544  				Value:    []int64{1000, 1000},
   545  				Label: map[string][]string{
   546  					"key1": []string{"tag1"},
   547  					"key2": []string{"tag1"},
   548  				},
   549  			},
   550  			{
   551  				Location: []*profile.Location{cpuL[0], cpuL[3]},
   552  				Value:    []int64{100, 100},
   553  				Label: map[string][]string{
   554  					"key1": []string{"tag2"},
   555  					"key3": []string{"tag2"},
   556  				},
   557  			},
   558  			{
   559  				Location: []*profile.Location{cpuL[1], cpuL[4]},
   560  				Value:    []int64{10, 10},
   561  				Label: map[string][]string{
   562  					"key1": []string{"tag3"},
   563  					"key2": []string{"tag2"},
   564  				},
   565  			},
   566  			{
   567  				Location: []*profile.Location{cpuL[2]},
   568  				Value:    []int64{10, 10},
   569  				Label: map[string][]string{
   570  					"key1": []string{"tag4"},
   571  					"key2": []string{"tag1"},
   572  				},
   573  			},
   574  		},
   575  		Location: cpuL,
   576  		Function: cpuF,
   577  		Mapping:  cpuM,
   578  	}
   579  }
   580  
   581  func cpuProfileSmall() *profile.Profile {
   582  	var cpuM = []*profile.Mapping{
   583  		{
   584  			ID:              1,
   585  			Start:           0x1000,
   586  			Limit:           0x4000,
   587  			File:            "/path/to/testbinary",
   588  			HasFunctions:    true,
   589  			HasFilenames:    true,
   590  			HasLineNumbers:  true,
   591  			HasInlineFrames: true,
   592  		},
   593  	}
   594  
   595  	var cpuL = []*profile.Location{
   596  		{
   597  			ID:      1000,
   598  			Mapping: cpuM[0],
   599  			Address: 0x1000,
   600  		},
   601  		{
   602  			ID:      2000,
   603  			Mapping: cpuM[0],
   604  			Address: 0x2000,
   605  		},
   606  		{
   607  			ID:      3000,
   608  			Mapping: cpuM[0],
   609  			Address: 0x3000,
   610  		},
   611  		{
   612  			ID:      4000,
   613  			Mapping: cpuM[0],
   614  			Address: 0x4000,
   615  		},
   616  		{
   617  			ID:      5000,
   618  			Mapping: cpuM[0],
   619  			Address: 0x5000,
   620  		},
   621  	}
   622  
   623  	return &profile.Profile{
   624  		PeriodType:    &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
   625  		Period:        1,
   626  		DurationNanos: 10e9,
   627  		SampleType: []*profile.ValueType{
   628  			{Type: "samples", Unit: "count"},
   629  			{Type: "cpu", Unit: "milliseconds"},
   630  		},
   631  		Sample: []*profile.Sample{
   632  			{
   633  				Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]},
   634  				Value:    []int64{1000, 1000},
   635  			},
   636  			{
   637  				Location: []*profile.Location{cpuL[3], cpuL[1], cpuL[4]},
   638  				Value:    []int64{1000, 1000},
   639  			},
   640  			{
   641  				Location: []*profile.Location{cpuL[2]},
   642  				Value:    []int64{1000, 1000},
   643  			},
   644  			{
   645  				Location: []*profile.Location{cpuL[4]},
   646  				Value:    []int64{1000, 1000},
   647  			},
   648  		},
   649  		Location: cpuL,
   650  		Function: nil,
   651  		Mapping:  cpuM,
   652  	}
   653  }
   654  
   655  func heapProfile() *profile.Profile {
   656  	var heapM = []*profile.Mapping{
   657  		{
   658  			ID:              1,
   659  			BuildID:         "buildid",
   660  			Start:           0x1000,
   661  			Limit:           0x4000,
   662  			HasFunctions:    true,
   663  			HasFilenames:    true,
   664  			HasLineNumbers:  true,
   665  			HasInlineFrames: true,
   666  		},
   667  	}
   668  
   669  	var heapF = []*profile.Function{
   670  		{ID: 1, Name: "pruneme", SystemName: "pruneme", Filename: "prune.h"},
   671  		{ID: 2, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
   672  		{ID: 3, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
   673  		{ID: 4, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
   674  		{ID: 5, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
   675  		{ID: 6, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
   676  		{ID: 7, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
   677  		{ID: 8, Name: "mangledMALLOC", SystemName: "mangledMALLOC", Filename: "malloc.h"},
   678  		{ID: 9, Name: "mangledNEW", SystemName: "mangledNEW", Filename: "new.h"},
   679  	}
   680  
   681  	var heapL = []*profile.Location{
   682  		{
   683  			ID:      1000,
   684  			Mapping: heapM[0],
   685  			Address: 0x1000,
   686  			Line: []profile.Line{
   687  				{Function: heapF[0], Line: 100},
   688  				{Function: heapF[7], Line: 100},
   689  				{Function: heapF[1], Line: 1},
   690  			},
   691  		},
   692  		{
   693  			ID:      2000,
   694  			Mapping: heapM[0],
   695  			Address: 0x2000,
   696  			Line: []profile.Line{
   697  				{Function: heapF[8], Line: 100},
   698  				{Function: heapF[3], Line: 2},
   699  				{Function: heapF[2], Line: 3},
   700  			},
   701  		},
   702  		{
   703  			ID:      3000,
   704  			Mapping: heapM[0],
   705  			Address: 0x3000,
   706  			Line: []profile.Line{
   707  				{Function: heapF[8], Line: 100},
   708  				{Function: heapF[6], Line: 3},
   709  				{Function: heapF[5], Line: 2},
   710  				{Function: heapF[4], Line: 4},
   711  			},
   712  		},
   713  		{
   714  			ID:      3001,
   715  			Mapping: heapM[0],
   716  			Address: 0x3001,
   717  			Line: []profile.Line{
   718  				{Function: heapF[0], Line: 100},
   719  				{Function: heapF[8], Line: 100},
   720  				{Function: heapF[5], Line: 2},
   721  				{Function: heapF[4], Line: 4},
   722  			},
   723  		},
   724  		{
   725  			ID:      3002,
   726  			Mapping: heapM[0],
   727  			Address: 0x3002,
   728  			Line: []profile.Line{
   729  				{Function: heapF[6], Line: 3},
   730  				{Function: heapF[4], Line: 4},
   731  			},
   732  		},
   733  	}
   734  
   735  	return &profile.Profile{
   736  		Comments:   []string{"comment", "#hidden comment"},
   737  		PeriodType: &profile.ValueType{Type: "allocations", Unit: "bytes"},
   738  		Period:     524288,
   739  		SampleType: []*profile.ValueType{
   740  			{Type: "inuse_objects", Unit: "count"},
   741  			{Type: "inuse_space", Unit: "bytes"},
   742  		},
   743  		Sample: []*profile.Sample{
   744  			{
   745  				Location: []*profile.Location{heapL[0], heapL[1], heapL[2]},
   746  				Value:    []int64{10, 1024000},
   747  				NumLabel: map[string][]int64{
   748  					"bytes": []int64{102400},
   749  				},
   750  			},
   751  			{
   752  				Location: []*profile.Location{heapL[0], heapL[3]},
   753  				Value:    []int64{20, 4096000},
   754  				NumLabel: map[string][]int64{
   755  					"bytes": []int64{204800},
   756  				},
   757  			},
   758  			{
   759  				Location: []*profile.Location{heapL[1], heapL[4]},
   760  				Value:    []int64{40, 65536000},
   761  				NumLabel: map[string][]int64{
   762  					"bytes": []int64{1638400},
   763  				},
   764  			},
   765  			{
   766  				Location: []*profile.Location{heapL[2]},
   767  				Value:    []int64{80, 32768000},
   768  				NumLabel: map[string][]int64{
   769  					"bytes": []int64{409600},
   770  				},
   771  			},
   772  		},
   773  		DropFrames: ".*operator new.*|malloc",
   774  		Location:   heapL,
   775  		Function:   heapF,
   776  		Mapping:    heapM,
   777  	}
   778  }
   779  
   780  func contentionProfile() *profile.Profile {
   781  	var contentionM = []*profile.Mapping{
   782  		{
   783  			ID:              1,
   784  			BuildID:         "buildid-contention",
   785  			Start:           0x1000,
   786  			Limit:           0x4000,
   787  			HasFunctions:    true,
   788  			HasFilenames:    true,
   789  			HasLineNumbers:  true,
   790  			HasInlineFrames: true,
   791  		},
   792  	}
   793  
   794  	var contentionF = []*profile.Function{
   795  		{ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
   796  		{ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
   797  		{ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
   798  		{ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
   799  		{ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
   800  		{ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
   801  	}
   802  
   803  	var contentionL = []*profile.Location{
   804  		{
   805  			ID:      1000,
   806  			Mapping: contentionM[0],
   807  			Address: 0x1000,
   808  			Line: []profile.Line{
   809  				{Function: contentionF[0], Line: 1},
   810  			},
   811  		},
   812  		{
   813  			ID:      2000,
   814  			Mapping: contentionM[0],
   815  			Address: 0x2000,
   816  			Line: []profile.Line{
   817  				{Function: contentionF[2], Line: 2},
   818  				{Function: contentionF[1], Line: 3},
   819  			},
   820  		},
   821  		{
   822  			ID:      3000,
   823  			Mapping: contentionM[0],
   824  			Address: 0x3000,
   825  			Line: []profile.Line{
   826  				{Function: contentionF[5], Line: 2},
   827  				{Function: contentionF[4], Line: 3},
   828  				{Function: contentionF[3], Line: 5},
   829  			},
   830  		},
   831  		{
   832  			ID:      3001,
   833  			Mapping: contentionM[0],
   834  			Address: 0x3001,
   835  			Line: []profile.Line{
   836  				{Function: contentionF[4], Line: 3},
   837  				{Function: contentionF[3], Line: 5},
   838  			},
   839  		},
   840  		{
   841  			ID:      3002,
   842  			Mapping: contentionM[0],
   843  			Address: 0x3002,
   844  			Line: []profile.Line{
   845  				{Function: contentionF[5], Line: 4},
   846  				{Function: contentionF[3], Line: 3},
   847  			},
   848  		},
   849  	}
   850  
   851  	return &profile.Profile{
   852  		PeriodType: &profile.ValueType{Type: "contentions", Unit: "count"},
   853  		Period:     524288,
   854  		SampleType: []*profile.ValueType{
   855  			{Type: "contentions", Unit: "count"},
   856  			{Type: "delay", Unit: "nanoseconds"},
   857  		},
   858  		Sample: []*profile.Sample{
   859  			{
   860  				Location: []*profile.Location{contentionL[0], contentionL[1], contentionL[2]},
   861  				Value:    []int64{10, 10240000},
   862  			},
   863  			{
   864  				Location: []*profile.Location{contentionL[0], contentionL[3]},
   865  				Value:    []int64{20, 40960000},
   866  			},
   867  			{
   868  				Location: []*profile.Location{contentionL[1], contentionL[4]},
   869  				Value:    []int64{40, 65536000},
   870  			},
   871  			{
   872  				Location: []*profile.Location{contentionL[2]},
   873  				Value:    []int64{80, 32768000},
   874  			},
   875  		},
   876  		Location: contentionL,
   877  		Function: contentionF,
   878  		Mapping:  contentionM,
   879  		Comments: []string{"Comment #1", "Comment #2"},
   880  	}
   881  }
   882  
   883  func symzProfile() *profile.Profile {
   884  	var symzM = []*profile.Mapping{
   885  		{
   886  			ID:    1,
   887  			Start: testStart,
   888  			Limit: 0x4000,
   889  			File:  "/path/to/testbinary",
   890  		},
   891  	}
   892  
   893  	var symzL = []*profile.Location{
   894  		{ID: 1, Mapping: symzM[0], Address: testStart},
   895  		{ID: 2, Mapping: symzM[0], Address: testStart + 0x1000},
   896  		{ID: 3, Mapping: symzM[0], Address: testStart + 0x2000},
   897  	}
   898  
   899  	return &profile.Profile{
   900  		PeriodType:    &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
   901  		Period:        1,
   902  		DurationNanos: 10e9,
   903  		SampleType: []*profile.ValueType{
   904  			{Type: "samples", Unit: "count"},
   905  			{Type: "cpu", Unit: "milliseconds"},
   906  		},
   907  		Sample: []*profile.Sample{
   908  			{
   909  				Location: []*profile.Location{symzL[0], symzL[1], symzL[2]},
   910  				Value:    []int64{1, 1},
   911  			},
   912  		},
   913  		Location: symzL,
   914  		Mapping:  symzM,
   915  	}
   916  }
   917  
   918  var autoCompleteTests = []struct {
   919  	in  string
   920  	out string
   921  }{
   922  	{"", ""},
   923  	{"xyz", "xyz"},                        // no match
   924  	{"dis", "disasm"},                     // single match
   925  	{"t", "t"},                            // many matches
   926  	{"top abc", "top abc"},                // no function name match
   927  	{"top mangledM", "top mangledMALLOC"}, // single function name match
   928  	{"top cmd cmd mangledM", "top cmd cmd mangledMALLOC"},
   929  	{"top mangled", "top mangled"},                      // many function name matches
   930  	{"cmd mangledM", "cmd mangledM"},                    // invalid command
   931  	{"top mangledM cmd", "top mangledM cmd"},            // cursor misplaced
   932  	{"top edMA", "top mangledMALLOC"},                   // single infix function name match
   933  	{"top -mangledM", "top -mangledMALLOC"},             // ignore sign handled
   934  	{"lin", "lines"},                                    // single variable match
   935  	{"EdGeF", "edgefraction"},                           // single capitalized match
   936  	{"help dis", "help disasm"},                         // help command match
   937  	{"help relative_perc", "help relative_percentages"}, // help variable match
   938  	{"help coMpa", "help compact_labels"},               // help variable capitalized match
   939  }
   940  
   941  func TestAutoComplete(t *testing.T) {
   942  	complete := newCompleter(functionNames(heapProfile()))
   943  
   944  	for _, test := range autoCompleteTests {
   945  		if out := complete(test.in); out != test.out {
   946  			t.Errorf("autoComplete(%s) = %s; want %s", test.in, out, test.out)
   947  		}
   948  	}
   949  }
   950  
   951  func TestTagFilter(t *testing.T) {
   952  	var tagFilterTests = []struct {
   953  		name, value string
   954  		tags        map[string][]string
   955  		want        bool
   956  	}{
   957  		{"test1", "tag2", map[string][]string{"value1": {"tag1", "tag2"}}, true},
   958  		{"test2", "tag3", map[string][]string{"value1": {"tag1", "tag2"}}, false},
   959  		{"test3", "tag1,tag3", map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, true},
   960  		{"test4", "t..[12],t..3", map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, true},
   961  		{"test5", "tag2,tag3", map[string][]string{"value1": {"tag1", "tag2"}}, false},
   962  	}
   963  
   964  	for _, test := range tagFilterTests {
   965  		filter, err := compileTagFilter(test.name, test.value, &proftest.TestUI{T: t}, nil)
   966  		if err != nil {
   967  			t.Errorf("tagFilter %s:%v", test.name, err)
   968  			continue
   969  		}
   970  		s := profile.Sample{
   971  			Label: test.tags,
   972  		}
   973  
   974  		if got := filter(&s); got != test.want {
   975  			t.Errorf("tagFilter %s: got %v, want %v", test.name, got, test.want)
   976  		}
   977  	}
   978  }
   979  
   980  func TestSymbolzAfterMerge(t *testing.T) {
   981  	baseVars := pprofVariables
   982  	pprofVariables = baseVars.makeCopy()
   983  	defer func() { pprofVariables = baseVars }()
   984  
   985  	f := baseFlags()
   986  	f.args = []string{"symbolz", "http://host2/symbolz"}
   987  
   988  	o := setDefaults(nil)
   989  	o.Flagset = f
   990  	o.Obj = new(mockObjTool)
   991  	src, cmd, err := parseFlags(o)
   992  	if err != nil {
   993  		t.Fatalf("parseFlags: %v", err)
   994  	}
   995  
   996  	if len(cmd) != 1 || cmd[0] != "proto" {
   997  		t.Fatalf("parseFlags returned command %v, want [proto]", cmd)
   998  	}
   999  
  1000  	o.Fetch = testFetcher{}
  1001  	o.Sym = testSymbolzSymbolizer{}
  1002  	p, err := fetchProfiles(src, o)
  1003  	if err != nil {
  1004  		t.Fatalf("fetchProfiles: %v", err)
  1005  	}
  1006  	if len(p.Location) != 3 {
  1007  		t.Errorf("Got %d locations after merge, want %d", len(p.Location), 3)
  1008  	}
  1009  	for i, l := range p.Location {
  1010  		if len(l.Line) != 1 {
  1011  			t.Errorf("Number of lines for symbolz %#x in iteration %d, got %d, want %d", l.Address, i, len(l.Line), 1)
  1012  			continue
  1013  		}
  1014  		address := l.Address - l.Mapping.Start
  1015  		if got, want := l.Line[0].Function.Name, fmt.Sprintf("%#x", address); got != want {
  1016  			t.Errorf("symbolz %#x, got %s, want %s", address, got, want)
  1017  		}
  1018  	}
  1019  }
  1020  
  1021  type mockObjTool struct{}
  1022  
  1023  func (*mockObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) {
  1024  	return &mockFile{file, "abcdef", 0}, nil
  1025  }
  1026  
  1027  func (m *mockObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
  1028  	switch start {
  1029  	case 0x1000:
  1030  		return []plugin.Inst{
  1031  			{Addr: 0x1000, Text: "instruction one"},
  1032  			{Addr: 0x1001, Text: "instruction two"},
  1033  			{Addr: 0x1002, Text: "instruction three"},
  1034  			{Addr: 0x1003, Text: "instruction four"},
  1035  		}, nil
  1036  	case 0x3000:
  1037  		return []plugin.Inst{
  1038  			{Addr: 0x3000, Text: "instruction one"},
  1039  			{Addr: 0x3001, Text: "instruction two"},
  1040  			{Addr: 0x3002, Text: "instruction three"},
  1041  			{Addr: 0x3003, Text: "instruction four"},
  1042  			{Addr: 0x3004, Text: "instruction five"},
  1043  		}, nil
  1044  	}
  1045  	return nil, fmt.Errorf("unimplemented")
  1046  }
  1047  
  1048  type mockFile struct {
  1049  	name, buildId string
  1050  	base          uint64
  1051  }
  1052  
  1053  // Name returns the underlyinf file name, if available
  1054  func (m *mockFile) Name() string {
  1055  	return m.name
  1056  }
  1057  
  1058  // Base returns the base address to use when looking up symbols in the file.
  1059  func (m *mockFile) Base() uint64 {
  1060  	return m.base
  1061  }
  1062  
  1063  // BuildID returns the GNU build ID of the file, or an empty string.
  1064  func (m *mockFile) BuildID() string {
  1065  	return m.buildId
  1066  }
  1067  
  1068  // SourceLine reports the source line information for a given
  1069  // address in the file. Due to inlining, the source line information
  1070  // is in general a list of positions representing a call stack,
  1071  // with the leaf function first.
  1072  func (*mockFile) SourceLine(addr uint64) ([]plugin.Frame, error) {
  1073  	return nil, fmt.Errorf("unimplemented")
  1074  }
  1075  
  1076  // Symbols returns a list of symbols in the object file.
  1077  // If r is not nil, Symbols restricts the list to symbols
  1078  // with names matching the regular expression.
  1079  // If addr is not zero, Symbols restricts the list to symbols
  1080  // containing that address.
  1081  func (m *mockFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
  1082  	switch r.String() {
  1083  	case "line[13]":
  1084  		return []*plugin.Sym{
  1085  			{[]string{"line1000"}, m.name, 0x1000, 0x1003},
  1086  			{[]string{"line3000"}, m.name, 0x3000, 0x3004},
  1087  		}, nil
  1088  	}
  1089  	return nil, fmt.Errorf("unimplemented")
  1090  }
  1091  
  1092  // Close closes the file, releasing associated resources.
  1093  func (*mockFile) Close() error {
  1094  	return nil
  1095  }