You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

600 lines
18 KiB

  1. // Copyright 2018 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. "fmt"
  17. "regexp"
  18. "strings"
  19. "testing"
  20. "github.com/google/pprof/internal/proftest"
  21. )
  22. var mappings = []*Mapping{
  23. {ID: 1, Start: 0x10000, Limit: 0x40000, File: "map0", HasFunctions: true, HasFilenames: true, HasLineNumbers: true, HasInlineFrames: true},
  24. {ID: 2, Start: 0x50000, Limit: 0x70000, File: "map1", HasFunctions: true, HasFilenames: true, HasLineNumbers: true, HasInlineFrames: true},
  25. }
  26. var functions = []*Function{
  27. {ID: 1, Name: "fun0", SystemName: "fun0", Filename: "file0"},
  28. {ID: 2, Name: "fun1", SystemName: "fun1", Filename: "file1"},
  29. {ID: 3, Name: "fun2", SystemName: "fun2", Filename: "file2"},
  30. {ID: 4, Name: "fun3", SystemName: "fun3", Filename: "file3"},
  31. {ID: 5, Name: "fun4", SystemName: "fun4", Filename: "file4"},
  32. {ID: 6, Name: "fun5", SystemName: "fun5", Filename: "file5"},
  33. {ID: 7, Name: "fun6", SystemName: "fun6", Filename: "file6"},
  34. {ID: 8, Name: "fun7", SystemName: "fun7", Filename: "file7"},
  35. {ID: 9, Name: "fun8", SystemName: "fun8", Filename: "file8"},
  36. {ID: 10, Name: "fun9", SystemName: "fun9", Filename: "file9"},
  37. {ID: 11, Name: "fun10", SystemName: "fun10", Filename: "file10"},
  38. }
  39. var noInlinesLocs = []*Location{
  40. {ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}}},
  41. {ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{{Function: functions[1], Line: 1}}},
  42. {ID: 3, Mapping: mappings[0], Address: 0x3000, Line: []Line{{Function: functions[2], Line: 1}}},
  43. {ID: 4, Mapping: mappings[0], Address: 0x4000, Line: []Line{{Function: functions[3], Line: 1}}},
  44. {ID: 5, Mapping: mappings[0], Address: 0x5000, Line: []Line{{Function: functions[4], Line: 1}}},
  45. {ID: 6, Mapping: mappings[0], Address: 0x6000, Line: []Line{{Function: functions[5], Line: 1}}},
  46. {ID: 7, Mapping: mappings[0], Address: 0x7000, Line: []Line{{Function: functions[6], Line: 1}}},
  47. {ID: 8, Mapping: mappings[0], Address: 0x8000, Line: []Line{{Function: functions[7], Line: 1}}},
  48. {ID: 9, Mapping: mappings[0], Address: 0x9000, Line: []Line{{Function: functions[8], Line: 1}}},
  49. {ID: 10, Mapping: mappings[0], Address: 0x10000, Line: []Line{{Function: functions[9], Line: 1}}},
  50. {ID: 11, Mapping: mappings[1], Address: 0x11000, Line: []Line{{Function: functions[10], Line: 1}}},
  51. }
  52. var noInlinesProfile = &Profile{
  53. TimeNanos: 10000,
  54. PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
  55. Period: 1,
  56. DurationNanos: 10e9,
  57. SampleType: []*ValueType{{Type: "samples", Unit: "count"}},
  58. Mapping: mappings,
  59. Function: functions,
  60. Location: noInlinesLocs,
  61. Sample: []*Sample{
  62. {Value: []int64{1}, Location: []*Location{noInlinesLocs[0], noInlinesLocs[1], noInlinesLocs[2], noInlinesLocs[3]}},
  63. {Value: []int64{2}, Location: []*Location{noInlinesLocs[4], noInlinesLocs[5], noInlinesLocs[1], noInlinesLocs[6]}},
  64. {Value: []int64{3}, Location: []*Location{noInlinesLocs[7], noInlinesLocs[8]}},
  65. {Value: []int64{4}, Location: []*Location{noInlinesLocs[9], noInlinesLocs[4], noInlinesLocs[10], noInlinesLocs[7]}},
  66. },
  67. }
  68. var allNoInlinesSampleFuncs = []string{
  69. "fun0 fun1 fun2 fun3: 1",
  70. "fun4 fun5 fun1 fun6: 2",
  71. "fun7 fun8: 3",
  72. "fun9 fun4 fun10 fun7: 4",
  73. }
  74. var inlinesLocs = []*Location{
  75. {ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}, {Function: functions[1], Line: 1}}},
  76. {ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{{Function: functions[2], Line: 1}, {Function: functions[3], Line: 1}}},
  77. {ID: 3, Mapping: mappings[0], Address: 0x3000, Line: []Line{{Function: functions[4], Line: 1}, {Function: functions[5], Line: 1}, {Function: functions[6], Line: 1}}},
  78. }
  79. var inlinesProfile = &Profile{
  80. TimeNanos: 10000,
  81. PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
  82. Period: 1,
  83. DurationNanos: 10e9,
  84. SampleType: []*ValueType{{Type: "samples", Unit: "count"}},
  85. Mapping: mappings,
  86. Function: functions,
  87. Location: inlinesLocs,
  88. Sample: []*Sample{
  89. {Value: []int64{1}, Location: []*Location{inlinesLocs[0], inlinesLocs[1]}},
  90. {Value: []int64{2}, Location: []*Location{inlinesLocs[2]}},
  91. },
  92. }
  93. var emptyLinesLocs = []*Location{
  94. {ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}, {Function: functions[1], Line: 1}}},
  95. {ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{}},
  96. {ID: 3, Mapping: mappings[1], Address: 0x2000, Line: []Line{}},
  97. }
  98. var emptyLinesProfile = &Profile{
  99. TimeNanos: 10000,
  100. PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"},
  101. Period: 1,
  102. DurationNanos: 10e9,
  103. SampleType: []*ValueType{{Type: "samples", Unit: "count"}},
  104. Mapping: mappings,
  105. Function: functions,
  106. Location: emptyLinesLocs,
  107. Sample: []*Sample{
  108. {Value: []int64{1}, Location: []*Location{emptyLinesLocs[0], emptyLinesLocs[1]}},
  109. {Value: []int64{2}, Location: []*Location{emptyLinesLocs[2]}},
  110. {Value: []int64{3}, Location: []*Location{}},
  111. },
  112. }
  113. func TestFilterSamplesByName(t *testing.T) {
  114. for _, tc := range []struct {
  115. // name is the name of the test case.
  116. name string
  117. // profile is the profile that gets filtered.
  118. profile *Profile
  119. // These are the inputs to FilterSamplesByName().
  120. focus, ignore, hide, show *regexp.Regexp
  121. // want{F,I,S,H}m are expected return values from FilterSamplesByName.
  122. wantFm, wantIm, wantSm, wantHm bool
  123. // wantSampleFuncs contains expected stack functions and sample value after
  124. // filtering, in the same order as in the profile. The format is as
  125. // returned by sampleFuncs function below, which is "callee caller: <num>".
  126. wantSampleFuncs []string
  127. }{
  128. // No Filters
  129. {
  130. name: "empty filters keep all frames",
  131. profile: noInlinesProfile,
  132. wantFm: true,
  133. wantSampleFuncs: allNoInlinesSampleFuncs,
  134. },
  135. // Focus
  136. {
  137. name: "focus with no matches",
  138. profile: noInlinesProfile,
  139. focus: regexp.MustCompile("unknown"),
  140. },
  141. {
  142. name: "focus matches function names",
  143. profile: noInlinesProfile,
  144. focus: regexp.MustCompile("fun1"),
  145. wantFm: true,
  146. wantSampleFuncs: []string{
  147. "fun0 fun1 fun2 fun3: 1",
  148. "fun4 fun5 fun1 fun6: 2",
  149. "fun9 fun4 fun10 fun7: 4",
  150. },
  151. },
  152. {
  153. name: "focus matches file names",
  154. profile: noInlinesProfile,
  155. focus: regexp.MustCompile("file1"),
  156. wantFm: true,
  157. wantSampleFuncs: []string{
  158. "fun0 fun1 fun2 fun3: 1",
  159. "fun4 fun5 fun1 fun6: 2",
  160. "fun9 fun4 fun10 fun7: 4",
  161. },
  162. },
  163. {
  164. name: "focus matches mapping names",
  165. profile: noInlinesProfile,
  166. focus: regexp.MustCompile("map1"),
  167. wantFm: true,
  168. wantSampleFuncs: []string{
  169. "fun9 fun4 fun10 fun7: 4",
  170. },
  171. },
  172. {
  173. name: "focus matches inline functions",
  174. profile: inlinesProfile,
  175. focus: regexp.MustCompile("fun5"),
  176. wantFm: true,
  177. wantSampleFuncs: []string{
  178. "fun4 fun5 fun6: 2",
  179. },
  180. },
  181. // Ignore
  182. {
  183. name: "ignore with no matches matches all samples",
  184. profile: noInlinesProfile,
  185. ignore: regexp.MustCompile("unknown"),
  186. wantFm: true,
  187. wantSampleFuncs: allNoInlinesSampleFuncs,
  188. },
  189. {
  190. name: "ignore matches function names",
  191. profile: noInlinesProfile,
  192. ignore: regexp.MustCompile("fun1"),
  193. wantFm: true,
  194. wantIm: true,
  195. wantSampleFuncs: []string{
  196. "fun7 fun8: 3",
  197. },
  198. },
  199. {
  200. name: "ignore matches file names",
  201. profile: noInlinesProfile,
  202. ignore: regexp.MustCompile("file1"),
  203. wantFm: true,
  204. wantIm: true,
  205. wantSampleFuncs: []string{
  206. "fun7 fun8: 3",
  207. },
  208. },
  209. {
  210. name: "ignore matches mapping names",
  211. profile: noInlinesProfile,
  212. ignore: regexp.MustCompile("map1"),
  213. wantFm: true,
  214. wantIm: true,
  215. wantSampleFuncs: []string{
  216. "fun0 fun1 fun2 fun3: 1",
  217. "fun4 fun5 fun1 fun6: 2",
  218. "fun7 fun8: 3",
  219. },
  220. },
  221. {
  222. name: "ignore matches inline functions",
  223. profile: inlinesProfile,
  224. ignore: regexp.MustCompile("fun5"),
  225. wantFm: true,
  226. wantIm: true,
  227. wantSampleFuncs: []string{
  228. "fun0 fun1 fun2 fun3: 1",
  229. },
  230. },
  231. // Show
  232. {
  233. name: "show with no matches",
  234. profile: noInlinesProfile,
  235. show: regexp.MustCompile("unknown"),
  236. wantFm: true,
  237. },
  238. {
  239. name: "show matches function names",
  240. profile: noInlinesProfile,
  241. show: regexp.MustCompile("fun1|fun2"),
  242. wantFm: true,
  243. wantSm: true,
  244. wantSampleFuncs: []string{
  245. "fun1 fun2: 1",
  246. "fun1: 2",
  247. "fun10: 4",
  248. },
  249. },
  250. {
  251. name: "show matches file names",
  252. profile: noInlinesProfile,
  253. show: regexp.MustCompile("file1|file3"),
  254. wantFm: true,
  255. wantSm: true,
  256. wantSampleFuncs: []string{
  257. "fun1 fun3: 1",
  258. "fun1: 2",
  259. "fun10: 4",
  260. },
  261. },
  262. {
  263. name: "show matches mapping names",
  264. profile: noInlinesProfile,
  265. show: regexp.MustCompile("map1"),
  266. wantFm: true,
  267. wantSm: true,
  268. wantSampleFuncs: []string{
  269. "fun10: 4",
  270. },
  271. },
  272. {
  273. name: "show matches inline functions",
  274. profile: inlinesProfile,
  275. show: regexp.MustCompile("fun[03]"),
  276. wantFm: true,
  277. wantSm: true,
  278. wantSampleFuncs: []string{
  279. "fun0 fun3: 1",
  280. },
  281. },
  282. {
  283. name: "show keeps all lines when matching both mapping and function",
  284. profile: inlinesProfile,
  285. show: regexp.MustCompile("map0|fun5"),
  286. wantFm: true,
  287. wantSm: true,
  288. wantSampleFuncs: []string{
  289. "fun0 fun1 fun2 fun3: 1",
  290. "fun4 fun5 fun6: 2",
  291. },
  292. },
  293. // Hide
  294. {
  295. name: "hide with no matches",
  296. profile: noInlinesProfile,
  297. hide: regexp.MustCompile("unknown"),
  298. wantFm: true,
  299. wantSampleFuncs: allNoInlinesSampleFuncs,
  300. },
  301. {
  302. name: "hide matches function names",
  303. profile: noInlinesProfile,
  304. hide: regexp.MustCompile("fun1|fun2"),
  305. wantFm: true,
  306. wantHm: true,
  307. wantSampleFuncs: []string{
  308. "fun0 fun3: 1",
  309. "fun4 fun5 fun6: 2",
  310. "fun7 fun8: 3",
  311. "fun9 fun4 fun7: 4",
  312. },
  313. },
  314. {
  315. name: "hide matches file names",
  316. profile: noInlinesProfile,
  317. hide: regexp.MustCompile("file1|file3"),
  318. wantFm: true,
  319. wantHm: true,
  320. wantSampleFuncs: []string{
  321. "fun0 fun2: 1",
  322. "fun4 fun5 fun6: 2",
  323. "fun7 fun8: 3",
  324. "fun9 fun4 fun7: 4",
  325. },
  326. },
  327. {
  328. name: "hide matches mapping names",
  329. profile: noInlinesProfile,
  330. hide: regexp.MustCompile("map1"),
  331. wantFm: true,
  332. wantHm: true,
  333. wantSampleFuncs: []string{
  334. "fun0 fun1 fun2 fun3: 1",
  335. "fun4 fun5 fun1 fun6: 2",
  336. "fun7 fun8: 3",
  337. "fun9 fun4 fun7: 4",
  338. },
  339. },
  340. {
  341. name: "hide matches inline functions",
  342. profile: inlinesProfile,
  343. hide: regexp.MustCompile("fun[125]"),
  344. wantFm: true,
  345. wantHm: true,
  346. wantSampleFuncs: []string{
  347. "fun0 fun3: 1",
  348. "fun4 fun6: 2",
  349. },
  350. },
  351. {
  352. name: "hide drops all lines when matching both mapping and function",
  353. profile: inlinesProfile,
  354. hide: regexp.MustCompile("map0|fun5"),
  355. wantFm: true,
  356. wantHm: true,
  357. },
  358. // Compound filters
  359. {
  360. name: "hides a stack matched by both focus and ignore",
  361. profile: noInlinesProfile,
  362. focus: regexp.MustCompile("fun1|fun7"),
  363. ignore: regexp.MustCompile("fun1"),
  364. wantFm: true,
  365. wantIm: true,
  366. wantSampleFuncs: []string{
  367. "fun7 fun8: 3",
  368. },
  369. },
  370. {
  371. name: "hides a function if both show and hide match it",
  372. profile: noInlinesProfile,
  373. show: regexp.MustCompile("fun1"),
  374. hide: regexp.MustCompile("fun10"),
  375. wantFm: true,
  376. wantSm: true,
  377. wantHm: true,
  378. wantSampleFuncs: []string{
  379. "fun1: 1",
  380. "fun1: 2",
  381. },
  382. },
  383. } {
  384. t.Run(tc.name, func(t *testing.T) {
  385. p := tc.profile.Copy()
  386. fm, im, hm, sm := p.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show)
  387. type match struct{ fm, im, hm, sm bool }
  388. if got, want := (match{fm: fm, im: im, hm: hm, sm: sm}), (match{fm: tc.wantFm, im: tc.wantIm, hm: tc.wantHm, sm: tc.wantSm}); got != want {
  389. t.Errorf("match got %+v want %+v", got, want)
  390. }
  391. if got, want := strings.Join(sampleFuncs(p), "\n")+"\n", strings.Join(tc.wantSampleFuncs, "\n")+"\n"; got != want {
  392. diff, err := proftest.Diff([]byte(want), []byte(got))
  393. if err != nil {
  394. t.Fatalf("failed to get diff: %v", err)
  395. }
  396. t.Errorf("FilterSamplesByName: got diff(want->got):\n%s", diff)
  397. }
  398. })
  399. }
  400. }
  401. func TestShowFrom(t *testing.T) {
  402. for _, tc := range []struct {
  403. name string
  404. profile *Profile
  405. showFrom *regexp.Regexp
  406. // wantMatch is the expected return value.
  407. wantMatch bool
  408. // wantSampleFuncs contains expected stack functions and sample value after
  409. // filtering, in the same order as in the profile. The format is as
  410. // returned by sampleFuncs function below, which is "callee caller: <num>".
  411. wantSampleFuncs []string
  412. }{
  413. {
  414. name: "nil showFrom keeps all frames",
  415. profile: noInlinesProfile,
  416. wantMatch: false,
  417. wantSampleFuncs: allNoInlinesSampleFuncs,
  418. },
  419. {
  420. name: "showFrom with no matches drops all samples",
  421. profile: noInlinesProfile,
  422. showFrom: regexp.MustCompile("unknown"),
  423. wantMatch: false,
  424. },
  425. {
  426. name: "showFrom matches function names",
  427. profile: noInlinesProfile,
  428. showFrom: regexp.MustCompile("fun1"),
  429. wantMatch: true,
  430. wantSampleFuncs: []string{
  431. "fun0 fun1: 1",
  432. "fun4 fun5 fun1: 2",
  433. "fun9 fun4 fun10: 4",
  434. },
  435. },
  436. {
  437. name: "showFrom matches file names",
  438. profile: noInlinesProfile,
  439. showFrom: regexp.MustCompile("file1"),
  440. wantMatch: true,
  441. wantSampleFuncs: []string{
  442. "fun0 fun1: 1",
  443. "fun4 fun5 fun1: 2",
  444. "fun9 fun4 fun10: 4",
  445. },
  446. },
  447. {
  448. name: "showFrom matches mapping names",
  449. profile: noInlinesProfile,
  450. showFrom: regexp.MustCompile("map1"),
  451. wantMatch: true,
  452. wantSampleFuncs: []string{
  453. "fun9 fun4 fun10: 4",
  454. },
  455. },
  456. {
  457. name: "showFrom drops frames above highest of multiple matches",
  458. profile: noInlinesProfile,
  459. showFrom: regexp.MustCompile("fun[12]"),
  460. wantMatch: true,
  461. wantSampleFuncs: []string{
  462. "fun0 fun1 fun2: 1",
  463. "fun4 fun5 fun1: 2",
  464. "fun9 fun4 fun10: 4",
  465. },
  466. },
  467. {
  468. name: "showFrom matches inline functions",
  469. profile: inlinesProfile,
  470. showFrom: regexp.MustCompile("fun0|fun5"),
  471. wantMatch: true,
  472. wantSampleFuncs: []string{
  473. "fun0: 1",
  474. "fun4 fun5: 2",
  475. },
  476. },
  477. {
  478. name: "showFrom drops frames above highest of multiple inline matches",
  479. profile: inlinesProfile,
  480. showFrom: regexp.MustCompile("fun[1245]"),
  481. wantMatch: true,
  482. wantSampleFuncs: []string{
  483. "fun0 fun1 fun2: 1",
  484. "fun4 fun5: 2",
  485. },
  486. },
  487. {
  488. name: "showFrom keeps all lines when matching mapping and function",
  489. profile: inlinesProfile,
  490. showFrom: regexp.MustCompile("map0|fun5"),
  491. wantMatch: true,
  492. wantSampleFuncs: []string{
  493. "fun0 fun1 fun2 fun3: 1",
  494. "fun4 fun5 fun6: 2",
  495. },
  496. },
  497. {
  498. name: "showFrom matches location with empty lines",
  499. profile: emptyLinesProfile,
  500. showFrom: regexp.MustCompile("map1"),
  501. wantMatch: true,
  502. wantSampleFuncs: []string{
  503. ": 2",
  504. },
  505. },
  506. } {
  507. t.Run(tc.name, func(t *testing.T) {
  508. p := tc.profile.Copy()
  509. if gotMatch := p.ShowFrom(tc.showFrom); gotMatch != tc.wantMatch {
  510. t.Errorf("match got %+v, want %+v", gotMatch, tc.wantMatch)
  511. }
  512. if got, want := strings.Join(sampleFuncs(p), "\n")+"\n", strings.Join(tc.wantSampleFuncs, "\n")+"\n"; got != want {
  513. diff, err := proftest.Diff([]byte(want), []byte(got))
  514. if err != nil {
  515. t.Fatalf("failed to get diff: %v", err)
  516. }
  517. t.Errorf("profile samples got diff(want->got):\n%s", diff)
  518. }
  519. })
  520. }
  521. }
  522. // sampleFuncs returns a slice of strings where each string represents one
  523. // profile sample in the format "<fun1> <fun2> <fun3>: <value>". This allows
  524. // the expected values for test cases to be specifed in human-readable strings.
  525. func sampleFuncs(p *Profile) []string {
  526. var ret []string
  527. for _, s := range p.Sample {
  528. var funcs []string
  529. for _, loc := range s.Location {
  530. for _, line := range loc.Line {
  531. funcs = append(funcs, line.Function.Name)
  532. }
  533. }
  534. ret = append(ret, fmt.Sprintf("%s: %d", strings.Join(funcs, " "), s.Value[0]))
  535. }
  536. return ret
  537. }
  538. func TestTagFilter(t *testing.T) {
  539. // Perform several forms of tag filtering on the test profile.
  540. type filterTestcase struct {
  541. include, exclude *regexp.Regexp
  542. im, em bool
  543. count int
  544. }
  545. countTags := func(p *Profile) map[string]bool {
  546. tags := make(map[string]bool)
  547. for _, s := range p.Sample {
  548. for l := range s.Label {
  549. tags[l] = true
  550. }
  551. for l := range s.NumLabel {
  552. tags[l] = true
  553. }
  554. }
  555. return tags
  556. }
  557. for tx, tc := range []filterTestcase{
  558. {nil, nil, true, false, 3},
  559. {regexp.MustCompile("notfound"), nil, false, false, 0},
  560. {regexp.MustCompile("key1"), nil, true, false, 1},
  561. {nil, regexp.MustCompile("key[12]"), true, true, 1},
  562. } {
  563. prof := testProfile1.Copy()
  564. gim, gem := prof.FilterTagsByName(tc.include, tc.exclude)
  565. if gim != tc.im {
  566. t.Errorf("Filter #%d, got include match=%v, want %v", tx, gim, tc.im)
  567. }
  568. if gem != tc.em {
  569. t.Errorf("Filter #%d, got exclude match=%v, want %v", tx, gem, tc.em)
  570. }
  571. if tags := countTags(prof); len(tags) != tc.count {
  572. t.Errorf("Filter #%d, got %d tags[%v], want %d", tx, len(tags), tags, tc.count)
  573. }
  574. }
  575. }