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.
 
 
 

494 lines
11 KiB

  1. // Copyright 2016 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 graph
  15. import (
  16. "fmt"
  17. "testing"
  18. "github.com/google/pprof/profile"
  19. )
  20. func edgeDebugString(edge *Edge) string {
  21. debug := ""
  22. debug += fmt.Sprintf("\t\tSrc: %p\n", edge.Src)
  23. debug += fmt.Sprintf("\t\tDest: %p\n", edge.Dest)
  24. debug += fmt.Sprintf("\t\tWeight: %d\n", edge.Weight)
  25. debug += fmt.Sprintf("\t\tResidual: %t\n", edge.Residual)
  26. debug += fmt.Sprintf("\t\tInline: %t\n", edge.Inline)
  27. return debug
  28. }
  29. func edgeMapsDebugString(in, out EdgeMap) string {
  30. debug := ""
  31. debug += "In Edges:\n"
  32. for parent, edge := range in {
  33. debug += fmt.Sprintf("\tParent: %p\n", parent)
  34. debug += edgeDebugString(edge)
  35. }
  36. debug += "Out Edges:\n"
  37. for child, edge := range out {
  38. debug += fmt.Sprintf("\tChild: %p\n", child)
  39. debug += edgeDebugString(edge)
  40. }
  41. return debug
  42. }
  43. func graphDebugString(graph *Graph) string {
  44. debug := ""
  45. for i, node := range graph.Nodes {
  46. debug += fmt.Sprintf("Node %d: %p\n", i, node)
  47. }
  48. for i, node := range graph.Nodes {
  49. debug += "\n"
  50. debug += fmt.Sprintf("=== Node %d: %p ===\n", i, node)
  51. debug += edgeMapsDebugString(node.In, node.Out)
  52. }
  53. return debug
  54. }
  55. func expectedNodesDebugString(expected []expectedNode) string {
  56. debug := ""
  57. for i, node := range expected {
  58. debug += fmt.Sprintf("Node %d: %p\n", i, node.node)
  59. }
  60. for i, node := range expected {
  61. debug += "\n"
  62. debug += fmt.Sprintf("=== Node %d: %p ===\n", i, node.node)
  63. debug += edgeMapsDebugString(node.in, node.out)
  64. }
  65. return debug
  66. }
  67. // edgeMapsEqual checks if all the edges in this equal all the edges in that.
  68. func edgeMapsEqual(this, that EdgeMap) bool {
  69. if len(this) != len(that) {
  70. return false
  71. }
  72. for node, thisEdge := range this {
  73. if *thisEdge != *that[node] {
  74. return false
  75. }
  76. }
  77. return true
  78. }
  79. // nodesEqual checks if node is equal to expected.
  80. func nodesEqual(node *Node, expected expectedNode) bool {
  81. return node == expected.node && edgeMapsEqual(node.In, expected.in) &&
  82. edgeMapsEqual(node.Out, expected.out)
  83. }
  84. // graphsEqual checks if graph is equivalent to the graph templated by expected.
  85. func graphsEqual(graph *Graph, expected []expectedNode) bool {
  86. if len(graph.Nodes) != len(expected) {
  87. return false
  88. }
  89. expectedSet := make(map[*Node]expectedNode)
  90. for i := range expected {
  91. expectedSet[expected[i].node] = expected[i]
  92. }
  93. for _, node := range graph.Nodes {
  94. expectedNode, found := expectedSet[node]
  95. if !found || !nodesEqual(node, expectedNode) {
  96. return false
  97. }
  98. }
  99. return true
  100. }
  101. type expectedNode struct {
  102. node *Node
  103. in, out EdgeMap
  104. }
  105. type trimTreeTestcase struct {
  106. initial *Graph
  107. expected []expectedNode
  108. keep NodePtrSet
  109. }
  110. // makeExpectedEdgeResidual makes the edge from parent to child residual.
  111. func makeExpectedEdgeResidual(parent, child expectedNode) {
  112. parent.out[child.node].Residual = true
  113. child.in[parent.node].Residual = true
  114. }
  115. func makeEdgeInline(edgeMap EdgeMap, node *Node) {
  116. edgeMap[node].Inline = true
  117. }
  118. func setEdgeWeight(edgeMap EdgeMap, node *Node, weight int64) {
  119. edgeMap[node].Weight = weight
  120. }
  121. // createEdges creates directed edges from the parent to each of the children.
  122. func createEdges(parent *Node, children ...*Node) {
  123. for _, child := range children {
  124. edge := &Edge{
  125. Src: parent,
  126. Dest: child,
  127. }
  128. parent.Out[child] = edge
  129. child.In[parent] = edge
  130. }
  131. }
  132. // createEmptyNode creates a node without any edges.
  133. func createEmptyNode() *Node {
  134. return &Node{
  135. In: make(EdgeMap),
  136. Out: make(EdgeMap),
  137. }
  138. }
  139. // createExpectedNodes creates a slice of expectedNodes from nodes.
  140. func createExpectedNodes(nodes ...*Node) ([]expectedNode, NodePtrSet) {
  141. expected := make([]expectedNode, len(nodes))
  142. keep := make(NodePtrSet, len(nodes))
  143. for i, node := range nodes {
  144. expected[i] = expectedNode{
  145. node: node,
  146. in: make(EdgeMap),
  147. out: make(EdgeMap),
  148. }
  149. keep[node] = true
  150. }
  151. return expected, keep
  152. }
  153. // createExpectedEdges creates directed edges from the parent to each of the
  154. // children.
  155. func createExpectedEdges(parent expectedNode, children ...expectedNode) {
  156. for _, child := range children {
  157. edge := &Edge{
  158. Src: parent.node,
  159. Dest: child.node,
  160. }
  161. parent.out[child.node] = edge
  162. child.in[parent.node] = edge
  163. }
  164. }
  165. // createTestCase1 creates a test case that initially looks like:
  166. // 0
  167. // |(5)
  168. // 1
  169. // (3)/ \(4)
  170. // 2 3.
  171. //
  172. // After keeping 0, 2, and 3, it expects the graph:
  173. // 0
  174. // (3)/ \(4)
  175. // 2 3.
  176. func createTestCase1() trimTreeTestcase {
  177. // Create initial graph
  178. graph := &Graph{make(Nodes, 4)}
  179. nodes := graph.Nodes
  180. for i := range nodes {
  181. nodes[i] = createEmptyNode()
  182. }
  183. createEdges(nodes[0], nodes[1])
  184. createEdges(nodes[1], nodes[2], nodes[3])
  185. makeEdgeInline(nodes[0].Out, nodes[1])
  186. makeEdgeInline(nodes[1].Out, nodes[2])
  187. setEdgeWeight(nodes[0].Out, nodes[1], 5)
  188. setEdgeWeight(nodes[1].Out, nodes[2], 3)
  189. setEdgeWeight(nodes[1].Out, nodes[3], 4)
  190. // Create expected graph
  191. expected, keep := createExpectedNodes(nodes[0], nodes[2], nodes[3])
  192. createExpectedEdges(expected[0], expected[1], expected[2])
  193. makeEdgeInline(expected[0].out, expected[1].node)
  194. makeExpectedEdgeResidual(expected[0], expected[1])
  195. makeExpectedEdgeResidual(expected[0], expected[2])
  196. setEdgeWeight(expected[0].out, expected[1].node, 3)
  197. setEdgeWeight(expected[0].out, expected[2].node, 4)
  198. return trimTreeTestcase{
  199. initial: graph,
  200. expected: expected,
  201. keep: keep,
  202. }
  203. }
  204. // createTestCase2 creates a test case that initially looks like:
  205. // 3
  206. // | (12)
  207. // 1
  208. // | (8)
  209. // 2
  210. // | (15)
  211. // 0
  212. // | (10)
  213. // 4.
  214. //
  215. // After keeping 3 and 4, it expects the graph:
  216. // 3
  217. // | (10)
  218. // 4.
  219. func createTestCase2() trimTreeTestcase {
  220. // Create initial graph
  221. graph := &Graph{make(Nodes, 5)}
  222. nodes := graph.Nodes
  223. for i := range nodes {
  224. nodes[i] = createEmptyNode()
  225. }
  226. createEdges(nodes[3], nodes[1])
  227. createEdges(nodes[1], nodes[2])
  228. createEdges(nodes[2], nodes[0])
  229. createEdges(nodes[0], nodes[4])
  230. setEdgeWeight(nodes[3].Out, nodes[1], 12)
  231. setEdgeWeight(nodes[1].Out, nodes[2], 8)
  232. setEdgeWeight(nodes[2].Out, nodes[0], 15)
  233. setEdgeWeight(nodes[0].Out, nodes[4], 10)
  234. // Create expected graph
  235. expected, keep := createExpectedNodes(nodes[3], nodes[4])
  236. createExpectedEdges(expected[0], expected[1])
  237. makeExpectedEdgeResidual(expected[0], expected[1])
  238. setEdgeWeight(expected[0].out, expected[1].node, 10)
  239. return trimTreeTestcase{
  240. initial: graph,
  241. expected: expected,
  242. keep: keep,
  243. }
  244. }
  245. // createTestCase3 creates an initially empty graph and expects an empty graph
  246. // after trimming.
  247. func createTestCase3() trimTreeTestcase {
  248. graph := &Graph{make(Nodes, 0)}
  249. expected, keep := createExpectedNodes()
  250. return trimTreeTestcase{
  251. initial: graph,
  252. expected: expected,
  253. keep: keep,
  254. }
  255. }
  256. // createTestCase4 creates a test case that initially looks like:
  257. // 0.
  258. //
  259. // After keeping 0, it expects the graph:
  260. // 0.
  261. func createTestCase4() trimTreeTestcase {
  262. graph := &Graph{make(Nodes, 1)}
  263. nodes := graph.Nodes
  264. for i := range nodes {
  265. nodes[i] = createEmptyNode()
  266. }
  267. expected, keep := createExpectedNodes(nodes[0])
  268. return trimTreeTestcase{
  269. initial: graph,
  270. expected: expected,
  271. keep: keep,
  272. }
  273. }
  274. func createTrimTreeTestCases() []trimTreeTestcase {
  275. caseGenerators := []func() trimTreeTestcase{
  276. createTestCase1,
  277. createTestCase2,
  278. createTestCase3,
  279. createTestCase4,
  280. }
  281. cases := make([]trimTreeTestcase, len(caseGenerators))
  282. for i, gen := range caseGenerators {
  283. cases[i] = gen()
  284. }
  285. return cases
  286. }
  287. func TestTrimTree(t *testing.T) {
  288. tests := createTrimTreeTestCases()
  289. for _, test := range tests {
  290. graph := test.initial
  291. graph.TrimTree(test.keep)
  292. if !graphsEqual(graph, test.expected) {
  293. t.Fatalf("Graphs do not match.\nExpected: %s\nFound: %s\n",
  294. expectedNodesDebugString(test.expected),
  295. graphDebugString(graph))
  296. }
  297. }
  298. }
  299. func nodeTestProfile() *profile.Profile {
  300. mappings := []*profile.Mapping{
  301. {
  302. ID: 1,
  303. File: "symbolized_binary",
  304. },
  305. {
  306. ID: 2,
  307. File: "unsymbolized_library_1",
  308. },
  309. {
  310. ID: 3,
  311. File: "unsymbolized_library_2",
  312. },
  313. }
  314. functions := []*profile.Function{
  315. {ID: 1, Name: "symname"},
  316. {ID: 2},
  317. }
  318. locations := []*profile.Location{
  319. {
  320. ID: 1,
  321. Mapping: mappings[0],
  322. Line: []profile.Line{
  323. {Function: functions[0]},
  324. },
  325. },
  326. {
  327. ID: 2,
  328. Mapping: mappings[1],
  329. Line: []profile.Line{
  330. {Function: functions[1]},
  331. },
  332. },
  333. {
  334. ID: 3,
  335. Mapping: mappings[2],
  336. },
  337. }
  338. return &profile.Profile{
  339. PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
  340. SampleType: []*profile.ValueType{
  341. {Type: "type", Unit: "unit"},
  342. },
  343. Sample: []*profile.Sample{
  344. {
  345. Location: []*profile.Location{locations[0]},
  346. Value: []int64{1},
  347. },
  348. {
  349. Location: []*profile.Location{locations[1]},
  350. Value: []int64{1},
  351. },
  352. {
  353. Location: []*profile.Location{locations[2]},
  354. Value: []int64{1},
  355. },
  356. },
  357. Location: locations,
  358. Function: functions,
  359. Mapping: mappings,
  360. }
  361. }
  362. // TestCreateNodes checks that nodes are properly created for a simple profile.
  363. func TestCreateNodes(t *testing.T) {
  364. testProfile := nodeTestProfile()
  365. wantNodeSet := NodeSet{
  366. {Name: "symname"}: true,
  367. {Objfile: "unsymbolized_library_1"}: true,
  368. {Objfile: "unsymbolized_library_2"}: true,
  369. }
  370. nodes, _ := CreateNodes(testProfile, &Options{})
  371. if len(nodes) != len(wantNodeSet) {
  372. t.Errorf("got %d nodes, want %d", len(nodes), len(wantNodeSet))
  373. }
  374. for _, node := range nodes {
  375. if !wantNodeSet[node.Info] {
  376. t.Errorf("unexpected node %v", node.Info)
  377. }
  378. }
  379. }
  380. func TestShortenFunctionName(t *testing.T) {
  381. type testCase struct {
  382. name string
  383. want string
  384. }
  385. testcases := []testCase{
  386. {
  387. "root",
  388. "root",
  389. },
  390. {
  391. "syscall.Syscall",
  392. "syscall.Syscall",
  393. },
  394. {
  395. "net/http.(*conn).serve",
  396. "http.(*conn).serve",
  397. },
  398. {
  399. "github.com/blahBlah/foo.Foo",
  400. "foo.Foo",
  401. },
  402. {
  403. "github.com/BlahBlah/foo.Foo",
  404. "foo.Foo",
  405. },
  406. {
  407. "github.com/blah-blah/foo_bar.(*FooBar).Foo",
  408. "foo_bar.(*FooBar).Foo",
  409. },
  410. {
  411. "encoding/json.(*structEncoder).(encoding/json.encode)-fm",
  412. "json.(*structEncoder).(encoding/json.encode)-fm",
  413. },
  414. {
  415. "github.com/blah/blah/vendor/gopkg.in/redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
  416. "redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm",
  417. },
  418. {
  419. "java.util.concurrent.ThreadPoolExecutor$Worker.run",
  420. "ThreadPoolExecutor$Worker.run",
  421. },
  422. {
  423. "java.bar.foo.FooBar.run(java.lang.Runnable)",
  424. "FooBar.run",
  425. },
  426. {
  427. "(anonymous namespace)::Bar::Foo",
  428. "Bar::Foo",
  429. },
  430. {
  431. "(anonymous namespace)::foo",
  432. "foo",
  433. },
  434. {
  435. "foo_bar::Foo::bar",
  436. "Foo::bar",
  437. },
  438. {
  439. "foo",
  440. "foo",
  441. },
  442. {
  443. "com.google.perftools.gwp.benchmark.FloatBench.lambda$run$0",
  444. "FloatBench.lambda$run$0",
  445. },
  446. {
  447. "java.bar.foo.FooBar.run$0",
  448. "FooBar.run$0",
  449. },
  450. }
  451. for _, tc := range testcases {
  452. name := ShortenFunctionName(tc.name)
  453. if got, want := name, tc.want; got != want {
  454. t.Errorf("ShortenFunctionName(%q) = %q, want %q", tc.name, got, want)
  455. }
  456. }
  457. }