Bläddra i källkod

Added option to normalize by base profile (#162)

Margaret Nolan 8 år sedan
förälder
incheckning
0100b9c117

+ 11
- 4
internal/driver/cli.go Visa fil

@@ -24,10 +24,11 @@ import (
24 24
 )
25 25
 
26 26
 type source struct {
27
-	Sources  []string
28
-	ExecName string
29
-	BuildID  string
30
-	Base     []string
27
+	Sources   []string
28
+	ExecName  string
29
+	BuildID   string
30
+	Base      []string
31
+	Normalize bool
31 32
 
32 33
 	Seconds   int
33 34
 	Timeout   int
@@ -142,6 +143,12 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
142 143
 		}
143 144
 	}
144 145
 
146
+	normalize := pprofVariables["normalize"].boolValue()
147
+	if normalize && len(source.Base) == 0 {
148
+		return nil, nil, fmt.Errorf("Must have base profile to normalize by")
149
+	}
150
+	source.Normalize = normalize
151
+
145 152
 	if bu, ok := o.Obj.(*binutils.Binutils); ok {
146 153
 		bu.SetTools(*flagTools)
147 154
 	}

+ 2
- 0
internal/driver/commands.go Visa fil

@@ -218,6 +218,8 @@ var pprofVariables = variables{
218 218
 		"Sample value to report (0-based index or name)",
219 219
 		"Profiles contain multiple values per sample.",
220 220
 		"Use sample_index=i to select the ith value (starting at 0).")},
221
+	"normalize": &variable{boolKind, "f", "", helpText(
222
+		"Scales profile based on the base profile.")},
221 223
 
222 224
 	// Data sorting criteria
223 225
 	"flat": &variable{boolKind, "t", "cumulative", helpText("Sort entries based on own weight")},

+ 9
- 5
internal/driver/driver_test.go Visa fil

