github.com/grafana/pyroscope@v1.18.0/pkg/pprof/pprof_test.go (about) 1 package pprof 2 3 import ( 4 "io/fs" 5 "math/rand" 6 "os" 7 "path/filepath" 8 "testing" 9 "time" 10 11 "github.com/google/pprof/profile" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 "google.golang.org/protobuf/proto" 15 16 profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" 17 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 18 "github.com/grafana/pyroscope/pkg/pprof/testhelper" 19 ) 20 21 func TestNormalizeProfile(t *testing.T) { 22 currentTime = func() time.Time { 23 t, _ := time.Parse(time.RFC3339, "2020-01-01T00:00:00Z") 24 return t 25 } 26 defer func() { 27 currentTime = time.Now 28 }() 29 30 p := &profilev1.Profile{ 31 SampleType: []*profilev1.ValueType{ 32 {Type: 2, Unit: 1}, 33 {Type: 3, Unit: 4}, 34 }, 35 Sample: []*profilev1.Sample{ 36 {LocationId: []uint64{2, 3}, Value: []int64{0, 1}, Label: []*profilev1.Label{{Num: 10, Key: 1}, {Num: 11, Key: 1}}}, 37 // Those samples should be dropped. 38 {LocationId: []uint64{1, 2, 3}, Value: []int64{0, 0}, Label: []*profilev1.Label{{Num: 10, Key: 1}}}, 39 {LocationId: []uint64{4}, Value: []int64{0, 0}, Label: []*profilev1.Label{{Num: 10, Key: 1}}}, 40 }, 41 Mapping: []*profilev1.Mapping{{Id: 1, HasFunctions: true, MemoryStart: 100, MemoryLimit: 200, FileOffset: 200}}, 42 Location: []*profilev1.Location{ 43 {Id: 1, MappingId: 1, Address: 5, Line: []*profilev1.Line{{FunctionId: 1, Line: 1}, {FunctionId: 2, Line: 3}}}, 44 {Id: 2, MappingId: 1, Address: 2, Line: []*profilev1.Line{{FunctionId: 2, Line: 1}, {FunctionId: 3, Line: 3}}}, 45 {Id: 3, MappingId: 1, Address: 1, Line: []*profilev1.Line{{FunctionId: 3, Line: 1}, {FunctionId: 4, Line: 3}}}, 46 {Id: 4, MappingId: 1, Address: 0, Line: []*profilev1.Line{{FunctionId: 5, Line: 1}}}, 47 }, 48 Function: []*profilev1.Function{ 49 {Id: 1, Name: 5, SystemName: 6, Filename: 7, StartLine: 1}, 50 {Id: 2, Name: 8, SystemName: 9, Filename: 10, StartLine: 1}, 51 {Id: 3, Name: 11, SystemName: 12, Filename: 13, StartLine: 1}, 52 {Id: 4, Name: 14, SystemName: 15, Filename: 7, StartLine: 1}, 53 {Id: 5, Name: 16, SystemName: 17, Filename: 18, StartLine: 1}, 54 }, 55 StringTable: []string{ 56 "", 57 "bytes", "in_used", "allocs", "count", 58 "main", "runtime.main", "main.go", // fn1 59 "foo", "runtime.foo", "foo.go", // fn2 60 "bar", "runtime.bar", "bar.go", // fn3 61 "buzz", "runtime.buzz", // fn4 62 "bla", "runtime.bla", "bla.go", // fn5 63 }, 64 PeriodType: &profilev1.ValueType{Type: 2, Unit: 1}, 65 Comment: []int64{}, 66 DefaultSampleType: 0, 67 } 68 69 pf := &Profile{Profile: p} 70 pf.Normalize() 71 require.Equal(t, &profilev1.Profile{ 72 SampleType: []*profilev1.ValueType{ 73 {Type: 2, Unit: 1}, 74 {Type: 3, Unit: 4}, 75 }, 76 Sample: []*profilev1.Sample{ 77 {LocationId: []uint64{1, 2}, Value: []int64{0, 1}, Label: []*profilev1.Label{}}, 78 }, 79 Mapping: []*profilev1.Mapping{{ 80 Id: 1, 81 HasFunctions: true, 82 }}, 83 Location: []*profilev1.Location{ 84 {Id: 1, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 1, Line: 1}, {FunctionId: 2, Line: 3}}}, 85 {Id: 2, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 2, Line: 1}, {FunctionId: 3, Line: 3}}}, 86 }, 87 Function: []*profilev1.Function{ 88 {Id: 1, Name: 6, SystemName: 7, Filename: 8, StartLine: 1}, 89 {Id: 2, Name: 9, SystemName: 10, Filename: 11, StartLine: 1}, 90 {Id: 3, Name: 12, SystemName: 13, Filename: 5, StartLine: 1}, 91 }, 92 StringTable: []string{ 93 "", 94 "bytes", "in_used", "allocs", "count", 95 "main.go", 96 "foo", "runtime.foo", "foo.go", 97 "bar", "runtime.bar", "bar.go", 98 "buzz", "runtime.buzz", 99 }, 100 PeriodType: &profilev1.ValueType{Type: 2, Unit: 1}, 101 Comment: []int64{}, 102 TimeNanos: 1577836800000000000, 103 DefaultSampleType: 0, 104 }, pf.Profile) 105 } 106 107 func TestNormalizeProfile_NegativeSample(t *testing.T) { 108 currentTime = func() time.Time { 109 t, _ := time.Parse(time.RFC3339, "2020-01-01T00:00:00Z") 110 return t 111 } 112 defer func() { 113 currentTime = time.Now 114 }() 115 116 p := &profilev1.Profile{ 117 SampleType: []*profilev1.ValueType{ 118 {Type: 1, Unit: 2}, 119 }, 120 Sample: []*profilev1.Sample{ 121 {LocationId: []uint64{2, 1}, Value: []int64{10}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 6}}}, 122 {LocationId: []uint64{2, 1}, Value: []int64{10}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 7}}}, 123 {LocationId: []uint64{2, 1}, Value: []int64{10}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 7}}}, 124 {LocationId: []uint64{2, 1}, Value: []int64{-10}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 7}}}, 125 {LocationId: []uint64{2, 1}, Value: []int64{0}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 7}}}, 126 }, 127 Mapping: []*profilev1.Mapping{{Id: 1, HasFunctions: true, MemoryStart: 100, MemoryLimit: 200, FileOffset: 200}}, 128 Location: []*profilev1.Location{ 129 {Id: 1, MappingId: 1, Address: 5, Line: []*profilev1.Line{{FunctionId: 1, Line: 1}}}, 130 {Id: 2, MappingId: 1, Address: 2, Line: []*profilev1.Line{{FunctionId: 2, Line: 1}}}, 131 }, 132 Function: []*profilev1.Function{ 133 {Id: 1, Name: 3, SystemName: 3, Filename: 4, StartLine: 1}, 134 {Id: 2, Name: 5, SystemName: 5, Filename: 4, StartLine: 1}, 135 }, 136 StringTable: []string{ 137 "", 138 "cpu", "nanoseconds", 139 "main", "main.go", 140 "foo", "bar", "baz", 141 }, 142 PeriodType: &profilev1.ValueType{Type: 1, Unit: 2}, 143 } 144 145 pf := &Profile{Profile: p} 146 pf.Normalize() 147 require.Equal(t, pf.Profile, &profilev1.Profile{ 148 SampleType: []*profilev1.ValueType{ 149 {Type: 1, Unit: 2}, 150 }, 151 Sample: []*profilev1.Sample{ 152 {LocationId: []uint64{2, 1}, Value: []int64{10}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 6}}}, 153 {LocationId: []uint64{2, 1}, Value: []int64{20}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 7}}}, 154 }, 155 Mapping: []*profilev1.Mapping{{Id: 1, HasFunctions: true}}, 156 Location: []*profilev1.Location{ 157 {Id: 1, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 1, Line: 1}}}, 158 {Id: 2, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 2, Line: 1}}}, 159 }, 160 Function: []*profilev1.Function{ 161 {Id: 1, Name: 3, SystemName: 3, Filename: 4, StartLine: 1}, 162 {Id: 2, Name: 5, SystemName: 5, Filename: 4, StartLine: 1}, 163 }, 164 StringTable: []string{ 165 "", 166 "cpu", "nanoseconds", 167 "main", "main.go", 168 "foo", "bar", "baz", 169 }, 170 PeriodType: &profilev1.ValueType{Type: 1, Unit: 2}, 171 TimeNanos: 1577836800000000000, 172 }) 173 } 174 175 func TestNormalizeProfile_SampleLabels(t *testing.T) { 176 currentTime = func() time.Time { 177 t, _ := time.Parse(time.RFC3339, "2020-01-01T00:00:00Z") 178 return t 179 } 180 defer func() { 181 currentTime = time.Now 182 }() 183 184 p := &profilev1.Profile{ 185 SampleType: []*profilev1.ValueType{ 186 {Type: 1, Unit: 2}, 187 }, 188 Sample: []*profilev1.Sample{ 189 {LocationId: []uint64{2, 1}, Value: []int64{10}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 6}}}, 190 {LocationId: []uint64{2, 1}, Value: []int64{10}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 7}}}, 191 {LocationId: []uint64{2, 1}, Value: []int64{10}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 7}}}, 192 }, 193 Mapping: []*profilev1.Mapping{{Id: 1, HasFunctions: true, MemoryStart: 100, MemoryLimit: 200, FileOffset: 200}}, 194 Location: []*profilev1.Location{ 195 {Id: 1, MappingId: 1, Address: 5, Line: []*profilev1.Line{{FunctionId: 1, Line: 1}}}, 196 {Id: 2, MappingId: 1, Address: 2, Line: []*profilev1.Line{{FunctionId: 2, Line: 1}}}, 197 }, 198 Function: []*profilev1.Function{ 199 {Id: 1, Name: 3, SystemName: 3, Filename: 4, StartLine: 1}, 200 {Id: 2, Name: 5, SystemName: 5, Filename: 4, StartLine: 1}, 201 }, 202 StringTable: []string{ 203 "", 204 "cpu", "nanoseconds", 205 "main", "main.go", 206 "foo", "bar", "baz", 207 }, 208 PeriodType: &profilev1.ValueType{Type: 1, Unit: 2}, 209 } 210 211 pf := &Profile{Profile: p} 212 pf.Normalize() 213 require.Equal(t, pf.Profile, &profilev1.Profile{ 214 SampleType: []*profilev1.ValueType{ 215 {Type: 1, Unit: 2}, 216 }, 217 Sample: []*profilev1.Sample{ 218 {LocationId: []uint64{2, 1}, Value: []int64{10}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 6}}}, 219 {LocationId: []uint64{2, 1}, Value: []int64{20}, Label: []*profilev1.Label{{Str: 5, Key: 5}, {Str: 5, Key: 7}}}, 220 }, 221 Mapping: []*profilev1.Mapping{{Id: 1, HasFunctions: true}}, 222 Location: []*profilev1.Location{ 223 {Id: 1, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 1, Line: 1}}}, 224 {Id: 2, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 2, Line: 1}}}, 225 }, 226 Function: []*profilev1.Function{ 227 {Id: 1, Name: 3, SystemName: 3, Filename: 4, StartLine: 1}, 228 {Id: 2, Name: 5, SystemName: 5, Filename: 4, StartLine: 1}, 229 }, 230 StringTable: []string{ 231 "", 232 "cpu", "nanoseconds", 233 "main", "main.go", 234 "foo", "bar", "baz", 235 }, 236 PeriodType: &profilev1.ValueType{Type: 1, Unit: 2}, 237 TimeNanos: 1577836800000000000, 238 }) 239 } 240 241 func Test_sanitizeReferences(t *testing.T) { 242 type testCase struct { 243 name string 244 profile *profilev1.Profile 245 expected *profilev1.Profile 246 } 247 248 testCases := []testCase{ 249 { 250 name: "string_reference", 251 profile: &profilev1.Profile{ 252 SampleType: []*profilev1.ValueType{{Type: 10, Unit: 10}}, 253 Sample: []*profilev1.Sample{{Label: []*profilev1.Label{{Key: 10, Str: 10, NumUnit: 10}}}}, 254 Mapping: []*profilev1.Mapping{{Filename: 10, BuildId: 10}}, 255 Function: []*profilev1.Function{{Name: 10, SystemName: 10, Filename: 10}}, 256 DropFrames: 10, 257 KeepFrames: 10, 258 PeriodType: &profilev1.ValueType{Type: 10, Unit: 10}, 259 DefaultSampleType: 10, 260 Comment: []int64{0, 10}, 261 StringTable: []string{}, 262 }, 263 expected: &profilev1.Profile{ 264 SampleType: []*profilev1.ValueType{{}}, 265 Sample: []*profilev1.Sample{}, 266 Mapping: []*profilev1.Mapping{{Id: 1}}, 267 Function: []*profilev1.Function{{Id: 1}}, 268 PeriodType: &profilev1.ValueType{}, 269 Comment: []int64{0, 0}, 270 StringTable: []string{""}, 271 }, 272 }, 273 { 274 name: "zero_string_non_0", 275 profile: &profilev1.Profile{ 276 SampleType: []*profilev1.ValueType{{Type: 10, Unit: 10}}, 277 Sample: []*profilev1.Sample{{Label: []*profilev1.Label{{Key: 10, Str: 10, NumUnit: 10}}}}, 278 Mapping: []*profilev1.Mapping{{Filename: 10, BuildId: 10}}, 279 Function: []*profilev1.Function{{Name: 10, Filename: 0, SystemName: 2}}, 280 DropFrames: 10, 281 KeepFrames: 10, 282 PeriodType: &profilev1.ValueType{Type: 10, Unit: 10}, 283 DefaultSampleType: 10, 284 Comment: []int64{1, 10}, 285 StringTable: []string{"foo", "", "bar"}, 286 }, 287 expected: &profilev1.Profile{ 288 SampleType: []*profilev1.ValueType{{}}, 289 Sample: []*profilev1.Sample{}, 290 Mapping: []*profilev1.Mapping{{Id: 1}}, 291 Function: []*profilev1.Function{{Id: 1, Filename: 1, SystemName: 2}}, 292 PeriodType: &profilev1.ValueType{}, 293 Comment: []int64{0, 0}, 294 StringTable: []string{"", "foo", "bar"}, 295 }, 296 }, 297 { 298 name: "zero_string_missing", 299 profile: &profilev1.Profile{ 300 SampleType: []*profilev1.ValueType{{Type: 10, Unit: 10}}, 301 Sample: []*profilev1.Sample{{Label: []*profilev1.Label{{Key: 10, Str: 10, NumUnit: 10}}}}, 302 Mapping: []*profilev1.Mapping{{Filename: 10, BuildId: 10}}, 303 Function: []*profilev1.Function{{Name: 0, SystemName: 0, Filename: 1}}, 304 DropFrames: 10, 305 KeepFrames: 10, 306 PeriodType: &profilev1.ValueType{Type: 10, Unit: 10}, 307 DefaultSampleType: 10, 308 StringTable: []string{"foo", "bar"}, 309 }, 310 expected: &profilev1.Profile{ 311 SampleType: []*profilev1.ValueType{{}}, 312 Sample: []*profilev1.Sample{}, 313 Mapping: []*profilev1.Mapping{{Id: 1}}, 314 Function: []*profilev1.Function{{Id: 1, Name: 2, SystemName: 2, Filename: 1}}, 315 PeriodType: &profilev1.ValueType{}, 316 StringTable: []string{"", "bar", "foo"}, 317 }, 318 }, 319 { 320 name: "multiple_zero_strings", 321 profile: &profilev1.Profile{ 322 SampleType: []*profilev1.ValueType{{}}, 323 Sample: []*profilev1.Sample{ 324 {LocationId: []uint64{1}, Value: []int64{1}, Label: []*profilev1.Label{{Key: 1, Str: 5}}}, 325 }, 326 Location: []*profilev1.Location{{Id: 1, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 1}}}}, 327 Function: []*profilev1.Function{{Id: 1, Name: 1, SystemName: 1, Filename: 2}}, 328 Mapping: []*profilev1.Mapping{{Id: 1, Filename: 1}}, 329 StringTable: []string{"", "foo", "", "", "", "bar"}, 330 }, 331 expected: &profilev1.Profile{ 332 SampleType: []*profilev1.ValueType{{}}, 333 Sample: []*profilev1.Sample{ 334 {LocationId: []uint64{1}, Value: []int64{1}, Label: []*profilev1.Label{{Key: 1, Str: 5}}}, 335 }, 336 Location: []*profilev1.Location{{Id: 1, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 1}}}}, 337 Function: []*profilev1.Function{{Id: 1, Name: 1, SystemName: 1, Filename: 2}}, 338 Mapping: []*profilev1.Mapping{{Id: 1, Filename: 1}}, 339 StringTable: []string{"", "foo", "", "", "", "bar"}, 340 }, 341 }, 342 { 343 name: "mapping_reference", 344 profile: &profilev1.Profile{ 345 Sample: []*profilev1.Sample{ 346 {LocationId: []uint64{2, 1}}, 347 {LocationId: []uint64{3, 2, 1}}, 348 }, 349 Location: []*profilev1.Location{ 350 {Id: 1, MappingId: 1, Address: 1}, 351 {Id: 3, MappingId: 5, Address: 2}, 352 {Id: 2, MappingId: 0, Address: 3}, 353 }, 354 Mapping: []*profilev1.Mapping{ 355 {Id: 1}, 356 }, 357 }, 358 expected: &profilev1.Profile{ 359 Sample: []*profilev1.Sample{ 360 {LocationId: []uint64{2, 1}}, 361 }, 362 Location: []*profilev1.Location{ 363 {Id: 1, MappingId: 1, Address: 1}, 364 {Id: 2, MappingId: 2, Address: 3}, 365 }, 366 Mapping: []*profilev1.Mapping{ 367 {Id: 1}, 368 {Id: 2}, 369 }, 370 StringTable: []string{""}, 371 }, 372 }, 373 { 374 name: "location_reference", 375 profile: &profilev1.Profile{ 376 Sample: []*profilev1.Sample{ 377 {LocationId: []uint64{1, 0}}, 378 {LocationId: []uint64{5}}, 379 }, 380 Location: []*profilev1.Location{ 381 {Id: 1, MappingId: 1, Address: 0xa}, 382 {Id: 0, MappingId: 0, Address: 0xa}, 383 }, 384 }, 385 expected: &profilev1.Profile{ 386 Sample: []*profilev1.Sample{}, 387 Location: []*profilev1.Location{ 388 {Id: 1, MappingId: 1, Address: 0xa}, 389 }, 390 Mapping: []*profilev1.Mapping{ 391 {Id: 1}, 392 }, 393 StringTable: []string{""}, 394 }, 395 }, 396 { 397 name: "function_reference", 398 profile: &profilev1.Profile{ 399 Sample: []*profilev1.Sample{ 400 {LocationId: []uint64{2, 1}}, 401 }, 402 Location: []*profilev1.Location{ 403 {Id: 2, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 5}}}, 404 {Id: 3, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 10}}}, 405 {Id: 1, MappingId: 1, Line: []*profilev1.Line{{}}}, 406 }, 407 Function: []*profilev1.Function{ 408 {Id: 10}, 409 }, 410 }, 411 expected: &profilev1.Profile{ 412 Sample: []*profilev1.Sample{}, 413 Location: []*profilev1.Location{}, 414 Function: []*profilev1.Function{ 415 {Id: 1}, 416 }, 417 StringTable: []string{""}, 418 }, 419 }, 420 { 421 name: "nil_profile", 422 profile: nil, 423 expected: nil, 424 }, 425 { 426 name: "nil_sample_type", 427 profile: &profilev1.Profile{ 428 SampleType: []*profilev1.ValueType{nil}, 429 }, 430 expected: &profilev1.Profile{ 431 SampleType: []*profilev1.ValueType{}, 432 StringTable: []string{""}, 433 }, 434 }, 435 { 436 name: "nil_sample", 437 profile: &profilev1.Profile{ 438 SampleType: []*profilev1.ValueType{{}}, 439 Sample: []*profilev1.Sample{nil}, 440 }, 441 expected: &profilev1.Profile{ 442 SampleType: []*profilev1.ValueType{{}}, 443 Sample: []*profilev1.Sample{}, 444 StringTable: []string{""}, 445 }, 446 }, 447 { 448 name: "nil_location", 449 profile: &profilev1.Profile{ 450 SampleType: []*profilev1.ValueType{{}}, 451 Sample: []*profilev1.Sample{{LocationId: []uint64{1}, Value: []int64{1}}}, 452 Location: []*profilev1.Location{nil}, 453 }, 454 expected: &profilev1.Profile{ 455 SampleType: []*profilev1.ValueType{{}}, 456 Sample: []*profilev1.Sample{}, 457 Location: []*profilev1.Location{}, 458 StringTable: []string{""}, 459 }, 460 }, 461 { 462 name: "nil_function", 463 profile: &profilev1.Profile{ 464 SampleType: []*profilev1.ValueType{{}}, 465 Sample: []*profilev1.Sample{{LocationId: []uint64{1}, Value: []int64{1}}}, 466 Location: []*profilev1.Location{{Line: []*profilev1.Line{{FunctionId: 1}}}}, 467 Function: []*profilev1.Function{nil}, 468 }, 469 expected: &profilev1.Profile{ 470 SampleType: []*profilev1.ValueType{{}}, 471 Sample: []*profilev1.Sample{}, 472 Location: []*profilev1.Location{}, 473 Function: []*profilev1.Function{}, 474 Mapping: []*profilev1.Mapping{{Id: 1}}, 475 StringTable: []string{""}, 476 }, 477 }, 478 { 479 name: "nil_mapping", 480 profile: &profilev1.Profile{ 481 SampleType: []*profilev1.ValueType{{}}, 482 Sample: []*profilev1.Sample{{LocationId: []uint64{1}, Value: []int64{1}}}, 483 Location: []*profilev1.Location{{MappingId: 1, Line: []*profilev1.Line{{FunctionId: 1}}}}, 484 Function: []*profilev1.Function{nil}, 485 Mapping: []*profilev1.Mapping{nil}, 486 }, 487 expected: &profilev1.Profile{ 488 SampleType: []*profilev1.ValueType{{}}, 489 Sample: []*profilev1.Sample{}, 490 Location: []*profilev1.Location{}, 491 Function: []*profilev1.Function{}, 492 Mapping: []*profilev1.Mapping{}, 493 StringTable: []string{""}, 494 }, 495 }, 496 } 497 498 for _, tc := range testCases { 499 tc := tc 500 t.Run(tc.name, func(t *testing.T) { 501 sanitizeProfile(tc.profile, new(sanitizeStats)) 502 assert.Equal(t, tc.expected, tc.profile) 503 }) 504 } 505 } 506 507 func Test_sanitize_fixtures(t *testing.T) { 508 require.NoError(t, filepath.WalkDir("testdata", func(path string, d fs.DirEntry, err error) error { 509 switch { 510 case err != nil: 511 return err 512 case filepath.Ext(path) == ".txt": 513 return nil 514 case d.IsDir(): 515 switch d.Name() { 516 case "fuzz": 517 case "malformed": 518 return fs.SkipDir 519 default: 520 return nil 521 } 522 } 523 524 t.Run(path, func(t *testing.T) { 525 f, err := OpenFile(path) 526 require.NoError(t, err) 527 c := f.CloneVT() 528 sanitizeProfile(f.Profile, new(sanitizeStats)) 529 assert.Equal(t, len(c.Sample), len(f.Sample)) 530 assert.Equal(t, len(c.Location), len(f.Location)) 531 assert.Equal(t, len(c.Function), len(f.Function)) 532 assert.Equal(t, len(c.StringTable), len(f.StringTable)) 533 if len(c.Mapping) != 0 { 534 assert.Equal(t, len(c.Mapping), len(f.Mapping)) 535 } else { 536 assert.Equal(t, 1, len(f.Mapping)) 537 } 538 }) 539 return nil 540 })) 541 } 542 543 func TestFromProfile(t *testing.T) { 544 out, err := FromProfile(testhelper.FooBarProfile) 545 require.NoError(t, err) 546 data, err := proto.Marshal(out) 547 require.NoError(t, err) 548 outProfile, err := profile.ParseUncompressed(data) 549 require.NoError(t, err) 550 551 require.Equal(t, testhelper.FooBarProfile, outProfile) 552 } 553 554 const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 555 556 func RandStringBytes(n int) string { 557 b := make([]byte, n) 558 for i := range b { 559 b[i] = letterBytes[rand.Intn(len(letterBytes))] 560 } 561 return string(b) 562 } 563 564 func BenchmarkNormalize(b *testing.B) { 565 profiles := make([]*Profile, b.N) 566 for i := 0; i < b.N; i++ { 567 builder := testhelper.NewProfileBuilder(0).CPUProfile() 568 // 10% of samples should be dropped. 569 for i := 0; i < 1000; i++ { 570 builder.ForStacktraceString(RandStringBytes(3), RandStringBytes(3)).AddSamples(0) 571 } 572 for i := 0; i < 10000; i++ { 573 builder.ForStacktraceString(RandStringBytes(3), RandStringBytes(3)).AddSamples(1) 574 } 575 profiles[i] = &Profile{Profile: builder.Profile} 576 } 577 578 b.ResetTimer() 579 b.ReportAllocs() 580 for i := 0; i < b.N; i++ { 581 profiles[i].Normalize() 582 } 583 } 584 585 func TestRemoveDuplicateSampleStacktraces(t *testing.T) { 586 p, err := OpenFile("testdata/heap") 587 require.NoError(t, err) 588 duplicate := countSampleDuplicates(p) 589 total := len(p.Sample) 590 t.Log("total dupe", duplicate) 591 t.Log("total samples", total) 592 593 p.Normalize() 594 595 require.Equal(t, 0, countSampleDuplicates(p), "duplicates should be removed") 596 require.Equal(t, total-duplicate, len(p.Sample), "unexpected total samples") 597 } 598 599 func TestEmptyMappingJava(t *testing.T) { 600 p, err := OpenFile("testdata/profile_java") 601 require.NoError(t, err) 602 require.Len(t, p.Mapping, 0) 603 604 p.Normalize() 605 require.Len(t, p.Mapping, 1) 606 607 for _, loc := range p.Location { 608 require.Equal(t, loc.MappingId, uint64(1)) 609 } 610 } 611 612 func countSampleDuplicates(p *Profile) int { 613 hashes := p.hasher.Hashes(p.Sample) 614 uniq := map[uint64][]*profilev1.Sample{} 615 for i, s := range p.Sample { 616 617 if _, ok := uniq[hashes[i]]; !ok { 618 uniq[hashes[i]] = []*profilev1.Sample{s} 619 continue 620 } 621 uniq[hashes[i]] = append(uniq[hashes[i]], s) 622 } 623 totalDupe := 0 624 for _, v := range uniq { 625 totalDupe += len(v) - 1 626 } 627 return totalDupe 628 } 629 630 var prof *profilev1.Profile 631 632 func BenchmarkFromRawBytes(b *testing.B) { 633 data, err := os.ReadFile("testdata/heap") 634 require.NoError(b, err) 635 b.ResetTimer() 636 b.ReportAllocs() 637 for i := 0; i < b.N; i++ { 638 err := FromBytes(data, func(p *profilev1.Profile, i int) error { 639 prof = p 640 return nil 641 }) 642 if err != nil { 643 b.Fatal(err) 644 } 645 } 646 } 647 648 func Test_GroupSamplesByLabels(t *testing.T) { 649 type testCase struct { 650 description string 651 input *profilev1.Profile 652 expected []SampleGroup 653 } 654 655 testCases := []*testCase{ 656 { 657 description: "no samples", 658 input: new(profilev1.Profile), 659 expected: nil, 660 }, 661 { 662 description: "single label set", 663 input: &profilev1.Profile{ 664 Sample: []*profilev1.Sample{ 665 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 666 }, 667 }, 668 expected: []SampleGroup{ 669 { 670 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}, 671 Samples: []*profilev1.Sample{ 672 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 673 }, 674 }, 675 }, 676 }, 677 { 678 description: "all sets are unique", 679 input: &profilev1.Profile{ 680 Sample: []*profilev1.Sample{ 681 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 682 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 683 }, 684 }, 685 expected: []SampleGroup{ 686 { 687 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}, 688 Samples: []*profilev1.Sample{ 689 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 690 }, 691 }, 692 { 693 Labels: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}, 694 Samples: []*profilev1.Sample{ 695 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 696 }, 697 }, 698 }, 699 }, 700 { 701 description: "ends with unique label set", 702 input: &profilev1.Profile{ 703 Sample: []*profilev1.Sample{ 704 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 705 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 706 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 707 }, 708 }, 709 expected: []SampleGroup{ 710 { 711 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}, 712 Samples: []*profilev1.Sample{ 713 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 714 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 715 }, 716 }, 717 { 718 Labels: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}, 719 Samples: []*profilev1.Sample{ 720 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 721 }, 722 }, 723 }, 724 }, 725 { 726 description: "starts with unique label set", 727 input: &profilev1.Profile{ 728 Sample: []*profilev1.Sample{ 729 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 730 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 731 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 732 }, 733 }, 734 expected: []SampleGroup{ 735 { 736 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}, 737 Samples: []*profilev1.Sample{ 738 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 739 }, 740 }, 741 { 742 Labels: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}, 743 Samples: []*profilev1.Sample{ 744 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 745 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 746 }, 747 }, 748 }, 749 }, 750 { 751 description: "no unique sets", 752 input: &profilev1.Profile{ 753 Sample: []*profilev1.Sample{ 754 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 755 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 756 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 757 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 758 }, 759 }, 760 expected: []SampleGroup{ 761 { 762 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}, 763 Samples: []*profilev1.Sample{ 764 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 765 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 766 }, 767 }, 768 { 769 Labels: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}, 770 Samples: []*profilev1.Sample{ 771 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 772 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 773 }, 774 }, 775 }, 776 }, 777 } 778 779 for _, tc := range testCases { 780 tc := tc 781 t.Run(tc.description, func(t *testing.T) { 782 require.Equal(t, tc.expected, GroupSamplesByLabels(tc.input)) 783 }) 784 } 785 } 786 787 func Test_FilterLabelsInPlace(t *testing.T) { 788 type testCase struct { 789 labels []*profilev1.Label 790 keys []int64 791 expectedOrder []*profilev1.Label 792 expectedIndex int 793 } 794 795 testCases := []testCase{ 796 { 797 labels: []*profilev1.Label{ 798 {Key: 1, Str: 100}, 799 {Key: 2, Str: 200}, 800 {Key: 3, Str: 300}, 801 {Key: 4, Str: 400}, 802 {Key: 5, Str: 500}, 803 }, 804 keys: []int64{2, 4}, 805 expectedOrder: []*profilev1.Label{ 806 {Key: 2, Str: 200}, 807 {Key: 4, Str: 400}, 808 {Key: 3, Str: 300}, 809 {Key: 1, Str: 100}, 810 {Key: 5, Str: 500}, 811 }, 812 expectedIndex: 2, 813 }, 814 { 815 labels: []*profilev1.Label{ 816 {Key: 1, Str: 100}, 817 {Key: 2, Str: 200}, 818 {Key: 3, Str: 300}, 819 {Key: 4, Str: 400}, 820 {Key: 5, Str: 500}, 821 }, 822 keys: []int64{1, 3, 5}, 823 expectedOrder: []*profilev1.Label{ 824 {Key: 1, Str: 100}, 825 {Key: 3, Str: 300}, 826 {Key: 5, Str: 500}, 827 {Key: 4, Str: 400}, 828 {Key: 2, Str: 200}, 829 }, 830 expectedIndex: 3, 831 }, 832 { 833 labels: []*profilev1.Label{ 834 {Key: 1, Str: 100}, 835 {Key: 2, Str: 200}, 836 {Key: 3, Str: 300}, 837 {Key: 4, Str: 400}, 838 {Key: 5, Str: 500}, 839 }, 840 keys: []int64{6, 7}, 841 expectedOrder: []*profilev1.Label{ 842 {Key: 1, Str: 100}, 843 {Key: 2, Str: 200}, 844 {Key: 3, Str: 300}, 845 {Key: 4, Str: 400}, 846 {Key: 5, Str: 500}, 847 }, 848 expectedIndex: 0, 849 }, 850 { 851 labels: []*profilev1.Label{ 852 {Key: 3, Str: 300}, 853 {Key: 4, Str: 400}, 854 {Key: 5, Str: 500}, 855 }, 856 keys: []int64{1, 2}, 857 expectedOrder: []*profilev1.Label{ 858 {Key: 3, Str: 300}, 859 {Key: 4, Str: 400}, 860 {Key: 5, Str: 500}, 861 }, 862 expectedIndex: 0, 863 }, 864 { 865 labels: []*profilev1.Label{ 866 {Key: 3, Str: 300}, 867 {Key: 4, Str: 400}, 868 {Key: 5, Str: 500}, 869 }, 870 keys: []int64{4}, 871 expectedOrder: []*profilev1.Label{ 872 {Key: 4, Str: 400}, 873 {Key: 3, Str: 300}, 874 {Key: 5, Str: 500}, 875 }, 876 expectedIndex: 1, 877 }, 878 { 879 labels: []*profilev1.Label{ 880 {Key: 3, Str: 300}, 881 {Key: 4, Str: 400}, 882 {Key: 5, Str: 500}, 883 }, 884 keys: []int64{3}, 885 expectedOrder: []*profilev1.Label{ 886 {Key: 3, Str: 300}, 887 {Key: 4, Str: 400}, 888 {Key: 5, Str: 500}, 889 }, 890 expectedIndex: 1, 891 }, 892 { 893 labels: []*profilev1.Label{ 894 {Key: 3, Str: 300}, 895 {Key: 4, Str: 400}, 896 {Key: 5, Str: 500}, 897 }, 898 keys: []int64{5}, 899 expectedOrder: []*profilev1.Label{ 900 {Key: 5, Str: 500}, 901 {Key: 4, Str: 400}, 902 {Key: 3, Str: 300}, 903 }, 904 expectedIndex: 1, 905 }, 906 { 907 labels: []*profilev1.Label{ 908 {Key: 3, Str: 300}, 909 {Key: 4, Str: 400}, 910 {Key: 5, Str: 500}, 911 }, 912 expectedOrder: []*profilev1.Label{ 913 {Key: 3, Str: 300}, 914 {Key: 4, Str: 400}, 915 {Key: 5, Str: 500}, 916 }, 917 expectedIndex: 0, 918 }, 919 } 920 921 for _, tc := range testCases { 922 tc := tc 923 t.Run("", func(t *testing.T) { 924 boundaryIdx := FilterLabelsInPlace(tc.labels, tc.keys) 925 require.Equal(t, tc.expectedOrder, tc.labels) 926 require.Equal(t, tc.expectedIndex, boundaryIdx) 927 }) 928 } 929 } 930 931 func Test_GroupSamplesWithout(t *testing.T) { 932 type testCase struct { 933 description string 934 input *profilev1.Profile 935 expected []SampleGroup 936 without []int64 937 } 938 939 testCases := []*testCase{ 940 { 941 description: "no samples", 942 input: new(profilev1.Profile), 943 expected: nil, 944 }, 945 { 946 description: "no sample labels", 947 input: &profilev1.Profile{ 948 Sample: []*profilev1.Sample{{}, {}}, 949 }, 950 expected: []SampleGroup{ 951 { 952 Samples: []*profilev1.Sample{{}, {}}, 953 }, 954 }, 955 }, 956 { 957 description: "without all, single label set", 958 input: &profilev1.Profile{ 959 Sample: []*profilev1.Sample{ 960 {Label: []*profilev1.Label{{Key: 2, Str: 2}, {Key: 1, Str: 1}}}, 961 }, 962 }, 963 without: []int64{1, 2}, 964 expected: []SampleGroup{ 965 { 966 Labels: []*profilev1.Label{}, 967 Samples: []*profilev1.Sample{ 968 {Label: []*profilev1.Label{{Key: 2, Str: 2}, {Key: 1, Str: 1}}}, 969 }, 970 }, 971 }, 972 }, 973 { 974 description: "without all, many label sets", 975 input: &profilev1.Profile{ 976 Sample: []*profilev1.Sample{ 977 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 978 {Label: []*profilev1.Label{{Key: 1, Str: 3}, {Key: 2, Str: 4}}}, 979 {Label: []*profilev1.Label{{Key: 1, Str: 3}}}, 980 {Label: []*profilev1.Label{}}, 981 }, 982 }, 983 without: []int64{1, 2}, 984 expected: []SampleGroup{ 985 { 986 Labels: []*profilev1.Label{}, 987 Samples: []*profilev1.Sample{ 988 {Label: []*profilev1.Label{{Key: 2, Str: 2}, {Key: 1, Str: 1}}}, 989 {Label: []*profilev1.Label{{Key: 2, Str: 4}, {Key: 1, Str: 3}}}, 990 {Label: []*profilev1.Label{{Key: 1, Str: 3}}}, 991 {Label: []*profilev1.Label{}}, 992 }, 993 }, 994 }, 995 }, 996 { 997 description: "without none", 998 input: &profilev1.Profile{ 999 Sample: []*profilev1.Sample{ 1000 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 1001 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}}, 1002 {Label: []*profilev1.Label{{Key: 1, Str: 3}}}, 1003 {Label: []*profilev1.Label{}}, 1004 }, 1005 }, 1006 without: []int64{}, 1007 expected: []SampleGroup{ 1008 { 1009 Labels: []*profilev1.Label{}, 1010 Samples: []*profilev1.Sample{ 1011 {Label: []*profilev1.Label{}}, 1012 }, 1013 }, 1014 { 1015 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}}, 1016 Samples: []*profilev1.Sample{ 1017 {Label: []*profilev1.Label{}}, 1018 {Label: []*profilev1.Label{}}, 1019 }, 1020 }, 1021 { 1022 Labels: []*profilev1.Label{{Key: 1, Str: 3}}, 1023 Samples: []*profilev1.Sample{ 1024 {Label: []*profilev1.Label{}}, 1025 }, 1026 }, 1027 }, 1028 }, 1029 { 1030 description: "without single, multiple groups", 1031 input: &profilev1.Profile{ 1032 Sample: []*profilev1.Sample{ 1033 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 100}, {Key: 3, Str: 3}}}, 1034 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 101}, {Key: 3, Str: 3}}}, 1035 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 102}, {Key: 3, Str: 4}}}, 1036 {Label: []*profilev1.Label{{Key: 1, Str: 1}}}, 1037 {Label: []*profilev1.Label{}}, 1038 }, 1039 }, 1040 without: []int64{2}, 1041 expected: []SampleGroup{ 1042 { 1043 Labels: []*profilev1.Label{}, 1044 Samples: []*profilev1.Sample{ 1045 {Label: []*profilev1.Label{}}, 1046 }, 1047 }, 1048 { 1049 Labels: []*profilev1.Label{{Key: 1, Str: 1}}, 1050 Samples: []*profilev1.Sample{ 1051 {Label: []*profilev1.Label{}}, 1052 }, 1053 }, 1054 { 1055 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 3, Str: 3}}, 1056 Samples: []*profilev1.Sample{ 1057 {Label: []*profilev1.Label{{Key: 2, Str: 100}}}, 1058 {Label: []*profilev1.Label{{Key: 2, Str: 101}}}, 1059 }, 1060 }, 1061 { 1062 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 3, Str: 4}}, 1063 Samples: []*profilev1.Sample{ 1064 {Label: []*profilev1.Label{{Key: 2, Str: 102}}}, 1065 }, 1066 }, 1067 }, 1068 }, 1069 { 1070 description: "without single, non-existent", 1071 input: &profilev1.Profile{ 1072 Sample: []*profilev1.Sample{ 1073 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}, {Key: 3, Str: 3}}}, 1074 {Label: []*profilev1.Label{{Key: 1, Str: 1}}}, 1075 {Label: []*profilev1.Label{}}, 1076 }, 1077 }, 1078 without: []int64{7}, 1079 expected: []SampleGroup{ 1080 { 1081 Labels: []*profilev1.Label{}, 1082 Samples: []*profilev1.Sample{ 1083 {Label: []*profilev1.Label{}}, 1084 }, 1085 }, 1086 { 1087 Labels: []*profilev1.Label{{Key: 1, Str: 1}}, 1088 Samples: []*profilev1.Sample{ 1089 {Label: []*profilev1.Label{}}, 1090 }, 1091 }, 1092 { 1093 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}, {Key: 3, Str: 3}}, 1094 Samples: []*profilev1.Sample{ 1095 {Label: []*profilev1.Label{}}, 1096 }, 1097 }, 1098 }, 1099 }, 1100 { 1101 description: "without multiple, non-existent mixed", 1102 input: &profilev1.Profile{ 1103 Sample: []*profilev1.Sample{ 1104 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}, {Key: 3, Str: 3}}}, 1105 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}, {Key: 3, Str: 13}}}, 1106 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}, {Key: 3, Str: 3}, {Key: 5, Str: 5}}}, 1107 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 2}, {Key: 3, Str: 13}, {Key: 5, Str: 15}}}, 1108 {Label: []*profilev1.Label{{Key: 1, Str: 1}}}, 1109 {Label: []*profilev1.Label{}}, 1110 }, 1111 }, 1112 without: []int64{2, 3, 5}, 1113 expected: []SampleGroup{ 1114 { 1115 Labels: []*profilev1.Label{}, 1116 Samples: []*profilev1.Sample{ 1117 {Label: []*profilev1.Label{}}, 1118 }, 1119 }, 1120 { 1121 Labels: []*profilev1.Label{{Key: 1, Str: 1}}, 1122 Samples: []*profilev1.Sample{ 1123 {Label: []*profilev1.Label{{Key: 3, Str: 3}, {Key: 2, Str: 2}}}, 1124 {Label: []*profilev1.Label{{Key: 3, Str: 13}, {Key: 2, Str: 2}}}, 1125 {Label: []*profilev1.Label{{Key: 5, Str: 5}, {Key: 3, Str: 3}, {Key: 2, Str: 2}}}, 1126 {Label: []*profilev1.Label{{Key: 5, Str: 15}, {Key: 3, Str: 13}, {Key: 2, Str: 2}}}, 1127 {Label: []*profilev1.Label{}}, 1128 }, 1129 }, 1130 }, 1131 }, 1132 { 1133 description: "without single existent, single group", 1134 input: &profilev1.Profile{ 1135 Sample: []*profilev1.Sample{ 1136 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 100}, {Key: 3, Str: 3}}}, 1137 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 3, Str: 3}}}, 1138 }, 1139 }, 1140 without: []int64{2}, 1141 expected: []SampleGroup{ 1142 { 1143 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 3, Str: 3}}, 1144 Samples: []*profilev1.Sample{ 1145 {Label: []*profilev1.Label{{Key: 2, Str: 100}}}, 1146 {Label: []*profilev1.Label{}}, 1147 }, 1148 }, 1149 }, 1150 }, 1151 { 1152 description: "Testcase for extra labels capacity (restoreRemovedLabels nil check)", 1153 input: &profilev1.Profile{ 1154 Sample: []*profilev1.Sample{ 1155 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 2, Str: 100}, {Key: 3, Str: 3}, nil, nil}[:3]}, 1156 {Label: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 3, Str: 3}}}, 1157 }[:2], 1158 }, 1159 without: []int64{2}, 1160 expected: []SampleGroup{ 1161 { 1162 Labels: []*profilev1.Label{{Key: 1, Str: 1}, {Key: 3, Str: 3}}, 1163 Samples: []*profilev1.Sample{ 1164 {Label: []*profilev1.Label{{Key: 2, Str: 100}}}, 1165 {Label: []*profilev1.Label{}}, 1166 }, 1167 }, 1168 }, 1169 }, 1170 } 1171 1172 for _, tc := range testCases { 1173 tc := tc 1174 t.Run(tc.description, func(t *testing.T) { 1175 require.Equal(t, tc.expected, GroupSamplesWithoutLabelsByKey(tc.input, tc.without)) 1176 for _, g := range tc.expected { 1177 for _, sample := range g.Samples { 1178 for _, label := range sample.Label { 1179 assert.NotNil(t, label) 1180 } 1181 } 1182 } 1183 }) 1184 } 1185 } 1186 1187 func Test_SampleExporter_WholeProfile(t *testing.T) { 1188 p, err := OpenFile("testdata/heap") 1189 require.NoError(t, err) 1190 e := NewSampleExporter(p.Profile) 1191 n := new(profilev1.Profile) 1192 e.ExportSamples(n, p.Sample) 1193 1194 // Samples are modified in-place, therefore 1195 // we have to re-read the profile. 1196 p, err = OpenFile("testdata/heap") 1197 require.NoError(t, err) 1198 requireProfilesEqual(t, p.Profile, n) 1199 } 1200 1201 func requireProfilesEqual(t *testing.T, expected, actual *profilev1.Profile) { 1202 require.Equal(t, expected.SampleType, actual.SampleType) 1203 require.Equal(t, expected.PeriodType, actual.PeriodType) 1204 require.Equal(t, expected.Period, actual.Period) 1205 require.Equal(t, expected.Comment, actual.Comment) 1206 require.Equal(t, expected.DropFrames, actual.DropFrames) 1207 require.Equal(t, expected.KeepFrames, actual.KeepFrames) 1208 require.Equal(t, expected.DefaultSampleType, actual.DefaultSampleType) 1209 require.Equal(t, expected.TimeNanos, actual.TimeNanos) 1210 require.Equal(t, expected.DurationNanos, actual.DurationNanos) 1211 require.Equal(t, expected.Sample, actual.Sample) 1212 require.Equal(t, expected.Location, actual.Location) 1213 require.Equal(t, expected.Function, actual.Function) 1214 require.Equal(t, expected.Mapping, actual.Mapping) 1215 require.Equal(t, expected.StringTable, actual.StringTable) 1216 } 1217 1218 func Test_SampleExporter_Partial(t *testing.T) { 1219 p, err := OpenFile("testdata/go.cpu.labels.pprof") 1220 require.NoError(t, err) 1221 e := NewSampleExporter(p.Profile) 1222 n := new(profilev1.Profile) 1223 e.ExportSamples(n, p.Sample[:2]) 1224 expected := &profilev1.Profile{ 1225 SampleType: []*profilev1.ValueType{ 1226 { 1227 Type: 1, 1228 Unit: 2, 1229 }, 1230 { 1231 Type: 3, 1232 Unit: 4, 1233 }, 1234 }, 1235 Sample: []*profilev1.Sample{ 1236 { 1237 LocationId: []uint64{1, 2, 3, 4, 5, 6, 3, 7, 8, 9}, 1238 Value: []int64{1, 10000000}, 1239 Label: []*profilev1.Label{ 1240 {Key: 5, Str: 6}, 1241 {Key: 7, Str: 8}, 1242 {Key: 9, Str: 10}, 1243 }, 1244 }, 1245 { 1246 LocationId: []uint64{1, 10, 6, 3, 7, 11, 12, 6, 3, 7, 8, 9}, 1247 Value: []int64{1, 10000000}, 1248 Label: []*profilev1.Label{ 1249 {Key: 5, Str: 6}, 1250 {Key: 7, Str: 11}, 1251 {Key: 9, Str: 12}, 1252 }, 1253 }, 1254 }, 1255 Mapping: []*profilev1.Mapping{ 1256 { 1257 Id: 1, 1258 HasFunctions: true, 1259 }, 1260 }, 1261 Location: []*profilev1.Location{ 1262 { 1263 Id: 1, 1264 MappingId: 1, 1265 Address: 19497668, 1266 Line: []*profilev1.Line{{FunctionId: 1, Line: 19}}, 1267 }, 1268 { 1269 Id: 2, 1270 MappingId: 1, 1271 Address: 19498429, 1272 Line: []*profilev1.Line{{FunctionId: 2, Line: 43}}, 1273 }, 1274 { 1275 Id: 3, 1276 MappingId: 1, 1277 Address: 19267106, 1278 Line: []*profilev1.Line{{FunctionId: 3, Line: 40}}, 1279 }, 1280 { 1281 Id: 4, 1282 MappingId: 1, 1283 Address: 19499013, 1284 Line: []*profilev1.Line{{FunctionId: 4, Line: 42}}, 1285 }, 1286 { 1287 Id: 5, 1288 MappingId: 1, 1289 Address: 19499251, 1290 Line: []*profilev1.Line{{FunctionId: 5, Line: 68}}, 1291 }, 1292 { 1293 Id: 6, 1294 MappingId: 1, 1295 Address: 19285318, 1296 Line: []*profilev1.Line{{FunctionId: 6, Line: 101}}, 1297 }, 1298 { 1299 Id: 7, 1300 MappingId: 1, 1301 Address: 19285188, 1302 Line: []*profilev1.Line{{FunctionId: 7, Line: 101}}, 1303 }, 1304 { 1305 Id: 8, 1306 MappingId: 1, 1307 Address: 19499465, 1308 Line: []*profilev1.Line{{FunctionId: 8, Line: 65}}, 1309 }, 1310 { 1311 Id: 9, 1312 MappingId: 1, 1313 Address: 17007057, 1314 Line: []*profilev1.Line{{FunctionId: 9, Line: 250}}, 1315 }, 1316 { 1317 Id: 10, 1318 MappingId: 1, 1319 Address: 19497725, 1320 Line: []*profilev1.Line{{FunctionId: 10, Line: 31}}, 1321 }, 1322 { 1323 Id: 11, 1324 MappingId: 1, 1325 Address: 19498309, 1326 Line: []*profilev1.Line{{FunctionId: 11, Line: 30}}, 1327 }, 1328 { 1329 Id: 12, 1330 MappingId: 1, 1331 Address: 19499236, 1332 Line: []*profilev1.Line{{FunctionId: 5, Line: 67}}, 1333 }, 1334 }, 1335 Function: []*profilev1.Function{ 1336 { 1337 Id: 1, 1338 Name: 13, 1339 SystemName: 13, 1340 Filename: 14, 1341 }, 1342 { 1343 Id: 2, 1344 Name: 15, 1345 SystemName: 15, 1346 Filename: 14, 1347 }, 1348 { 1349 Id: 3, 1350 Name: 16, 1351 SystemName: 16, 1352 Filename: 17, 1353 }, 1354 { 1355 Id: 4, 1356 Name: 18, 1357 SystemName: 18, 1358 Filename: 14, 1359 }, 1360 { 1361 Id: 5, 1362 Name: 19, 1363 SystemName: 19, 1364 Filename: 14, 1365 }, 1366 { 1367 Id: 6, 1368 Name: 20, 1369 SystemName: 20, 1370 Filename: 21, 1371 }, 1372 { 1373 Id: 7, 1374 Name: 22, 1375 SystemName: 22, 1376 Filename: 21, 1377 }, 1378 { 1379 Id: 8, 1380 Name: 23, 1381 SystemName: 23, 1382 Filename: 14, 1383 }, 1384 { 1385 Id: 9, 1386 Name: 24, 1387 SystemName: 24, 1388 Filename: 25, 1389 }, 1390 { 1391 Id: 10, 1392 Name: 26, 1393 SystemName: 26, 1394 Filename: 14, 1395 }, 1396 { 1397 Id: 11, 1398 Name: 27, 1399 SystemName: 27, 1400 Filename: 14, 1401 }, 1402 }, 1403 StringTable: []string{ 1404 "", 1405 "samples", 1406 "count", 1407 "cpu", 1408 "nanoseconds", 1409 "foo", 1410 "bar", 1411 "profile_id", 1412 "c717c11b87121639", 1413 "function", 1414 "slow", 1415 "8c946fa4ae322f7f", 1416 "fast", 1417 "main.work", 1418 "/Users/kolesnikovae/Documents/src/pyroscope/examples/golang-push/simple/main.go", 1419 "main.slowFunction.func1", 1420 "runtime/pprof.Do", 1421 "/usr/local/go/src/runtime/pprof/runtime.go", 1422 "main.slowFunction", 1423 "main.main.func2", 1424 "github.com/pyroscope-io/client/pyroscope.TagWrapper.func1", 1425 "/Users/kolesnikovae/go/pkg/mod/github.com/pyroscope-io/client@v0.2.4-0.20220607180407-0ba26860ce5b/pyroscope/api.go", 1426 "github.com/pyroscope-io/client/pyroscope.TagWrapper", 1427 "main.main", 1428 "runtime.main", 1429 "/usr/local/go/src/runtime/proc.go", 1430 "main.fastFunction.func1", 1431 "main.fastFunction", 1432 }, 1433 TimeNanos: 1654798932062349000, 1434 DurationNanos: 10123363553, 1435 PeriodType: &profilev1.ValueType{ 1436 Type: 3, 1437 Unit: 4, 1438 }, 1439 Period: 10000000, 1440 } 1441 requireProfilesEqual(t, expected, n) 1442 } 1443 1444 func Test_GroupSamplesWithout_Go_CPU_profile(t *testing.T) { 1445 p, err := OpenFile("testdata/go.cpu.labels.pprof") 1446 require.NoError(t, err) 1447 1448 groups := GroupSamplesWithoutLabels(p.Profile, ProfileIDLabelName) 1449 require.Len(t, groups, 3) 1450 1451 assert.Equal(t, groups[0].Labels, []*profilev1.Label{{Key: 18, Str: 19}}) 1452 assert.Equal(t, len(groups[0].Samples), 5) 1453 1454 assert.Equal(t, groups[1].Labels, []*profilev1.Label{{Key: 18, Str: 19}, {Key: 22, Str: 23}}) 1455 assert.Equal(t, len(groups[1].Samples), 325) 1456 1457 assert.Equal(t, groups[2].Labels, []*profilev1.Label{{Key: 18, Str: 19}, {Key: 22, Str: 27}}) 1458 assert.Equal(t, len(groups[2].Samples), 150) 1459 } 1460 1461 func Test_GroupSamplesWithout_dotnet_profile(t *testing.T) { 1462 p, err := OpenFile("testdata/dotnet.labels.pprof") 1463 require.NoError(t, err) 1464 1465 groups := GroupSamplesWithoutLabels(p.Profile, ProfileIDLabelName) 1466 require.Len(t, groups, 1) 1467 assert.Equal(t, groups[0].Labels, []*profilev1.Label{{Key: 64, Str: 65}, {Key: 66, Str: 67}}) 1468 } 1469 1470 func Test_GroupSamplesWithout_single_group_with_optional_span_id(t *testing.T) { 1471 // pprof.Do(context.Background(), pprof.Labels("function", "slow", "qwe", "asd", "asdasd", "zxczxc"), func(c context.Context) { 1472 // work(40000) 1473 // pprof.Do(c, pprof.Labels("span_id", "239"), func(c context.Context) { 1474 // work(40000) 1475 // }) 1476 // }) 1477 p, err := OpenFile("testdata/single_group_with_optional_span_id.pb.gz") 1478 require.NoError(t, err) 1479 1480 groups := GroupSamplesWithoutLabels(p.Profile, SpanIDLabelName) 1481 require.Len(t, groups, 1) 1482 assert.Equal(t, groups[0].Labels, []*profilev1.Label{{Key: 5, Str: 6}, {Key: 7, Str: 8}, {Key: 9, Str: 10}}) 1483 } 1484 1485 func Test_GetProfileLanguage_go_cpu_profile(t *testing.T) { 1486 p, err := OpenFile("testdata/go.cpu.labels.pprof") 1487 require.NoError(t, err) 1488 1489 language := GetLanguage(p) 1490 assert.Equal(t, "go", language) 1491 } 1492 1493 func Test_GetProfileLanguage_go_heap_profile(t *testing.T) { 1494 p, err := OpenFile("testdata/heap") 1495 require.NoError(t, err) 1496 1497 language := GetLanguage(p) 1498 assert.Equal(t, "go", language) 1499 } 1500 1501 func Test_GetProfileLanguage_dotnet_profile(t *testing.T) { 1502 p, err := OpenFile("testdata/dotnet.labels.pprof") 1503 require.NoError(t, err) 1504 1505 language := GetLanguage(p) 1506 assert.Equal(t, "dotnet", language) 1507 } 1508 1509 func Test_GetProfileLanguage_java_profile(t *testing.T) { 1510 p, err := OpenFile("testdata/profile_java") 1511 require.NoError(t, err) 1512 1513 language := GetLanguage(p) 1514 assert.Equal(t, "java", language) 1515 } 1516 1517 func Test_GetProfileLanguage_python_profile(t *testing.T) { 1518 p, err := OpenFile("testdata/profile_python") 1519 require.NoError(t, err) 1520 1521 language := GetLanguage(p) 1522 assert.Equal(t, "python", language) 1523 } 1524 1525 func Test_GetProfileLanguage_ruby_profile(t *testing.T) { 1526 p, err := OpenFile("testdata/profile_ruby") 1527 require.NoError(t, err) 1528 1529 language := GetLanguage(p) 1530 assert.Equal(t, "ruby", language) 1531 } 1532 1533 func Test_GetProfileLanguage_nodejs_profile(t *testing.T) { 1534 p, err := OpenFile("testdata/profile_nodejs") 1535 require.NoError(t, err) 1536 1537 language := GetLanguage(p) 1538 assert.Equal(t, "nodejs", language) 1539 } 1540 1541 func Test_GetProfileLanguage_rust_profile(t *testing.T) { 1542 p, err := OpenFile("testdata/profile_rust") 1543 require.NoError(t, err) 1544 1545 language := GetLanguage(p) 1546 assert.Equal(t, "rust", language) 1547 } 1548 1549 func Benchmark_GetProfileLanguage(b *testing.B) { 1550 tests := []string{ 1551 "testdata/go.cpu.labels.pprof", 1552 "testdata/heap", 1553 "testdata/dotnet.labels.pprof", 1554 "testdata/profile_java", 1555 "testdata/profile_nodejs", 1556 "testdata/profile_python", 1557 "testdata/profile_ruby", 1558 "testdata/profile_rust", 1559 } 1560 1561 for _, testdata := range tests { 1562 f := testdata 1563 b.Run(testdata, func(b *testing.B) { 1564 p, err := OpenFile(f) 1565 require.NoError(b, err) 1566 b.ResetTimer() 1567 b.ReportAllocs() 1568 for i := 0; i < b.N; i++ { 1569 language := GetLanguage(p) 1570 if language == "unknown" { 1571 b.Fatal() 1572 } 1573 } 1574 }) 1575 } 1576 } 1577 1578 func Test_SetProfileMetadata(t *testing.T) { 1579 p := &profilev1.Profile{ 1580 SampleType: []*profilev1.ValueType{{}}, 1581 StringTable: []string{"", "qux"}, 1582 PeriodType: &profilev1.ValueType{}, 1583 } 1584 pt := &typesv1.ProfileType{ 1585 ID: "alfa", 1586 Name: "bravo", 1587 SampleType: "foo", 1588 SampleUnit: "bar", 1589 PeriodType: "baz", 1590 PeriodUnit: "qux", 1591 } 1592 SetProfileMetadata(p, pt, 1, 2) 1593 expected := &profilev1.Profile{ 1594 SampleType: []*profilev1.ValueType{{ 1595 Type: 3, // foo 1596 Unit: 2, // bar 1597 }}, 1598 StringTable: []string{"", "qux", "bar", "foo", "baz"}, 1599 PeriodType: &profilev1.ValueType{ 1600 Type: 4, // baz 1601 Unit: 1, // qux 1602 }, 1603 TimeNanos: 1, 1604 Period: 1, 1605 DefaultSampleType: 3, // foo 1606 } 1607 require.Equal(t, expected.String(), p.String()) 1608 } 1609 1610 func Test_pprof_zero_addr_no_line_locations(t *testing.T) { 1611 b, err := OpenFile("testdata/malformed/no_addr_no_line.pb.gz") 1612 require.NoError(t, err) 1613 1614 var found bool 1615 for _, loc := range b.Location { 1616 if len(loc.Line) == 0 && loc.Address == 0 { 1617 found = true 1618 break 1619 } 1620 } 1621 if !found { 1622 t.Fatal("invalid fixture") 1623 } 1624 1625 b.Normalize() 1626 for _, loc := range b.Location { 1627 if len(loc.Line) == 0 && loc.Address == 0 { 1628 t.Fatal("found location without lines and address") 1629 } 1630 } 1631 1632 expected := "samples_total=2 location_empty=1 sample_location_invalid=1" 1633 assert.Equal(t, expected, b.stats.pretty()) 1634 } 1635 1636 func TestRawFromBytesWithLimit(t *testing.T) { 1637 // Create a simple profile 1638 p := &profilev1.Profile{ 1639 SampleType: []*profilev1.ValueType{ 1640 {Type: 1, Unit: 2}, 1641 }, 1642 Sample: []*profilev1.Sample{ 1643 {LocationId: []uint64{1}, Value: []int64{100}}, 1644 {LocationId: []uint64{2}, Value: []int64{200}}, 1645 }, 1646 Location: []*profilev1.Location{ 1647 {Id: 1, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 1, Line: 1}}}, 1648 {Id: 2, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 2, Line: 1}}}, 1649 }, 1650 Mapping: []*profilev1.Mapping{ 1651 {Id: 1, Filename: 3}, 1652 }, 1653 Function: []*profilev1.Function{ 1654 {Id: 1, Name: 4, SystemName: 4, Filename: 3}, 1655 {Id: 2, Name: 5, SystemName: 5, Filename: 3}, 1656 }, 1657 StringTable: []string{ 1658 "", 1659 "cpu", "nanoseconds", 1660 "main.go", 1661 "foo", "bar", 1662 }, 1663 PeriodType: &profilev1.ValueType{Type: 1, Unit: 2}, 1664 TimeNanos: 1, 1665 Period: 1, 1666 } 1667 1668 // Marshal the profile to bytes (compressed) 1669 data, err := Marshal(p, true) 1670 require.NoError(t, err) 1671 require.NotEmpty(t, data) 1672 1673 // Get the actual decompressed size 1674 normalProfile, err := RawFromBytesWithLimit(data, -1) 1675 require.NoError(t, err) 1676 require.NotNil(t, normalProfile) 1677 decompressedSize := normalProfile.rawSize 1678 1679 t.Logf("Compressed size: %d bytes, Decompressed size: %d bytes", len(data), decompressedSize) 1680 1681 t.Run("unlimited", func(t *testing.T) { 1682 // Test with -1 (no limit) - should succeed 1683 profile, err := RawFromBytesWithLimit(data, -1) 1684 require.NoError(t, err) 1685 require.NotNil(t, profile) 1686 require.Equal(t, 2, len(profile.Sample)) 1687 }) 1688 1689 t.Run("limit_exceeded", func(t *testing.T) { 1690 // Test with a limit smaller than decompressed size - should fail 1691 _, err := RawFromBytesWithLimit(data, int64(decompressedSize/2)) 1692 require.Error(t, err) 1693 require.Contains(t, err.Error(), "decompressed size exceeds maximum allowed size") 1694 }) 1695 1696 t.Run("limit_sufficient", func(t *testing.T) { 1697 // Test with a limit larger than decompressed size - should succeed 1698 profile, err := RawFromBytesWithLimit(data, int64(decompressedSize*2)) 1699 require.NoError(t, err) 1700 require.NotNil(t, profile) 1701 require.Equal(t, 2, len(profile.Sample)) 1702 }) 1703 1704 t.Run("limit_exact", func(t *testing.T) { 1705 // Test with limit exactly equal to decompressed size - should succeed 1706 profile, err := RawFromBytesWithLimit(data, int64(decompressedSize)) 1707 require.NoError(t, err) 1708 require.NotNil(t, profile) 1709 require.Equal(t, 2, len(profile.Sample)) 1710 }) 1711 } 1712 1713 func TestUnmarshalWithLimit(t *testing.T) { 1714 // Create a simple profile 1715 p := &profilev1.Profile{ 1716 SampleType: []*profilev1.ValueType{ 1717 {Type: 1, Unit: 2}, 1718 }, 1719 Sample: []*profilev1.Sample{ 1720 {LocationId: []uint64{1}, Value: []int64{100}}, 1721 }, 1722 Location: []*profilev1.Location{ 1723 {Id: 1, MappingId: 1, Line: []*profilev1.Line{{FunctionId: 1, Line: 1}}}, 1724 }, 1725 Mapping: []*profilev1.Mapping{ 1726 {Id: 1, Filename: 3}, 1727 }, 1728 Function: []*profilev1.Function{ 1729 {Id: 1, Name: 4, SystemName: 4, Filename: 3}, 1730 }, 1731 StringTable: []string{ 1732 "", 1733 "cpu", "nanoseconds", 1734 "main.go", 1735 "foo", 1736 }, 1737 PeriodType: &profilev1.ValueType{Type: 1, Unit: 2}, 1738 TimeNanos: 1, 1739 Period: 1, 1740 } 1741 1742 // Marshal the profile to bytes (compressed) 1743 data, err := Marshal(p, true) 1744 require.NoError(t, err) 1745 1746 // Get the actual decompressed size 1747 testProfile := &profilev1.Profile{} 1748 err = UnmarshalWithLimit(data, testProfile, -1) 1749 require.NoError(t, err) 1750 decompressedSize, err := testProfile.MarshalVT() 1751 require.NoError(t, err) 1752 1753 t.Run("unlimited", func(t *testing.T) { 1754 result := &profilev1.Profile{} 1755 err := UnmarshalWithLimit(data, result, -1) 1756 require.NoError(t, err) 1757 require.Equal(t, 1, len(result.Sample)) 1758 }) 1759 1760 t.Run("limit_exceeded", func(t *testing.T) { 1761 result := &profilev1.Profile{} 1762 err := UnmarshalWithLimit(data, result, int64(len(decompressedSize)/2)) 1763 require.Error(t, err) 1764 require.Contains(t, err.Error(), "decompressed size exceeds maximum allowed size") 1765 }) 1766 1767 t.Run("limit_sufficient", func(t *testing.T) { 1768 result := &profilev1.Profile{} 1769 err := UnmarshalWithLimit(data, result, int64(len(decompressedSize)*2)) 1770 require.NoError(t, err) 1771 require.Equal(t, 1, len(result.Sample)) 1772 }) 1773 }