...

Source file src/github.com/google/pprof/profile/profile_test.go

Documentation: github.com/google/pprof/profile

     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  	"flag"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"reflect"
    25  	"strings"
    26  	"sync"
    27  	"testing"
    28  
    29  	"github.com/google/pprof/internal/proftest"
    30  )
    31  
    32  var update = flag.Bool("update", false, "Update the golden files")
    33  
    34  func TestParse(t *testing.T) {
    35  	const path = "testdata/"
    36  
    37  	for _, source := range []string{
    38  		"go.crc32.cpu",
    39  		"go.godoc.thread",
    40  		"gobench.cpu",
    41  		"gobench.heap",
    42  		"cppbench.cpu",
    43  		"cppbench.heap",
    44  		"cppbench.contention",
    45  		"cppbench.growth",
    46  		"cppbench.thread",
    47  		"cppbench.thread.all",
    48  		"cppbench.thread.none",
    49  		"java.cpu",
    50  		"java.heap",
    51  		"java.contention",
    52  	} {
    53  		inbytes, err := os.ReadFile(filepath.Join(path, source))
    54  		if err != nil {
    55  			t.Fatal(err)
    56  		}
    57  		p, err := Parse(bytes.NewBuffer(inbytes))
    58  		if err != nil {
    59  			t.Fatalf("%s: %s", source, err)
    60  		}
    61  
    62  		js := p.String()
    63  		goldFilename := path + source + ".string"
    64  		if *update {
    65  			err := os.WriteFile(goldFilename, []byte(js), 0644)
    66  			if err != nil {
    67  				t.Errorf("failed to update the golden file file %q: %v", goldFilename, err)
    68  			}
    69  		}
    70  		gold, err := os.ReadFile(goldFilename)
    71  		if err != nil {
    72  			t.Fatalf("%s: %v", source, err)
    73  		}
    74  
    75  		if js != string(gold) {
    76  			t.Errorf("diff %s %s", source, goldFilename)
    77  			d, err := proftest.Diff(gold, []byte(js))
    78  			if err != nil {
    79  				t.Fatalf("%s: %v", source, err)
    80  			}
    81  			t.Error(source + "\n" + string(d) + "\n" + "new profile at:\n" + leaveTempfile([]byte(js)))
    82  		}
    83  
    84  		// Reencode and decode.
    85  		var bw bytes.Buffer
    86  		if err := p.Write(&bw); err != nil {
    87  			t.Fatalf("%s: %v", source, err)
    88  		}
    89  		if p, err = Parse(&bw); err != nil {
    90  			t.Fatalf("%s: %v", source, err)
    91  		}
    92  		js2 := p.String()
    93  		if js2 != string(gold) {
    94  			d, err := proftest.Diff(gold, []byte(js2))
    95  			if err != nil {
    96  				t.Fatalf("%s: %v", source, err)
    97  			}
    98  			t.Error(source + "\n" + string(d) + "\n" + "gold:\n" + goldFilename +
    99  				"\nnew profile at:\n" + leaveTempfile([]byte(js)))
   100  		}
   101  	}
   102  }
   103  
   104  func TestParseError(t *testing.T) {
   105  	testcases := []string{
   106  		"",
   107  		"garbage text",
   108  		"\x1f\x8b", // truncated gzip header
   109  		"\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
   110  	}
   111  
   112  	for i, input := range testcases {
   113  		_, err := Parse(strings.NewReader(input))
   114  		if err == nil {
   115  			t.Errorf("got nil, want error for input #%d", i)
   116  		}
   117  	}
   118  }
   119  
   120  func TestParseConcatentated(t *testing.T) {
   121  	prof := testProfile1.Copy()
   122  	// Write the profile twice to buffer to create concatented profile.
   123  	var buf bytes.Buffer
   124  	prof.Write(&buf)
   125  	prof.Write(&buf)
   126  	_, err := Parse(&buf)
   127  	if err == nil {
   128  		t.Fatalf("got nil, want error")
   129  	}
   130  	if got, want := err.Error(), "parsing profile: concatenated profiles detected"; want != got {
   131  		t.Fatalf("got error %q, want error %q", got, want)
   132  	}
   133  }
   134  
   135  func TestCheckValid(t *testing.T) {
   136  	const path = "testdata/java.cpu"
   137  
   138  	inbytes, err := os.ReadFile(path)
   139  	if err != nil {
   140  		t.Fatalf("failed to read profile file %q: %v", path, err)
   141  	}
   142  	p, err := Parse(bytes.NewBuffer(inbytes))
   143  	if err != nil {
   144  		t.Fatalf("failed to parse profile %q: %s", path, err)
   145  	}
   146  
   147  	for _, tc := range []struct {
   148  		mutateFn func(*Profile)
   149  		wantErr  string
   150  	}{
   151  		{
   152  			mutateFn: func(p *Profile) { p.SampleType = nil },
   153  			wantErr:  "missing sample type information",
   154  		},
   155  		{
   156  			mutateFn: func(p *Profile) { p.Sample[0] = nil },
   157  			wantErr:  "profile has nil sample",
   158  		},
   159  		{
   160  			mutateFn: func(p *Profile) { p.Sample[0].Value = append(p.Sample[0].Value, 0) },
   161  			wantErr:  "sample has 3 values vs. 2 types",
   162  		},
   163  		{
   164  			mutateFn: func(p *Profile) { p.Sample[0].Location[0] = nil },
   165  			wantErr:  "sample has nil location",
   166  		},
   167  		{
   168  			mutateFn: func(p *Profile) { p.Location[0] = nil },
   169  			wantErr:  "profile has nil location",
   170  		},
   171  		{
   172  			mutateFn: func(p *Profile) { p.Mapping = append(p.Mapping, nil) },
   173  			wantErr:  "profile has nil mapping",
   174  		},
   175  		{
   176  			mutateFn: func(p *Profile) { p.Function[0] = nil },
   177  			wantErr:  "profile has nil function",
   178  		},
   179  		{
   180  			mutateFn: func(p *Profile) { p.Location[0].Line = append(p.Location[0].Line, Line{}) },
   181  			wantErr:  "has a line with nil function",
   182  		},
   183  	} {
   184  		t.Run(tc.wantErr, func(t *testing.T) {
   185  			p := p.Copy()
   186  			tc.mutateFn(p)
   187  			if err := p.CheckValid(); err == nil {
   188  				t.Errorf("CheckValid(): got no error, want error %q", tc.wantErr)
   189  			} else if !strings.Contains(err.Error(), tc.wantErr) {
   190  				t.Errorf("CheckValid(): got error %v, want error %q", err, tc.wantErr)
   191  			}
   192  		})
   193  	}
   194  }
   195  
   196  // leaveTempfile leaves |b| in a temporary file on disk and returns the
   197  // temp filename. This is useful to recover a profile when the test
   198  // fails.
   199  func leaveTempfile(b []byte) string {
   200  	f1, err := os.CreateTemp("", "profile_test")
   201  	if err != nil {
   202  		panic(err)
   203  	}
   204  	if _, err := f1.Write(b); err != nil {
   205  		panic(err)
   206  	}
   207  	return f1.Name()
   208  }
   209  
   210  const mainBinary = "/bin/main"
   211  
   212  var cpuM = []*Mapping{
   213  	{
   214  		ID:              1,
   215  		Start:           0x10000,
   216  		Limit:           0x40000,
   217  		File:            mainBinary,
   218  		HasFunctions:    true,
   219  		HasFilenames:    true,
   220  		HasLineNumbers:  true,
   221  		HasInlineFrames: true,
   222  	},
   223  	{
   224  		ID:              2,
   225  		Start:           0x1000,
   226  		Limit:           0x4000,
   227  		File:            "/lib/lib.so",
   228  		HasFunctions:    true,
   229  		HasFilenames:    true,
   230  		HasLineNumbers:  true,
   231  		HasInlineFrames: true,
   232  	},
   233  	{
   234  		ID:              3,
   235  		Start:           0x4000,
   236  		Limit:           0x5000,
   237  		File:            "/lib/lib2_c.so.6",
   238  		HasFunctions:    true,
   239  		HasFilenames:    true,
   240  		HasLineNumbers:  true,
   241  		HasInlineFrames: true,
   242  	},
   243  	{
   244  		ID:              4,
   245  		Start:           0x5000,
   246  		Limit:           0x9000,
   247  		File:            "/lib/lib.so_6 (deleted)",
   248  		HasFunctions:    true,
   249  		HasFilenames:    true,
   250  		HasLineNumbers:  true,
   251  		HasInlineFrames: true,
   252  	},
   253  	{
   254  		ID:              5,
   255  		Start:           0xffff000010080000,
   256  		Limit:           0xffffffffffffffff,
   257  		File:            "[kernel.kallsyms]_text",
   258  		HasFunctions:    true,
   259  		HasFilenames:    true,
   260  		HasLineNumbers:  true,
   261  		HasInlineFrames: true,
   262  	},
   263  }
   264  
   265  var cpuF = []*Function{
   266  	{ID: 1, Name: "main", SystemName: "main", Filename: "main.c"},
   267  	{ID: 2, Name: "foo", SystemName: "foo", Filename: "foo.c"},
   268  	{ID: 3, Name: "foo_caller", SystemName: "foo_caller", Filename: "foo.c"},
   269  }
   270  
   271  var cpuL = []*Location{
   272  	{
   273  		ID:      1000,
   274  		Mapping: cpuM[1],
   275  		Address: 0x1000,
   276  		Line: []Line{
   277  			{Function: cpuF[0], Line: 1, Column: 1},
   278  		},
   279  	},
   280  	{
   281  		ID:      2000,
   282  		Mapping: cpuM[0],
   283  		Address: 0x2000,
   284  		Line: []Line{
   285  			{Function: cpuF[1], Line: 2, Column: 2},
   286  			{Function: cpuF[2], Line: 1, Column: 1},
   287  		},
   288  	},
   289  	{
   290  		ID:      3000,
   291  		Mapping: cpuM[0],
   292  		Address: 0x3000,
   293  		Line: []Line{
   294  			{Function: cpuF[1], Line: 2, Column: 2},
   295  			{Function: cpuF[2], Line: 1, Column: 1},
   296  		},
   297  	},
   298  	{
   299  		ID:      3001,
   300  		Mapping: cpuM[0],
   301  		Address: 0x3001,
   302  		Line: []Line{
   303  			{Function: cpuF[2], Line: 2, Column: 2},
   304  		},
   305  	},
   306  	{
   307  		ID:      3002,
   308  		Mapping: cpuM[0],
   309  		Address: 0x3002,
   310  		Line: []Line{
   311  			{Function: cpuF[2], Line: 3, Column: 3},
   312  		},
   313  	},
   314  	// Differs from 1000 due to address and column number.
   315  	{
   316  		ID:      1001,
   317  		Mapping: cpuM[1],
   318  		Address: 0x1001,
   319  		Line: []Line{
   320  			{Function: cpuF[0], Line: 1, Column: 2},
   321  		},
   322  	},
   323  }
   324  
   325  var testProfile1 = &Profile{
   326  	TimeNanos:     10000,
   327  	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
   328  	Period:        1,
   329  	DurationNanos: 10e9,
   330  	SampleType: []*ValueType{
   331  		{Type: "samples", Unit: "count"},
   332  		{Type: "cpu", Unit: "milliseconds"},
   333  	},
   334  	Sample: []*Sample{
   335  		{
   336  			Location: []*Location{cpuL[0]},
   337  			Value:    []int64{1000, 1000},
   338  			Label: map[string][]string{
   339  				"key1": {"tag1"},
   340  				"key2": {"tag1"},
   341  			},
   342  		},
   343  		{
   344  			Location: []*Location{cpuL[1], cpuL[0]},
   345  			Value:    []int64{100, 100},
   346  			Label: map[string][]string{
   347  				"key1": {"tag2"},
   348  				"key3": {"tag2"},
   349  			},
   350  		},
   351  		{
   352  			Location: []*Location{cpuL[2], cpuL[0]},
   353  			Value:    []int64{10, 10},
   354  			Label: map[string][]string{
   355  				"key1": {"tag3"},
   356  				"key2": {"tag2"},
   357  			},
   358  		},
   359  		{
   360  			Location: []*Location{cpuL[3], cpuL[0]},
   361  			Value:    []int64{10000, 10000},
   362  			Label: map[string][]string{
   363  				"key1": {"tag4"},
   364  				"key2": {"tag1"},
   365  			},
   366  		},
   367  		{
   368  			Location: []*Location{cpuL[4], cpuL[0]},
   369  			Value:    []int64{1, 1},
   370  			Label: map[string][]string{
   371  				"key1": {"tag4"},
   372  				"key2": {"tag1"},
   373  			},
   374  		},
   375  	},
   376  	Location: cpuL,
   377  	Function: cpuF,
   378  	Mapping:  cpuM,
   379  }
   380  
   381  var testProfile1NoMapping = &Profile{
   382  	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
   383  	Period:        1,
   384  	DurationNanos: 10e9,
   385  	SampleType: []*ValueType{
   386  		{Type: "samples", Unit: "count"},
   387  		{Type: "cpu", Unit: "milliseconds"},
   388  	},
   389  	Sample: []*Sample{
   390  		{
   391  			Location: []*Location{cpuL[0]},
   392  			Value:    []int64{1000, 1000},
   393  			Label: map[string][]string{
   394  				"key1": {"tag1"},
   395  				"key2": {"tag1"},
   396  			},
   397  		},
   398  		{
   399  			Location: []*Location{cpuL[1], cpuL[0]},
   400  			Value:    []int64{100, 100},
   401  			Label: map[string][]string{
   402  				"key1": {"tag2"},
   403  				"key3": {"tag2"},
   404  			},
   405  		},
   406  		{
   407  			Location: []*Location{cpuL[2], cpuL[0]},
   408  			Value:    []int64{10, 10},
   409  			Label: map[string][]string{
   410  				"key1": {"tag3"},
   411  				"key2": {"tag2"},
   412  			},
   413  		},
   414  		{
   415  			Location: []*Location{cpuL[3], cpuL[0]},
   416  			Value:    []int64{10000, 10000},
   417  			Label: map[string][]string{
   418  				"key1": {"tag4"},
   419  				"key2": {"tag1"},
   420  			},
   421  		},
   422  		{
   423  			Location: []*Location{cpuL[4], cpuL[0]},
   424  			Value:    []int64{1, 1},
   425  			Label: map[string][]string{
   426  				"key1": {"tag4"},
   427  				"key2": {"tag1"},
   428  			},
   429  		},
   430  	},
   431  	Location: cpuL,
   432  	Function: cpuF,
   433  }
   434  
   435  var testProfile2 = &Profile{
   436  	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
   437  	Period:        1,
   438  	DurationNanos: 10e9,
   439  	SampleType: []*ValueType{
   440  		{Type: "samples", Unit: "count"},
   441  		{Type: "cpu", Unit: "milliseconds"},
   442  	},
   443  	Sample: []*Sample{
   444  		{
   445  			Location: []*Location{cpuL[0]},
   446  			Value:    []int64{70, 1000},
   447  			Label: map[string][]string{
   448  				"key1": {"tag1"},
   449  				"key2": {"tag1"},
   450  			},
   451  		},
   452  		{
   453  			Location: []*Location{cpuL[1], cpuL[0]},
   454  			Value:    []int64{60, 100},
   455  			Label: map[string][]string{
   456  				"key1": {"tag2"},
   457  				"key3": {"tag2"},
   458  			},
   459  		},
   460  		{
   461  			Location: []*Location{cpuL[2], cpuL[0]},
   462  			Value:    []int64{50, 10},
   463  			Label: map[string][]string{
   464  				"key1": {"tag3"},
   465  				"key2": {"tag2"},
   466  			},
   467  		},
   468  		{
   469  			Location: []*Location{cpuL[3], cpuL[0]},
   470  			Value:    []int64{40, 10000},
   471  			Label: map[string][]string{
   472  				"key1": {"tag4"},
   473  				"key2": {"tag1"},
   474  			},
   475  		},
   476  		{
   477  			Location: []*Location{cpuL[4], cpuL[0]},
   478  			Value:    []int64{1, 1},
   479  			Label: map[string][]string{
   480  				"key1": {"tag4"},
   481  				"key2": {"tag1"},
   482  			},
   483  		},
   484  	},
   485  	Location: cpuL,
   486  	Function: cpuF,
   487  	Mapping:  cpuM,
   488  }
   489  
   490  var testProfile3 = &Profile{
   491  	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
   492  	Period:        1,
   493  	DurationNanos: 10e9,
   494  	SampleType: []*ValueType{
   495  		{Type: "samples", Unit: "count"},
   496  	},
   497  	Sample: []*Sample{
   498  		{
   499  			Location: []*Location{cpuL[0]},
   500  			Value:    []int64{1000},
   501  			Label: map[string][]string{
   502  				"key1": {"tag1"},
   503  				"key2": {"tag1"},
   504  			},
   505  		},
   506  	},
   507  	Location: cpuL,
   508  	Function: cpuF,
   509  	Mapping:  cpuM,
   510  }
   511  
   512  var testProfile4 = &Profile{
   513  	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
   514  	Period:        1,
   515  	DurationNanos: 10e9,
   516  	SampleType: []*ValueType{
   517  		{Type: "samples", Unit: "count"},
   518  	},
   519  	Sample: []*Sample{
   520  		{
   521  			Location: []*Location{cpuL[0]},
   522  			Value:    []int64{1000},
   523  			NumLabel: map[string][]int64{
   524  				"key1": {10},
   525  				"key2": {30},
   526  			},
   527  			NumUnit: map[string][]string{
   528  				"key1": {"bytes"},
   529  				"key2": {"bytes"},
   530  			},
   531  		},
   532  	},
   533  	Location: cpuL,
   534  	Function: cpuF,
   535  	Mapping:  cpuM,
   536  }
   537  
   538  var testProfile5 = &Profile{
   539  	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
   540  	Period:        1,
   541  	DurationNanos: 10e9,
   542  	SampleType: []*ValueType{
   543  		{Type: "samples", Unit: "count"},
   544  	},
   545  	Sample: []*Sample{
   546  		{
   547  			Location: []*Location{cpuL[0]},
   548  			Value:    []int64{1000},
   549  			NumLabel: map[string][]int64{
   550  				"key1": {10},
   551  				"key2": {30},
   552  			},
   553  			NumUnit: map[string][]string{
   554  				"key1": {"bytes"},
   555  				"key2": {"bytes"},
   556  			},
   557  		},
   558  		{
   559  			Location: []*Location{cpuL[0]},
   560  			Value:    []int64{1000},
   561  			NumLabel: map[string][]int64{
   562  				"key1": {10},
   563  				"key2": {30},
   564  			},
   565  			NumUnit: map[string][]string{
   566  				"key1": {"kilobytes"},
   567  				"key2": {"kilobytes"},
   568  			},
   569  		},
   570  	},
   571  	Location: cpuL,
   572  	Function: cpuF,
   573  	Mapping:  cpuM,
   574  }
   575  
   576  var testProfile6 = &Profile{
   577  	TimeNanos:     10000,
   578  	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
   579  	Period:        1,
   580  	DurationNanos: 10e9,
   581  	SampleType: []*ValueType{
   582  		{Type: "samples", Unit: "count"},
   583  		{Type: "cpu", Unit: "milliseconds"},
   584  	},
   585  	Sample: []*Sample{
   586  		{
   587  			Location: []*Location{cpuL[0]},
   588  			Value:    []int64{1000, 1000},
   589  			Label: map[string][]string{
   590  				"key1": {"tag1"},
   591  				"key2": {"tag1"},
   592  			},
   593  		},
   594  		{
   595  			Location: []*Location{cpuL[1], cpuL[0]},
   596  			Value:    []int64{100, 100},
   597  			Label: map[string][]string{
   598  				"key1": {"tag2"},
   599  				"key3": {"tag2"},
   600  			},
   601  		},
   602  		{
   603  			Location: []*Location{cpuL[2], cpuL[0]},
   604  			Value:    []int64{10, 10},
   605  			Label: map[string][]string{
   606  				"key1": {"tag3"},
   607  				"key2": {"tag2"},
   608  			},
   609  		},
   610  		{
   611  			Location: []*Location{cpuL[3], cpuL[0]},
   612  			Value:    []int64{10000, 10000},
   613  			Label: map[string][]string{
   614  				"key1": {"tag4"},
   615  				"key2": {"tag1"},
   616  			},
   617  		},
   618  		{
   619  			Location: []*Location{cpuL[4], cpuL[0]},
   620  			Value:    []int64{1, 1},
   621  			Label: map[string][]string{
   622  				"key1": {"tag4"},
   623  				"key2": {"tag1"},
   624  			},
   625  		},
   626  		{
   627  			Location: []*Location{cpuL[5]},
   628  			Value:    []int64{1, 1},
   629  			Label: map[string][]string{
   630  				"key1": {"tag5"},
   631  				"key2": {"tag1"},
   632  			},
   633  		},
   634  	},
   635  	Location: cpuL,
   636  	Function: cpuF,
   637  	Mapping:  cpuM,
   638  }
   639  
   640  var aggTests = map[string]aggTest{
   641  	"precise":         {true, true, true, true, true, 6},
   642  	"columns":         {false, true, true, true, true, 5},
   643  	"fileline":        {false, true, true, false, true, 4},
   644  	"inline_function": {false, true, false, false, true, 3},
   645  	"function":        {false, true, false, false, false, 2},
   646  }
   647  
   648  type aggTest struct {
   649  	precise, function, fileline, column, inlineFrame bool
   650  	rows                                             int
   651  }
   652  
   653  // totalSamples is the sum of sample.Value[0] for testProfile6.
   654  const totalSamples = int64(11112)
   655  
   656  func TestAggregation(t *testing.T) {
   657  	prof := testProfile6.Copy()
   658  	for _, resolution := range []string{"precise", "columns", "fileline", "inline_function", "function"} {
   659  		a := aggTests[resolution]
   660  		if !a.precise {
   661  			if err := prof.Aggregate(a.inlineFrame, a.function, a.fileline, a.fileline, a.column, false); err != nil {
   662  				t.Error("aggregating to " + resolution + ":" + err.Error())
   663  			}
   664  		}
   665  		if err := checkAggregation(prof, &a); err != nil {
   666  			t.Error("failed aggregation to " + resolution + ": " + err.Error())
   667  		}
   668  	}
   669  }
   670  
   671  // checkAggregation verifies that the profile remained consistent
   672  // with its aggregation.
   673  func checkAggregation(prof *Profile, a *aggTest) error {
   674  	// Check that the total number of samples for the rows was preserved.
   675  	total := int64(0)
   676  
   677  	samples := make(map[string]bool)
   678  	for _, sample := range prof.Sample {
   679  		tb := locationHash(sample)
   680  		samples[tb] = true
   681  		total += sample.Value[0]
   682  	}
   683  
   684  	if total != totalSamples {
   685  		return fmt.Errorf("sample total %d, want %d", total, totalSamples)
   686  	}
   687  
   688  	// Check the number of unique sample locations
   689  	if a.rows != len(samples) {
   690  		return fmt.Errorf("number of samples %d, want %d", len(samples), a.rows)
   691  	}
   692  
   693  	// Check that all mappings have the right detail flags.
   694  	for _, m := range prof.Mapping {
   695  		if m.HasFunctions != a.function {
   696  			return fmt.Errorf("unexpected mapping.HasFunctions %v, want %v", m.HasFunctions, a.function)
   697  		}
   698  		if m.HasFilenames != a.fileline {
   699  			return fmt.Errorf("unexpected mapping.HasFilenames %v, want %v", m.HasFilenames, a.fileline)
   700  		}
   701  		if m.HasLineNumbers != a.fileline {
   702  			return fmt.Errorf("unexpected mapping.HasLineNumbers %v, want %v", m.HasLineNumbers, a.fileline)
   703  		}
   704  		if m.HasInlineFrames != a.inlineFrame {
   705  			return fmt.Errorf("unexpected mapping.HasInlineFrames %v, want %v", m.HasInlineFrames, a.inlineFrame)
   706  		}
   707  	}
   708  
   709  	// Check that aggregation has removed finer resolution data.
   710  	for _, l := range prof.Location {
   711  		if !a.inlineFrame && len(l.Line) > 1 {
   712  			return fmt.Errorf("found %d lines on location %d, want 1", len(l.Line), l.ID)
   713  		}
   714  
   715  		for _, ln := range l.Line {
   716  			if !a.column && ln.Column != 0 {
   717  				return fmt.Errorf("found column %d on location %d, want:0", ln.Column, l.ID)
   718  			}
   719  			if !a.fileline && (ln.Function.Filename != "" || ln.Line != 0) {
   720  				return fmt.Errorf("found line %s:%d on location %d, want :0",
   721  					ln.Function.Filename, ln.Line, l.ID)
   722  			}
   723  			if !a.function && (ln.Function.Name != "") {
   724  				return fmt.Errorf(`found file %s location %d, want ""`,
   725  					ln.Function.Name, l.ID)
   726  			}
   727  		}
   728  	}
   729  
   730  	return nil
   731  }
   732  
   733  // TestScale tests that Scale() rounds values and drops samples
   734  // as expected.
   735  func TestScale(t *testing.T) {
   736  	for _, tc := range []struct {
   737  		desc        string
   738  		ratio       float64
   739  		p           *Profile
   740  		wantSamples [][]int64
   741  	}{
   742  		{
   743  			desc:  "scale by 1",
   744  			ratio: 1.0,
   745  			p:     testProfile1.Copy(),
   746  			wantSamples: [][]int64{
   747  				{1000, 1000},
   748  				{100, 100},
   749  				{10, 10},
   750  				{10000, 10000},
   751  				{1, 1},
   752  			},
   753  		},
   754  		{
   755  			desc:  "sample values will be rounded up",
   756  			ratio: .66666,
   757  			p:     testProfile1.Copy(),
   758  			wantSamples: [][]int64{
   759  				{667, 667},
   760  				{67, 67},
   761  				{7, 7},
   762  				{6667, 6667},
   763  				{1, 1},
   764  			},
   765  		},
   766  		{
   767  			desc:  "sample values will be rounded down",
   768  			ratio: .33333,
   769  			p:     testProfile1.Copy(),
   770  			wantSamples: [][]int64{
   771  				{333, 333},
   772  				{33, 33},
   773  				{3, 3},
   774  				{3333, 3333},
   775  			},
   776  		},
   777  		{
   778  			desc:        "all sample values will be dropped",
   779  			ratio:       0.00001,
   780  			p:           testProfile1.Copy(),
   781  			wantSamples: [][]int64{},
   782  		},
   783  	} {
   784  		t.Run(tc.desc, func(t *testing.T) {
   785  			tc.p.Scale(tc.ratio)
   786  			if got, want := len(tc.p.Sample), len(tc.wantSamples); got != want {
   787  				t.Fatalf("got %d samples, want %d", got, want)
   788  			}
   789  			for i, s := range tc.p.Sample {
   790  				for j, got := range s.Value {
   791  					want := tc.wantSamples[i][j]
   792  					if want != got {
   793  						t.Errorf("For value %d of sample %d, got %d want %d", j, i, got, want)
   794  					}
   795  				}
   796  			}
   797  		})
   798  	}
   799  }
   800  
   801  // TestMergeMain tests merge leaves the main binary in place.
   802  func TestMergeMain(t *testing.T) {
   803  	prof := testProfile1.Copy()
   804  	p1, err := Merge([]*Profile{prof})
   805  	if err != nil {
   806  		t.Fatalf("merge error: %v", err)
   807  	}
   808  	if cpuM[0].File != p1.Mapping[0].File {
   809  		t.Errorf("want Mapping[0]=%s got %s", cpuM[0].File, p1.Mapping[0].File)
   810  	}
   811  }
   812  
   813  func TestMerge(t *testing.T) {
   814  	// Aggregate a profile with itself and once again with a factor of
   815  	// -2. Should end up with an empty profile (all samples for a
   816  	// location should add up to 0).
   817  
   818  	prof := testProfile1.Copy()
   819  	prof.Comments = []string{"comment1"}
   820  	p1, err := Merge([]*Profile{prof, prof})
   821  	if err != nil {
   822  		t.Errorf("merge error: %v", err)
   823  	}
   824  	prof.Scale(-2)
   825  	prof, err = Merge([]*Profile{p1, prof})
   826  	if err != nil {
   827  		t.Errorf("merge error: %v", err)
   828  	}
   829  	if got, want := len(prof.Comments), 1; got != want {
   830  		t.Errorf("len(prof.Comments) = %d, want %d", got, want)
   831  	}
   832  
   833  	// Use aggregation to merge locations at function granularity.
   834  	if err := prof.Aggregate(false, true, false, false, false, false); err != nil {
   835  		t.Errorf("aggregating after merge: %v", err)
   836  	}
   837  
   838  	samples := make(map[string]int64)
   839  	for _, s := range prof.Sample {
   840  		tb := locationHash(s)
   841  		samples[tb] = samples[tb] + s.Value[0]
   842  	}
   843  	for s, v := range samples {
   844  		if v != 0 {
   845  			t.Errorf("nonzero value for sample %s: %d", s, v)
   846  		}
   847  	}
   848  }
   849  
   850  func TestMergeAll(t *testing.T) {
   851  	// Aggregate 10 copies of the profile.
   852  	profs := make([]*Profile, 10)
   853  	for i := 0; i < 10; i++ {
   854  		profs[i] = testProfile1.Copy()
   855  	}
   856  	prof, err := Merge(profs)
   857  	if err != nil {
   858  		t.Errorf("merge error: %v", err)
   859  	}
   860  	samples := make(map[string]int64)
   861  	for _, s := range prof.Sample {
   862  		tb := locationHash(s)
   863  		samples[tb] = samples[tb] + s.Value[0]
   864  	}
   865  	for _, s := range testProfile1.Sample {
   866  		tb := locationHash(s)
   867  		if samples[tb] != s.Value[0]*10 {
   868  			t.Errorf("merge got wrong value at %s : %d instead of %d", tb, samples[tb], s.Value[0]*10)
   869  		}
   870  	}
   871  }
   872  
   873  func TestIsFoldedMerge(t *testing.T) {
   874  	testProfile1Folded := testProfile1.Copy()
   875  	testProfile1Folded.Location[0].IsFolded = true
   876  	testProfile1Folded.Location[1].IsFolded = true
   877  
   878  	for _, tc := range []struct {
   879  		name            string
   880  		profs           []*Profile
   881  		wantLocationLen int
   882  	}{
   883  		{
   884  			name:            "folded and non-folded locations not merged",
   885  			profs:           []*Profile{testProfile1.Copy(), testProfile1Folded.Copy()},
   886  			wantLocationLen: 7,
   887  		},
   888  		{
   889  			name:            "identical folded locations are merged",
   890  			profs:           []*Profile{testProfile1Folded.Copy(), testProfile1Folded.Copy()},
   891  			wantLocationLen: 5,
   892  		},
   893  	} {
   894  		t.Run(tc.name, func(t *testing.T) {
   895  			prof, err := Merge(tc.profs)
   896  			if err != nil {
   897  				t.Fatalf("merge error: %v", err)
   898  			}
   899  			if got, want := len(prof.Location), tc.wantLocationLen; got != want {
   900  				t.Fatalf("got %d locations, want %d locations", got, want)
   901  			}
   902  		})
   903  	}
   904  }
   905  
   906  func TestNumLabelMerge(t *testing.T) {
   907  	for _, tc := range []struct {
   908  		name          string
   909  		profs         []*Profile
   910  		wantNumLabels []map[string][]int64
   911  		wantNumUnits  []map[string][]string
   912  	}{
   913  		{
   914  			name:  "different label units not merged",
   915  			profs: []*Profile{testProfile4.Copy(), testProfile5.Copy()},
   916  			wantNumLabels: []map[string][]int64{
   917  				{
   918  					"key1": {10},
   919  					"key2": {30},
   920  				},
   921  				{
   922  					"key1": {10},
   923  					"key2": {30},
   924  				},
   925  			},
   926  			wantNumUnits: []map[string][]string{
   927  				{
   928  					"key1": {"bytes"},
   929  					"key2": {"bytes"},
   930  				},
   931  				{
   932  					"key1": {"kilobytes"},
   933  					"key2": {"kilobytes"},
   934  				},
   935  			},
   936  		},
   937  	} {
   938  		t.Run(tc.name, func(t *testing.T) {
   939  			prof, err := Merge(tc.profs)
   940  			if err != nil {
   941  				t.Errorf("merge error: %v", err)
   942  			}
   943  
   944  			if want, got := len(tc.wantNumLabels), len(prof.Sample); want != got {
   945  				t.Fatalf("got %d samples, want %d samples", got, want)
   946  			}
   947  			for i, wantLabels := range tc.wantNumLabels {
   948  				numLabels := prof.Sample[i].NumLabel
   949  				if !reflect.DeepEqual(wantLabels, numLabels) {
   950  					t.Errorf("got numeric labels %v, want %v", numLabels, wantLabels)
   951  				}
   952  
   953  				wantUnits := tc.wantNumUnits[i]
   954  				numUnits := prof.Sample[i].NumUnit
   955  				if !reflect.DeepEqual(wantUnits, numUnits) {
   956  					t.Errorf("got numeric labels %v, want %v", numUnits, wantUnits)
   957  				}
   958  			}
   959  		})
   960  	}
   961  }
   962  
   963  func TestEmptyMappingMerge(t *testing.T) {
   964  	// Aggregate a profile with itself and once again with a factor of
   965  	// -2. Should end up with an empty profile (all samples for a
   966  	// location should add up to 0).
   967  
   968  	prof1 := testProfile1.Copy()
   969  	prof2 := testProfile1NoMapping.Copy()
   970  	p1, err := Merge([]*Profile{prof2, prof1})
   971  	if err != nil {
   972  		t.Errorf("merge error: %v", err)
   973  	}
   974  	prof2.Scale(-2)
   975  	prof, err := Merge([]*Profile{p1, prof2})
   976  	if err != nil {
   977  		t.Errorf("merge error: %v", err)
   978  	}
   979  
   980  	// Use aggregation to merge locations at function granularity.
   981  	if err := prof.Aggregate(false, true, false, false, false, false); err != nil {
   982  		t.Errorf("aggregating after merge: %v", err)
   983  	}
   984  
   985  	samples := make(map[string]int64)
   986  	for _, s := range prof.Sample {
   987  		tb := locationHash(s)
   988  		samples[tb] = samples[tb] + s.Value[0]
   989  	}
   990  	for s, v := range samples {
   991  		if v != 0 {
   992  			t.Errorf("nonzero value for sample %s: %d", s, v)
   993  		}
   994  	}
   995  }
   996  
   997  func TestNormalizeBySameProfile(t *testing.T) {
   998  	pb := testProfile1.Copy()
   999  	p := testProfile1.Copy()
  1000  
  1001  	if err := p.Normalize(pb); err != nil {
  1002  		t.Fatal(err)
  1003  	}
  1004  
  1005  	for i, s := range p.Sample {
  1006  		for j, v := range s.Value {
  1007  			expectedSampleValue := testProfile1.Sample[i].Value[j]
  1008  			if v != expectedSampleValue {
  1009  				t.Errorf("For sample %d, value %d want %d got %d", i, j, expectedSampleValue, v)
  1010  			}
  1011  		}
  1012  	}
  1013  }
  1014  
  1015  func TestNormalizeByDifferentProfile(t *testing.T) {
  1016  	p := testProfile1.Copy()
  1017  	pb := testProfile2.Copy()
  1018  
  1019  	if err := p.Normalize(pb); err != nil {
  1020  		t.Fatal(err)
  1021  	}
  1022  
  1023  	expectedSampleValues := [][]int64{
  1024  		{20, 1000},
  1025  		{2, 100},
  1026  		{199, 10000},
  1027  		{0, 1},
  1028  	}
  1029  
  1030  	for i, s := range p.Sample {
  1031  		for j, v := range s.Value {
  1032  			if v != expectedSampleValues[i][j] {
  1033  				t.Errorf("For sample %d, value %d want %d got %d", i, j, expectedSampleValues[i][j], v)
  1034  			}
  1035  		}
  1036  	}
  1037  }
  1038  
  1039  func TestNormalizeByMultipleOfSameProfile(t *testing.T) {
  1040  	pb := testProfile1.Copy()
  1041  	for i, s := range pb.Sample {
  1042  		for j, v := range s.Value {
  1043  			pb.Sample[i].Value[j] = 10 * v
  1044  		}
  1045  	}
  1046  
  1047  	p := testProfile1.Copy()
  1048  
  1049  	err := p.Normalize(pb)
  1050  	if err != nil {
  1051  		t.Fatal(err)
  1052  	}
  1053  
  1054  	for i, s := range p.Sample {
  1055  		for j, v := range s.Value {
  1056  			expectedSampleValue := 10 * testProfile1.Sample[i].Value[j]
  1057  			if v != expectedSampleValue {
  1058  				t.Errorf("For sample %d, value %d, want %d got %d", i, j, expectedSampleValue, v)
  1059  			}
  1060  		}
  1061  	}
  1062  }
  1063  
  1064  func TestNormalizeIncompatibleProfiles(t *testing.T) {
  1065  	p := testProfile1.Copy()
  1066  	pb := testProfile3.Copy()
  1067  
  1068  	if err := p.Normalize(pb); err == nil {
  1069  		t.Errorf("Expected an error")
  1070  	}
  1071  }
  1072  
  1073  // locationHash constructs a string to use as a hashkey for a sample, based on its locations
  1074  func locationHash(s *Sample) string {
  1075  	var tb string
  1076  	for _, l := range s.Location {
  1077  		for _, ln := range l.Line {
  1078  			tb = tb + fmt.Sprintf("%s:%d:%d@%d ", ln.Function.Name, ln.Line, ln.Column, l.Address)
  1079  		}
  1080  	}
  1081  	return tb
  1082  }
  1083  
  1084  func TestHasLabel(t *testing.T) {
  1085  	var testcases = []struct {
  1086  		desc         string
  1087  		labels       map[string][]string
  1088  		key          string
  1089  		value        string
  1090  		wantHasLabel bool
  1091  	}{
  1092  		{
  1093  			desc:         "empty label does not have label",
  1094  			labels:       map[string][]string{},
  1095  			key:          "key",
  1096  			value:        "value",
  1097  			wantHasLabel: false,
  1098  		},
  1099  		{
  1100  			desc:         "label with one key and value has label",
  1101  			labels:       map[string][]string{"key": {"value"}},
  1102  			key:          "key",
  1103  			value:        "value",
  1104  			wantHasLabel: true,
  1105  		},
  1106  		{
  1107  			desc:         "label with one key and value does not have label",
  1108  			labels:       map[string][]string{"key": {"value"}},
  1109  			key:          "key1",
  1110  			value:        "value1",
  1111  			wantHasLabel: false,
  1112  		},
  1113  		{
  1114  			desc: "label with many keys and values has label",
  1115  			labels: map[string][]string{
  1116  				"key1": {"value2", "value1"},
  1117  				"key2": {"value1", "value2", "value2"},
  1118  				"key3": {"value1", "value2", "value2"},
  1119  			},
  1120  			key:          "key1",
  1121  			value:        "value1",
  1122  			wantHasLabel: true,
  1123  		},
  1124  		{
  1125  			desc: "label with many keys and values does not have label",
  1126  			labels: map[string][]string{
  1127  				"key1": {"value2", "value1"},
  1128  				"key2": {"value1", "value2", "value2"},
  1129  				"key3": {"value1", "value2", "value2"},
  1130  			},
  1131  			key:          "key5",
  1132  			value:        "value5",
  1133  			wantHasLabel: false,
  1134  		},
  1135  	}
  1136  
  1137  	for _, tc := range testcases {
  1138  		t.Run(tc.desc, func(t *testing.T) {
  1139  			sample := &Sample{
  1140  				Label: tc.labels,
  1141  			}
  1142  			if gotHasLabel := sample.HasLabel(tc.key, tc.value); gotHasLabel != tc.wantHasLabel {
  1143  				t.Errorf("sample.HasLabel(%q, %q) got %v, want %v", tc.key, tc.value, gotHasLabel, tc.wantHasLabel)
  1144  			}
  1145  		})
  1146  	}
  1147  }
  1148  
  1149  func TestDiffBaseSample(t *testing.T) {
  1150  	var testcases = []struct {
  1151  		desc               string
  1152  		labels             map[string][]string
  1153  		wantDiffBaseSample bool
  1154  	}{
  1155  		{
  1156  			desc:               "empty label does not have label",
  1157  			labels:             map[string][]string{},
  1158  			wantDiffBaseSample: false,
  1159  		},
  1160  		{
  1161  			desc:               "label with one key and value, including diff base label",
  1162  			labels:             map[string][]string{"pprof::base": {"true"}},
  1163  			wantDiffBaseSample: true,
  1164  		},
  1165  		{
  1166  			desc:               "label with one key and value, not including diff base label",
  1167  			labels:             map[string][]string{"key": {"value"}},
  1168  			wantDiffBaseSample: false,
  1169  		},
  1170  		{
  1171  			desc: "label with many keys and values, including diff base label",
  1172  			labels: map[string][]string{
  1173  				"pprof::base": {"value2", "true"},
  1174  				"key2":        {"true", "value2", "value2"},
  1175  				"key3":        {"true", "value2", "value2"},
  1176  			},
  1177  			wantDiffBaseSample: true,
  1178  		},
  1179  		{
  1180  			desc: "label with many keys and values, not including diff base label",
  1181  			labels: map[string][]string{
  1182  				"key1": {"value2", "value1"},
  1183  				"key2": {"value1", "value2", "value2"},
  1184  				"key3": {"value1", "value2", "value2"},
  1185  			},
  1186  			wantDiffBaseSample: false,
  1187  		},
  1188  	}
  1189  
  1190  	for _, tc := range testcases {
  1191  		t.Run(tc.desc, func(t *testing.T) {
  1192  			sample := &Sample{
  1193  				Label: tc.labels,
  1194  			}
  1195  			if gotHasLabel := sample.DiffBaseSample(); gotHasLabel != tc.wantDiffBaseSample {
  1196  				t.Errorf("sample.DiffBaseSample() got %v, want %v", gotHasLabel, tc.wantDiffBaseSample)
  1197  			}
  1198  		})
  1199  	}
  1200  }
  1201  
  1202  func TestRemove(t *testing.T) {
  1203  	var testcases = []struct {
  1204  		desc       string
  1205  		samples    []*Sample
  1206  		removeKey  string
  1207  		wantLabels []map[string][]string
  1208  	}{
  1209  		{
  1210  			desc: "some samples have label already",
  1211  			samples: []*Sample{
  1212  				{
  1213  					Location: []*Location{cpuL[0]},
  1214  					Value:    []int64{1000},
  1215  				},
  1216  				{
  1217  					Location: []*Location{cpuL[0]},
  1218  					Value:    []int64{1000},
  1219  					Label: map[string][]string{
  1220  						"key1": {"value1", "value2", "value3"},
  1221  						"key2": {"value1"},
  1222  					},
  1223  				},
  1224  				{
  1225  					Location: []*Location{cpuL[0]},
  1226  					Value:    []int64{1000},
  1227  					Label: map[string][]string{
  1228  						"key1": {"value2"},
  1229  					},
  1230  				},
  1231  			},
  1232  			removeKey: "key1",
  1233  			wantLabels: []map[string][]string{
  1234  				{},
  1235  				{"key2": {"value1"}},
  1236  				{},
  1237  			},
  1238  		},
  1239  	}
  1240  
  1241  	for _, tc := range testcases {
  1242  		t.Run(tc.desc, func(t *testing.T) {
  1243  			profile := testProfile1.Copy()
  1244  			profile.Sample = tc.samples
  1245  			profile.RemoveLabel(tc.removeKey)
  1246  			if got, want := len(profile.Sample), len(tc.wantLabels); got != want {
  1247  				t.Fatalf("got %v samples, want %v samples", got, want)
  1248  			}
  1249  			for i, sample := range profile.Sample {
  1250  				wantLabels := tc.wantLabels[i]
  1251  				if got, want := len(sample.Label), len(wantLabels); got != want {
  1252  					t.Errorf("got %v label keys for sample %v, want %v", got, i, want)
  1253  					continue
  1254  				}
  1255  				for wantKey, wantValues := range wantLabels {
  1256  					if gotValues, ok := sample.Label[wantKey]; ok {
  1257  						if !reflect.DeepEqual(gotValues, wantValues) {
  1258  							t.Errorf("for key %s, got values %v, want values %v", wantKey, gotValues, wantValues)
  1259  						}
  1260  					} else {
  1261  						t.Errorf("for key %s got no values, want %v", wantKey, wantValues)
  1262  					}
  1263  				}
  1264  			}
  1265  		})
  1266  	}
  1267  }
  1268  
  1269  func TestSetLabel(t *testing.T) {
  1270  	var testcases = []struct {
  1271  		desc       string
  1272  		samples    []*Sample
  1273  		setKey     string
  1274  		setVal     []string
  1275  		wantLabels []map[string][]string
  1276  	}{
  1277  		{
  1278  			desc: "some samples have label already",
  1279  			samples: []*Sample{
  1280  				{
  1281  					Location: []*Location{cpuL[0]},
  1282  					Value:    []int64{1000},
  1283  				},
  1284  				{
  1285  					Location: []*Location{cpuL[0]},
  1286  					Value:    []int64{1000},
  1287  					Label: map[string][]string{
  1288  						"key1": {"value1", "value2", "value3"},
  1289  						"key2": {"value1"},
  1290  					},
  1291  				},
  1292  				{
  1293  					Location: []*Location{cpuL[0]},
  1294  					Value:    []int64{1000},
  1295  					Label: map[string][]string{
  1296  						"key1": {"value2"},
  1297  					},
  1298  				},
  1299  			},
  1300  			setKey: "key1",
  1301  			setVal: []string{"value1"},
  1302  			wantLabels: []map[string][]string{
  1303  				{"key1": {"value1"}},
  1304  				{"key1": {"value1"}, "key2": {"value1"}},
  1305  				{"key1": {"value1"}},
  1306  			},
  1307  		},
  1308  		{
  1309  			desc: "no samples have labels",
  1310  			samples: []*Sample{
  1311  				{
  1312  					Location: []*Location{cpuL[0]},
  1313  					Value:    []int64{1000},
  1314  				},
  1315  			},
  1316  			setKey: "key1",
  1317  			setVal: []string{"value1"},
  1318  			wantLabels: []map[string][]string{
  1319  				{"key1": {"value1"}},
  1320  			},
  1321  		},
  1322  		{
  1323  			desc: "all samples have some labels, but not key being added",
  1324  			samples: []*Sample{
  1325  				{
  1326  					Location: []*Location{cpuL[0]},
  1327  					Value:    []int64{1000},
  1328  					Label: map[string][]string{
  1329  						"key2": {"value2"},
  1330  					},
  1331  				},
  1332  				{
  1333  					Location: []*Location{cpuL[0]},
  1334  					Value:    []int64{1000},
  1335  					Label: map[string][]string{
  1336  						"key3": {"value3"},
  1337  					},
  1338  				},
  1339  			},
  1340  			setKey: "key1",
  1341  			setVal: []string{"value1"},
  1342  			wantLabels: []map[string][]string{
  1343  				{"key1": {"value1"}, "key2": {"value2"}},
  1344  				{"key1": {"value1"}, "key3": {"value3"}},
  1345  			},
  1346  		},
  1347  		{
  1348  			desc: "all samples have key being added",
  1349  			samples: []*Sample{
  1350  				{
  1351  					Location: []*Location{cpuL[0]},
  1352  					Value:    []int64{1000},
  1353  					Label: map[string][]string{
  1354  						"key1": {"value1"},
  1355  					},
  1356  				},
  1357  				{
  1358  					Location: []*Location{cpuL[0]},
  1359  					Value:    []int64{1000},
  1360  					Label: map[string][]string{
  1361  						"key1": {"value1"},
  1362  					},
  1363  				},
  1364  			},
  1365  			setKey: "key1",
  1366  			setVal: []string{"value1"},
  1367  			wantLabels: []map[string][]string{
  1368  				{"key1": {"value1"}},
  1369  				{"key1": {"value1"}},
  1370  			},
  1371  		},
  1372  	}
  1373  
  1374  	for _, tc := range testcases {
  1375  		t.Run(tc.desc, func(t *testing.T) {
  1376  			profile := testProfile1.Copy()
  1377  			profile.Sample = tc.samples
  1378  			profile.SetLabel(tc.setKey, tc.setVal)
  1379  			if got, want := len(profile.Sample), len(tc.wantLabels); got != want {
  1380  				t.Fatalf("got %v samples, want %v samples", got, want)
  1381  			}
  1382  			for i, sample := range profile.Sample {
  1383  				wantLabels := tc.wantLabels[i]
  1384  				if got, want := len(sample.Label), len(wantLabels); got != want {
  1385  					t.Errorf("got %v label keys for sample %v, want %v", got, i, want)
  1386  					continue
  1387  				}
  1388  				for wantKey, wantValues := range wantLabels {
  1389  					if gotValues, ok := sample.Label[wantKey]; ok {
  1390  						if !reflect.DeepEqual(gotValues, wantValues) {
  1391  							t.Errorf("for key %s, got values %v, want values %v", wantKey, gotValues, wantValues)
  1392  						}
  1393  					} else {
  1394  						t.Errorf("for key %s got no values, want %v", wantKey, wantValues)
  1395  					}
  1396  				}
  1397  			}
  1398  		})
  1399  	}
  1400  }
  1401  
  1402  func TestSetNumLabel(t *testing.T) {
  1403  	var testcases = []struct {
  1404  		desc       string
  1405  		samples    []*Sample
  1406  		setKey     string
  1407  		setVal     []int64
  1408  		setUnit    []string
  1409  		wantValues []map[string][]int64
  1410  		wantUnits  []map[string][]string
  1411  	}{
  1412  		{
  1413  			desc: "some samples have label already",
  1414  			samples: []*Sample{
  1415  				{
  1416  					Location: []*Location{cpuL[0]},
  1417  					Value:    []int64{1000},
  1418  				},
  1419  				{
  1420  					Location: []*Location{cpuL[0]},
  1421  					Value:    []int64{1000},
  1422  					NumLabel: map[string][]int64{
  1423  						"key1": {1, 2, 3},
  1424  						"key2": {1},
  1425  					},
  1426  					NumUnit: map[string][]string{
  1427  						"key1": {"bytes", "bytes", "bytes"},
  1428  						"key2": {"gallons"},
  1429  					},
  1430  				},
  1431  				{
  1432  					Location: []*Location{cpuL[0]},
  1433  					Value:    []int64{1000},
  1434  					NumLabel: map[string][]int64{
  1435  						"key1": {2},
  1436  					},
  1437  					NumUnit: map[string][]string{
  1438  						"key1": {"volts"},
  1439  					},
  1440  				},
  1441  			},
  1442  			setKey:  "key1",
  1443  			setVal:  []int64{1},
  1444  			setUnit: []string{"bytes"},
  1445  			wantValues: []map[string][]int64{
  1446  				{"key1": {1}},
  1447  				{"key1": {1}, "key2": {1}},
  1448  				{"key1": {1}},
  1449  			},
  1450  			wantUnits: []map[string][]string{
  1451  				{"key1": {"bytes"}},
  1452  				{"key1": {"bytes"}, "key2": {"gallons"}},
  1453  				{"key1": {"bytes"}},
  1454  			},
  1455  		},
  1456  		{
  1457  			desc: "no samples have labels",
  1458  			samples: []*Sample{
  1459  				{
  1460  					Location: []*Location{cpuL[0]},
  1461  					Value:    []int64{1000},
  1462  				},
  1463  			},
  1464  			setKey:  "key1",
  1465  			setVal:  []int64{1},
  1466  			setUnit: []string{"bytes"},
  1467  			wantValues: []map[string][]int64{
  1468  				{"key1": {1}},
  1469  			},
  1470  			wantUnits: []map[string][]string{
  1471  				{"key1": {"bytes"}},
  1472  			},
  1473  		},
  1474  		{
  1475  			desc: "all samples have some labels, but not key being added",
  1476  			samples: []*Sample{
  1477  				{
  1478  					Location: []*Location{cpuL[0]},
  1479  					Value:    []int64{1000},
  1480  					NumLabel: map[string][]int64{
  1481  						"key2": {2},
  1482  					},
  1483  					NumUnit: map[string][]string{
  1484  						"key2": {"joules"},
  1485  					},
  1486  				},
  1487  				{
  1488  					Location: []*Location{cpuL[0]},
  1489  					Value:    []int64{1000},
  1490  					NumLabel: map[string][]int64{
  1491  						"key3": {3},
  1492  					},
  1493  					NumUnit: map[string][]string{
  1494  						"key3": {"meters"},
  1495  					},
  1496  				},
  1497  			},
  1498  			setKey:  "key1",
  1499  			setVal:  []int64{1},
  1500  			setUnit: []string{"seconds"},
  1501  			wantValues: []map[string][]int64{
  1502  				{"key1": {1}, "key2": {2}},
  1503  				{"key1": {1}, "key3": {3}},
  1504  			},
  1505  			wantUnits: []map[string][]string{
  1506  				{"key1": {"seconds"}, "key2": {"joules"}},
  1507  				{"key1": {"seconds"}, "key3": {"meters"}},
  1508  			},
  1509  		},
  1510  		{
  1511  			desc: "all samples have key being added",
  1512  			samples: []*Sample{
  1513  				{
  1514  					Location: []*Location{cpuL[0]},
  1515  					Value:    []int64{1000},
  1516  					NumLabel: map[string][]int64{
  1517  						"key1": {1},
  1518  					},
  1519  					NumUnit: map[string][]string{
  1520  						"key1": {"exabytes"},
  1521  					},
  1522  				},
  1523  				{
  1524  					Location: []*Location{cpuL[0]},
  1525  					Value:    []int64{1000},
  1526  					NumLabel: map[string][]int64{
  1527  						"key1": {1},
  1528  					},
  1529  					NumUnit: map[string][]string{
  1530  						"key1": {"petabytes"},
  1531  					},
  1532  				},
  1533  			},
  1534  			setKey:  "key1",
  1535  			setVal:  []int64{1, 2},
  1536  			setUnit: []string{"daltons", ""},
  1537  			wantValues: []map[string][]int64{
  1538  				{"key1": {1, 2}},
  1539  				{"key1": {1, 2}},
  1540  			},
  1541  			wantUnits: []map[string][]string{
  1542  				{"key1": {"daltons", ""}},
  1543  				{"key1": {"daltons", ""}},
  1544  			},
  1545  		},
  1546  	}
  1547  
  1548  	for _, tc := range testcases {
  1549  		t.Run(tc.desc, func(t *testing.T) {
  1550  			profile := testProfile1.Copy()
  1551  			profile.Sample = tc.samples
  1552  			profile.SetNumLabel(tc.setKey, tc.setVal, tc.setUnit)
  1553  			if got, want := len(profile.Sample), len(tc.wantValues); got != want {
  1554  				t.Fatalf("got %v samples, want %v samples", got, want)
  1555  			}
  1556  			if got, want := len(profile.Sample), len(tc.wantUnits); got != want {
  1557  				t.Fatalf("got %v samples, want %v samples", got, want)
  1558  			}
  1559  			for i, sample := range profile.Sample {
  1560  				wantValues := tc.wantValues[i]
  1561  				if got, want := len(sample.NumLabel), len(wantValues); got != want {
  1562  					t.Errorf("got %v label values for sample %v, want %v", got, i, want)
  1563  					continue
  1564  				}
  1565  				for key, values := range wantValues {
  1566  					if gotValues, ok := sample.NumLabel[key]; ok {
  1567  						if !reflect.DeepEqual(gotValues, values) {
  1568  							t.Errorf("for key %s, got values %v, want values %v", key, gotValues, values)
  1569  						}
  1570  					} else {
  1571  						t.Errorf("for key %s got no values, want %v", key, values)
  1572  					}
  1573  				}
  1574  
  1575  				wantUnits := tc.wantUnits[i]
  1576  				if got, want := len(sample.NumUnit), len(wantUnits); got != want {
  1577  					t.Errorf("got %v label units for sample %v, want %v", got, i, want)
  1578  					continue
  1579  				}
  1580  				for key, units := range wantUnits {
  1581  					if gotUnits, ok := sample.NumUnit[key]; ok {
  1582  						if !reflect.DeepEqual(gotUnits, units) {
  1583  							t.Errorf("for key %s, got units %v, want units %v", key, gotUnits, units)
  1584  						}
  1585  					} else {
  1586  						t.Errorf("for key %s got no units, want %v", key, units)
  1587  					}
  1588  				}
  1589  			}
  1590  		})
  1591  	}
  1592  }
  1593  
  1594  func TestRemoveNumLabel(t *testing.T) {
  1595  	var testcases = []struct {
  1596  		desc       string
  1597  		samples    []*Sample
  1598  		removeKey  string
  1599  		wantValues []map[string][]int64
  1600  		wantUnits  []map[string][]string
  1601  	}{
  1602  		{
  1603  			desc: "some samples have label already",
  1604  			samples: []*Sample{
  1605  				{
  1606  					Location: []*Location{cpuL[0]},
  1607  					Value:    []int64{1000},
  1608  				},
  1609  				{
  1610  					Location: []*Location{cpuL[0]},
  1611  					Value:    []int64{1000},
  1612  					NumLabel: map[string][]int64{
  1613  						"key1": {1, 2, 3},
  1614  						"key2": {1},
  1615  					},
  1616  					NumUnit: map[string][]string{
  1617  						"key1": {"foo", "bar", "baz"},
  1618  						"key2": {"seconds"},
  1619  					},
  1620  				},
  1621  				{
  1622  					Location: []*Location{cpuL[0]},
  1623  					Value:    []int64{1000},
  1624  					NumLabel: map[string][]int64{
  1625  						"key1": {2},
  1626  					},
  1627  					NumUnit: map[string][]string{
  1628  						"key1": {"seconds"},
  1629  					},
  1630  				},
  1631  			},
  1632  			removeKey: "key1",
  1633  			wantValues: []map[string][]int64{
  1634  				{},
  1635  				{"key2": {1}},
  1636  				{},
  1637  			},
  1638  			wantUnits: []map[string][]string{
  1639  				{},
  1640  				{"key2": {"seconds"}},
  1641  				{},
  1642  			},
  1643  		},
  1644  		{
  1645  			desc: "no samples have label",
  1646  			samples: []*Sample{
  1647  				{
  1648  					Location: []*Location{cpuL[0]},
  1649  					Value:    []int64{1000},
  1650  				},
  1651  			},
  1652  			removeKey: "key1",
  1653  			wantValues: []map[string][]int64{
  1654  				{},
  1655  			},
  1656  			wantUnits: []map[string][]string{
  1657  				{},
  1658  			},
  1659  		},
  1660  		{
  1661  			desc: "all samples have some labels, but not key being removed",
  1662  			samples: []*Sample{
  1663  				{
  1664  					Location: []*Location{cpuL[0]},
  1665  					Value:    []int64{1000},
  1666  					NumLabel: map[string][]int64{
  1667  						"key2": {2},
  1668  					},
  1669  					NumUnit: map[string][]string{
  1670  						"key2": {"terabytes"},
  1671  					},
  1672  				},
  1673  				{
  1674  					Location: []*Location{cpuL[0]},
  1675  					Value:    []int64{1000},
  1676  					NumLabel: map[string][]int64{
  1677  						"key3": {3},
  1678  					},
  1679  					NumUnit: map[string][]string{
  1680  						"key3": {""},
  1681  					},
  1682  				},
  1683  			},
  1684  			removeKey: "key1",
  1685  			wantValues: []map[string][]int64{
  1686  				{"key2": {2}},
  1687  				{"key3": {3}},
  1688  			},
  1689  			wantUnits: []map[string][]string{
  1690  				{"key2": {"terabytes"}},
  1691  				{"key3": {""}},
  1692  			},
  1693  		},
  1694  	}
  1695  
  1696  	for _, tc := range testcases {
  1697  		t.Run(tc.desc, func(t *testing.T) {
  1698  			profile := testProfile1.Copy()
  1699  			profile.Sample = tc.samples
  1700  			profile.RemoveNumLabel(tc.removeKey)
  1701  			if got, want := len(profile.Sample), len(tc.wantValues); got != want {
  1702  				t.Fatalf("got %v samples, want %v values", got, want)
  1703  			}
  1704  			if got, want := len(profile.Sample), len(tc.wantUnits); got != want {
  1705  				t.Fatalf("got %v samples, want %v units", got, want)
  1706  			}
  1707  			for i, sample := range profile.Sample {
  1708  				wantValues := tc.wantValues[i]
  1709  				if got, want := len(sample.NumLabel), len(wantValues); got != want {
  1710  					t.Errorf("got %v label values for sample %v, want %v", got, i, want)
  1711  					continue
  1712  				}
  1713  				for key, values := range wantValues {
  1714  					if gotValues, ok := sample.NumLabel[key]; ok {
  1715  						if !reflect.DeepEqual(gotValues, values) {
  1716  							t.Errorf("for key %s, got values %v, want values %v", key, gotValues, values)
  1717  						}
  1718  					} else {
  1719  						t.Errorf("for key %s got no values, want %v", key, values)
  1720  					}
  1721  				}
  1722  				wantUnits := tc.wantUnits[i]
  1723  				if got, want := len(sample.NumLabel), len(wantUnits); got != want {
  1724  					t.Errorf("got %v label values for sample %v, want %v", got, i, want)
  1725  					continue
  1726  				}
  1727  				for key, units := range wantUnits {
  1728  					if gotUnits, ok := sample.NumUnit[key]; ok {
  1729  						if !reflect.DeepEqual(gotUnits, units) {
  1730  							t.Errorf("for key %s, got units %v, want units %v", key, gotUnits, units)
  1731  						}
  1732  					} else {
  1733  						t.Errorf("for key %s got no units, want %v", key, units)
  1734  					}
  1735  				}
  1736  			}
  1737  		})
  1738  	}
  1739  }
  1740  
  1741  func TestNumLabelUnits(t *testing.T) {
  1742  	var tagFilterTests = []struct {
  1743  		desc             string
  1744  		tagVals          []map[string][]int64
  1745  		tagUnits         []map[string][]string
  1746  		wantUnits        map[string]string
  1747  		wantIgnoredUnits map[string][]string
  1748  	}{
  1749  		{
  1750  			"One sample, multiple keys, different specified units",
  1751  			[]map[string][]int64{{"key1": {131072}, "key2": {128}}},
  1752  			[]map[string][]string{{"key1": {"bytes"}, "key2": {"kilobytes"}}},
  1753  			map[string]string{"key1": "bytes", "key2": "kilobytes"},
  1754  			map[string][]string{},
  1755  		},
  1756  		{
  1757  			"One sample, one key with one value, unit specified",
  1758  			[]map[string][]int64{{"key1": {8}}},
  1759  			[]map[string][]string{{"key1": {"bytes"}}},
  1760  			map[string]string{"key1": "bytes"},
  1761  			map[string][]string{},
  1762  		},
  1763  		{
  1764  			"One sample, one key with one value, empty unit specified",
  1765  			[]map[string][]int64{{"key1": {8}}},
  1766  			[]map[string][]string{{"key1": {""}}},
  1767  			map[string]string{"key1": "key1"},
  1768  			map[string][]string{},
  1769  		},
  1770  		{
  1771  			"Key bytes, unit not specified",
  1772  			[]map[string][]int64{{"bytes": {8}}},
  1773  			[]map[string][]string{nil},
  1774  			map[string]string{"bytes": "bytes"},
  1775  			map[string][]string{},
  1776  		},
  1777  		{
  1778  			"One sample, one key with one value, unit not specified",
  1779  			[]map[string][]int64{{"kilobytes": {8}}},
  1780  			[]map[string][]string{nil},
  1781  			map[string]string{"kilobytes": "kilobytes"},
  1782  			map[string][]string{},
  1783  		},
  1784  		{
  1785  			"Key request, unit not specified",
  1786  			[]map[string][]int64{{"request": {8}}},
  1787  			[]map[string][]string{nil},
  1788  			map[string]string{"request": "bytes"},
  1789  			map[string][]string{},
  1790  		},
  1791  		{
  1792  			"Key alignment, unit not specified",
  1793  			[]map[string][]int64{{"alignment": {8}}},
  1794  			[]map[string][]string{nil},
  1795  			map[string]string{"alignment": "bytes"},
  1796  			map[string][]string{},
  1797  		},
  1798  		{
  1799  			"One sample, one key with multiple values and two different units",
  1800  			[]map[string][]int64{{"key1": {8, 8}}},
  1801  			[]map[string][]string{{"key1": {"bytes", "kilobytes"}}},
  1802  			map[string]string{"key1": "bytes"},
  1803  			map[string][]string{"key1": {"kilobytes"}},
  1804  		},
  1805  		{
  1806  			"One sample, one key with multiple values and three different units",
  1807  			[]map[string][]int64{{"key1": {8, 8}}},
  1808  			[]map[string][]string{{"key1": {"bytes", "megabytes", "kilobytes"}}},
  1809  			map[string]string{"key1": "bytes"},
  1810  			map[string][]string{"key1": {"kilobytes", "megabytes"}},
  1811  		},
  1812  		{
  1813  			"Two samples, one key, different units specified",
  1814  			[]map[string][]int64{{"key1": {8}}, {"key1": {8}}},
  1815  			[]map[string][]string{{"key1": {"bytes"}}, {"key1": {"kilobytes"}}},
  1816  			map[string]string{"key1": "bytes"},
  1817  			map[string][]string{"key1": {"kilobytes"}},
  1818  		},
  1819  		{
  1820  			"Keys alignment, request, and bytes have units specified",
  1821  			[]map[string][]int64{{
  1822  				"alignment": {8},
  1823  				"request":   {8},
  1824  				"bytes":     {8},
  1825  			}},
  1826  			[]map[string][]string{{
  1827  				"alignment": {"seconds"},
  1828  				"request":   {"minutes"},
  1829  				"bytes":     {"hours"},
  1830  			}},
  1831  			map[string]string{
  1832  				"alignment": "seconds",
  1833  				"request":   "minutes",
  1834  				"bytes":     "hours",
  1835  			},
  1836  			map[string][]string{},
  1837  		},
  1838  	}
  1839  	for _, test := range tagFilterTests {
  1840  		p := &Profile{Sample: make([]*Sample, len(test.tagVals))}
  1841  		for i, numLabel := range test.tagVals {
  1842  			s := Sample{
  1843  				NumLabel: numLabel,
  1844  				NumUnit:  test.tagUnits[i],
  1845  			}
  1846  			p.Sample[i] = &s
  1847  		}
  1848  		units, ignoredUnits := p.NumLabelUnits()
  1849  		if !reflect.DeepEqual(test.wantUnits, units) {
  1850  			t.Errorf("%s: got %v units, want %v", test.desc, units, test.wantUnits)
  1851  		}
  1852  		if !reflect.DeepEqual(test.wantIgnoredUnits, ignoredUnits) {
  1853  			t.Errorf("%s: got %v ignored units, want %v", test.desc, ignoredUnits, test.wantIgnoredUnits)
  1854  		}
  1855  	}
  1856  }
  1857  
  1858  func TestSetMain(t *testing.T) {
  1859  	testProfile1.massageMappings()
  1860  	if testProfile1.Mapping[0].File != mainBinary {
  1861  		t.Errorf("got %s for main", testProfile1.Mapping[0].File)
  1862  	}
  1863  }
  1864  
  1865  func TestParseKernelRelocation(t *testing.T) {
  1866  	src := testProfile1.Copy()
  1867  	if src.Mapping[len(src.Mapping)-1].KernelRelocationSymbol != "_text" {
  1868  		t.Errorf("got %s for Mapping.KernelRelocationSymbol", src.Mapping[0].KernelRelocationSymbol)
  1869  	}
  1870  }
  1871  
  1872  // parallel runs n copies of fn in parallel.
  1873  func parallel(n int, fn func()) {
  1874  	var wg sync.WaitGroup
  1875  	wg.Add(n)
  1876  	for i := 0; i < n; i++ {
  1877  		go func() {
  1878  			fn()
  1879  			wg.Done()
  1880  		}()
  1881  	}
  1882  	wg.Wait()
  1883  }
  1884  
  1885  func TestThreadSafety(t *testing.T) {
  1886  	src := testProfile1.Copy()
  1887  	parallel(4, func() { src.Copy() })
  1888  	parallel(4, func() {
  1889  		var b bytes.Buffer
  1890  		src.WriteUncompressed(&b)
  1891  	})
  1892  	parallel(4, func() {
  1893  		var b bytes.Buffer
  1894  		src.Write(&b)
  1895  	})
  1896  }
  1897  
  1898  func BenchmarkParse(b *testing.B) {
  1899  	data := proftest.LargeProfile(b)
  1900  	b.ResetTimer()
  1901  	for i := 0; i < b.N; i++ {
  1902  		_, err := Parse(bytes.NewBuffer(data))
  1903  		if err != nil {
  1904  			b.Fatal(err)
  1905  		}
  1906  	}
  1907  }
  1908  
  1909  func BenchmarkWrite(b *testing.B) {
  1910  	p, err := Parse(bytes.NewBuffer(proftest.LargeProfile(b)))
  1911  	if err != nil {
  1912  		b.Fatal(err)
  1913  	}
  1914  	b.ResetTimer()
  1915  	for i := 0; i < b.N; i++ {
  1916  		if err := p.WriteUncompressed(io.Discard); err != nil {
  1917  			b.Fatal(err)
  1918  		}
  1919  	}
  1920  }
  1921  

View as plain text