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 21 namespace 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. 69 COMPILE_ASSERT(sizeof(int)==4, expect_4_byte_ints); 70 71 namespace { 72 73 // An internal version in case we ever change the format of this 74 // file, and so that we can identify our table. 75 const int kTableVersion = 0x13131313; 76 77 // The name for un-named counters and threads in the table. 78 const char kUnknownName[] = "<unknown>"; 79 80 // Calculates delta to align an offset to the size of an int 81 inline int AlignOffset(int offset) { 82 return (sizeof(int) - (offset % sizeof(int))) % sizeof(int); 83 } 84 85 inline 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. 94 class 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 166 StatsTable::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 194 void 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 208 void 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. 249 struct StatsTable::TLSData { 250 StatsTable* table; 251 int slot; 252 }; 253 254 // We keep a singleton table which can be easily accessed. 255 StatsTable* StatsTable::global_table_ = NULL; 256 257 StatsTable::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 275 StatsTable::~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 292 int StatsTable::GetSlot() const { 293 TLSData* data = GetTLSData(); 294 if (!data) 295 return 0; 296 return data->slot; 297 } 298 299 int 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 336 int 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 351 int 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 373 int* 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 383 const char* StatsTable::GetRowName(int index) const { 384 if (!impl_) 385 return NULL; 386 387 return impl_->counter_name(index); 388 } 389 390 int StatsTable::GetRowValue(int index) const { 391 return GetRowValue(index, 0); 392 } 393 394 int 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 407 int StatsTable::GetCounterValue(const std::string& name) { 408 return GetCounterValue(name, 0); 409 } 410 411 int 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 421 int StatsTable::GetMaxCounters() const { 422 if (!impl_) 423 return 0; 424 return impl_->max_counters(); 425 } 426 427 int StatsTable::GetMaxThreads() const { 428 if (!impl_) 429 return 0; 430 return impl_->max_threads(); 431 } 432 433 int* 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 453 void StatsTable::UnregisterThread() { 454 UnregisterThread(GetTLSData()); 455 } 456 457 void 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 471 void 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 482 int 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 505 int 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 527 int 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 562 StatsTable::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