github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/debug.go (about) 1 // Copyright 2016 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package cli 12 13 import ( 14 "bufio" 15 "bytes" 16 "context" 17 gohex "encoding/hex" 18 "fmt" 19 "io" 20 "math" 21 "os" 22 "path/filepath" 23 "regexp" 24 "sort" 25 "strconv" 26 "strings" 27 "time" 28 29 "github.com/cockroachdb/cockroach/pkg/base" 30 "github.com/cockroachdb/cockroach/pkg/cli/syncbench" 31 "github.com/cockroachdb/cockroach/pkg/config" 32 "github.com/cockroachdb/cockroach/pkg/config/zonepb" 33 "github.com/cockroachdb/cockroach/pkg/gossip" 34 "github.com/cockroachdb/cockroach/pkg/keys" 35 "github.com/cockroachdb/cockroach/pkg/kv/kvserver" 36 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/gc" 37 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverpb" 38 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/rditer" 39 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/stateloader" 40 "github.com/cockroachdb/cockroach/pkg/roachpb" 41 "github.com/cockroachdb/cockroach/pkg/server" 42 "github.com/cockroachdb/cockroach/pkg/server/serverpb" 43 "github.com/cockroachdb/cockroach/pkg/server/status/statuspb" 44 "github.com/cockroachdb/cockroach/pkg/sql/execinfrapb" 45 "github.com/cockroachdb/cockroach/pkg/storage" 46 "github.com/cockroachdb/cockroach/pkg/storage/enginepb" 47 "github.com/cockroachdb/cockroach/pkg/ts/tspb" 48 "github.com/cockroachdb/cockroach/pkg/util/envutil" 49 "github.com/cockroachdb/cockroach/pkg/util/flagutil" 50 "github.com/cockroachdb/cockroach/pkg/util/hlc" 51 "github.com/cockroachdb/cockroach/pkg/util/humanizeutil" 52 "github.com/cockroachdb/cockroach/pkg/util/log" 53 "github.com/cockroachdb/cockroach/pkg/util/protoutil" 54 "github.com/cockroachdb/cockroach/pkg/util/stop" 55 "github.com/cockroachdb/cockroach/pkg/util/sysutil" 56 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 57 "github.com/cockroachdb/cockroach/pkg/util/uuid" 58 "github.com/cockroachdb/errors" 59 "github.com/cockroachdb/pebble" 60 "github.com/cockroachdb/pebble/tool" 61 "github.com/gogo/protobuf/jsonpb" 62 "github.com/kr/pretty" 63 "github.com/spf13/cobra" 64 ) 65 66 var debugKeysCmd = &cobra.Command{ 67 Use: "keys <directory>", 68 Short: "dump all the keys in a store", 69 Long: ` 70 Pretty-prints all keys in a store. 71 `, 72 Args: cobra.ExactArgs(1), 73 RunE: MaybeDecorateGRPCError(runDebugKeys), 74 } 75 76 var debugBallastCmd = &cobra.Command{ 77 Use: "ballast <file>", 78 Short: "create a ballast file", 79 Long: ` 80 Create a ballast file to fill the store directory up to a given amount 81 `, 82 Args: cobra.ExactArgs(1), 83 RunE: runDebugBallast, 84 } 85 86 // PopulateRocksDBConfigHook is a callback set by CCL code. 87 // It populates any needed fields in the RocksDBConfig. 88 // It must do nothing in OSS code. 89 var PopulateRocksDBConfigHook func(*base.StorageConfig) error 90 91 func parsePositiveInt(arg string) (int64, error) { 92 i, err := strconv.ParseInt(arg, 10, 64) 93 if err != nil { 94 return 0, err 95 } 96 if i < 1 { 97 return 0, fmt.Errorf("illegal val: %d < 1", i) 98 } 99 return i, nil 100 } 101 102 func parseRangeID(arg string) (roachpb.RangeID, error) { 103 rangeIDInt, err := parsePositiveInt(arg) 104 if err != nil { 105 return 0, err 106 } 107 return roachpb.RangeID(rangeIDInt), nil 108 } 109 110 // OpenEngineOptions tunes the behavior of OpenEngine. 111 type OpenEngineOptions struct { 112 ReadOnly bool 113 MustExist bool 114 } 115 116 // OpenExistingStore opens the rocksdb engine rooted at 'dir'. 117 // If 'readOnly' is true, opens the store in read-only mode. 118 func OpenExistingStore(dir string, stopper *stop.Stopper, readOnly bool) (storage.Engine, error) { 119 return OpenEngine(dir, stopper, OpenEngineOptions{ReadOnly: readOnly, MustExist: true}) 120 } 121 122 // OpenEngine opens the engine at 'dir'. Depending on the supplied options, 123 // an empty engine might be initialized. 124 func OpenEngine(dir string, stopper *stop.Stopper, opts OpenEngineOptions) (storage.Engine, error) { 125 maxOpenFiles, err := server.SetOpenFileLimitForOneStore() 126 if err != nil { 127 return nil, err 128 } 129 130 storageConfig := base.StorageConfig{ 131 Settings: serverCfg.Settings, 132 Dir: dir, 133 MustExist: opts.MustExist, 134 } 135 if PopulateRocksDBConfigHook != nil { 136 if err := PopulateRocksDBConfigHook(&storageConfig); err != nil { 137 return nil, err 138 } 139 } 140 141 var db storage.Engine 142 storageEngine := resolveStorageEngineType(context.Background(), storage.DefaultStorageEngine, storageConfig) 143 144 switch storageEngine { 145 case enginepb.EngineTypePebble: 146 cfg := storage.PebbleConfig{ 147 StorageConfig: storageConfig, 148 Opts: storage.DefaultPebbleOptions(), 149 } 150 cfg.Opts.Cache = pebble.NewCache(server.DefaultCacheSize) 151 defer cfg.Opts.Cache.Unref() 152 153 cfg.Opts.MaxOpenFiles = int(maxOpenFiles) 154 cfg.Opts.ReadOnly = opts.ReadOnly 155 156 db, err = storage.NewPebble(context.Background(), cfg) 157 158 case enginepb.EngineTypeRocksDB: 159 cache := storage.NewRocksDBCache(server.DefaultCacheSize) 160 defer cache.Release() 161 162 cfg := storage.RocksDBConfig{ 163 StorageConfig: storageConfig, 164 MaxOpenFiles: maxOpenFiles, 165 ReadOnly: opts.ReadOnly, 166 } 167 168 db, err = storage.NewRocksDB(cfg, cache) 169 } 170 171 if err != nil { 172 return nil, err 173 } 174 175 stopper.AddCloser(db) 176 return db, nil 177 } 178 179 func printKey(kv storage.MVCCKeyValue) (bool, error) { 180 fmt.Printf("%s %s: ", kv.Key.Timestamp, kv.Key.Key) 181 if debugCtx.sizes { 182 fmt.Printf(" %d %d", len(kv.Key.Key), len(kv.Value)) 183 } 184 fmt.Printf("\n") 185 return false, nil 186 } 187 188 func runDebugKeys(cmd *cobra.Command, args []string) error { 189 stopper := stop.NewStopper() 190 defer stopper.Stop(context.Background()) 191 192 db, err := OpenExistingStore(args[0], stopper, true /* readOnly */) 193 if err != nil { 194 return err 195 } 196 197 printer := printKey 198 if debugCtx.values { 199 printer = func(kv storage.MVCCKeyValue) (bool, error) { 200 kvserver.PrintKeyValue(kv) 201 return false, nil 202 } 203 } 204 205 results := 0 206 return db.Iterate(debugCtx.startKey.Key, debugCtx.endKey.Key, func(kv storage.MVCCKeyValue) (bool, error) { 207 done, err := printer(kv) 208 if done || err != nil { 209 return done, err 210 } 211 results++ 212 return results == debugCtx.maxResults, nil 213 }) 214 } 215 216 func runDebugBallast(cmd *cobra.Command, args []string) error { 217 ballastFile := args[0] // we use cobra.ExactArgs(1) 218 dataDirectory := filepath.Dir(ballastFile) 219 220 fs, err := sysutil.StatFS(dataDirectory) 221 if err != nil { 222 return errors.Wrapf(err, "failed to stat filesystem %s", dataDirectory) 223 } 224 total := fs.TotalBlocks * fs.BlockSize 225 free := fs.AvailBlocks * fs.BlockSize 226 227 used := total - free 228 var targetUsage int64 229 p := debugCtx.ballastSize.Percent 230 if math.Abs(p) > 100 { 231 return errors.Errorf("absolute percentage value %f greater than 100", p) 232 } 233 b := debugCtx.ballastSize.InBytes 234 if p != 0 && b != 0 { 235 return errors.New("expected exactly one of percentage or bytes non-zero, found both") 236 } 237 switch { 238 case p > 0: 239 fillRatio := p / float64(100) 240 targetUsage = used + int64((fillRatio)*float64(total)) 241 case p < 0: 242 // Negative means leave the absolute %age of disk space. 243 fillRatio := 1.0 + (p / float64(100)) 244 targetUsage = int64((fillRatio) * float64(total)) 245 case b > 0: 246 targetUsage = used + b 247 case b < 0: 248 // Negative means leave that many bytes of disk space. 249 targetUsage = total + b 250 default: 251 return errors.New("expected exactly one of percentage or bytes non-zero, found none") 252 } 253 if used > targetUsage { 254 return errors.Errorf( 255 "Used space %s already more than needed to be filled %s\n", 256 humanizeutil.IBytes(used), 257 humanizeutil.IBytes(targetUsage), 258 ) 259 } 260 if used == targetUsage { 261 return nil 262 } 263 ballastSize := targetUsage - used 264 if err := sysutil.CreateLargeFile(ballastFile, ballastSize); err != nil { 265 return errors.Wrap(err, "failed to fallocate to ballast file") 266 } 267 return nil 268 } 269 270 var debugRangeDataCmd = &cobra.Command{ 271 Use: "range-data <directory> <range id>", 272 Short: "dump all the data in a range", 273 Long: ` 274 Pretty-prints all keys and values in a range. By default, includes unreplicated 275 state like the raft HardState. With --replicated, only includes data covered by 276 the consistency checker. 277 `, 278 Args: cobra.ExactArgs(2), 279 RunE: MaybeDecorateGRPCError(runDebugRangeData), 280 } 281 282 func runDebugRangeData(cmd *cobra.Command, args []string) error { 283 stopper := stop.NewStopper() 284 defer stopper.Stop(context.Background()) 285 286 db, err := OpenExistingStore(args[0], stopper, true /* readOnly */) 287 if err != nil { 288 return err 289 } 290 291 rangeID, err := parseRangeID(args[1]) 292 if err != nil { 293 return err 294 } 295 296 desc, err := loadRangeDescriptor(db, rangeID) 297 if err != nil { 298 return err 299 } 300 301 iter := rditer.NewReplicaDataIterator(&desc, db, debugCtx.replicated, false /* seekEnd */) 302 defer iter.Close() 303 results := 0 304 for ; ; iter.Next() { 305 if ok, err := iter.Valid(); err != nil { 306 return err 307 } else if !ok { 308 break 309 } 310 kvserver.PrintKeyValue(storage.MVCCKeyValue{ 311 Key: iter.Key(), 312 Value: iter.Value(), 313 }) 314 results++ 315 if results == debugCtx.maxResults { 316 break 317 } 318 } 319 return nil 320 } 321 322 var debugRangeDescriptorsCmd = &cobra.Command{ 323 Use: "range-descriptors <directory>", 324 Short: "print all range descriptors in a store", 325 Long: ` 326 Prints all range descriptors in a store with a history of changes. 327 `, 328 Args: cobra.ExactArgs(1), 329 RunE: MaybeDecorateGRPCError(runDebugRangeDescriptors), 330 } 331 332 func loadRangeDescriptor( 333 db storage.Engine, rangeID roachpb.RangeID, 334 ) (roachpb.RangeDescriptor, error) { 335 var desc roachpb.RangeDescriptor 336 handleKV := func(kv storage.MVCCKeyValue) (bool, error) { 337 if kv.Key.Timestamp == (hlc.Timestamp{}) { 338 // We only want values, not MVCCMetadata. 339 return false, nil 340 } 341 if err := kvserver.IsRangeDescriptorKey(kv.Key); err != nil { 342 // Range descriptor keys are interleaved with others, so if it 343 // doesn't parse as a range descriptor just skip it. 344 return false, nil //nolint:returnerrcheck 345 } 346 if len(kv.Value) == 0 { 347 // RangeDescriptor was deleted (range merged away). 348 return false, nil 349 } 350 if err := (roachpb.Value{RawBytes: kv.Value}).GetProto(&desc); err != nil { 351 log.Warningf(context.Background(), "ignoring range descriptor due to error %s: %+v", err, kv) 352 return false, nil 353 } 354 return desc.RangeID == rangeID, nil 355 } 356 357 // Range descriptors are stored by key, so we have to scan over the 358 // range-local data to find the one for this RangeID. 359 start := keys.LocalRangePrefix 360 end := keys.LocalRangeMax 361 362 if err := db.Iterate(start, end, handleKV); err != nil { 363 return roachpb.RangeDescriptor{}, err 364 } 365 if desc.RangeID == rangeID { 366 return desc, nil 367 } 368 return roachpb.RangeDescriptor{}, fmt.Errorf("range descriptor %d not found", rangeID) 369 } 370 371 func runDebugRangeDescriptors(cmd *cobra.Command, args []string) error { 372 stopper := stop.NewStopper() 373 defer stopper.Stop(context.Background()) 374 375 db, err := OpenExistingStore(args[0], stopper, true /* readOnly */) 376 if err != nil { 377 return err 378 } 379 380 start := keys.LocalRangePrefix 381 end := keys.LocalRangeMax 382 383 return db.Iterate(start, end, func(kv storage.MVCCKeyValue) (bool, error) { 384 if kvserver.IsRangeDescriptorKey(kv.Key) != nil { 385 return false, nil 386 } 387 kvserver.PrintKeyValue(kv) 388 return false, nil 389 }) 390 } 391 392 var debugDecodeKeyCmd = &cobra.Command{ 393 Use: "decode-key", 394 Short: "decode <key>", 395 Long: ` 396 Decode a hexadecimal-encoded key and pretty-print it. For example: 397 398 $ decode-key BB89F902ADB43000151C2D1ED07DE6C009 399 /Table/51/1/44938288/1521140384.514565824,0 400 `, 401 Args: cobra.ArbitraryArgs, 402 RunE: func(cmd *cobra.Command, args []string) error { 403 for _, arg := range args { 404 b, err := gohex.DecodeString(arg) 405 if err != nil { 406 return err 407 } 408 k, err := storage.DecodeMVCCKey(b) 409 if err != nil { 410 return err 411 } 412 fmt.Println(k) 413 } 414 return nil 415 }, 416 } 417 418 var debugDecodeValueCmd = &cobra.Command{ 419 Use: "decode-value", 420 Short: "decode-value <key> <value>", 421 Long: ` 422 Decode and print a hexadecimal-encoded key-value pair. 423 424 $ decode-value <TBD> <TBD> 425 <TBD> 426 `, 427 Args: cobra.ExactArgs(2), 428 RunE: func(cmd *cobra.Command, args []string) error { 429 var bs [][]byte 430 for _, arg := range args { 431 b, err := gohex.DecodeString(arg) 432 if err != nil { 433 return err 434 } 435 bs = append(bs, b) 436 } 437 438 isTS := bytes.HasPrefix(bs[0], keys.TimeseriesPrefix) 439 k, err := storage.DecodeMVCCKey(bs[0]) 440 if err != nil { 441 // Older versions of the consistency checker give you diffs with a raw_key that 442 // is already a roachpb.Key, so make a half-assed attempt to support both. 443 if !isTS { 444 fmt.Printf("unable to decode key: %v, assuming it's a roachpb.Key with fake timestamp;\n"+ 445 "if the result below looks like garbage, then it likely is:\n\n", err) 446 } 447 k = storage.MVCCKey{ 448 Key: bs[0], 449 Timestamp: hlc.Timestamp{WallTime: 987654321}, 450 } 451 } 452 453 kvserver.PrintKeyValue(storage.MVCCKeyValue{ 454 Key: k, 455 Value: bs[1], 456 }) 457 return nil 458 }, 459 } 460 461 var debugRaftLogCmd = &cobra.Command{ 462 Use: "raft-log <directory> <range id>", 463 Short: "print the raft log for a range", 464 Long: ` 465 Prints all log entries in a store for the given range. 466 `, 467 Args: cobra.ExactArgs(2), 468 RunE: MaybeDecorateGRPCError(runDebugRaftLog), 469 } 470 471 func runDebugRaftLog(cmd *cobra.Command, args []string) error { 472 stopper := stop.NewStopper() 473 defer stopper.Stop(context.Background()) 474 475 db, err := OpenExistingStore(args[0], stopper, true /* readOnly */) 476 if err != nil { 477 return err 478 } 479 480 rangeID, err := parseRangeID(args[1]) 481 if err != nil { 482 return err 483 } 484 485 start := keys.RaftLogPrefix(rangeID) 486 end := keys.RaftLogPrefix(rangeID).PrefixEnd() 487 fmt.Printf("Printing keys %s -> %s (RocksDB keys: %#x - %#x )\n", 488 start, end, 489 string(storage.EncodeKey(storage.MakeMVCCMetadataKey(start))), 490 string(storage.EncodeKey(storage.MakeMVCCMetadataKey(end)))) 491 492 return db.Iterate(start, end, func(kv storage.MVCCKeyValue) (bool, error) { 493 kvserver.PrintKeyValue(kv) 494 return false, nil 495 }) 496 } 497 498 var debugGCCmd = &cobra.Command{ 499 Use: "estimate-gc <directory> [range id] [ttl-in-seconds]", 500 Short: "find out what a GC run would do", 501 Long: ` 502 Sets up (but does not run) a GC collection cycle, giving insight into how much 503 work would be done (assuming all intent resolution and pushes succeed). 504 505 Without a RangeID specified on the command line, runs the analysis for all 506 ranges individually. 507 508 Uses a configurable GC policy, with a default 24 hour TTL, for old versions. 509 `, 510 Args: cobra.RangeArgs(1, 2), 511 RunE: MaybeDecorateGRPCError(runDebugGCCmd), 512 } 513 514 func runDebugGCCmd(cmd *cobra.Command, args []string) error { 515 stopper := stop.NewStopper() 516 defer stopper.Stop(context.Background()) 517 518 var rangeID roachpb.RangeID 519 gcTTLInSeconds := int64((24 * time.Hour).Seconds()) 520 switch len(args) { 521 case 3: 522 var err error 523 if rangeID, err = parseRangeID(args[1]); err != nil { 524 return errors.Wrapf(err, "unable to parse %v as range ID", args[1]) 525 } 526 if gcTTLInSeconds, err = parsePositiveInt(args[2]); err != nil { 527 return errors.Wrapf(err, "unable to parse %v as TTL", args[2]) 528 } 529 530 case 2: 531 var err error 532 if rangeID, err = parseRangeID(args[1]); err != nil { 533 return err 534 } 535 } 536 537 db, err := OpenExistingStore(args[0], stopper, true /* readOnly */) 538 if err != nil { 539 return err 540 } 541 542 start := keys.RangeDescriptorKey(roachpb.RKeyMin) 543 end := keys.RangeDescriptorKey(roachpb.RKeyMax) 544 545 var descs []roachpb.RangeDescriptor 546 547 if _, err := storage.MVCCIterate(context.Background(), db, start, end, hlc.MaxTimestamp, 548 storage.MVCCScanOptions{Inconsistent: true}, func(kv roachpb.KeyValue) (bool, error) { 549 var desc roachpb.RangeDescriptor 550 _, suffix, _, err := keys.DecodeRangeKey(kv.Key) 551 if err != nil { 552 return false, err 553 } 554 if !bytes.Equal(suffix, keys.LocalRangeDescriptorSuffix) { 555 return false, nil 556 } 557 if err := kv.Value.GetProto(&desc); err != nil { 558 return false, err 559 } 560 if desc.RangeID == rangeID || rangeID == 0 { 561 descs = append(descs, desc) 562 } 563 return desc.RangeID == rangeID, nil 564 }); err != nil { 565 return err 566 } 567 568 if len(descs) == 0 { 569 return fmt.Errorf("no range matching the criteria found") 570 } 571 572 for _, desc := range descs { 573 snap := db.NewSnapshot() 574 defer snap.Close() 575 policy := zonepb.GCPolicy{TTLSeconds: int32(gcTTLInSeconds)} 576 now := hlc.Timestamp{WallTime: timeutil.Now().UnixNano()} 577 thresh := gc.CalculateThreshold(now, policy) 578 info, err := gc.Run( 579 context.Background(), 580 &desc, snap, 581 now, thresh, policy, 582 gc.NoopGCer{}, 583 func(_ context.Context, _ []roachpb.Intent) error { return nil }, 584 func(_ context.Context, _ *roachpb.Transaction, _ []roachpb.LockUpdate) error { return nil }, 585 ) 586 if err != nil { 587 return err 588 } 589 fmt.Printf("RangeID: %d [%s, %s):\n", desc.RangeID, desc.StartKey, desc.EndKey) 590 _, _ = pretty.Println(info) 591 } 592 return nil 593 } 594 595 var debugRocksDBCmd = &cobra.Command{ 596 Use: "rocksdb", 597 Short: "run the RocksDB 'ldb' tool", 598 Long: ` 599 Runs the RocksDB 'ldb' tool, which provides various subcommands for examining 600 raw store data. 'cockroach debug rocksdb' accepts the same arguments and flags 601 as 'ldb'. 602 603 https://github.com/facebook/rocksdb/wiki/Administration-and-Data-Access-Tool#ldb-tool 604 `, 605 // LDB does its own flag parsing. 606 // TODO(mberhault): support encrypted stores. 607 DisableFlagParsing: true, 608 Run: func(cmd *cobra.Command, args []string) { 609 storage.RunLDB(args) 610 }, 611 } 612 613 var debugPebbleCmd = &cobra.Command{ 614 Use: "pebble [command]", 615 Short: "run a Pebble introspection tool command", 616 Long: ` 617 Allows the use of pebble tools, such as to introspect manifests, SSTables, etc. 618 `, 619 } 620 621 var debugSSTDumpCmd = &cobra.Command{ 622 Use: "sst_dump", 623 Short: "run the RocksDB 'sst_dump' tool", 624 Long: ` 625 Runs the RocksDB 'sst_dump' tool 626 `, 627 // sst_dump does its own flag parsing. 628 // TODO(mberhault): support encrypted stores. 629 DisableFlagParsing: true, 630 Run: func(cmd *cobra.Command, args []string) { 631 storage.RunSSTDump(args) 632 }, 633 } 634 635 var debugEnvCmd = &cobra.Command{ 636 Use: "env", 637 Short: "output environment settings", 638 Long: ` 639 Output environment variables that influence configuration. 640 `, 641 Args: cobra.NoArgs, 642 Run: func(cmd *cobra.Command, args []string) { 643 env := envutil.GetEnvReport() 644 fmt.Print(env) 645 }, 646 } 647 648 var debugCompactCmd = &cobra.Command{ 649 Use: "compact <directory>", 650 Short: "compact the sstables in a store", 651 Long: ` 652 Compact the sstables in a store. 653 `, 654 Args: cobra.ExactArgs(1), 655 RunE: MaybeDecorateGRPCError(runDebugCompact), 656 } 657 658 func runDebugCompact(cmd *cobra.Command, args []string) error { 659 stopper := stop.NewStopper() 660 defer stopper.Stop(context.Background()) 661 662 db, err := OpenExistingStore(args[0], stopper, false /* readOnly */) 663 if err != nil { 664 return err 665 } 666 667 { 668 approxBytesBefore, err := db.ApproximateDiskBytes(roachpb.KeyMin, roachpb.KeyMax) 669 if err != nil { 670 return errors.Wrap(err, "while computing approximate size before compaction") 671 } 672 fmt.Printf("approximate reported database size before compaction: %s\n", humanizeutil.IBytes(int64(approxBytesBefore))) 673 } 674 675 if err := db.Compact(); err != nil { 676 return errors.Wrap(err, "while compacting") 677 } 678 679 { 680 approxBytesAfter, err := db.ApproximateDiskBytes(roachpb.KeyMin, roachpb.KeyMax) 681 if err != nil { 682 return errors.Wrap(err, "while computing approximate size after compaction") 683 } 684 fmt.Printf("approximate reported database size after compaction: %s\n", humanizeutil.IBytes(int64(approxBytesAfter))) 685 } 686 return nil 687 } 688 689 var debugSSTablesCmd = &cobra.Command{ 690 Use: "sstables <directory>", 691 Short: "list the sstables in a store", 692 Long: ` 693 694 List the sstables in a store. The output format is 1 or more lines of: 695 696 level [ total size #files ]: file sizes 697 698 Only non-empty levels are shown. For levels greater than 0, the files span 699 non-overlapping ranges of the key space. Level-0 is special in that sstables 700 are created there by flushing the mem-table, thus every level-0 sstable must be 701 consulted to see if it contains a particular key. Within a level, the file 702 sizes are displayed in decreasing order and bucketed by the number of files of 703 that size. The following example shows 3-level output. In Level-3, there are 19 704 total files and 14 files that are 129 MiB in size. 705 706 1 [ 8M 3 ]: 7M 1M 63K 707 2 [ 110M 7 ]: 31M 30M 13M[2] 10M 8M 5M 708 3 [ 2G 19 ]: 129M[14] 122M 93M 24M 18M 9M 709 710 The suffixes K, M, G and T are used for terseness to represent KiB, MiB, GiB 711 and TiB. 712 `, 713 Args: cobra.ExactArgs(1), 714 RunE: MaybeDecorateGRPCError(runDebugSSTables), 715 } 716 717 func runDebugSSTables(cmd *cobra.Command, args []string) error { 718 stopper := stop.NewStopper() 719 defer stopper.Stop(context.Background()) 720 721 db, err := OpenExistingStore(args[0], stopper, true /* readOnly */) 722 if err != nil { 723 return err 724 } 725 726 fmt.Printf("%s", db.GetSSTables()) 727 return nil 728 } 729 730 var debugGossipValuesCmd = &cobra.Command{ 731 Use: "gossip-values", 732 Short: "dump all the values in a node's gossip instance", 733 Long: ` 734 Pretty-prints the values in a node's gossip instance. 735 736 Can connect to a running server to get the values or can be provided with 737 a JSON file captured from a node's /_status/gossip/ debug endpoint. 738 `, 739 Args: cobra.NoArgs, 740 RunE: MaybeDecorateGRPCError(runDebugGossipValues), 741 } 742 743 func runDebugGossipValues(cmd *cobra.Command, args []string) error { 744 ctx, cancel := context.WithCancel(context.Background()) 745 defer cancel() 746 // If a file is provided, use it. Otherwise, try talking to the running node. 747 var gossipInfo *gossip.InfoStatus 748 if debugCtx.inputFile != "" { 749 file, err := os.Open(debugCtx.inputFile) 750 if err != nil { 751 return err 752 } 753 defer file.Close() 754 gossipInfo = new(gossip.InfoStatus) 755 if err := jsonpb.Unmarshal(file, gossipInfo); err != nil { 756 return errors.Wrap(err, "failed to parse provided file as gossip.InfoStatus") 757 } 758 } else { 759 conn, _, finish, err := getClientGRPCConn(ctx, serverCfg) 760 if err != nil { 761 return err 762 } 763 defer finish() 764 765 status := serverpb.NewStatusClient(conn) 766 gossipInfo, err = status.Gossip(ctx, &serverpb.GossipRequest{}) 767 if err != nil { 768 return errors.Wrap(err, "failed to retrieve gossip from server") 769 } 770 } 771 772 output, err := parseGossipValues(gossipInfo) 773 if err != nil { 774 return err 775 } 776 fmt.Println(output) 777 return nil 778 } 779 780 func parseGossipValues(gossipInfo *gossip.InfoStatus) (string, error) { 781 var output []string 782 for key, info := range gossipInfo.Infos { 783 bytes, err := info.Value.GetBytes() 784 if err != nil { 785 return "", errors.Wrapf(err, "failed to extract bytes for key %q", key) 786 } 787 if key == gossip.KeyClusterID || key == gossip.KeySentinel { 788 clusterID, err := uuid.FromBytes(bytes) 789 if err != nil { 790 return "", errors.Wrapf(err, "failed to parse value for key %q", key) 791 } 792 output = append(output, fmt.Sprintf("%q: %v", key, clusterID)) 793 } else if key == gossip.KeySystemConfig { 794 if debugCtx.printSystemConfig { 795 var config config.SystemConfigEntries 796 if err := protoutil.Unmarshal(bytes, &config); err != nil { 797 return "", errors.Wrapf(err, "failed to parse value for key %q", key) 798 } 799 output = append(output, fmt.Sprintf("%q: %+v", key, config)) 800 } else { 801 output = append(output, fmt.Sprintf("%q: omitted", key)) 802 } 803 } else if key == gossip.KeyFirstRangeDescriptor { 804 var desc roachpb.RangeDescriptor 805 if err := protoutil.Unmarshal(bytes, &desc); err != nil { 806 return "", errors.Wrapf(err, "failed to parse value for key %q", key) 807 } 808 output = append(output, fmt.Sprintf("%q: %v", key, desc)) 809 } else if gossip.IsNodeIDKey(key) { 810 var desc roachpb.NodeDescriptor 811 if err := protoutil.Unmarshal(bytes, &desc); err != nil { 812 return "", errors.Wrapf(err, "failed to parse value for key %q", key) 813 } 814 output = append(output, fmt.Sprintf("%q: %+v", key, desc)) 815 } else if strings.HasPrefix(key, gossip.KeyStorePrefix) { 816 var desc roachpb.StoreDescriptor 817 if err := protoutil.Unmarshal(bytes, &desc); err != nil { 818 return "", errors.Wrapf(err, "failed to parse value for key %q", key) 819 } 820 output = append(output, fmt.Sprintf("%q: %+v", key, desc)) 821 } else if strings.HasPrefix(key, gossip.KeyNodeLivenessPrefix) { 822 var liveness kvserverpb.Liveness 823 if err := protoutil.Unmarshal(bytes, &liveness); err != nil { 824 return "", errors.Wrapf(err, "failed to parse value for key %q", key) 825 } 826 output = append(output, fmt.Sprintf("%q: %+v", key, liveness)) 827 } else if strings.HasPrefix(key, gossip.KeyNodeHealthAlertPrefix) { 828 var healthAlert statuspb.HealthCheckResult 829 if err := protoutil.Unmarshal(bytes, &healthAlert); err != nil { 830 return "", errors.Wrapf(err, "failed to parse value for key %q", key) 831 } 832 output = append(output, fmt.Sprintf("%q: %+v", key, healthAlert)) 833 } else if strings.HasPrefix(key, gossip.KeyDistSQLNodeVersionKeyPrefix) { 834 var version execinfrapb.DistSQLVersionGossipInfo 835 if err := protoutil.Unmarshal(bytes, &version); err != nil { 836 return "", errors.Wrapf(err, "failed to parse value for key %q", key) 837 } 838 output = append(output, fmt.Sprintf("%q: %+v", key, version)) 839 } else if strings.HasPrefix(key, gossip.KeyDistSQLDrainingPrefix) { 840 var drainingInfo execinfrapb.DistSQLDrainingInfo 841 if err := protoutil.Unmarshal(bytes, &drainingInfo); err != nil { 842 return "", errors.Wrapf(err, "failed to parse value for key %q", key) 843 } 844 output = append(output, fmt.Sprintf("%q: %+v", key, drainingInfo)) 845 } else if strings.HasPrefix(key, gossip.KeyTableStatAddedPrefix) { 846 gossipedTime := timeutil.Unix(0, info.OrigStamp) 847 output = append(output, fmt.Sprintf("%q: %v", key, gossipedTime)) 848 } else if strings.HasPrefix(key, gossip.KeyGossipClientsPrefix) { 849 output = append(output, fmt.Sprintf("%q: %v", key, string(bytes))) 850 } 851 } 852 853 sort.Strings(output) 854 return strings.Join(output, "\n"), nil 855 } 856 857 var debugTimeSeriesDumpCmd = &cobra.Command{ 858 Use: "tsdump", 859 Short: "dump all the raw timeseries values in a cluster", 860 Long: ` 861 Dumps all of the raw timeseries values in a cluster. 862 `, 863 RunE: MaybeDecorateGRPCError(runTimeSeriesDump), 864 } 865 866 func runTimeSeriesDump(cmd *cobra.Command, args []string) error { 867 ctx, cancel := context.WithCancel(context.Background()) 868 defer cancel() 869 870 conn, _, finish, err := getClientGRPCConn(ctx, serverCfg) 871 if err != nil { 872 return err 873 } 874 defer finish() 875 876 tsClient := tspb.NewTimeSeriesClient(conn) 877 stream, err := tsClient.Dump(context.Background(), &tspb.DumpRequest{}) 878 if err != nil { 879 log.Fatalf(context.Background(), "%v", err) 880 } 881 882 var name, source string 883 for { 884 data, err := stream.Recv() 885 if err == io.EOF { 886 return nil 887 } 888 if err != nil { 889 return err 890 } 891 if name != data.Name || source != data.Source { 892 name, source = data.Name, data.Source 893 fmt.Printf("%s %s\n", name, source) 894 } 895 for _, d := range data.Datapoints { 896 fmt.Printf("%d %v\n", d.TimestampNanos, d.Value) 897 } 898 } 899 } 900 901 var debugSyncBenchCmd = &cobra.Command{ 902 Use: "syncbench [directory]", 903 Short: "Run a performance test for WAL sync speed", 904 Long: ` 905 `, 906 Args: cobra.MaximumNArgs(1), 907 Hidden: true, 908 RunE: MaybeDecorateGRPCError(runDebugSyncBench), 909 } 910 911 var syncBenchOpts = syncbench.Options{ 912 Concurrency: 1, 913 Duration: 10 * time.Second, 914 LogOnly: true, 915 } 916 917 func runDebugSyncBench(cmd *cobra.Command, args []string) error { 918 syncBenchOpts.Dir = "./testdb" 919 if len(args) == 1 { 920 syncBenchOpts.Dir = args[0] 921 } 922 return syncbench.Run(syncBenchOpts) 923 } 924 925 var debugUnsafeRemoveDeadReplicasCmd = &cobra.Command{ 926 Use: "unsafe-remove-dead-replicas --dead-store-ids=[store ID,...] [path]", 927 Short: "Unsafely remove all other replicas from the given range", 928 Long: ` 929 930 This command is UNSAFE and should only be used with the supervision of 931 a Cockroach Labs engineer. It is a last-resort option to recover data 932 after multiple node failures. The recovered data is not guaranteed to 933 be consistent. 934 935 The --dead-store-ids flag takes a comma-separated list of dead store 936 IDs and scans this store for any ranges whose only live replica is on 937 this store. These range descriptors will be edited to forcibly remove 938 the dead stores, allowing the range to recover from this single 939 replica. 940 941 Must only be used when the dead stores are lost and unrecoverable. If 942 the dead stores were to rejoin the cluster after this command was 943 used, data may be corrupted. 944 945 This comand will prompt for confirmation before committing its changes. 946 947 After this command is used, the node should not be restarted until at 948 least 10 seconds have passed since it was stopped. Restarting it too 949 early may lead to things getting stuck (if it happens, it can be fixed 950 by restarting a second time). 951 `, 952 Args: cobra.ExactArgs(1), 953 RunE: MaybeDecorateGRPCError(runDebugUnsafeRemoveDeadReplicas), 954 } 955 956 var removeDeadReplicasOpts struct { 957 deadStoreIDs []int 958 } 959 960 func runDebugUnsafeRemoveDeadReplicas(cmd *cobra.Command, args []string) error { 961 stopper := stop.NewStopper() 962 defer stopper.Stop(context.Background()) 963 964 db, err := OpenExistingStore(args[0], stopper, false /* readOnly */) 965 if err != nil { 966 return err 967 } 968 969 deadStoreIDs := map[roachpb.StoreID]struct{}{} 970 for _, id := range removeDeadReplicasOpts.deadStoreIDs { 971 deadStoreIDs[roachpb.StoreID(id)] = struct{}{} 972 } 973 batch, err := removeDeadReplicas(db, deadStoreIDs) 974 if err != nil { 975 return err 976 } else if batch == nil { 977 fmt.Printf("Nothing to do\n") 978 return nil 979 } 980 defer batch.Close() 981 982 fmt.Printf("Proceed with the above rewrites? [y/N] ") 983 984 reader := bufio.NewReader(os.Stdin) 985 line, err := reader.ReadString('\n') 986 if err != nil { 987 return err 988 } 989 fmt.Printf("\n") 990 if line[0] == 'y' || line[0] == 'Y' { 991 fmt.Printf("Committing\n") 992 if err := batch.Commit(true); err != nil { 993 return err 994 } 995 } else { 996 fmt.Printf("Aborting\n") 997 } 998 return nil 999 } 1000 1001 func removeDeadReplicas( 1002 db storage.Engine, deadStoreIDs map[roachpb.StoreID]struct{}, 1003 ) (storage.Batch, error) { 1004 clock := hlc.NewClock(hlc.UnixNano, 0) 1005 1006 ctx := context.Background() 1007 1008 storeIdent, err := kvserver.ReadStoreIdent(ctx, db) 1009 if err != nil { 1010 return nil, err 1011 } 1012 fmt.Printf("Scanning replicas on store %s for dead peers %v\n", storeIdent.String(), 1013 removeDeadReplicasOpts.deadStoreIDs) 1014 1015 if _, ok := deadStoreIDs[storeIdent.StoreID]; ok { 1016 return nil, errors.Errorf("This store's ID (%s) marked as dead, aborting", storeIdent.StoreID) 1017 } 1018 1019 var newDescs []roachpb.RangeDescriptor 1020 1021 err = kvserver.IterateRangeDescriptors(ctx, db, func(desc roachpb.RangeDescriptor) (bool, error) { 1022 hasSelf := false 1023 numDeadPeers := 0 1024 allReplicas := desc.Replicas().All() 1025 maxLivePeer := roachpb.StoreID(-1) 1026 for _, rep := range allReplicas { 1027 if rep.StoreID == storeIdent.StoreID { 1028 hasSelf = true 1029 } 1030 if _, ok := deadStoreIDs[rep.StoreID]; ok { 1031 numDeadPeers++ 1032 } else { 1033 if rep.StoreID > maxLivePeer { 1034 maxLivePeer = rep.StoreID 1035 } 1036 } 1037 } 1038 if hasSelf && numDeadPeers > 0 && storeIdent.StoreID == maxLivePeer { 1039 canMakeProgress := desc.Replicas().CanMakeProgress(func(rep roachpb.ReplicaDescriptor) bool { 1040 _, ok := deadStoreIDs[rep.StoreID] 1041 return !ok 1042 }) 1043 if canMakeProgress { 1044 return false, nil 1045 } 1046 1047 // Rewrite the range as having a single replica. The winning 1048 // replica is picked arbitrarily: the one with the highest store 1049 // ID. This is not always the best option: it may lose writes 1050 // that were committed on another surviving replica that had 1051 // applied more of the raft log. However, in practice when we 1052 // have multiple surviving replicas but still need this tool 1053 // (because the replication factor was 4 or higher), we see that 1054 // the logs are nearly always in sync and the choice doesn't 1055 // matter. Correctly picking the replica with the longer log 1056 // would complicate the use of this tool. 1057 newDesc := desc 1058 // Rewrite the replicas list. Bump the replica ID so that in 1059 // case there are other surviving nodes that were members of the 1060 // old incarnation of the range, they no longer recognize this 1061 // revived replica (because they are not in sync with it). 1062 replicas := []roachpb.ReplicaDescriptor{{ 1063 NodeID: storeIdent.NodeID, 1064 StoreID: storeIdent.StoreID, 1065 ReplicaID: desc.NextReplicaID, 1066 }} 1067 newDesc.SetReplicas(roachpb.MakeReplicaDescriptors(replicas)) 1068 newDesc.NextReplicaID++ 1069 fmt.Printf("Replica %s -> %s\n", &desc, &newDesc) 1070 newDescs = append(newDescs, newDesc) 1071 } 1072 return false, nil 1073 }) 1074 if err != nil { 1075 return nil, err 1076 } 1077 1078 if len(newDescs) == 0 { 1079 return nil, nil 1080 } 1081 1082 batch := db.NewBatch() 1083 for _, desc := range newDescs { 1084 // Write the rewritten descriptor to the range-local descriptor 1085 // key. We do not update the meta copies of the descriptor. 1086 // Instead, we leave them in a temporarily inconsistent state and 1087 // they will be overwritten when the cluster recovers and 1088 // up-replicates this range from its single copy to multiple 1089 // copies. We rely on the fact that all range descriptor updates 1090 // start with a CPut on the range-local copy followed by a blind 1091 // Put to the meta copy. 1092 // 1093 // For example, if we have replicas on s1-s4 but s3 and s4 are 1094 // dead, we will rewrite the replica on s2 to have s2 as its only 1095 // member only. When the cluster is restarted (and the dead nodes 1096 // remain dead), the rewritten replica will be the only one able 1097 // to make progress. It will elect itself leader and upreplicate. 1098 // 1099 // The old replica on s1 is untouched by this process. It will 1100 // eventually either be overwritten by a new replica when s2 1101 // upreplicates, or it will be destroyed by the replica GC queue 1102 // after upreplication has happened and s1 is no longer a member. 1103 // (Note that in the latter case, consistency between s1 and s2 no 1104 // longer matters; the consistency checker will only run on nodes 1105 // that the new leader believes are members of the range). 1106 // 1107 // Note that this tool does not guarantee fully consistent 1108 // results; the most recent writes to the raft log may have been 1109 // lost. In the most unfortunate cases, this means that we would 1110 // be "winding back" a split or a merge, which is almost certainly 1111 // to result in irrecoverable corruption (for example, not only 1112 // will individual values stored in the meta ranges diverge, but 1113 // there will be keys not represented by any ranges or vice 1114 // versa). 1115 key := keys.RangeDescriptorKey(desc.StartKey) 1116 sl := stateloader.Make(desc.RangeID) 1117 ms, err := sl.LoadMVCCStats(ctx, batch) 1118 if err != nil { 1119 return nil, errors.Wrap(err, "loading MVCCStats") 1120 } 1121 err = storage.MVCCPutProto(ctx, batch, &ms, key, clock.Now(), nil /* txn */, &desc) 1122 if wiErr := (*roachpb.WriteIntentError)(nil); errors.As(err, &wiErr) { 1123 if len(wiErr.Intents) != 1 { 1124 return nil, errors.Errorf("expected 1 intent, found %d: %s", len(wiErr.Intents), wiErr) 1125 } 1126 intent := wiErr.Intents[0] 1127 // We rely on the property that transactions involving the range 1128 // descriptor always start on the range-local descriptor's key. 1129 // This guarantees that when the transaction commits, the intent 1130 // will be resolved synchronously. If we see an intent on this 1131 // key, we know that the transaction did not commit and we can 1132 // abort it. 1133 // 1134 // TODO(nvanbenschoten): This need updating for parallel 1135 // commits. If the transaction record is in the STAGING state, 1136 // we can't just delete it. Simplest solution to this is to 1137 // avoid parallel commits for membership change transactions; if 1138 // we can't do that I don't think we'll be able to recover them 1139 // with an offline tool. 1140 fmt.Printf("Conflicting intent found on %s. Aborting txn %s to resolve.\n", key, intent.Txn.ID) 1141 1142 // A crude form of the intent resolution process: abort the 1143 // transaction by deleting its record. 1144 txnKey := keys.TransactionKey(intent.Txn.Key, intent.Txn.ID) 1145 if err := storage.MVCCDelete(ctx, batch, &ms, txnKey, hlc.Timestamp{}, nil); err != nil { 1146 return nil, err 1147 } 1148 update := roachpb.LockUpdate{ 1149 Span: roachpb.Span{Key: intent.Key}, 1150 Txn: intent.Txn, 1151 Status: roachpb.ABORTED, 1152 } 1153 if _, err := storage.MVCCResolveWriteIntent(ctx, batch, &ms, update); err != nil { 1154 return nil, err 1155 } 1156 // With the intent resolved, we can try again. 1157 if err := storage.MVCCPutProto(ctx, batch, &ms, key, clock.Now(), 1158 nil /* txn */, &desc); err != nil { 1159 return nil, err 1160 } 1161 } else if err != nil { 1162 batch.Close() 1163 return nil, err 1164 } 1165 if err := sl.SetMVCCStats(ctx, batch, &ms); err != nil { 1166 return nil, errors.Wrap(err, "updating MVCCStats") 1167 } 1168 } 1169 1170 return batch, nil 1171 } 1172 1173 var debugMergeLogsCommand = &cobra.Command{ 1174 Use: "merge-logs <log file globs>", 1175 Short: "merge multiple log files from different machines into a single stream", 1176 Long: ` 1177 Takes a list of glob patterns (not left exclusively to the shell because of 1178 MAX_ARG_STRLEN, usually 128kB) pointing to log files and merges them into a 1179 single stream printed to stdout. Files not matching the log file name pattern 1180 are ignored. If log lines appear out of order within a file (which happens), the 1181 timestamp is ratcheted to the highest value seen so far. The command supports 1182 efficient time filtering as well as multiline regexp pattern matching via flags. 1183 If the filter regexp contains captures, such as '^abc(hello)def(world)', only 1184 the captured parts will be printed. 1185 `, 1186 Args: cobra.MinimumNArgs(1), 1187 RunE: runDebugMergeLogs, 1188 } 1189 1190 var debugMergeLogsOpts = struct { 1191 from time.Time 1192 to time.Time 1193 filter *regexp.Regexp 1194 program *regexp.Regexp 1195 file *regexp.Regexp 1196 prefix string 1197 }{ 1198 program: regexp.MustCompile("^cockroach.*$"), 1199 file: regexp.MustCompile(log.FilePattern), 1200 } 1201 1202 func runDebugMergeLogs(cmd *cobra.Command, args []string) error { 1203 o := debugMergeLogsOpts 1204 s, err := newMergedStreamFromPatterns(context.Background(), 1205 args, o.file, o.program, o.from, o.to) 1206 if err != nil { 1207 return err 1208 } 1209 return writeLogStream(s, cmd.OutOrStdout(), o.filter, o.prefix) 1210 } 1211 1212 // DebugCmdsForRocksDB lists debug commands that access rocksdb through the engine 1213 // and need encryption flags (injected by CCL code). 1214 // Note: do NOT include commands that just call rocksdb code without setting up an engine. 1215 var DebugCmdsForRocksDB = []*cobra.Command{ 1216 debugCheckStoreCmd, 1217 debugCompactCmd, 1218 debugGCCmd, 1219 debugKeysCmd, 1220 debugRaftLogCmd, 1221 debugRangeDataCmd, 1222 debugRangeDescriptorsCmd, 1223 debugSSTablesCmd, 1224 } 1225 1226 // All other debug commands go here. 1227 var debugCmds = append(DebugCmdsForRocksDB, 1228 debugBallastCmd, 1229 debugDecodeKeyCmd, 1230 debugDecodeValueCmd, 1231 debugRocksDBCmd, 1232 debugSSTDumpCmd, 1233 debugGossipValuesCmd, 1234 debugTimeSeriesDumpCmd, 1235 debugSyncBenchCmd, 1236 debugSyncTestCmd, 1237 debugUnsafeRemoveDeadReplicasCmd, 1238 debugEnvCmd, 1239 debugZipCmd, 1240 debugMergeLogsCommand, 1241 ) 1242 1243 // DebugCmd is the root of all debug commands. Exported to allow modification by CCL code. 1244 var DebugCmd = &cobra.Command{ 1245 Use: "debug [command]", 1246 Short: "debugging commands", 1247 Long: `Various commands for debugging. 1248 1249 These commands are useful for extracting data from the data files of a 1250 process that has failed and cannot restart. 1251 `, 1252 RunE: usageAndErr, 1253 } 1254 1255 // mvccValueFormatter is an fmt.Formatter for MVCC values. 1256 type mvccValueFormatter struct { 1257 kv storage.MVCCKeyValue 1258 err error 1259 } 1260 1261 // Format implements the fmt.Formatter interface. 1262 func (m mvccValueFormatter) Format(f fmt.State, c rune) { 1263 if m.err != nil { 1264 errors.FormatError(m.err, f, c) 1265 return 1266 } 1267 fmt.Fprint(f, kvserver.SprintKeyValue(m.kv, false /* printKey */)) 1268 } 1269 1270 func init() { 1271 DebugCmd.AddCommand(debugCmds...) 1272 1273 // Note: we hook up FormatValue here in order to avoid a circular dependency 1274 // between kvserver and storage. 1275 storage.MVCCComparer.FormatValue = func(key, value []byte) fmt.Formatter { 1276 decoded, err := storage.DecodeMVCCKey(key) 1277 if err != nil { 1278 return mvccValueFormatter{err: err} 1279 } 1280 return mvccValueFormatter{kv: storage.MVCCKeyValue{Key: decoded, Value: value}} 1281 } 1282 1283 // To be able to read Cockroach-written RocksDB manifests/SSTables, comparator 1284 // and merger functions must be specified to pebble that match the ones used 1285 // to write those files. 1286 pebbleTool := tool.New(tool.Mergers(storage.MVCCMerger), 1287 tool.DefaultComparer(storage.MVCCComparer)) 1288 debugPebbleCmd.AddCommand(pebbleTool.Commands...) 1289 DebugCmd.AddCommand(debugPebbleCmd) 1290 1291 f := debugSyncBenchCmd.Flags() 1292 f.IntVarP(&syncBenchOpts.Concurrency, "concurrency", "c", syncBenchOpts.Concurrency, 1293 "number of concurrent writers") 1294 f.DurationVarP(&syncBenchOpts.Duration, "duration", "d", syncBenchOpts.Duration, 1295 "duration to run the test for") 1296 f.BoolVarP(&syncBenchOpts.LogOnly, "log-only", "l", syncBenchOpts.LogOnly, 1297 "only write to the WAL, not to sstables") 1298 1299 f = debugUnsafeRemoveDeadReplicasCmd.Flags() 1300 f.IntSliceVar(&removeDeadReplicasOpts.deadStoreIDs, "dead-store-ids", nil, 1301 "list of dead store IDs") 1302 1303 f = debugMergeLogsCommand.Flags() 1304 f.Var(flagutil.Time(&debugMergeLogsOpts.from), "from", 1305 "time before which messages should be filtered") 1306 f.Var(flagutil.Time(&debugMergeLogsOpts.to), "to", 1307 "time after which messages should be filtered") 1308 f.Var(flagutil.Regexp(&debugMergeLogsOpts.filter), "filter", 1309 "re which filters log messages") 1310 f.Var(flagutil.Regexp(&debugMergeLogsOpts.file), "file-pattern", 1311 "re which filters log files based on path, also used with prefix and program-filter") 1312 f.Var(flagutil.Regexp(&debugMergeLogsOpts.program), "program-filter", 1313 "re which filter log files that operates on the capture group named \"program\" in file-pattern, "+ 1314 "if no such group exists, program-filter is ignored") 1315 f.StringVar(&debugMergeLogsOpts.prefix, "prefix", "${host}> ", 1316 "expansion template (see regexp.Expand) used as prefix to merged log messages evaluated on file-pattern") 1317 }