YugabyteDB (2.13.1.0-b60, 21121d69985fbf76aa6958d8f04a9bfa936293b5)

Coverage Report

Created: 2022-03-22 16:43

/Users/deen/code/yugabyte-db/src/yb/tserver/tserver-path-handlers.cc
Line
Count
Source (jump to first uncovered line)
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
//
18
// The following only applies to changes made to this file as part of YugaByte development.
19
//
20
// Portions Copyright (c) YugaByte, Inc.
21
//
22
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
23
// in compliance with the License.  You may obtain a copy of the License at
24
//
25
// http://www.apache.org/licenses/LICENSE-2.0
26
//
27
// Unless required by applicable law or agreed to in writing, software distributed under the License
28
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
29
// or implied.  See the License for the specific language governing permissions and limitations
30
// under the License.
31
//
32
33
#include "yb/tserver/tserver-path-handlers.h"
34
35
#include <algorithm>
36
#include <functional>
37
#include <memory>
38
#include <sstream>
39
#include <string>
40
#include <vector>
41
42
#include "yb/consensus/consensus.h"
43
#include "yb/consensus/consensus.pb.h"
44
#include "yb/consensus/log_anchor_registry.h"
45
#include "yb/consensus/quorum_util.h"
46
47
#include "yb/gutil/map-util.h"
48
#include "yb/gutil/strings/human_readable.h"
49
#include "yb/gutil/strings/join.h"
50
#include "yb/gutil/strings/numbers.h"
51
#include "yb/gutil/strings/substitute.h"
52
53
#include "yb/rocksdb/db.h"
54
#include "yb/rocksdb/util/options_parser.h"
55
56
#include "yb/server/webui_util.h"
57
58
#include "yb/tablet/maintenance_manager.h"
59
#include "yb/tablet/tablet.h"
60
#include "yb/tablet/tablet.pb.h"
61
#include "yb/tablet/tablet_bootstrap_if.h"
62
#include "yb/tablet/tablet_metadata.h"
63
#include "yb/tablet/tablet_peer.h"
64
#include "yb/tablet/transaction_participant.h"
65
66
#include "yb/tserver/tablet_server.h"
67
#include "yb/tserver/ts_tablet_manager.h"
68
69
#include "yb/util/jsonwriter.h"
70
#include "yb/util/url-coding.h"
71
#include "yb/util/version_info.h"
72
#include "yb/util/version_info.pb.h"
73
74
namespace {
75
76
// A struct representing some information about a tablet peer.
77
struct TabletPeerInfo {
78
  string namespace_name;
79
  string name;
80
  uint64_t num_sst_files;
81
  yb::tablet::TabletOnDiskSizeInfo disk_size_info;
82
  bool has_on_disk_size;
83
  yb::PeerRole raft_role;
84
};
85
86
// An identifier for a table, according to the `/tables` page.
87
struct TableIdentifier {
88
  string uuid;
89
  string state;
90
};
91
92
// A struct representing some information about a table.
93
struct TableInfo {
94
  string namespace_name;
95
  string name;
96
  uint64_t num_sst_files;
97
  yb::tablet::TabletOnDiskSizeInfo disk_size_info;
98
  bool has_complete_on_disk_size;
99
  std::map<yb::PeerRole, size_t> raft_role_counts;
100
101
  explicit TableInfo(TabletPeerInfo info)
102
      : namespace_name(info.namespace_name),
103
        name(info.name),
104
        num_sst_files(info.num_sst_files),
105
        disk_size_info(info.disk_size_info),
106
0
        has_complete_on_disk_size(info.has_on_disk_size) {
107
0
    raft_role_counts.emplace(info.raft_role, 1);
108
0
  }
109
110
  // Adds information about a single tablet peer to a table.
111
0
  void Aggregate(const TabletPeerInfo& other) {
112
0
    auto rc_iter = raft_role_counts.find(other.raft_role);
113
0
    if (rc_iter == raft_role_counts.end()) {
114
0
      raft_role_counts.emplace(other.raft_role, 1);
115
0
    } else {
116
0
      ++rc_iter->second;
117
0
    }
118
119
0
    num_sst_files += other.num_sst_files;
120
0
    disk_size_info += other.disk_size_info;
121
0
    has_complete_on_disk_size = has_complete_on_disk_size && other.has_on_disk_size;
122
0
  }
123
};
124
125
}  // anonymous namespace
126
127
namespace std {
128
129
template<>
130
struct less<TableIdentifier> {
131
0
  bool operator() (const TableIdentifier& lhs, const TableIdentifier& rhs) const {
132
0
    if (lhs.uuid == rhs.uuid) {
133
0
      return lhs.state < rhs.state;
134
0
    } else {
135
0
      return lhs.uuid < rhs.uuid;
136
0
    }
137
0
  }
138
};
139
140
}  //  namespace std
141
142
namespace yb {
143
namespace tserver {
144
145
using yb::consensus::GetConsensusRole;
146
using yb::consensus::CONSENSUS_CONFIG_COMMITTED;
147
using yb::consensus::ConsensusStatePB;
148
using yb::consensus::RaftPeerPB;
149
using yb::consensus::OperationStatusPB;
150
using yb::tablet::MaintenanceManagerStatusPB;
151
using yb::tablet::MaintenanceManagerStatusPB_CompletedOpPB;
152
using yb::tablet::MaintenanceManagerStatusPB_MaintenanceOpPB;
153
using yb::tablet::Tablet;
154
using yb::tablet::TabletDataState;
155
using yb::tablet::TabletPeer;
156
using yb::tablet::TabletStatusPB;
157
using yb::tablet::Operation;
158
using std::endl;
159
using std::shared_ptr;
160
using std::vector;
161
using strings::Substitute;
162
163
using namespace std::placeholders;  // NOLINT(build/namespaces)
164
165
namespace {
166
167
42
bool GetTabletID(const Webserver::WebRequest& req, string* id, std::stringstream *out) {
168
42
  if (!FindCopy(req.parsed_args, "id", id)) {
169
    // TODO: webserver should give a way to return a non-200 response code
170
3
    (*out) << "Tablet missing 'id' argument";
171
3
    return false;
172
3
  }
173
39
  return true;
174
42
}
175
176
bool GetTabletPeer(TabletServer* tserver, const Webserver::WebRequest& req,
177
                   std::shared_ptr<TabletPeer>* peer, const string& tablet_id,
178
39
                   std::stringstream *out) {
179
39
  if (!tserver->tablet_manager()->LookupTablet(tablet_id, peer)) {
180
0
    (*out) << "Tablet " << EscapeForHtmlToString(tablet_id) << " not found";
181
0
    return false;
182
0
  }
183
39
  return true;
184
39
}
185
186
bool TabletBootstrapping(const std::shared_ptr<TabletPeer>& peer, const string& tablet_id,
187
39
                         std::stringstream* out) {
188
39
  if (peer->state() == tablet::BOOTSTRAPPING) {
189
0
    (*out) << "Tablet " << EscapeForHtmlToString(tablet_id) << " is still bootstrapping";
190
0
    return false;
191
0
  }
192
39
  return true;
193
39
}
194
195
// Returns true if the tablet_id was properly specified, the
196
// tablet is found, and is in a non-bootstrapping state.
197
bool LoadTablet(TabletServer* tserver,
198
                const Webserver::WebRequest& req,
199
                string* tablet_id, std::shared_ptr<TabletPeer>* peer,
200
42
                std::stringstream* out) {
201
42
  if (!GetTabletID(req, tablet_id, out)) 
return false3
;
202
39
  if (!GetTabletPeer(tserver, req, peer, *tablet_id, out)) 
return false0
;
203
39
  if (!TabletBootstrapping(*peer, *tablet_id, out)) 
return false0
;
204
39
  return true;
205
39
}
206
207
void HandleTabletPage(
208
    const std::string& tablet_id, const tablet::TabletPeerPtr& peer,
209
1
    const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
210
1
  std::stringstream *output = &resp->output;
211
1
  string table_name = peer->tablet_metadata()->table_name();
212
213
1
  *output << "<h1>Tablet " << EscapeForHtmlToString(tablet_id) << "</h1>\n";
214
215
  // Output schema in tabular format.
216
1
  *output << "<h2>Schema</h2>\n";
217
1
  const SchemaPtr schema = peer->tablet_metadata()->schema();
218
1
  server::HtmlOutputSchemaTable(*schema, output);
219
220
1
  *output << "<h2>Other Tablet Info Pages</h2>" << endl;
221
222
  // List of links to various tablet-specific info pages
223
1
  *output << "<ul>";
224
225
1
  std::initializer_list<std::array<const char*, 2>> entries = {
226
1
      {"tablet-consensus-status", "Consensus Status"},
227
1
      {"log-anchors", "Tablet Log Anchors"},
228
1
      {"transactions", "Transactions"},
229
1
      {"rocksdb", "RocksDB" }};
230
231
1
  auto encoded_tablet_id = UrlEncodeToString(tablet_id);
232
4
  for (const auto& entry : entries) {
233
4
    *output << Format("<li><a href=\"/$0?id=$2\">$1</a></li>\n",
234
4
                      entry[0], entry[1], encoded_tablet_id);
235
4
  }
236
237
  // End list
238
1
  *output << "</ul>\n";
239
1
}
240
241
void HandleLogAnchorsPage(
242
    const std::string& tablet_id, const tablet::TabletPeerPtr& peer,
243
0
    const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
244
0
  std::stringstream *output = &resp->output;
245
0
  *output << "<h1>Log Anchors for Tablet " << EscapeForHtmlToString(tablet_id) << "</h1>"
246
0
          << std::endl;
247
248
0
  string dump = peer->log_anchor_registry()->DumpAnchorInfo();
249
0
  *output << "<pre>" << EscapeForHtmlToString(dump) << "</pre>" << std::endl;
250
0
  std::string retain_op_id_details;
251
0
  auto result = peer->GetEarliestNeededLogIndex(&retain_op_id_details);
252
0
  *output << "<pre>";
253
0
  if (result.ok()) {
254
0
    *output << EscapeForHtmlToString(retain_op_id_details);
255
0
  } else {
256
0
    *output << EscapeForHtmlToString(result.status().ToString());
257
0
  }
258
0
  *output << "</pre>" << std::endl;
259
0
}
260
261
void HandleConsensusStatusPage(
262
    const std::string& tablet_id, const tablet::TabletPeerPtr& peer,
263
38
    const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
264
38
  std::stringstream *output = &resp->output;
265
38
  shared_ptr<consensus::Consensus> consensus = peer->shared_consensus();
266
38
  if (!consensus) {
267
0
    *output << "Tablet " << EscapeForHtmlToString(tablet_id) << " not running";
268
0
    return;
269
0
  }
270
271
38
  *output << "<h1>Tablet " << EscapeForHtmlToString(tablet_id) << "</h1>\n";
272
38
  consensus->DumpStatusHtml(*output);
273
38
}
274
275
void HandleTransactionsPage(
276
    const std::string& tablet_id, const tablet::TabletPeerPtr& peer,
277
0
    const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
278
0
  std::stringstream *output = &resp->output;
279
0
  auto tablet = peer->shared_tablet();
280
0
  if (!tablet) {
281
0
    *output << "Tablet " << EscapeForHtmlToString(tablet_id) << " not running";
282
0
    return;
283
0
  }
284
285
0
  *output << "<h1>Transactions for Tablet " << EscapeForHtmlToString(tablet_id) << "</h1>"
286
0
          << std::endl;
287
288
0
  auto transaction_participant = tablet->transaction_participant();
289
0
  if (transaction_participant) {
290
0
    *output << "<pre>" << EscapeForHtmlToString(transaction_participant->DumpTransactions())
291
0
            << "</pre>" << std::endl;
292
0
    return;
293
0
  }
294
295
0
  auto transaction_coordinator = tablet->transaction_coordinator();
296
0
  if (transaction_coordinator) {
297
0
    *output << "<pre>" << EscapeForHtmlToString(transaction_coordinator->DumpTransactions())
298
0
            << "</pre>" << std::endl;
299
0
    return;
300
0
  }
301
302
0
  *output << "Tablet is non transactional";
303
0
}
304
305
0
void DumpRocksDBOptions(rocksdb::DB* db, std::stringstream* out) {
306
0
    std::vector<std::string> cf_names;
307
0
    std::vector<rocksdb::ColumnFamilyOptions> cf_options;
308
0
    db->GetColumnFamiliesOptions(&cf_names, &cf_options);
309
310
0
    auto env = rocksdb::NewMemEnv(db->GetEnv());
311
0
    const std::string tag_id = Uuid::Generate().ToHexString();
312
0
    *out << "<input type=\"checkbox\" id=\"" << tag_id << "\" class=\"yb-collapsible-cb\"/>"
313
0
         << "<label for=\"" << tag_id << "\"><h3>Options</h3></label>" << std::endl
314
0
         << "<pre>" << std::endl;
315
316
0
    std::string content;
317
0
    auto status = rocksdb::PersistRocksDBOptions(db->GetDBOptions(),
318
0
                                                 cf_names, cf_options, "opts", env,
319
0
                                                 rocksdb::IncludeHeader::kFalse,
320
0
                                                 rocksdb::IncludeFileVersion::kFalse);
321
0
    if (PREDICT_TRUE(status.ok())) {
322
0
      status = rocksdb::ReadFileToString(env, "opts", &content);
323
0
    }
324
0
    if (PREDICT_TRUE(status.ok())) {
325
0
      EscapeForHtml(content, out);
326
0
    } else {
327
0
      *out << "Failed to get options: " << status << std::endl;
328
0
    }
329
0
    *out << "</pre>" << std::endl;
330
0
    delete env;
331
0
}
332
333
0
void DumpRocksDB(const char* title, rocksdb::DB* db, std::stringstream* out) {
334
0
  if (db) {
335
0
    *out << "<h2>" << title << "</h2>" << std::endl;
336
0
    DumpRocksDBOptions(db, out);
337
338
0
    *out << "<h3>Files</h3>" << std::endl;
339
0
    auto files = db->GetLiveFilesMetaData();
340
0
    *out << "<pre>" << std::endl;
341
0
    for (const auto& file : files) {
342
0
      *out << file.ToString() << std::endl;
343
0
    }
344
0
    *out << "</pre>" << std::endl;
345
346
0
    rocksdb::TablePropertiesCollection properties;
347
0
    auto status = db->GetPropertiesOfAllTables(&properties);
348
0
    if (status.ok()) {
349
0
      for (const auto& p : properties) {
350
0
        *out << "<h3>" << EscapeForHtmlToString(p.first) << " properties</h3>" << std::endl;
351
0
        *out << "<pre>" << p.second->ToString("\n") << "</pre>" << std::endl;
352
0
      }
353
0
    } else {
354
0
      *out << "Failed to get properties: " << status << std::endl;
355
0
    }
356
0
  }
357
0
}
358
359
void HandleRocksDBPage(
360
    const std::string& tablet_id, const tablet::TabletPeerPtr& peer,
361
0
    const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
362
0
  std::stringstream *output = &resp->output;
363
0
  *output << "<h1>RocksDB for Tablet " << EscapeForHtmlToString(tablet_id) << "</h1>" << std::endl;
364
365
0
  auto doc_db = peer->tablet()->doc_db();
366
0
  DumpRocksDB("Regular", doc_db.regular, output);
367
0
  DumpRocksDB("Intents", doc_db.intents, output);
368
0
}
369
370
template<class F>
371
void RegisterTabletPathHandler(
372
43.7k
    Webserver* web_server, TabletServer* tserver, const std::string& path, const F& f) {
373
43.7k
  auto handler = [tserver, f](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
374
42
    std::stringstream *output = &resp->output;
375
42
    string tablet_id;
376
42
    tablet::TabletPeerPtr peer;
377
42
    if (!LoadTablet(tserver, req, &tablet_id, &peer, output)) 
return3
;
378
379
39
    f(tablet_id, peer, req, resp);
380
39
  };
381
43.7k
  web_server->RegisterPathHandler(path, "", handler, true /* styled */, false /* is_on_nav_bar */);
382
43.7k
}
383
384
}  // namespace
385
386
164
TabletServerPathHandlers::~TabletServerPathHandlers() {
387
164
}
388
389
8.74k
Status TabletServerPathHandlers::Register(Webserver* server) {
390
8.74k
  server->RegisterPathHandler(
391
8.74k
      "/tables", "Tables", std::bind(&TabletServerPathHandlers::HandleTablesPage, this, _1, _2),
392
8.74k
      true /* styled */, true /* is_on_nav_bar */, "fa fa-table");
393
8.74k
  server->RegisterPathHandler(
394
8.74k
      "/tablets", "Tablets", std::bind(&TabletServerPathHandlers::HandleTabletsPage, this, _1, _2),
395
8.74k
      true /* styled */, true /* is_on_nav_bar */, "fa fa-server");
396
8.74k
  RegisterTabletPathHandler(server, tserver_, "/tablet", &HandleTabletPage);
397
8.74k
  server->RegisterPathHandler(
398
8.74k
      "/operations", "",
399
8.74k
      std::bind(&TabletServerPathHandlers::HandleOperationsPage, this, _1, _2), true /* styled */,
400
8.74k
      false /* is_on_nav_bar */);
401
8.74k
  RegisterTabletPathHandler(
402
8.74k
      server, tserver_, "/tablet-consensus-status", &HandleConsensusStatusPage);
403
8.74k
  RegisterTabletPathHandler(server, tserver_, "/log-anchors", &HandleLogAnchorsPage);
404
8.74k
  RegisterTabletPathHandler(server, tserver_, "/transactions", &HandleTransactionsPage);
405
8.74k
  RegisterTabletPathHandler(server, tserver_, "/rocksdb", &HandleRocksDBPage);
406
8.74k
  server->RegisterPathHandler(
407
8.74k
      "/", "Dashboards",
408
8.74k
      std::bind(&TabletServerPathHandlers::HandleDashboardsPage, this, _1, _2), true /* styled */,
409
8.74k
      true /* is_on_nav_bar */, "fa fa-dashboard");
410
8.74k
  server->RegisterPathHandler(
411
8.74k
      "/maintenance-manager", "",
412
8.74k
      std::bind(&TabletServerPathHandlers::HandleMaintenanceManagerPage, this, _1, _2),
413
8.74k
      true /* styled */, false /* is_on_nav_bar */);
414
8.74k
  server->RegisterPathHandler(
415
8.74k
      "/api/v1/health-check", "TServer Health Check",
416
8.74k
      std::bind(&TabletServerPathHandlers::HandleHealthCheck, this, _1, _2),
417
8.74k
      false /* styled */, false /* is_on_nav_bar */);
418
8.74k
  server->RegisterPathHandler(
419
8.74k
      "/api/v1/version", "YB Version Information",
420
8.74k
      std::bind(&TabletServerPathHandlers::HandleVersionInfoDump, this, _1, _2),
421
8.74k
      false /* styled */, false /* is_on_nav_bar */);
422
8.74k
  return Status::OK();
423
8.74k
}
424
425
void TabletServerPathHandlers::HandleVersionInfoDump(const Webserver::WebRequest& req,
426
0
                                                    Webserver::WebResponse* resp) {
427
  // Get the version info.
428
0
  VersionInfoPB version_info;
429
0
  VersionInfo::GetVersionInfoPB(&version_info);
430
431
0
  std::stringstream *output = &resp->output;
432
0
  JsonWriter jw(output, JsonWriter::PRETTY);
433
434
0
  jw.Protobuf(version_info);
435
0
}
436
437
void TabletServerPathHandlers::HandleOperationsPage(const Webserver::WebRequest& req,
438
0
                                                    Webserver::WebResponse* resp) {
439
0
  std::stringstream *output = &resp->output;
440
0
  bool as_text = ContainsKey(req.parsed_args, "raw");
441
442
0
  auto peers = tserver_->tablet_manager()->GetTabletPeers();
443
444
0
  string arg = FindWithDefault(req.parsed_args, "include_traces", "false");
445
0
  Operation::TraceType trace_type = ParseLeadingBoolValue(
446
0
      arg.c_str(), false) ? Operation::TRACE_TXNS : Operation::NO_TRACE_TXNS;
447
448
0
  if (!as_text) {
449
0
    *output << "<h1>Transactions</h1>\n";
450
0
    *output << "<table class='table table-striped'>\n";
451
0
    *output << "   <tr><th>Tablet id</th><th>Op Id</th>"
452
0
      "<th>Transaction Type</th><th>"
453
0
      "Total time in-flight</th><th>Description</th></tr>\n";
454
0
  }
455
456
0
  for (const std::shared_ptr<TabletPeer>& peer : peers) {
457
0
    vector<OperationStatusPB> inflight;
458
459
0
    if (peer->tablet() == nullptr) {
460
0
      continue;
461
0
    }
462
463
0
    peer->GetInFlightOperations(trace_type, &inflight);
464
0
    for (const auto& inflight_tx : inflight) {
465
0
      string total_time_str = Substitute("$0 us.", inflight_tx.running_for_micros());
466
0
      string description;
467
0
      if (trace_type == Operation::TRACE_TXNS) {
468
0
        description = Substitute("$0, Trace: $1",
469
0
                                  inflight_tx.description(), inflight_tx.trace_buffer());
470
0
      } else {
471
0
        description = inflight_tx.description();
472
0
      }
473
474
0
      if (!as_text) {
475
0
        (*output) << Substitute(
476
0
          "<tr><th>$0</th><th>$1</th><th>$2</th><th>$3</th><th>$4</th></tr>\n",
477
0
          EscapeForHtmlToString(peer->tablet_id()),
478
0
          EscapeForHtmlToString(inflight_tx.op_id().ShortDebugString()),
479
0
          OperationType_Name(inflight_tx.operation_type()),
480
0
          total_time_str,
481
0
          EscapeForHtmlToString(description));
482
0
      } else {
483
0
        (*output) << "Tablet: " << peer->tablet_id() << endl;
484
0
        (*output) << "Op ID: " << inflight_tx.op_id().ShortDebugString() << endl;
485
0
        (*output) << "Type: " << OperationType_Name(inflight_tx.operation_type()) << endl;
486
0
        (*output) << "Running: " << total_time_str;
487
0
        (*output) << description << endl;
488
0
        (*output) << endl;
489
0
      }
490
0
    }
491
0
  }
492
493
0
  if (!as_text) {
494
0
    *output << "</table>\n";
495
0
  }
496
0
}
497
498
namespace {
499
12
string TabletLink(const string& id) {
500
12
  return Substitute("<a href=\"/tablet?id=$0\">$1</a>",
501
12
                    UrlEncodeToString(id),
502
12
                    EscapeForHtmlToString(id));
503
12
}
504
505
bool CompareByTabletId(const std::shared_ptr<TabletPeer>& a,
506
10
                       const std::shared_ptr<TabletPeer>& b) {
507
10
  return a->tablet_id() < b->tablet_id();
508
10
}
509
510
13
string GetOnDiskSizeInHtml(const yb::tablet::TabletOnDiskSizeInfo& info) {
511
13
  std::ostringstream disk_size_html;
512
13
  disk_size_html << "<ul>"
513
13
                 << "<li>" << "Total: "
514
13
                 << HumanReadableNumBytes::ToString(info.sum_on_disk_size)
515
13
                 << "<li>" << "Consensus Metadata: "
516
13
                 << HumanReadableNumBytes::ToString(info.consensus_metadata_disk_size)
517
13
                 << "<li>" << "WAL Files: "
518
13
                 << HumanReadableNumBytes::ToString(info.wal_files_disk_size)
519
13
                 << "<li>" << "SST Files: "
520
13
                 << HumanReadableNumBytes::ToString(info.sst_files_disk_size)
521
13
                 << "<li>" << "SST Files Uncompressed: "
522
13
                 << HumanReadableNumBytes::ToString(info.uncompressed_sst_files_disk_size)
523
13
                 << "</ul>";
524
13
  return disk_size_html.str();
525
13
}
526
527
// Returns information about the tables stored on this tablet server.
528
std::map<TableIdentifier, TableInfo> GetTablesInfo(
529
0
    const vector<std::shared_ptr<TabletPeer>>& peers) {
530
0
  std::map<TableIdentifier, TableInfo> table_map;
531
532
0
  for (const auto& peer : peers) {
533
0
    TabletStatusPB status;
534
0
    peer->GetTabletStatusPB(&status);
535
536
0
    const auto tablet_data_state = status.tablet_data_state();
537
0
    if (tablet_data_state != TabletDataState::TABLET_DATA_COPYING &&
538
0
        tablet_data_state != TabletDataState::TABLET_DATA_READY &&
539
0
        tablet_data_state != TabletDataState::TABLET_DATA_SPLIT_COMPLETED) {
540
0
      continue;
541
0
    }
542
543
0
    auto consensus = peer->shared_consensus();
544
0
    auto raft_role = PeerRole::UNKNOWN_ROLE;
545
0
    if (consensus) {
546
0
      raft_role = consensus->role();
547
0
    } else if (status.tablet_data_state() == TabletDataState::TABLET_DATA_COPYING) {
548
0
      raft_role = PeerRole::LEARNER;
549
0
    }
550
551
0
    auto identifer = TableIdentifier {
552
0
      .uuid = std::move(status.table_id()),
553
0
      .state = peer->HumanReadableState()
554
0
    };
555
556
0
    auto tablet = peer->shared_tablet();
557
0
    uint64_t num_sst_files = (tablet) ? tablet->GetCurrentVersionNumSSTFiles() : 0;
558
559
0
    auto info = TabletPeerInfo {
560
0
      .namespace_name = std::move(status.namespace_name()),
561
0
      .name = std::move(status.table_name()),
562
0
      .num_sst_files = num_sst_files,
563
0
      .disk_size_info = yb::tablet::TabletOnDiskSizeInfo::FromPB(status),
564
0
      .has_on_disk_size = status.has_estimated_on_disk_size(),
565
0
      .raft_role = raft_role
566
0
    };
567
568
0
    auto table_iter = table_map.find(identifer);
569
0
    if (table_iter == table_map.end()) {
570
0
      table_map.emplace(identifer, TableInfo(std::move(info)));
571
0
    } else {
572
0
      table_iter->second.Aggregate(std::move(info));
573
0
    }
574
0
  }
575
576
0
  return table_map;
577
0
}
578
579
}  // anonymous namespace
580
581
void TabletServerPathHandlers::HandleTablesPage(const Webserver::WebRequest& req,
582
0
                                                Webserver::WebResponse* resp) {
583
0
  std::stringstream *output = &resp->output;
584
0
  auto peers = tserver_->tablet_manager()->GetTabletPeers();
585
0
  auto table_map = GetTablesInfo(peers);
586
0
  bool show_missing_size_footer = false;
587
588
0
  *output << "<h1>Tables</h1>\n"
589
0
          << "<table class='table table-striped'>\n"
590
0
          << "  <tr>\n"
591
0
          << "    <th>Namespace</th><th>Table name</th><th>Table UUID</th>\n"
592
0
          << "    <th>State</th><th>Num SST Files</th><th>On-disk size</th><th>Raft roles</th>\n"
593
0
          << "  </tr>\n";
594
595
0
  for (const auto& table_iter : table_map) {
596
0
    const auto& identifier = table_iter.first;
597
0
    const auto& info = table_iter.second;
598
599
0
    string tables_disk_size_html = GetOnDiskSizeInHtml(info.disk_size_info);
600
0
    if (!info.has_complete_on_disk_size) {
601
0
      tables_disk_size_html += "*";
602
0
      show_missing_size_footer = true;
603
0
    }
604
605
0
    std::stringstream role_counts_html;
606
0
    role_counts_html << "<ul>";
607
0
    for (const auto& rc_iter : info.raft_role_counts) {
608
0
      role_counts_html << "<li>" << PeerRole_Name(rc_iter.first)
609
0
                       << ": " << rc_iter.second << "</li>";
610
0
    }
611
0
    role_counts_html << "</ul>";
612
613
0
    *output << Substitute(
614
0
        "<tr><td>$0</td><td>$1</td><td>$2</td><td>$3</td><td>$4</td><td>$5</td><td>$6</td></tr>\n",
615
0
        EscapeForHtmlToString(info.namespace_name),
616
0
        EscapeForHtmlToString(info.name),
617
0
        EscapeForHtmlToString(identifier.uuid),
618
0
        EscapeForHtmlToString(identifier.state),
619
0
        info.num_sst_files,
620
0
        tables_disk_size_html,
621
0
        role_counts_html.str());
622
0
  }
623
624
0
  *output << "</table>\n";
625
626
0
  if (show_missing_size_footer) {
627
0
    *output << "<p>* Some tablets did not provide disk size estimates,"
628
0
            << " and were not added to the displayed totals.</p>";
629
0
  }
630
0
}
631
632
void TabletServerPathHandlers::HandleTabletsPage(const Webserver::WebRequest& req,
633
5
                                                 Webserver::WebResponse* resp) {
634
5
  std::stringstream *output = &resp->output;
635
5
  auto peers = tserver_->tablet_manager()->GetTabletPeers();
636
5
  std::sort(peers.begin(), peers.end(), &CompareByTabletId);
637
638
5
  *output << "<h1>Tablets</h1>\n";
639
5
  *output << "<table class='table table-striped'>\n";
640
5
  *output << "  <tr><th>Namespace</th><th>Table name</th><th>Table UUID</th><th>Tablet ID</th>"
641
5
      "<th>Partition</th>"
642
5
      "<th>State</th><th>Num SST Files</th><th>On-disk size</th><th>RaftConfig</th>"
643
5
      "<th>Last status</th></tr>\n";
644
13
  for (const std::shared_ptr<TabletPeer>& peer : peers) {
645
13
    TabletStatusPB status;
646
13
    peer->GetTabletStatusPB(&status);
647
13
    string id = status.tablet_id();
648
13
    string namespace_name = status.namespace_name();
649
13
    string table_name = status.table_name();
650
13
    string table_id = status.table_id();
651
13
    string tablet_id_or_link;
652
13
    if (peer->tablet() != nullptr) {
653
12
      tablet_id_or_link = TabletLink(id);
654
12
    } else {
655
1
      tablet_id_or_link = EscapeForHtmlToString(id);
656
1
    }
657
13
    string tablets_disk_size_html = GetOnDiskSizeInHtml(
658
13
        yb::tablet::TabletOnDiskSizeInfo::FromPB(status)
659
13
    );
660
661
13
    string partition = peer->tablet_metadata()->partition_schema()
662
13
                            ->PartitionDebugString(*peer->status_listener()->partition(),
663
13
                                                   *peer->tablet_metadata()->schema());
664
665
13
    auto tablet = peer->shared_tablet();
666
13
    uint64_t num_sst_files = (tablet) ? 
tablet->GetCurrentVersionNumSSTFiles()12
:
01
;
667
668
    // TODO: would be nice to include some other stuff like memory usage
669
13
    shared_ptr<consensus::Consensus> consensus = peer->shared_consensus();
670
13
    (*output) << Substitute(
671
        // Namespace, Table name, UUID of table, tablet id, partition
672
13
        "<tr><td>$0</td><td>$1</td><td>$2</td><td>$3</td><td>$4</td>"
673
        // State, num SST files, on-disk size, consensus configuration, last status
674
13
        "<td>$5</td><td>$6</td><td>$7</td><td>$8</td><td>$9</td></tr>\n",
675
13
        EscapeForHtmlToString(namespace_name),  // $0
676
13
        EscapeForHtmlToString(table_name),  // $1
677
13
        EscapeForHtmlToString(table_id),  // $2
678
13
        tablet_id_or_link,  // $3
679
13
        EscapeForHtmlToString(partition),  // $4
680
13
        EscapeForHtmlToString(peer->HumanReadableState()),  // $5
681
13
        num_sst_files,  // $6
682
13
        tablets_disk_size_html,  // $7
683
13
        consensus ? 
ConsensusStatePBToHtml(consensus->ConsensusState(CONSENSUS_CONFIG_COMMITTED))12
684
13
                  : 
""1
, // $8
685
13
        EscapeForHtmlToString(status.last_status()));  // $9
686
13
  }
687
5
  *output << "</table>\n";
688
5
}
689
690
namespace {
691
692
22
bool CompareByMemberType(const RaftPeerPB& a, const RaftPeerPB& b) {
693
22
  if (!a.has_member_type()) 
return false0
;
694
22
  if (!b.has_member_type()) 
return true0
;
695
22
  return a.member_type() < b.member_type();
696
22
}
697
698
}  // anonymous namespace
699
700
12
string TabletServerPathHandlers::ConsensusStatePBToHtml(const ConsensusStatePB& cstate) const {
701
12
  std::stringstream html;
702
703
12
  html << "<ul>\n";
704
12
  std::vector<RaftPeerPB> sorted_peers;
705
12
  sorted_peers.assign(cstate.config().peers().begin(), cstate.config().peers().end());
706
12
  std::sort(sorted_peers.begin(), sorted_peers.end(), &CompareByMemberType);
707
34
  for (const RaftPeerPB& peer : sorted_peers) {
708
34
    std::string peer_addr_or_uuid = !peer.last_known_private_addr().empty()
709
34
        ? peer.last_known_private_addr()[0].host()
710
34
        : 
peer.permanent_uuid()0
;
711
34
    peer_addr_or_uuid = EscapeForHtmlToString(peer_addr_or_uuid);
712
34
    string role_name = PeerRole_Name(GetConsensusRole(peer.permanent_uuid(), cstate));
713
34
    string formatted = Substitute("$0: $1", role_name, peer_addr_or_uuid);
714
    // Make the local peer bold.
715
34
    if (peer.permanent_uuid() == tserver_->instance_pb().permanent_uuid()) {
716
12
      formatted = Substitute("<b>$0</b>", formatted);
717
12
    }
718
719
34
    html << Substitute(" <li>$0</li>\n", formatted);
720
34
  }
721
12
  html << "</ul>\n";
722
12
  return html.str();
723
12
}
724
725
void TabletServerPathHandlers::HandleDashboardsPage(const Webserver::WebRequest& req,
726
0
                                                    Webserver::WebResponse* resp) {
727
0
  std::stringstream *output = &resp->output;
728
0
  *output << "<h3>Dashboards</h3>\n";
729
0
  *output << "<table class='table table-striped'>\n";
730
0
  *output << "  <tr><th>Dashboard</th><th>Description</th></tr>\n";
731
0
  *output << GetDashboardLine(
732
0
      "operations", "Operations", "List of operations that are currently replicating.");
733
0
  *output << GetDashboardLine("maintenance-manager", "Maintenance Manager",
734
0
                              "List of operations that are currently running and those "
735
0
                              "that are registered.");
736
0
}
737
738
string TabletServerPathHandlers::GetDashboardLine(const std::string& link,
739
                                                  const std::string& text,
740
0
                                                  const std::string& desc) {
741
0
  return Substitute("  <tr><td><a href=\"$0\">$1</a></td><td>$2</td></tr>\n",
742
0
                    EscapeForHtmlToString(link),
743
0
                    EscapeForHtmlToString(text),
744
0
                    EscapeForHtmlToString(desc));
745
0
}
746
747
void TabletServerPathHandlers::HandleMaintenanceManagerPage(const Webserver::WebRequest& req,
748
0
                                                            Webserver::WebResponse* resp) {
749
0
  std::stringstream *output = &resp->output;
750
0
  MaintenanceManager* manager = tserver_->maintenance_manager();
751
0
  MaintenanceManagerStatusPB pb;
752
0
  manager->GetMaintenanceManagerStatusDump(&pb);
753
0
  if (ContainsKey(req.parsed_args, "raw")) {
754
0
    *output << pb.DebugString();
755
0
    return;
756
0
  }
757
758
0
  int ops_count = pb.registered_operations_size();
759
760
0
  *output << "<h1>Maintenance Manager state</h1>\n";
761
0
  *output << "<h3>Running operations</h3>\n";
762
0
  *output << "<table class='table table-striped'>\n";
763
0
  *output << "  <tr><th>Name</th><th>Instances running</th></tr>\n";
764
0
  for (int i = 0; i < ops_count; i++) {
765
0
    MaintenanceManagerStatusPB_MaintenanceOpPB op_pb = pb.registered_operations(i);
766
0
    if (op_pb.running() > 0) {
767
0
      *output <<  Substitute("<tr><td>$0</td><td>$1</td></tr>\n",
768
0
                             EscapeForHtmlToString(op_pb.name()),
769
0
                             op_pb.running());
770
0
    }
771
0
  }
772
0
  *output << "</table>\n";
773
774
0
  *output << "<h3>Recent completed operations</h3>\n";
775
0
  *output << "<table class='table table-striped'>\n";
776
0
  *output << "  <tr><th>Name</th><th>Duration</th><th>Time since op started</th></tr>\n";
777
0
  for (int i = 0; i < pb.completed_operations_size(); i++) {
778
0
    MaintenanceManagerStatusPB_CompletedOpPB op_pb = pb.completed_operations(i);
779
0
    *output <<  Substitute("<tr><td>$0</td><td>$1</td><td>$2</td></tr>\n",
780
0
                           EscapeForHtmlToString(op_pb.name()),
781
0
                           HumanReadableElapsedTime::ToShortString(
782
0
                               op_pb.duration_millis() / 1000.0),
783
0
                           HumanReadableElapsedTime::ToShortString(
784
0
                               op_pb.secs_since_start()));
785
0
  }
786
0
  *output << "</table>\n";
787
788
0
  *output << "<h3>Non-running operations</h3>\n";
789
0
  *output << "<table class='table table-striped'>\n";
790
0
  *output << "  <tr><th>Name</th><th>Runnable</th><th>RAM anchored</th>\n"
791
0
          << "       <th>Logs retained</th><th>Perf</th></tr>\n";
792
0
  for (int i = 0; i < ops_count; i++) {
793
0
    MaintenanceManagerStatusPB_MaintenanceOpPB op_pb = pb.registered_operations(i);
794
0
    if (op_pb.running() == 0) {
795
0
      *output << Substitute("<tr><td>$0</td><td>$1</td><td>$2</td><td>$3</td><td>$4</td></tr>\n",
796
0
                            EscapeForHtmlToString(op_pb.name()),
797
0
                            op_pb.runnable(),
798
0
                            HumanReadableNumBytes::ToString(op_pb.ram_anchored_bytes()),
799
0
                            HumanReadableNumBytes::ToString(op_pb.logs_retained_bytes()),
800
0
                            op_pb.perf_improvement());
801
0
    }
802
0
  }
803
0
  *output << "</table>\n";
804
0
}
805
806
void TabletServerPathHandlers::HandleHealthCheck(const Webserver::WebRequest& req,
807
1
                                                 Webserver::WebResponse* resp) {
808
1
  std::stringstream *output = &resp->output;
809
1
  JsonWriter jw(output, JsonWriter::COMPACT);
810
1
  auto tablet_peers = tserver_->tablet_manager()->GetTabletPeers();
811
812
1
  jw.StartObject();
813
1
  jw.String("failed_tablets");
814
1
  jw.StartArray();
815
4
  for (const auto& peer : tablet_peers) {
816
4
    if (peer->state() == tablet::FAILED) {
817
1
      jw.String(peer->tablet_id());
818
1
    }
819
4
  }
820
1
  jw.EndArray();
821
1
  jw.EndObject();
822
1
}
823
824
}  // namespace tserver
825
}  // namespace yb