David Reiss | c3b3622 | 2010-10-06 17:10:10 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Licensed to the Apache Software Foundation (ASF) under one |
| 3 | * or more contributor license agreements. See the NOTICE file |
| 4 | * distributed with this work for additional information |
| 5 | * regarding copyright ownership. The ASF licenses this file |
| 6 | * to you under the Apache License, Version 2.0 (the |
| 7 | * "License"); you may not use this file except in compliance |
| 8 | * with the License. You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, |
| 13 | * software distributed under the License is distributed on an |
| 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | * KIND, either express or implied. See the License for the |
| 16 | * specific language governing permissions and limitations |
| 17 | * under the License. |
| 18 | */ |
| 19 | |
| 20 | #include <Thrift.h> |
| 21 | |
| 22 | // Do nothing if virtual call profiling is not enabled |
| 23 | #if T_GLOBAL_DEBUG_VIRTUAL > 1 |
| 24 | |
| 25 | // TODO: This code only works with g++ (since we rely on the fact |
| 26 | // that all std::type_info instances referring to a particular type |
| 27 | // always return the exact same pointer value from name().) |
| 28 | #ifndef __GNUG__ |
| 29 | #error "Thrift virtual function profiling currently only works with gcc" |
| 30 | #endif // !__GNUG__ |
| 31 | |
| 32 | // TODO: We also require glibc for the backtrace() and backtrace_symbols() |
| 33 | // functions. |
| 34 | #ifndef __GLIBC__ |
| 35 | #error "Thrift virtual function profiling currently requires glibc" |
| 36 | #endif // !__GLIBC__ |
| 37 | |
| 38 | |
| 39 | #include <concurrency/Mutex.h> |
| 40 | |
| 41 | #include <ext/hash_map> |
| 42 | #include <execinfo.h> |
| 43 | #include <stdio.h> |
| 44 | |
| 45 | namespace apache { namespace thrift { |
| 46 | |
| 47 | using ::apache::thrift::concurrency::Mutex; |
| 48 | using ::apache::thrift::concurrency::Guard; |
| 49 | |
| 50 | static const unsigned int MAX_STACK_DEPTH = 15; |
| 51 | |
| 52 | /** |
| 53 | * A stack trace |
| 54 | */ |
| 55 | class Backtrace { |
| 56 | public: |
| 57 | Backtrace(int skip = 0); |
| 58 | Backtrace(Backtrace const &bt); |
| 59 | |
| 60 | void operator=(Backtrace const &bt) { |
| 61 | numCallers_ = bt.numCallers_; |
| 62 | if (numCallers_ >= 0) { |
| 63 | memcpy(callers_, bt.callers_, numCallers_ * sizeof(void*)); |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | bool operator==(Backtrace const &bt) const { |
| 68 | return (cmp(bt) == 0); |
| 69 | } |
| 70 | |
| 71 | size_t hash() const { |
| 72 | intptr_t ret = 0; |
| 73 | for (int n = 0; n < numCallers_; ++n) { |
| 74 | ret ^= reinterpret_cast<intptr_t>(callers_[n]); |
| 75 | } |
| 76 | return static_cast<size_t>(ret); |
| 77 | } |
| 78 | |
| 79 | int cmp(Backtrace const& bt) const { |
| 80 | int depth_diff = (numCallers_ - bt.numCallers_); |
| 81 | if (depth_diff != 0) { |
| 82 | return depth_diff; |
| 83 | } |
| 84 | |
| 85 | for (int n = 0; n < numCallers_; ++n) { |
| 86 | int diff = reinterpret_cast<intptr_t>(callers_[n]) - |
| 87 | reinterpret_cast<intptr_t>(bt.callers_[n]); |
| 88 | if (diff != 0) { |
| 89 | return diff; |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | return 0; |
| 94 | } |
| 95 | |
| 96 | void print(FILE *f, int indent=0, int start=0) const { |
| 97 | char **strings = backtrace_symbols(callers_, numCallers_); |
| 98 | if (strings) { |
| 99 | start += skip_; |
| 100 | if (start < 0) { |
| 101 | start = 0; |
| 102 | } |
| 103 | for (int n = start; n < numCallers_; ++n) { |
| 104 | fprintf(f, "%*s#%-2d %s\n", indent, "", n, strings[n]); |
| 105 | } |
| 106 | free(strings); |
| 107 | } else { |
| 108 | fprintf(f, "%*s<failed to determine symbols>\n", indent, ""); |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | int getDepth() const { |
| 113 | return numCallers_ - skip_; |
| 114 | } |
| 115 | |
| 116 | void *getFrame(int index) const { |
| 117 | int adjusted_index = index + skip_; |
| 118 | if (adjusted_index < 0 || adjusted_index >= numCallers_) { |
| 119 | return NULL; |
| 120 | } |
| 121 | return callers_[adjusted_index]; |
| 122 | } |
| 123 | |
| 124 | private: |
| 125 | void *callers_[MAX_STACK_DEPTH]; |
| 126 | int numCallers_; |
| 127 | int skip_; |
| 128 | }; |
| 129 | |
| 130 | // Define the constructors non-inline, so they consistently add a single |
| 131 | // frame to the stack trace, regardless of whether optimization is enabled |
| 132 | Backtrace::Backtrace(int skip) |
| 133 | : skip_(skip + 1) // ignore the constructor itself |
| 134 | { |
| 135 | numCallers_ = backtrace(callers_, MAX_STACK_DEPTH); |
| 136 | if (skip_ > numCallers_) { |
| 137 | skip_ = numCallers_; |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | Backtrace::Backtrace(Backtrace const &bt) |
| 142 | : numCallers_(bt.numCallers_) |
| 143 | , skip_(bt.skip_) { |
| 144 | if (numCallers_ >= 0) { |
| 145 | memcpy(callers_, bt.callers_, numCallers_ * sizeof(void*)); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * A backtrace, plus one or two type names |
| 151 | */ |
| 152 | class Key { |
| 153 | public: |
| 154 | class Hash { |
| 155 | public: |
| 156 | size_t operator()(Key const& k) const { |
| 157 | return k.hash(); |
| 158 | } |
| 159 | }; |
| 160 | |
| 161 | Key(const Backtrace* bt, const std::type_info& type_info) |
| 162 | : backtrace_(bt) |
| 163 | , typeName1_(type_info.name()) |
| 164 | , typeName2_(NULL) { |
| 165 | } |
| 166 | |
| 167 | Key(const Backtrace* bt, const std::type_info& type_info1, |
| 168 | const std::type_info& type_info2) |
| 169 | : backtrace_(bt) |
| 170 | , typeName1_(type_info1.name()) |
| 171 | , typeName2_(type_info2.name()) { |
| 172 | } |
| 173 | |
| 174 | Key(const Key& k) |
| 175 | : backtrace_(k.backtrace_) |
| 176 | , typeName1_(k.typeName1_) |
| 177 | , typeName2_(k.typeName2_) { |
| 178 | } |
| 179 | |
| 180 | void operator=(const Key& k) { |
| 181 | backtrace_ = k.backtrace_; |
| 182 | typeName1_ = k.typeName1_; |
| 183 | typeName2_ = k.typeName2_; |
| 184 | } |
| 185 | |
| 186 | const Backtrace* getBacktrace() const { |
| 187 | return backtrace_; |
| 188 | } |
| 189 | |
| 190 | const char* getTypeName() const { |
| 191 | return typeName1_; |
| 192 | } |
| 193 | |
| 194 | const char* getTypeName2() const { |
| 195 | return typeName2_; |
| 196 | } |
| 197 | |
| 198 | void makePersistent() { |
| 199 | // Copy the Backtrace object |
| 200 | backtrace_ = new Backtrace(*backtrace_); |
| 201 | |
| 202 | // NOTE: We don't copy the type name. |
| 203 | // The GNU libstdc++ implementation of type_info::name() returns a value |
| 204 | // that will be valid for the lifetime of the program. (Although the C++ |
| 205 | // standard doesn't guarantee this will be true on all implementations.) |
| 206 | } |
| 207 | |
| 208 | /** |
| 209 | * Clean up memory allocated by makePersistent() |
| 210 | * |
| 211 | * Should only be invoked if makePersistent() has previously been called. |
| 212 | * The Key should no longer be used after cleanup() is called. |
| 213 | */ |
| 214 | void cleanup() { |
| 215 | delete backtrace_; |
| 216 | backtrace_ = NULL; |
| 217 | } |
| 218 | |
| 219 | int cmp(const Key& k) const { |
| 220 | int ret = backtrace_->cmp(*k.backtrace_); |
| 221 | if (ret != 0) { |
| 222 | return ret; |
| 223 | } |
| 224 | |
| 225 | // NOTE: We compare just the name pointers. |
| 226 | // With GNU libstdc++, every type_info object for the same type points to |
| 227 | // exactly the same name string. (Although this isn't guaranteed by the |
| 228 | // C++ standard.) |
| 229 | ret = k.typeName1_ - typeName1_; |
| 230 | if (ret != 0) { |
| 231 | return ret; |
| 232 | } |
| 233 | return k.typeName2_ - typeName2_; |
| 234 | } |
| 235 | |
| 236 | bool operator==(const Key& k) const { |
| 237 | return cmp(k) == 0; |
| 238 | } |
| 239 | |
| 240 | size_t hash() const { |
| 241 | // NOTE: As above, we just use the name pointer value. |
| 242 | // Works with GNU libstdc++, but not guaranteed to be correct on all |
| 243 | // implementations. |
| 244 | return backtrace_->hash() ^ |
| 245 | reinterpret_cast<size_t>(typeName1_) ^ |
| 246 | reinterpret_cast<size_t>(typeName2_); |
| 247 | } |
| 248 | |
| 249 | private: |
| 250 | const Backtrace* backtrace_; |
| 251 | const char* typeName1_; |
| 252 | const char* typeName2_; |
| 253 | }; |
| 254 | |
| 255 | /** |
| 256 | * A functor that determines which of two BacktraceMap entries |
| 257 | * has a higher count. |
| 258 | */ |
| 259 | class CountGreater { |
| 260 | public: |
| 261 | bool operator()(std::pair<Key, size_t> bt1, |
| 262 | std::pair<Key, size_t> bt2) const { |
| 263 | return bt1.second > bt2.second; |
| 264 | } |
| 265 | }; |
| 266 | |
| 267 | typedef __gnu_cxx::hash_map<Key, size_t, Key::Hash> BacktraceMap; |
| 268 | |
| 269 | /** |
| 270 | * A map describing how many times T_VIRTUAL_CALL() has been invoked. |
| 271 | */ |
| 272 | BacktraceMap virtual_calls; |
| 273 | Mutex virtual_calls_mutex; |
| 274 | |
| 275 | /** |
| 276 | * A map describing how many times T_GENERIC_PROTOCOL() has been invoked. |
| 277 | */ |
| 278 | BacktraceMap generic_calls; |
| 279 | Mutex generic_calls_mutex; |
| 280 | |
| 281 | |
| 282 | void _record_backtrace(BacktraceMap* map, const Mutex& mutex, Key *k) { |
| 283 | Guard guard(mutex); |
| 284 | |
| 285 | BacktraceMap::iterator it = map->find(*k); |
| 286 | if (it == map->end()) { |
| 287 | k->makePersistent(); |
| 288 | map->insert(std::make_pair(*k, 1)); |
| 289 | } else { |
| 290 | // increment the count |
| 291 | // NOTE: we could assert if it->second is 0 afterwards, since that would |
| 292 | // mean we've wrapped. |
| 293 | ++(it->second); |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | /** |
| 298 | * Record an unnecessary virtual function call. |
| 299 | * |
| 300 | * This method is invoked by the T_VIRTUAL_CALL() macro. |
| 301 | */ |
| 302 | void profile_virtual_call(const std::type_info& type) { |
| 303 | int const skip = 1; // ignore this frame |
| 304 | Backtrace bt(skip); |
| 305 | Key k(&bt, type); |
| 306 | _record_backtrace(&virtual_calls, virtual_calls_mutex, &k); |
| 307 | } |
| 308 | |
| 309 | /** |
| 310 | * Record a call to a template processor with a protocol that is not the one |
| 311 | * specified in the template parameter. |
| 312 | * |
| 313 | * This method is invoked by the T_GENERIC_PROTOCOL() macro. |
| 314 | */ |
| 315 | void profile_generic_protocol(const std::type_info& template_type, |
| 316 | const std::type_info& prot_type) { |
| 317 | int const skip = 1; // ignore this frame |
| 318 | Backtrace bt(skip); |
| 319 | Key k(&bt, template_type, prot_type); |
| 320 | _record_backtrace(&generic_calls, generic_calls_mutex, &k); |
| 321 | } |
| 322 | |
| 323 | /** |
| 324 | * Print the recorded profiling information to the specified file. |
| 325 | */ |
| 326 | void profile_print_info(FILE* f) { |
| 327 | typedef std::vector< std::pair<Key, size_t> > BacktraceVector; |
| 328 | |
| 329 | CountGreater is_greater; |
| 330 | |
| 331 | // Grab both locks for the duration of the print operation, |
| 332 | // to ensure the output is a consistent snapshot of a single point in time |
| 333 | Guard generic_calls_guard(generic_calls_mutex); |
| 334 | Guard virtual_calls_guard(virtual_calls_mutex); |
| 335 | |
| 336 | // print the info from generic_calls, sorted by frequency |
| 337 | // |
| 338 | // We print the generic_calls info ahead of virtual_calls, since it is more |
| 339 | // useful in some cases. All T_GENERIC_PROTOCOL calls can be eliminated |
| 340 | // from most programs. Not all T_VIRTUAL_CALLs will be eliminated by |
| 341 | // converting to templates. |
| 342 | BacktraceVector gp_sorted(generic_calls.begin(), generic_calls.end()); |
| 343 | std::sort(gp_sorted.begin(), gp_sorted.end(), is_greater); |
| 344 | |
| 345 | for (BacktraceVector::const_iterator it = gp_sorted.begin(); |
| 346 | it != gp_sorted.end(); |
| 347 | ++it) { |
| 348 | Key const &key = it->first; |
| 349 | size_t const count = it->second; |
| 350 | fprintf(f, "T_GENERIC_PROTOCOL: %zu calls to %s with a %s:\n", |
| 351 | count, key.getTypeName(), key.getTypeName2()); |
| 352 | key.getBacktrace()->print(f, 2); |
| 353 | fprintf(f, "\n"); |
| 354 | } |
| 355 | |
| 356 | // print the info from virtual_calls, sorted by frequency |
| 357 | BacktraceVector vc_sorted(virtual_calls.begin(), virtual_calls.end()); |
| 358 | std::sort(vc_sorted.begin(), vc_sorted.end(), is_greater); |
| 359 | |
| 360 | for (BacktraceVector::const_iterator it = vc_sorted.begin(); |
| 361 | it != vc_sorted.end(); |
| 362 | ++it) { |
| 363 | Key const &key = it->first; |
| 364 | size_t const count = it->second; |
| 365 | fprintf(f, "T_VIRTUAL_CALL: %zu calls on %s:\n", count, key.getTypeName()); |
| 366 | key.getBacktrace()->print(f, 2); |
| 367 | fprintf(f, "\n"); |
| 368 | } |
| 369 | } |
| 370 | |
| 371 | /** |
| 372 | * Print the recorded profiling information to stdout. |
| 373 | */ |
| 374 | void profile_print_info() { |
| 375 | profile_print_info(stdout); |
| 376 | } |
| 377 | |
| 378 | /** |
| 379 | * Write a BacktraceMap as Google CPU profiler binary data. |
| 380 | */ |
| 381 | static void profile_write_pprof_file(FILE* f, BacktraceMap const& map) { |
| 382 | // Write the header |
| 383 | uintptr_t header[5] = { 0, 3, 0, 0, 0 }; |
| 384 | fwrite(&header, sizeof(header), 1, f); |
| 385 | |
| 386 | // Write the profile records |
| 387 | for (BacktraceMap::const_iterator it = map.begin(); it != map.end(); ++it) { |
| 388 | uintptr_t count = it->second; |
| 389 | fwrite(&count, sizeof(count), 1, f); |
| 390 | |
| 391 | Backtrace const* bt = it->first.getBacktrace(); |
| 392 | uintptr_t num_pcs = bt->getDepth(); |
| 393 | fwrite(&num_pcs, sizeof(num_pcs), 1, f); |
| 394 | |
| 395 | for (uintptr_t n = 0; n < num_pcs; ++n) { |
| 396 | void* pc = bt->getFrame(n); |
| 397 | fwrite(&pc, sizeof(pc), 1, f); |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | // Write the trailer |
| 402 | uintptr_t trailer[3] = { 0, 1, 0 }; |
| 403 | fwrite(&trailer, sizeof(trailer), 1, f); |
| 404 | |
| 405 | // Write /proc/self/maps |
| 406 | // TODO(simpkins): This only works on linux |
| 407 | FILE *proc_maps = fopen("/proc/self/maps", "r"); |
| 408 | if (proc_maps) { |
| 409 | uint8_t buf[4096]; |
| 410 | while (true) { |
| 411 | size_t bytes_read = fread(buf, 1, sizeof(buf), proc_maps); |
| 412 | if (bytes_read == 0) { |
| 413 | break; |
| 414 | } |
| 415 | fwrite(buf, 1, bytes_read, f); |
| 416 | } |
| 417 | fclose(proc_maps); |
| 418 | } |
| 419 | } |
| 420 | |
| 421 | /** |
| 422 | * Write the recorded profiling information as pprof files. |
| 423 | * |
| 424 | * This writes the information using the Google CPU profiler binary data |
| 425 | * format, so it can be analyzed with pprof. Note that information about the |
| 426 | * protocol/transport data types cannot be stored in this file format. |
| 427 | * |
| 428 | * See http://code.google.com/p/google-perftools/ for more details. |
| 429 | * |
| 430 | * @param gen_calls_f The information about calls to |
| 431 | * profile_generic_protocol() will be written to this |
| 432 | * file. |
| 433 | * @param virtual_calls_f The information about calls to |
| 434 | * profile_virtual_call() will be written to this file. |
| 435 | */ |
| 436 | void profile_write_pprof(FILE* gen_calls_f, FILE* virtual_calls_f) { |
| 437 | typedef std::vector< std::pair<Key, size_t> > BacktraceVector; |
| 438 | |
| 439 | CountGreater is_greater; |
| 440 | |
| 441 | // Grab both locks for the duration of the print operation, |
| 442 | // to ensure the output is a consistent snapshot of a single point in time |
| 443 | Guard generic_calls_guard(generic_calls_mutex); |
| 444 | Guard virtual_calls_guard(virtual_calls_mutex); |
| 445 | |
| 446 | // write the info from generic_calls |
| 447 | profile_write_pprof_file(gen_calls_f, generic_calls); |
| 448 | |
| 449 | // write the info from virtual_calls |
| 450 | profile_write_pprof_file(virtual_calls_f, virtual_calls); |
| 451 | } |
| 452 | |
| 453 | }} // apache::thrift |
| 454 | |
| 455 | #endif // T_GLOBAL_PROFILE_VIRTUAL > 0 |