github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/blockchain/info.go (about) 1 package blockchain 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "net/http" 7 8 "github.com/emicklei/dot" 9 "github.com/prysmaticlabs/prysm/shared/params" 10 ) 11 12 const template = `<html> 13 <head> 14 <script src="//cdnjs.cloudflare.com/ajax/libs/viz.js/2.1.2/viz.js"></script> 15 <script src="//cdnjs.cloudflare.com/ajax/libs/viz.js/2.1.2/full.render.js"></script> 16 <body> 17 <script type="application/javascript"> 18 var graph = ` + "`%s`;" + ` 19 var viz = new Viz(); 20 viz.renderSVGElement(graph) // reading the graph. 21 .then(function(element) { 22 document.body.appendChild(element); // appends to document. 23 }) 24 .catch(error => { 25 // Create a new Viz instance (@see Caveats page for more info) 26 viz = new Viz(); 27 // Possibly display the error 28 console.error(error); 29 }); 30 </script> 31 </head> 32 </body> 33 </html>` 34 35 // TreeHandler is a handler to serve /tree page in metrics. 36 func (s *Service) TreeHandler(w http.ResponseWriter, r *http.Request) { 37 headState, err := s.HeadState(r.Context()) 38 if err != nil { 39 log.WithError(err).Error("Could not get head state") 40 return 41 } 42 if headState == nil || headState.IsNil() { 43 if _, err := w.Write([]byte("Unavailable during initial syncing")); err != nil { 44 log.WithError(err).Error("Failed to render p2p info page") 45 } 46 } 47 48 nodes := s.cfg.ForkChoiceStore.Nodes() 49 50 graph := dot.NewGraph(dot.Directed) 51 graph.Attr("rankdir", "RL") 52 graph.Attr("labeljust", "l") 53 54 dotNodes := make([]*dot.Node, len(nodes)) 55 avgBalance := uint64(averageBalance(headState.Balances())) 56 57 for i := len(nodes) - 1; i >= 0; i-- { 58 // Construct label for each node. 59 slot := fmt.Sprintf("%d", nodes[i].Slot()) 60 weight := fmt.Sprintf("%d", nodes[i].Weight()/1e9) // Convert unit Gwei to unit ETH. 61 votes := fmt.Sprintf("%d", nodes[i].Weight()/1e9/avgBalance) 62 index := fmt.Sprintf("%d", i) 63 g := nodes[i].Graffiti() 64 graffiti := hex.EncodeToString(g[:8]) 65 label := "slot: " + slot + "\n votes: " + votes + "\n weight: " + weight + "\n graffiti: " + graffiti 66 var dotN dot.Node 67 if nodes[i].Parent() != ^uint64(0) { 68 dotN = graph.Node(index).Box().Attr("label", label) 69 } 70 71 if nodes[i].Slot() == s.HeadSlot() && 72 nodes[i].BestDescendant() == ^uint64(0) && 73 nodes[i].Parent() != ^uint64(0) { 74 dotN = dotN.Attr("color", "green") 75 } 76 77 dotNodes[i] = &dotN 78 } 79 80 for i := len(nodes) - 1; i >= 0; i-- { 81 if nodes[i].Parent() != ^uint64(0) && nodes[i].Parent() < uint64(len(dotNodes)) { 82 graph.Edge(*dotNodes[i], *dotNodes[nodes[i].Parent()]) 83 } 84 } 85 86 w.WriteHeader(http.StatusOK) 87 w.Header().Set("Content-Type", "text/html") 88 if _, err := fmt.Fprintf(w, template, graph.String()); err != nil { 89 log.WithError(err).Error("Failed to render p2p info page") 90 } 91 } 92 93 func averageBalance(balances []uint64) float64 { 94 total := uint64(0) 95 for i := 0; i < len(balances); i++ { 96 total += balances[i] 97 } 98 return float64(total) / float64(len(balances)) / float64(params.BeaconConfig().GweiPerEth) 99 }