kudu-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mpe...@apache.org
Subject [1/3] kudu git commit: [tools] ksck improvements [7/n] Add JSON output option to ksck
Date Fri, 11 May 2018 23:26:04 GMT
Repository: kudu
Updated Branches:
  refs/heads/master 72df429ec -> 05d594867


[tools] ksck improvements [7/n] Add JSON output option to ksck

This adds support for JSON output to ksck, using a new flag
--ksck_format that supports four options: 'plain_concise',
'plain_full', 'json_pretty', and 'json_concise'.
'plain_concise' formatting is the default: it's the original
formatting and it is not changed by this patch. 'plain_full'
supersedes the --verbose flag. The 'json_pretty' option
pretty-prints a json representation of all the information
gathered by ksck. 'json_compact' ugly-prints the same, and is
suitable for parsing by other programs, like jq.

There's a knock-on effect of how this change is implemented: since
KsckResults is translated to PB and then to JSON via generic PB-to-JSON
code, the printing for CONSENSUS_MISMATCH health has changed to
match its PB stringification. Previously it was stringified as
UNAVAILABLE. Note that it isn't necessarily true that
CONSENSUS_MISMATCH implies unavailable, anyway, since, e.g.
a replica not yet aware of a new leader will cause a
CONSENSUS_MISMATCH state on a tablet that's available.

Here's a sample of the 4 formats run against the same 1-table,
8-tablet cluster:

plain_concise: https://gist.github.com/wdberkeley/674a8b0322c4c0ad6e8eb4ef79664d37
plain_full: https://gist.github.com/wdberkeley/841c92cf4e500782b0dc3a30a8c1cbd8
json_pretty: https://gist.github.com/wdberkeley/04dca6dd5ec7a10ad10c4bd30decab35
json_compact: https://gist.github.com/wdberkeley/283d48ad248a26073a30e013e19443c3

Change-Id: Ib5da0752f8e41c022611253c300450368f6ae969
Reviewed-on: http://gerrit.cloudera.org:8080/10288
Reviewed-by: Alexey Serbin <aserbin@cloudera.com>
Reviewed-by: Andrew Wong <awong@cloudera.com>
Tested-by: Will Berkeley <wdberkeley@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/ee0a75c5
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/ee0a75c5
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/ee0a75c5

Branch: refs/heads/master
Commit: ee0a75c5a22751241ff25c8f7b9c0a2654fbd767
Parents: 72df429
Author: Will Berkeley <wdberkeley@apache.org>
Authored: Tue May 1 13:17:57 2018 -0700
Committer: Will Berkeley <wdberkeley@gmail.com>
Committed: Fri May 11 21:32:01 2018 +0000

----------------------------------------------------------------------
 src/kudu/tools/CMakeLists.txt         |   1 +
 src/kudu/tools/ksck-test.cc           | 490 ++++++++++++++++++++++++++---
 src/kudu/tools/ksck.cc                |  35 ++-
 src/kudu/tools/ksck_results.cc        | 227 ++++++++++++-
 src/kudu/tools/ksck_results.h         |  25 +-
 src/kudu/tools/tool.proto             | 114 +++++++
 src/kudu/tools/tool_action_cluster.cc |   2 +-
 7 files changed, 819 insertions(+), 75 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/tools/CMakeLists.txt b/src/kudu/tools/CMakeLists.txt
