Line data Source code
1 : // Copyright 2024 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 : // of this source code is governed by a BSD-style license that can be found in 3 : // the LICENSE file. 4 : 5 : package treesteps 6 : 7 : import ( 8 : "bytes" 9 : "compress/zlib" 10 : "encoding/base64" 11 : "encoding/json" 12 : "fmt" 13 : "net/url" 14 : "strings" 15 : 16 : "github.com/cockroachdb/pebble/internal/treeprinter" 17 : ) 18 : 19 : // Steps contains the result of a recording: the full details of the hierarchy 20 : // for each step. 21 : type Steps struct { 22 : Name string 23 : Steps []Step `json:"steps"` 24 : } 25 : 26 : // Step contains the tree for a single step in a recording. 27 : type Step struct { 28 : Name string `json:"name"` 29 : Root TreeNode `json:"tree"` 30 : } 31 : 32 : // TreeNode is a node in the tree for a step in a recording. 33 : type TreeNode struct { 34 : Name string `json:"name"` 35 : Properties [][2]string `json:"props"` 36 : Ops []string `json:"ops"` 37 : Children []TreeNode `json:"children"` 38 : } 39 : 40 : // String returns the steps as a string (using treeprinter). 41 1 : func (s Steps) String() string { 42 1 : var buf strings.Builder 43 1 : for step := range s.Steps { 44 1 : if len(s.Steps) > 1 { 45 1 : fmt.Fprintf(&buf, "step %d/%d:\n", step+1, len(s.Steps)) 46 1 : } 47 1 : out := s.Steps[step].Root.String() 48 1 : if len(s.Steps) > 1 { 49 1 : for _, l := range strings.Split(strings.TrimSpace(out), "\n") { 50 1 : fmt.Fprintf(&buf, " %s\n", l) 51 1 : } 52 0 : } else { 53 0 : buf.WriteString(out) 54 0 : } 55 : } 56 1 : return buf.String() 57 : } 58 : 59 1 : func (t *TreeNode) String() string { 60 1 : tp := treeprinter.New() 61 1 : t.print(tp) 62 1 : return tp.String() 63 1 : } 64 : 65 1 : func (t *TreeNode) print(parent treeprinter.Node) { 66 1 : name := t.Name 67 1 : for i := range t.Ops { 68 1 : name += fmt.Sprintf(" ← %s", t.Ops[i]) 69 1 : } 70 1 : n := parent.Child(name) 71 1 : for _, p := range t.Properties { 72 1 : n.Childf("%s: %s", p[0], p[1]) 73 1 : } 74 1 : for i := range t.Children { 75 1 : t.Children[i].print(n) 76 1 : } 77 : } 78 : 79 : // URL for a visualization of the steps. The URL contains the encoded and 80 : // compressed data as the URL fragment. 81 1 : func (s Steps) URL() url.URL { 82 1 : // TODO(radu): ideally we would encode Steps and have a graphical 83 1 : // visualization. For now, we generate and encode the ASCII trees. 84 1 : var output struct { 85 1 : Name string 86 1 : StepNames []string 87 1 : Steps []string 88 1 : } 89 1 : output.Name = s.Name 90 1 : output.StepNames = make([]string, len(s.Steps)) 91 1 : output.Steps = make([]string, len(s.Steps)) 92 1 : for i := range s.Steps { 93 1 : output.StepNames[i] = s.Steps[i].Name 94 1 : output.Steps[i] = s.Steps[i].Root.String() 95 1 : } 96 : 97 1 : var jsonBuf bytes.Buffer 98 1 : if err := json.NewEncoder(&jsonBuf).Encode(&output); err != nil { 99 0 : panic(err) 100 : } 101 1 : var compressed bytes.Buffer 102 1 : encoder := base64.NewEncoder(base64.URLEncoding, &compressed) 103 1 : compressor := zlib.NewWriter(encoder) 104 1 : if _, err := jsonBuf.WriteTo(compressor); err != nil { 105 0 : panic(err) 106 : } 107 1 : if err := compressor.Close(); err != nil { 108 0 : panic(err) 109 : } 110 1 : if err := encoder.Close(); err != nil { 111 0 : panic(err) 112 : } 113 1 : return url.URL{ 114 1 : Scheme: "https", 115 1 : Host: "raduberinde.github.io", 116 1 : Path: "treesteps/decode.html", 117 1 : Fragment: compressed.String(), 118 1 : } 119 : }