blob: 3b809218d8823d4048d2fe1b84f6e662827a6441 [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/pprof_path_handlers.h"
#include <unistd.h>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <gflags/gflags_declare.h>
#include <glog/logging.h>
#ifdef TCMALLOC_ENABLED
#include <gperftools/heap-profiler.h>
#include <gperftools/malloc_extension.h>
#include <gperftools/profiler.h>
#endif
#include "kudu/gutil/map-util.h"
#include "kudu/gutil/strings/numbers.h"
#include "kudu/gutil/strings/split.h"
#include "kudu/gutil/strings/stringpiece.h"
#include "kudu/gutil/strings/strip.h"
#include "kudu/gutil/strings/substitute.h"
#include "kudu/gutil/sysinfo.h"
#include "kudu/server/webserver.h"
#include "kudu/util/env.h"
#include "kudu/util/faststring.h"
#include "kudu/util/monotime.h"
#include "kudu/util/spinlock_profiling.h"
#include "kudu/util/status.h"
#include "kudu/util/web_callback_registry.h"
DECLARE_bool(enable_process_lifetime_heap_profiling);
DECLARE_string(heap_profile_path);
using std::endl;
using std::ifstream;
using std::ostringstream;
using std::string;
using std::vector;
// GLog already implements symbolization. Just import their hidden symbol.
namespace google {
// Symbolizes a program counter. On success, returns true and write the
// symbol name to "out". The symbol name is demangled if possible
// (supports symbols generated by GCC 3.x or newer). Otherwise,
// returns false.
bool Symbolize(void *pc, char *out, size_t out_size);
}
namespace kudu {
const int kPprofDefaultSampleSecs = 30; // pprof default sample time in seconds.
// pprof asks for the url /pprof/cmdline to figure out what application it's profiling.
// The server should respond by sending the executable path.
static void PprofCmdLineHandler(const Webserver::WebRequest& /*req*/,
Webserver::PrerenderedWebResponse* resp) {
string executable_path;
Env* env = Env::Default();
WARN_NOT_OK(env->GetExecutablePath(&executable_path), "Failed to get executable path");
resp->output << executable_path;
}
// pprof asks for the url /pprof/heap to get heap information. This should be implemented
// by calling HeapProfileStart(filename), continue to do work, and then, some number of
// seconds later, call GetHeapProfile() followed by HeapProfilerStop().
static void PprofHeapHandler(const Webserver::WebRequest& /*req*/,
Webserver::PrerenderedWebResponse* resp) {
ostringstream* output = &resp->output;
#ifndef TCMALLOC_ENABLED
*output << "%warn Heap profiling is not available without tcmalloc.\n";
#else
// If we've started the process with heap profiling then dump the full profile.
if (IsHeapProfilerRunning()) {
char* profile = GetHeapProfile();
*output << profile;
free(profile);
return;
}
// Otherwise dump the sample.
string buf;
MallocExtension::instance()->GetHeapSample(&buf);
if (buf.find("This heap profile does not have any data") != string::npos) {
// If sampling is disabled, tcmalloc prints a nice message with instructions
// how to enable it. However, it only describes the environment variable method
// and not our own gflag-based method, so we'll replace it with our own message.
buf = "%warn\n"
"%warn This heap profile does not have any data in it, because\n"
"%warn the application was run with heap sampling turned off.\n"
"%warn To obtain a heap sample, you must set the environment\n"
"%warn variable TCMALLOC_SAMPLE_PARAMETER or the flag\n"
"%warn --heap-sample-every-n-bytes to a positive sampling period,\n"
"%warn such as 524288.\n"
"%warn\n";
}
*output << buf;
#endif
}
// pprof asks for the url /pprof/profile?seconds=XX to get cpu-profiling information.
// The server should respond by calling ProfilerStart(), continuing to do its work,
// and then, XX seconds later, calling ProfilerStop().
static void PprofCpuProfileHandler(const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
ostringstream* output = &resp->output;
#ifndef TCMALLOC_ENABLED
*output << "%warn CPU profiling is not available without tcmalloc.\n";
#else
auto it = req.parsed_args.find("seconds");
int seconds = kPprofDefaultSampleSecs;
if (it != req.parsed_args.end()) {
seconds = atoi(it->second.c_str());
}
// Build a temporary file name that is hopefully unique.
string tmp_prof_file_name = strings::Substitute("/tmp/kudu_cpu_profile.$0.$1", getpid(), rand());
ProfilerStart(tmp_prof_file_name.c_str());
SleepFor(MonoDelta::FromSeconds(seconds));
ProfilerStop();
ifstream prof_file(tmp_prof_file_name.c_str(), std::ios::in);
if (!prof_file.is_open()) {
*output << "Unable to open cpu profile: " << tmp_prof_file_name;
return;
}
*output << prof_file.rdbuf();
prof_file.close();
#endif
}
// pprof asks for the url /pprof/growth to get heap-profiling delta (growth) information.
// The server should respond by calling:
// MallocExtension::instance()->GetHeapGrowthStacks(&output);
static void PprofGrowthHandler(const Webserver::WebRequest& /*req*/,
Webserver::PrerenderedWebResponse* resp) {
#ifndef TCMALLOC_ENABLED
resp->output << "%warn Growth profiling is not available without tcmalloc.\n";
#else
string heap_growth_stack;
MallocExtension::instance()->GetHeapGrowthStacks(&heap_growth_stack);
resp->output << heap_growth_stack;
#endif
}
// Lock contention profiling
static void PprofContentionHandler(const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
string secs_str = FindWithDefault(req.parsed_args, "seconds", "");
int32_t seconds = ParseLeadingInt32Value(secs_str.c_str(), kPprofDefaultSampleSecs);
int64_t discarded_samples = 0;
MonoTime end = MonoTime::Now() + MonoDelta::FromSeconds(seconds);
ostringstream profile;
StartSynchronizationProfiling();
while (MonoTime::Now() < end) {
SleepFor(MonoDelta::FromMilliseconds(500));
FlushSynchronizationProfile(&profile, &discarded_samples);
}
StopSynchronizationProfiling();
FlushSynchronizationProfile(&profile, &discarded_samples);
ostringstream* output = &resp->output;
*output << "--- contention:" << endl;
*output << "sampling period = 1" << endl;
*output << "cycles/second = " << static_cast<int64_t>(base::CyclesPerSecond()) << endl;
// pprof itself ignores this value, but we can at least look at it in the textual
// output.
*output << "discarded samples = " << discarded_samples << std::endl;
*output << profile.str();
#if defined(__linux__)
// procfs only exists on Linux.
faststring maps;
ReadFileToString(Env::Default(), "/proc/self/maps", &maps);
*output << "--- Memory map: ---" << endl;
*output << maps.ToString();
#endif // defined(__linux__)
}
// pprof asks for the url /pprof/symbol to map from hex addresses to variable names.
// When the server receives a GET request for /pprof/symbol, it should return a line
// formatted like: num_symbols: ###
// where ### is the number of symbols found in the binary. For now, the only important
// distinction is whether the value is 0, which it is for executables that lack debug
// information, or not-0).
//
// In addition to the GET request for this url, the server must accept POST requests.
// This means that after the HTTP headers, pprof will pass in a list of hex addresses
// connected by +, like:
// curl -d '0x0824d061+0x0824d1cf' http://remote_host:80/pprof/symbol
// The server should read the POST data, which will be in one line, and for each hex value
// should write one line of output to the output stream, like so:
// <hex address><tab><function name>
// For instance:
// 0x08b2dabd _Update
static void PprofSymbolHandler(const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
if (req.request_method == "GET") {
// Per the above comment, pprof doesn't expect to know the actual number of symbols.
// Any non-zero value indicates that we support symbol lookup.
resp->output << "num_symbols: 1";
return;
}
int missing_symbols = 0;
int invalid_addrs = 0;
// Symbolization request.
vector<StringPiece> pieces = strings::Split(req.post_data, "+");
for (StringPiece p : pieces) {
string hex_addr;
if (!TryStripPrefixString(p, "0x", &hex_addr)) {
invalid_addrs++;
continue;
}
uint64_t addr;
if (!safe_strtou64_base(hex_addr.c_str(), &addr, 16)) {
invalid_addrs++;
continue;
}
char symbol_buf[1024];
if (google::Symbolize(reinterpret_cast<void*>(addr), symbol_buf, sizeof(symbol_buf))) {
resp->output << p << "\t" << symbol_buf << std::endl;
} else {
missing_symbols++;
}
}
LOG(INFO) << strings::Substitute(
"Handled request for /pprof/symbol: requested=$0 invalid_addrs=$1 missing=$2",
pieces.size(), invalid_addrs, missing_symbols);
}
void AddPprofPathHandlers(Webserver* webserver) {
// Path handlers for remote pprof profiling. For information see:
// https://gperftools.googlecode.com/svn/trunk/doc/pprof_remote_servers.html
webserver->RegisterPrerenderedPathHandler("/pprof/cmdline", "", PprofCmdLineHandler,
false, false);
webserver->RegisterPrerenderedPathHandler("/pprof/heap", "", PprofHeapHandler, false, false);
webserver->RegisterPrerenderedPathHandler("/pprof/growth", "", PprofGrowthHandler, false, false);
webserver->RegisterPrerenderedPathHandler("/pprof/profile", "", PprofCpuProfileHandler,
false, false);
webserver->RegisterPrerenderedPathHandler("/pprof/symbol", "", PprofSymbolHandler, false, false);
webserver->RegisterPrerenderedPathHandler("/pprof/contention", "", PprofContentionHandler,
false, false);
}
} // namespace kudu