diff --git a/src/kudu/tools/kudu-tool-test.cc b/src/kudu/tools/kudu-tool-test.cc index 1dd63a193d..cc2687cad6 100644 --- a/src/kudu/tools/kudu-tool-test.cc +++ b/src/kudu/tools/kudu-tool-test.cc @@ -377,6 +377,7 @@ TEST_F(ToolTest, TestModeHelp) { } { const vector kFsDumpModeRegexes = { + "block.*binary contents of a data block", "cfile.*contents of a CFile", "tree.*tree of a Kudu filesystem", "uuid.*UUID of a Kudu filesystem" @@ -818,6 +819,27 @@ TEST_F(ToolTest, TestFsDumpCFile) { } } +TEST_F(ToolTest, TestFsDumpBlock) { + const string kTestDir = GetTestPath("test"); + FsManager fs(env_, kTestDir); + ASSERT_OK(fs.CreateInitialFileSystemLayout()); + ASSERT_OK(fs.Open()); + + unique_ptr block; + ASSERT_OK(fs.CreateNewBlock({}, &block)); + ASSERT_OK(block->Append("hello world")); + ASSERT_OK(block->Close()); + BlockId block_id = block->id(); + + { + string stdout; + NO_FATALS(RunActionStdoutString(Substitute( + "fs dump block --fs_wal_dir=$0 $1", + kTestDir, block_id.ToString()), &stdout)); + ASSERT_EQ("hello world", stdout); + } +} + TEST_F(ToolTest, TestWalDump) { const string kTestDir = GetTestPath("test"); const string kTestTablet = "test-tablet"; diff --git a/src/kudu/tools/tool_action_fs.cc b/src/kudu/tools/tool_action_fs.cc index fc4ef59fd2..a0c9dfe284 100644 --- a/src/kudu/tools/tool_action_fs.cc +++ b/src/kudu/tools/tool_action_fs.cc @@ -173,14 +173,21 @@ Status DumpUuid(const RunnerContext& /*context*/) { return Status::OK(); } -Status DumpCFile(const RunnerContext& context) { +Status ParseBlockIdArg(const RunnerContext& context, + BlockId* id) { const string& block_id_str = FindOrDie(context.required_args, "block_id"); uint64_t numeric_id; if (!safe_strtou64(block_id_str, &numeric_id)) { return Status::InvalidArgument(Substitute( "Could not parse $0 as numeric block ID", block_id_str)); } - BlockId block_id(numeric_id); + *id = BlockId(numeric_id); + return Status::OK(); +} + +Status DumpCFile(const RunnerContext& context) { + BlockId block_id; + RETURN_NOT_OK(ParseBlockIdArg(context, &block_id)); FsManagerOpts fs_opts; fs_opts.read_only = true; @@ -209,6 +216,35 @@ Status DumpCFile(const RunnerContext& context) { return Status::OK(); } +Status DumpBlock(const RunnerContext& context) { + BlockId block_id; + RETURN_NOT_OK(ParseBlockIdArg(context, &block_id)); + + FsManagerOpts fs_opts; + fs_opts.read_only = true; + FsManager fs_manager(Env::Default(), fs_opts); + RETURN_NOT_OK(fs_manager.Open()); + + unique_ptr block; + RETURN_NOT_OK(fs_manager.OpenBlock(block_id, &block)); + + uint64_t size = 0; + RETURN_NOT_OK_PREPEND(block->Size(&size), "couldn't get block size"); + + faststring buf; + uint64_t offset = 0; + while (offset < size) { + int64_t chunk = std::min(size - offset, 64 * 1024); + buf.resize(chunk); + Slice s(buf); + RETURN_NOT_OK(block->Read(offset, &s)); + offset += s.size(); + cout << s.ToString(); + } + + return Status::OK(); +} + Status DumpFsTree(const RunnerContext& /*context*/) { FsManagerOpts fs_opts; fs_opts.read_only = true; @@ -225,6 +261,8 @@ static unique_ptr BuildFsDumpMode() { unique_ptr dump_cfile = ActionBuilder("cfile", &DumpCFile) .Description("Dump the contents of a CFile (column file)") + .ExtraDescription("This interprets the contents of a CFile-formatted block " + "and outputs the decoded row data.") .AddRequiredParameter({ "block_id", "block identifier" }) .AddOptionalParameter("fs_wal_dir") .AddOptionalParameter("fs_data_dirs") @@ -232,6 +270,16 @@ static unique_ptr BuildFsDumpMode() { .AddOptionalParameter("print_rows") .Build(); + unique_ptr dump_block = + ActionBuilder("block", &DumpBlock) + .Description("Dump the binary contents of a data block") + .ExtraDescription("This performs no parsing or interpretation of the data stored " + "in the block but rather outputs its binary contents directly.") + .AddRequiredParameter({ "block_id", "block identifier" }) + .AddOptionalParameter("fs_wal_dir") + .AddOptionalParameter("fs_data_dirs") + .Build(); + unique_ptr dump_tree = ActionBuilder("tree", &DumpFsTree) .Description("Dump the tree of a Kudu filesystem") @@ -248,6 +296,7 @@ static unique_ptr BuildFsDumpMode() { return ModeBuilder("dump") .Description("Dump a Kudu filesystem") + .AddAction(std::move(dump_block)) .AddAction(std::move(dump_cfile)) .AddAction(std::move(dump_tree)) .AddAction(std::move(dump_uuid))