@@ -269,11 +269,12 @@ func addString(name []string, f *testFlags, components []string) []string {
269 269
 
270 270
 // testFlags implements the plugin.FlagSet interface.
271 271
 type testFlags struct {
272
-	bools   map[string]bool
273
-	ints    map[string]int
274
-	floats  map[string]float64
275
-	strings map[string]string
276
-	args    []string
272
+	bools       map[string]bool
273
+	ints        map[string]int
274
+	floats      map[string]float64
275
+	strings     map[string]string
276
+	args        []string
277
+	stringLists map[string][]*string
277 278
 }
278 279
 
279 280
 func (testFlags) ExtraUsage() string { return "" }
@@ -339,6 +340,9 @@ func (f testFlags) StringVar(p *string, s, d, c string) {
339 340
 }
340 341
 
341 342
 func (f testFlags) StringList(s, d, c string) *[]*string {
343
+	if t, ok := f.stringLists[s]; ok {
344
+		return &t
345
+	}
342 346
 	return &[]*string{}
343 347
 }
344 348
 

+ 64
- 17
internal/driver/fetch.go Visa fil

@@ -41,34 +41,43 @@ import (
41 41
 // there are some failures. It will return an error if it is unable to
42 42
 // fetch any profiles.
43 43
 func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
44
-	sources := make([]profileSource, 0, len(s.Sources)+len(s.Base))
44
+	sources := make([]profileSource, 0, len(s.Sources))
45 45
 	for _, src := range s.Sources {
46 46
 		sources = append(sources, profileSource{
47 47
 			addr:   src,
48 48
 			source: s,
49
-			scale:  1,
50 49
 		})
51 50
 	}
51
+
52
+	bases := make([]profileSource, 0, len(s.Base))
52 53
 	for _, src := range s.Base {
53
-		sources = append(sources, profileSource{
54
+		bases = append(bases, profileSource{
54 55
 			addr:   src,
55 56
 			source: s,
56
-			scale:  -1,
57 57
 		})
58 58
 	}
59
-	p, msrcs, save, cnt, err := chunkedGrab(sources, o.Fetch, o.Obj, o.UI)
59
+
60
+	p, pbase, m, mbase, save, err := grabSourcesAndBases(sources, bases, o.Fetch, o.Obj, o.UI)
60 61
 	if err != nil {
61 62
 		return nil, err
62 63
 	}
63
-	if cnt == 0 {
64
-		return nil, fmt.Errorf("failed to fetch any profiles")
65
-	}
66
-	if want, got := len(sources), cnt; want != got {
67
-		o.UI.PrintErr(fmt.Sprintf("fetched %d profiles out of %d", got, want))
64
+
65
+	if pbase != nil {
66
+		if s.Normalize {
67
+			err := p.Normalize(pbase)
68
+			if err != nil {
69
+				return nil, err
70
+			}
71
+		}
72
+		pbase.Scale(-1)
73
+		p, m, err = combineProfiles([]*profile.Profile{p, pbase}, []plugin.MappingSources{m, mbase})
74
+		if err != nil {
75
+			return nil, err
76
+		}
68 77
 	}
69 78
 
70 79
 	// Symbolize the merged profile.
71
-	if err := o.Sym.Symbolize(s.Symbolize, msrcs, p); err != nil {
80
+	if err := o.Sym.Symbolize(s.Symbolize, m, p); err != nil {
72 81
 		return nil, err
73 82
 	}
74 83
 	p.RemoveUninteresting()
@@ -107,6 +116,47 @@ func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
107 116
 	return p, nil
108 117
 }
109 118
 
119
+func grabSourcesAndBases(sources, bases []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, *profile.Profile, plugin.MappingSources, plugin.MappingSources, bool, error) {
120
+	wg := sync.WaitGroup{}
121
+	wg.Add(2)
122
+	var psrc, pbase *profile.Profile
123
+	var msrc, mbase plugin.MappingSources
124
+	var savesrc, savebase bool
125
+	var errsrc, errbase error
126
+	var countsrc, countbase int
127
+	go func() {
128
+		defer wg.Done()
129
+		psrc, msrc, savesrc, countsrc, errsrc = chunkedGrab(sources, fetch, obj, ui)
130
+	}()
131
+	go func() {
132
+		defer wg.Done()
133
+		pbase, mbase, savebase, countbase, errbase = chunkedGrab(bases, fetch, obj, ui)
134
+	}()
135
+	wg.Wait()
136
+	save := savesrc || savebase
137
+
138
+	if errsrc != nil {
139
+		return nil, nil, nil, nil, false, fmt.Errorf("problem fetching source profiles: %v", errsrc)
140
+	}
141
+	if errbase != nil {
142
+		return nil, nil, nil, nil, false, fmt.Errorf("problem fetching base profiles: %v,", errbase)
143
+	}
144
+	if countsrc == 0 {
145
+		return nil, nil, nil, nil, false, fmt.Errorf("failed to fetch any source profiles")
146
+	}
147
+	if countbase == 0 && len(bases) > 0 {
148
+		return nil, nil, nil, nil, false, fmt.Errorf("failed to fetch any base profiles")
149
+	}
150
+	if want, got := len(sources), countsrc; want != got {
151
+		ui.PrintErr(fmt.Sprintf("Fetched %d source profiles out of %d", got, want))
152
+	}
153
+	if want, got := len(bases), countbase; want != got {
154
+		ui.PrintErr(fmt.Sprintf("Fetched %d base profiles out of %d", got, want))
155
+	}
156
+
157
+	return psrc, pbase, msrc, mbase, save, nil
158
+}
159
+
110 160
 // chunkedGrab fetches the profiles described in source and merges them into
111 161
 // a single profile. It fetches a chunk of profiles concurrently, with a maximum
112 162
 // chunk size to limit its memory usage.
@@ -142,6 +192,7 @@ func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTo
142 192
 			count += chunkCount
143 193
 		}
144 194
 	}
195
+
145 196
 	return p, msrc, save, count, nil
146 197
 }
147 198
 
@@ -152,7 +203,7 @@ func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.Ob
152 203
 	for i := range sources {
153 204
 		go func(s *profileSource) {
154 205
 			defer wg.Done()
155
-			s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, s.scale, fetch, obj, ui)
206
+			s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, fetch, obj, ui)
156 207
 		}(&sources[i])
157 208
 	}
158 209
 	wg.Wait()
