blob: 0b023af271f15124938e1781388338ac98e023bb [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 <Thrift.h>
// Do nothing if virtual call profiling is not enabled
#if T_GLOBAL_DEBUG_VIRTUAL > 1
// TODO: This code only works with g++ (since we rely on the fact
// that all std::type_info instances referring to a particular type
// always return the exact same pointer value from name().)
#ifndef __GNUG__
#error "Thrift virtual function profiling currently only works with gcc"
#endif // !__GNUG__
// TODO: We also require glibc for the backtrace() and backtrace_symbols()
// functions.
#ifndef __GLIBC__
#error "Thrift virtual function profiling currently requires glibc"
#endif // !__GLIBC__
#include <concurrency/Mutex.h>
#include <ext/hash_map>
#include <execinfo.h>
#include <stdio.h>
namespace apache { namespace thrift {
using ::apache::thrift::concurrency::Mutex;
using ::apache::thrift::concurrency::Guard;
static const unsigned int MAX_STACK_DEPTH = 15;
/**
* A stack trace
*/
class Backtrace {
public:
Backtrace(int skip = 0);
Backtrace(Backtrace const &bt);
void operator=(Backtrace const &bt) {
numCallers_ = bt.numCallers_;
if (numCallers_ >= 0) {
memcpy(callers_, bt.callers_, numCallers_ * sizeof(void*));
}
}
bool operator==(Backtrace const &bt) const {
return (cmp(bt) == 0);
}
size_t hash() const {
intptr_t ret = 0;
for (int n = 0; n < numCallers_; ++n) {
ret ^= reinterpret_cast<intptr_t>(callers_[n]);
}
return static_cast<size_t>(ret);
}
int cmp(Backtrace const& bt) const {
int depth_diff = (numCallers_ - bt.numCallers_);
if (depth_diff != 0) {
return depth_diff;
}
for (int n = 0; n < numCallers_; ++n) {
int diff = reinterpret_cast<intptr_t>(callers_[n]) -
reinterpret_cast<intptr_t>(bt.callers_[n]);
if (diff != 0) {
return diff;
}
}
return 0;
}
void print(FILE *f, int indent=0, int start=0) const {
char **strings = backtrace_symbols(callers_, numCallers_);
if (strings) {
start += skip_;
if (start < 0) {
start = 0;
}
for (int n = start; n < numCallers_; ++n) {
fprintf(f, "%*s#%-2d %s\n", indent, "", n, strings[n]);
}
free(strings);
} else {
fprintf(f, "%*s<failed to determine symbols>\n", indent, "");
}
}
int getDepth() const {
return numCallers_ - skip_;
}
void *getFrame(int index) const {
int adjusted_index = index + skip_;
if (adjusted_index < 0 || adjusted_index >= numCallers_) {
return NULL;
}
return callers_[adjusted_index];
}
private:
void *callers_[MAX_STACK_DEPTH];
int numCallers_;
int skip_;
};
// Define the constructors non-inline, so they consistently add a single
// frame to the stack trace, regardless of whether optimization is enabled
Backtrace::Backtrace(int skip)
: skip_(skip + 1) // ignore the constructor itself
{
numCallers_ = backtrace(callers_, MAX_STACK_DEPTH);
if (skip_ > numCallers_) {
skip_ = numCallers_;
}
}
Backtrace::Backtrace(Backtrace const &bt)
: numCallers_(bt.numCallers_)
, skip_(bt.skip_) {
if (numCallers_ >= 0) {
memcpy(callers_, bt.callers_, numCallers_ * sizeof(void*));
}
}
/**
* A backtrace, plus one or two type names
*/
class Key {
public:
class Hash {
public:
size_t operator()(Key const& k) const {
return k.hash();
}
};
Key(const Backtrace* bt, const std::type_info& type_info)
: backtrace_(bt)
, typeName1_(type_info.name())
, typeName2_(NULL) {
}
Key(const Backtrace* bt, const std::type_info& type_info1,
const std::type_info& type_info2)
: backtrace_(bt)
, typeName1_(type_info1.name())
, typeName2_(type_info2.name()) {
}
Key(const Key& k)
: backtrace_(k.backtrace_)
, typeName1_(k.typeName1_)
, typeName2_(k.typeName2_) {
}
void operator=(const Key& k) {
backtrace_ = k.backtrace_;
typeName1_ = k.typeName1_;
typeName2_ = k.typeName2_;
}
const Backtrace* getBacktrace() const {
return backtrace_;
}
const char* getTypeName() const {
return typeName1_;
}
const char* getTypeName2() const {
return typeName2_;
}
void makePersistent() {
// Copy the Backtrace object
backtrace_ = new Backtrace(*backtrace_);
// NOTE: We don't copy the type name.
// The GNU libstdc++ implementation of type_info::name() returns a value
// that will be valid for the lifetime of the program. (Although the C++
// standard doesn't guarantee this will be true on all implementations.)
}
/**
* Clean up memory allocated by makePersistent()
*
* Should only be invoked if makePersistent() has previously been called.
* The Key should no longer be used after cleanup() is called.
*/
void cleanup() {
delete backtrace_;
backtrace_ = NULL;
}
int cmp(const Key& k) const {
int ret = backtrace_->cmp(*k.backtrace_);
if (ret != 0) {
return ret;
}
// NOTE: We compare just the name pointers.
// With GNU libstdc++, every type_info object for the same type points to
// exactly the same name string. (Although this isn't guaranteed by the
// C++ standard.)
ret = k.typeName1_ - typeName1_;
if (ret != 0) {
return ret;
}
return k.typeName2_ - typeName2_;
}
bool operator==(const Key& k) const {
return cmp(k) == 0;
}
size_t hash() const {
// NOTE: As above, we just use the name pointer value.
// Works with GNU libstdc++, but not guaranteed to be correct on all
// implementations.
return backtrace_->hash() ^
reinterpret_cast<size_t>(typeName1_) ^
reinterpret_cast<size_t>(typeName2_);
}
private:
const Backtrace* backtrace_;
const char* typeName1_;
const char* typeName2_;
};
/**
* A functor that determines which of two BacktraceMap entries
* has a higher count.
*/
class CountGreater {
public:
bool operator()(std::pair<Key, size_t> bt1,
std::pair<Key, size_t> bt2) const {
return bt1.second > bt2.second;
}
};
typedef __gnu_cxx::hash_map<Key, size_t, Key::Hash> BacktraceMap;
/**
* A map describing how many times T_VIRTUAL_CALL() has been invoked.
*/
BacktraceMap virtual_calls;
Mutex virtual_calls_mutex;
/**
* A map describing how many times T_GENERIC_PROTOCOL() has been invoked.
*/
BacktraceMap generic_calls;
Mutex generic_calls_mutex;
void _record_backtrace(BacktraceMap* map, const Mutex& mutex, Key *k) {
Guard guard(mutex);
BacktraceMap::iterator it = map->find(*k);
if (it == map->end()) {
k->makePersistent();
map->insert(std::make_pair(*k, 1));
} else {
// increment the count
// NOTE: we could assert if it->second is 0 afterwards, since that would
// mean we've wrapped.
++(it->second);
}
}
/**
* Record an unnecessary virtual function call.
*
* This method is invoked by the T_VIRTUAL_CALL() macro.
*/
void profile_virtual_call(const std::type_info& type) {
int const skip = 1; // ignore this frame
Backtrace bt(skip);
Key k(&bt, type);
_record_backtrace(&virtual_calls, virtual_calls_mutex, &k);
}
/**
* Record a call to a template processor with a protocol that is not the one
* specified in the template parameter.
*
* This method is invoked by the T_GENERIC_PROTOCOL() macro.
*/
void profile_generic_protocol(const std::type_info& template_type,
const std::type_info& prot_type) {
int const skip = 1; // ignore this frame
Backtrace bt(skip);
Key k(&bt, template_type, prot_type);
_record_backtrace(&generic_calls, generic_calls_mutex, &k);
}
/**
* Print the recorded profiling information to the specified file.
*/
void profile_print_info(FILE* f) {
typedef std::vector< std::pair<Key, size_t> > BacktraceVector;
CountGreater is_greater;
// Grab both locks for the duration of the print operation,
// to ensure the output is a consistent snapshot of a single point in time
Guard generic_calls_guard(generic_calls_mutex);
Guard virtual_calls_guard(virtual_calls_mutex);
// print the info from generic_calls, sorted by frequency
//
// We print the generic_calls info ahead of virtual_calls, since it is more
// useful in some cases. All T_GENERIC_PROTOCOL calls can be eliminated
// from most programs. Not all T_VIRTUAL_CALLs will be eliminated by
// converting to templates.
BacktraceVector gp_sorted(generic_calls.begin(), generic_calls.end());
std::sort(gp_sorted.begin(), gp_sorted.end(), is_greater);
for (BacktraceVector::const_iterator it = gp_sorted.begin();
it != gp_sorted.end();
++it) {
Key const &key = it->first;
size_t const count = it->second;
fprintf(f, "T_GENERIC_PROTOCOL: %zu calls to %s with a %s:\n",
count, key.getTypeName(), key.getTypeName2());
key.getBacktrace()->print(f, 2);
fprintf(f, "\n");
}
// print the info from virtual_calls, sorted by frequency
BacktraceVector vc_sorted(virtual_calls.begin(), virtual_calls.end());
std::sort(vc_sorted.begin(), vc_sorted.end(), is_greater);
for (BacktraceVector::const_iterator it = vc_sorted.begin();
it != vc_sorted.end();
++it) {
Key const &key = it->first;
size_t const count = it->second;
fprintf(f, "T_VIRTUAL_CALL: %zu calls on %s:\n", count, key.getTypeName());
key.getBacktrace()->print(f, 2);
fprintf(f, "\n");
}
}
/**
* Print the recorded profiling information to stdout.
*/
void profile_print_info() {
profile_print_info(stdout);
}
/**
* Write a BacktraceMap as Google CPU profiler binary data.
*/
static void profile_write_pprof_file(FILE* f, BacktraceMap const& map) {
// Write the header
uintptr_t header[5] = { 0, 3, 0, 0, 0 };
fwrite(&header, sizeof(header), 1, f);
// Write the profile records
for (BacktraceMap::const_iterator it = map.begin(); it != map.end(); ++it) {
uintptr_t count = it->second;
fwrite(&count, sizeof(count), 1, f);
Backtrace const* bt = it->first.getBacktrace();
uintptr_t num_pcs = bt->getDepth();
fwrite(&num_pcs, sizeof(num_pcs), 1, f);
for (uintptr_t n = 0; n < num_pcs; ++n) {
void* pc = bt->getFrame(n);
fwrite(&pc, sizeof(pc), 1, f);
}
}
// Write the trailer
uintptr_t trailer[3] = { 0, 1, 0 };
fwrite(&trailer, sizeof(trailer), 1, f);
// Write /proc/self/maps
// TODO(simpkins): This only works on linux
FILE *proc_maps = fopen("/proc/self/maps", "r");
if (proc_maps) {
uint8_t buf[4096];
while (true) {
size_t bytes_read = fread(buf, 1, sizeof(buf), proc_maps);
if (bytes_read == 0) {
break;
}
fwrite(buf, 1, bytes_read, f);
}
fclose(proc_maps);
}
}
/**
* Write the recorded profiling information as pprof files.
*
* This writes the information using the Google CPU profiler binary data
* format, so it can be analyzed with pprof. Note that information about the
* protocol/transport data types cannot be stored in this file format.
*
* See http://code.google.com/p/google-perftools/ for more details.
*
* @param gen_calls_f The information about calls to
* profile_generic_protocol() will be written to this
* file.
* @param virtual_calls_f The information about calls to
* profile_virtual_call() will be written to this file.
*/
void profile_write_pprof(FILE* gen_calls_f, FILE* virtual_calls_f) {
typedef std::vector< std::pair<Key, size_t> > BacktraceVector;
CountGreater is_greater;
// Grab both locks for the duration of the print operation,
// to ensure the output is a consistent snapshot of a single point in time
Guard generic_calls_guard(generic_calls_mutex);
Guard virtual_calls_guard(virtual_calls_mutex);
// write the info from generic_calls
profile_write_pprof_file(gen_calls_f, generic_calls);
// write the info from virtual_calls
profile_write_pprof_file(virtual_calls_f, virtual_calls);
}
}} // apache::thrift
#endif // T_GLOBAL_PROFILE_VIRTUAL > 0