github.com/ledgerwatch/erigon-lib@v1.0.0/bptree/graph.go (about) 1 /* 2 Copyright 2022 Erigon contributors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package bptree 18 19 import ( 20 "fmt" 21 "log" 22 "os" 23 "os/exec" 24 "strconv" 25 ) 26 27 type Node23Graph struct { 28 node *Node23 29 } 30 31 func NewGraph(node *Node23) *Node23Graph { 32 return &Node23Graph{node} 33 } 34 35 func (g *Node23Graph) saveDot(filename string, debug bool) { 36 palette := []string{"#FDF3D0", "#DCE8FA", "#D9E7D6", "#F1CFCD", "#F5F5F5", "#E1D5E7", "#FFE6CC", "white"} 37 const unexposedIndex = 0 38 const exposedIndex = 1 39 const updatedIndex = 2 40 41 f, err := os.OpenFile(filename+".dot", os.O_RDWR|os.O_CREATE, 0755) 42 if err != nil { 43 log.Fatal(err) 44 } 45 defer func() { 46 if err := f.Close(); err != nil { 47 log.Fatal(err) 48 } 49 }() 50 if g.node == nil { 51 if _, err := f.WriteString("strict digraph {\nnode [shape=record];}\n"); err != nil { 52 log.Fatal(err) 53 } 54 return 55 } 56 if _, err := f.WriteString("strict digraph {\nnode [shape=record];\n"); err != nil { 57 log.Fatal(err) 58 } 59 for _, n := range g.node.walkNodesPostOrder() { 60 left, down, right := "", "", "" 61 switch n.childrenCount() { 62 case 1: 63 left = "<L>L" 64 case 2: 65 left = "<L>L" 66 right = "<R>R" 67 case 3: 68 left = "<L>L" 69 down = "<D>D" 70 right = "<R>R" 71 } 72 var nodeID string 73 if n.isLeaf { 74 var next string 75 if n.keyCount() > 0 { 76 if n.nextKey() == nil { 77 next = "nil" 78 } else { 79 next = strconv.FormatUint(uint64(*n.nextKey()), 10) 80 } 81 if debug { 82 nodeID = fmt.Sprintf("k=%v %s-%v", deref(n.keys[:len(n.keys)-1]), next, n.keys) 83 } else { 84 nodeID = fmt.Sprintf("k=%v %s", deref(n.keys[:len(n.keys)-1]), next) 85 } 86 } else { 87 nodeID = "k=[]" 88 } 89 } else { 90 if debug { 91 nodeID = fmt.Sprintf("k=%v-%v", deref(n.keys), n.keys) 92 } else { 93 nodeID = fmt.Sprintf("k=%v", deref(n.keys)) 94 } 95 } 96 var color string 97 if n.exposed { 98 if n.updated { 99 color = palette[updatedIndex] 100 } else { 101 color = palette[exposedIndex] 102 } 103 } else { 104 ensure(!n.updated, fmt.Sprintf("saveDot: node %v is not exposed but updated", n)) 105 color = palette[unexposedIndex] 106 } 107 s := fmt.Sprintf("%d [label=\"%s|{<C>%s|%s}|%s\" style=filled fillcolor=\"%s\"];\n", n.rawPointer(), left, nodeID, down, right, color) 108 if _, err := f.WriteString(s); err != nil { 109 log.Fatal(err) 110 } 111 } 112 for _, n := range g.node.walkNodesPostOrder() { 113 var treeLeft, treeDown, treeRight *Node23 114 switch n.childrenCount() { 115 case 1: 116 treeLeft = n.children[0] 117 case 2: 118 treeLeft = n.children[0] 119 treeRight = n.children[1] 120 case 3: 121 treeLeft = n.children[0] 122 treeDown = n.children[1] 123 treeRight = n.children[2] 124 } 125 if treeLeft != nil { 126 //if _, err := f.WriteString(fmt.Sprintln(n.rawPointer(), ":L -> ", treeLeft.rawPointer(), ":C;")); err != nil { 127 if _, err := f.WriteString(fmt.Sprintf("%d:L -> %d:C;\n", n.rawPointer(), treeLeft.rawPointer())); err != nil { 128 log.Fatal(err) 129 } 130 } 131 if treeDown != nil { 132 //if _, err := f.WriteString(fmt.Sprintln(n.rawPointer(), ":D -> ", treeDown.rawPointer(), ":C;")); err != nil { 133 if _, err := f.WriteString(fmt.Sprintf("%d:D -> %d:C;\n", n.rawPointer(), treeDown.rawPointer())); err != nil { 134 log.Fatal(err) 135 } 136 } 137 if treeRight != nil { 138 //if _, err := f.WriteString(fmt.Sprintln(n.rawPointer(), ":R -> ", treeRight.rawPointer(), ":C;")); err != nil { 139 if _, err := f.WriteString(fmt.Sprintf("%d:R -> %d:C;\n", n.rawPointer(), treeRight.rawPointer())); err != nil { 140 log.Fatal(err) 141 } 142 } 143 } 144 if _, err := f.WriteString("}\n"); err != nil { 145 log.Fatal(err) 146 } 147 } 148 149 func (g *Node23Graph) saveDotAndPicture(filename string, debug bool) error { 150 graphDir := "testdata/graph/" 151 _ = os.MkdirAll(graphDir, os.ModePerm) 152 filepath := graphDir + filename 153 _ = os.Remove(filepath + ".dot") 154 _ = os.Remove(filepath + ".png") 155 g.saveDot(filepath, debug) 156 dotExecutable, _ := exec.LookPath("dot") 157 cmdDot := &exec.Cmd{ 158 Path: dotExecutable, 159 Args: []string{dotExecutable, "-Tpng", filepath + ".dot", "-o", filepath + ".png"}, 160 Stdout: os.Stdout, 161 Stderr: os.Stderr, 162 } 163 if err := cmdDot.Run(); err != nil { 164 return err 165 } 166 return nil 167 }