inspector.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. package main
  2. import (
  3. "encoding/binary"
  4. "fmt"
  5. "io"
  6. "os"
  7. "text/tabwriter"
  8. "igit.com/xbase/raft/db"
  9. )
  10. const (
  11. RecordTypePut = 0x01
  12. RecordTypeDelete = 0x02
  13. // CRC(4) + Type(1) + KeyLen(2) + ValLen(4) + CommitIndex(8)
  14. HeaderSize = 19
  15. )
  16. func main() {
  17. if len(os.Args) < 2 {
  18. fmt.Println("Usage: go run main.go <data_dir>")
  19. return
  20. }
  21. dataDir := os.Args[1]
  22. dataFile := dataDir + "/values.data"
  23. f, err := os.Open(dataFile)
  24. if err != nil {
  25. fmt.Printf("Error opening file: %v\n", err)
  26. return
  27. }
  28. defer f.Close()
  29. fmt.Printf("Scanning storage file: %s\n\n", dataFile)
  30. w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.Debug)
  31. fmt.Fprintln(w, "Offset\tType\tCRC\tKeyLen\tValLen\tCommit\tKey\tValue")
  32. fmt.Fprintln(w, "------\t----\t---\t------\t------\t------\t---\t-----")
  33. offset := int64(0)
  34. reader := f
  35. for {
  36. // Read Header
  37. header := make([]byte, HeaderSize)
  38. if _, err := reader.ReadAt(header, offset); err != nil {
  39. if err == io.EOF {
  40. break
  41. }
  42. // If we hit EOF during read, it might be partial read at end
  43. if n, _ := reader.ReadAt(header, offset); n == 0 {
  44. break
  45. }
  46. fmt.Printf("Error reading header at offset %d: %v\n", offset, err)
  47. break
  48. }
  49. // Parse Header
  50. crc := binary.LittleEndian.Uint32(header[0:4])
  51. recType := header[4]
  52. keyLen := binary.LittleEndian.Uint16(header[5:7])
  53. valLen := binary.LittleEndian.Uint32(header[7:11])
  54. commitIndex := binary.LittleEndian.Uint64(header[11:19])
  55. typeStr := "PUT"
  56. if recType == RecordTypeDelete {
  57. typeStr = "DEL"
  58. } else if recType != RecordTypePut {
  59. typeStr = fmt.Sprintf("UNK(%d)", recType)
  60. }
  61. // Read Data (Key + Value)
  62. totalDataLen := int(keyLen) + int(valLen)
  63. data := make([]byte, totalDataLen)
  64. if _, err := reader.ReadAt(data, offset+HeaderSize); err != nil {
  65. fmt.Printf("Error reading data at offset %d: %v\n", offset+HeaderSize, err)
  66. break
  67. }
  68. key := string(data[:keyLen])
  69. val := string(data[keyLen:])
  70. // Truncate long values for display
  71. displayVal := val
  72. if len(displayVal) > 30 {
  73. displayVal = displayVal[:27] + "..."
  74. }
  75. if recType == RecordTypeDelete {
  76. displayVal = "<tombstone>"
  77. }
  78. fmt.Fprintf(w, "%d\t%s\t%08X\t%d\t%d\t%d\t%s\t%q\n",
  79. offset, typeStr, crc, keyLen, valLen, commitIndex, key, displayVal)
  80. offset += int64(HeaderSize + totalDataLen)
  81. }
  82. w.Flush()
  83. fmt.Println()
  84. // Also try to open Engine to verify Index Rebuild
  85. e, err := db.NewEngine(dataDir)
  86. if err == nil {
  87. fmt.Println("Engine Load Check: SUCCESS (Index rebuilt from disk)")
  88. e.Close()
  89. } else {
  90. fmt.Printf("Engine Load Check: FAILED (%v)\n", err)
  91. }
  92. }