stats_table.cc revision 58e6fbe4ee35d65e14b626c557d37565bf8ad179
1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/metrics/stats_table.h" 6 7#include "base/logging.h" 8#include "base/memory/scoped_ptr.h" 9#include "base/memory/shared_memory.h" 10#include "base/process/process_handle.h" 11#include "base/strings/string_piece.h" 12#include "base/strings/string_util.h" 13#include "base/strings/utf_string_conversions.h" 14#include "base/threading/platform_thread.h" 15#include "base/threading/thread_local_storage.h" 16 17#if defined(OS_POSIX) 18#include "errno.h" 19#endif 20 21namespace base { 22 23// The StatsTable uses a shared memory segment that is laid out as follows 24// 25// +-------------------------------------------+ 26// | Version | Size | MaxCounters | MaxThreads | 27// +-------------------------------------------+ 28// | Thread names table | 29// +-------------------------------------------+ 30// | Thread TID table | 31// +-------------------------------------------+ 32// | Thread PID table | 33// +-------------------------------------------+ 34// | Counter names table | 35// +-------------------------------------------+ 36// | Data | 37// +-------------------------------------------+ 38// 39// The data layout is a grid, where the columns are the thread_ids and the 40// rows are the counter_ids. 41// 42// If the first character of the thread_name is '\0', then that column is 43// empty. 44// If the first character of the counter_name is '\0', then that row is 45// empty. 46// 47// About Locking: 48// This class is designed to be both multi-thread and multi-process safe. 49// Aside from initialization, this is done by partitioning the data which 50// each thread uses so that no locking is required. However, to allocate 51// the rows and columns of the table to particular threads, locking is 52// required. 53// 54// At the shared-memory level, we have a lock. This lock protects the 55// shared-memory table only, and is used when we create new counters (e.g. 56// use rows) or when we register new threads (e.g. use columns). Reading 57// data from the table does not require any locking at the shared memory 58// level. 59// 60// Each process which accesses the table will create a StatsTable object. 61// The StatsTable maintains a hash table of the existing counters in the 62// table for faster lookup. Since the hash table is process specific, 63// each process maintains its own cache. We avoid complexity here by never 64// de-allocating from the hash table. (Counters are dynamically added, 65// but not dynamically removed). 66 67// In order for external viewers to be able to read our shared memory, 68// we all need to use the same size ints. 69COMPILE_ASSERT(sizeof(int)==4, expect_4_byte_ints); 70 71namespace { 72 73// An internal version in case we ever change the format of this 74// file, and so that we can identify our table. 75const int kTableVersion = 0x13131313; 76 77// The name for un-named counters and threads in the table. 78const char kUnknownName[] = "<unknown>"; 79 80// Calculates delta to align an offset to the size of an int 81inline int AlignOffset(int offset) { 82 return (sizeof(int) - (offset % sizeof(int))) % sizeof(int); 83} 84 85inline int AlignedSize(int size) { 86 return size + AlignOffset(size); 87} 88 89} // namespace 90 91// The StatsTable::Private maintains convenience pointers into the 92// shared memory segment. Use this class to keep the data structure 93// clean and accessible. 94class StatsTable::Private { 95 public: 96 // Various header information contained in the memory mapped segment. 97 struct TableHeader { 98 int version; 99 int size; 100 int max_counters; 101 int max_threads; 102 }; 103 104 // Construct a new Private based on expected size parameters, or 105 // return NULL on failure. 106 static Private* New(const std::string& name, int size, 107 int max_threads, int max_counters); 108 109 SharedMemory* shared_memory() { return &shared_memory_; } 110 111 // Accessors for our header pointers 112 TableHeader* table_header() const { return table_header_; } 113 int version() const { return table_header_->version; } 114 int size() const { return table_header_->size; } 115 int max_counters() const { return table_header_->max_counters; } 116 int max_threads() const { return table_header_->max_threads; } 117 118 // Accessors for our tables 119 char* thread_name(int slot_id) const { 120 return &thread_names_table_[ 121 (slot_id-1) * (StatsTable::kMaxThreadNameLength)]; 122 } 123 PlatformThreadId* thread_tid(int slot_id) const { 124 return &(thread_tid_table_[slot_id-1]); 125 } 126 int* thread_pid(int slot_id) const { 127 return &(thread_pid_table_[slot_id-1]); 128 } 129 char* counter_name(int counter_id) const { 130 return &counter_names_table_[ 131 (counter_id-1) * (StatsTable::kMaxCounterNameLength)]; 132 } 133 int* row(int counter_id) const { 134 return &data_table_[(counter_id-1) * max_threads()]; 135 } 136 137 private: 138 // Constructor is private because you should use New() instead. 139 Private() 140 : table_header_(NULL), 141 thread_names_table_(NULL), 142 thread_tid_table_(NULL), 143 thread_pid_table_(NULL), 144 counter_names_table_(NULL), 145 data_table_(NULL) { 146 } 147 148 // Initializes the table on first access. Sets header values 149 // appropriately and zeroes all counters. 150 void InitializeTable(void* memory, int size, int max_counters, 151 int max_threads); 152 153 // Initializes our in-memory pointers into a pre-created StatsTable. 154 void ComputeMappedPointers(void* memory); 155 156 SharedMemory shared_memory_; 157 TableHeader* table_header_; 158 char* thread_names_table_; 159 PlatformThreadId* thread_tid_table_; 160 int* thread_pid_table_; 161 char* counter_names_table_; 162 int* data_table_; 163}; 164 165// static 166StatsTable::Private* StatsTable::Private::New(const std::string& name, 167 int size, 168 int max_threads, 169 int max_counters) { 170 scoped_ptr<Private> priv(new Private()); 171 if (!priv->shared_memory_.CreateNamed(name, true, size)) 172 return NULL; 173 if (!priv->shared_memory_.Map(size)) 174 return NULL; 175 void* memory = priv->shared_memory_.memory(); 176 177 TableHeader* header = static_cast<TableHeader*>(memory); 178 179 // If the version does not match, then assume the table needs 180 // to be initialized. 181 if (header->version != kTableVersion) 182 priv->InitializeTable(memory, size, max_counters, max_threads); 183 184 // We have a valid table, so compute our pointers. 185 priv->ComputeMappedPointers(memory); 186 187 return priv.release(); 188} 189 190void StatsTable::Private::InitializeTable(void* memory, int size, 191 int max_counters, 192 int max_threads) { 193 // Zero everything. 194 memset(memory, 0, size); 195 196 // Initialize the header. 197 TableHeader* header = static_cast<TableHeader*>(memory); 198 header->version = kTableVersion; 199 header->size = size; 200 header->max_counters = max_counters; 201 header->max_threads = max_threads; 202} 203 204void StatsTable::Private::ComputeMappedPointers(void* memory) { 205 char* data = static_cast<char*>(memory); 206 int offset = 0; 207 208 table_header_ = reinterpret_cast<TableHeader*>(data); 209 offset += sizeof(*table_header_); 210 offset += AlignOffset(offset); 211 212 // Verify we're looking at a valid StatsTable. 213 DCHECK_EQ(table_header_->version, kTableVersion); 214 215 thread_names_table_ = reinterpret_cast<char*>(data + offset); 216 offset += sizeof(char) * 217 max_threads() * StatsTable::kMaxThreadNameLength; 218 offset += AlignOffset(offset); 219 220 thread_tid_table_ = reinterpret_cast<PlatformThreadId*>(data + offset); 221 offset += sizeof(int) * max_threads(); 222 offset += AlignOffset(offset); 223 224 thread_pid_table_ = reinterpret_cast<int*>(data + offset); 225 offset += sizeof(int) * max_threads(); 226 offset += AlignOffset(offset); 227 228 counter_names_table_ = reinterpret_cast<char*>(data + offset); 229 offset += sizeof(char) * 230 max_counters() * StatsTable::kMaxCounterNameLength; 231 offset += AlignOffset(offset); 232 233 data_table_ = reinterpret_cast<int*>(data + offset); 234 offset += sizeof(int) * max_threads() * max_counters(); 235 236 DCHECK_EQ(offset, size()); 237} 238 239// TLSData carries the data stored in the TLS slots for the 240// StatsTable. This is used so that we can properly cleanup when the 241// thread exits and return the table slot. 242// 243// Each thread that calls RegisterThread in the StatsTable will have 244// a TLSData stored in its TLS. 245struct StatsTable::TLSData { 246 StatsTable* table; 247 int slot; 248}; 249 250// We keep a singleton table which can be easily accessed. 251StatsTable* global_table = NULL; 252 253StatsTable::StatsTable(const std::string& name, int max_threads, 254 int max_counters) 255 : impl_(NULL), 256 tls_index_(SlotReturnFunction) { 257 int table_size = 258 AlignedSize(sizeof(Private::TableHeader)) + 259 AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) + 260 AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) + 261 AlignedSize(max_threads * sizeof(int)) + 262 AlignedSize(max_threads * sizeof(int)) + 263 AlignedSize((sizeof(int) * (max_counters * max_threads))); 264 265 impl_ = Private::New(name, table_size, max_threads, max_counters); 266 267 if (!impl_) 268 DPLOG(ERROR) << "StatsTable did not initialize"; 269} 270 271StatsTable::~StatsTable() { 272 // Before we tear down our copy of the table, be sure to 273 // unregister our thread. 274 UnregisterThread(); 275 276 // Return ThreadLocalStorage. At this point, if any registered threads 277 // still exist, they cannot Unregister. 278 tls_index_.Free(); 279 280 // Cleanup our shared memory. 281 delete impl_; 282 283 // If we are the global table, unregister ourselves. 284 if (global_table == this) 285 global_table = NULL; 286} 287 288StatsTable* StatsTable::current() { 289 return global_table; 290} 291 292void StatsTable::set_current(StatsTable* value) { 293 global_table = value; 294} 295 296int StatsTable::GetSlot() const { 297 TLSData* data = GetTLSData(); 298 if (!data) 299 return 0; 300 return data->slot; 301} 302 303int StatsTable::RegisterThread(const std::string& name) { 304 int slot = 0; 305 if (!impl_) 306 return 0; 307 308 // Registering a thread requires that we lock the shared memory 309 // so that two threads don't grab the same slot. Fortunately, 310 // thread creation shouldn't happen in inner loops. 311 { 312 SharedMemoryAutoLock lock(impl_->shared_memory()); 313 slot = FindEmptyThread(); 314 if (!slot) { 315 return 0; 316 } 317 318 // We have space, so consume a column in the table. 319 std::string thread_name = name; 320 if (name.empty()) 321 thread_name = kUnknownName; 322 strlcpy(impl_->thread_name(slot), thread_name.c_str(), 323 kMaxThreadNameLength); 324 *(impl_->thread_tid(slot)) = PlatformThread::CurrentId(); 325 *(impl_->thread_pid(slot)) = GetCurrentProcId(); 326 } 327 328 // Set our thread local storage. 329 TLSData* data = new TLSData; 330 data->table = this; 331 data->slot = slot; 332 tls_index_.Set(data); 333 return slot; 334} 335 336int StatsTable::CountThreadsRegistered() const { 337 if (!impl_) 338 return 0; 339 340 // Loop through the shared memory and count the threads that are active. 341 // We intentionally do not lock the table during the operation. 342 int count = 0; 343 for (int index = 1; index <= impl_->max_threads(); index++) { 344 char* name = impl_->thread_name(index); 345 if (*name != '\0') 346 count++; 347 } 348 return count; 349} 350 351int StatsTable::FindCounter(const std::string& name) { 352 // Note: the API returns counters numbered from 1..N, although 353 // internally, the array is 0..N-1. This is so that we can return 354 // zero as "not found". 355 if (!impl_) 356 return 0; 357 358 // Create a scope for our auto-lock. 359 { 360 AutoLock scoped_lock(counters_lock_); 361 362 // Attempt to find the counter. 363 CountersMap::const_iterator iter; 364 iter = counters_.find(name); 365 if (iter != counters_.end()) 366 return iter->second; 367 } 368 369 // Counter does not exist, so add it. 370 return AddCounter(name); 371} 372 373int* StatsTable::GetLocation(int counter_id, int slot_id) const { 374 if (!impl_) 375 return NULL; 376 if (slot_id > impl_->max_threads()) 377 return NULL; 378 379 int* row = impl_->row(counter_id); 380 return &(row[slot_id-1]); 381} 382 383const char* StatsTable::GetRowName(int index) const { 384 if (!impl_) 385 return NULL; 386 387 return impl_->counter_name(index); 388} 389 390int StatsTable::GetRowValue(int index) const { 391 return GetRowValue(index, 0); 392} 393 394int StatsTable::GetRowValue(int index, int pid) const { 395 if (!impl_) 396 return 0; 397 398 int rv = 0; 399 int* row = impl_->row(index); 400 for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) { 401 if (pid == 0 || *impl_->thread_pid(slot_id) == pid) 402 rv += row[slot_id]; 403 } 404 return rv; 405} 406 407int StatsTable::GetCounterValue(const std::string& name) { 408 return GetCounterValue(name, 0); 409} 410 411int StatsTable::GetCounterValue(const std::string& name, int pid) { 412 if (!impl_) 413 return 0; 414 415 int row = FindCounter(name); 416 if (!row) 417 return 0; 418 return GetRowValue(row, pid); 419} 420 421int StatsTable::GetMaxCounters() const { 422 if (!impl_) 423 return 0; 424 return impl_->max_counters(); 425} 426 427int StatsTable::GetMaxThreads() const { 428 if (!impl_) 429 return 0; 430 return impl_->max_threads(); 431} 432 433int* StatsTable::FindLocation(const char* name) { 434 // Get the static StatsTable 435 StatsTable *table = StatsTable::current(); 436 if (!table) 437 return NULL; 438 439 // Get the slot for this thread. Try to register 440 // it if none exists. 441 int slot = table->GetSlot(); 442 if (!slot && !(slot = table->RegisterThread(std::string()))) 443 return NULL; 444 445 // Find the counter id for the counter. 446 std::string str_name(name); 447 int counter = table->FindCounter(str_name); 448 449 // Now we can find the location in the table. 450 return table->GetLocation(counter, slot); 451} 452 453void StatsTable::UnregisterThread() { 454 UnregisterThread(GetTLSData()); 455} 456 457void StatsTable::UnregisterThread(TLSData* data) { 458 if (!data) 459 return; 460 DCHECK(impl_); 461 462 // Mark the slot free by zeroing out the thread name. 463 char* name = impl_->thread_name(data->slot); 464 *name = '\0'; 465 466 // Remove the calling thread's TLS so that it cannot use the slot. 467 tls_index_.Set(NULL); 468 delete data; 469} 470 471void StatsTable::SlotReturnFunction(void* data) { 472 // This is called by the TLS destructor, which on some platforms has 473 // already cleared the TLS info, so use the tls_data argument 474 // rather than trying to fetch it ourselves. 475 TLSData* tls_data = static_cast<TLSData*>(data); 476 if (tls_data) { 477 DCHECK(tls_data->table); 478 tls_data->table->UnregisterThread(tls_data); 479 } 480} 481 482int StatsTable::FindEmptyThread() const { 483 // Note: the API returns slots numbered from 1..N, although 484 // internally, the array is 0..N-1. This is so that we can return 485 // zero as "not found". 486 // 487 // The reason for doing this is because the thread 'slot' is stored 488 // in TLS, which is always initialized to zero, not -1. If 0 were 489 // returned as a valid slot number, it would be confused with the 490 // uninitialized state. 491 if (!impl_) 492 return 0; 493 494 int index = 1; 495 for (; index <= impl_->max_threads(); index++) { 496 char* name = impl_->thread_name(index); 497 if (!*name) 498 break; 499 } 500 if (index > impl_->max_threads()) 501 return 0; // The table is full. 502 return index; 503} 504 505int StatsTable::FindCounterOrEmptyRow(const std::string& name) const { 506 // Note: the API returns slots numbered from 1..N, although 507 // internally, the array is 0..N-1. This is so that we can return 508 // zero as "not found". 509 // 510 // There isn't much reason for this other than to be consistent 511 // with the way we track columns for thread slots. (See comments 512 // in FindEmptyThread for why it is done this way). 513 if (!impl_) 514 return 0; 515 516 int free_slot = 0; 517 for (int index = 1; index <= impl_->max_counters(); index++) { 518 char* row_name = impl_->counter_name(index); 519 if (!*row_name && !free_slot) 520 free_slot = index; // save that we found a free slot 521 else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength)) 522 return index; 523 } 524 return free_slot; 525} 526 527int StatsTable::AddCounter(const std::string& name) { 528 if (!impl_) 529 return 0; 530 531 int counter_id = 0; 532 { 533 // To add a counter to the shared memory, we need the 534 // shared memory lock. 535 SharedMemoryAutoLock lock(impl_->shared_memory()); 536 537 // We have space, so create a new counter. 538 counter_id = FindCounterOrEmptyRow(name); 539 if (!counter_id) 540 return 0; 541 542 std::string counter_name = name; 543 if (name.empty()) 544 counter_name = kUnknownName; 545 strlcpy(impl_->counter_name(counter_id), counter_name.c_str(), 546 kMaxCounterNameLength); 547 } 548 549 // now add to our in-memory cache 550 { 551 AutoLock lock(counters_lock_); 552 counters_[name] = counter_id; 553 } 554 return counter_id; 555} 556 557StatsTable::TLSData* StatsTable::GetTLSData() const { 558 TLSData* data = 559 static_cast<TLSData*>(tls_index_.Get()); 560 if (!data) 561 return NULL; 562 563 DCHECK(data->slot); 564 DCHECK_EQ(data->table, this); 565 return data; 566} 567 568} // namespace base 569