From d9acd484c0d18643deb141db7e99e5c4ac11b9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 1 May 2020 14:06:19 +0200 Subject: [PATCH] storageminer: Improve CLI UX --- chain/stmgr/utils.go | 11 +++ chain/types/bigint.go | 10 +- cmd/lotus-storage-miner/info.go | 146 ++++++++++++++++++----------- cmd/lotus-storage-miner/storage.go | 52 ++++++++-- cmd/lotus-storage-miner/workers.go | 65 +++++++++---- go.mod | 1 + 6 files changed, 196 insertions(+), 89 deletions(-) diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index 0780a4710..a5b791b80 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -98,6 +98,16 @@ func SectorSetSizes(ctx context.Context, sm *StateManager, maddr address.Address return api.MinerSectors{}, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) } + notProving, err := abi.BitFieldUnion(mas.Faults, mas.Recoveries) + if err != nil { + return api.MinerSectors{}, err + } + + npc, err := notProving.Count() + if err != nil { + return api.MinerSectors{}, err + } + blks := cbor.NewCborStore(sm.ChainStore().Blockstore()) ss, err := amt.LoadAMT(ctx, blks, mas.Sectors) if err != nil { @@ -106,6 +116,7 @@ func SectorSetSizes(ctx context.Context, sm *StateManager, maddr address.Address return api.MinerSectors{ Sset: ss.Count, + Pset: ss.Count - npc, }, nil } diff --git a/chain/types/bigint.go b/chain/types/bigint.go index 80cb98914..22ecf833c 100644 --- a/chain/types/bigint.go +++ b/chain/types/bigint.go @@ -79,18 +79,18 @@ func SizeStr(bi BigInt) string { return fmt.Sprintf("%.3g %s", f, byteSizeUnits[i]) } -var decUnits = []string{"", "K", "M", "G", "T", "P", "E", "Z"} +var deciUnits = []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} -func DecStr(bi BigInt) string { +func DeciStr(bi BigInt) string { r := new(big.Rat).SetInt(bi.Int) - den := big.NewRat(1, 1000) + den := big.NewRat(1, 1024) var i int - for f, _ := r.Float64(); f >= 1000 && i+1 < len(decUnits); f, _ = r.Float64() { + for f, _ := r.Float64(); f >= 1024 && i+1 < len(deciUnits); f, _ = r.Float64() { i++ r = r.Mul(r, den) } f, _ := r.Float64() - return fmt.Sprintf("%.3g %s", f, decUnits[i]) + return fmt.Sprintf("%.3g %s", f, deciUnits[i]) } diff --git a/cmd/lotus-storage-miner/info.go b/cmd/lotus-storage-miner/info.go index f6b1281ae..32b52f5e2 100644 --- a/cmd/lotus-storage-miner/info.go +++ b/cmd/lotus-storage-miner/info.go @@ -4,8 +4,10 @@ import ( "bytes" "context" "fmt" - "golang.org/x/xerrors" + "sort" + "github.com/fatih/color" + "golang.org/x/xerrors" "gopkg.in/urfave/cli.v2" "github.com/filecoin-project/specs-actors/actors/builtin/miner" @@ -19,7 +21,12 @@ import ( var infoCmd = &cli.Command{ Name: "info", Usage: "Print storage miner info", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "color"}, + }, Action: func(cctx *cli.Context) error { + color.NoColor = !cctx.Bool("color") + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err @@ -54,7 +61,7 @@ var infoCmd = &cli.Command{ } } - fmt.Printf("Miner: %s\n", maddr) + fmt.Printf("Miner: %s\n", color.BlueString("%s", maddr)) // Sector size mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) @@ -71,8 +78,17 @@ var infoCmd = &cli.Command{ rpercI := types.BigDiv(types.BigMul(pow.MinerPower.RawBytePower, types.NewInt(1000000)), pow.TotalPower.RawBytePower) qpercI := types.BigDiv(types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(1000000)), pow.TotalPower.QualityAdjPower) - fmt.Printf("Byte Power: %s / %s (%0.4f%%)\n", types.SizeStr(pow.MinerPower.RawBytePower), types.SizeStr(pow.TotalPower.RawBytePower), float64(rpercI.Int64())/10000) - fmt.Printf("Actual Power: %s / %s (%0.4f%%)\n", types.DecStr(pow.MinerPower.QualityAdjPower), types.DecStr(pow.TotalPower.QualityAdjPower), float64(qpercI.Int64())/10000) + + fmt.Printf("Byte Power: %s / %s (%0.4f%%)\n", + color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), + types.SizeStr(pow.TotalPower.RawBytePower), + float64(rpercI.Int64())/10000) + + fmt.Printf("Actual Power: %s / %s (%0.4f%%)\n", + color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), + types.DeciStr(pow.TotalPower.QualityAdjPower), + float64(qpercI.Int64())/10000) + secCounts, err := api.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) if err != nil { return err @@ -92,15 +108,17 @@ var infoCmd = &cli.Command{ float64(10000*uint64(len(faults))/secCounts.Pset)/100.) } - fmt.Printf("Miner Balance: %s\n", types.FIL(mact.Balance)) + fmt.Println() + + fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance))) fmt.Printf("\tPreCommit: %s\n", types.FIL(mas.PreCommitDeposits)) fmt.Printf("\tLocked: %s\n", types.FIL(mas.LockedFunds)) - fmt.Printf("\tAvailable: %s\n", types.FIL(types.BigSub(mact.Balance, types.BigAdd(mas.LockedFunds, mas.PreCommitDeposits)))) + color.Green("\tAvailable: %s", types.FIL(types.BigSub(mact.Balance, types.BigAdd(mas.LockedFunds, mas.PreCommitDeposits)))) wb, err := api.WalletBalance(ctx, mi.Worker) if err != nil { return xerrors.Errorf("getting worker balance: %w", err) } - fmt.Printf("Worker Balance: %s\n", types.FIL(wb)) + color.Cyan("Worker Balance: %s", types.FIL(wb)) mb, err := api.StateMarketBalance(ctx, maddr, types.EmptyTSK) if err != nil { @@ -109,53 +127,14 @@ var infoCmd = &cli.Command{ fmt.Printf("Market (Escrow): %s\n", types.FIL(mb.Escrow)) fmt.Printf("Market (Locked): %s\n", types.FIL(mb.Locked)) - /*// TODO: indicate whether the post worker is in use - wstat, err := nodeApi.WorkerStats(ctx) + fmt.Println() + + fmt.Println("Sectors:") + err = sectorsInfo(ctx, nodeApi) if err != nil { return err } - fmt.Printf("Worker use:\n") - fmt.Printf("\tLocal: %d / %d (+%d reserved)\n", wstat.LocalTotal-wstat.LocalReserved-wstat.LocalFree, wstat.LocalTotal-wstat.LocalReserved, wstat.LocalReserved) - fmt.Printf("\tRemote: %d / %d\n", wstat.RemotesTotal-wstat.RemotesFree, wstat.RemotesTotal) - - fmt.Printf("Queues:\n") - fmt.Printf("\tAddPiece: %d\n", wstat.AddPieceWait) - fmt.Printf("\tPreCommit: %d\n", wstat.PreCommitWait) - fmt.Printf("\tCommit: %d\n", wstat.CommitWait) - fmt.Printf("\tUnseal: %d\n", wstat.UnsealWait)*/ - - /*ps, err := api.StateMinerPostState(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - if ps.ProvingPeriodStart != 0 { - head, err := api.ChainHead(ctx) - if err != nil { - return err - } - - fallback := ps.ProvingPeriodStart - head.Height() - fallbackS := fallback * build.BlockDelay - - next := fallback + power.WindowedPostChallengeDuration - nextS := next * build.BlockDelay - - fmt.Printf("PoSt Submissions:\n") - fmt.Printf("\tFallback: Epoch %d (in %d blocks, ~%dm %ds)\n", ps.ProvingPeriodStart, fallback, fallbackS/60, fallbackS%60) - fmt.Printf("\tDeadline: Epoch %d (in %d blocks, ~%dm %ds)\n", ps.ProvingPeriodStart+build.SlashablePowerDelay, next, nextS/60, nextS%60) - fmt.Printf("\tConsecutive Failures: %d\n", ps.NumConsecutiveFailures) - } else { - fmt.Printf("Proving Period: Not Proving\n") - }*/ - - sinfo, err := sectorsInfo(ctx, nodeApi) - if err != nil { - return err - } - - fmt.Println("Sectors: ", sinfo) - // TODO: grab actr state / info // * Sealed sectors (count / bytes) // * Power @@ -163,23 +142,78 @@ var infoCmd = &cli.Command{ }, } -func sectorsInfo(ctx context.Context, napi api.StorageMiner) (map[sealing.SectorState]int, error) { +type stateMeta struct{ + i int + col color.Attribute + state sealing.SectorState +} + +var stateOrder = map[sealing.SectorState]stateMeta{} +var stateList = []stateMeta{ + {col: 39, state: "Total"}, + {col: color.FgGreen, state: sealing.Proving}, + + {col: color.FgRed, state: sealing.UndefinedSectorState}, + {col: color.FgYellow, state: sealing.Empty}, + {col: color.FgYellow, state: sealing.Packing}, + {col: color.FgYellow, state: sealing.PreCommit1}, + {col: color.FgYellow, state: sealing.PreCommit2}, + {col: color.FgYellow, state: sealing.PreCommitting}, + {col: color.FgYellow, state: sealing.WaitSeed}, + {col: color.FgYellow, state: sealing.Committing}, + {col: color.FgYellow, state: sealing.CommitWait}, + {col: color.FgYellow, state: sealing.FinalizeSector}, + + {col: color.FgRed, state: sealing.FailedUnrecoverable}, + {col: color.FgRed, state: sealing.SealFailed}, + {col: color.FgRed, state: sealing.PreCommitFailed}, + {col: color.FgRed, state: sealing.ComputeProofFailed}, + {col: color.FgRed, state: sealing.CommitFailed}, + {col: color.FgRed, state: sealing.PackingFailed}, + {col: color.FgRed, state: sealing.Faulty}, + {col: color.FgRed, state: sealing.FaultReported}, + {col: color.FgRed, state: sealing.FaultedFinal}, +} + +func init() { + for i, state := range stateList { + stateOrder[state.state] = stateMeta{ + i: i, + col: state.col, + } + } +} + +func sectorsInfo(ctx context.Context, napi api.StorageMiner) error { sectors, err := napi.SectorsList(ctx) if err != nil { - return nil, err + return err } - out := map[sealing.SectorState]int{ + buckets := map[sealing.SectorState]int{ "Total": len(sectors), } for _, s := range sectors { st, err := napi.SectorsStatus(ctx, s) if err != nil { - return nil, err + return err } - out[sealing.SectorState(st.State)]++ + buckets[sealing.SectorState(st.State)]++ } - return out, nil + var sorted []stateMeta + for state, i := range buckets { + sorted = append(sorted, stateMeta{i: i, state: state}) + } + + sort.Slice(sorted, func(i, j int) bool { + return stateOrder[sorted[i].state].i < stateOrder[sorted[j].state].i + }) + + for _, s := range sorted { + _, _ = color.New(stateOrder[s.state].col).Printf("\t%s: %d\n", s.state, s.i) + } + + return nil } diff --git a/cmd/lotus-storage-miner/storage.go b/cmd/lotus-storage-miner/storage.go index 213a666ab..875c5e9a6 100644 --- a/cmd/lotus-storage-miner/storage.go +++ b/cmd/lotus-storage-miner/storage.go @@ -8,8 +8,10 @@ import ( "path/filepath" "sort" "strconv" + "strings" "time" + "github.com/fatih/color" "github.com/google/uuid" "github.com/mitchellh/go-homedir" "golang.org/x/xerrors" @@ -117,7 +119,12 @@ var storageAttachCmd = &cli.Command{ var storageListCmd = &cli.Command{ Name: "list", Usage: "list local storage paths", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "color"}, + }, Action: func(cctx *cli.Context) error { + color.NoColor = !cctx.Bool("color") + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err @@ -138,15 +145,25 @@ var storageListCmd = &cli.Command{ sorted := make([]struct { stores.ID sectors []stores.Decl + stat stores.FsStat }, 0, len(st)) for id, decls := range st { + st, err := nodeApi.StorageStat(ctx, id) + if err != nil { + return err + } + sorted = append(sorted, struct { stores.ID sectors []stores.Decl - }{id, decls}) + stat stores.FsStat + }{id, decls, st}) } sort.Slice(sorted, func(i, j int) bool { + if sorted[i].stat.Capacity != sorted[j].stat.Capacity { + return sorted[i].stat.Capacity > sorted[j].stat.Capacity + } return sorted[i].ID < sorted[j].ID }) @@ -168,13 +185,29 @@ var storageListCmd = &cli.Command{ } ping := time.Now().Sub(pingStart) + usedPercent := (st.Capacity-st.Available)*100/st.Capacity + + percCol := color.FgGreen + switch { + case usedPercent > 98: + percCol = color.FgRed + case usedPercent > 90: + percCol = color.FgYellow + } + + var barCols = uint64(50) + set := (st.Capacity-st.Available)*barCols/st.Capacity + bar := strings.Repeat("|", int(set)) + strings.Repeat(" ", int(barCols-set)) + fmt.Printf("%s:\n", s.ID) - fmt.Printf("\tUnsealed: %d; Sealed: %d; Caches: %d\n", cnt[0], cnt[1], cnt[2]) - fmt.Printf("\tSpace Used: %s/%s %d%% (%s avail)\n", + fmt.Printf("\t[%s] %s/%s %s\n", color.New(percCol).Sprint(bar), types.SizeStr(types.NewInt(st.Capacity-st.Available)), types.SizeStr(types.NewInt(st.Capacity)), - (st.Capacity-st.Available)*100/st.Capacity, - types.SizeStr(types.NewInt(st.Available))) + color.New(percCol).Sprintf("%d%%", usedPercent)) + fmt.Printf("\t%s; %s; %s\n", + color.YellowString("Unsealed: %d", cnt[0]), + color.GreenString("Sealed: %d", cnt[1]), + color.BlueString("Caches: %d", cnt[2])) si, err := nodeApi.StorageInfo(ctx, s.ID) if err != nil { @@ -185,18 +218,18 @@ var storageListCmd = &cli.Command{ if si.CanSeal || si.CanStore { fmt.Printf("Weight: %d; Use: ", si.Weight) if si.CanSeal { - fmt.Print("Seal ") + fmt.Print(color.MagentaString("Seal ")) } if si.CanStore { - fmt.Print("Store") + fmt.Print(color.CyanString("Store")) } fmt.Println("") } else { - fmt.Println("Use: ReadOnly") + fmt.Print(color.HiYellowString("Use: ReadOnly")) } if localPath, ok := local[s.ID]; ok { - fmt.Printf("\tLocal: %s\n", localPath) + fmt.Printf("\tLocal: %s\n", color.GreenString(localPath)) } for i, l := range si.URLs { var rtt string @@ -206,6 +239,7 @@ var storageListCmd = &cli.Command{ fmt.Printf("\tURL: %s%s\n", l, rtt) // TODO; try pinging maybe?? print latency? } + fmt.Println() } return nil diff --git a/cmd/lotus-storage-miner/workers.go b/cmd/lotus-storage-miner/workers.go index b6d547480..408dc9b2f 100644 --- a/cmd/lotus-storage-miner/workers.go +++ b/cmd/lotus-storage-miner/workers.go @@ -2,11 +2,15 @@ package main import ( "fmt" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/sector-storage/storiface" - "gopkg.in/urfave/cli.v2" "sort" + "strings" + "github.com/fatih/color" + "gopkg.in/urfave/cli.v2" + + "github.com/filecoin-project/sector-storage/storiface" + + "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" ) @@ -21,7 +25,12 @@ var workersCmd = &cli.Command{ var workersListCmd = &cli.Command{ Name: "list", Usage: "list workers", + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "color"}, + }, Action: func(cctx *cli.Context) error { + color.NoColor = !cctx.Bool("color") + nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err @@ -51,30 +60,48 @@ var workersListCmd = &cli.Command{ for _, stat := range st { gpuUse := "not " + gpuCol := color.FgBlue if stat.GpuUsed { + gpuCol = color.FgGreen gpuUse = "" } - fmt.Printf("Worker %d, host %s\n", stat.id, stat.Info.Hostname) - fmt.Printf("\tCPU: %d core(s) in use\n", stat.CpuUse) + fmt.Printf("Worker %d, host %s\n", stat.id, color.MagentaString(stat.Info.Hostname)) + + var barCols = uint64(64) + cpuBars := int(stat.CpuUse * barCols / stat.Info.Resources.CPUs) + cpuBar := strings.Repeat("|", cpuBars) + strings.Repeat(" ", int(barCols) - cpuBars) + + fmt.Printf("\tCPU: [%s] %d core(s) in use\n", color.GreenString(cpuBar), stat.CpuUse) + + ramBarsRes := int(stat.Info.Resources.MemReserved*barCols/stat.Info.Resources.MemPhysical) + ramBarsUsed := int(stat.MemUsedMin*barCols/stat.Info.Resources.MemPhysical) + ramBar := color.YellowString(strings.Repeat("|", ramBarsRes)) + + color.GreenString(strings.Repeat("|", ramBarsUsed)) + + strings.Repeat(" ", int(barCols) - ramBarsUsed - ramBarsRes) + + vmem := stat.Info.Resources.MemPhysical+stat.Info.Resources.MemSwap + + vmemBarsRes := int(stat.Info.Resources.MemReserved*barCols/vmem) + vmemBarsUsed := int(stat.MemUsedMax*barCols/vmem) + vmemBar := color.YellowString(strings.Repeat("|", vmemBarsRes)) + + color.GreenString(strings.Repeat("|", vmemBarsUsed)) + + strings.Repeat(" ", int(barCols) - vmemBarsUsed - vmemBarsRes) + + fmt.Printf("\tRAM: [%s] %d%% %s/%s\n", ramBar, + (stat.Info.Resources.MemReserved + stat.MemUsedMin)*100/stat.Info.Resources.MemPhysical, + types.SizeStr(types.NewInt(stat.Info.Resources.MemReserved + stat.MemUsedMin)), + types.SizeStr(types.NewInt(stat.Info.Resources.MemPhysical))) + + fmt.Printf("\tVMEM: [%s] %d%% %s/%s\n", vmemBar, + (stat.Info.Resources.MemReserved + stat.MemUsedMax)*100/vmem, + types.SizeStr(types.NewInt(stat.Info.Resources.MemReserved + stat.MemUsedMax)), + types.SizeStr(types.NewInt(vmem))) for _, gpu := range stat.Info.Resources.GPUs { - fmt.Printf("\tGPU: %s, %sused\n", gpu, gpuUse) + fmt.Printf("\tGPU: %s\n", color.New(gpuCol).Sprintf("%s, %sused", gpu, gpuUse)) } - - fmt.Printf("\tMemory: System: Physical %s, Swap %s, Reserved %s (%d%% phys)\n", - types.SizeStr(types.NewInt(stat.Info.Resources.MemPhysical)), - types.SizeStr(types.NewInt(stat.Info.Resources.MemSwap)), - types.SizeStr(types.NewInt(stat.Info.Resources.MemReserved)), - stat.Info.Resources.MemReserved*100/stat.Info.Resources.MemPhysical) - - fmt.Printf("\t\tUsed: Physical %s (%d%% phys), Virtual %s (%d%% phys, %d%% virt)\n", - types.SizeStr(types.NewInt(stat.MemUsedMin)), - stat.MemUsedMin*100/stat.Info.Resources.MemPhysical, - types.SizeStr(types.NewInt(stat.MemUsedMax)), - stat.MemUsedMax*100/stat.Info.Resources.MemPhysical, - stat.MemUsedMax*100/(stat.Info.Resources.MemPhysical+stat.Info.Resources.MemSwap)) } return nil diff --git a/go.mod b/go.mod index e855882a4..943c3fa96 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f // indirect github.com/docker/go-units v0.4.0 github.com/drand/drand v0.8.1 + github.com/fatih/color v1.8.0 github.com/filecoin-project/chain-validation v0.0.6-0.20200430201010-ce84f6e96519 github.com/filecoin-project/filecoin-ffi v0.0.0-20200427223233-a0014b17f124 github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be