2019-10-11 23:47:29 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-10-14 02:32:32 +00:00
|
|
|
"context"
|
2019-10-11 23:47:29 +00:00
|
|
|
"fmt"
|
2020-05-01 12:06:19 +00:00
|
|
|
"sort"
|
2020-06-04 15:10:41 +00:00
|
|
|
"time"
|
2019-10-11 23:47:29 +00:00
|
|
|
|
2020-05-01 12:06:19 +00:00
|
|
|
"github.com/fatih/color"
|
2020-06-02 18:12:53 +00:00
|
|
|
"github.com/urfave/cli/v2"
|
2020-06-05 22:59:01 +00:00
|
|
|
"golang.org/x/xerrors"
|
2019-10-11 23:47:29 +00:00
|
|
|
|
2020-08-22 17:15:08 +00:00
|
|
|
cbor "github.com/ipfs/go-ipld-cbor"
|
|
|
|
|
|
|
|
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
2020-09-07 03:49:10 +00:00
|
|
|
"github.com/filecoin-project/go-state-types/abi"
|
2020-12-01 18:43:22 +00:00
|
|
|
"github.com/filecoin-project/go-state-types/big"
|
2020-08-17 13:39:33 +00:00
|
|
|
sealing "github.com/filecoin-project/lotus/extern/storage-sealing"
|
2020-04-24 17:35:41 +00:00
|
|
|
|
2019-10-18 04:47:41 +00:00
|
|
|
"github.com/filecoin-project/lotus/api"
|
2020-08-22 17:15:08 +00:00
|
|
|
"github.com/filecoin-project/lotus/api/apibstore"
|
2020-06-04 15:10:41 +00:00
|
|
|
"github.com/filecoin-project/lotus/build"
|
2020-09-18 21:46:42 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/actors/adt"
|
|
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
2019-10-18 04:47:41 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
|
|
lcli "github.com/filecoin-project/lotus/cli"
|
2020-08-22 17:15:08 +00:00
|
|
|
"github.com/filecoin-project/lotus/lib/blockstore"
|
|
|
|
"github.com/filecoin-project/lotus/lib/bufbstore"
|
2019-10-11 23:47:29 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var infoCmd = &cli.Command{
|
|
|
|
Name: "info",
|
2020-07-11 08:55:13 +00:00
|
|
|
Usage: "Print miner info",
|
2020-08-04 18:57:40 +00:00
|
|
|
Subcommands: []*cli.Command{
|
|
|
|
infoAllCmd,
|
|
|
|
},
|
2020-09-28 19:34:06 +00:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
&cli.BoolFlag{
|
|
|
|
Name: "hide-sectors-info",
|
|
|
|
Usage: "hide sectors info",
|
|
|
|
},
|
|
|
|
},
|
2020-08-04 18:57:40 +00:00
|
|
|
Action: infoCmdAct,
|
|
|
|
}
|
2020-05-01 12:06:19 +00:00
|
|
|
|
2020-08-04 18:57:40 +00:00
|
|
|
func infoCmdAct(cctx *cli.Context) error {
|
|
|
|
color.NoColor = !cctx.Bool("color")
|
|
|
|
|
|
|
|
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer closer()
|
|
|
|
|
|
|
|
api, acloser, err := lcli.GetFullNodeAPI(cctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer acloser()
|
|
|
|
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
|
2020-12-01 17:56:43 +00:00
|
|
|
fmt.Print("Chain: ")
|
2020-10-30 18:39:31 +00:00
|
|
|
|
|
|
|
head, err := api.ChainHead(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs*3/2): // within 1.5 epochs
|
|
|
|
fmt.Printf("[%s]", color.GreenString("sync ok"))
|
|
|
|
case time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs*5): // within 5 epochs
|
|
|
|
fmt.Printf("[%s]", color.YellowString("sync slow (%s behind)", time.Now().Sub(time.Unix(int64(head.MinTimestamp()), 0)).Truncate(time.Second)))
|
|
|
|
default:
|
|
|
|
fmt.Printf("[%s]", color.RedString("sync behind! (%s behind)", time.Now().Sub(time.Unix(int64(head.MinTimestamp()), 0)).Truncate(time.Second)))
|
|
|
|
}
|
|
|
|
|
2020-12-01 17:56:43 +00:00
|
|
|
basefee := head.MinTicketBlock().ParentBaseFee
|
|
|
|
gasCol := []color.Attribute{color.FgBlue}
|
|
|
|
switch {
|
|
|
|
case basefee.GreaterThan(big.NewInt(7000_000_000)): // 7 nFIL
|
|
|
|
gasCol = []color.Attribute{color.BgRed, color.FgBlack}
|
|
|
|
case basefee.GreaterThan(big.NewInt(3000_000_000)): // 3 nFIL
|
|
|
|
gasCol = []color.Attribute{color.FgRed}
|
|
|
|
case basefee.GreaterThan(big.NewInt(750_000_000)): // 750 uFIL
|
|
|
|
gasCol = []color.Attribute{color.FgYellow}
|
|
|
|
case basefee.GreaterThan(big.NewInt(100_000_000)): // 100 uFIL
|
|
|
|
gasCol = []color.Attribute{color.FgGreen}
|
|
|
|
}
|
2020-12-01 18:15:20 +00:00
|
|
|
fmt.Printf(" [basefee %s]", color.New(gasCol...).Sprint(types.FIL(basefee).Short()))
|
2020-12-01 17:56:43 +00:00
|
|
|
|
2020-10-30 18:39:31 +00:00
|
|
|
fmt.Println()
|
|
|
|
|
2020-08-04 18:57:40 +00:00
|
|
|
maddr, err := getActorAddress(ctx, nodeApi, cctx.String("actor"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
mact, err := api.StateGetActor(ctx, maddr, types.EmptyTSK)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-09-18 21:46:42 +00:00
|
|
|
|
|
|
|
tbs := bufbstore.NewTieredBstore(apibstore.NewAPIBlockstore(api), blockstore.NewTemporary())
|
|
|
|
mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-08-04 18:57:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sector size
|
|
|
|
mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-12-01 18:01:08 +00:00
|
|
|
ssize := types.SizeStr(types.NewInt(uint64(mi.SectorSize)))
|
|
|
|
fmt.Printf("Miner: %s (%s sectors)\n", color.BlueString("%s", maddr), ssize)
|
2020-08-04 18:57:40 +00:00
|
|
|
|
|
|
|
pow, err := api.StateMinerPower(ctx, maddr, types.EmptyTSK)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
2020-12-01 18:43:22 +00:00
|
|
|
fmt.Printf("Power: %s / %s (%0.4f%%)\n",
|
2020-08-04 18:57:40 +00:00
|
|
|
color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)),
|
|
|
|
types.DeciStr(pow.TotalPower.QualityAdjPower),
|
|
|
|
float64(qpercI.Int64())/10000)
|
|
|
|
|
2020-12-01 18:43:22 +00:00
|
|
|
fmt.Printf("\tRaw: %s / %s (%0.4f%%)\n",
|
|
|
|
color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)),
|
|
|
|
types.SizeStr(pow.TotalPower.RawBytePower),
|
|
|
|
float64(rpercI.Int64())/10000)
|
|
|
|
|
2020-08-04 18:57:40 +00:00
|
|
|
secCounts, err := api.StateMinerSectorCount(ctx, maddr, types.EmptyTSK)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-09-15 19:09:39 +00:00
|
|
|
proving := secCounts.Active + secCounts.Faulty
|
|
|
|
nfaults := secCounts.Faulty
|
|
|
|
fmt.Printf("\tCommitted: %s\n", types.SizeStr(types.BigMul(types.NewInt(secCounts.Live), types.NewInt(uint64(mi.SectorSize)))))
|
2020-08-04 18:57:40 +00:00
|
|
|
if nfaults == 0 {
|
2020-09-15 19:09:39 +00:00
|
|
|
fmt.Printf("\tProving: %s\n", types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))))
|
2020-08-04 18:57:40 +00:00
|
|
|
} else {
|
|
|
|
var faultyPercentage float64
|
2020-09-15 19:09:39 +00:00
|
|
|
if secCounts.Live != 0 {
|
|
|
|
faultyPercentage = float64(10000*nfaults/secCounts.Live) / 100.
|
2020-08-04 18:57:40 +00:00
|
|
|
}
|
|
|
|
fmt.Printf("\tProving: %s (%s Faulty, %.2f%%)\n",
|
2020-09-15 19:09:39 +00:00
|
|
|
types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))),
|
2020-08-04 18:57:40 +00:00
|
|
|
types.SizeStr(types.BigMul(types.NewInt(nfaults), types.NewInt(uint64(mi.SectorSize)))),
|
|
|
|
faultyPercentage)
|
|
|
|
}
|
|
|
|
|
2020-09-16 05:49:33 +00:00
|
|
|
if !pow.HasMinPower {
|
2020-08-04 18:57:40 +00:00
|
|
|
fmt.Print("Below minimum power threshold, no blocks will be won")
|
|
|
|
} else {
|
|
|
|
expWinChance := float64(types.BigMul(qpercI, types.NewInt(build.BlocksPerEpoch)).Int64()) / 1000000
|
|
|
|
if expWinChance > 0 {
|
|
|
|
if expWinChance > 1 {
|
|
|
|
expWinChance = 1
|
2020-06-04 15:10:41 +00:00
|
|
|
}
|
2020-08-04 18:57:40 +00:00
|
|
|
winRate := time.Duration(float64(time.Second*time.Duration(build.BlockDelaySecs)) / expWinChance)
|
|
|
|
winPerDay := float64(time.Hour*24) / float64(winRate)
|
|
|
|
|
|
|
|
fmt.Print("Expected block win rate: ")
|
|
|
|
color.Blue("%.4f/day (every %s)", winPerDay, winRate.Truncate(time.Second))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println()
|
|
|
|
|
2020-08-22 17:15:08 +00:00
|
|
|
deals, err := nodeApi.MarketListIncompleteDeals(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var nactiveDeals, nVerifDeals, ndeals uint64
|
|
|
|
var activeDealBytes, activeVerifDealBytes, dealBytes abi.PaddedPieceSize
|
|
|
|
for _, deal := range deals {
|
2020-12-01 18:15:20 +00:00
|
|
|
if deal.State == storagemarket.StorageDealError {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-08-22 17:15:08 +00:00
|
|
|
ndeals++
|
|
|
|
dealBytes += deal.Proposal.PieceSize
|
|
|
|
|
2020-08-22 17:16:37 +00:00
|
|
|
if deal.State == storagemarket.StorageDealActive {
|
2020-08-22 17:15:08 +00:00
|
|
|
nactiveDeals++
|
|
|
|
activeDealBytes += deal.Proposal.PieceSize
|
|
|
|
|
|
|
|
if deal.Proposal.VerifiedDeal {
|
|
|
|
nVerifDeals++
|
|
|
|
activeVerifDealBytes += deal.Proposal.PieceSize
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("Deals: %d, %s\n", ndeals, types.SizeStr(types.NewInt(uint64(dealBytes))))
|
|
|
|
fmt.Printf("\tActive: %d, %s (Verified: %d, %s)\n", nactiveDeals, types.SizeStr(types.NewInt(uint64(activeDealBytes))), nVerifDeals, types.SizeStr(types.NewInt(uint64(activeVerifDealBytes))))
|
|
|
|
fmt.Println()
|
|
|
|
|
2020-12-01 18:56:47 +00:00
|
|
|
spendable := big.Zero()
|
|
|
|
|
2020-09-18 21:46:42 +00:00
|
|
|
// NOTE: there's no need to unlock anything here. Funds only
|
|
|
|
// vest on deadline boundaries, and they're unlocked by cron.
|
|
|
|
lockedFunds, err := mas.LockedFunds()
|
2020-08-22 17:15:08 +00:00
|
|
|
if err != nil {
|
2020-09-18 21:46:42 +00:00
|
|
|
return xerrors.Errorf("getting locked funds: %w", err)
|
|
|
|
}
|
|
|
|
availBalance, err := mas.AvailableBalance(mact.Balance)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("getting available balance: %w", err)
|
2020-08-22 17:15:08 +00:00
|
|
|
}
|
2020-12-01 18:56:47 +00:00
|
|
|
spendable = big.Add(spendable, availBalance)
|
|
|
|
|
2020-12-01 18:43:22 +00:00
|
|
|
fmt.Printf("Miner Balance: %s\n", color.YellowString("%s", types.FIL(mact.Balance).Short()))
|
|
|
|
fmt.Printf(" PreCommit: %s\n", types.FIL(lockedFunds.PreCommitDeposits).Short())
|
|
|
|
fmt.Printf(" Pledge: %s\n", types.FIL(lockedFunds.InitialPledgeRequirement).Short())
|
|
|
|
fmt.Printf(" Vesting: %s\n", types.FIL(lockedFunds.VestingFunds).Short())
|
|
|
|
color.Green(" Available: %s", types.FIL(availBalance).Short())
|
2020-08-04 18:57:40 +00:00
|
|
|
|
|
|
|
mb, err := api.StateMarketBalance(ctx, maddr, types.EmptyTSK)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("getting market balance: %w", err)
|
|
|
|
}
|
2020-12-01 18:56:47 +00:00
|
|
|
spendable = big.Add(spendable, big.Sub(mb.Escrow, mb.Locked))
|
|
|
|
|
2020-12-01 18:43:22 +00:00
|
|
|
fmt.Printf("Market Balance: %s\n", types.FIL(mb.Escrow).Short())
|
|
|
|
fmt.Printf(" Locked: %s\n", types.FIL(mb.Locked).Short())
|
|
|
|
color.Green(" Available: %s\n", types.FIL(big.Sub(mb.Escrow, mb.Locked)).Short())
|
2020-12-01 18:22:57 +00:00
|
|
|
|
|
|
|
wb, err := api.WalletBalance(ctx, mi.Worker)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("getting worker balance: %w", err)
|
|
|
|
}
|
2020-12-01 18:56:47 +00:00
|
|
|
spendable = big.Add(spendable, wb)
|
2020-12-01 18:43:22 +00:00
|
|
|
color.Cyan("Worker Balance: %s", types.FIL(wb).Short())
|
2020-12-01 18:56:47 +00:00
|
|
|
if len(mi.ControlAddresses) > 0 {
|
|
|
|
cbsum := big.Zero()
|
|
|
|
for _, ca := range mi.ControlAddresses {
|
|
|
|
b, err := api.WalletBalance(ctx, ca)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("getting control address balance: %w", err)
|
|
|
|
}
|
|
|
|
cbsum = big.Add(cbsum, b)
|
|
|
|
}
|
|
|
|
spendable = big.Add(spendable, cbsum)
|
|
|
|
|
|
|
|
fmt.Printf(" Control: %s\n", types.FIL(cbsum).Short())
|
|
|
|
}
|
|
|
|
fmt.Printf("Total Spendable: %s\n", color.YellowString(types.FIL(spendable).Short()))
|
2020-08-04 18:57:40 +00:00
|
|
|
|
|
|
|
fmt.Println()
|
|
|
|
|
2020-09-28 19:34:06 +00:00
|
|
|
if !cctx.Bool("hide-sectors-info") {
|
|
|
|
fmt.Println("Sectors:")
|
|
|
|
err = sectorsInfo(ctx, nodeApi)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-08-04 18:57:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: grab actr state / info
|
|
|
|
// * Sealed sectors (count / bytes)
|
|
|
|
// * Power
|
|
|
|
return nil
|
2019-10-11 23:47:29 +00:00
|
|
|
}
|
2019-10-14 02:32:32 +00:00
|
|
|
|
2020-05-01 19:51:31 +00:00
|
|
|
type stateMeta struct {
|
|
|
|
i int
|
|
|
|
col color.Attribute
|
2020-05-01 12:06:19 +00:00
|
|
|
state sealing.SectorState
|
|
|
|
}
|
|
|
|
|
|
|
|
var stateOrder = map[sealing.SectorState]stateMeta{}
|
|
|
|
var stateList = []stateMeta{
|
|
|
|
{col: 39, state: "Total"},
|
|
|
|
{col: color.FgGreen, state: sealing.Proving},
|
|
|
|
|
2020-08-27 10:41:12 +00:00
|
|
|
{col: color.FgBlue, state: sealing.Empty},
|
|
|
|
{col: color.FgBlue, state: sealing.WaitDeals},
|
|
|
|
|
2020-05-01 12:06:19 +00:00
|
|
|
{col: color.FgRed, state: sealing.UndefinedSectorState},
|
|
|
|
{col: color.FgYellow, state: sealing.Packing},
|
2020-11-26 09:53:31 +00:00
|
|
|
{col: color.FgYellow, state: sealing.GetTicket},
|
2020-05-01 12:06:19 +00:00
|
|
|
{col: color.FgYellow, state: sealing.PreCommit1},
|
|
|
|
{col: color.FgYellow, state: sealing.PreCommit2},
|
|
|
|
{col: color.FgYellow, state: sealing.PreCommitting},
|
2020-06-04 15:10:41 +00:00
|
|
|
{col: color.FgYellow, state: sealing.PreCommitWait},
|
2020-05-01 12:06:19 +00:00
|
|
|
{col: color.FgYellow, state: sealing.WaitSeed},
|
|
|
|
{col: color.FgYellow, state: sealing.Committing},
|
2020-08-27 10:57:08 +00:00
|
|
|
{col: color.FgYellow, state: sealing.SubmitCommit},
|
2020-05-01 12:06:19 +00:00
|
|
|
{col: color.FgYellow, state: sealing.CommitWait},
|
|
|
|
{col: color.FgYellow, state: sealing.FinalizeSector},
|
|
|
|
|
2020-08-27 10:41:12 +00:00
|
|
|
{col: color.FgCyan, state: sealing.Removing},
|
|
|
|
{col: color.FgCyan, state: sealing.Removed},
|
|
|
|
|
2020-05-01 12:06:19 +00:00
|
|
|
{col: color.FgRed, state: sealing.FailedUnrecoverable},
|
2020-06-04 19:22:53 +00:00
|
|
|
{col: color.FgRed, state: sealing.SealPreCommit1Failed},
|
|
|
|
{col: color.FgRed, state: sealing.SealPreCommit2Failed},
|
2020-05-01 12:06:19 +00:00
|
|
|
{col: color.FgRed, state: sealing.PreCommitFailed},
|
|
|
|
{col: color.FgRed, state: sealing.ComputeProofFailed},
|
|
|
|
{col: color.FgRed, state: sealing.CommitFailed},
|
|
|
|
{col: color.FgRed, state: sealing.PackingFailed},
|
2020-06-04 15:10:41 +00:00
|
|
|
{col: color.FgRed, state: sealing.FinalizeFailed},
|
2020-05-01 12:06:19 +00:00
|
|
|
{col: color.FgRed, state: sealing.Faulty},
|
|
|
|
{col: color.FgRed, state: sealing.FaultReported},
|
|
|
|
{col: color.FgRed, state: sealing.FaultedFinal},
|
2020-08-27 10:41:12 +00:00
|
|
|
{col: color.FgRed, state: sealing.RemoveFailed},
|
2020-08-27 11:51:13 +00:00
|
|
|
{col: color.FgRed, state: sealing.DealsExpired},
|
2020-08-27 20:41:35 +00:00
|
|
|
{col: color.FgRed, state: sealing.RecoverDealIDs},
|
2020-05-01 12:06:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
for i, state := range stateList {
|
|
|
|
stateOrder[state.state] = stateMeta{
|
2020-05-01 19:51:31 +00:00
|
|
|
i: i,
|
|
|
|
col: state.col,
|
2020-05-01 12:06:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func sectorsInfo(ctx context.Context, napi api.StorageMiner) error {
|
2020-12-06 00:51:48 +00:00
|
|
|
summary, err := napi.SectorsSummary(ctx)
|
2019-10-14 02:32:32 +00:00
|
|
|
if err != nil {
|
2020-05-01 12:06:19 +00:00
|
|
|
return err
|
2019-10-14 02:32:32 +00:00
|
|
|
}
|
|
|
|
|
2020-12-06 00:51:48 +00:00
|
|
|
buckets := make(map[sealing.SectorState]int)
|
|
|
|
var total int
|
|
|
|
for s, c := range summary {
|
|
|
|
buckets[sealing.SectorState(s)] = c
|
|
|
|
total += c
|
2020-05-01 12:06:19 +00:00
|
|
|
}
|
2020-12-06 00:51:48 +00:00
|
|
|
buckets["Total"] = total
|
2020-05-01 12:06:19 +00:00
|
|
|
|
|
|
|
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)
|
2019-10-14 02:32:32 +00:00
|
|
|
}
|
|
|
|
|
2020-05-01 12:06:19 +00:00
|
|
|
return nil
|
2019-10-14 02:32:32 +00:00
|
|
|
}
|