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/process_util.h" 10#include "base/shared_memory.h" 11#include "base/string_piece.h" 12#include "base/string_util.h" 13#include "base/threading/platform_thread.h" 14#include "base/threading/thread_local_storage.h" 15#include "base/utf_string_conversions.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#ifdef ANDROID 171 return NULL; 172#else 173 scoped_ptr<Private> priv(new Private()); 174 if (!priv->shared_memory_.CreateNamed(name, true, size)) 175 return NULL; 176 if (!priv->shared_memory_.Map(size)) 177 return NULL; 178 void* memory = priv->shared_memory_.memory(); 179 180 TableHeader* header = static_cast<TableHeader*>(memory); 181 182 // If the version does not match, then assume the table needs 183 // to be initialized. 184 if (header->version != kTableVersion) 185 priv->InitializeTable(memory, size, max_counters, max_threads); 186 187 // We have a valid table, so compute our pointers. 188 priv->ComputeMappedPointers(memory); 189 190 return priv.release(); 191#endif 192} 193 194void StatsTable::Private::InitializeTable(void* memory, int size, 195 int max_counters, 196 int max_threads) { 197 // Zero everything. 198 memset(memory, 0, size); 199 200 // Initialize the header. 201 TableHeader* header = static_cast<TableHeader*>(memory); 202 header->version = kTableVersion; 203 header->size = size; 204 header->max_counters = max_counters; 205 header->max_threads = max_threads; 206} 207 208void StatsTable::Private::ComputeMappedPointers(void* memory) { 209 char* data = static_cast<char*>(memory); 210 int offset = 0; 211 212 table_header_ = reinterpret_cast<TableHeader*>(data); 213 offset += sizeof(*table_header_); 214 offset += AlignOffset(offset); 215 216 // Verify we're looking at a valid StatsTable. 217 DCHECK_EQ(table_header_->version, kTableVersion); 218 219 thread_names_table_ = reinterpret_cast<char*>(data + offset); 220 offset += sizeof(char) * 221 max_threads() * StatsTable::kMaxThreadNameLength; 222 offset += AlignOffset(offset); 223 224 thread_tid_table_ = reinterpret_cast<PlatformThreadId*>(data + offset); 225 offset += sizeof(int) * max_threads(); 226 offset += AlignOffset(offset); 227 228 thread_pid_table_ = reinterpret_cast<int*>(data + offset); 229 offset += sizeof(int) * max_threads(); 230 offset += AlignOffset(offset); 231 232 counter_names_table_ = reinterpret_cast<char*>(data + offset); 233 offset += sizeof(char) * 234 max_counters() * StatsTable::kMaxCounterNameLength; 235 offset += AlignOffset(offset); 236 237 data_table_ = reinterpret_cast<int*>(data + offset); 238 offset += sizeof(int) * max_threads() * max_counters(); 239 240 DCHECK_EQ(offset, size()); 241} 242 243// TLSData carries the data stored in the TLS slots for the 244// StatsTable. This is used so that we can properly cleanup when the 245// thread exits and return the table slot. 246// 247// Each thread that calls RegisterThread in the StatsTable will have 248// a TLSData stored in its TLS. 249struct StatsTable::TLSData { 250 StatsTable* table; 251 int slot; 252}; 253 254// We keep a singleton table which can be easily accessed. 255StatsTable* StatsTable::global_table_ = NULL; 256 257StatsTable::StatsTable(const std::string& name, int max_threads, 258 int max_counters) 259 : impl_(NULL), 260 tls_index_(SlotReturnFunction) { 261 int table_size = 262 AlignedSize(sizeof(Private::TableHeader)) + 263 AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) + 264 AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) + 265 AlignedSize(max_threads * sizeof(int)) + 266 AlignedSize(max_threads * sizeof(int)) + 267 AlignedSize((sizeof(int) * (max_counters * max_threads))); 268 269 impl_ = Private::New(name, table_size, max_threads, max_counters); 270 271 if (!impl_) 272 PLOG(ERROR) << "StatsTable did not initialize"; 273} 274 275StatsTable::~StatsTable() { 276 // Before we tear down our copy of the table, be sure to 277 // unregister our thread. 278 UnregisterThread(); 279 280 // Return ThreadLocalStorage. At this point, if any registered threads 281 // still exist, they cannot Unregister. 282 tls_index_.Free(); 283 284 // Cleanup our shared memory. 285 delete impl_; 286 287 // If we are the global table, unregister ourselves. 288 if (global_table_ == this) 289 global_table_ = NULL; 290} 291 292int StatsTable::GetSlot() const { 293 TLSData* data = GetTLSData(); 294 if (!data) 295 return 0; 296 return data->slot; 297} 298 299int StatsTable::RegisterThread(const std::string& name) { 300#ifdef ANDROID 301 return 0; 302#else 303 int slot = 0; 304 if (!impl_) 305 return 0; 306 307 // Registering a thread requires that we lock the shared memory 308 // so that two threads don't grab the same slot. Fortunately, 309 // thread creation shouldn't happen in inner loops. 310 { 311 SharedMemoryAutoLock lock(impl_->shared_memory()); 312 slot = FindEmptyThread(); 313 if (!slot) { 314 return 0; 315 } 316 317 // We have space, so consume a column in the table. 318 std::string thread_name = name; 319 if (name.empty()) 320 thread_name = kUnknownName; 321 strlcpy(impl_->thread_name(slot), thread_name.c_str(), 322 kMaxThreadNameLength); 323 *(impl_->thread_tid(slot)) = PlatformThread::CurrentId(); 324 *(impl_->thread_pid(slot)) = GetCurrentProcId(); 325 } 326 327 // Set our thread local storage. 328 TLSData* data = new TLSData; 329 data->table = this; 330 data->slot = slot; 331 tls_index_.Set(data); 332 return slot; 333#endif 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(""))) 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#ifdef ANDROID 529 return 0; 530#else 531 532 if (!impl_) 533 return 0; 534 535 int counter_id = 0; 536 { 537 // To add a counter to the shared memory, we need the 538 // shared memory lock. 539 SharedMemoryAutoLock lock(impl_->shared_memory()); 540 541 // We have space, so create a new counter. 542 counter_id = FindCounterOrEmptyRow(name); 543 if (!counter_id) 544 return 0; 545 546 std::string counter_name = name; 547 if (name.empty()) 548 counter_name = kUnknownName; 549 strlcpy(impl_->counter_name(counter_id), counter_name.c_str(), 550 kMaxCounterNameLength); 551 } 552 553 // now add to our in-memory cache 554 { 555 AutoLock lock(counters_lock_); 556 counters_[name] = counter_id; 557 } 558 return counter_id; 559#endif 560} 561 562StatsTable::TLSData* StatsTable::GetTLSData() const { 563 TLSData* data = 564 static_cast<TLSData*>(tls_index_.Get()); 565 if (!data) 566 return NULL; 567 568 DCHECK(data->slot); 569 DCHECK_EQ(data->table, this); 570 return data; 571} 572 573} // namespace base 574