暂无描述

profile_test.go 14KB

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