No Description

filter_test.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  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. func TestFilter(t *testing.T) {
  94. for _, tc := range []struct {
  95. // name is the name of the test case.
  96. name string
  97. // profile is the profile that gets filtered.
  98. profile *Profile
  99. // These are the inputs to FilterSamplesByName().
  100. focus, ignore, hide, show *regexp.Regexp
  101. // want{F,I,S,H}m are expected return values from FilterSamplesByName.
  102. wantFm, wantIm, wantSm, wantHm bool
  103. // wantSampleFuncs contains expected stack functions and sample value after
  104. // filtering, in the same order as in the profile. The format is as
  105. // returned by sampleFuncs function below, which is "callee caller: <num>".
  106. wantSampleFuncs []string
  107. }{
  108. // No Filters
  109. {
  110. name: "empty filters keep all frames",
  111. profile: noInlinesProfile,
  112. wantFm: true,
  113. wantSampleFuncs: allNoInlinesSampleFuncs,
  114. },
  115. // Focus
  116. {
  117. name: "focus with no matches",
  118. profile: noInlinesProfile,
  119. focus: regexp.MustCompile("unknown"),
  120. },
  121. {
  122. name: "focus matches function names",
  123. profile: noInlinesProfile,
  124. focus: regexp.MustCompile("fun1"),
  125. wantFm: true,
  126. wantSampleFuncs: []string{
  127. "fun0 fun1 fun2 fun3: 1",
  128. "fun4 fun5 fun1 fun6: 2",
  129. "fun9 fun4 fun10 fun7: 4",
  130. },
  131. },
  132. {
  133. name: "focus matches file names",
  134. profile: noInlinesProfile,
  135. focus: regexp.MustCompile("file1"),
  136. wantFm: true,
  137. wantSampleFuncs: []string{
  138. "fun0 fun1 fun2 fun3: 1",
  139. "fun4 fun5 fun1 fun6: 2",
  140. "fun9 fun4 fun10 fun7: 4",
  141. },
  142. },
  143. {
  144. name: "focus matches mapping names",
  145. profile: noInlinesProfile,
  146. focus: regexp.MustCompile("map1"),
  147. wantFm: true,
  148. wantSampleFuncs: []string{
  149. "fun9 fun4 fun10 fun7: 4",
  150. },
  151. },
  152. {
  153. name: "focus matches inline functions",
  154. profile: inlinesProfile,
  155. focus: regexp.MustCompile("fun5"),
  156. wantFm: true,
  157. wantSampleFuncs: []string{
  158. "fun4 fun5 fun6: 2",
  159. },
  160. },
  161. // Ignore
  162. {
  163. name: "ignore with no matches matches all samples",
  164. profile: noInlinesProfile,
  165. ignore: regexp.MustCompile("unknown"),
  166. wantFm: true,
  167. wantSampleFuncs: allNoInlinesSampleFuncs,
  168. },
  169. {
  170. name: "ignore matches function names",
  171. profile: noInlinesProfile,
  172. ignore: regexp.MustCompile("fun1"),
  173. wantFm: true,
  174. wantIm: true,
  175. wantSampleFuncs: []string{
  176. "fun7 fun8: 3",
  177. },
  178. },
  179. {
  180. name: "ignore matches file names",
  181. profile: noInlinesProfile,
  182. ignore: regexp.MustCompile("file1"),
  183. wantFm: true,
  184. wantIm: true,
  185. wantSampleFuncs: []string{
  186. "fun7 fun8: 3",
  187. },
  188. },
  189. {
  190. name: "ignore matches mapping names",
  191. profile: noInlinesProfile,
  192. ignore: regexp.MustCompile("map1"),
  193. wantFm: true,
  194. wantIm: true,
  195. wantSampleFuncs: []string{
  196. "fun0 fun1 fun2 fun3: 1",
  197. "fun4 fun5 fun1 fun6: 2",
  198. "fun7 fun8: 3",
  199. },
  200. },
  201. {
  202. name: "ignore matches inline functions",
  203. profile: inlinesProfile,
  204. ignore: regexp.MustCompile("fun5"),
  205. wantFm: true,
  206. wantIm: true,
  207. wantSampleFuncs: []string{
  208. "fun0 fun1 fun2 fun3: 1",
  209. },
  210. },
  211. // Show
  212. {
  213. name: "show with no matches",
  214. profile: noInlinesProfile,
  215. show: regexp.MustCompile("unknown"),
  216. wantFm: true,
  217. },
  218. {
  219. name: "show matches function names",
  220. profile: noInlinesProfile,
  221. show: regexp.MustCompile("fun1|fun2"),
  222. wantFm: true,
  223. wantSm: true,
  224. wantSampleFuncs: []string{
  225. "fun1 fun2: 1",
  226. "fun1: 2",
  227. "fun10: 4",
  228. },
  229. },
  230. {
  231. name: "show matches file names",
  232. profile: noInlinesProfile,
  233. show: regexp.MustCompile("file1|file3"),
  234. wantFm: true,
  235. wantSm: true,
  236. wantSampleFuncs: []string{
  237. "fun1 fun3: 1",
  238. "fun1: 2",
  239. "fun10: 4",
  240. },
  241. },
  242. {
  243. name: "show matches mapping names",
  244. profile: noInlinesProfile,
  245. show: regexp.MustCompile("map1"),
  246. wantFm: true,
  247. wantSm: true,
  248. wantSampleFuncs: []string{
  249. "fun10: 4",
  250. },
  251. },
  252. {
  253. name: "show matches inline functions",
  254. profile: inlinesProfile,
  255. show: regexp.MustCompile("fun[03]"),
  256. wantFm: true,
  257. wantSm: true,
  258. wantSampleFuncs: []string{
  259. "fun0 fun3: 1",
  260. },
  261. },
  262. {
  263. name: "show keeps all lines when matching both mapping and function",
  264. profile: inlinesProfile,
  265. show: regexp.MustCompile("map0|fun5"),
  266. wantFm: true,
  267. wantSm: true,
  268. wantSampleFuncs: []string{
  269. "fun0 fun1 fun2 fun3: 1",
  270. "fun4 fun5 fun6: 2",
  271. },
  272. },
  273. // Hide
  274. {
  275. name: "hide with no matches",
  276. profile: noInlinesProfile,
  277. hide: regexp.MustCompile("unknown"),
  278. wantFm: true,
  279. wantSampleFuncs: allNoInlinesSampleFuncs,
  280. },
  281. {
  282. name: "hide matches function names",
  283. profile: noInlinesProfile,
  284. hide: regexp.MustCompile("fun1|fun2"),
  285. wantFm: true,
  286. wantHm: true,
  287. wantSampleFuncs: []string{
  288. "fun0 fun3: 1",
  289. "fun4 fun5 fun6: 2",
  290. "fun7 fun8: 3",
  291. "fun9 fun4 fun7: 4",
  292. },
  293. },
  294. {
  295. name: "hide matches file names",
  296. profile: noInlinesProfile,
  297. hide: regexp.MustCompile("file1|file3"),
  298. wantFm: true,
  299. wantHm: true,
  300. wantSampleFuncs: []string{
  301. "fun0 fun2: 1",
  302. "fun4 fun5 fun6: 2",
  303. "fun7 fun8: 3",
  304. "fun9 fun4 fun7: 4",
  305. },
  306. },
  307. {
  308. name: "hide matches mapping names",
  309. profile: noInlinesProfile,
  310. hide: regexp.MustCompile("map1"),
  311. wantFm: true,
  312. wantHm: true,
  313. wantSampleFuncs: []string{
  314. "fun0 fun1 fun2 fun3: 1",
  315. "fun4 fun5 fun1 fun6: 2",
  316. "fun7 fun8: 3",
  317. "fun9 fun4 fun7: 4",
  318. },
  319. },
  320. {
  321. name: "hide matches inline functions",
  322. profile: inlinesProfile,
  323. hide: regexp.MustCompile("fun[125]"),
  324. wantFm: true,
  325. wantHm: true,
  326. wantSampleFuncs: []string{
  327. "fun0 fun3: 1",
  328. "fun4 fun6: 2",
  329. },
  330. },
  331. {
  332. name: "hide drops all lines when matching both mapping and function",
  333. profile: inlinesProfile,
  334. hide: regexp.MustCompile("map0|fun5"),
  335. wantFm: true,
  336. wantHm: true,
  337. },
  338. // Compound filters
  339. {
  340. name: "hides a stack matched by both focus and ignore",
  341. profile: noInlinesProfile,
  342. focus: regexp.MustCompile("fun1|fun7"),
  343. ignore: regexp.MustCompile("fun1"),
  344. wantFm: true,
  345. wantIm: true,
  346. wantSampleFuncs: []string{
  347. "fun7 fun8: 3",
  348. },
  349. },
  350. {
  351. name: "hides a function if both show and hide match it",
  352. profile: noInlinesProfile,
  353. show: regexp.MustCompile("fun1"),
  354. hide: regexp.MustCompile("fun10"),
  355. wantFm: true,
  356. wantSm: true,
  357. wantHm: true,
  358. wantSampleFuncs: []string{
  359. "fun1: 1",
  360. "fun1: 2",
  361. },
  362. },
  363. } {
  364. t.Run(tc.name, func(t *testing.T) {
  365. p := tc.profile.Copy()
  366. fm, im, hm, sm := p.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show)
  367. type match struct{ fm, im, hm, sm bool }
  368. 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 {
  369. t.Errorf("match got %+v want %+v", got, want)
  370. }
  371. if got, want := strings.Join(sampleFuncs(p), "\n")+"\n", strings.Join(tc.wantSampleFuncs, "\n")+"\n"; got != want {
  372. diff, err := proftest.Diff([]byte(want), []byte(got))
  373. if err != nil {
  374. t.Fatalf("failed to get diff: %v", err)
  375. }
  376. t.Errorf("FilterSamplesByName: got diff(want->got):\n%s", diff)
  377. }
  378. })
  379. }
  380. }
  381. // sampleFuncs returns a slice of strings where each string represents one
  382. // profile sample in the format "<fun1> <fun2> <fun3>: <value>". This allows
  383. // the expected values for test cases to be specifed in human-readable strings.
  384. func sampleFuncs(p *Profile) []string {
  385. var ret []string
  386. for _, s := range p.Sample {
  387. var funcs []string
  388. for _, loc := range s.Location {
  389. for _, line := range loc.Line {
  390. funcs = append(funcs, line.Function.Name)
  391. }
  392. }
  393. ret = append(ret, fmt.Sprintf("%s: %d", strings.Join(funcs, " "), s.Value[0]))
  394. }
  395. return ret
  396. }
  397. func TestTagFilter(t *testing.T) {
  398. // Perform several forms of tag filtering on the test profile.
  399. type filterTestcase struct {
  400. include, exclude *regexp.Regexp
  401. im, em bool
  402. count int
  403. }
  404. countTags := func(p *Profile) map[string]bool {
  405. tags := make(map[string]bool)
  406. for _, s := range p.Sample {
  407. for l := range s.Label {
  408. tags[l] = true
  409. }
  410. for l := range s.NumLabel {
  411. tags[l] = true
  412. }
  413. }
  414. return tags
  415. }
  416. for tx, tc := range []filterTestcase{
  417. {nil, nil, true, false, 3},
  418. {regexp.MustCompile("notfound"), nil, false, false, 0},
  419. {regexp.MustCompile("key1"), nil, true, false, 1},
  420. {nil, regexp.MustCompile("key[12]"), true, true, 1},
  421. } {
  422. prof := testProfile1.Copy()
  423. gim, gem := prof.FilterTagsByName(tc.include, tc.exclude)
  424. if gim != tc.im {
  425. t.Errorf("Filter #%d, got include match=%v, want %v", tx, gim, tc.im)
  426. }
  427. if gem != tc.em {
  428. t.Errorf("Filter #%d, got exclude match=%v, want %v", tx, gem, tc.em)
  429. }
  430. if tags := countTags(prof); len(tags) != tc.count {
  431. t.Errorf("Filter #%d, got %d tags[%v], want %d", tx, len(tags), tags, tc.count)
  432. }
  433. }
  434. }