in src/kudu/master/master_path_handlers.cc [322:563]
void MasterPathHandlers::HandleTablePage(const Webserver::WebRequest& req,
Webserver::WebResponse* resp) {
EasyJson* output = &resp->output;
// Parse argument.
string table_id;
if (!FindCopy(req.parsed_args, "id", &table_id)) {
resp->status_code = HttpStatusCode::BadRequest;
(*output)["error"] = "Missing 'id' argument";
return;
}
CatalogManager::ScopedLeaderSharedLock l(master_->catalog_manager());
if (!l.catalog_status().ok()) {
(*output)["error"] = Substitute("Master is not ready: $0", l.catalog_status().ToString());
return;
}
if (!l.leader_status().ok()) {
// It's possible to respond 307 Temporary Redirect and automatically redirect with
// a Location header, but this would likely confuse users about which master's web ui
// they are looking at. Instead, we show a link users can click to go to the leader master.
// We track redirects to prevent a redirect loop.
int redirects = ExtractRedirectsFromRequest(req);
SetupLeaderMasterRedirect(Substitute("table?id=$0", table_id), redirects, output);
return;
}
scoped_refptr<TableInfo> table;
Status s = master_->catalog_manager()->GetTableInfo(table_id, &table);
if (!s.ok()) {
resp->status_code = HttpStatusCode::ServiceUnavailable;
(*output)["error"] = Substitute("Master is not ready: $0", s.ToString());
return;
}
if (!table) {
resp->status_code = HttpStatusCode::NotFound;
(*output)["error"] = "Table not found";
return;
}
Schema schema;
PartitionSchema partition_schema;
map<string, string> extra_configs;
vector<scoped_refptr<TabletInfo>> tablets;
{
TableMetadataLock l(table.get(), LockMode::READ);
(*output)["name"] = l.data().name();
(*output)["owner"] = l.data().owner();
(*output)["comment"] = l.data().comment();
// Not all Kudu tablenames are also valid Impala identifiers. We need to
// replace such names with a placeholder when they are used as Impala
// identifiers, like when used for Impala tablename in the Kudu Web UI.
// Valid Impala identifiers conform to the following rules:
// 1. Must not be empty.
// 2. Length must not exceed 128 characters.
// 3. First character must be alphabetic.
// 4. Every character must be alphanumeric or an underscore.
// 5. If it matches any Impala-reserved keyword, it must be surrounded
// by backticks.
// The last rule is not tested for in the code segment below as the backticks
// can be added at the time of usage, as is the case for Web UI where it's
// currently being passed to.
string impala_name = "<placeholder>";
string orig_name = l.data().name();
if (!orig_name.empty() &&
orig_name.length() <= 128 &&
ascii_isalpha(orig_name[0]) &&
find_if(orig_name.begin(), orig_name.end(), [](char c) {
return !(ascii_isalnum(c) || (c == '_'));
}) == orig_name.end()) {
impala_name = orig_name;
}
(*output)["impala_table_name"] = impala_name;
(*output)["id"] = table_id;
(*output)["version"] = l.data().pb.version();
string state = SysTablesEntryPB_State_Name(l.data().pb.state());
Capitalize(&state);
(*output)["state"] = state;
string state_msg = l.data().pb.state_msg();
if (!state_msg.empty()) {
(*output)["state_msg"] = state_msg;
}
s = SchemaFromPB(l.data().pb.schema(), &schema);
if (!s.ok()) {
(*output)["error"] = Substitute("Unable to decode schema: $0", s.ToString());
return;
}
// On platforms where !std::is_same<size_t, uint64_t>::value
// (e.g., on macOS 'size_t' is a typedef for 'unsigned long',
// but 'uint64_t' is a typedef for 'unsigned long long'),
// EasyJson does not have appropriate assignment operator defined. Adding
// a static_cast<uint64_t> here is a reasonable stopgap for those cases.
(*output)["column_count"] = static_cast<uint64_t>(schema.num_columns());
s = PartitionSchema::FromPB(l.data().pb.partition_schema(), schema, &partition_schema);
if (!s.ok()) {
(*output)["error"] =
Substitute("Unable to decode partition schema: $0", s.ToString());
return;
}
s = ExtraConfigPBToMap(l.data().pb.extra_config(), &extra_configs);
if (!s.ok()) {
(*output)["error"] =
Substitute("Unable to decode extra configuration properties: $0", s.ToString());
return;
}
table->GetAllTablets(&tablets);
}
SchemaToJson(schema, output);
// We have to collate partition schema and tablet information in order to set
// up the partition schema, tablet summary, and tablet detail tables.
std::vector<string> range_partitions;
map<string, int> summary_states;
map<string, int> replica_counts;
(*output)["detail_partition_schema_header"] = partition_schema.PartitionTableHeader(schema);
EasyJson tablets_detail_json = output->Set("tablets_detail", EasyJson::kArray);
for (const scoped_refptr<TabletInfo>& tablet : tablets) {
vector<pair<TabletDetailPeerInfo, RaftPeerPB::Role>> sorted_replicas;
TabletMetadataLock l(tablet.get(), LockMode::READ);
// Count states for tablet summary.
summary_states[SysTabletsEntryPB_State_Name(l.data().pb.state())]++;
// Collect details about each tablet replica.
if (l.data().pb.has_consensus_state()) {
const ConsensusStatePB& cstate = l.data().pb.consensus_state();
for (const auto& peer : cstate.committed_config().peers()) {
replica_counts[peer.permanent_uuid()]++;
TabletDetailPeerInfo peer_info;
shared_ptr<TSDescriptor> ts_desc;
if (master_->ts_manager()->LookupTSByUUID(peer.permanent_uuid(), &ts_desc)) {
auto link_pair = TSDescToLinkPair(*ts_desc.get(), tablet->id());
peer_info.text = std::move(link_pair.first);
peer_info.target = std::move(link_pair.second);
} else {
peer_info.text = peer.permanent_uuid();
}
RaftPeerPB::Role role = GetConsensusRole(peer.permanent_uuid(), cstate);
peer_info.role = RaftPeerPB_Role_Name(role);
peer_info.is_leader = role == RaftPeerPB::LEADER;
sorted_replicas.emplace_back(std::make_pair(peer_info, role));
}
}
std::sort(sorted_replicas.begin(), sorted_replicas.end(), &CompareByRole);
// Generate a readable description of the partition of each tablet, used
// both for each tablet's details and the readable range partition schema.
Partition partition;
Partition::FromPB(l.data().pb.partition(), &partition);
// For each unique range partition, add a debug string to range_partitions.
// To ensure uniqueness, only use partitions whose hash buckets are all 0.
if (std::all_of(partition.hash_buckets().begin(),
partition.hash_buckets().end(),
[] (const int32_t& bucket) { return bucket == 0; })) {
range_partitions.emplace_back(
partition_schema.RangeWithCustomHashPartitionDebugString(partition.begin().range_key(),
partition.end().range_key(),
schema));
}
// Combine the tablet details and partition info for each tablet.
EasyJson tablet_detail_json = tablets_detail_json.PushBack(EasyJson::kObject);
tablet_detail_json["id"] = tablet->id();
tablet_detail_json["partition_cols"] = partition_schema.PartitionTableEntry(schema, partition);
string state = SysTabletsEntryPB_State_Name(l.data().pb.state());
Capitalize(&state);
tablet_detail_json["state"] = state;
tablet_detail_json["state_msg"] = l.data().pb.state_msg();
tablet_detail_json["on_disk_size"] =
HumanReadableNumBytes::ToString(tablet->GetStats().on_disk_size());
EasyJson peers_json = tablet_detail_json.Set("peers", EasyJson::kArray);
for (const auto& e : sorted_replicas) {
EasyJson peer_json = peers_json.PushBack(EasyJson::kObject);
peer_json["text"] = e.first.text;
if (!e.first.target.empty()) {
peer_json["target"] = e.first.target;
}
peer_json["role"] = e.first.role;
peer_json["is_leader"] = e.first.is_leader;
}
}
const TableMetrics* table_metrics = table->GetMetrics();
if (table_metrics) {
// If the table doesn't support 'on disk size' and 'live row count',
// we need to show their values as "N/A".
if (table_metrics->TableSupportsOnDiskSize()) {
(*output)["table_disk_size"] =
HumanReadableNumBytes::ToString(table_metrics->on_disk_size->value());
} else {
(*output)["table_disk_size"] = "N/A";
}
if (table_metrics->TableSupportsLiveRowCount()) {
(*output)["table_live_row_count"] = table_metrics->live_row_count->value();
} else {
(*output)["table_live_row_count"] = "N/A";
}
}
(*output)["partition_schema"] = partition_schema.DisplayString(schema, range_partitions);
string str_extra_configs;
JoinMapKeysAndValues(extra_configs, " : ", "\n", &str_extra_configs);
(*output)["extra_config"] = str_extra_configs;
EasyJson summary_json = output->Set("tablets_summary", EasyJson::kArray);
for (const auto& entry : summary_states) {
EasyJson state_json = summary_json.PushBack(EasyJson::kObject);
state_json["state"] = entry.first;
state_json["count"] = entry.second;
double percentage = (100.0 * entry.second) / tablets.size();
state_json["percentage"] = tablets.empty() ? "0.0" : StringPrintf("%.2f", percentage);
}
// Set up the report on replica distribution.
EasyJson replica_dist_json = output->Set("replica_distribution", EasyJson::kObject);
EasyJson counts_json = replica_dist_json.Set("counts", EasyJson::kArray);
int min_count = replica_counts.empty() ? 0 : std::numeric_limits<int>::max();
int max_count = 0;
for (const auto& entry : replica_counts) {
EasyJson count_json = counts_json.PushBack(EasyJson::kObject);
count_json["ts_uuid"] = entry.first;
count_json["count"] = entry.second;
min_count = std::min(min_count, entry.second);
max_count = std::max(max_count, entry.second);
}
replica_dist_json["max_count"] = max_count;
replica_dist_json["min_count"] = min_count;
replica_dist_json["skew"] = max_count - min_count;
// Used to make the Impala CREATE TABLE statement.
(*output)["master_addresses"] = MasterAddrsToCsv();
std::vector<scoped_refptr<MonitoredTask>> task_list;
table->GetTaskList(&task_list);
TaskListToJson(task_list, output);
}