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.
 
 
 

415 lines
9.3 KiB

  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 report
  15. import (
  16. "bytes"
  17. "io/ioutil"
  18. "regexp"
  19. "runtime"
  20. "testing"
  21. "github.com/google/pprof/internal/binutils"
  22. "github.com/google/pprof/internal/graph"
  23. "github.com/google/pprof/internal/proftest"
  24. "github.com/google/pprof/profile"
  25. )
  26. type testcase struct {
  27. rpt *Report
  28. want string
  29. }
  30. func TestSource(t *testing.T) {
  31. const path = "testdata/"
  32. sampleValue1 := func(v []int64) int64 {
  33. return v[1]
  34. }
  35. for _, tc := range []testcase{
  36. {
  37. rpt: New(
  38. testProfile.Copy(),
  39. &Options{
  40. OutputFormat: List,
  41. Symbol: regexp.MustCompile(`.`),
  42. TrimPath: "/some/path",
  43. SampleValue: sampleValue1,
  44. SampleUnit: testProfile.SampleType[1].Unit,
  45. },
  46. ),
  47. want: path + "source.rpt",
  48. },
  49. {
  50. rpt: New(
  51. testProfile.Copy(),
  52. &Options{
  53. OutputFormat: Dot,
  54. CallTree: true,
  55. Symbol: regexp.MustCompile(`.`),
  56. TrimPath: "/some/path",
  57. SampleValue: sampleValue1,
  58. SampleUnit: testProfile.SampleType[1].Unit,
  59. },
  60. ),
  61. want: path + "source.dot",
  62. },
  63. } {
  64. var b bytes.Buffer
  65. if err := Generate(&b, tc.rpt, &binutils.Binutils{}); err != nil {
  66. t.Fatalf("%s: %v", tc.want, err)
  67. }
  68. gold, err := ioutil.ReadFile(tc.want)
  69. if err != nil {
  70. t.Fatalf("%s: %v", tc.want, err)
  71. }
  72. if runtime.GOOS == "windows" {
  73. gold = bytes.Replace(gold, []byte("testdata/"), []byte("testdata\\"), -1)
  74. }
  75. if string(b.String()) != string(gold) {
  76. d, err := proftest.Diff(gold, b.Bytes())
  77. if err != nil {
  78. t.Fatalf("%s: %v", "source", err)
  79. }
  80. t.Error("source" + "\n" + string(d) + "\n" + "gold:\n" + tc.want)
  81. }
  82. }
  83. }
  84. var testM = []*profile.Mapping{
  85. {
  86. ID: 1,
  87. HasFunctions: true,
  88. HasFilenames: true,
  89. HasLineNumbers: true,
  90. HasInlineFrames: true,
  91. },
  92. }
  93. var testF = []*profile.Function{
  94. {
  95. ID: 1,
  96. Name: "main",
  97. Filename: "testdata/source1",
  98. },
  99. {
  100. ID: 2,
  101. Name: "foo",
  102. Filename: "testdata/source1",
  103. },
  104. {
  105. ID: 3,
  106. Name: "bar",
  107. Filename: "testdata/source1",
  108. },
  109. {
  110. ID: 4,
  111. Name: "tee",
  112. Filename: "/some/path/testdata/source2",
  113. },
  114. }
  115. var testL = []*profile.Location{
  116. {
  117. ID: 1,
  118. Mapping: testM[0],
  119. Line: []profile.Line{
  120. {
  121. Function: testF[0],
  122. Line: 2,
  123. },
  124. },
  125. },
  126. {
  127. ID: 2,
  128. Mapping: testM[0],
  129. Line: []profile.Line{
  130. {
  131. Function: testF[1],
  132. Line: 4,
  133. },
  134. },
  135. },
  136. {
  137. ID: 3,
  138. Mapping: testM[0],
  139. Line: []profile.Line{
  140. {
  141. Function: testF[2],
  142. Line: 10,
  143. },
  144. },
  145. },
  146. {
  147. ID: 4,
  148. Mapping: testM[0],
  149. Line: []profile.Line{
  150. {
  151. Function: testF[3],
  152. Line: 2,
  153. },
  154. },
  155. },
  156. {
  157. ID: 5,
  158. Mapping: testM[0],
  159. Line: []profile.Line{
  160. {
  161. Function: testF[3],
  162. Line: 8,
  163. },
  164. },
  165. },
  166. }
  167. var testProfile = &profile.Profile{
  168. PeriodType: &profile.ValueType{Type: "cpu", Unit: "millisecond"},
  169. Period: 10,
  170. DurationNanos: 10e9,
  171. SampleType: []*profile.ValueType{
  172. {Type: "samples", Unit: "count"},
  173. {Type: "cpu", Unit: "cycles"},
  174. },
  175. Sample: []*profile.Sample{
  176. {
  177. Location: []*profile.Location{testL[0]},
  178. Value: []int64{1, 1},
  179. },
  180. {
  181. Location: []*profile.Location{testL[2], testL[1], testL[0]},
  182. Value: []int64{1, 10},
  183. },
  184. {
  185. Location: []*profile.Location{testL[4], testL[2], testL[0]},
  186. Value: []int64{1, 100},
  187. },
  188. {
  189. Location: []*profile.Location{testL[3], testL[0]},
  190. Value: []int64{1, 1000},
  191. },
  192. {
  193. Location: []*profile.Location{testL[4], testL[3], testL[0]},
  194. Value: []int64{1, 10000},
  195. },
  196. },
  197. Location: testL,
  198. Function: testF,
  199. Mapping: testM,
  200. }
  201. func TestDisambiguation(t *testing.T) {
  202. parent1 := &graph.Node{Info: graph.NodeInfo{Name: "parent1"}}
  203. parent2 := &graph.Node{Info: graph.NodeInfo{Name: "parent2"}}
  204. child1 := &graph.Node{Info: graph.NodeInfo{Name: "child"}, Function: parent1}
  205. child2 := &graph.Node{Info: graph.NodeInfo{Name: "child"}, Function: parent2}
  206. child3 := &graph.Node{Info: graph.NodeInfo{Name: "child"}, Function: parent1}
  207. sibling := &graph.Node{Info: graph.NodeInfo{Name: "sibling"}, Function: parent1}
  208. n := []*graph.Node{parent1, parent2, child1, child2, child3, sibling}
  209. wanted := map[*graph.Node]string{
  210. parent1: "parent1",
  211. parent2: "parent2",
  212. child1: "child [1/2]",
  213. child2: "child [2/2]",
  214. child3: "child [1/2]",
  215. sibling: "sibling",
  216. }
  217. g := &graph.Graph{Nodes: n}
  218. names := getDisambiguatedNames(g)
  219. for node, want := range wanted {
  220. if got := names[node]; got != want {
  221. t.Errorf("name %s, got %s, want %s", node.Info.Name, got, want)
  222. }
  223. }
  224. }
  225. func TestFunctionMap(t *testing.T) {
  226. fm := make(functionMap)
  227. nodes := []graph.NodeInfo{
  228. {Name: "fun1"},
  229. {Name: "fun2", File: "filename"},
  230. {Name: "fun1"},
  231. {Name: "fun2", File: "filename2"},
  232. }
  233. want := []struct {
  234. wantFunction profile.Function
  235. wantAdded bool
  236. }{
  237. {profile.Function{ID: 1, Name: "fun1"}, true},
  238. {profile.Function{ID: 2, Name: "fun2", Filename: "filename"}, true},
  239. {profile.Function{ID: 1, Name: "fun1"}, false},
  240. {profile.Function{ID: 3, Name: "fun2", Filename: "filename2"}, true},
  241. }
  242. for i, tc := range nodes {
  243. gotFunc, gotAdded := fm.findOrAdd(tc)
  244. if got, want := gotFunc, want[i].wantFunction; *got != want {
  245. t.Errorf("%d: got %v, want %v", i, got, want)
  246. }
  247. if got, want := gotAdded, want[i].wantAdded; got != want {
  248. t.Errorf("%d: got %v, want %v", i, got, want)
  249. }
  250. }
  251. }
  252. func TestLegendActiveFilters(t *testing.T) {
  253. activeFilterInput := []string{
  254. "focus=123|456|789|101112|131415|161718|192021|222324|252627|282930|313233|343536|363738|acbdefghijklmnop",
  255. "show=short filter",
  256. }
  257. expectedLegendActiveFilter := []string{
  258. "Active filters:",
  259. " focus=123|456|789|101112|131415|161718|192021|222324|252627|282930|313233|343536…",
  260. " show=short filter",
  261. }
  262. legendActiveFilter := legendActiveFilters(activeFilterInput)
  263. if len(legendActiveFilter) != len(expectedLegendActiveFilter) {
  264. t.Errorf("wanted length %v got length %v", len(expectedLegendActiveFilter), len(legendActiveFilter))
  265. }
  266. for i := range legendActiveFilter {
  267. if legendActiveFilter[i] != expectedLegendActiveFilter[i] {
  268. t.Errorf("%d: want \"%v\", got \"%v\"", i, expectedLegendActiveFilter[i], legendActiveFilter[i])
  269. }
  270. }
  271. }
  272. func TestComputeTotal(t *testing.T) {
  273. p1 := testProfile.Copy()
  274. p1.Sample = []*profile.Sample{
  275. {
  276. Location: []*profile.Location{testL[0]},
  277. Value: []int64{1, 1},
  278. },
  279. {
  280. Location: []*profile.Location{testL[2], testL[1], testL[0]},
  281. Value: []int64{1, 10},
  282. },
  283. {
  284. Location: []*profile.Location{testL[4], testL[2], testL[0]},
  285. Value: []int64{1, 100},
  286. },
  287. }
  288. p2 := testProfile.Copy()
  289. p2.Sample = []*profile.Sample{
  290. {
  291. Location: []*profile.Location{testL[0]},
  292. Value: []int64{1, 1},
  293. },
  294. {
  295. Location: []*profile.Location{testL[2], testL[1], testL[0]},
  296. Value: []int64{1, -10},
  297. },
  298. {
  299. Location: []*profile.Location{testL[4], testL[2], testL[0]},
  300. Value: []int64{1, 100},
  301. },
  302. }
  303. p3 := testProfile.Copy()
  304. p3.Sample = []*profile.Sample{
  305. {
  306. Location: []*profile.Location{testL[0]},
  307. Value: []int64{10000, 1},
  308. },
  309. {
  310. Location: []*profile.Location{testL[2], testL[1], testL[0]},
  311. Value: []int64{-10, 3},
  312. Label: map[string][]string{"pprof::base": {"true"}},
  313. },
  314. {
  315. Location: []*profile.Location{testL[2], testL[1], testL[0]},
  316. Value: []int64{1000, -10},
  317. },
  318. {
  319. Location: []*profile.Location{testL[2], testL[1], testL[0]},
  320. Value: []int64{-9000, 3},
  321. Label: map[string][]string{"pprof::base": {"true"}},
  322. },
  323. {
  324. Location: []*profile.Location{testL[2], testL[1], testL[0]},
  325. Value: []int64{-1, 3},
  326. Label: map[string][]string{"pprof::base": {"true"}},
  327. },
  328. {
  329. Location: []*profile.Location{testL[4], testL[2], testL[0]},
  330. Value: []int64{100, 100},
  331. },
  332. {
  333. Location: []*profile.Location{testL[2], testL[1], testL[0]},
  334. Value: []int64{100, 3},
  335. Label: map[string][]string{"pprof::base": {"true"}},
  336. },
  337. }
  338. testcases := []struct {
  339. desc string
  340. prof *profile.Profile
  341. value, meanDiv func(v []int64) int64
  342. wantTotal int64
  343. }{
  344. {
  345. desc: "no diff base, all positive values, index 1",
  346. prof: p1,
  347. value: func(v []int64) int64 {
  348. return v[0]
  349. },
  350. wantTotal: 3,
  351. },
  352. {
  353. desc: "no diff base, all positive values, index 2",
  354. prof: p1,
  355. value: func(v []int64) int64 {
  356. return v[1]
  357. },
  358. wantTotal: 111,
  359. },
  360. {
  361. desc: "no diff base, some negative values",
  362. prof: p2,
  363. value: func(v []int64) int64 {
  364. return v[1]
  365. },
  366. wantTotal: 111,
  367. },
  368. {
  369. desc: "diff base, some negative values",
  370. prof: p3,
  371. value: func(v []int64) int64 {
  372. return v[0]
  373. },
  374. wantTotal: 9111,
  375. },
  376. }
  377. for _, tc := range testcases {
  378. t.Run(tc.desc, func(t *testing.T) {
  379. if gotTotal := computeTotal(tc.prof, tc.value, tc.meanDiv); gotTotal != tc.wantTotal {
  380. t.Errorf("got total %d, want %v", gotTotal, tc.wantTotal)
  381. }
  382. })
  383. }
  384. }