@@ -207,7 +258,6 @@ func combineProfiles(profiles []*profile.Profile, msrcs []plugin.MappingSources)
207 258
 type profileSource struct {
208 259
 	addr   string
209 260
 	source *source
210
-	scale  float64
211 261
 
212 262
 	p      *profile.Profile
213 263
 	msrc   plugin.MappingSources
@@ -247,7 +297,7 @@ const testSourceAddress = "pproftest.local"
247 297
 // grabProfile fetches a profile. Returns the profile, sources for the
248 298
 // profile mappings, a bool indicating if the profile was fetched
249 299
 // remotely, and an error.
250
-func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {
300
+func grabProfile(s *source, source string, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {
251 301
 	var src string
252 302
 	duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second
253 303
 	if fetcher != nil {
@@ -268,9 +318,6 @@ func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher
268 318
 		return
269 319
 	}
270 320
 
271
-	// Apply local changes to the profile.
272
-	p.Scale(scale)
273
-
274 321
 	// Update the binary locations from command line and paths.
275 322
 	locateBinaries(p, s, obj, ui)
276 323
 

+ 112
- 1
internal/driver/fetch_test.go Visa fil

@@ -188,7 +188,7 @@ func TestFetch(t *testing.T) {
188 188
 		{path + "go.nomappings.crash", "/bin/gotest.exe"},
189 189
 		{"http://localhost/profile?file=cppbench.cpu", ""},
190 190
 	} {
191
-		p, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, 0, nil, testObj{}, &proftest.TestUI{T: t})
191
+		p, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, nil, testObj{}, &proftest.TestUI{T: t})
192 192
 		if err != nil {
193 193
 			t.Fatalf("%s: %s", tc.source, err)
194 194
 		}
@@ -206,6 +206,117 @@ func TestFetch(t *testing.T) {
206 206
 	}
207 207
 }
208 208
 
209
+func TestFetchWithBase(t *testing.T) {
210
+	baseVars := pprofVariables
211
+	defer func() { pprofVariables = baseVars }()
212
+
213
+	const path = "testdata/"
214
+	type testcase struct {
215
+		desc            string
216
+		sources         []string
217
+		bases           []string
218
+		normalize       bool
219
+		expectedSamples [][]int64
220
+	}
221
+
222
+	testcases := []testcase{
223
+		{
224
+			"not normalized base is same as source",
225
+			[]string{path + "cppbench.contention"},
226
+			[]string{path + "cppbench.contention"},
227
+			false,
228
+			[][]int64{},
229
+		},
230
+		{
231
+			"not normalized single source, multiple base (all profiles same)",
232
+			[]string{path + "cppbench.contention"},
233
+			[]string{path + "cppbench.contention", path + "cppbench.contention"},
234
+			false,
235
+			[][]int64{{-2700, -608881724}, {-100, -23992}, {-200, -179943}, {-100, -17778444}, {-100, -75976}, {-300, -63568134}},
236
+		},
237
+		{
238
+			"not normalized, different base and source",
239
+			[]string{path + "cppbench.contention"},
240
+			[]string{path + "cppbench.small.contention"},
241
+			false,
242
+			[][]int64{{1700, 608878600}, {100, 23992}, {200, 179943}, {100, 17778444}, {100, 75976}, {300, 63568134}},
243
+		},
244
+		{
245
+			"normalized base is same as source",
246
+			[]string{path + "cppbench.contention"},
247
+			[]string{path + "cppbench.contention"},
248
+			true,
249
+			[][]int64{},
250
+		},
251
+		{
252
+			"normalized single source, multiple base (all profiles same)",
253
+			[]string{path + "cppbench.contention"},
254
+			[]string{path + "cppbench.contention", path + "cppbench.contention"},
255
+			true,
256
+			[][]int64{},
257
+		},
258
+		{
259
+			"normalized different base and source",
260
+			[]string{path + "cppbench.contention"},
261
+			[]string{path + "cppbench.small.contention"},
262
+			true,
263
+			[][]int64{{-229, -370}, {28, 0}, {57, 0}, {28, 80}, {28, 0}, {85, 287}},
264
+		},
265
+	}
266
+
267
+	for _, tc := range testcases {
268
+		t.Run(tc.desc, func(t *testing.T) {
269
+			pprofVariables = baseVars.makeCopy()
270
+
271
+			base := make([]*string, len(tc.bases))
272
+			for i, s := range tc.bases {
273
+				base[i] = &s
274
+			}
275
+
276
+			f := testFlags{
277
+				stringLists: map[string][]*string{
278
+					"base": base,
279
+				},
280
+				bools: map[string]bool{
281
+					"normalize": tc.normalize,
282
+				},
283
+			}
284
+			f.args = tc.sources
285
+
286
+			o := setDefaults(nil)
287
+			o.Flagset = f
288
+			src, _, err := parseFlags(o)
289
+
290
+			if err != nil {
291
+				t.Fatalf("%s: %v", tc.desc, err)
292
+			}
293
+
294
+			p, err := fetchProfiles(src, o)
295
+			pprofVariables = baseVars
296
+			if err != nil {
297
+				t.Fatal(err)
298
+			}
299
+
300
+			if want, got := len(tc.expectedSamples), len(p.Sample); want != got {
301
+				t.Fatalf("want %d samples got %d", want, got)
302
+			}
303
+
304
+			if len(p.Sample) > 0 {
305
+				for i, sample := range p.Sample {
306
+					if want, got := len(tc.expectedSamples[i]), len(sample.Value); want != got {
307
+						t.Errorf("want %d values for sample %d, got %d", want, i, got)
308
+					}
309
+					for j, value := range sample.Value {
310
+						if want, got := tc.expectedSamples[i][j], value; want != got {
311
+							t.Errorf("want value of %d for value %d of sample %d, got %d", want, j, i, got)
312
+						}
313
+					}
314
+				}
315
+			}
316
+		})
317
+	}
318
+}
319
+
209 320
 // mappingSources creates MappingSources map with a single item.
210 321
 func mappingSources(key, source string, start uint64) plugin.MappingSources {
211 322
 	return plugin.MappingSources{

+ 24
- 0
internal/driver/testdata/cppbench.contention Visa fil

@@ -0,0 +1,24 @@
1
+--- contentionz 1 ---
2
+cycles/second = 3201000000
3
+sampling period = 100
4
+ms since reset = 16502830
5
+discarded samples = 0
6
+  19490304       27 @ 0xbccc97 0xc61202 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e
7
+       768        1 @ 0xbccc97 0xa42dc7 0xa456e4 0x7fcdc2ff214e
8
+      5760        2 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87eab 0xb8814c 0x4e969d 0x4faa17 0x4fc5f6 0x4fd028 0x4fd230 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e
9
+    569088        1 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87f08 0xb8814c 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e
10
+      2432        1 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87eab 0xb8814c 0x7aa74c 0x7ab844 0x7ab914 0x79e9e9 0x79e326 0x4d299e 0x4d4b7b 0x4b7be8 0x4b7ff1 0x4d2dae 0x79e80a
11
+   2034816        3 @ 0xbccc97 0xb82f0f 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e
12
+--- Memory map: ---
13
+  00400000-00fcb000: cppbench_server_main
14
+  7fcdc231e000-7fcdc2321000: /libnss_cache-2.15.so
15
+  7fcdc2522000-7fcdc252e000: /libnss_files-2.15.so
16
+  7fcdc272f000-7fcdc28dd000: /libc-2.15.so
17
+  7fcdc2ae7000-7fcdc2be2000: /libm-2.15.so
18
+  7fcdc2de3000-7fcdc2dea000: /librt-2.15.so
19
+  7fcdc2feb000-7fcdc3003000: /libpthread-2.15.so
20
+  7fcdc3208000-7fcdc320a000: /libdl-2.15.so
21
+  7fcdc340c000-7fcdc3415000: /libcrypt-2.15.so
22
+  7fcdc3645000-7fcdc3669000: /ld-2.15.so
23
+  7fff86bff000-7fff86c00000: [vdso]
24
+  ffffffffff600000-ffffffffff601000: [vsyscall]

+ 19
- 0
internal/driver/testdata/cppbench.small.contention Visa fil

@@ -0,0 +1,19 @@
1
+--- contentionz 1 ---
2
+cycles/second = 3201000000
3
+sampling period = 100
4
+ms since reset = 16502830
5
+discarded samples = 0
6
+       100     10 @ 0xbccc97 0xc61202 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e
7
+--- Memory map: ---
8
+  00400000-00fcb000: cppbench_server_main
9
+  7fcdc231e000-7fcdc2321000: /libnss_cache-2.15.so
10
+  7fcdc2522000-7fcdc252e000: /libnss_files-2.15.so
11
+  7fcdc272f000-7fcdc28dd000: /libc-2.15.so
12
+  7fcdc2ae7000-7fcdc2be2000: /libm-2.15.so
13
+  7fcdc2de3000-7fcdc2dea000: /librt-2.15.so
14
+  7fcdc2feb000-7fcdc3003000: /libpthread-2.15.so
15
+  7fcdc3208000-7fcdc320a000: /libdl-2.15.so
16
+  7fcdc340c000-7fcdc3415000: /libcrypt-2.15.so
17
+  7fcdc3645000-7fcdc3669000: /ld-2.15.so
18
+  7fff86bff000-7fff86c00000: [vdso]
19
+  ffffffffff600000-ffffffffff601000: [vsyscall]

+ 35
- 1
profile/merge.go Visa fil

@@ -85,6 +85,41 @@ func Merge(srcs []*Profile) (*Profile, error) {
85 85
 	return p, nil
86 86
 }
87 87
 
88
+// Normalize normalizes the source profile by multiplying each value in profile by the
89
+// ratio of the sum of the base profile's values of that sample type to the sum of the
90
+// source profile's value of that sample type.
91
+func (p *Profile) Normalize(pb *Profile) error {
92
+
93
+	if err := p.compatible(pb); err != nil {
94
+		return err
95
+	}
96
+
97
+	baseVals := make([]int64, len(p.SampleType))
98
+	for _, s := range pb.Sample {
99
+		for i, v := range s.Value {
100
+			baseVals[i] += v
101
+		}
102
+	}
103
+
104
+	srcVals := make([]int64, len(p.SampleType))
105
+	for _, s := range p.Sample {
106
+		for i, v := range s.Value {
107
+			srcVals[i] += v
108
+		}
109
+	}
110
+
111
+	normScale := make([]float64, len(baseVals))
112
+	for i := range baseVals {
113
+		if srcVals[i] == 0 {
114
+			normScale[i] = 0.0
115
+		} else {
116
+			normScale[i] = float64(baseVals[i]) / float64(srcVals[i])
117
+		}
118
+	}
119
+	p.ScaleN(normScale)
120
+	return nil
121
+}
122
+
88 123
 func isZeroSample(s *Sample) bool {
89 124
 	for _, v := range s.Value {
90 125
 		if v != 0 {
@@ -432,7 +467,6 @@ func (p *Profile) compatible(pb *Profile) error {
432 467
 			return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
433 468
 		}
434 469
 	}
435
-
436 470
 	return nil
437 471
 }
438 472
 

+ 165
- 11
profile/profile_test.go Visa fil

@@ -217,7 +217,7 @@ var cpuL = []*Location{
217 217
 	},
218 218
 }
219 219
 
220
-var testProfile = &Profile{
220
+var testProfile1 = &Profile{
221 221
 	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
222 222
 	Period:        1,
223 223
 	DurationNanos: 10e9,
@@ -272,6 +272,83 @@ var testProfile = &Profile{
272 272
 	Mapping:  cpuM,
273 273
 }
274 274
 
275
+var testProfile2 = &Profile{
276
+	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
277
+	Period:        1,
278
+	DurationNanos: 10e9,
279
+	SampleType: []*ValueType{
280
+		{Type: "samples", Unit: "count"},
281
+		{Type: "cpu", Unit: "milliseconds"},
282
+	},
283
+	Sample: []*Sample{
284
+		{
285
+			Location: []*Location{cpuL[0]},
286
+			Value:    []int64{70, 1000},
287
+			Label: map[string][]string{
288
+				"key1": {"tag1"},
289
+				"key2": {"tag1"},
290
+			},
291
+		},
292
+		{
293
+			Location: []*Location{cpuL[1], cpuL[0]},
294
+			Value:    []int64{60, 100},
295
+			Label: map[string][]string{
296
+				"key1": {"tag2"},
297
+				"key3": {"tag2"},
298
+			},
299
+		},
300
+		{
301
+			Location: []*Location{cpuL[2], cpuL[0]},
302
+			Value:    []int64{50, 10},
303
+			Label: map[string][]string{
304
+				"key1": {"tag3"},
305
+				"key2": {"tag2"},
306
+			},
307
+		},
308
+		{
309
+			Location: []*Location{cpuL[3], cpuL[0]},
310
+			Value:    []int64{40, 10000},
311
+			Label: map[string][]string{
312
+				"key1": {"tag4"},
313
+				"key2": {"tag1"},
314
+			},
315
+		},
316
+		{
317
+			Location: []*Location{cpuL[4], cpuL[0]},
318
+			Value:    []int64{1, 1},
319
+			Label: map[string][]string{
320
+				"key1": {"tag4"},
321
+				"key2": {"tag1"},
322
+			},
323
+		},
324
+	},
325
+	Location: cpuL,
326
+	Function: cpuF,
327
+	Mapping:  cpuM,
328
+}
329
+
330
+var testProfile3 = &Profile{
331
+	PeriodType:    &ValueType{Type: "cpu", Unit: "milliseconds"},
332
+	Period:        1,
333
+	DurationNanos: 10e9,
334
+	SampleType: []*ValueType{
335
+		{Type: "samples", Unit: "count"},
336
+	},
337
+	Sample: []*Sample{
338
+		{
339
+			Location: []*Location{cpuL[0]},
340
+			Value:    []int64{1000},
341
+			Label: map[string][]string{
342
+				"key1": {"tag1"},
343
+				"key2": {"tag1"},
344
+			},
345
+		},
346
+	},
347
+	Location: cpuL,
348
+	Function: cpuF,
349
+	Mapping:  cpuM,
350
+}
351
+
275 352
 var aggTests = map[string]aggTest{
276 353
 	"precise":         {true, true, true, true, 5},
277 354
 	"fileline":        {false, true, true, true, 4},
@@ -287,7 +364,7 @@ type aggTest struct {
287 364
 const totalSamples = int64(11111)
288 365
 
289 366
 func TestAggregation(t *testing.T) {
290
-	prof := testProfile.Copy()
367
+	prof := testProfile1.Copy()
291 368
 	for _, resolution := range []string{"precise", "fileline", "inline_function", "function"} {
292 369
 		a := aggTests[resolution]
293 370
 		if !a.precise {
@@ -362,7 +439,7 @@ func checkAggregation(prof *Profile, a *aggTest) error {
362 439
 
363 440
 // Test merge leaves the main binary in place.
364 441
 func TestMergeMain(t *testing.T) {
365
-	prof := testProfile.Copy()
442
+	prof := testProfile1.Copy()
366 443
 	p1, err := Merge([]*Profile{prof})
367 444
 	if err != nil {
368 445
 		t.Fatalf("merge error: %v", err)
@@ -377,7 +454,7 @@ func TestMerge(t *testing.T) {
377 454
 	// -2. Should end up with an empty profile (all samples for a
378 455
 	// location should add up to 0).
379 456
 
380
-	prof := testProfile.Copy()
457
+	prof := testProfile1.Copy()
381 458
 	p1, err := Merge([]*Profile{prof, prof})
382 459
 	if err != nil {
383 460
 		t.Errorf("merge error: %v", err)
@@ -409,7 +486,7 @@ func TestMergeAll(t *testing.T) {
409 486
 	// Aggregate 10 copies of the profile.
410 487
 	profs := make([]*Profile, 10)
411 488
 	for i := 0; i < 10; i++ {
412
-		profs[i] = testProfile.Copy()
489
+		profs[i] = testProfile1.Copy()
413 490
 	}
414 491
 	prof, err := Merge(profs)
415 492
 	if err != nil {
@@ -420,7 +497,7 @@ func TestMergeAll(t *testing.T) {
420 497
 		tb := locationHash(s)
421 498
 		samples[tb] = samples[tb] + s.Value[0]
422 499
 	}
423
-	for _, s := range testProfile.Sample {
500
+	for _, s := range testProfile1.Sample {
424 501
 		tb := locationHash(s)
425 502
 		if samples[tb] != s.Value[0]*10 {
426 503
 			t.Errorf("merge got wrong value at %s : %d instead of %d", tb, samples[tb], s.Value[0]*10)
@@ -428,6 +505,83 @@ func TestMergeAll(t *testing.T) {
428 505
 	}
429 506
 }
430 507
 
508
+func TestNormalizeBySameProfile(t *testing.T) {
509
+	pb := testProfile1.Copy()
510
+	p := testProfile1.Copy()
511
+
512
+	if err := p.Normalize(pb); err != nil {
513
+		t.Fatal(err)
514
+	}
515
+
516
+	for i, s := range p.Sample {
517
+		for j, v := range s.Value {
518
+			expectedSampleValue := testProfile1.Sample[i].Value[j]
519
+			if v != expectedSampleValue {
520
+				t.Errorf("For sample %d, value %d want %d got %d", i, j, expectedSampleValue, v)
521
+			}
522
+		}
523
+	}
524
+}
525
+
526
+func TestNormalizeByDifferentProfile(t *testing.T) {
527
+	p := testProfile1.Copy()
528
+	pb := testProfile2.Copy()
529
+
530
+	if err := p.Normalize(pb); err != nil {
531
+		t.Fatal(err)
532
+	}
533
+
534
+	expectedSampleValues := [][]int64{
535
+		{19, 1000},
536
+		{1, 100},
537
+		{0, 10},
538
+		{198, 10000},
539
+		{0, 1},
540
+	}
541
+
542
+	for i, s := range p.Sample {
543
+		for j, v := range s.Value {
544
+			if v != expectedSampleValues[i][j] {
545
+				t.Errorf("For sample %d, value %d want %d got %d", i, j, expectedSampleValues[i][j], v)
546
+			}
547
+		}
548
+	}
549
+}
550
+
551
+func TestNormalizeByMultipleOfSameProfile(t *testing.T) {
552
+	pb := testProfile1.Copy()
553
+	for i, s := range pb.Sample {
554
+		for j, v := range s.Value {
555
+			pb.Sample[i].Value[j] = 10 * v
556
+		}
557
+	}
558
+
559
+	p := testProfile1.Copy()
560
+
561
+	err := p.Normalize(pb)
562
+	if err != nil {
563
+		t.Fatal(err)
564
+	}
565
+
566
+	for i, s := range p.Sample {
567
+		for j, v := range s.Value {
568
+			expectedSampleValue := 10 * testProfile1.Sample[i].Value[j]
569
+			if v != expectedSampleValue {
570
+				t.Errorf("For sample %d, value %d, want %d got %d", i, j, expectedSampleValue, v)
571
+			}
572
+		}
573
+	}
574
+}
575
+
576
+func TestNormalizeIncompatibleProfiles(t *testing.T) {
577
+	p := testProfile1.Copy()
578
+	pb := testProfile3.Copy()
579
+
580
+	if err := p.Normalize(pb); err == nil {
581
+		t.Errorf("Expected an error")
582
+	}
583
+}
584
+
431 585
 func TestFilter(t *testing.T) {
432 586
 	// Perform several forms of filtering on the test profile.
433 587
 
@@ -442,7 +596,7 @@ func TestFilter(t *testing.T) {
442 596
 		{nil, regexp.MustCompile("foo.c"), nil, nil, true, true, false, false},
443 597
 		{nil, nil, regexp.MustCompile("lib.so"), nil, true, false, true, false},
444 598
 	} {
445
-		prof := *testProfile.Copy()
599
+		prof := *testProfile1.Copy()
446 600
 		gf, gi, gh, gnh := prof.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show)
447 601
 		if gf != tc.fm {
448 602
 			t.Errorf("Filter #%d, got fm=%v, want %v", tx, gf, tc.fm)
@@ -488,7 +642,7 @@ func TestTagFilter(t *testing.T) {
488 642
 		{regexp.MustCompile("key1"), nil, true, false, 1},
489 643
 		{nil, regexp.MustCompile("key[12]"), true, true, 1},
490 644
 	} {
491
-		prof := testProfile.Copy()
645
+		prof := testProfile1.Copy()
492 646
 		gim, gem := prof.FilterTagsByName(tc.include, tc.exclude)
493 647
 		if gim != tc.im {
494 648
 			t.Errorf("Filter #%d, got include match=%v, want %v", tx, gim, tc.im)
@@ -514,8 +668,8 @@ func locationHash(s *Sample) string {
514 668
 }
515 669
 
516 670
 func TestSetMain(t *testing.T) {
517
-	testProfile.massageMappings()
518
-	if testProfile.Mapping[0].File != mainBinary {
519
-		t.Errorf("got %s for main", testProfile.Mapping[0].File)
671
+	testProfile1.massageMappings()
672
+	if testProfile1.Mapping[0].File != mainBinary {
673
+		t.Errorf("got %s for main", testProfile1.Mapping[0].File)
520 674
 	}
521 675
 }