github.com/prysmaticlabs/prysm@v1.4.4/tools/exploredb/main.go (about)

     1  /**
     2   * Explore DB contents
     3   *
     4   * Given a beacon-chain DB, This tool provides many option to
     5   * inspect and explore it. For every non-empty bucket, print
     6   * the number of rows, bucket size,min/average/max size of values
     7   */
     8  
     9  package main
    10  
    11  import (
    12  	"context"
    13  	"flag"
    14  	"fmt"
    15  	"os"
    16  	"path/filepath"
    17  	"time"
    18  
    19  	"github.com/dustin/go-humanize"
    20  	"github.com/prysmaticlabs/prysm/beacon-chain/db/kv"
    21  	pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
    22  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    23  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    24  	log "github.com/sirupsen/logrus"
    25  	"github.com/status-im/keycard-go/hexutils"
    26  	bolt "go.etcd.io/bbolt"
    27  )
    28  
    29  var (
    30  	datadir        = flag.String("datadir", "", "Path to data directory.")
    31  	dbName         = flag.String("dbname", "", "database name.")
    32  	bucketStats    = flag.Bool("bucket-stats", false, "Show all the bucket stats.")
    33  	bucketContents = flag.Bool("bucket-contents", false, "Show contents of a given bucket.")
    34  	bucketName     = flag.String("bucket-name", "", "bucket to show contents.")
    35  	rowLimit       = flag.Uint64("limit", 10, "limit to rows.")
    36  )
    37  
    38  func main() {
    39  	flag.Parse()
    40  
    41  	// Check for the mandatory flags.
    42  	if *datadir == "" {
    43  		log.Fatal("Please specify --datadir <db path> to read the database")
    44  	}
    45  	if *dbName == "" {
    46  		log.Fatal("Please specify --dbname <db file name> to specify the database file.")
    47  	}
    48  
    49  	// check if the database file is present.
    50  	dbNameWithPath := filepath.Join(*datadir, *dbName)
    51  	if _, err := os.Stat(*datadir); os.IsNotExist(err) {
    52  		log.Fatalf("could not locate database file : %s, %v", dbNameWithPath, err)
    53  	}
    54  
    55  	// show stats of all the buckets.
    56  	if *bucketStats {
    57  		showBucketStats(dbNameWithPath)
    58  		return
    59  	}
    60  
    61  	// show teh contents of the specified bucket.
    62  	if *bucketContents {
    63  		switch *bucketName {
    64  		case "state", "state-summary":
    65  			printBucketContents(dbNameWithPath, *rowLimit, *bucketName)
    66  		default:
    67  			log.Fatal("Oops, Only 'state' and 'state-summary' buckets are supported for now.")
    68  		}
    69  	}
    70  }
    71  
    72  func showBucketStats(dbNameWithPath string) {
    73  	// open the raw database file. If the file is busy, then exit.
    74  	db, openErr := bolt.Open(dbNameWithPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
    75  	if openErr != nil {
    76  		log.Fatalf("could not open db to show bucket stats, %v", openErr)
    77  	}
    78  
    79  	// make sure we close the database before ejecting out of this function.
    80  	defer func() {
    81  		closeErr := db.Close()
    82  		if closeErr != nil {
    83  			log.Fatalf("could not close db after showing bucket stats, %v", closeErr)
    84  		}
    85  	}()
    86  
    87  	// get a list of all the existing buckets.
    88  	var buckets []string
    89  	if viewErr1 := db.View(func(tx *bolt.Tx) error {
    90  		return tx.ForEach(func(name []byte, buc *bolt.Bucket) error {
    91  			buckets = append(buckets, string(name))
    92  			return nil
    93  		})
    94  	}); viewErr1 != nil {
    95  		log.Fatalf("could not read buckets from db while getting list of buckets: %v", viewErr1)
    96  	}
    97  
    98  	// for every bucket, calculate the stats and display them.
    99  	// TODO: parallelize the execution
   100  	for _, bName := range buckets {
   101  		count := uint64(0)
   102  		minValueSize := ^uint64(0)
   103  		maxValueSize := uint64(0)
   104  		totalValueSize := uint64(0)
   105  		minKeySize := ^uint64(0)
   106  		maxKeySize := uint64(0)
   107  		totalKeySize := uint64(0)
   108  		if viewErr2 := db.View(func(tx *bolt.Tx) error {
   109  			b := tx.Bucket([]byte(bName))
   110  			if forEachErr := b.ForEach(func(k, v []byte) error {
   111  				count++
   112  				valueSize := uint64(len(v))
   113  				if valueSize < minValueSize {
   114  					minValueSize = valueSize
   115  				}
   116  				if valueSize > maxValueSize {
   117  					maxValueSize = valueSize
   118  				}
   119  				totalValueSize += valueSize
   120  
   121  				keyize := uint64(len(k))
   122  				if keyize < minKeySize {
   123  					minKeySize = keyize
   124  				}
   125  				if keyize > maxKeySize {
   126  					maxKeySize = keyize
   127  				}
   128  				totalKeySize += uint64(len(k))
   129  				return nil
   130  			}); forEachErr != nil {
   131  				log.Errorf("could not process row %d for bucket: %s, %v", count, bName, forEachErr)
   132  				return forEachErr
   133  			}
   134  			return nil
   135  		}); viewErr2 != nil {
   136  			log.Errorf("could not get stats for bucket: %s, %v", bName, viewErr2)
   137  			continue
   138  		}
   139  
   140  		if count != 0 {
   141  			averageValueSize := totalValueSize / count
   142  			averageKeySize := totalKeySize / count
   143  			fmt.Println("------ ", bName, " --------")
   144  			fmt.Println("NumberOfRows     = ", count)
   145  			fmt.Println("TotalBucketSize  = ", humanize.Bytes(totalValueSize+totalKeySize))
   146  			fmt.Println("KeySize          = ", humanize.Bytes(totalKeySize), "(min = "+humanize.Bytes(minKeySize)+", avg = "+humanize.Bytes(averageKeySize)+", max = "+humanize.Bytes(maxKeySize)+")")
   147  			fmt.Println("ValueSize        = ", humanize.Bytes(totalValueSize), "(min = "+humanize.Bytes(minValueSize)+", avg = "+humanize.Bytes(averageValueSize)+", max = "+humanize.Bytes(maxValueSize)+")")
   148  		}
   149  	}
   150  }
   151  
   152  func printBucketContents(dbNameWithPath string, rowLimit uint64, bucketName string) {
   153  	// get the keys within the supplied limit for the given bucket.
   154  	bucketNameInBytes := []byte(bucketName)
   155  	keys, sizes := keysOfBucket(dbNameWithPath, bucketNameInBytes, rowLimit)
   156  
   157  	// create a new KV Store.
   158  	dbDirectory := filepath.Dir(dbNameWithPath)
   159  	db, openErr := kv.NewKVStore(context.Background(), dbDirectory, &kv.Config{})
   160  	if openErr != nil {
   161  		log.Fatalf("could not open db, %v", openErr)
   162  	}
   163  
   164  	// dont forget to close it when ejecting out of this function.
   165  	defer func() {
   166  		closeErr := db.Close()
   167  		if closeErr != nil {
   168  			log.Fatalf("could not close db, %v", closeErr)
   169  		}
   170  	}()
   171  
   172  	// retrieve every element for keys in the list and call the respective display function.
   173  	ctx := context.Background()
   174  	rowCount := uint64(0)
   175  	for index, key := range keys {
   176  		switch bucketName {
   177  		case "state":
   178  			printState(ctx, db, key, rowCount, sizes[index])
   179  		case "state-summary":
   180  			printStateSummary(ctx, db, key, rowCount)
   181  		}
   182  		rowCount++
   183  	}
   184  }
   185  
   186  func printState(ctx context.Context, db *kv.Store, key []byte, rowCount, valueSize uint64) {
   187  	st, stateErr := db.State(ctx, bytesutil.ToBytes32(key))
   188  	if stateErr != nil {
   189  		log.Errorf("could not get state for key : , %v", stateErr)
   190  	}
   191  	rowStr := fmt.Sprintf("---- row = %04d ----", rowCount)
   192  	fmt.Println(rowStr)
   193  	fmt.Println("key                           :", key)
   194  	fmt.Println("value                         : compressed size = ", humanize.Bytes(valueSize))
   195  	fmt.Println("genesis_time                  :", st.GenesisTime())
   196  	fmt.Println("genesis_validators_root       :", hexutils.BytesToHex(st.GenesisValidatorRoot()))
   197  	fmt.Println("slot                          :", st.Slot())
   198  	fmt.Println("fork                          : previous_version: ", st.Fork().PreviousVersion, ",  current_version: ", st.Fork().CurrentVersion)
   199  	fmt.Println("latest_block_header           : sizeSSZ = ", humanize.Bytes(uint64(st.LatestBlockHeader().SizeSSZ())))
   200  	size, count := sizeAndCountOfByteList(st.BlockRoots())
   201  	fmt.Println("block_roots                   : size =  ", humanize.Bytes(size), ", count =  ", count)
   202  	size, count = sizeAndCountOfByteList(st.StateRoots())
   203  	fmt.Println("state_roots                   : size =  ", humanize.Bytes(size), ", count =  ", count)
   204  	size, count = sizeAndCountOfByteList(st.HistoricalRoots())
   205  	fmt.Println("historical_roots              : size =  ", humanize.Bytes(size), ", count =  ", count)
   206  	fmt.Println("eth1_data                     : sizeSSZ =  ", humanize.Bytes(uint64(st.Eth1Data().SizeSSZ())))
   207  	size, count = sizeAndCountGeneric(st.Eth1DataVotes(), nil)
   208  	fmt.Println("eth1_data_votes               : sizeSSZ = ", humanize.Bytes(size), ", count =  ", count)
   209  	fmt.Println("eth1_deposit_index            :", st.Eth1DepositIndex())
   210  	size, count = sizeAndCountGeneric(st.Validators(), nil)
   211  	fmt.Println("validators                    : sizeSSZ = ", humanize.Bytes(size), ", count =  ", count)
   212  	size, count = sizeAndCountOfUin64List(st.Balances())
   213  	fmt.Println("balances                      : size = ", humanize.Bytes(size), ", count =  ", count)
   214  	size, count = sizeAndCountOfByteList(st.RandaoMixes())
   215  	fmt.Println("randao_mixes                  : size = ", humanize.Bytes(size), ", count =  ", count)
   216  	size, count = sizeAndCountOfUin64List(st.Slashings())
   217  	fmt.Println("slashings                     : size =  ", humanize.Bytes(size), ", count =  ", count)
   218  	size, count = sizeAndCountGeneric(st.PreviousEpochAttestations())
   219  	fmt.Println("previous_epoch_attestations   : sizeSSZ ", humanize.Bytes(size), ", count =  ", count)
   220  	size, count = sizeAndCountGeneric(st.CurrentEpochAttestations())
   221  	fmt.Println("current_epoch_attestations    : sizeSSZ =  ", humanize.Bytes(size), ", count =  ", count)
   222  	fmt.Println("justification_bits            : size =  ", humanize.Bytes(st.JustificationBits().Len()), ", count =  ", st.JustificationBits().Count())
   223  	fmt.Println("previous_justified_checkpoint : sizeSSZ =  ", humanize.Bytes(uint64(st.PreviousJustifiedCheckpoint().SizeSSZ())))
   224  	fmt.Println("current_justified_checkpoint  : sizeSSZ =  ", humanize.Bytes(uint64(st.CurrentJustifiedCheckpoint().SizeSSZ())))
   225  	fmt.Println("finalized_checkpoint          : sizeSSZ =  ", humanize.Bytes(uint64(st.FinalizedCheckpoint().SizeSSZ())))
   226  }
   227  
   228  func printStateSummary(ctx context.Context, db *kv.Store, key []byte, rowCount uint64) {
   229  	ss, ssErr := db.StateSummary(ctx, bytesutil.ToBytes32(key))
   230  	if ssErr != nil {
   231  		log.Errorf("could not get state summary for key : , %v", ssErr)
   232  	}
   233  	rowCountStr := fmt.Sprintf("row : %04d, ", rowCount)
   234  	fmt.Println(rowCountStr, "slot : ", ss.Slot, ", root : ", hexutils.BytesToHex(ss.Root))
   235  }
   236  
   237  func keysOfBucket(dbNameWithPath string, bucketName []byte, rowLimit uint64) ([][]byte, []uint64) {
   238  	// open the raw database file. If the file is busy, then exit.
   239  	db, openErr := bolt.Open(dbNameWithPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
   240  	if openErr != nil {
   241  		log.Fatalf("could not open db while getting keys of a bucket, %v", openErr)
   242  	}
   243  
   244  	// make sure we close the database before ejecting out of this function.
   245  	defer func() {
   246  		closeErr := db.Close()
   247  		if closeErr != nil {
   248  			log.Fatalf("could not close db while getting keys of a bucket, %v", closeErr)
   249  		}
   250  	}()
   251  
   252  	// get all the keys of the given bucket.
   253  	var keys [][]byte
   254  	var sizes []uint64
   255  	if viewErr := db.View(func(tx *bolt.Tx) error {
   256  		b := tx.Bucket(bucketName)
   257  		c := b.Cursor()
   258  		count := uint64(0)
   259  		for k, v := c.First(); k != nil; k, v = c.Next() {
   260  			if count >= rowLimit {
   261  				return nil
   262  			}
   263  			keys = append(keys, k)
   264  			sizes = append(sizes, uint64(len(v)))
   265  			count++
   266  		}
   267  		return nil
   268  	}); viewErr != nil {
   269  		log.Fatalf("could not read keys of bucket from db: %v", viewErr)
   270  	}
   271  	return keys, sizes
   272  }
   273  
   274  func sizeAndCountOfByteList(list [][]byte) (uint64, uint64) {
   275  	size := uint64(0)
   276  	count := uint64(0)
   277  	for _, root := range list {
   278  		size += uint64(len(root))
   279  		count += 1
   280  	}
   281  	return size, count
   282  }
   283  
   284  func sizeAndCountOfUin64List(list []uint64) (uint64, uint64) {
   285  	size := uint64(0)
   286  	count := uint64(0)
   287  	for i := 0; i < len(list); i++ {
   288  		size += uint64(8)
   289  		count += 1
   290  	}
   291  	return size, count
   292  }
   293  
   294  func sizeAndCountGeneric(genericItems interface{}, err error) (uint64, uint64) {
   295  	size := uint64(0)
   296  	count := uint64(0)
   297  	if err != nil {
   298  		return size, count
   299  	}
   300  
   301  	switch items := genericItems.(type) {
   302  	case []*ethpb.Eth1Data:
   303  		for _, item := range items {
   304  			size += uint64(item.SizeSSZ())
   305  		}
   306  		count = uint64(len(items))
   307  	case []*ethpb.Validator:
   308  		for _, item := range items {
   309  			size += uint64(item.SizeSSZ())
   310  		}
   311  		count = uint64(len(items))
   312  	case []*pbp2p.PendingAttestation:
   313  		for _, item := range items {
   314  			size += uint64(item.SizeSSZ())
   315  		}
   316  		count = uint64(len(items))
   317  	default:
   318  		return 0, 0
   319  	}
   320  
   321  	return size, count
   322  }