stats_table.cc revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2010 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/platform_thread.h" 9#include "base/process_util.h" 10#include "base/scoped_ptr.h" 11#include "base/shared_memory.h" 12#include "base/string_piece.h" 13#include "base/string_util.h" 14#include "base/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 141 // Initializes the table on first access. Sets header values 142 // appropriately and zeroes all counters. 143 void InitializeTable(void* memory, int size, int max_counters, 144 int max_threads); 145 146 // Initializes our in-memory pointers into a pre-created StatsTable. 147 void ComputeMappedPointers(void* memory); 148 149 SharedMemory shared_memory_; 150 TableHeader* table_header_; 151 char* thread_names_table_; 152 PlatformThreadId* thread_tid_table_; 153 int* thread_pid_table_; 154 char* counter_names_table_; 155 int* data_table_; 156}; 157 158// static 159StatsTable::Private* StatsTable::Private::New(const std::string& name, 160 int size, 161 int max_threads, 162 int max_counters) { 163#ifdef ANDROID 164 return NULL; 165#else 166 scoped_ptr<Private> priv(new Private()); 167 if (!priv->shared_memory_.CreateNamed(name, true, size)) 168 return NULL; 169 if (!priv->shared_memory_.Map(size)) 170 return NULL; 171 void* memory = priv->shared_memory_.memory(); 172 173 TableHeader* header = static_cast<TableHeader*>(memory); 174 175 // If the version does not match, then assume the table needs 176 // to be initialized. 177 if (header->version != kTableVersion) 178 priv->InitializeTable(memory, size, max_counters, max_threads); 179 180 // We have a valid table, so compute our pointers. 181 priv->ComputeMappedPointers(memory); 182 183 return priv.release(); 184#endif 185} 186 187void StatsTable::Private::InitializeTable(void* memory, int size, 188 int max_counters, 189 int max_threads) { 190 // Zero everything. 191 memset(memory, 0, size); 192 193 // Initialize the header. 194 TableHeader* header = static_cast<TableHeader*>(memory); 195 header->version = kTableVersion; 196 header->size = size; 197 header->max_counters = max_counters; 198 header->max_threads = max_threads; 199} 200 201void StatsTable::Private::ComputeMappedPointers(void* memory) { 202 char* data = static_cast<char*>(memory); 203 int offset = 0; 204 205 table_header_ = reinterpret_cast<TableHeader*>(data); 206 offset += sizeof(*table_header_); 207 offset += AlignOffset(offset); 208 209 // Verify we're looking at a valid StatsTable. 210 DCHECK_EQ(table_header_->version, kTableVersion); 211 212 thread_names_table_ = reinterpret_cast<char*>(data + offset); 213 offset += sizeof(char) * 214 max_threads() * StatsTable::kMaxThreadNameLength; 215 offset += AlignOffset(offset); 216 217 thread_tid_table_ = reinterpret_cast<PlatformThreadId*>(data + offset); 218 offset += sizeof(int) * max_threads(); 219 offset += AlignOffset(offset); 220 221 thread_pid_table_ = reinterpret_cast<int*>(data + offset); 222 offset += sizeof(int) * max_threads(); 223 offset += AlignOffset(offset); 224 225 counter_names_table_ = reinterpret_cast<char*>(data + offset); 226 offset += sizeof(char) * 227 max_counters() * StatsTable::kMaxCounterNameLength; 228 offset += AlignOffset(offset); 229 230 data_table_ = reinterpret_cast<int*>(data + offset); 231 offset += sizeof(int) * max_threads() * max_counters(); 232 233 DCHECK_EQ(offset, size()); 234} 235 236// TLSData carries the data stored in the TLS slots for the 237// StatsTable. This is used so that we can properly cleanup when the 238// thread exits and return the table slot. 239// 240// Each thread that calls RegisterThread in the StatsTable will have 241// a TLSData stored in its TLS. 242struct StatsTable::TLSData { 243 StatsTable* table; 244 int slot; 245}; 246 247// We keep a singleton table which can be easily accessed. 248StatsTable* StatsTable::global_table_ = NULL; 249 250StatsTable::StatsTable(const std::string& name, int max_threads, 251 int max_counters) 252 : impl_(NULL), 253 tls_index_(SlotReturnFunction) { 254 int table_size = 255 AlignedSize(sizeof(Private::TableHeader)) + 256 AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) + 257 AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) + 258 AlignedSize(max_threads * sizeof(int)) + 259 AlignedSize(max_threads * sizeof(int)) + 260 AlignedSize((sizeof(int) * (max_counters * max_threads))); 261 262 impl_ = Private::New(name, table_size, max_threads, max_counters); 263 264 if (!impl_) 265 PLOG(ERROR) << "StatsTable did not initialize"; 266} 267 268StatsTable::~StatsTable() { 269 // Before we tear down our copy of the table, be sure to 270 // unregister our thread. 271 UnregisterThread(); 272 273 // Return ThreadLocalStorage. At this point, if any registered threads 274 // still exist, they cannot Unregister. 275 tls_index_.Free(); 276 277 // Cleanup our shared memory. 278 delete impl_; 279 280 // If we are the global table, unregister ourselves. 281 if (global_table_ == this) 282 global_table_ = NULL; 283} 284 285int StatsTable::RegisterThread(const std::string& name) { 286#ifdef ANDROID 287 return 0; 288#else 289 int slot = 0; 290 if (!impl_) 291 return 0; 292 293 // Registering a thread requires that we lock the shared memory 294 // so that two threads don't grab the same slot. Fortunately, 295 // thread creation shouldn't happen in inner loops. 296 { 297 SharedMemoryAutoLock lock(impl_->shared_memory()); 298 slot = FindEmptyThread(); 299 if (!slot) { 300 return 0; 301 } 302 303 // We have space, so consume a column in the table. 304 std::string thread_name = name; 305 if (name.empty()) 306 thread_name = kUnknownName; 307 strlcpy(impl_->thread_name(slot), thread_name.c_str(), 308 kMaxThreadNameLength); 309 *(impl_->thread_tid(slot)) = PlatformThread::CurrentId(); 310 *(impl_->thread_pid(slot)) = GetCurrentProcId(); 311 } 312 313 // Set our thread local storage. 314 TLSData* data = new TLSData; 315 data->table = this; 316 data->slot = slot; 317 tls_index_.Set(data); 318 return slot; 319#endif 320} 321 322StatsTable::TLSData* StatsTable::GetTLSData() const { 323 TLSData* data = 324 static_cast<TLSData*>(tls_index_.Get()); 325 if (!data) 326 return NULL; 327 328 DCHECK(data->slot); 329 DCHECK_EQ(data->table, this); 330 return data; 331} 332 333void StatsTable::UnregisterThread() { 334 UnregisterThread(GetTLSData()); 335} 336 337void StatsTable::UnregisterThread(TLSData* data) { 338 if (!data) 339 return; 340 DCHECK(impl_); 341 342 // Mark the slot free by zeroing out the thread name. 343 char* name = impl_->thread_name(data->slot); 344 *name = '\0'; 345 346 // Remove the calling thread's TLS so that it cannot use the slot. 347 tls_index_.Set(NULL); 348 delete data; 349} 350 351void StatsTable::SlotReturnFunction(void* data) { 352 // This is called by the TLS destructor, which on some platforms has 353 // already cleared the TLS info, so use the tls_data argument 354 // rather than trying to fetch it ourselves. 355 TLSData* tls_data = static_cast<TLSData*>(data); 356 if (tls_data) { 357 DCHECK(tls_data->table); 358 tls_data->table->UnregisterThread(tls_data); 359 } 360} 361 362int StatsTable::CountThreadsRegistered() const { 363 if (!impl_) 364 return 0; 365 366 // Loop through the shared memory and count the threads that are active. 367 // We intentionally do not lock the table during the operation. 368 int count = 0; 369 for (int index = 1; index <= impl_->max_threads(); index++) { 370 char* name = impl_->thread_name(index); 371 if (*name != '\0') 372 count++; 373 } 374 return count; 375} 376 377int StatsTable::GetSlot() const { 378 TLSData* data = GetTLSData(); 379 if (!data) 380 return 0; 381 return data->slot; 382} 383 384int StatsTable::FindEmptyThread() const { 385 // Note: the API returns slots numbered from 1..N, although 386 // internally, the array is 0..N-1. This is so that we can return 387 // zero as "not found". 388 // 389 // The reason for doing this is because the thread 'slot' is stored 390 // in TLS, which is always initialized to zero, not -1. If 0 were 391 // returned as a valid slot number, it would be confused with the 392 // uninitialized state. 393 if (!impl_) 394 return 0; 395 396 int index = 1; 397 for (; index <= impl_->max_threads(); index++) { 398 char* name = impl_->thread_name(index); 399 if (!*name) 400 break; 401 } 402 if (index > impl_->max_threads()) 403 return 0; // The table is full. 404 return index; 405} 406 407int StatsTable::FindCounterOrEmptyRow(const std::string& name) const { 408 // Note: the API returns slots numbered from 1..N, although 409 // internally, the array is 0..N-1. This is so that we can return 410 // zero as "not found". 411 // 412 // There isn't much reason for this other than to be consistent 413 // with the way we track columns for thread slots. (See comments 414 // in FindEmptyThread for why it is done this way). 415 if (!impl_) 416 return 0; 417 418 int free_slot = 0; 419 for (int index = 1; index <= impl_->max_counters(); index++) { 420 char* row_name = impl_->counter_name(index); 421 if (!*row_name && !free_slot) 422 free_slot = index; // save that we found a free slot 423 else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength)) 424 return index; 425 } 426 return free_slot; 427} 428 429int StatsTable::FindCounter(const std::string& name) { 430 // Note: the API returns counters numbered from 1..N, although 431 // internally, the array is 0..N-1. This is so that we can return 432 // zero as "not found". 433 if (!impl_) 434 return 0; 435 436 // Create a scope for our auto-lock. 437 { 438 AutoLock scoped_lock(counters_lock_); 439 440 // Attempt to find the counter. 441 CountersMap::const_iterator iter; 442 iter = counters_.find(name); 443 if (iter != counters_.end()) 444 return iter->second; 445 } 446 447 // Counter does not exist, so add it. 448 return AddCounter(name); 449} 450 451int StatsTable::AddCounter(const std::string& name) { 452#ifdef ANDROID 453 return 0; 454#else 455 456 if (!impl_) 457 return 0; 458 459 int counter_id = 0; 460 { 461 // To add a counter to the shared memory, we need the 462 // shared memory lock. 463 SharedMemoryAutoLock lock(impl_->shared_memory()); 464 465 // We have space, so create a new counter. 466 counter_id = FindCounterOrEmptyRow(name); 467 if (!counter_id) 468 return 0; 469 470 std::string counter_name = name; 471 if (name.empty()) 472 counter_name = kUnknownName; 473 strlcpy(impl_->counter_name(counter_id), counter_name.c_str(), 474 kMaxCounterNameLength); 475 } 476 477 // now add to our in-memory cache 478 { 479 AutoLock lock(counters_lock_); 480 counters_[name] = counter_id; 481 } 482 return counter_id; 483#endif 484} 485 486int* StatsTable::GetLocation(int counter_id, int slot_id) const { 487 if (!impl_) 488 return NULL; 489 if (slot_id > impl_->max_threads()) 490 return NULL; 491 492 int* row = impl_->row(counter_id); 493 return &(row[slot_id-1]); 494} 495 496const char* StatsTable::GetRowName(int index) const { 497 if (!impl_) 498 return NULL; 499 500 return impl_->counter_name(index); 501} 502 503int StatsTable::GetRowValue(int index, int pid) const { 504 if (!impl_) 505 return 0; 506 507 int rv = 0; 508 int* row = impl_->row(index); 509 for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) { 510 if (pid == 0 || *impl_->thread_pid(slot_id) == pid) 511 rv += row[slot_id]; 512 } 513 return rv; 514} 515 516int StatsTable::GetRowValue(int index) const { 517 return GetRowValue(index, 0); 518} 519 520int StatsTable::GetCounterValue(const std::string& name, int pid) { 521 if (!impl_) 522 return 0; 523 524 int row = FindCounter(name); 525 if (!row) 526 return 0; 527 return GetRowValue(row, pid); 528} 529 530int StatsTable::GetCounterValue(const std::string& name) { 531 return GetCounterValue(name, 0); 532} 533 534int StatsTable::GetMaxCounters() const { 535 if (!impl_) 536 return 0; 537 return impl_->max_counters(); 538} 539 540int StatsTable::GetMaxThreads() const { 541 if (!impl_) 542 return 0; 543 return impl_->max_threads(); 544} 545 546int* StatsTable::FindLocation(const char* name) { 547 // Get the static StatsTable 548 StatsTable *table = StatsTable::current(); 549 if (!table) 550 return NULL; 551 552 // Get the slot for this thread. Try to register 553 // it if none exists. 554 int slot = table->GetSlot(); 555 if (!slot && !(slot = table->RegisterThread(""))) 556 return NULL; 557 558 // Find the counter id for the counter. 559 std::string str_name(name); 560 int counter = table->FindCounter(str_name); 561 562 // Now we can find the location in the table. 563 return table->GetLocation(counter, slot); 564} 565 566} // namespace base 567