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