index 832bb1d..1a7a0fa 100644
--- a/src/kudu/tools/CMakeLists.txt
+++ b/src/kudu/tools/CMakeLists.txt
@@ -31,6 +31,7 @@ add_library(tool_proto
 target_link_libraries(tool_proto
   kudu_common_proto
   protobuf
+  tablet_proto
   wire_protocol_proto)
 
 #######################################

http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/ksck-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/ksck-test.cc b/src/kudu/tools/ksck-test.cc
index 18d28ef..18db2ce 100644
--- a/src/kudu/tools/ksck-test.cc
+++ b/src/kudu/tools/ksck-test.cc
@@ -15,8 +15,12 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include "kudu/tools/ksck.h"
+
+#include <cstdint>
 #include <map>
 #include <memory>
+#include <set>
 #include <sstream>
 #include <string>
 #include <type_traits>
@@ -28,6 +32,7 @@
 #include <gflags/gflags_declare.h>
 #include <glog/logging.h>
 #include <gtest/gtest.h>
+#include <rapidjson/document.h>
 
 #include "kudu/common/schema.h"
 #include "kudu/consensus/metadata.pb.h"
@@ -36,8 +41,8 @@
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/tablet/metadata.pb.h"
 #include "kudu/tablet/tablet.pb.h"
-#include "kudu/tools/ksck.h"
 #include "kudu/tools/ksck_results.h"
+#include "kudu/util/jsonreader.h"
 #include "kudu/util/scoped_cleanup.h"
 #include "kudu/util/status.h"
 #include "kudu/util/test_macros.h"
@@ -49,6 +54,7 @@ DECLARE_string(color);
 namespace kudu {
 namespace tools {
 
+using std::ostringstream;
 using std::shared_ptr;
 using std::static_pointer_cast;
 using std::string;
@@ -336,6 +342,7 @@ class KsckTest : public KuduTest {
     pb.set_tablet_id(tablet_id);
     pb.set_table_name("fake-table");
     pb.set_state(is_running ? tablet::RUNNING : tablet::FAILED);
+    pb.set_tablet_data_state(TabletDataState::TABLET_DATA_UNKNOWN);
     InsertOrDie(&ts->tablet_status_map_, tablet_id, pb);
   }
 
@@ -346,6 +353,12 @@ class KsckTest : public KuduTest {
     return ksck_->RunAndPrintResults();
   }
 
+  const string KsckResultsToJsonString() {
+    ostringstream json_stream;
+    ksck_->results().PrintJsonTo(PrintMode::JSON_COMPACT, json_stream);
+    return json_stream.str();
+  }
+
   shared_ptr<MockKsckCluster> cluster_;
   shared_ptr<Ksck> ksck_;
   // This is used as a stack. First the unit test is responsible to create a plan to follow, that
@@ -358,6 +371,327 @@ class KsckTest : public KuduTest {
   std::ostringstream err_stream_;
 };
 
+// Helpful macros for checking JSON fields vs. expected values.
+// In all cases, the meaning of the parameters are as follows:
+// 'reader' is the JsonReader that owns the parsed JSON data.
+// 'value' is the rapidjson::Value* containing the field, or, if 'field'
+// is nullptr, the field itself.
+// 'field' is a const char* naming the field of 'value' to check.
+// If it is null, the field value is extracted from 'value' directly.
+// 'expected' is the expected value.
+#define EXPECT_JSON_STRING_FIELD(reader, value, field, expected) do { \
+  string actual; \
+  ASSERT_OK((reader).ExtractString((value), (field), &actual)); \
+  EXPECT_EQ((expected), actual); \
+} while (0);
+
+#define EXPECT_JSON_INT_FIELD(reader, value, field, expected) do { \
+  int64_t actual; \
+  ASSERT_OK((reader).ExtractInt64((value), (field), &actual)); \
+  EXPECT_EQ((expected), actual); \
+} while (0);
+
+#define EXPECT_JSON_BOOL_FIELD(reader, value, field, expected) do { \
+  bool actual; \
+  ASSERT_OK((reader).ExtractBool((value), (field), &actual)); \
+  EXPECT_EQ((expected), actual); \
+} while (0);
+
+#define EXPECT_JSON_FIELD_NOT_PRESENT(reader, value, field) do { \
+  int64_t unused; \
+  ASSERT_TRUE((reader).ExtractInt64((value), (field), &unused).IsNotFound()); \
+} while (0);
+
+// 'array' is a vector<const rapidjson::Value*> into which the array elements
+// will be extracted.
+// 'exp_size' is the expected size of the vector after extraction.
+#define EXTRACT_ARRAY_CHECK_SIZE(reader, value, field, array, exp_size) do { \
+  ASSERT_OK((reader).ExtractObjectArray((value), (field), &(array))); \
+  ASSERT_EQ(exp_size, (array).size()); \
+} while (0);
+
+void CheckJsonVsServerHealthSummaries(const JsonReader& r,
+                                      const string& key,
+                                      const vector<KsckServerHealthSummary>& summaries) {
+  if (summaries.empty()) {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), key.c_str());
+    return;
+  }
+  vector<const rapidjson::Value*> health;
+  EXTRACT_ARRAY_CHECK_SIZE(r, r.root(), key.c_str(), health, summaries.size());
+  for (int i = 0; i < summaries.size(); i++) {
+    const auto& summary = summaries[i];
+    const auto* server = health[i];
+    EXPECT_JSON_STRING_FIELD(r, server, "uuid", summary.uuid);
+    EXPECT_JSON_STRING_FIELD(r, server, "address", summary.address);
+    EXPECT_JSON_STRING_FIELD(r, server, "health", ServerHealthToString(summary.health));
+    EXPECT_JSON_STRING_FIELD(r, server, "status", summary.status.ToString());
+  }
+}
+
+const string KsckConsensusConfigTypeToString(KsckConsensusConfigType t) {
+  switch (t) {
+    case KsckConsensusConfigType::COMMITTED:
+      return "COMMITTED";
+    case KsckConsensusConfigType::PENDING:
+      return "PENDING";
+    case KsckConsensusConfigType::MASTER:
+      return "MASTER";
+    default:
+      LOG(FATAL) << "unknown KsckConsensusConfigType";
+  }
+}
+
+void CheckJsonVsConsensusState(const JsonReader& r,
+                               const rapidjson::Value* cstate,
+                               const string& ref_id,
+                               const KsckConsensusState& ref_cstate) {
+  EXPECT_JSON_STRING_FIELD(r, cstate, "type",
+                           KsckConsensusConfigTypeToString(ref_cstate.type));
+  if (ref_cstate.leader_uuid) {
+    EXPECT_JSON_STRING_FIELD(r, cstate, "leader_uuid", ref_cstate.leader_uuid);
+  } else {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, cstate, "leader_uuid");
+  }
+  if (ref_cstate.term) {
+    EXPECT_JSON_INT_FIELD(r, cstate, "term", ref_cstate.term);
+  } else {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, cstate, "term");
+  }
+  if (ref_cstate.opid_index) {
+    EXPECT_JSON_INT_FIELD(r, cstate, "opid_index", ref_cstate.opid_index);
+  } else {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, cstate, "opid_index");
+  }
+  // Check voters.
+  if (ref_cstate.voter_uuids.empty()) {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, cstate, "voter_uuids");
+  } else {
+    const vector<string> ref_voter_uuids(ref_cstate.voter_uuids.begin(),
+                                         ref_cstate.voter_uuids.end());
+    vector<const rapidjson::Value*> voter_uuids;
+    EXTRACT_ARRAY_CHECK_SIZE(r, cstate, "voter_uuids",
+                             voter_uuids, ref_voter_uuids.size());
+    for (int j = 0; j < voter_uuids.size(); j++) {
+      EXPECT_JSON_STRING_FIELD(r, voter_uuids[j], nullptr, ref_voter_uuids[j]);
+    }
+  }
+  // Check non-voters.
+  if (ref_cstate.non_voter_uuids.empty()) {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, cstate, "non_voter_uuids");
+  } else {
+    const vector<string> ref_non_voter_uuids(ref_cstate.non_voter_uuids.begin(),
+                                             ref_cstate.non_voter_uuids.end());
+    vector<const rapidjson::Value*> non_voter_uuids;
+    EXTRACT_ARRAY_CHECK_SIZE(r, cstate, "nonvoter_uuids",
+                             non_voter_uuids, ref_non_voter_uuids.size());
+    for (int j = 0; j < non_voter_uuids.size(); j++) {
+      EXPECT_JSON_STRING_FIELD(r, non_voter_uuids[j], nullptr, ref_non_voter_uuids[j]);
+    }
+  }
+}
+
+void CheckJsonVsReplicaSummary(const JsonReader& r,
+                               const rapidjson::Value* replica,
+                               const KsckReplicaSummary& ref_replica) {
+  EXPECT_JSON_STRING_FIELD(r, replica, "ts_uuid", ref_replica.ts_uuid);
+  if (ref_replica.ts_address) {
+    EXPECT_JSON_STRING_FIELD(r, replica, "ts_address", ref_replica.ts_address);
+  } else {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, replica, "ts_address");
+  }
+  EXPECT_JSON_BOOL_FIELD(r, replica, "is_leader", ref_replica.is_leader);
+  EXPECT_JSON_BOOL_FIELD(r, replica, "is_voter", ref_replica.is_voter);
+  EXPECT_JSON_BOOL_FIELD(r, replica, "ts_healthy", ref_replica.ts_healthy);
+  EXPECT_JSON_STRING_FIELD(r, replica, "state", tablet::TabletStatePB_Name(ref_replica.state));
+  // The only thing ksck expects from the status_pb is the data state,
+  // so it's all we check (even though the other info is nice to have).
+  if (ref_replica.status_pb) {
+    const rapidjson::Value* status_pb;
+    ASSERT_OK(r.ExtractObject(replica, "status_pb", &status_pb));
+    EXPECT_JSON_STRING_FIELD(
+        r,
+        status_pb,
+        "tablet_data_state",
+        tablet::TabletDataState_Name(ref_replica.status_pb->tablet_data_state()));
+  } else {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, replica, "status_pb");
+  }
+  if (ref_replica.consensus_state) {
+    const rapidjson::Value* cstate;
+    ASSERT_OK(r.ExtractObject(replica, "consensus_state", &cstate));
+    CheckJsonVsConsensusState(r, cstate, ref_replica.ts_uuid, *ref_replica.consensus_state);
+  } else {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, replica, "consensus_state");
+  }
+}
+
+void CheckJsonVsMasterConsensus(const JsonReader& r,
+                                bool ref_conflict,
+                                const KsckConsensusStateMap& ref_cstates) {
+  if (ref_cstates.empty()) {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), "master_consensus_states");
+    return;
+  }
+  EXPECT_JSON_BOOL_FIELD(r, r.root(), "master_consensus_conflict", ref_conflict);
+  vector<const rapidjson::Value*> cstates;
+  EXTRACT_ARRAY_CHECK_SIZE(r, r.root(), "master_consensus_states",
+                           cstates, ref_cstates.size());
+  int i = 0;
+  for (const auto& entry : ref_cstates) {
+    CheckJsonVsConsensusState(r, cstates[i++], entry.first, entry.second);
+  }
+}
+
+void CheckJsonVsTableSummaries(const JsonReader& r,
+                               const string& key,
+                               const vector<KsckTableSummary>& ref_tables) {
+  if (ref_tables.empty()) {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), key.c_str());
+    return;
+  }
+  vector<const rapidjson::Value*> tables;
+  EXTRACT_ARRAY_CHECK_SIZE(r, r.root(), key.c_str(), tables, ref_tables.size());
+  for (int i = 0; i < ref_tables.size(); i++) {
+    const auto& ref_table = ref_tables[i];
+    const auto* table = tables[i];
+    EXPECT_JSON_STRING_FIELD(r, table, "id", ref_table.id);
+    EXPECT_JSON_STRING_FIELD(r, table, "name", ref_table.name);
+    EXPECT_JSON_STRING_FIELD(r, table,
+                             "health", KsckCheckResultToString(ref_table.TableStatus()));
+    EXPECT_JSON_INT_FIELD(r, table,
+                          "replication_factor", ref_table.replication_factor);
+    EXPECT_JSON_INT_FIELD(r, table,
+                          "total_tablets", ref_table.TotalTablets());
+    EXPECT_JSON_INT_FIELD(r, table,
+                          "healthy_tablets", ref_table.healthy_tablets);
+    EXPECT_JSON_INT_FIELD(r, table,
+                          "recovering_tablets", ref_table.recovering_tablets);
+    EXPECT_JSON_INT_FIELD(r, table,
+                          "underreplicated_tablets", ref_table.underreplicated_tablets);
+    EXPECT_JSON_INT_FIELD(r, table,
+                          "unavailable_tablets", ref_table.unavailable_tablets);
+    EXPECT_JSON_INT_FIELD(r, table,
+                          "consensus_mismatch_tablets", ref_table.consensus_mismatch_tablets);
+  }
+}
+
+void CheckJsonVsTabletSummaries(const JsonReader& r,
+                                const string& key,
+                                const vector<KsckTabletSummary>& ref_tablets) {
+  if (ref_tablets.empty()) {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), key.c_str());
+    return;
+  }
+  vector<const rapidjson::Value*> tablets;
+  EXTRACT_ARRAY_CHECK_SIZE(r, r.root(), key.c_str(), tablets, ref_tablets.size());
+  for (int i = 0; i < ref_tablets.size(); i++) {
+    const auto& ref_tablet = ref_tablets[i];
+    const auto& tablet = tablets[i];
+    EXPECT_JSON_STRING_FIELD(r, tablet, "id", ref_tablet.id);
+    EXPECT_JSON_STRING_FIELD(r, tablet, "table_id", ref_tablet.table_id);
+    EXPECT_JSON_STRING_FIELD(r, tablet, "table_name", ref_tablet.table_name);
+    EXPECT_JSON_STRING_FIELD(r, tablet,
+                             "health", KsckCheckResultToString(ref_tablet.result));
+    EXPECT_JSON_STRING_FIELD(r, tablet, "status", ref_tablet.status);
+    const rapidjson::Value* master_cstate;
+    ASSERT_OK(r.ExtractObject(tablet, "master_cstate", &master_cstate));
+    CheckJsonVsConsensusState(r, master_cstate, "master", ref_tablet.master_cstate);
+    if (ref_tablet.replicas.empty()) {
+      EXPECT_JSON_FIELD_NOT_PRESENT(r, tablet, "replicas");
+      continue;
+    }
+    vector<const rapidjson::Value*> replicas;
+    EXTRACT_ARRAY_CHECK_SIZE(r, tablet,
+                             "replicas", replicas, ref_tablet.replicas.size());
+    for (int j = 0; j < replicas.size(); j++) {
+      const auto& ref_replica = ref_tablet.replicas[j];
+      const auto* replica = replicas[j];
+      CheckJsonVsReplicaSummary(r, replica, ref_replica);
+    }
+  }
+}
+
+void CheckJsonVsChecksumResults(const JsonReader& r,
+                                const string& key,
+                                const KsckChecksumResults& ref_checksum_results) {
+  if (ref_checksum_results.tables.empty()) {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), key.c_str());
+    return;
+  }
+  const rapidjson::Value* checksum_results;
+  ASSERT_OK(r.ExtractObject(r.root(), key.c_str(), &checksum_results));
+  if (ref_checksum_results.snapshot_timestamp) {
+    EXPECT_JSON_INT_FIELD(r, checksum_results,
+                          "snapshot_timestamp", *ref_checksum_results.snapshot_timestamp);
+  } else {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, checksum_results, "snapshot_timestamp");
+  }
+  vector<const rapidjson::Value*> tables;
+  EXTRACT_ARRAY_CHECK_SIZE(r, checksum_results, "tables",
+                           tables, ref_checksum_results.tables.size());
+  int i = 0;
+  for (const auto& table_entry : ref_checksum_results.tables) {
+    const auto& ref_table = table_entry.second;
+    const auto* table = tables[i++];
+    EXPECT_JSON_STRING_FIELD(r, table, "name", table_entry.first);
+    vector<const rapidjson::Value*> tablets;
+    EXTRACT_ARRAY_CHECK_SIZE(r, table, "tablets", tablets, ref_table.size());
+    int j = 0;
+    for (const auto& tablet_entry : ref_table) {
+      const auto& ref_tablet = tablet_entry.second;
+      const auto* tablet = tablets[j++];
+      EXPECT_JSON_STRING_FIELD(r, tablet, "tablet_id", tablet_entry.first);
+      EXPECT_JSON_BOOL_FIELD(r, tablet, "mismatch", ref_tablet.mismatch);
+      vector<const rapidjson::Value*> checksums;
+      EXTRACT_ARRAY_CHECK_SIZE(r, tablet, "replica_checksums",
+                               checksums, ref_tablet.replica_checksums.size());
+      int k = 0;
+      for (const auto& replica_entry : ref_tablet.replica_checksums) {
+        const auto& ref_replica = replica_entry.second;
+        const auto* replica = checksums[k++];
+        EXPECT_JSON_STRING_FIELD(r, replica, "ts_uuid", ref_replica.ts_uuid);
+        EXPECT_JSON_STRING_FIELD(r, replica, "ts_address", ref_replica.ts_address);
+        EXPECT_JSON_STRING_FIELD(r, replica, "status", ref_replica.status.ToString());
+        // Checksum is a uint64_t and might plausibly be larger than int64_t's max,
+        // so we're handling it special.
+        int64_t signed_checksum;
+        ASSERT_OK(r.ExtractInt64(replica, "checksum", &signed_checksum));
+        ASSERT_EQ(ref_replica.checksum, static_cast<uint64_t>(signed_checksum));
+      }
+    }
+  }
+}
+
+void CheckJsonVsErrors(const JsonReader& r,
+                       const string& key,
+                       const vector<Status>& ref_errors) {
+  if (ref_errors.empty()) {
+    EXPECT_JSON_FIELD_NOT_PRESENT(r, r.root(), key.c_str());
+    return;
+  }
+  vector<const rapidjson::Value*> errors;
+  EXTRACT_ARRAY_CHECK_SIZE(r, r.root(), "errors", errors, ref_errors.size());
+  for (int i = 0; i < ref_errors.size(); i++) {
+    EXPECT_JSON_STRING_FIELD(r, errors[i], nullptr, ref_errors[i].ToString());
+  }
+}
+
+void CheckJsonStringVsKsckResults(const string& json, const KsckResults& results) {
+  JsonReader r(json);
+  ASSERT_OK(r.Init());
+
+  CheckJsonVsServerHealthSummaries(r, "master_summaries", results.master_summaries);
+  CheckJsonVsMasterConsensus(r,
+                             results.master_consensus_conflict,
+                             results.master_consensus_state_map);
+  CheckJsonVsServerHealthSummaries(r, "tserver_summaries", results.tserver_summaries);
+  CheckJsonVsTabletSummaries(r, "tablet_summaries", results.tablet_summaries);;
+  CheckJsonVsTableSummaries(r, "table_summaries", results.table_summaries);;
+  CheckJsonVsChecksumResults(r, "checksum_results", results.checksum_results);
+  CheckJsonVsErrors(r, "errors", results.error_messages);
+}
+
 TEST_F(KsckTest, TestServersOk) {
   ASSERT_OK(RunKsck());
   const string err_string = err_stream_.str();
@@ -377,6 +711,8 @@ TEST_F(KsckTest, TestServersOk) {
     " ts-id-0 | <mock>  | HEALTHY\n"
     " ts-id-1 | <mock>  | HEALTHY\n"
     " ts-id-2 | <mock>  | HEALTHY\n");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestMasterUnavailable) {
@@ -405,6 +741,8 @@ TEST_F(KsckTest, TestMasterUnavailable) {
     " A             | A*  B   C              | 0            |              | Yes\n"
     " B             | [config not available] |              |              | \n"
     " C             | A*  B   C              | 0            |              | Yes\n");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 // A wrong-master-uuid situation can happen if a master that is part of, e.g.,
@@ -442,6 +780,8 @@ TEST_F(KsckTest, TestWrongMasterUuid) {
     " A             | A*  B       D    | 0            |              | Yes\n"
     " B             | A*  B       D    | 0            |              | Yes\n"
     " C             |         C*       | 0            |              | Yes\n");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestTwoLeaderMasters) {
@@ -463,13 +803,15 @@ TEST_F(KsckTest, TestTwoLeaderMasters) {
     " A             | A*  B   C    | 0            |              | Yes\n"
     " B             | A   B*  C    | 0            |              | Yes\n"
     " C             | A*  B   C    | 0            |              | Yes\n");
-}
 
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
+}
 
 TEST_F(KsckTest, TestLeaderMasterUnavailable) {
   Status error = Status::NetworkError("Network failure");
   cluster_->fetch_info_status_ = error;
   ASSERT_TRUE(ksck_->CheckClusterRunning().IsNetworkError());
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestWrongUUIDTabletServer) {
@@ -491,6 +833,8 @@ TEST_F(KsckTest, TestWrongUUIDTabletServer) {
     " ts-id-0 | <mock>  | HEALTHY\n"
     " ts-id-2 | <mock>  | HEALTHY\n"
     " ts-id-1 | <mock>  | WRONG_SERVER_UUID\n");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestBadTabletServer) {
@@ -538,6 +882,8 @@ TEST_F(KsckTest, TestBadTabletServer) {
       "  ts-id-0 (<mock>): RUNNING [LEADER]\n"
       "  ts-id-1 (<mock>): TS unavailable\n"
       "  ts-id-2 (<mock>): RUNNING\n");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestOneTableCheck) {
@@ -546,6 +892,8 @@ TEST_F(KsckTest, TestOneTableCheck) {
   ASSERT_OK(RunKsck());
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       "0/1 replicas remaining (20B from disk, 10 rows summed)");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestOneSmallReplicatedTable) {
@@ -554,6 +902,8 @@ TEST_F(KsckTest, TestOneSmallReplicatedTable) {
   ASSERT_OK(RunKsck());
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       "0/9 replicas remaining (180B from disk, 90 rows summed)");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 // Test filtering on a non-matching table pattern.
@@ -568,6 +918,8 @@ TEST_F(KsckTest, TestNonMatchingTableFilter) {
             error_messages[0].ToString());
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       "The cluster doesn't have any matching tables");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 // Test filtering with a matching table pattern.
@@ -578,6 +930,8 @@ TEST_F(KsckTest, TestMatchingTableFilter) {
   ASSERT_OK(RunKsck());
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       "0/9 replicas remaining (180B from disk, 90 rows summed)");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 // Test filtering on a non-matching tablet id pattern.
@@ -593,6 +947,8 @@ TEST_F(KsckTest, TestNonMatchingTabletIdFilter) {
       error_messages[0].ToString());
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       "The cluster doesn't have any matching tablets");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 // Test filtering with a matching tablet ID pattern.
@@ -603,6 +959,8 @@ TEST_F(KsckTest, TestMatchingTabletIdFilter) {
   ASSERT_OK(RunKsck());
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       "0/3 replicas remaining (60B from disk, 30 rows summed)");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestOneSmallReplicatedTableWithConsensusState) {
@@ -611,11 +969,13 @@ TEST_F(KsckTest, TestOneSmallReplicatedTableWithConsensusState) {
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       ExpectedKsckTableSummary("test",
                                                /*replication_factor=*/ 3,
-                                               /*healthy_tables=*/ 3,
-                                               /*recovering_tables=*/ 0,
-                                               /*underreplicated_tables=*/ 0,
-                                               /*consensus_mismatch_tables=*/ 0,
-                                               /*unavailable_tables=*/ 0));
+                                               /*healthy_tablets=*/ 3,
+                                               /*recovering_tablets=*/ 0,
+                                               /*underreplicated_tablets=*/ 0,
+                                               /*consensus_mismatch_tablets=*/ 0,
+                                               /*unavailable_tablets=*/ 0));
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestConsensusConflictExtraPeer) {
@@ -642,11 +1002,13 @@ TEST_F(KsckTest, TestConsensusConflictExtraPeer) {
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       ExpectedKsckTableSummary("test",
                                                /*replication_factor=*/ 3,
-                                               /*healthy_tables=*/ 2,
-                                               /*recovering_tables=*/ 0,
-                                               /*underreplicated_tables=*/ 0,
-                                               /*consensus_mismatch_tables=*/ 0,
-                                               /*unavailable_tables=*/ 1));
+                                               /*healthy_tablets=*/ 2,
+                                               /*recovering_tablets=*/ 0,
+                                               /*underreplicated_tablets=*/ 0,
+                                               /*consensus_mismatch_tablets=*/ 1,
+                                               /*unavailable_tablets=*/ 0));
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestConsensusConflictMissingPeer) {
@@ -673,11 +1035,13 @@ TEST_F(KsckTest, TestConsensusConflictMissingPeer) {
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       ExpectedKsckTableSummary("test",
                                                /*replication_factor=*/ 3,
-                                               /*healthy_tables=*/ 2,
-                                               /*recovering_tables=*/ 0,
-                                               /*underreplicated_tables=*/ 0,
-                                               /*consensus_mismatch_tables=*/ 0,
-                                               /*unavailable_tables=*/ 1));
+                                               /*healthy_tablets=*/ 2,
+                                               /*recovering_tablets=*/ 0,
+                                               /*underreplicated_tablets=*/ 0,
+                                               /*consensus_mismatch_tablets=*/ 1,
+                                               /*unavailable_tablets=*/ 0));
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestConsensusConflictDifferentLeader) {
@@ -704,11 +1068,13 @@ TEST_F(KsckTest, TestConsensusConflictDifferentLeader) {
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       ExpectedKsckTableSummary("test",
                                                /*replication_factor=*/ 3,
-                                               /*healthy_tables=*/ 2,
-                                               /*recovering_tables=*/ 0,
-                                               /*underreplicated_tables=*/ 0,
-                                               /*consensus_mismatch_tables=*/ 0,
-                                               /*unavailable_tables=*/ 1));
+                                               /*healthy_tablets=*/ 2,
+                                               /*recovering_tablets=*/ 0,
+                                               /*underreplicated_tablets=*/ 0,
+                                               /*consensus_mismatch_tablets=*/ 1,
+                                               /*unavailable_tablets=*/ 0));
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestOneOneTabletBrokenTable) {
@@ -724,11 +1090,13 @@ TEST_F(KsckTest, TestOneOneTabletBrokenTable) {
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       ExpectedKsckTableSummary("test",
                                                /*replication_factor=*/ 3,
-                                               /*healthy_tables=*/ 0,
-                                               /*recovering_tables=*/ 0,
-                                               /*underreplicated_tables=*/ 1,
-                                               /*consensus_mismatch_tables=*/ 0,
-                                               /*unavailable_tables=*/ 0));
+                                               /*healthy_tablets=*/ 0,
+                                               /*recovering_tablets=*/ 0,
+                                               /*underreplicated_tablets=*/ 1,
+                                               /*consensus_mismatch_tablets=*/ 0,
+                                               /*unavailable_tablets=*/ 0));
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestMismatchedAssignments) {
@@ -751,11 +1119,13 @@ TEST_F(KsckTest, TestMismatchedAssignments) {
   ASSERT_STR_CONTAINS(err_stream_.str(),
                      ExpectedKsckTableSummary("test",
                                               /*replication_factor=*/ 3,
-                                              /*healthy_tables=*/ 2,
-                                              /*recovering_tables=*/ 0,
-                                              /*underreplicated_tables=*/ 1,
-                                              /*consensus_mismatch_tables=*/ 0,
-                                              /*unavailable_tables=*/ 0));
+                                              /*healthy_tablets=*/ 2,
+                                              /*recovering_tablets=*/ 0,
+                                              /*underreplicated_tablets=*/ 1,
+                                              /*consensus_mismatch_tablets=*/ 0,
+                                              /*unavailable_tablets=*/ 0));
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestTabletNotRunning) {
@@ -784,11 +1154,13 @@ TEST_F(KsckTest, TestTabletNotRunning) {
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       ExpectedKsckTableSummary("test",
                                                /*replication_factor=*/ 3,
-                                               /*healthy_tables=*/ 2,
-                                               /*recovering_tables=*/ 0,
-                                               /*underreplicated_tables=*/ 0,
-                                               /*consensus_mismatch_tables=*/ 0,
-                                               /*unavailable_tables=*/ 1));
+                                               /*healthy_tablets=*/ 2,
+                                               /*recovering_tablets=*/ 0,
+                                               /*underreplicated_tablets=*/ 0,
+                                               /*consensus_mismatch_tablets=*/ 0,
+                                               /*unavailable_tablets=*/ 1));
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestTabletCopying) {
@@ -808,11 +1180,13 @@ TEST_F(KsckTest, TestTabletCopying) {
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       ExpectedKsckTableSummary("test",
                                                /*replication_factor=*/ 3,
-                                               /*healthy_tables=*/ 2,
-                                               /*recovering_tables=*/ 1,
-                                               /*underreplicated_tables=*/ 0,
-                                               /*consensus_mismatch_tables=*/ 0,
-                                               /*unavailable_tables=*/ 0));
+                                               /*healthy_tablets=*/ 2,
+                                               /*recovering_tablets=*/ 1,
+                                               /*underreplicated_tablets=*/ 0,
+                                               /*consensus_mismatch_tablets=*/ 0,
+                                               /*unavailable_tablets=*/ 0));
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 // Test for a bug where we weren't properly handling a tserver not reported by the master.
@@ -831,11 +1205,13 @@ TEST_F(KsckTest, TestMasterNotReportingTabletServer) {
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       ExpectedKsckTableSummary("test",
                                                /*replication_factor=*/ 3,
-                                               /*healthy_tables=*/ 0,
-                                               /*recovering_tables=*/ 0,
-                                               /*underreplicated_tables=*/ 3,
-                                               /*consensus_mismatch_tables=*/ 0,
-                                               /*unavailable_tables=*/ 0));
+                                               /*healthy_tablets=*/ 0,
+                                               /*recovering_tablets=*/ 0,
+                                               /*underreplicated_tablets=*/ 3,
+                                               /*consensus_mismatch_tablets=*/ 0,
+                                               /*unavailable_tablets=*/ 0));
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 // KUDU-2113: Test for a bug where we weren't properly handling a tserver not
@@ -868,11 +1244,13 @@ TEST_F(KsckTest, TestMasterNotReportingTabletServerWithConsensusConflict) {
   ASSERT_STR_CONTAINS(err_stream_.str(),
                       ExpectedKsckTableSummary("test",
                                                /*replication_factor=*/ 3,
-                                               /*healthy_tables=*/ 0,
-                                               /*recovering_tables=*/ 0,
-                                               /*underreplicated_tables=*/ 3,
-                                               /*consensus_mismatch_tables=*/ 0,
-                                               /*unavailable_tables=*/ 0));
+                                               /*healthy_tablets=*/ 0,
+                                               /*recovering_tablets=*/ 0,
+                                               /*underreplicated_tablets=*/ 3,
+                                               /*consensus_mismatch_tablets=*/ 0,
+                                               /*unavailable_tablets=*/ 0));
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestTableFiltersNoMatch) {
@@ -886,6 +1264,8 @@ TEST_F(KsckTest, TestTableFiltersNoMatch) {
   ASSERT_STR_NOT_CONTAINS(err_stream_.str(),
       "                | Total Count\n"
       "----------------+-------------\n");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestTableFilters) {
@@ -902,6 +1282,8 @@ TEST_F(KsckTest, TestTableFilters) {
       " Tables         | 1\n"
       " Tablets        | 3\n"
       " Replicas       | 9\n");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestTabletFiltersNoMatch) {
@@ -915,6 +1297,8 @@ TEST_F(KsckTest, TestTabletFiltersNoMatch) {
   ASSERT_STR_NOT_CONTAINS(err_stream_.str(),
       "                | Total Count\n"
       "----------------+-------------\n");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 TEST_F(KsckTest, TestTabletFilters) {
@@ -930,6 +1314,8 @@ TEST_F(KsckTest, TestTabletFilters) {
       " Tables         | 1\n"
       " Tablets        | 2\n"
       " Replicas       | 6\n");
+
+  CheckJsonStringVsKsckResults(KsckResultsToJsonString(), ksck_->results());
 }
 
 } // namespace tools

http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/ksck.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/ksck.cc b/src/kudu/tools/ksck.cc
index 70a1b64..48a0814 100644
--- a/src/kudu/tools/ksck.cc
+++ b/src/kudu/tools/ksck.cc
@@ -72,10 +72,17 @@ DEFINE_uint64(checksum_snapshot_timestamp,
 DEFINE_int32(fetch_replica_info_concurrency, 20,
              "Number of concurrent tablet servers to fetch replica info from.");
 
+DEFINE_string(ksck_format, "plain_concise",
+              "Output format for ksck. Available options are 'plain_concise', "
+              "'plain_full', 'json_pretty', and 'json_compact'.\n"
+              "'plain_concise' format is plain text, omitting most information "
+              "about healthy tablets.\n"
+              "'plain_full' is plain text with all results included.\n"
+              "'json_pretty' produces pretty-printed json.\n"
+              "'json_compact' produces json suitable for parsing by other programs.\n"
+              "'json_pretty' and 'json_compact' differ in format, not content.");
 DEFINE_bool(consensus, true,
             "Whether to check the consensus state from each tablet against the master.");
-DEFINE_bool(verbose, false,
-            "Output detailed information even if no inconsistency is found.");
 
 using std::cout;
 using std::endl;
@@ -362,7 +369,19 @@ Status Ksck::Run() {
 }
 
 Status Ksck::PrintResults() {
-  PrintMode mode = FLAGS_verbose ? PrintMode::VERBOSE : PrintMode::DEFAULT;
+  PrintMode mode;
+  if (FLAGS_ksck_format == "plain_concise") {
+    mode = PrintMode::PLAIN_CONCISE;
+  } else if (FLAGS_ksck_format == "plain_full") {
+    mode = PrintMode::PLAIN_FULL;
+  } else if (FLAGS_ksck_format == "json_pretty") {
+    mode = PrintMode::JSON_PRETTY;
+  } else if (FLAGS_ksck_format == "json_compact") {
+    mode = PrintMode::JSON_COMPACT;
+  } else {
+    return Status::InvalidArgument("unknown ksck format (--ksck_format)",
+                                   FLAGS_ksck_format);
+  }
   return results_.PrintTo(mode, *out_);
 }
 
@@ -780,10 +799,10 @@ KsckCheckResult Ksck::VerifyTablet(const shared_ptr<KsckTablet>& tablet,
   int copying_replicas_count = 0;
   int conflicting_states = 0;
   int num_voters = 0;
-  vector<KsckReplicaSummary> replica_infos;
+  vector<KsckReplicaSummary> replicas;
   for (const shared_ptr<KsckTabletReplica>& replica : tablet->replicas()) {
-    replica_infos.emplace_back();
-    auto* repl_info = &replica_infos.back();
+    replicas.emplace_back();
+    auto* repl_info = &replicas.back();
     repl_info->ts_uuid = replica->ts_uuid();
     VLOG(1) << Substitute("A replica of tablet $0 is on live tablet server $1",
                           tablet->id(), replica->ts_uuid());
@@ -824,7 +843,7 @@ KsckCheckResult Ksck::VerifyTablet(const shared_ptr<KsckTablet>& tablet,
       copying_replicas_count++;
     }
     // Compare the master's and peers' consensus configs.
-    for (const auto& r : replica_infos) {
+    for (const auto& r : replicas) {
       if (r.consensus_state && !r.consensus_state->Matches(master_config)) {
         conflicting_states++;
       }
@@ -883,7 +902,7 @@ KsckCheckResult Ksck::VerifyTablet(const shared_ptr<KsckTablet>& tablet,
   tablet_summary.result = result;
   tablet_summary.status = status;
   tablet_summary.master_cstate = std::move(master_config);
-  tablet_summary.replica_infos.swap(replica_infos);
+  tablet_summary.replicas.swap(replicas);
   results_.tablet_summaries.push_back(std::move(tablet_summary));
   return result;
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/ksck_results.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/ksck_results.cc b/src/kudu/tools/ksck_results.cc
index a38b9b8..29776a6 100644
--- a/src/kudu/tools/ksck_results.cc
+++ b/src/kudu/tools/ksck_results.cc
@@ -32,11 +32,12 @@
 
 #include "kudu/gutil/map-util.h"
 #include "kudu/gutil/strings/substitute.h"
-#include "kudu/tools/tool_action_common.h"
 #include "kudu/tools/color.h"
+#include "kudu/tools/tool.pb.h"
+#include "kudu/tools/tool_action_common.h"
+#include "kudu/util/jsonwriter.h"
 #include "kudu/util/status.h"
 
-using std::cout;
 using std::endl;
 using std::left;
 using std::map;
@@ -127,8 +128,9 @@ const char* const KsckCheckResultToString(KsckCheckResult cr) {
     case KsckCheckResult::RECOVERING:
       return "RECOVERING";
     case KsckCheckResult::UNDER_REPLICATED:
-      return "UNDER-REPLICATED";
+      return "UNDER_REPLICATED";
     case KsckCheckResult::CONSENSUS_MISMATCH:
+      return "CONSENSUS_MISMATCH";
     case KsckCheckResult::UNAVAILABLE:
       return "UNAVAILABLE";
     default:
@@ -174,12 +176,16 @@ int ServerHealthScore(KsckServerHealth sh) {
 }
 
 Status KsckResults::PrintTo(PrintMode mode, ostream& out) {
+  if (mode == PrintMode::JSON_PRETTY || mode == PrintMode::JSON_COMPACT) {
+    return PrintJsonTo(mode, out);
+  }
+
   // First, report on the masters and master tablet.
   std::sort(master_summaries.begin(), master_summaries.end(), ServerByHealthComparator);
   RETURN_NOT_OK(PrintServerHealthSummaries(KsckServerType::MASTER,
                                            master_summaries,
                                            out));
-  if (mode == PrintMode::VERBOSE || master_consensus_conflict) {
+  if (mode == PrintMode::PLAIN_FULL || master_consensus_conflict) {
     RETURN_NOT_OK(PrintConsensusMatrix(master_uuids,
                                        boost::none,
                                        master_consensus_state_map,
@@ -325,12 +331,19 @@ Status PrintTabletSummaries(const vector<KsckTabletSummary>& tablet_summaries,
     out << "The cluster doesn't have any matching tablets" << endl;
     return Status::OK();
   }
+  bool first = true;
   for (const auto& tablet_summary : tablet_summaries) {
-    if (mode != PrintMode::VERBOSE && tablet_summary.result == KsckCheckResult::HEALTHY) {
+    if (first) {
+      first = false;
+    } else {
+      out << endl;
+    }
+    if (mode != PrintMode::PLAIN_FULL &&
+        tablet_summary.result == KsckCheckResult::HEALTHY) {
       continue;
     }
     out << tablet_summary.status << endl;
-    for (const KsckReplicaSummary& r : tablet_summary.replica_infos) {
+    for (const KsckReplicaSummary& r : tablet_summary.replicas) {
       const char* spec_str = r.is_leader
           ? " [LEADER]" : (!r.is_voter ? " [NONVOTER]" : "");
 
@@ -364,11 +377,11 @@ Status PrintTabletSummaries(const vector<KsckTabletSummary>& tablet_summaries,
 
     auto& master_cstate = tablet_summary.master_cstate;
     vector<string> ts_uuids;
-    for (const KsckReplicaSummary& rs : tablet_summary.replica_infos) {
+    for (const KsckReplicaSummary& rs : tablet_summary.replicas) {
       ts_uuids.push_back(rs.ts_uuid);
     }
     KsckConsensusStateMap consensus_state_map;
-    for (const KsckReplicaSummary& rs : tablet_summary.replica_infos) {
+    for (const KsckReplicaSummary& rs : tablet_summary.replicas) {
       if (rs.consensus_state) {
         InsertOrDie(&consensus_state_map, rs.ts_uuid, *rs.consensus_state);
       }
@@ -424,7 +437,7 @@ Status PrintTotalCounts(const KsckResults& results, std::ostream& out) {
                                      results.tablet_summaries.end(),
                                      0,
                                      [](int acc, const KsckTabletSummary& ts) {
-                                       return acc + ts.replica_infos.size();
+                                       return acc + ts.replicas.size();
                                      });
   DataTable totals({ "", "Total Count" });
   totals.AddRow({ "Masters", to_string(results.master_summaries.size()) });
@@ -435,5 +448,201 @@ Status PrintTotalCounts(const KsckResults& results, std::ostream& out) {
   return totals.PrintTo(out);
 }
 
+void KsckServerHealthSummaryToPb(const KsckServerHealthSummary& summary,
+                                 KsckServerHealthSummaryPB* pb) {
+  switch (summary.health) {
+    case KsckServerHealth::HEALTHY:
+      pb->set_health(KsckServerHealthSummaryPB_ServerHealth_HEALTHY);
+      break;
+    case KsckServerHealth::UNAVAILABLE:
+      pb->set_health(KsckServerHealthSummaryPB_ServerHealth_UNAVAILABLE);
+      break;
+    case KsckServerHealth::WRONG_SERVER_UUID:
+      pb->set_health(KsckServerHealthSummaryPB_ServerHealth_WRONG_SERVER_UUID);
+      break;
+    default:
+      pb->set_health(KsckServerHealthSummaryPB_ServerHealth_UNKNOWN);
+  }
+  pb->set_uuid(summary.uuid);
+  pb->set_address(summary.address);
+  pb->set_status(summary.status.ToString());
+}
+
+void KsckConsensusStateToPb(const KsckConsensusState& cstate,
+                            KsckConsensusStatePB* pb) {
+  switch (cstate.type) {
+    case KsckConsensusConfigType::MASTER:
+      pb->set_type(KsckConsensusStatePB_ConfigType_MASTER);
+      break;
+    case KsckConsensusConfigType::COMMITTED:
+      pb->set_type(KsckConsensusStatePB_ConfigType_COMMITTED);
+      break;
+    case KsckConsensusConfigType::PENDING:
+      pb->set_type(KsckConsensusStatePB_ConfigType_PENDING);
+      break;
+    default:
+      pb->set_type(KsckConsensusStatePB_ConfigType_UNKNOWN);
+  }
+  if (cstate.term) {
+    pb->set_term(*cstate.term);
+  }
+  if (cstate.opid_index) {
+    pb->set_opid_index(*cstate.opid_index);
+  }
+  if (cstate.leader_uuid) {
+    pb->set_leader_uuid(*cstate.leader_uuid);
+  }
+  for (const auto& voter_uuid : cstate.voter_uuids) {
+    pb->add_voter_uuids(voter_uuid);
+  }
+  for (const auto& non_voter_uuid : cstate.non_voter_uuids) {
+    pb->add_non_voter_uuids(non_voter_uuid);
+  }
+}
+
+void KsckReplicaSummaryToPb(const KsckReplicaSummary& replica,
+                            KsckReplicaSummaryPB* pb) {
+  pb->set_ts_uuid(replica.ts_uuid);
+  if (replica.ts_address) {
+    pb->set_ts_address(*replica.ts_address);
+  }
+  pb->set_ts_healthy(replica.ts_healthy);
+  pb->set_is_leader(replica.is_leader);
+  pb->set_is_voter(replica.is_voter);
+  pb->set_state(replica.state);
+  if (replica.status_pb) {
+    pb->mutable_status_pb()->CopyFrom(*replica.status_pb);
+  }
+  if (replica.consensus_state) {
+    KsckConsensusStateToPb(*replica.consensus_state, pb->mutable_consensus_state());
+  }
+}
+
+KsckTabletHealthPB KsckTabletHealthToPB(KsckCheckResult c) {
+  switch (c) {
+    case KsckCheckResult::HEALTHY:
+      return KsckTabletHealthPB::HEALTHY;
+    case KsckCheckResult::RECOVERING:
+      return KsckTabletHealthPB::RECOVERING;
+    case KsckCheckResult::UNDER_REPLICATED:
+      return KsckTabletHealthPB::UNDER_REPLICATED;
+    case KsckCheckResult::UNAVAILABLE:
+      return KsckTabletHealthPB::UNAVAILABLE;
+    case KsckCheckResult::CONSENSUS_MISMATCH:
+      return KsckTabletHealthPB::CONSENSUS_MISMATCH;
+    default:
+      return KsckTabletHealthPB::UNKNOWN;
+  }
+}
+
+void KsckTabletSummaryToPb(const KsckTabletSummary& tablet,
+                           KsckTabletSummaryPB* pb) {
+  pb->set_id(tablet.id);
+  pb->set_table_id(tablet.table_id);
+  pb->set_table_name(tablet.table_name);
+  pb->set_health(KsckTabletHealthToPB(tablet.result));
+  pb->set_status(tablet.status);
+  KsckConsensusStateToPb(tablet.master_cstate, pb->mutable_master_cstate());
+  for (const auto& replica : tablet.replicas) {
+    KsckReplicaSummaryToPb(replica, pb->add_replicas());
+  }
+}
+
+void KsckTableSummaryToPb(const KsckTableSummary& table, KsckTableSummaryPB* pb) {
+  pb->set_id(table.id);
+  pb->set_name(table.name);
+  pb->set_health(KsckTabletHealthToPB(table.TableStatus()));
+  pb->set_replication_factor(table.replication_factor);
+  pb->set_total_tablets(table.TotalTablets());
+  pb->set_healthy_tablets(table.healthy_tablets);
+  pb->set_recovering_tablets(table.recovering_tablets);
+  pb->set_underreplicated_tablets(table.underreplicated_tablets);
+  pb->set_unavailable_tablets(table.unavailable_tablets);
+  pb->set_consensus_mismatch_tablets(table.consensus_mismatch_tablets);
+}
+
+void KsckReplicaChecksumToPb(const string& ts_uuid,
+                             const KsckReplicaChecksum& replica,
+                             KsckReplicaChecksumPB* pb) {
+  pb->set_ts_uuid(ts_uuid);
+  pb->set_ts_address(replica.ts_address);
+  pb->set_checksum(replica.checksum);
+  pb->set_status(replica.status.ToString());
+}
+
+void KsckTabletChecksumToPb(const string& tablet_id,
+                            const KsckTabletChecksum& tablet,
+                            KsckTabletChecksumPB* pb) {
+  pb->set_tablet_id(tablet_id);
+  pb->set_mismatch(tablet.mismatch);
+  for (const auto& entry : tablet.replica_checksums) {
+    KsckReplicaChecksumToPb(entry.first, entry.second, pb->add_replica_checksums());
+  }
+}
+
+void KsckTableChecksumToPb(const string& name,
+                           const KsckTableChecksum& table,
+                           KsckTableChecksumPB* pb) {
+  pb->set_name(name);
+  for (const auto& entry : table) {
+    KsckTabletChecksumToPb(entry.first, entry.second, pb->add_tablets());
+  }
+}
+
+void KsckChecksumResultsToPb(const KsckChecksumResults& results,
+                             KsckChecksumResultsPB* pb) {
+  if (results.snapshot_timestamp) {
+    pb->set_snapshot_timestamp(*results.snapshot_timestamp);
+  }
+  for (const auto& entry : results.tables) {
+    KsckTableChecksumToPb(entry.first, entry.second, pb->add_tables());
+  }
+}
+
+void KsckResults::ToPb(KsckResultsPB* pb) const {
+  for (const auto& error : error_messages) {
+    pb->add_errors(error.ToString());
+  }
+
+  for (const auto& master_summary : master_summaries) {
+    KsckServerHealthSummaryToPb(master_summary, pb->add_master_summaries());
+  }
+  for (const auto& tserver_summary : tserver_summaries) {
+    KsckServerHealthSummaryToPb(tserver_summary, pb->add_tserver_summaries());
+  }
+
+  for (const auto& master_uuid : master_uuids) {
+    pb->add_master_uuids(master_uuid);
+  }
+  pb->set_master_consensus_conflict(master_consensus_conflict);
+  for (const auto& entry : master_consensus_state_map) {
+    KsckConsensusStateToPb(entry.second, pb->add_master_consensus_states());
+  }
+
+  for (const auto& tablet : tablet_summaries) {
+    KsckTabletSummaryToPb(tablet, pb->add_tablet_summaries());
+  }
+  for (const auto& table : table_summaries) {
+    KsckTableSummaryToPb(table, pb->add_table_summaries());
+  }
+
+  if (!checksum_results.tables.empty()) {
+    KsckChecksumResultsToPb(checksum_results, pb->mutable_checksum_results());
+  }
+}
+
+Status KsckResults::PrintJsonTo(PrintMode mode, ostream& out) const {
+  CHECK(mode == PrintMode::JSON_PRETTY || mode == PrintMode::JSON_COMPACT);
+  JsonWriter::Mode jw_mode = JsonWriter::Mode::PRETTY;
+  if (mode == PrintMode::JSON_COMPACT) {
+    jw_mode = JsonWriter::Mode::COMPACT;
+  }
+
+  KsckResultsPB pb;
+  ToPb(&pb);
+  out << JsonWriter::ToJson(pb, jw_mode) << endl;
+  return Status::OK();
+}
+
 } // namespace tools
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/ksck_results.h
----------------------------------------------------------------------
diff --git a/src/kudu/tools/ksck_results.h b/src/kudu/tools/ksck_results.h
index 55b8920..c9fba92 100644
--- a/src/kudu/tools/ksck_results.h
+++ b/src/kudu/tools/ksck_results.h
@@ -16,10 +16,10 @@
 // under the License.
 #pragma once
 
+#include <cstdint>
 #include <iosfwd>
 #include <map>
 #include <set>
-#include <stdint.h>
 #include <string>
 #include <utility>
 #include <vector>
@@ -34,6 +34,8 @@
 namespace kudu {
 namespace tools {
 
+class KsckResultsPB;
+
 // The result of health check on a tablet.
 // Also used to indicate the health of a table, since the health of a table is
 // the health of its least healthy tablet.
@@ -208,7 +210,7 @@ struct KsckTabletSummary {
   KsckCheckResult result;
   std::string status;
   KsckConsensusState master_cstate;
-  std::vector<KsckReplicaSummary> replica_infos;
+  std::vector<KsckReplicaSummary> replicas;
 };
 
 // The result of a checksum on a tablet replica.
@@ -237,9 +239,16 @@ struct KsckChecksumResults {
 };
 
 enum class PrintMode {
-  DEFAULT,
-  // Print all results, including for healthy tablets.
-  VERBOSE,
+  // Print results in pretty-printed JSON format.
+  JSON_PRETTY,
+  // Print results in compact JSON format. Differs from JSON_PRETTY only in
+  // format, not content.
+  JSON_COMPACT,
+  // Print results in plain text, focusing on errors and omitting most
+  // information about healthy tablets.
+  PLAIN_CONCISE,
+  // Print results in plain text.
+  PLAIN_FULL,
 };
 
 typedef std::map<std::string, KsckConsensusState> KsckConsensusStateMap;
@@ -270,6 +279,12 @@ struct KsckResults {
 
   // Print this KsckResults to 'out', according to the PrintMode 'mode'.
   Status PrintTo(PrintMode mode, std::ostream& out);
+
+  // Print this KsckResults to 'out' in JSON format.
+  // 'mode' must be PrintMode::JSON_PRETTY or PrintMode::JSON_COMPACT.
+  Status PrintJsonTo(PrintMode mode, std::ostream& out) const;
+
+  void ToPb(KsckResultsPB* pb) const;
 };
 
 // Print a formatted health summary to 'out', given a list `summaries`

http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/tool.proto
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool.proto b/src/kudu/tools/tool.proto
index 2757ee7..e6acb0e 100644
--- a/src/kudu/tools/tool.proto
+++ b/src/kudu/tools/tool.proto
@@ -21,6 +21,8 @@ option java_package = "org.apache.kudu.tools";
 
 import "kudu/common/common.proto";
 import "kudu/common/wire_protocol.proto";
+import "kudu/tablet/metadata.proto";
+import "kudu/tablet/tablet.proto";
 
 // Creates a new ExternalMiniCluster.
 //
@@ -193,3 +195,115 @@ message ControlShellRequestPB {
     KinitRequestPB kinit = 11;
   }
 }
+
+// Results of ksck, the Kudu system check.
+// See the struct analogues of these messages in ksck_results.h.
+message KsckResultsPB {
+  repeated string errors = 1;
+
+  repeated KsckServerHealthSummaryPB master_summaries = 2;
+  repeated KsckServerHealthSummaryPB tserver_summaries = 3;
+
+  repeated string master_uuids = 4;
+  optional bool master_consensus_conflict = 5;
+  repeated KsckConsensusStatePB master_consensus_states = 6;
+
+  repeated KsckTabletSummaryPB tablet_summaries = 7;
+  repeated KsckTableSummaryPB table_summaries = 8;
+
+  optional KsckChecksumResultsPB checksum_results = 9;
+}
+
+message KsckServerHealthSummaryPB {
+  enum ServerHealth {
+    UNKNOWN = 999;
+    HEALTHY = 0;
+    UNAVAILABLE = 1;
+    WRONG_SERVER_UUID = 2;
+  }
+  optional string uuid = 1;
+  optional string address = 2;
+  optional ServerHealth health = 3;
+  optional string status = 4;
+}
+
+message KsckConsensusStatePB {
+  enum ConfigType {
+    UNKNOWN = 999;
+    MASTER = 0;
+    COMMITTED = 1;
+    PENDING = 2;
+  }
+  optional ConfigType type = 1;
+  optional int64 term = 2;
+  optional int64 opid_index = 3;
+  optional string leader_uuid = 4;
+  repeated string voter_uuids = 5;
+  repeated string non_voter_uuids = 6;
+}
+
+enum KsckTabletHealthPB {
+  UNKNOWN = 999;
+  HEALTHY = 0;
+  RECOVERING = 1;
+  UNDER_REPLICATED = 2;
+  UNAVAILABLE = 3;
+  CONSENSUS_MISMATCH = 4;
+}
+
+message KsckTabletSummaryPB {
+  optional string id = 1;
+  optional string table_id = 2;
+  optional string table_name = 3;
+  optional KsckTabletHealthPB health = 4;
+  optional string status = 5;
+  optional KsckConsensusStatePB master_cstate = 6;
+  repeated KsckReplicaSummaryPB replicas = 7;
+}
+
+message KsckReplicaSummaryPB {
+  optional string ts_uuid = 1;
+  optional string ts_address = 2;
+  optional bool ts_healthy = 3;
+  optional bool is_leader = 4;
+  optional bool is_voter = 5;
+  optional tablet.TabletStatePB state = 6;
+  optional tablet.TabletStatusPB status_pb = 7;
+  optional KsckConsensusStatePB consensus_state = 8;
+}
+
+message KsckTableSummaryPB {
+  optional string id = 1;
+  optional string name = 2;
+  optional KsckTabletHealthPB health = 3;
+  optional int32 replication_factor = 4;
+  optional int32 total_tablets = 5;
+  optional int32 healthy_tablets = 6;
+  optional int32 recovering_tablets = 7;
+  optional int32 underreplicated_tablets = 8;
+  optional int32 unavailable_tablets = 9;
+  optional int32 consensus_mismatch_tablets = 10;
+}
+
+message KsckChecksumResultsPB {
+  optional fixed64 snapshot_timestamp = 1;
+  repeated KsckTableChecksumPB tables = 2;
+}
+
+message KsckTableChecksumPB {
+  optional string name = 1;
+  repeated KsckTabletChecksumPB tablets = 2;
+}
+
+message KsckTabletChecksumPB {
+  optional string tablet_id = 1;
+  optional bool mismatch = 2;
+  repeated KsckReplicaChecksumPB replica_checksums = 3;
+}
+
+message KsckReplicaChecksumPB {
+  optional string ts_address = 1;
+  optional string ts_uuid = 2;
+  optional string status = 3;
+  optional fixed64 checksum = 4;
+}

http://git-wip-us.apache.org/repos/asf/kudu/blob/ee0a75c5/src/kudu/tools/tool_action_cluster.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action_cluster.cc b/src/kudu/tools/tool_action_cluster.cc
index 662ad64..e225254 100644
--- a/src/kudu/tools/tool_action_cluster.cc
+++ b/src/kudu/tools/tool_action_cluster.cc
@@ -101,9 +101,9 @@ unique_ptr<Mode> BuildClusterMode() {
       .AddOptionalParameter("checksum_timeout_sec")
       .AddOptionalParameter("color")
       .AddOptionalParameter("consensus")
+      .AddOptionalParameter("ksck_format")
       .AddOptionalParameter("tables")
       .AddOptionalParameter("tablets")
-      .AddOptionalParameter("verbose")
       .Build();
 
   return ModeBuilder("cluster")


Mime
View raw message