|
| 1 | +// Copyright 2025 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 provides a framework for recording and visualizing |
| 6 | +// step-by-step operations on hierarchical data structures. |
| 7 | +// |
| 8 | +// The package captures the propagation of operations through a tree-like |
| 9 | +// structure, recording each intermediate step along with any state changes. |
| 10 | +// This is particularly useful for understanding and debugging how operations |
| 11 | +// traverse and modify complex hierarchies. |
| 12 | +// |
| 13 | +// # Basic Usage |
| 14 | +// |
| 15 | +// To use treesteps, your hierarchical structure must implement the Node |
| 16 | +// interface. Each node should provide: |
| 17 | +// - A descriptive name |
| 18 | +// - Key properties (as key-value pairs) |
| 19 | +// - References to child nodes |
| 20 | +// |
| 21 | +// Recording works as follows: |
| 22 | +// |
| 23 | +// 1. Start a recording with StartRecording() on the root node |
| 24 | +// 2. For each operation on a node, call StartOpf() before and Finishf() after |
| 25 | +// 3. Call NodeUpdated() whenever a node's state changes significantly |
| 26 | +// 4. Call Finish() to obtain the recorded steps |
| 27 | +// |
| 28 | +// The result can be rendered as text or exported as a URL to an interactive |
| 29 | +// visualization. |
| 30 | +// |
| 31 | +// # Build Tags |
| 32 | +// |
| 33 | +// The treesteps functionality is only available when building with the |
| 34 | +// 'invariants' tag. Without this tag, all recording operations become no-ops. |
| 35 | +// This allows instrumentation to remain in production code without performance |
| 36 | +// impact. |
| 37 | +// |
| 38 | +// # Example |
| 39 | +// |
| 40 | +// This example demonstrates recording operations on a simple binary tree that |
| 41 | +// tracks the sum of values at each node: |
| 42 | +// |
| 43 | +// type SumTree struct { |
| 44 | +// Value int |
| 45 | +// Sum int |
| 46 | +// Left *SumTree |
| 47 | +// Right *SumTree |
| 48 | +// } |
| 49 | +// |
| 50 | +// // TreeStepsNode implements the treesteps.Node interface. |
| 51 | +// func (t *SumTree) TreeStepsNode() treesteps.NodeInfo { |
| 52 | +// info := treesteps.NodeInfof("node") |
| 53 | +// info.AddPropf("value", "%d", t.Value) |
| 54 | +// info.AddPropf("sum", "%d", t.Sum) |
| 55 | +// info.AddChildren(t.Left, t.Right) |
| 56 | +// return info |
| 57 | +// } |
| 58 | +// |
| 59 | +// // UpdateValue sets a new value and propagates the change up the tree. |
| 60 | +// func (t *SumTree) UpdateValue(newValue int) { |
| 61 | +// op := treesteps.StartOpf(t, "update(%d)", newValue) |
| 62 | +// t.Value = newValue |
| 63 | +// treesteps.NodeUpdated(t, "value changed") |
| 64 | +// t.recomputeSum() |
| 65 | +// op.Finishf("done") |
| 66 | +// } |
| 67 | +// |
| 68 | +// func (t *SumTree) recomputeSum() {) |
| 69 | +// sum := t.Value |
| 70 | +// if t.Left != nil { |
| 71 | +// sum += t.Left.Sum |
| 72 | +// } |
| 73 | +// if t.Right != nil { |
| 74 | +// sum += t.Right.Sum |
| 75 | +// } |
| 76 | +// if sum != t.Sum { |
| 77 | +// t.Sum = sum |
| 78 | +// treesteps.NodeUpdated(t, "sum updated") |
| 79 | +// } |
| 80 | +// } |
| 81 | +// |
| 82 | +// func Example() { |
| 83 | +// root := &SumTree{Value: 5} |
| 84 | +// root.Left = &SumTree{Value: 3} |
| 85 | +// root.Right = &SumTree{Value: 7} |
| 86 | +// |
| 87 | +// // Start recording |
| 88 | +// rec := treesteps.StartRecording(root, "Update value") |
| 89 | +// |
| 90 | +// // Perform the operation |
| 91 | +// root.Left.UpdateValue(10) |
| 92 | +// |
| 93 | +// // Finish and get the steps |
| 94 | +// steps := rec.Finish() |
| 95 | +// |
| 96 | +// // Print the recorded steps |
| 97 | +// fmt.Println(steps.String()) |
| 98 | +// |
| 99 | +// // Or get a URL for visualization |
| 100 | +// url := steps.URL() |
| 101 | +// fmt.Println("Visualization:", url.String()) |
| 102 | +// } |
| 103 | +// |
| 104 | +// # Recording Options |
| 105 | +// |
| 106 | +// The MaxTreeDepth and MaxOpDepth options can limit the depth of recording, |
| 107 | +// which is useful for large or deeply nested structures: |
| 108 | +// |
| 109 | +// rec := treesteps.StartRecording(root, "operation", |
| 110 | +// treesteps.MaxTreeDepth(5), |
| 111 | +// treesteps.MaxOpDepth(3), |
| 112 | +// ) |
| 113 | +// |
| 114 | +// # Performance Considerations |
| 115 | +// |
| 116 | +// Check Enabled && IsRecording() before formatting operations: |
| 117 | +// |
| 118 | +// if treesteps.Enabled && treesteps.IsRecording(node) { |
| 119 | +// op := treesteps.StartOpf(node, "expensive operation: %s", expensiveFormat()) |
| 120 | +// defer op.Finishf("done") |
| 121 | +// } |
| 122 | +// |
| 123 | +// This avoids allocations when no recording is active. It also allows |
| 124 | +// statically eliminating code in non-invariants builds. |
| 125 | +package treesteps |
0 commit comments