From a90e25a1298602c30d0b9cc0c86acede7c8e4ed4 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 24 Dec 2022 23:49:01 -0700 Subject: tree-wide: Outlaw fmt.Print, force textui --- cmd/btrfs-rec/inspect_dumptrees.go | 7 ++++--- cmd/btrfs-rec/inspect_lsfiles.go | 7 ++++--- cmd/btrfs-rec/inspect_lstrees.go | 5 +++-- cmd/btrfs-rec/inspect_spewitems.go | 6 +++--- 4 files changed, 14 insertions(+), 11 deletions(-) (limited to 'cmd/btrfs-rec') diff --git a/cmd/btrfs-rec/inspect_dumptrees.go b/cmd/btrfs-rec/inspect_dumptrees.go index 4abeaba..610f502 100644 --- a/cmd/btrfs-rec/inspect_dumptrees.go +++ b/cmd/btrfs-rec/inspect_dumptrees.go @@ -5,7 +5,6 @@ package main import ( - "fmt" "os" "github.com/datawire/ocibuild/pkg/cliutil" @@ -13,6 +12,7 @@ import ( "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsinspect" + "git.lukeshu.com/btrfs-progs-ng/lib/textui" ) func init() { @@ -24,8 +24,9 @@ func init() { }, RunE: func(fs *btrfs.FS, cmd *cobra.Command, _ []string) error { const version = "5.18.1" - fmt.Printf("btrfs-progs v%v\n", version) - btrfsinspect.DumpTrees(cmd.Context(), os.Stdout, fs) + out := os.Stdout + textui.Fprintf(out, "btrfs-progs v%v\n", version) + btrfsinspect.DumpTrees(cmd.Context(), out, fs) return nil }, }) diff --git a/cmd/btrfs-rec/inspect_lsfiles.go b/cmd/btrfs-rec/inspect_lsfiles.go index 9267f77..801da5d 100644 --- a/cmd/btrfs-rec/inspect_lsfiles.go +++ b/cmd/btrfs-rec/inspect_lsfiles.go @@ -22,6 +22,7 @@ import ( "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsprim" "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil" "git.lukeshu.com/btrfs-progs-ng/lib/maps" + "git.lukeshu.com/btrfs-progs-ng/lib/textui" ) func init() { @@ -32,20 +33,20 @@ func init() { Args: cliutil.WrapPositionalArgs(cobra.NoArgs), }, RunE: func(fs *btrfs.FS, cmd *cobra.Command, _ []string) (err error) { + out := bufio.NewWriter(os.Stdout) + defer out.Flush() defer func() { if r := derror.PanicToError(recover()); r != nil { - fmt.Printf("\n\n%+v\n", r) + textui.Fprintf(out, "\n\n%+v\n", r) err = fmt.Errorf("panicked") } }() ctx := cmd.Context() - out := bufio.NewWriter(os.Stdout) printSubvol(out, "", true, "/", &btrfs.Subvolume{ FS: btrfsutil.NewBrokenTrees(ctx, fs), TreeID: btrfsprim.FS_TREE_OBJECTID, }) - out.Flush() return nil }, diff --git a/cmd/btrfs-rec/inspect_lstrees.go b/cmd/btrfs-rec/inspect_lstrees.go index 54c2074..e0dc4e1 100644 --- a/cmd/btrfs-rec/inspect_lstrees.go +++ b/cmd/btrfs-rec/inspect_lstrees.go @@ -25,6 +25,7 @@ import ( "git.lukeshu.com/btrfs-progs-ng/lib/diskio" "git.lukeshu.com/btrfs-progs-ng/lib/maps" "git.lukeshu.com/btrfs-progs-ng/lib/slices" + "git.lukeshu.com/btrfs-progs-ng/lib/textui" ) func init() { @@ -69,7 +70,7 @@ func init() { PreTree: func(name string, treeID btrfsprim.ObjID) { treeErrCnt = 0 treeItemCnt = make(map[btrfsitem.Type]int) - fmt.Printf("tree id=%v name=%q\n", treeID, name) + textui.Fprintf(os.Stdout, "tree id=%v name=%q\n", treeID, name) }, Err: func(_ *btrfsutil.WalkError) { treeErrCnt++ @@ -98,7 +99,7 @@ func init() { if scandevicesFilename != "" { treeErrCnt = 0 treeItemCnt = make(map[btrfsitem.Type]int) - fmt.Printf("lost+found\n") + textui.Fprintf(os.Stdout, "lost+found\n") sb, _ := fs.Superblock() for _, devResults := range scanResults { for laddr := range devResults.FoundNodes { diff --git a/cmd/btrfs-rec/inspect_spewitems.go b/cmd/btrfs-rec/inspect_spewitems.go index a4e8d69..8d71797 100644 --- a/cmd/btrfs-rec/inspect_spewitems.go +++ b/cmd/btrfs-rec/inspect_spewitems.go @@ -5,7 +5,6 @@ package main import ( - "fmt" "os" "github.com/datawire/dlib/dlog" @@ -16,6 +15,7 @@ import ( "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfstree" "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil" + "git.lukeshu.com/btrfs-progs-ng/lib/textui" ) func init() { @@ -37,13 +37,13 @@ func init() { }, TreeWalkHandler: btrfstree.TreeWalkHandler{ Item: func(path btrfstree.TreePath, item btrfstree.Item) error { - fmt.Printf("%s = ", path) + textui.Fprintf(os.Stdout, "%s = ", path) spew.Dump(item) os.Stdout.WriteString("\n") return nil }, BadItem: func(path btrfstree.TreePath, item btrfstree.Item) error { - fmt.Printf("%s = ", path) + textui.Fprintf(os.Stdout, "%s = ", path) spew.Dump(item) os.Stdout.WriteString("\n") return nil -- cgit v1.2.3-54-g00ecf From 74bb10779338bbb06be44a523d1e166b760018f7 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 24 Dec 2022 23:55:03 -0700 Subject: tree-wide: Audit for fmt.Fprint usage, in favor of textui --- cmd/btrfs-rec/inspect_lstrees.go | 7 +- cmd/btrfs-rec/main.go | 4 +- lib/btrfsprogs/btrfsinspect/print_addrspace.go | 13 +- lib/btrfsprogs/btrfsinspect/print_tree.go | 193 +++++++++++++------------ 4 files changed, 108 insertions(+), 109 deletions(-) (limited to 'cmd/btrfs-rec') diff --git a/cmd/btrfs-rec/inspect_lstrees.go b/cmd/btrfs-rec/inspect_lstrees.go index e0dc4e1..7f59eaa 100644 --- a/cmd/btrfs-rec/inspect_lstrees.go +++ b/cmd/btrfs-rec/inspect_lstrees.go @@ -6,7 +6,6 @@ package main import ( "encoding/json" - "fmt" "os" "strconv" "text/tabwriter" @@ -58,11 +57,11 @@ func init() { numWidth := len(strconv.Itoa(slices.Max(treeErrCnt, totalItems))) table := tabwriter.NewWriter(os.Stdout, 0, 8, 2, ' ', 0) - fmt.Fprintf(table, " errors\t% *s\n", numWidth, strconv.Itoa(treeErrCnt)) + textui.Fprintf(table, " errors\t% *s\n", numWidth, strconv.Itoa(treeErrCnt)) for _, typ := range maps.SortedKeys(treeItemCnt) { - fmt.Fprintf(table, " %v items\t% *s\n", typ, numWidth, strconv.Itoa(treeItemCnt[typ])) + textui.Fprintf(table, " %v items\t% *s\n", typ, numWidth, strconv.Itoa(treeItemCnt[typ])) } - fmt.Fprintf(table, " total items\t% *s\n", numWidth, strconv.Itoa(totalItems)) + textui.Fprintf(table, " total items\t% *s\n", numWidth, strconv.Itoa(totalItems)) table.Flush() } visitedNodes := make(containers.Set[btrfsvol.LogicalAddr]) diff --git a/cmd/btrfs-rec/main.go b/cmd/btrfs-rec/main.go index c808237..a221cb7 100644 --- a/cmd/btrfs-rec/main.go +++ b/cmd/btrfs-rec/main.go @@ -7,7 +7,6 @@ package main import ( "context" "encoding/json" - "fmt" "os" "github.com/datawire/dlib/dgroup" @@ -20,6 +19,7 @@ import ( "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil" + "git.lukeshu.com/btrfs-progs-ng/lib/textui" ) type logLevelFlag struct { @@ -162,7 +162,7 @@ func main() { } if err := argparser.ExecuteContext(context.Background()); err != nil { - fmt.Fprintf(os.Stderr, "%v: error: %v\n", argparser.CommandPath(), err) + textui.Fprintf(os.Stderr, "%v: error: %v\n", argparser.CommandPath(), err) os.Exit(1) } } diff --git a/lib/btrfsprogs/btrfsinspect/print_addrspace.go b/lib/btrfsprogs/btrfsinspect/print_addrspace.go index cd90288..a8b992e 100644 --- a/lib/btrfsprogs/btrfsinspect/print_addrspace.go +++ b/lib/btrfsprogs/btrfsinspect/print_addrspace.go @@ -5,7 +5,6 @@ package btrfsinspect import ( - "fmt" "io" "sort" @@ -21,19 +20,19 @@ func PrintLogicalSpace(out io.Writer, fs *btrfs.FS) { for _, mapping := range mappings { if mapping.LAddr > prevEnd { size := mapping.LAddr.Sub(prevEnd) - fmt.Fprintf(out, "logical_hole laddr=%v size=%v\n", prevEnd, size) + textui.Fprintf(out, "logical_hole laddr=%v size=%v\n", prevEnd, size) sumHole += size } if mapping.LAddr != prevBeg { if !mapping.Flags.OK { - fmt.Fprintf(out, "chunk laddr=%v size=%v flags=(missing)\n", + textui.Fprintf(out, "chunk laddr=%v size=%v flags=(missing)\n", mapping.LAddr, mapping.Size) } else { - fmt.Fprintf(out, "chunk laddr=%v size=%v flags=%v\n", + textui.Fprintf(out, "chunk laddr=%v size=%v flags=%v\n", mapping.LAddr, mapping.Size, mapping.Flags.Val) } } - fmt.Fprintf(out, "\tstripe dev_id=%v paddr=%v\n", + textui.Fprintf(out, "\tstripe dev_id=%v paddr=%v\n", mapping.PAddr.Dev, mapping.PAddr.Addr) sumChunk += mapping.Size prevBeg = mapping.LAddr @@ -60,10 +59,10 @@ func PrintPhysicalSpace(out io.Writer, fs *btrfs.FS) { } if mapping.PAddr.Addr > prevEnd { size := mapping.PAddr.Addr.Sub(prevEnd) - fmt.Fprintf(out, "physical_hole paddr=%v size=%v\n", prevEnd, size) + textui.Fprintf(out, "physical_hole paddr=%v size=%v\n", prevEnd, size) sumHole += size } - fmt.Fprintf(out, "devext dev=%v paddr=%v size=%v laddr=%v\n", + textui.Fprintf(out, "devext dev=%v paddr=%v size=%v laddr=%v\n", mapping.PAddr.Dev, mapping.PAddr.Addr, mapping.Size, mapping.LAddr) sumExt += mapping.Size prevEnd = mapping.PAddr.Addr.Add(mapping.Size) diff --git a/lib/btrfsprogs/btrfsinspect/print_tree.go b/lib/btrfsprogs/btrfsinspect/print_tree.go index 9e5a5d1..b882c6b 100644 --- a/lib/btrfsprogs/btrfsinspect/print_tree.go +++ b/lib/btrfsprogs/btrfsinspect/print_tree.go @@ -22,6 +22,7 @@ import ( "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" "git.lukeshu.com/btrfs-progs-ng/lib/diskio" "git.lukeshu.com/btrfs-progs-ng/lib/slices" + "git.lukeshu.com/btrfs-progs-ng/lib/textui" ) func DumpTrees(ctx context.Context, out io.Writer, fs *btrfs.FS) { @@ -32,19 +33,19 @@ func DumpTrees(ctx context.Context, out io.Writer, fs *btrfs.FS) { } if superblock.RootTree != 0 { - fmt.Fprintf(out, "root tree\n") + textui.Fprintf(out, "root tree\n") printTree(ctx, out, fs, btrfsprim.ROOT_TREE_OBJECTID) } if superblock.ChunkTree != 0 { - fmt.Fprintf(out, "chunk tree\n") + textui.Fprintf(out, "chunk tree\n") printTree(ctx, out, fs, btrfsprim.CHUNK_TREE_OBJECTID) } if superblock.LogTree != 0 { - fmt.Fprintf(out, "log root tree\n") + textui.Fprintf(out, "log root tree\n") printTree(ctx, out, fs, btrfsprim.TREE_LOG_OBJECTID) } if superblock.BlockGroupRoot != 0 { - fmt.Fprintf(out, "block group tree\n") + textui.Fprintf(out, "block group tree\n") printTree(ctx, out, fs, btrfsprim.BLOCK_GROUP_TREE_OBJECTID) } fs.TreeWalk( @@ -81,15 +82,15 @@ func DumpTrees(ctx context.Context, out io.Writer, fs *btrfs.FS) { if !ok { treeName = "file" } - fmt.Fprintf(out, "%v tree %v \n", treeName, fmtKey(item.Key)) + textui.Fprintf(out, "%v tree %v \n", treeName, fmtKey(item.Key)) printTree(ctx, out, fs, item.Key.ObjectID) return nil }, }, ) - fmt.Fprintf(out, "total bytes %v\n", superblock.TotalBytes) - fmt.Fprintf(out, "bytes used %v\n", superblock.BytesUsed) - fmt.Fprintf(out, "uuid %v\n", superblock.FSUUID) + textui.Fprintf(out, "total bytes %v\n", superblock.TotalBytes) + textui.Fprintf(out, "bytes used %v\n", superblock.BytesUsed) + textui.Fprintf(out, "uuid %v\n", superblock.FSUUID) } // printTree mimics btrfs-progs @@ -104,7 +105,7 @@ func printTree(ctx context.Context, out io.Writer, fs *btrfs.FS, treeID btrfspri return nil }, PreKeyPointer: func(_ btrfstree.TreePath, item btrfstree.KeyPointer) error { - fmt.Fprintf(out, "\t%v block %v gen %v\n", + textui.Fprintf(out, "\t%v block %v gen %v\n", fmtKey(item.Key), item.BlockPtr, item.Generation) @@ -115,65 +116,65 @@ func printTree(ctx context.Context, out io.Writer, fs *btrfs.FS, treeID btrfspri bs, _ := binstruct.Marshal(item.Body) itemSize := uint32(len(bs)) itemOffset -= itemSize - fmt.Fprintf(out, "\titem %v %v itemoff %v itemsize %v\n", + textui.Fprintf(out, "\titem %v %v itemoff %v itemsize %v\n", i, fmtKey(item.Key), itemOffset, itemSize) switch body := item.Body.(type) { case btrfsitem.FreeSpaceHeader: - fmt.Fprintf(out, "\t\tlocation %v\n", fmtKey(body.Location)) - fmt.Fprintf(out, "\t\tcache generation %v entries %v bitmaps %v\n", + textui.Fprintf(out, "\t\tlocation %v\n", fmtKey(body.Location)) + textui.Fprintf(out, "\t\tcache generation %v entries %v bitmaps %v\n", body.Generation, body.NumEntries, body.NumBitmaps) case btrfsitem.Inode: - fmt.Fprintf(out, ""+ + textui.Fprintf(out, ""+ "\t\tgeneration %v transid %v size %v nbytes %v\n"+ "\t\tblock group %v mode %o links %v uid %v gid %v rdev %v\n"+ "\t\tsequence %v flags %v\n", body.Generation, body.TransID, body.Size, body.NumBytes, body.BlockGroup, body.Mode, body.NLink, body.UID, body.GID, body.RDev, body.Sequence, body.Flags) - fmt.Fprintf(out, "\t\tatime %v\n", fmtTime(body.ATime)) - fmt.Fprintf(out, "\t\tctime %v\n", fmtTime(body.CTime)) - fmt.Fprintf(out, "\t\tmtime %v\n", fmtTime(body.MTime)) - fmt.Fprintf(out, "\t\totime %v\n", fmtTime(body.OTime)) + textui.Fprintf(out, "\t\tatime %v\n", fmtTime(body.ATime)) + textui.Fprintf(out, "\t\tctime %v\n", fmtTime(body.CTime)) + textui.Fprintf(out, "\t\tmtime %v\n", fmtTime(body.MTime)) + textui.Fprintf(out, "\t\totime %v\n", fmtTime(body.OTime)) case btrfsitem.InodeRefs: for _, ref := range body { - fmt.Fprintf(out, "\t\tindex %v namelen %v name: %s\n", + textui.Fprintf(out, "\t\tindex %v namelen %v name: %s\n", ref.Index, ref.NameLen, ref.Name) } //case btrfsitem.INODE_EXTREF_KEY: // // TODO case btrfsitem.DirEntry: - fmt.Fprintf(out, "\t\tlocation %v type %v\n", + textui.Fprintf(out, "\t\tlocation %v type %v\n", fmtKey(body.Location), body.Type) - fmt.Fprintf(out, "\t\ttransid %v data_len %v name_len %v\n", + textui.Fprintf(out, "\t\ttransid %v data_len %v name_len %v\n", body.TransID, body.DataLen, body.NameLen) - fmt.Fprintf(out, "\t\tname: %s\n", body.Name) + textui.Fprintf(out, "\t\tname: %s\n", body.Name) if len(body.Data) > 0 { - fmt.Fprintf(out, "\t\tdata %v\n", body.Data) + textui.Fprintf(out, "\t\tdata %v\n", body.Data) } //case btrfsitem.DIR_LOG_INDEX_KEY, btrfsitem.DIR_LOG_ITEM_KEY: // // TODO case btrfsitem.Root: - fmt.Fprintf(out, "\t\tgeneration %v root_dirid %v bytenr %d byte_limit %v bytes_used %v\n", + textui.Fprintf(out, "\t\tgeneration %v root_dirid %v bytenr %d byte_limit %v bytes_used %v\n", body.Generation, body.RootDirID, body.ByteNr, body.ByteLimit, body.BytesUsed) - fmt.Fprintf(out, "\t\tlast_snapshot %v flags %v refs %v\n", + textui.Fprintf(out, "\t\tlast_snapshot %v flags %v refs %v\n", body.LastSnapshot, body.Flags, body.Refs) - fmt.Fprintf(out, "\t\tdrop_progress %v drop_level %v\n", + textui.Fprintf(out, "\t\tdrop_progress %v drop_level %v\n", fmtKey(body.DropProgress), body.DropLevel) - fmt.Fprintf(out, "\t\tlevel %v generation_v2 %v\n", + textui.Fprintf(out, "\t\tlevel %v generation_v2 %v\n", body.Level, body.GenerationV2) if body.Generation == body.GenerationV2 { - fmt.Fprintf(out, "\t\tuuid %v\n", body.UUID) - fmt.Fprintf(out, "\t\tparent_uuid %v\n", body.ParentUUID) - fmt.Fprintf(out, "\t\treceived_uuid %v\n", body.ReceivedUUID) - fmt.Fprintf(out, "\t\tctransid %v otransid %v stransid %v rtransid %v\n", + textui.Fprintf(out, "\t\tuuid %v\n", body.UUID) + textui.Fprintf(out, "\t\tparent_uuid %v\n", body.ParentUUID) + textui.Fprintf(out, "\t\treceived_uuid %v\n", body.ReceivedUUID) + textui.Fprintf(out, "\t\tctransid %v otransid %v stransid %v rtransid %v\n", body.CTransID, body.OTransID, body.STransID, body.RTransID) - fmt.Fprintf(out, "\t\tctime %v\n", fmtTime(body.CTime)) - fmt.Fprintf(out, "\t\totime %v\n", fmtTime(body.OTime)) - fmt.Fprintf(out, "\t\tstime %v\n", fmtTime(body.STime)) - fmt.Fprintf(out, "\t\trtime %v\n", fmtTime(body.RTime)) + textui.Fprintf(out, "\t\tctime %v\n", fmtTime(body.CTime)) + textui.Fprintf(out, "\t\totime %v\n", fmtTime(body.OTime)) + textui.Fprintf(out, "\t\tstime %v\n", fmtTime(body.STime)) + textui.Fprintf(out, "\t\trtime %v\n", fmtTime(body.RTime)) } case btrfsitem.RootRef: var tag string @@ -185,20 +186,20 @@ func printTree(ctx context.Context, out io.Writer, fs *btrfs.FS, treeID btrfspri default: tag = fmt.Sprintf("(error: unhandled RootRef item type: %v)", item.Key.ItemType) } - fmt.Fprintf(out, "\t\troot %v key dirid %v sequence %v name %s\n", + textui.Fprintf(out, "\t\troot %v key dirid %v sequence %v name %s\n", tag, body.DirID, body.Sequence, body.Name) case btrfsitem.Extent: - fmt.Fprintf(out, "\t\trefs %v gen %v flags %v\n", + textui.Fprintf(out, "\t\trefs %v gen %v flags %v\n", body.Head.Refs, body.Head.Generation, body.Head.Flags) if body.Head.Flags.Has(btrfsitem.EXTENT_FLAG_TREE_BLOCK) { - fmt.Fprintf(out, "\t\ttree block %v level %v\n", + textui.Fprintf(out, "\t\ttree block %v level %v\n", fmtKey(body.Info.Key), body.Info.Level) } printExtentInlineRefs(out, body.Refs) case btrfsitem.Metadata: - fmt.Fprintf(out, "\t\trefs %v gen %v flags %v\n", + textui.Fprintf(out, "\t\trefs %v gen %v flags %v\n", body.Head.Refs, body.Head.Generation, body.Head.Flags) - fmt.Fprintf(out, "\t\ttree block skinny level %v\n", item.Key.Offset) + textui.Fprintf(out, "\t\ttree block skinny level %v\n", item.Key.Offset) printExtentInlineRefs(out, body.Refs) //case btrfsitem.EXTENT_DATA_REF_KEY: // // TODO @@ -206,72 +207,72 @@ func printTree(ctx context.Context, out io.Writer, fs *btrfs.FS, treeID btrfspri // // TODO case btrfsitem.ExtentCSum: start := btrfsvol.LogicalAddr(item.Key.Offset) - fmt.Fprintf(out, "\t\trange start %d end %d length %d", + textui.Fprintf(out, "\t\trange start %d end %d length %d", start, start.Add(body.Size()), body.Size()) sumsPerLine := slices.Max(1, len(btrfssum.CSum{})/body.ChecksumSize/2) i := 0 _ = body.Walk(ctx, func(pos btrfsvol.LogicalAddr, sum btrfssum.ShortSum) error { if i%sumsPerLine == 0 { - fmt.Fprintf(out, "\n\t\t") + textui.Fprintf(out, "\n\t\t") } else { - fmt.Fprintf(out, " ") + textui.Fprintf(out, " ") } - fmt.Fprintf(out, "[%d] 0x%x", pos, sum) + textui.Fprintf(out, "[%d] 0x%x", pos, sum) i++ return nil }) - fmt.Fprintf(out, "\n") + textui.Fprintf(out, "\n") case btrfsitem.FileExtent: - fmt.Fprintf(out, "\t\tgeneration %v type %v\n", + textui.Fprintf(out, "\t\tgeneration %v type %v\n", body.Generation, body.Type) switch body.Type { case btrfsitem.FILE_EXTENT_INLINE: - fmt.Fprintf(out, "\t\tinline extent data size %v ram_bytes %v compression %v\n", + textui.Fprintf(out, "\t\tinline extent data size %v ram_bytes %v compression %v\n", len(body.BodyInline), body.RAMBytes, body.Compression) case btrfsitem.FILE_EXTENT_PREALLOC: - fmt.Fprintf(out, "\t\tprealloc data disk byte %v nr %v\n", + textui.Fprintf(out, "\t\tprealloc data disk byte %v nr %v\n", body.BodyExtent.DiskByteNr, body.BodyExtent.DiskNumBytes) - fmt.Fprintf(out, "\t\tprealloc data offset %v nr %v\n", + textui.Fprintf(out, "\t\tprealloc data offset %v nr %v\n", body.BodyExtent.Offset, body.BodyExtent.NumBytes) case btrfsitem.FILE_EXTENT_REG: - fmt.Fprintf(out, "\t\textent data disk byte %d nr %d\n", + textui.Fprintf(out, "\t\textent data disk byte %d nr %d\n", body.BodyExtent.DiskByteNr, body.BodyExtent.DiskNumBytes) - fmt.Fprintf(out, "\t\textent data offset %d nr %d ram %v\n", + textui.Fprintf(out, "\t\textent data offset %d nr %d ram %v\n", body.BodyExtent.Offset, body.BodyExtent.NumBytes, body.RAMBytes) - fmt.Fprintf(out, "\t\textent compression %v\n", + textui.Fprintf(out, "\t\textent compression %v\n", body.Compression) default: - fmt.Fprintf(out, "\t\t(error) unknown file extent type %v", body.Type) + textui.Fprintf(out, "\t\t(error) unknown file extent type %v", body.Type) } case btrfsitem.BlockGroup: - fmt.Fprintf(out, "\t\tblock group used %v chunk_objectid %v flags %v\n", + textui.Fprintf(out, "\t\tblock group used %v chunk_objectid %v flags %v\n", body.Used, body.ChunkObjectID, body.Flags) case btrfsitem.FreeSpaceInfo: - fmt.Fprintf(out, "\t\tfree space info extent count %v flags %v\n", + textui.Fprintf(out, "\t\tfree space info extent count %v flags %v\n", body.ExtentCount, body.Flags) case btrfsitem.FreeSpaceBitmap: - fmt.Fprintf(out, "\t\tfree space bitmap\n") + textui.Fprintf(out, "\t\tfree space bitmap\n") case btrfsitem.Chunk: - fmt.Fprintf(out, "\t\tlength %d owner %d stripe_len %v type %v\n", + textui.Fprintf(out, "\t\tlength %d owner %d stripe_len %v type %v\n", body.Head.Size, body.Head.Owner, body.Head.StripeLen, body.Head.Type) - fmt.Fprintf(out, "\t\tio_align %v io_width %v sector_size %v\n", + textui.Fprintf(out, "\t\tio_align %v io_width %v sector_size %v\n", body.Head.IOOptimalAlign, body.Head.IOOptimalWidth, body.Head.IOMinSize) - fmt.Fprintf(out, "\t\tnum_stripes %v sub_stripes %v\n", + textui.Fprintf(out, "\t\tnum_stripes %v sub_stripes %v\n", body.Head.NumStripes, body.Head.SubStripes) for i, stripe := range body.Stripes { - fmt.Fprintf(out, "\t\t\tstripe %v devid %d offset %d\n", + textui.Fprintf(out, "\t\t\tstripe %v devid %d offset %d\n", i, stripe.DeviceID, stripe.Offset) - fmt.Fprintf(out, "\t\t\tdev_uuid %v\n", + textui.Fprintf(out, "\t\t\tdev_uuid %v\n", stripe.DeviceUUID) } case btrfsitem.Dev: - fmt.Fprintf(out, ""+ + textui.Fprintf(out, ""+ "\t\tdevid %d total_bytes %v bytes_used %v\n"+ "\t\tio_align %v io_width %v sector_size %v type %v\n"+ "\t\tgeneration %v start_offset %v dev_group %v\n"+ @@ -285,7 +286,7 @@ func printTree(ctx context.Context, out io.Writer, fs *btrfs.FS, treeID btrfspri body.DevUUID, body.FSUUID) case btrfsitem.DevExtent: - fmt.Fprintf(out, ""+ + textui.Fprintf(out, ""+ "\t\tdev extent chunk_tree %v\n"+ "\t\tchunk_objectid %v chunk_offset %d length %d\n"+ "\t\tchunk_tree_uuid %v\n", @@ -298,49 +299,49 @@ func printTree(ctx context.Context, out io.Writer, fs *btrfs.FS, treeID btrfspri //case btrfsitem.QGROUP_LIMIT_KEY: // // TODO case btrfsitem.UUIDMap: - fmt.Fprintf(out, "\t\tsubvol_id %d\n", body.ObjID) + textui.Fprintf(out, "\t\tsubvol_id %d\n", body.ObjID) //case btrfsitem.STRING_ITEM_KEY: // // TODO case btrfsitem.DevStats: - fmt.Fprintf(out, "\t\tpersistent item objectid %v offset %v\n", + textui.Fprintf(out, "\t\tpersistent item objectid %v offset %v\n", item.Key.ObjectID.Format(item.Key.ItemType), item.Key.Offset) switch item.Key.ObjectID { case btrfsprim.DEV_STATS_OBJECTID: - fmt.Fprintf(out, "\t\tdevice stats\n") - fmt.Fprintf(out, "\t\twrite_errs %v read_errs %v flush_errs %v corruption_errs %v generation %v\n", + textui.Fprintf(out, "\t\tdevice stats\n") + textui.Fprintf(out, "\t\twrite_errs %v read_errs %v flush_errs %v corruption_errs %v generation %v\n", body.Values[btrfsitem.DEV_STAT_WRITE_ERRS], body.Values[btrfsitem.DEV_STAT_READ_ERRS], body.Values[btrfsitem.DEV_STAT_FLUSH_ERRS], body.Values[btrfsitem.DEV_STAT_CORRUPTION_ERRS], body.Values[btrfsitem.DEV_STAT_GENERATION_ERRS]) default: - fmt.Fprintf(out, "\t\tunknown persistent item objectid %v\n", item.Key.ObjectID) + textui.Fprintf(out, "\t\tunknown persistent item objectid %v\n", item.Key.ObjectID) } //case btrfsitem.TEMPORARY_ITEM_KEY: // // TODO case btrfsitem.Empty: switch item.Key.ItemType { case btrfsitem.ORPHAN_ITEM_KEY: // 48 - fmt.Fprintf(out, "\t\torphan item\n") + textui.Fprintf(out, "\t\torphan item\n") case btrfsitem.TREE_BLOCK_REF_KEY: // 176 - fmt.Fprintf(out, "\t\ttree block backref\n") + textui.Fprintf(out, "\t\ttree block backref\n") case btrfsitem.SHARED_BLOCK_REF_KEY: // 182 - fmt.Fprintf(out, "\t\tshared block backref\n") + textui.Fprintf(out, "\t\tshared block backref\n") case btrfsitem.FREE_SPACE_EXTENT_KEY: // 199 - fmt.Fprintf(out, "\t\tfree space extent\n") + textui.Fprintf(out, "\t\tfree space extent\n") case btrfsitem.QGROUP_RELATION_KEY: // 246 // do nothing //case btrfsitem.EXTENT_REF_V0_KEY: - // fmt.Fprintf(out, "\t\textent ref v0 (deprecated)\n") + // textui.Fprintf(out, "\t\textent ref v0 (deprecated)\n") //case btrfsitem.CSUM_ITEM_KEY: - // fmt.Fprintf(out, "\t\tcsum item\n") + // textui.Fprintf(out, "\t\tcsum item\n") default: - fmt.Fprintf(out, "\t\t(error) unhandled empty item type: %v\n", item.Key.ItemType) + textui.Fprintf(out, "\t\t(error) unhandled empty item type: %v\n", item.Key.ItemType) } case btrfsitem.Error: - fmt.Fprintf(out, "\t\t(error) error item: %v\n", body.Err) + textui.Fprintf(out, "\t\t(error) error item: %v\n", body.Err) default: - fmt.Fprintf(out, "\t\t(error) unhandled item type: %T\n", body) + textui.Fprintf(out, "\t\t(error) unhandled item type: %T\n", body) } return nil }, @@ -361,37 +362,37 @@ func printHeaderInfo(out io.Writer, node btrfstree.Node) { var typename string if node.Head.Level > 0 { // internal node typename = "node" - fmt.Fprintf(out, "node %v level %v items %v free space %v", + textui.Fprintf(out, "node %v level %v items %v free space %v", node.Head.Addr, node.Head.Level, node.Head.NumItems, node.MaxItems()-node.Head.NumItems) } else { // leaf node typename = "leaf" - fmt.Fprintf(out, "leaf %d items %v free space %v", + textui.Fprintf(out, "leaf %d items %v free space %v", node.Head.Addr, node.Head.NumItems, node.LeafFreeSpace()) } - fmt.Fprintf(out, " generation %v owner %v\n", + textui.Fprintf(out, " generation %v owner %v\n", node.Head.Generation, node.Head.Owner) - fmt.Fprintf(out, "%v %d flags %v backref revision %v\n", + textui.Fprintf(out, "%v %d flags %v backref revision %v\n", typename, node.Head.Addr, node.Head.Flags, node.Head.BackrefRev) - fmt.Fprintf(out, "checksum stored %v\n", node.Head.Checksum.Fmt(node.ChecksumType)) + textui.Fprintf(out, "checksum stored %v\n", node.Head.Checksum.Fmt(node.ChecksumType)) if calcSum, err := node.CalculateChecksum(); err != nil { - fmt.Fprintf(out, "checksum calced %v\n", err) + textui.Fprintf(out, "checksum calced %v\n", err) } else { - fmt.Fprintf(out, "checksum calced %v\n", calcSum.Fmt(node.ChecksumType)) + textui.Fprintf(out, "checksum calced %v\n", calcSum.Fmt(node.ChecksumType)) } - fmt.Fprintf(out, "fs uuid %v\n", node.Head.MetadataUUID) - fmt.Fprintf(out, "chunk uuid %v\n", node.Head.ChunkTreeUUID) + textui.Fprintf(out, "fs uuid %v\n", node.Head.MetadataUUID) + textui.Fprintf(out, "chunk uuid %v\n", node.Head.ChunkTreeUUID) } // printExtentInlineRefs mimics part of btrfs-progs kernel-shared/print-tree.c:print_extent_item() @@ -401,22 +402,22 @@ func printExtentInlineRefs(out io.Writer, refs []btrfsitem.ExtentInlineRef) { case nil: switch ref.Type { case btrfsitem.TREE_BLOCK_REF_KEY: - fmt.Fprintf(out, "\t\ttree block backref root %v\n", + textui.Fprintf(out, "\t\ttree block backref root %v\n", btrfsprim.ObjID(ref.Offset)) case btrfsitem.SHARED_BLOCK_REF_KEY: - fmt.Fprintf(out, "\t\tshared block backref parent %v\n", + textui.Fprintf(out, "\t\tshared block backref parent %v\n", ref.Offset) default: - fmt.Fprintf(out, "\t\t(error) unexpected empty sub-item type: %v\n", ref.Type) + textui.Fprintf(out, "\t\t(error) unexpected empty sub-item type: %v\n", ref.Type) } case btrfsitem.ExtentDataRef: - fmt.Fprintf(out, "\t\textent data backref root %v objectid %v offset %v count %v\n", + textui.Fprintf(out, "\t\textent data backref root %v objectid %v offset %v count %v\n", subitem.Root, subitem.ObjectID, subitem.Offset, subitem.Count) case btrfsitem.SharedDataRef: - fmt.Fprintf(out, "\t\tshared data backref parent %v count %v\n", + textui.Fprintf(out, "\t\tshared data backref parent %v count %v\n", ref.Offset, subitem.Count) default: - fmt.Fprintf(out, "\t\t(error) unexpected sub-item type: %T\n", subitem) + textui.Fprintf(out, "\t\t(error) unexpected sub-item type: %T\n", subitem) } } } @@ -424,19 +425,19 @@ func printExtentInlineRefs(out io.Writer, refs []btrfsitem.ExtentInlineRef) { // mimics print-tree.c:btrfs_print_key() func fmtKey(key btrfsprim.Key) string { var out strings.Builder - fmt.Fprintf(&out, "key (%v %v", key.ObjectID.Format(key.ItemType), key.ItemType) + textui.Fprintf(&out, "key (%v %v", key.ObjectID.Format(key.ItemType), key.ItemType) switch key.ItemType { case btrfsitem.QGROUP_RELATION_KEY: //TODO, btrfsitem.QGROUP_INFO_KEY, btrfsitem.QGROUP_LIMIT_KEY: panic("not implemented") case btrfsitem.UUID_SUBVOL_KEY, btrfsitem.UUID_RECEIVED_SUBVOL_KEY: - fmt.Fprintf(&out, " %#08x)", key.Offset) + textui.Fprintf(&out, " %#08x)", key.Offset) case btrfsitem.ROOT_ITEM_KEY: - fmt.Fprintf(&out, " %v)", btrfsprim.ObjID(key.Offset)) + textui.Fprintf(&out, " %v)", btrfsprim.ObjID(key.Offset)) default: if key.Offset == math.MaxUint64 { - fmt.Fprintf(&out, " -1)") + textui.Fprintf(&out, " -1)") } else { - fmt.Fprintf(&out, " %v)", key.Offset) + textui.Fprintf(&out, " %v)", key.Offset) } } return out.String() -- cgit v1.2.3-54-g00ecf From 9fe871dcf2a15afa66df52e927e9c402b24231fe Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sun, 25 Dec 2022 01:05:31 -0700 Subject: tree-wide: Audit for fmt.Sprint usage, in favor of textui --- cmd/btrfs-rec/inspect_lsfiles.go | 22 +++++++++++----------- lib/btrfsprogs/btrfsinspect/print_tree.go | 5 ++--- .../btrfsinspect/rebuildmappings/fuzzymatchsums.go | 7 ++++--- .../rebuildmappings/rebuildmappings.go | 6 +++--- .../rebuildnodes/btrees/rebuilt_btrees.go | 4 ++-- 5 files changed, 22 insertions(+), 22 deletions(-) (limited to 'cmd/btrfs-rec') diff --git a/cmd/btrfs-rec/inspect_lsfiles.go b/cmd/btrfs-rec/inspect_lsfiles.go index 801da5d..502d91d 100644 --- a/cmd/btrfs-rec/inspect_lsfiles.go +++ b/cmd/btrfs-rec/inspect_lsfiles.go @@ -65,7 +65,7 @@ func printText(out io.Writer, prefix string, isLast bool, name, text string) { if isLast { first, rest = tL, tS } - for i, line := range strings.Split(fmt.Sprintf("%q %s", name, text), "\n") { + for i, line := range strings.Split(textui.Sprintf("%q %s", name, text), "\n") { _, _ = io.WriteString(out, prefix) if i == 0 { _, _ = io.WriteString(out, first) @@ -80,13 +80,13 @@ func printText(out io.Writer, prefix string, isLast bool, name, text string) { func printSubvol(out io.Writer, prefix string, isLast bool, name string, subvol *btrfs.Subvolume) { rootInode, err := subvol.GetRootInode() if err != nil { - printText(out, prefix, isLast, name+"/", fmt.Sprintf("subvol_id=%v err=%v", + printText(out, prefix, isLast, name+"/", textui.Sprintf("subvol_id=%v err=%v", subvol.TreeID, fmtErr(err))) return } dir, err := subvol.LoadDir(rootInode) if err != nil { - printText(out, prefix, isLast, name+"/", fmt.Sprintf("subvol_id=%v err=%v", + printText(out, prefix, isLast, name+"/", textui.Sprintf("subvol_id=%v err=%v", subvol.TreeID, fmtErr(err))) return } @@ -94,7 +94,7 @@ func printSubvol(out io.Writer, prefix string, isLast bool, name string, subvol printDir(out, prefix, isLast, name, dir) return } - printText(out, prefix, isLast, name+"/", fmt.Sprintf("subvol_id=%v", subvol.TreeID)) + printText(out, prefix, isLast, name+"/", textui.Sprintf("subvol_id=%v", subvol.TreeID)) if isLast { prefix += tS } else { @@ -118,7 +118,7 @@ func fmtInode(inode btrfs.BareInode) string { } else { mode = inode.InodeItem.Mode } - ret := fmt.Sprintf("ino=%v mode=%v", inode.Inode, mode) + ret := textui.Sprintf("ino=%v mode=%v", inode.Inode, mode) if len(inode.Errs) > 0 { ret += " err=" + fmtErr(inode.Errs) } @@ -153,7 +153,7 @@ func printDirEntry(out io.Writer, prefix string, isLast bool, subvol *btrfs.Subv case btrfsitem.INODE_ITEM_KEY: dir, err := subvol.LoadDir(entry.Location.ObjectID) if err != nil { - printText(out, prefix, isLast, name, fmt.Sprintf("%v err=%v", entry.Type, fmtErr(err))) + printText(out, prefix, isLast, name, textui.Sprintf("%v err=%v", entry.Type, fmtErr(err))) return } printDir(out, prefix, isLast, name, dir) @@ -173,7 +173,7 @@ func printDirEntry(out io.Writer, prefix string, isLast bool, subvol *btrfs.Subv } file, err := subvol.LoadFile(entry.Location.ObjectID) if err != nil { - printText(out, prefix, isLast, name, fmt.Sprintf("%v err=%v", entry.Type, fmtErr(err))) + printText(out, prefix, isLast, name, textui.Sprintf("%v err=%v", entry.Type, fmtErr(err))) return } printSymlink(out, prefix, isLast, name, file) @@ -184,7 +184,7 @@ func printDirEntry(out io.Writer, prefix string, isLast bool, subvol *btrfs.Subv } file, err := subvol.LoadFile(entry.Location.ObjectID) if err != nil { - printText(out, prefix, isLast, name, fmt.Sprintf("%v err=%v", entry.Type, fmtErr(err))) + printText(out, prefix, isLast, name, textui.Sprintf("%v err=%v", entry.Type, fmtErr(err))) return } printFile(out, prefix, isLast, name, file) @@ -195,7 +195,7 @@ func printDirEntry(out io.Writer, prefix string, isLast bool, subvol *btrfs.Subv } file, err := subvol.LoadFile(entry.Location.ObjectID) if err != nil { - printText(out, prefix, isLast, name, fmt.Sprintf("%v err=%v", entry.Type, fmtErr(err))) + printText(out, prefix, isLast, name, textui.Sprintf("%v err=%v", entry.Type, fmtErr(err))) return } printSocket(out, prefix, isLast, name, file) @@ -206,7 +206,7 @@ func printDirEntry(out io.Writer, prefix string, isLast bool, subvol *btrfs.Subv } file, err := subvol.LoadFile(entry.Location.ObjectID) if err != nil { - printText(out, prefix, isLast, name, fmt.Sprintf("%v err=%v", entry.Type, fmtErr(err))) + printText(out, prefix, isLast, name, textui.Sprintf("%v err=%v", entry.Type, fmtErr(err))) return } printPipe(out, prefix, isLast, name, file) @@ -225,7 +225,7 @@ func printSymlink(out io.Writer, prefix string, isLast bool, name string, file * file.Errs = append(file.Errs, err) } } - printText(out, prefix, isLast, name, fmt.Sprintf( + printText(out, prefix, isLast, name, textui.Sprintf( "-> %q : %s", tgt, fmtInode(file.BareInode))) diff --git a/lib/btrfsprogs/btrfsinspect/print_tree.go b/lib/btrfsprogs/btrfsinspect/print_tree.go index b882c6b..2ede80f 100644 --- a/lib/btrfsprogs/btrfsinspect/print_tree.go +++ b/lib/btrfsprogs/btrfsinspect/print_tree.go @@ -6,7 +6,6 @@ package btrfsinspect import ( "context" - "fmt" "io" "math" "strings" @@ -184,7 +183,7 @@ func printTree(ctx context.Context, out io.Writer, fs *btrfs.FS, treeID btrfspri case btrfsitem.ROOT_BACKREF_KEY: tag = "backref" default: - tag = fmt.Sprintf("(error: unhandled RootRef item type: %v)", item.Key.ItemType) + tag = textui.Sprintf("(error: unhandled RootRef item type: %v)", item.Key.ItemType) } textui.Fprintf(out, "\t\troot %v key dirid %v sequence %v name %s\n", tag, body.DirID, body.Sequence, body.Name) @@ -444,6 +443,6 @@ func fmtKey(key btrfsprim.Key) string { } func fmtTime(t btrfsprim.Time) string { - return fmt.Sprintf("%v.%v (%v)", + return textui.Sprintf("%v.%v (%v)", t.Sec, t.NSec, t.ToStd().Format("2006-01-02 15:04:05")) } diff --git a/lib/btrfsprogs/btrfsinspect/rebuildmappings/fuzzymatchsums.go b/lib/btrfsprogs/btrfsinspect/rebuildmappings/fuzzymatchsums.go index c6e23bc..0b5fc6f 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildmappings/fuzzymatchsums.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildmappings/fuzzymatchsums.go @@ -6,16 +6,17 @@ package rebuildmappings import ( "context" - "fmt" "sort" "github.com/datawire/dlib/dlog" + "golang.org/x/text/number" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfssum" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" "git.lukeshu.com/btrfs-progs-ng/lib/containers" "git.lukeshu.com/btrfs-progs-ng/lib/maps" + "git.lukeshu.com/btrfs-progs-ng/lib/textui" ) const minFuzzyPct = 0.5 @@ -95,12 +96,12 @@ func fuzzyMatchBlockGroupSums(ctx context.Context, matchesStr = "" case 1: // not sure how this can happen, but whatev pct := float64(d-best.Dat[0].N) / float64(d) - matchesStr = fmt.Sprintf("%v%%", int(100*pct)) + matchesStr = textui.Sprintf("%v", number.Percent(pct)) apply = pct > minFuzzyPct case 2: pct := float64(d-best.Dat[0].N) / float64(d) pct2 := float64(d-best.Dat[1].N) / float64(d) - matchesStr = fmt.Sprintf("best=%v%% secondbest=%v%%", int(100*pct), int(100*pct2)) + matchesStr = textui.Sprintf("best=%v secondbest=%v", number.Percent(pct), number.Percent(pct2)) apply = pct > minFuzzyPct && pct2 < minFuzzyPct } lvl := dlog.LogLevelError diff --git a/lib/btrfsprogs/btrfsinspect/rebuildmappings/rebuildmappings.go b/lib/btrfsprogs/btrfsinspect/rebuildmappings/rebuildmappings.go index 5bf1347..6eb119c 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildmappings/rebuildmappings.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildmappings/rebuildmappings.go @@ -168,20 +168,20 @@ func RebuildMappings(ctx context.Context, fs *btrfs.FS, scanResults btrfsinspect unmappedPhysical += region.End.Sub(region.Beg) } } - dlog.Info(ctx, fmt.Sprintf("... %d KiB of unmapped physical space (across %d regions)", textui.Humanized(int(unmappedPhysical/1024)), textui.Humanized(numUnmappedPhysical))) + dlog.Infof(ctx, "... %d KiB of unmapped physical space (across %d regions)", textui.Humanized(int(unmappedPhysical/1024)), textui.Humanized(numUnmappedPhysical)) unmappedLogicalRegions := ListUnmappedLogicalRegions(fs, logicalSums) var unmappedLogical btrfsvol.AddrDelta for _, region := range unmappedLogicalRegions { unmappedLogical += region.Size() } - dlog.Info(ctx, fmt.Sprintf("... %d KiB of unmapped summed logical space (across %d regions)", textui.Humanized(int(unmappedLogical/1024)), textui.Humanized(len(unmappedLogicalRegions)))) + dlog.Infof(ctx, "... %d KiB of unmapped summed logical space (across %d regions)", textui.Humanized(int(unmappedLogical/1024)), textui.Humanized(len(unmappedLogicalRegions))) var unmappedBlockGroups btrfsvol.AddrDelta for _, bg := range bgs { unmappedBlockGroups += bg.Size } - dlog.Info(ctx, fmt.Sprintf("... %d KiB of unmapped block groups (across %d groups)", textui.Humanized(int(unmappedBlockGroups/1024)), textui.Humanized(len(bgs)))) + dlog.Infof(ctx, "... %d KiB of unmapped block groups (across %d groups)", textui.Humanized(int(unmappedBlockGroups/1024)), textui.Humanized(len(bgs))) dlog.Info(ctx, "detailed report:") for _, devID := range maps.SortedKeys(unmappedPhysicalRegions) { diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/btrees/rebuilt_btrees.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/btrees/rebuilt_btrees.go index ab4b55f..4f0f5d4 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/btrees/rebuilt_btrees.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/btrees/rebuilt_btrees.go @@ -178,7 +178,7 @@ type rootStats struct { } func (s rootStats) String() string { - return fmt.Sprintf("tree %v: adding root node@%v: %v%% (%v/%v) (added %v items, replaced %v items)", + return textui.Sprintf("tree %v: adding root node@%v: %v%% (%v/%v) (added %v items, replaced %v items)", s.TreeID, s.RootNode, int(100*float64(s.DoneLeafs)/float64(s.TotalLeafs)), s.DoneLeafs, s.TotalLeafs, @@ -309,7 +309,7 @@ type indexStats struct { } func (s indexStats) String() string { - return fmt.Sprintf("tree %v: indexing leaf nodes: %v%% (%v/%v)", + return textui.Sprintf("tree %v: indexing leaf nodes: %v%% (%v/%v)", s.TreeID, int(100*float64(s.DoneNodes)/float64(s.TotalNodes)), s.DoneNodes, s.TotalNodes) -- cgit v1.2.3-54-g00ecf From 31341857abc73334852d3b888f4fb558d8ed3c61 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sun, 25 Dec 2022 14:03:02 -0700 Subject: Factor logger code from main.go in to textui --- cmd/btrfs-rec/main.go | 20 ++------------------ lib/textui/log.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 lib/textui/log.go (limited to 'cmd/btrfs-rec') diff --git a/cmd/btrfs-rec/main.go b/cmd/btrfs-rec/main.go index a221cb7..d14675d 100644 --- a/cmd/btrfs-rec/main.go +++ b/cmd/btrfs-rec/main.go @@ -14,7 +14,6 @@ import ( "github.com/datawire/ocibuild/pkg/cliutil" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/pflag" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" @@ -22,19 +21,6 @@ import ( "git.lukeshu.com/btrfs-progs-ng/lib/textui" ) -type logLevelFlag struct { - logrus.Level -} - -func (lvl *logLevelFlag) Type() string { return "loglevel" } -func (lvl *logLevelFlag) Set(str string) error { - var err error - lvl.Level, err = logrus.ParseLevel(str) - return err -} - -var _ pflag.Value = (*logLevelFlag)(nil) - type subcommand struct { cobra.Command RunE func(*btrfs.FS, *cobra.Command, []string) error @@ -43,7 +29,7 @@ type subcommand struct { var inspectors, repairers []subcommand func main() { - logLevelFlag := logLevelFlag{ + logLevelFlag := textui.LogLevelFlag{ Level: logrus.InfoLevel, } var pvsFlag []string @@ -115,9 +101,7 @@ func main() { runE := child.RunE cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - logger := logrus.New() - logger.SetLevel(logLevelFlag.Level) - ctx = dlog.WithLogger(ctx, dlog.WrapLogrus(logger)) + ctx = dlog.WithLogger(ctx, textui.NewLogger(logLevelFlag.Level)) grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{ EnableSignalHandling: true, diff --git a/lib/textui/log.go b/lib/textui/log.go new file mode 100644 index 0000000..cee51db --- /dev/null +++ b/lib/textui/log.go @@ -0,0 +1,33 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package textui + +import ( + "github.com/datawire/dlib/dlog" + "github.com/sirupsen/logrus" + "github.com/spf13/pflag" +) + +type LogLevelFlag struct { + logrus.Level +} + +var _ pflag.Value = (*LogLevelFlag)(nil) + +// Type implements pflag.Value. +func (lvl *LogLevelFlag) Type() string { return "loglevel" } + +// Type implements pflag.Value. +func (lvl *LogLevelFlag) Set(str string) error { + var err error + lvl.Level, err = logrus.ParseLevel(str) + return err +} + +func NewLogger(lvl logrus.Level) dlog.Logger { + logger := logrus.New() + logger.SetLevel(lvl) + return dlog.WrapLogrus(logger) +} -- cgit v1.2.3-54-g00ecf From 29ad8ec24e07888953c14fb5435d2e8e2023931b Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sun, 25 Dec 2022 22:47:51 -0700 Subject: textui: Implement a custom dlog backend that uses x/text/message --- cmd/btrfs-rec/main.go | 5 +- lib/textui/log.go | 282 +++++++++++++++++++++++++++++++++++++++++++++++-- lib/textui/log_test.go | 64 +++++++++++ 3 files changed, 338 insertions(+), 13 deletions(-) create mode 100644 lib/textui/log_test.go (limited to 'cmd/btrfs-rec') diff --git a/cmd/btrfs-rec/main.go b/cmd/btrfs-rec/main.go index d14675d..1135be2 100644 --- a/cmd/btrfs-rec/main.go +++ b/cmd/btrfs-rec/main.go @@ -12,7 +12,6 @@ import ( "github.com/datawire/dlib/dgroup" "github.com/datawire/dlib/dlog" "github.com/datawire/ocibuild/pkg/cliutil" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" @@ -30,7 +29,7 @@ var inspectors, repairers []subcommand func main() { logLevelFlag := textui.LogLevelFlag{ - Level: logrus.InfoLevel, + Level: dlog.LogLevelInfo, } var pvsFlag []string var mappingsFlag string @@ -101,7 +100,7 @@ func main() { runE := child.RunE cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - ctx = dlog.WithLogger(ctx, textui.NewLogger(logLevelFlag.Level)) + ctx = dlog.WithLogger(ctx, textui.NewLogger(os.Stderr, logLevelFlag.Level)) grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{ EnableSignalHandling: true, diff --git a/lib/textui/log.go b/lib/textui/log.go index cee51db..ca8c201 100644 --- a/lib/textui/log.go +++ b/lib/textui/log.go @@ -1,17 +1,33 @@ +// Copyright (C) 2019-2022 Ambassador Labs // Copyright (C) 2022 Luke Shumaker // -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: Apache-2.0 +// +// Contains code based on: +// https://github.com/datawire/dlib/blob/b09ab2e017e16d261f05fff5b3b860d645e774d4/dlog/logger_logrus.go +// https://github.com/datawire/dlib/blob/b09ab2e017e16d261f05fff5b3b860d645e774d4/dlog/logger_testing.go +// https://github.com/telepresenceio/telepresence/blob/ece94a40b00a90722af36b12e40f91cbecc0550c/pkg/log/formatter.go package textui import ( + "bytes" + "fmt" + "io" + "log" + "path/filepath" + "runtime" + "sort" + "strings" + "sync" + "time" + "github.com/datawire/dlib/dlog" - "github.com/sirupsen/logrus" "github.com/spf13/pflag" ) type LogLevelFlag struct { - logrus.Level + Level dlog.LogLevel } var _ pflag.Value = (*LogLevelFlag)(nil) @@ -21,13 +37,259 @@ func (lvl *LogLevelFlag) Type() string { return "loglevel" } // Type implements pflag.Value. func (lvl *LogLevelFlag) Set(str string) error { - var err error - lvl.Level, err = logrus.ParseLevel(str) - return err + switch strings.ToLower(str) { + case "error": + lvl.Level = dlog.LogLevelError + case "warn", "warning": + lvl.Level = dlog.LogLevelWarn + case "info": + lvl.Level = dlog.LogLevelInfo + case "debug": + lvl.Level = dlog.LogLevelDebug + case "trace": + lvl.Level = dlog.LogLevelTrace + default: + return fmt.Errorf("invalid log level: %q", str) + } + return nil +} + +// Type implements pflag.Value. +func (lvl *LogLevelFlag) String() string { + switch lvl.Level { + case dlog.LogLevelError: + return "error" + case dlog.LogLevelWarn: + return "warn" + case dlog.LogLevelInfo: + return "info" + case dlog.LogLevelDebug: + return "debug" + case dlog.LogLevelTrace: + return "trace" + default: + panic(fmt.Errorf("invalid log level: %#v", lvl.Level)) + } +} + +type logger struct { + parent *logger + out io.Writer + lvl dlog.LogLevel + + // only valid if parent is non-nil + fieldKey string + fieldVal any +} + +var _ dlog.OptimizedLogger = (*logger)(nil) + +func NewLogger(out io.Writer, lvl dlog.LogLevel) dlog.Logger { + return &logger{ + out: out, + lvl: lvl, + } +} + +// Helper implements dlog.Logger. +func (l *logger) Helper() {} + +// WithField implements dlog.Logger. +func (l *logger) WithField(key string, value any) dlog.Logger { + return &logger{ + parent: l, + out: l.out, + lvl: l.lvl, + + fieldKey: key, + fieldVal: value, + } +} + +type logWriter struct { + log *logger + lvl dlog.LogLevel +} + +// Write implements io.Writer. +func (lw logWriter) Write(data []byte) (int, error) { + lw.log.log(lw.lvl, func(w io.Writer) { + _, _ = w.Write(data) + }) + return len(data), nil +} + +// StdLogger implements dlog.Logger. +func (l *logger) StdLogger(lvl dlog.LogLevel) *log.Logger { + return log.New(logWriter{log: l, lvl: lvl}, "", 0) +} + +// Log implements dlog.Logger. +func (l *logger) Log(lvl dlog.LogLevel, msg string) { + panic("should not happen: optimized log methods should be used instead") +} + +// UnformattedLog implements dlog.OptimizedLogger. +func (l *logger) UnformattedLog(lvl dlog.LogLevel, args ...any) { + l.log(lvl, func(w io.Writer) { + printer.Fprint(w, args...) + }) +} + +// UnformattedLogln implements dlog.OptimizedLogger. +func (l *logger) UnformattedLogln(lvl dlog.LogLevel, args ...any) { + l.log(lvl, func(w io.Writer) { + printer.Fprintln(w, args...) + }) +} + +// UnformattedLogf implements dlog.OptimizedLogger. +func (l *logger) UnformattedLogf(lvl dlog.LogLevel, format string, args ...any) { + l.log(lvl, func(w io.Writer) { + printer.Fprintf(w, format, args...) + }) +} + +var ( + logBuf bytes.Buffer + logMu sync.Mutex + thisModDir string +) + +func init() { + _, file, _, _ := runtime.Caller(0) + thisModDir = filepath.Dir(filepath.Dir(filepath.Dir(file))) +} + +func (l *logger) log(lvl dlog.LogLevel, writeMsg func(io.Writer)) { + // boilerplate ///////////////////////////////////////////////////////// + if lvl > l.lvl { + return + } + // This is optimized for mostly-single-threaded usage. If I cared more + // about multi-threaded performance, I'd trade in some + // memory-use/allocations and (1) instead of using a static `logBuf`, + // I'd have a `logBufPool` `sync.Pool`, and (2) have the the final call + // to `l.out.Write()` be the only thing protected by `logMu`. + logMu.Lock() + defer logMu.Unlock() + defer logBuf.Reset() + + // time //////////////////////////////////////////////////////////////// + now := time.Now() + const timeFmt = "2006-01-02 15:04:05.0000" + logBuf.WriteString(timeFmt) + now.AppendFormat(logBuf.Bytes()[:0], timeFmt) + + // level /////////////////////////////////////////////////////////////// + switch lvl { + case dlog.LogLevelError: + logBuf.WriteString(" ERR") + case dlog.LogLevelWarn: + logBuf.WriteString(" WRN") + case dlog.LogLevelInfo: + logBuf.WriteString(" INF") + case dlog.LogLevelDebug: + logBuf.WriteString(" DBG") + case dlog.LogLevelTrace: + logBuf.WriteString(" TRC") + } + + // fields (early) ////////////////////////////////////////////////////// + fields := make(map[string]any) + var fieldKeys []string + for f := l; f.parent != nil; f = f.parent { + if _, exists := fields[f.fieldKey]; exists { + continue + } + fields[f.fieldKey] = f.fieldVal + fieldKeys = append(fieldKeys, f.fieldKey) + } + sort.Slice(fieldKeys, func(i, j int) bool { + iOrd := fieldOrd(fieldKeys[i]) + jOrd := fieldOrd(fieldKeys[j]) + if iOrd != jOrd { + return iOrd < jOrd + } + return fieldKeys[i] < fieldKeys[j] + }) + nextField := len(fieldKeys) + for i, fieldKey := range fieldKeys { + if fieldOrd(fieldKey) >= 0 { + nextField = i + break + } + printer.Fprintf(&logBuf, " %s=%v", fieldName(fieldKey), fields[fieldKey]) + } + + // message ///////////////////////////////////////////////////////////// + logBuf.WriteString(" : ") + writeMsg(&logBuf) + + // fields (late) /////////////////////////////////////////////////////// + if nextField < len(fieldKeys) { + logBuf.WriteString(" :") + } + for _, fieldKey := range fieldKeys[nextField:] { + printer.Fprintf(&logBuf, " %s=%v", fieldName(fieldKey), fields[fieldKey]) + } + + // caller ////////////////////////////////////////////////////////////// + const ( + thisModule = "git.lukeshu.com/btrfs-progs-ng" + thisPackage = "git.lukeshu.com/btrfs-progs-ng/lib/textui" + maximumCallerDepth int = 25 + minimumCallerDepth int = 3 // runtime.Callers + .log + .Log + ) + var pcs [maximumCallerDepth]uintptr + depth := runtime.Callers(minimumCallerDepth, pcs[:]) + frames := runtime.CallersFrames(pcs[:depth]) + for f, again := frames.Next(); again; f, again = frames.Next() { + if !strings.HasPrefix(f.Function, thisModule+"/") { + continue + } + if strings.HasPrefix(f.Function, thisPackage+".") { + continue + } + if nextField == len(fieldKeys) { + logBuf.WriteString(" :") + } + file := f.File[strings.LastIndex(f.File, thisModDir+"/")+len(thisModDir+"/"):] + fmt.Fprintf(&logBuf, " (from %s:%d)", file, f.Line) + break + } + + // boilerplate ///////////////////////////////////////////////////////// + logBuf.WriteByte('\n') + _, _ = l.out.Write(logBuf.Bytes()) +} + +// fieldOrd returns the sort-position for a given log-field-key. Lower return +// values should be positioned on the left when logging, and higher values +// should be positioned on the right; values <0 should be on the left of the log +// message, while values ≥0 should be on the right of the log message. +func fieldOrd(key string) int { + switch key { + case "THREAD": // dgroup + return -7 + case "dexec.pid": + return -6 + case "dexec.stream": + return -5 + case "dexec.data": + return -4 + case "dexec.err": + return -3 + default: + return 1 + } } -func NewLogger(lvl logrus.Level) dlog.Logger { - logger := logrus.New() - logger.SetLevel(lvl) - return dlog.WrapLogrus(logger) +func fieldName(key string) string { + switch key { + case "THREAD": + return "thread" + default: + return key + } } diff --git a/lib/textui/log_test.go b/lib/textui/log_test.go new file mode 100644 index 0000000..08eb38c --- /dev/null +++ b/lib/textui/log_test.go @@ -0,0 +1,64 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package textui_test + +import ( + "context" + "strings" + "testing" + + "github.com/datawire/dlib/dlog" + "github.com/stretchr/testify/assert" + + "git.lukeshu.com/btrfs-progs-ng/lib/textui" +) + +func logLineRegexp(inner string) string { + return `[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{4} ` + inner + ` \(from lib/textui/log_test\.go:[0-9]+\)\n` +} + +func TestLogFormat(t *testing.T) { + var out strings.Builder + ctx := dlog.WithLogger(context.Background(), textui.NewLogger(&out, dlog.LogLevelTrace)) + dlog.Debugf(ctx, "foo %d", 12345) + assert.Regexp(t, + `^`+logLineRegexp(`DBG : foo 12,345 :`)+`$`, + out.String()) +} + +func TestLogLevel(t *testing.T) { + var out strings.Builder + ctx := dlog.WithLogger(context.Background(), textui.NewLogger(&out, dlog.LogLevelInfo)) + dlog.Error(ctx, "Error") + dlog.Warn(ctx, "Warn") + dlog.Info(ctx, "Info") + dlog.Debug(ctx, "Debug") + dlog.Trace(ctx, "Trace") + dlog.Trace(ctx, "Trace") + dlog.Debug(ctx, "Debug") + dlog.Info(ctx, "Info") + dlog.Warn(ctx, "Warn") + dlog.Error(ctx, "Error") + assert.Regexp(t, + `^`+ + logLineRegexp(`ERR : Error :`)+ + logLineRegexp(`WRN : Warn :`)+ + logLineRegexp(`INF : Info :`)+ + logLineRegexp(`INF : Info :`)+ + logLineRegexp(`WRN : Warn :`)+ + logLineRegexp(`ERR : Error :`)+ + `$`, + out.String()) +} + +func TestLogField(t *testing.T) { + var out strings.Builder + ctx := dlog.WithLogger(context.Background(), textui.NewLogger(&out, dlog.LogLevelInfo)) + ctx = dlog.WithField(ctx, "foo", 12345) + dlog.Info(ctx, "msg") + assert.Regexp(t, + `^`+logLineRegexp(`INF : msg : foo=12,345`)+`$`, + out.String()) +} -- cgit v1.2.3-54-g00ecf From b1b37ae185fc4fa540c355f46511c309b5e62339 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sun, 25 Dec 2022 22:49:19 -0700 Subject: cmd/btrfs-rec: Set the dlog fallback logger --- cmd/btrfs-rec/main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'cmd/btrfs-rec') diff --git a/cmd/btrfs-rec/main.go b/cmd/btrfs-rec/main.go index 1135be2..3a00544 100644 --- a/cmd/btrfs-rec/main.go +++ b/cmd/btrfs-rec/main.go @@ -100,7 +100,9 @@ func main() { runE := child.RunE cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - ctx = dlog.WithLogger(ctx, textui.NewLogger(os.Stderr, logLevelFlag.Level)) + logger := textui.NewLogger(os.Stderr, logLevelFlag.Level) + ctx = dlog.WithLogger(ctx, logger) + dlog.SetFallbackLogger(logger.WithField("btrfs-progs.THIS_IS_A_BUG", true)) grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{ EnableSignalHandling: true, -- cgit v1.2.3-54-g00ecf From d5737a0e71b17a97b82ae68e49acc41a08fcc0ad Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Mon, 26 Dec 2022 22:02:13 -0700 Subject: cmd/btrfs-rec: Add a utility function for reading JSON files with progress --- cmd/btrfs-rec/inspect_lstrees.go | 12 ++--- cmd/btrfs-rec/inspect_rebuildmappings.go | 3 +- cmd/btrfs-rec/inspect_rebuildnodes.go | 3 +- cmd/btrfs-rec/inspect_scandevices.go | 14 ------ cmd/btrfs-rec/main.go | 7 +-- cmd/btrfs-rec/util.go | 83 ++++++++++++++++++++++++++++++++ lib/textui/log.go | 4 ++ 7 files changed, 97 insertions(+), 29 deletions(-) create mode 100644 cmd/btrfs-rec/util.go (limited to 'cmd/btrfs-rec') diff --git a/cmd/btrfs-rec/inspect_lstrees.go b/cmd/btrfs-rec/inspect_lstrees.go index 7f59eaa..e92c544 100644 --- a/cmd/btrfs-rec/inspect_lstrees.go +++ b/cmd/btrfs-rec/inspect_lstrees.go @@ -5,7 +5,6 @@ package main import ( - "encoding/json" "os" "strconv" "text/tabwriter" @@ -36,15 +35,14 @@ func init() { Args: cliutil.WrapPositionalArgs(cobra.NoArgs), }, RunE: func(fs *btrfs.FS, cmd *cobra.Command, _ []string) error { - var scanResults map[btrfsvol.DeviceID]btrfsinspect.ScanOneDeviceResult + ctx := cmd.Context() + var scanResults btrfsinspect.ScanDevicesResult if scandevicesFilename != "" { - scanResultsBytes, err := os.ReadFile(scandevicesFilename) + var err error + scanResults, err = readJSONFile[btrfsinspect.ScanDevicesResult](ctx, scandevicesFilename) if err != nil { return err } - if err := json.Unmarshal(scanResultsBytes, &scanResults); err != nil { - return err - } } var treeErrCnt int @@ -65,7 +63,7 @@ func init() { table.Flush() } visitedNodes := make(containers.Set[btrfsvol.LogicalAddr]) - btrfsutil.WalkAllTrees(cmd.Context(), fs, btrfsutil.WalkAllTreesHandler{ + btrfsutil.WalkAllTrees(ctx, fs, btrfsutil.WalkAllTreesHandler{ PreTree: func(name string, treeID btrfsprim.ObjID) { treeErrCnt = 0 treeItemCnt = make(map[btrfsitem.Type]int) diff --git a/cmd/btrfs-rec/inspect_rebuildmappings.go b/cmd/btrfs-rec/inspect_rebuildmappings.go index 54535ec..da7d12e 100644 --- a/cmd/btrfs-rec/inspect_rebuildmappings.go +++ b/cmd/btrfs-rec/inspect_rebuildmappings.go @@ -15,6 +15,7 @@ import ( "github.com/spf13/cobra" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsinspect" "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsinspect/rebuildmappings" ) @@ -37,7 +38,7 @@ func init() { ctx := cmd.Context() dlog.Infof(ctx, "Reading %q...", args[0]) - scanResults, err := readScanResults(args[0]) + scanResults, err := readJSONFile[btrfsinspect.ScanDevicesResult](ctx, args[0]) if err != nil { return err } diff --git a/cmd/btrfs-rec/inspect_rebuildnodes.go b/cmd/btrfs-rec/inspect_rebuildnodes.go index 5f6d9b5..0f3d60e 100644 --- a/cmd/btrfs-rec/inspect_rebuildnodes.go +++ b/cmd/btrfs-rec/inspect_rebuildnodes.go @@ -17,6 +17,7 @@ import ( "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/btrfsvol" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsinspect" "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsinspect/rebuildnodes" "git.lukeshu.com/btrfs-progs-ng/lib/containers" ) @@ -31,7 +32,7 @@ func init() { ctx := cmd.Context() dlog.Infof(ctx, "Reading %q...", args[0]) - nodeScanResults, err := readScanResults(args[0]) + nodeScanResults, err := readJSONFile[btrfsinspect.ScanDevicesResult](ctx, args[0]) if err != nil { return err } diff --git a/cmd/btrfs-rec/inspect_scandevices.go b/cmd/btrfs-rec/inspect_scandevices.go index 5c8b2b0..7235e45 100644 --- a/cmd/btrfs-rec/inspect_scandevices.go +++ b/cmd/btrfs-rec/inspect_scandevices.go @@ -58,17 +58,3 @@ func writeScanResults(w io.Writer, results btrfsinspect.ScanDevicesResult) (err CompactIfUnder: 16, }, results) } - -func readScanResults(filename string) (btrfsinspect.ScanDevicesResult, error) { - fh, err := os.Open(filename) - if err != nil { - return nil, err - } - var scanResults btrfsinspect.ScanDevicesResult - buf := bufio.NewReader(fh) - if err := lowmemjson.DecodeThenEOF(buf, &scanResults); err != nil { - return nil, err - } - _ = fh.Close() - return scanResults, nil -} diff --git a/cmd/btrfs-rec/main.go b/cmd/btrfs-rec/main.go index 3a00544..87e8696 100644 --- a/cmd/btrfs-rec/main.go +++ b/cmd/btrfs-rec/main.go @@ -6,7 +6,6 @@ package main import ( "context" - "encoding/json" "os" "github.com/datawire/dlib/dgroup" @@ -122,14 +121,10 @@ func main() { }() if mappingsFlag != "" { - bs, err := os.ReadFile(mappingsFlag) + mappingsJSON, err := readJSONFile[[]btrfsvol.Mapping](ctx, mappingsFlag) if err != nil { return err } - var mappingsJSON []btrfsvol.Mapping - if err := json.Unmarshal(bs, &mappingsJSON); err != nil { - return err - } for _, mapping := range mappingsJSON { if err := fs.LV.AddMapping(mapping); err != nil { return err diff --git a/cmd/btrfs-rec/util.go b/cmd/btrfs-rec/util.go new file mode 100644 index 0000000..adfe97e --- /dev/null +++ b/cmd/btrfs-rec/util.go @@ -0,0 +1,83 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "bufio" + "context" + "io" + "os" + "time" + + "git.lukeshu.com/go/lowmemjson" + "github.com/datawire/dlib/dlog" + + "git.lukeshu.com/btrfs-progs-ng/lib/textui" +) + +type runeScanner struct { + progress textui.Portion[int64] + progressWriter *textui.Progress[textui.Portion[int64]] + unreadCnt uint64 + reader *bufio.Reader + closer io.Closer +} + +func newRuneScanner(ctx context.Context, fh *os.File) (*runeScanner, error) { + fi, err := fh.Stat() + if err != nil { + return nil, err + } + ret := &runeScanner{ + progress: textui.Portion[int64]{ + D: fi.Size(), + }, + progressWriter: textui.NewProgress[textui.Portion[int64]](ctx, dlog.LogLevelInfo, 1*time.Second), + reader: bufio.NewReader(fh), + closer: fh, + } + return ret, nil +} + +func (rs *runeScanner) ReadRune() (r rune, size int, err error) { + r, size, err = rs.reader.ReadRune() + if rs.unreadCnt > 0 { + rs.unreadCnt-- + } else { + rs.progress.N += int64(size) + rs.progressWriter.Set(rs.progress) + } + return +} + +func (rs *runeScanner) UnreadRune() error { + rs.unreadCnt++ + return rs.reader.UnreadRune() +} + +func (rs *runeScanner) Close() error { + rs.progressWriter.Done() + return rs.closer.Close() +} + +func readJSONFile[T any](ctx context.Context, filename string) (T, error) { + fh, err := os.Open(filename) + if err != nil { + var zero T + return zero, err + } + buf, err := newRuneScanner(dlog.WithField(ctx, "btrfs.read-json-file", filename), fh) + if err != nil { + var zero T + return zero, err + } + var ret T + if err := lowmemjson.DecodeThenEOF(buf, &ret); err != nil { + var zero T + return zero, err + } + _ = buf.Close() + return ret, nil +} diff --git a/lib/textui/log.go b/lib/textui/log.go index 4421074..e94a24f 100644 --- a/lib/textui/log.go +++ b/lib/textui/log.go @@ -288,6 +288,8 @@ func fieldOrd(key string) int { case "btrfsinspect.rebuild-mappings.substep": return -1 + case "btrfs.read-json-file": + return -1 default: return 1 } @@ -303,6 +305,8 @@ func fieldName(key string) string { return strings.TrimPrefix(key, "btrfsinspect.scandevices.") case strings.HasPrefix(key, "btrfsinspect.rebuild-mappings."): return strings.TrimPrefix(key, "btrfsinspect.rebuild-mappings.") + case strings.HasPrefix(key, "btrfs."): + return strings.TrimPrefix(key, "btrfs.") default: return key } -- cgit v1.2.3-54-g00ecf From 3d0937e9ab148c074922b0d46ed33bdbcbef85b5 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 28 Dec 2022 18:14:14 -0700 Subject: cmd/btrfs-rec: Have all logging include live memory statistics --- cmd/btrfs-rec/main.go | 1 + lib/textui/log_memstats.go | 132 +++++++++++++++++++++++++++++++++++++++++++++ lib/textui/text.go | 119 ++++++++++++++++++++++++++++++++++++++++ scripts/main.sh | 2 +- 4 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 lib/textui/log_memstats.go (limited to 'cmd/btrfs-rec') diff --git a/cmd/btrfs-rec/main.go b/cmd/btrfs-rec/main.go index 87e8696..13ae886 100644 --- a/cmd/btrfs-rec/main.go +++ b/cmd/btrfs-rec/main.go @@ -101,6 +101,7 @@ func main() { ctx := cmd.Context() logger := textui.NewLogger(os.Stderr, logLevelFlag.Level) ctx = dlog.WithLogger(ctx, logger) + ctx = dlog.WithField(ctx, "mem", new(textui.LiveMemUse)) dlog.SetFallbackLogger(logger.WithField("btrfs-progs.THIS_IS_A_BUG", true)) grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{ diff --git a/lib/textui/log_memstats.go b/lib/textui/log_memstats.go new file mode 100644 index 0000000..39733c6 --- /dev/null +++ b/lib/textui/log_memstats.go @@ -0,0 +1,132 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package textui + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +type LiveMemUse struct { + mu sync.Mutex + stats runtime.MemStats + last time.Time +} + +var _ fmt.Stringer = (*LiveMemUse)(nil) + +const liveMemUseUpdateInterval = 1 * time.Second + +func (o *LiveMemUse) String() string { + o.mu.Lock() + + // runtime.ReadMemStats() calls stopTheWorld(), so we want to + // rate-limit how often we call it. + if now := time.Now(); now.Sub(o.last) > liveMemUseUpdateInterval { + runtime.ReadMemStats(&o.stats) + o.last = now + } + + // runtime.MemStats only knows about memory managed by the Go runtime; + // even for a pure Go program, there's also + // + // - memory mapped to the executable itself + // - vDSO and friends + // + // But those are pretty small, just a few MiB. + // + // OK, so: memory managed by the Go runtime. runtime.MemStats is pretty + // obtuse, I think it was designed more for "debugging the Go runtime" + // than "monitoring the behavior of a Go program". From the Go + // runtime's perspective, regions of the virtual address space are in + // one of 4 states (see `runtime/mem.go`): + // + // - None : not mapped + // + // - Reserved : mapped, but without r/w permissions (PROT_NONE); so + // this region isn't actually backed by anything + // + // - Prepared : mapped, but allowed to be collected by the OS + // (MADV_FREE, or MADV_DONTNEED on systems without MADV_FREE); so + // this region may or may not actually be backed by anything. + // + // - Ready : mapped, ready to be used + // + // Normal tools count Reserved+Prepared+Ready toward the VSS (which is a + // little silly, when inspecting /proc/{pid}/maps to calculate the VSS, + // IMO they should exclude maps without r/w permissions, which would + // exclude Reserved), but we all know that VSS numbers are over + // inflated. And RSS only useful if we fit in RAM and don't spill to + // swap (this is being written for btrfs-rec, which is quite likely to + // consume all RAM on a laptop). Useful numbers are Ready and Prepared; + // as I said above, outside tools reporting Ready+Prepared would be easy + // and useful, but none do; but I don't think outside tools have a way + // to distinguish between Ready and Prepared (unless you can detect + // MADV_FREE/MADV_DONTNEED in /proc/{pid}/smaps?). + // + // Of the 3 mapped states, here's how we get them from runtime: + // + // - Reserved : AFAICT, you can't :( + // + // - Prepared : `runtime.MemStats.HeapReleased` + // + // - Ready : `runtime.MemStats.Sys - runtime.MemStats.HeapReleased` + // (that is, runtime.MemStats.Sys is Prepared+Ready) + // + // It's a bummer that we can't get Reserved from runtime, but as I've + // said, it's not super useful; it's only use would really be + // cross-referencing runtime's numbers against the VSS. + // + // The godocs for runtime.MemStats.Sys say "It's likely that not all of + // the virtual address space is backed by physical memory at any given + // moment, though in general it all was at some point." That's both + // confusing and a lie. It's confusing because it doesn't give you + // hooks to find out more; it could have said that this is + // Ready+Prepared and that Prepared is the portion of the space that + // might not be backed by physical memory, but instead it wants you to + // throw your hands up and say "this is too weird for me to understand". + // It's a lie because "in general it all was at some point" implies that + // all Prepared memory was previously Ready, which is false; it can go + // None->Reserved->Prepared (but it only does that Reserved->Prepared + // transition if it thinks it will need to transition it to Ready very + // soon, so maybe the doc author though that was negligible?). + // + // Now, those are still pretty opaque numbers; most of runtime.MemStats + // goes toward accounting for what's going on inside of Ready space. + // + // For our purposes, we don't care too much about specifics of how Ready + // space is being used; just how much is "actually storing data", vs + // "overhead from heap-fragmentation", vs "idle". + + var ( + // We're going to add up all of the `o.stats.{thing}Sys` + // variables and check that against `o.stats.Sys`, in order to + // make sure that we're not missing any {thing} when adding up + // `inuse`. + calcSys = o.stats.HeapSys + o.stats.StackSys + o.stats.MSpanSys + o.stats.MCacheSys + o.stats.BuckHashSys + o.stats.GCSys + o.stats.OtherSys + inuse = o.stats.HeapInuse + o.stats.StackInuse + o.stats.MSpanInuse + o.stats.MCacheInuse + o.stats.BuckHashSys + o.stats.GCSys + o.stats.OtherSys + ) + if calcSys != o.stats.Sys { + panic("should not happen") + } + prepared := o.stats.HeapReleased + ready := o.stats.Sys - prepared + + readyFragOverhead := o.stats.HeapInuse - o.stats.HeapAlloc + readyData := inuse - readyFragOverhead + readyIdle := ready - inuse + + o.mu.Unlock() + + return Sprintf("Ready+Prepared=%.1f (Ready=%.1f (data:%.1f + fragOverhead:%.1f + idle:%.1f) ; Prepared=%.1f)", + IEC(ready+prepared, "B"), + IEC(ready, "B"), + IEC(readyData, "B"), + IEC(readyFragOverhead, "B"), + IEC(readyIdle, "B"), + IEC(prepared, "B")) +} diff --git a/lib/textui/text.go b/lib/textui/text.go index f628eab..d6a80b3 100644 --- a/lib/textui/text.go +++ b/lib/textui/text.go @@ -7,6 +7,7 @@ package textui import ( "fmt" "io" + "math" "golang.org/x/exp/constraints" "golang.org/x/text/language" @@ -83,3 +84,121 @@ func (p Portion[T]) String() string { } return printer.Sprintf("%v (%v/%v)", number.Percent(pct), uint64(p.N), uint64(p.D)) } + +type metric[T constraints.Integer | constraints.Float] struct { + Val T + Unit string +} + +var ( + _ fmt.Formatter = metric[int]{} + _ fmt.Stringer = metric[int]{} +) + +func Metric[T constraints.Integer | constraints.Float](x T, unit string) metric[T] { + return metric[T]{ + Val: x, + Unit: unit, + } +} + +var metricSmallPrefixes = []string{ + "m", + "μ", + "n", + "p", + "f", + "a", + "z", + "y", + "r", + "q", +} + +var metricBigPrefixes = []string{ + "k", + "M", + "G", + "T", + "P", + "E", + "Z", + "Y", + "R", + "Q", +} + +// String implements fmt.Formatter. +func (v metric[T]) Format(f fmt.State, verb rune) { + var prefix string + y := math.Abs(float64(v.Val)) + if y < 1 { + for i := 0; y < 1 && i <= len(metricSmallPrefixes); i++ { + y *= 1000 + prefix = metricSmallPrefixes[i] + } + } else { + for i := 0; y > 1000 && i <= len(metricBigPrefixes); i++ { + y /= 1000 + prefix = metricBigPrefixes[i] + } + } + if v.Val < 0 { + y = -y + } + printer.Fprintf(f, fmtutil.FmtStateString(f, verb)+"%s%s", + y, prefix, v.Unit) +} + +// String implements fmt.Stringer. +func (v metric[T]) String() string { + return fmt.Sprint(v) +} + +type iec[T constraints.Integer | constraints.Float] struct { + Val T + Unit string +} + +var ( + _ fmt.Formatter = iec[int]{} + _ fmt.Stringer = iec[int]{} +) + +func IEC[T constraints.Integer | constraints.Float](x T, unit string) iec[T] { + return iec[T]{ + Val: x, + Unit: unit, + } +} + +var iecPrefixes = []string{ + "Ki", + "Mi", + "Gi", + "Ti", + "Pi", + "Ei", + "Zi", + "Yi", +} + +// String implements fmt.Formatter. +func (v iec[T]) Format(f fmt.State, verb rune) { + var prefix string + y := math.Abs(float64(v.Val)) + for i := 0; y > 1024 && i <= len(iecPrefixes); i++ { + y /= 1024 + prefix = iecPrefixes[i] + } + if v.Val < 0 { + y = -y + } + printer.Fprintf(f, fmtutil.FmtStateString(f, verb)+"%s%s", + number.Decimal(y), prefix, v.Unit) +} + +// String implements fmt.Stringer. +func (v iec[T]) String() string { + return fmt.Sprint(v) +} diff --git a/scripts/main.sh b/scripts/main.sh index e44ae7f..160aa42 100755 --- a/scripts/main.sh +++ b/scripts/main.sh @@ -13,7 +13,7 @@ gen() ( ) set -x -go build ./cmd/btrfs-rec +CGO_ENABLED=0 go build -trimpath ./cmd/btrfs-rec mkdir -p "$b.gen" { set +x; } &>/dev/null -- cgit v1.2.3-54-g00ecf