blob: f9dac99bbea627e3b3fa4ad99686f580bd87ad5a [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 <fstream>
#include <glog/logging.h>
#include <gperftools/heap-profiler.h>
#include <gperftools/malloc_extension.h>
#include <gperftools/profiler.h>
#include <string>
#include <sys/stat.h>
#include <vector>
#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/substitute.h"
#include "kudu/gutil/sysinfo.h"
#include "kudu/server/webserver.h"
#include "kudu/util/env.h"
#include "kudu/util/logging.h"
#include "kudu/util/monotime.h"
#include "kudu/util/spinlock_profiling.h"
#include "kudu/util/status.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::stringstream;
// 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, int out_size);
}
namespace kudu {
const int PPROF_DEFAULT_SAMPLE_SECS = 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, stringstream* output) {
string executable_path;
Env* env = Env::Default();
WARN_NOT_OK(env->GetExecutablePath(&executable_path), "Failed to get executable path");
*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, stringstream* output) {
#ifndef TCMALLOC_ENABLED
(*output) << "Heap profiling is not available without tcmalloc.";
#else
// Remote (on-demand) profiling is disabled if the process is already being profiled.
if (FLAGS_enable_process_lifetime_heap_profiling) {
(*output) << "Heap profiling is running for the process lifetime.";
return;
}
auto it = req.parsed_args.find("seconds");
int seconds = PPROF_DEFAULT_SAMPLE_SECS;
if (it != req.parsed_args.end()) {
seconds = atoi(it->second.c_str());
}
HeapProfilerStart(FLAGS_heap_profile_path.c_str());
// Sleep to allow for some samples to be collected.
SleepFor(MonoDelta::FromSeconds(seconds));
const char* profile = GetHeapProfile();
HeapProfilerStop();
(*output) << profile;
delete profile;
#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, stringstream* output) {
#ifndef TCMALLOC_ENABLED
(*output) << "CPU profiling is not available without tcmalloc.";
#else
auto it = req.parsed_args.find("seconds");
int seconds = PPROF_DEFAULT_SAMPLE_SECS;
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, stringstream* output) {
#ifndef TCMALLOC_ENABLED
(*output) << "Growth profiling is not available without tcmalloc.";
#else
string heap_growth_stack;
MallocExtension::instance()->GetHeapGrowthStacks(&heap_growth_stack);
(*output) << heap_growth_stack;
#endif
}
// Lock contention profiling
static void PprofContentionHandler(const Webserver::WebRequest& req, stringstream* output) {
string secs_str = FindWithDefault(req.parsed_args, "seconds", "");
int32_t seconds = ParseLeadingInt32Value(secs_str.c_str(), PPROF_DEFAULT_SAMPLE_SECS);
int64_t discarded_samples = 0;
*output << "--- contention" << endl;
*output << "sampling period = 1" << endl;
*output << "cycles/second = " << base::CyclesPerSecond() << endl;
MonoTime end = MonoTime::Now(MonoTime::FINE);
end.AddDelta(MonoDelta::FromSeconds(seconds));
StartSynchronizationProfiling();
while (MonoTime::Now(MonoTime::FINE).ComesBefore(end)) {
SleepFor(MonoDelta::FromMilliseconds(500));
FlushSynchronizationProfile(output, &discarded_samples);
}
StopSynchronizationProfiling();
FlushSynchronizationProfile(output, &discarded_samples);
// pprof itself ignores this value, but we can at least look at it in the textual
// output.
*output << "discarded samples = " << discarded_samples << std::endl;
#if defined(__linux__)
// procfs only exists on Linux.
faststring maps;
ReadFileToString(Env::Default(), "/proc/self/maps", &maps);
*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, stringstream* output) {
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.
(*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))) {
*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->RegisterPathHandler("/pprof/cmdline", "", PprofCmdLineHandler, false, false);
webserver->RegisterPathHandler("/pprof/heap", "", PprofHeapHandler, false, false);
webserver->RegisterPathHandler("/pprof/growth", "", PprofGrowthHandler, false, false);
webserver->RegisterPathHandler("/pprof/profile", "", PprofCpuProfileHandler, false, false);
webserver->RegisterPathHandler("/pprof/symbol", "", PprofSymbolHandler, false, false);
webserver->RegisterPathHandler("/pprof/contention", "", PprofContentionHandler, false, false);
}
} // namespace kudu