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  }