summaryrefslogtreecommitdiff
path: root/lib/btrfsprogs
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-09-04 15:16:45 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2022-09-04 15:16:45 -0600
commita16c3b96ddfae01a2e89c1855058230eb369263c (patch)
treeb13aed81f5aac2a6cd6a22976a99daf3e80293c8 /lib/btrfsprogs
parentc2e3f389ecebd887935c13c76679b0dae8bc366b (diff)
1st pass at visualize-nodes
Diffstat (limited to 'lib/btrfsprogs')
-rw-r--r--lib/btrfsprogs/btrfsinspect/rebuildnodes/visualizenodes.go170
1 files changed, 170 insertions, 0 deletions
diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/visualizenodes.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/visualizenodes.go
new file mode 100644
index 0000000..ffb07c3
--- /dev/null
+++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/visualizenodes.go
@@ -0,0 +1,170 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package rebuildnodes
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "html"
+ "io"
+ iofs "io/fs"
+ "strings"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsprim"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfstree"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsinspect"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil"
+ "git.lukeshu.com/btrfs-progs-ng/lib/containers"
+ "git.lukeshu.com/btrfs-progs-ng/lib/diskio"
+ "git.lukeshu.com/btrfs-progs-ng/lib/maps"
+)
+
+func VisualizeNodes(ctx context.Context, out io.Writer, fs *btrfs.FS, nodeScanResults btrfsinspect.ScanDevicesResult) error {
+ uuidMap, err := buildUUIDMap(ctx, fs, nodeScanResults)
+ if err != nil {
+ return err
+ }
+
+ nfs := &RebuiltTrees{
+ inner: fs,
+ uuidMap: uuidMap,
+ }
+
+ orphanedNodes, _, treeAncestors, err := classifyNodes(ctx, nfs, nodeScanResults)
+ if err != nil {
+ return err
+ }
+
+ uuidMap.considerAncestors(ctx, treeAncestors)
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+
+ nodes := make(map[btrfsprim.ObjID]containers.Set[string])
+ edges := make(containers.Set[string])
+ visitedNodes := make(containers.Set[btrfsvol.LogicalAddr])
+ var isOrphan bool
+
+ nodeHandler := func(path btrfstree.TreePath, nodeRef *diskio.Ref[btrfsvol.LogicalAddr, btrfstree.Node], err error) error {
+ addr := path.Node(-1).ToNodeAddr
+
+ // Node
+ var treeID btrfsprim.ObjID
+ var nodeStr string
+ if err != nil && (errors.Is(err, btrfstree.ErrNotANode) || errors.As(err, new(*btrfstree.IOError))) {
+ treeID = 0
+ nodeStr = fmt.Sprintf(`n%d [shape=star label="%v"]`, addr, addr)
+ } else {
+ treeID = nodeRef.Data.Head.Owner
+ var buf strings.Builder
+ fmt.Fprintf(&buf, `n%d [shape=record label="%v\ngen=%v\nlvl=%v|`,
+ addr,
+ nodeRef.Data.Head.Addr,
+ nodeRef.Data.Head.Generation,
+ nodeRef.Data.Head.Level)
+ if nodeRef.Data.Head.Level == 0 {
+ for i, item := range nodeRef.Data.BodyLeaf {
+ sep := "|"
+ if i == 0 {
+ sep = "{"
+ }
+ fmt.Fprintf(&buf, "%s<p%d>%d: (%d,%v,%d)",
+ sep, i, i,
+ item.Key.ObjectID,
+ item.Key.ItemType,
+ item.Key.Offset)
+ }
+ } else {
+ for i, ptr := range nodeRef.Data.BodyInternal {
+ sep := "|"
+ if i == 0 {
+ sep = "{"
+ }
+ fmt.Fprintf(&buf, "%s<p%d>%d: (%d,%v,%d) gen=%v",
+ sep, i, i,
+ ptr.Key.ObjectID,
+ ptr.Key.ItemType,
+ ptr.Key.Offset,
+ ptr.Generation)
+ }
+ }
+ buf.WriteString(`}"]`)
+ nodeStr = buf.String()
+ }
+ if _, ok := nodes[treeID]; !ok {
+ nodes[treeID] = make(containers.Set[string])
+ nodes[treeID].Insert(fmt.Sprintf(`t%d [label="%s"]`, treeID, html.EscapeString(treeID.String())))
+ }
+ nodes[treeID].Insert(nodeStr)
+
+ // Edge
+ var edge strings.Builder
+ if len(path) == 1 {
+ if isOrphan {
+ edge.WriteString("orphanRoot")
+ } else {
+ fmt.Fprintf(&edge, "t%d", path[0].FromTree)
+ }
+ } else {
+ fmt.Fprintf(&edge, "n%d:p%d", path.Node(-2).ToNodeAddr, path.Node(-1).FromItemIdx)
+ }
+ fmt.Fprintf(&edge, " -> n%d", addr)
+ if err != nil {
+ fmt.Fprintf(&edge, ` [color=red label="%s"]`, html.EscapeString(err.Error()))
+ }
+ edges.Insert(edge.String())
+
+ // Return
+ if _, alreadyVisited := visitedNodes[addr]; alreadyVisited {
+ return iofs.SkipDir
+ }
+ visitedNodes[addr] = struct{}{}
+ return err
+ }
+
+ walkHandler := btrfstree.TreeWalkHandler{
+ Node: func(path btrfstree.TreePath, nodeRef *diskio.Ref[btrfsvol.LogicalAddr, btrfstree.Node]) error {
+ return nodeHandler(path, nodeRef, nil)
+ },
+ BadNode: func(path btrfstree.TreePath, nodeRef *diskio.Ref[btrfsvol.LogicalAddr, btrfstree.Node], err error) error {
+ return nodeHandler(path, nodeRef, err)
+ },
+ }
+
+ btrfsutil.WalkAllTrees(ctx, nfs, btrfsutil.WalkAllTreesHandler{
+ TreeWalkHandler: walkHandler,
+ Err: func(err *btrfsutil.WalkError) {
+ // do nothing
+ },
+ })
+ isOrphan = true
+ for _, potentialRoot := range maps.SortedKeys(orphanedNodes) {
+ walkFromNode(ctx, nfs, potentialRoot,
+ func(err *btrfstree.TreeError) {
+ // do nothing
+ },
+ walkHandler,
+ )
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+
+ fmt.Fprintln(out, "digraph FS {")
+ for _, treeID := range maps.SortedKeys(nodes) {
+ fmt.Fprintf(out, " subgraph cluster_%d {\n", treeID)
+ for _, node := range maps.SortedKeys(nodes[treeID]) {
+ fmt.Fprintf(out, " %s;\n", node)
+ }
+ fmt.Fprintln(out, " }")
+ }
+ for _, edge := range maps.SortedKeys(edges) {
+ fmt.Fprintf(out, " %s;\n", edge)
+ }
+ fmt.Fprintln(out, "}")
+
+ return nil
+}