Output is buggy but halfway there
This commit is contained in:
parent
d599833b64
commit
36a88f45fc
@ -23,6 +23,7 @@ func main() {
|
|||||||
local := []*cli.Command{
|
local := []*cli.Command{
|
||||||
addressCmd,
|
addressCmd,
|
||||||
statActorCmd,
|
statActorCmd,
|
||||||
|
statSnapshotCmd,
|
||||||
statObjCmd,
|
statObjCmd,
|
||||||
base64Cmd,
|
base64Cmd,
|
||||||
base32Cmd,
|
base32Cmd,
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
@ -22,9 +23,12 @@ import (
|
|||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
"github.com/filecoin-project/go-state-types/abi"
|
"github.com/filecoin-project/go-state-types/abi"
|
||||||
|
gstactors "github.com/filecoin-project/go-state-types/actors"
|
||||||
|
"github.com/filecoin-project/go-state-types/network"
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/api"
|
"github.com/filecoin-project/lotus/api"
|
||||||
"github.com/filecoin-project/lotus/blockstore"
|
"github.com/filecoin-project/lotus/blockstore"
|
||||||
|
"github.com/filecoin-project/lotus/chain/actors"
|
||||||
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
||||||
"github.com/filecoin-project/lotus/chain/consensus"
|
"github.com/filecoin-project/lotus/chain/consensus"
|
||||||
"github.com/filecoin-project/lotus/chain/consensus/filcns"
|
"github.com/filecoin-project/lotus/chain/consensus/filcns"
|
||||||
@ -51,6 +55,19 @@ type fieldItem struct {
|
|||||||
Stats api.ObjStat
|
Stats api.ObjStat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type job struct {
|
||||||
|
c cid.Cid
|
||||||
|
key string // prefix path for the region being recorded i.e. "/state/mineractor"
|
||||||
|
}
|
||||||
|
type cidCall struct {
|
||||||
|
c cid.Cid
|
||||||
|
resp chan bool
|
||||||
|
}
|
||||||
|
type result struct {
|
||||||
|
key string
|
||||||
|
stats api.ObjStat
|
||||||
|
}
|
||||||
|
|
||||||
type cacheNodeGetter struct {
|
type cacheNodeGetter struct {
|
||||||
ds format.NodeGetter
|
ds format.NodeGetter
|
||||||
cache *lru.TwoQueueCache[cid.Cid, format.Node]
|
cache *lru.TwoQueueCache[cid.Cid, format.Node]
|
||||||
@ -213,7 +230,6 @@ func loadChainStore(ctx context.Context, repoPath string) (*StoreHandle, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer lr.Close() //nolint:errcheck
|
|
||||||
|
|
||||||
bs, err := lr.Blockstore(ctx, repo.UniversalBlockstore)
|
bs, err := lr.Blockstore(ctx, repo.UniversalBlockstore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -221,6 +237,7 @@ func loadChainStore(ctx context.Context, repoPath string) (*StoreHandle, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
closer := func() {
|
closer := func() {
|
||||||
|
lr.Close()
|
||||||
if c, ok := bs.(io.Closer); ok {
|
if c, ok := bs.(io.Closer); ok {
|
||||||
if err := c.Close(); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
log.Warnf("failed to close blockstore: %s", err)
|
log.Warnf("failed to close blockstore: %s", err)
|
||||||
@ -297,60 +314,66 @@ var statSnapshotCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type job struct {
|
|
||||||
c cid.Cid
|
|
||||||
key string // prefix path for the region being recorded i.e. "/state/mineractor"
|
|
||||||
}
|
|
||||||
type cidCall struct {
|
|
||||||
c cid.Cid
|
|
||||||
resp chan bool
|
|
||||||
}
|
|
||||||
type result struct {
|
|
||||||
key string
|
|
||||||
stats api.ObjStat
|
|
||||||
}
|
|
||||||
// Stage 1 walk the latest state root
|
|
||||||
// A) walk all actors
|
|
||||||
// B) when done walk the actor HAMT
|
|
||||||
|
|
||||||
st, err := h.sm.StateTree(ts.ParentState())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
numWorkers := cctx.Int("workers")
|
numWorkers := cctx.Int("workers")
|
||||||
dagCacheSize := cctx.Int("dag-cache-size")
|
dagCacheSize := cctx.Int("dag-cache-size")
|
||||||
|
|
||||||
eg, egctx := errgroup.WithContext(ctx)
|
eg, egctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
jobs := make(chan job, numWorkers)
|
jobCh := make(chan job, numWorkers)
|
||||||
results := make(chan result)
|
resultCh := make(chan result)
|
||||||
cidCh := make(chan cidCall, numWorkers)
|
cidCh := make(chan cidCall, numWorkers)
|
||||||
|
summary := make(map[string]api.ObjStat)
|
||||||
|
combine := func(statsA, statsB api.ObjStat) api.ObjStat {
|
||||||
|
return api.ObjStat{
|
||||||
|
Size: statsA.Size + statsB.Size,
|
||||||
|
Links: statsA.Links + statsB.Links,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var workerWg sync.WaitGroup
|
||||||
|
|
||||||
go func() {
|
// Threadsafe cid set lives across different pipelines so not part of error group
|
||||||
|
go func() error {
|
||||||
seen := cid.NewSet()
|
seen := cid.NewSet()
|
||||||
|
for {
|
||||||
select {
|
select {
|
||||||
case call := <-cidCh:
|
case call := <-cidCh:
|
||||||
call.resp <- seen.Visit(call.c)
|
call.resp <- seen.Visit(call.c)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Infof("shutting down cid set goroutine")
|
log.Infof("shutting down cid set goroutine")
|
||||||
close(cidCh)
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
visit := func(c cid.Cid) bool {
|
||||||
|
ch := make(chan bool)
|
||||||
|
cidCh <- cidCall{c: c, resp: ch}
|
||||||
|
out := <-ch
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
// Stage 1 walk all actors in latest state root
|
||||||
|
eg.Go(func() error {
|
||||||
worker := func(ctx context.Context, id int) error {
|
|
||||||
defer func() {
|
defer func() {
|
||||||
//log.Infow("worker done", "id", id, "completed", completed)
|
close(jobCh)
|
||||||
}()
|
}()
|
||||||
|
st, err := h.sm.StateTree(ts.ParentState())
|
||||||
for {
|
if err != nil {
|
||||||
select {
|
return err
|
||||||
case job, ok := <-jobs:
|
}
|
||||||
if !ok {
|
return st.ForEach(func(_ address.Address, act *types.Actor) error {
|
||||||
return nil
|
actType := builtin.ActorNameByCode(act.Code)
|
||||||
|
if actType == "<unknown>" {
|
||||||
|
actType = act.Code.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jobCh <- job{c: act.Head, key: fmt.Sprintf("/state/%s", actType)}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
worker := func(ctx context.Context, id int, jobCh chan job, resultCh chan result) error {
|
||||||
var dag format.NodeGetter = merkledag.NewDAGService(blockservice.New(h.bs, offline.Exchange(h.bs)))
|
var dag format.NodeGetter = merkledag.NewDAGService(blockservice.New(h.bs, offline.Exchange(h.bs)))
|
||||||
if dagCacheSize != 0 {
|
if dagCacheSize != 0 {
|
||||||
var err error
|
var err error
|
||||||
@ -360,73 +383,75 @@ var statSnapshotCmd = &cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := snapshotJobStats(job, dag, cidCh)
|
for job := range jobCh {
|
||||||
|
stats, err := collectSnapshotJobStats(ctx, job, dag, visit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for _, stat := range stats {
|
for _, stat := range stats {
|
||||||
select {
|
select {
|
||||||
case results <- stat:
|
case resultCh <- stat:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
}
|
||||||
return ctx.Err()
|
return nil
|
||||||
|
}
|
||||||
|
var id int
|
||||||
|
for w := 0; w < numWorkers; w++ {
|
||||||
|
id = w
|
||||||
|
workerWg.Add(1)
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer workerWg.Done()
|
||||||
|
return worker(egctx, id, jobCh, resultCh)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// Close result channel when workers are done sending to it.
|
||||||
}
|
eg.Go(func() error {
|
||||||
|
workerWg.Wait()
|
||||||
go func() {
|
close(resultCh)
|
||||||
st.ForEach(func(_ address.Address, act *types.Actor) error {
|
|
||||||
actType := builtin.ActorNameByCode(act.Code)
|
|
||||||
if actType == "<unknown>" {
|
|
||||||
actType = act.Code.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
jobs <- job{c: act.Head, key: fmt.Sprintf("/state/%s", actType)}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
}()
|
|
||||||
|
|
||||||
for w := 0; w < numWorkers; w++ {
|
|
||||||
id := w
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
return worker(egctx, id)
|
for result := range resultCh {
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
summary := make(map[string]api.ObjStat)
|
|
||||||
combine := func(statsA, statsB api.ObjStat) api.ObjStat {
|
|
||||||
return api.ObjStat{
|
|
||||||
Size: statsA.Size + statsB.Size,
|
|
||||||
Links: statsA.Links + statsB.Links,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOOP:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case result, ok := <-results:
|
|
||||||
if !ok {
|
|
||||||
return eg.Wait()
|
|
||||||
}
|
|
||||||
if stat, ok := summary[result.key]; ok {
|
if stat, ok := summary[result.key]; ok {
|
||||||
summary[result.key] = combine(stat, summary[result.key])
|
summary[result.key] = combine(stat, summary[result.key])
|
||||||
} else {
|
} else {
|
||||||
summary[result.key] = result.stats
|
summary[result.key] = result.stats
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-ctx.Done():
|
|
||||||
break LOOP
|
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
// XXX walk the top of the state tree
|
})
|
||||||
|
|
||||||
// Stage 2 walk the rest of the chain: headers, messages, churn
|
if err := eg.Wait(); err != nil {
|
||||||
|
return fmt.Errorf("failed to measure space in latest state root: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage 2: walk the top of the latest state root
|
||||||
|
|
||||||
|
id += 1
|
||||||
|
resultCh = make(chan result)
|
||||||
|
jobCh = make(chan job)
|
||||||
|
go func() {
|
||||||
|
defer close(jobCh)
|
||||||
|
jobCh <- job{c: ts.ParentState(), key: "statetop"}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer close(resultCh)
|
||||||
|
worker(ctx, id, jobCh, resultCh)
|
||||||
|
}()
|
||||||
|
res := <-resultCh
|
||||||
|
summary[res.key] = res.stats
|
||||||
|
|
||||||
|
// Stage 3 walk the rest of the chain: headers, messages, churn
|
||||||
// ordering:
|
// ordering:
|
||||||
// A) for each header send jobs for messages, receipts, state tree churn
|
// for each header send jobs for messages, receipts, state tree churn
|
||||||
// don't walk header directly as it would just walk everything including parent tipsets
|
// don't walk header directly as it would just walk everything including parent tipsets
|
||||||
// B) when done walk all actor HAMTs for churn
|
|
||||||
|
// Stage 4 walk all actor HAMTs for churn
|
||||||
|
|
||||||
if cctx.Bool("pretty") {
|
if cctx.Bool("pretty") {
|
||||||
DumpSnapshotStats(summary)
|
DumpSnapshotStats(summary)
|
||||||
@ -612,6 +637,93 @@ to reduce the number of decode operations performed by caching the decoded objec
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectSnapshotJobStats(ctx context.Context, in job, dag format.NodeGetter, visit func(c cid.Cid) bool) ([]result, error) {
|
||||||
|
// "state" and "churn" require further breakdown by actor type
|
||||||
|
if path.Dir(in.key) != "state" && path.Dir(in.key) != "churn" {
|
||||||
|
dsc := &dagStatCollector{
|
||||||
|
ds: dag,
|
||||||
|
walk: carWalkFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := merkledag.Walk(ctx, dsc.walkLinks, in.c, visit, merkledag.Concurrent()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []result{{key: in.key, stats: dsc.stats}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// in.c is an actor head cid, try to unmarshal and create sub keys for different regions of state
|
||||||
|
nd, err := dag.Get(ctx, in.c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
subjobs := make([]job, 0)
|
||||||
|
results := make([]result, 0)
|
||||||
|
|
||||||
|
// reconstruct actor for state parsing from key
|
||||||
|
av, err := gstactors.VersionForNetwork(network.Version20)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get actors version for network: %w", err)
|
||||||
|
}
|
||||||
|
code, ok := actors.GetActorCodeID(av, path.Base(in.key))
|
||||||
|
if !ok { // try parsing key directly
|
||||||
|
code, err = cid.Parse(path.Base(in.key))
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failing to parse actor string: %s", path.Base(in.key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actor := types.ActorV5{Head: in.c, Code: code}
|
||||||
|
oif, err := vm.DumpActorState(consensus.NewTipSetExecutor(filcns.RewardFunc).NewActorRegistry(), &actor, nd.RawData())
|
||||||
|
if err != nil {
|
||||||
|
oif = nil
|
||||||
|
}
|
||||||
|
// Account actors return nil from DumpActorState as they have no state
|
||||||
|
if oif != nil {
|
||||||
|
v := reflect.Indirect(reflect.ValueOf(oif))
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
varName := v.Type().Field(i).Name
|
||||||
|
varType := v.Type().Field(i).Type
|
||||||
|
varValue := v.Field(i).Interface()
|
||||||
|
|
||||||
|
if varType == reflect.TypeOf(cid.Cid{}) {
|
||||||
|
subjobs = append(subjobs, job{
|
||||||
|
key: fmt.Sprintf("%s/%s", in.key, varName),
|
||||||
|
c: varValue.(cid.Cid),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk subfields
|
||||||
|
for _, job := range subjobs {
|
||||||
|
dsc := &dagStatCollector{
|
||||||
|
ds: dag,
|
||||||
|
walk: carWalkFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := merkledag.Walk(ctx, dsc.walkLinks, job.c, visit, merkledag.Concurrent()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res result
|
||||||
|
res.key = job.key
|
||||||
|
res.stats = dsc.stats
|
||||||
|
|
||||||
|
results = append(results, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now walk the top level object of actor state
|
||||||
|
dsc := &dagStatCollector{
|
||||||
|
ds: dag,
|
||||||
|
walk: carWalkFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := merkledag.Walk(ctx, dsc.walkLinks, in.c, visit, merkledag.Concurrent()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results = append(results, result{key: in.key, stats: dsc.stats})
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
func collectStats(ctx context.Context, addr address.Address, actor *types.Actor, dag format.NodeGetter) (actorStats, error) {
|
func collectStats(ctx context.Context, addr address.Address, actor *types.Actor, dag format.NodeGetter) (actorStats, error) {
|
||||||
// log.Infow("actor", "addr", addr, "code", actor.Code, "name", builtin.ActorNameByCode(actor.Code))
|
// log.Infow("actor", "addr", addr, "code", actor.Code, "name", builtin.ActorNameByCode(actor.Code))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user