|  | /* | 
|  | * 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 |