blob: 842504b45c7c3bf269c116f57d81cc0acf153914 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "kudu/server/tracing_path_handlers.h"
#include <functional>
#include <map>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
#include <glog/logging.h>
#include <rapidjson/document.h>
#include "kudu/gutil/strings/escaping.h"
#include "kudu/server/webserver.h"
#include "kudu/util/debug/trace_event_impl.h"
#include "kudu/util/jsonwriter.h"
#include "kudu/util/monotime.h"
#include "kudu/util/status.h"
#include "kudu/util/web_callback_registry.h"
#include "kudu/util/zlib.h"
using std::map;
using std::ostringstream;
using std::pair;
using std::string;
using std::vector;
using kudu::debug::CategoryFilter;
using kudu::debug::TraceLog;
using kudu::debug::TraceResultBuffer;
namespace kudu {
namespace server {
enum Handler {
kBeginMonitoring,
kEndMonitoring,
kCaptureMonitoring,
kGetMonitoringStatus,
kCategories,
kBeginRecording,
kGetBufferPercentFull,
kEndRecording,
kEndRecordingCompressed,
kSimpleDump
};
namespace {
Status ParseBase64JsonRequest(const string& json_base64,
rapidjson::Document* doc) {
string json_str;
if (!strings::Base64Unescape(json_base64, &json_str)) {
return Status::InvalidArgument("Invalid base64-encoded JSON");
}
doc->Parse<0>(json_str.c_str());
if (!doc->IsObject()) {
return Status::InvalidArgument("Invalid JSON", json_str);
}
return Status::OK();
}
Status GetTracingOptions(const std::string& json_base64,
std::string* category_filter_string,
int* tracing_options) {
rapidjson::Document doc;
RETURN_NOT_OK(ParseBase64JsonRequest(json_base64, &doc));
bool use_continuous_tracing = false;
bool use_sampling = false;
if (!doc.HasMember("categoryFilter") ||
!doc["categoryFilter"].IsString()) {
return Status::InvalidArgument("missing categoryFilter");
}
*category_filter_string = doc["categoryFilter"].GetString();
if (doc.HasMember("useContinuousTracing") &&
doc["useContinuousTracing"].IsBool()) {
use_continuous_tracing = doc["useContinuousTracing"].GetBool();
}
if (doc.HasMember("useSampling") &&
doc["useSampling"].IsBool()) {
use_sampling = doc["useSampling"].GetBool();
}
*tracing_options = 0;
if (use_sampling)
*tracing_options |= TraceLog::ENABLE_SAMPLING;
if (use_continuous_tracing)
*tracing_options |= TraceLog::RECORD_CONTINUOUSLY;
return Status::OK();
}
Status BeginRecording(const Webserver::WebRequest& req,
TraceLog::Mode mode) {
string filter_str;
int options = 0;
RETURN_NOT_OK(GetTracingOptions(req.query_string, &filter_str, &options));
kudu::debug::TraceLog::GetInstance()->SetEnabled(
CategoryFilter(filter_str),
mode,
static_cast<TraceLog::Options>(options));
return Status::OK();
}
Status EndRecording(const Webserver::WebRequest& /*req*/,
bool compressed,
ostringstream* out) {
TraceLog* tl = TraceLog::GetInstance();
tl->SetDisabled();
string json = TraceResultBuffer::FlushTraceLogToString();
if (compressed) {
RETURN_NOT_OK_PREPEND(zlib::Compress(json, out),
"Could not compress output");
} else {
*out << json;
}
return Status::OK();
}
Status CaptureMonitoring(ostringstream* out) {
TraceLog* tl = TraceLog::GetInstance();
if (!tl->IsEnabled()) {
return Status::IllegalState("monitoring not enabled");
}
*out << TraceResultBuffer::FlushTraceLogToStringButLeaveBufferIntact();
return Status::OK();
}
void GetCategories(ostringstream* out) {
vector<string> groups;
kudu::debug::TraceLog::GetInstance()->GetKnownCategoryGroups(&groups);
JsonWriter j(out, JsonWriter::COMPACT);
j.StartArray();
for (const string& g : groups) {
j.String(g);
}
j.EndArray();
}
void GetMonitoringStatus(ostringstream* out) {
TraceLog* tl = TraceLog::GetInstance();
bool is_monitoring = tl->IsEnabled();
std::string category_filter = tl->GetCurrentCategoryFilter().ToString();
int options = static_cast<int>(tl->trace_options());
ostringstream json_out;
JsonWriter j(&json_out, JsonWriter::COMPACT);
j.StartObject();
j.String("isMonitoring");
j.Bool(is_monitoring);
j.String("categoryFilter");
j.String(category_filter);
j.String("useContinuousTracing");
j.Bool((options & TraceLog::RECORD_CONTINUOUSLY) != 0);
j.String("useSampling");
j.Bool((options & TraceLog::ENABLE_SAMPLING) != 0);
j.EndObject();
string encoded;
strings::Base64Escape(json_out.str(), &encoded);
*out << encoded;
}
void HandleTraceJsonPage(const Webserver::ArgumentMap& /*args*/,
std::ostringstream* output) {
TraceLog* tl = TraceLog::GetInstance();
tl->SetEnabled(CategoryFilter(CategoryFilter::kDefaultCategoryFilterString),
TraceLog::RECORDING_MODE,
TraceLog::RECORD_CONTINUOUSLY);
SleepFor(MonoDelta::FromSeconds(10));
tl->SetDisabled();
*output << TraceResultBuffer::FlushTraceLogToString();
}
Status DoHandleRequest(Handler handler,
const Webserver::WebRequest& req,
std::ostringstream* output) {
VLOG(2) << "Tracing request type=" << handler << ": " << req.query_string;
switch (handler) {
case kBeginMonitoring:
RETURN_NOT_OK(BeginRecording(req, TraceLog::MONITORING_MODE));
break;
case kCaptureMonitoring:
RETURN_NOT_OK(CaptureMonitoring(output));
break;
case kGetMonitoringStatus:
GetMonitoringStatus(output);
break;
case kCategories:
GetCategories(output);
break;
case kBeginRecording:
RETURN_NOT_OK(BeginRecording(req, TraceLog::RECORDING_MODE));
break;
case kGetBufferPercentFull:
*output << TraceLog::GetInstance()->GetBufferPercentFull();
break;
case kEndMonitoring:
case kEndRecording:
RETURN_NOT_OK(EndRecording(req, false, output));
break;
case kEndRecordingCompressed:
RETURN_NOT_OK(EndRecording(req, true, output));
break;
case kSimpleDump:
HandleTraceJsonPage(req.parsed_args, output);
break;
}
return Status::OK();
}
void HandleRequest(Handler handler,
const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
Status s = DoHandleRequest(handler, req, &resp->output);
if (!s.ok()) {
LOG(WARNING) << "Tracing error for handler " << handler << ": "
<< s.ToString();
// The trace-viewer JS expects '##ERROR##' to indicate that an error
// occurred. TODO: change the JS to bubble up the actual error message
// to the user.
resp->output << "##ERROR##";
}
}
} // anonymous namespace
void TracingPathHandlers::RegisterHandlers(Webserver* server) {
// All of the tracing-related hand
std::map<string, Handler> handlers = {
{ "/tracing/json/begin_monitoring", kBeginMonitoring },
{ "/tracing/json/end_monitoring", kEndMonitoring },
{ "/tracing/json/capture_monitoring", kCaptureMonitoring },
{ "/tracing/json/get_monitoring_status", kGetMonitoringStatus },
{ "/tracing/json/categories", kCategories },
{ "/tracing/json/begin_recording", kBeginRecording },
{ "/tracing/json/get_buffer_percent_full", kGetBufferPercentFull },
{ "/tracing/json/end_recording", kEndRecording },
{ "/tracing/json/end_recording_compressed", kEndRecordingCompressed },
{ "/tracing/json/simple_dump", kSimpleDump } };
typedef pair<const string, Handler> HandlerPair;
for (const HandlerPair& e : handlers) {
server->RegisterPrerenderedPathHandler(
e.first, "", [e](const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
HandleRequest(e.second, req, resp);
},
false /* styled */, false /* is_on_nav_bar */);
}
}
} // namespace server
} // namespace kudu