ipc/chromium/src/base/stats_table.cc

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/ipc/chromium/src/base/stats_table.cc	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,559 @@
     1.4 +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
     1.5 +// Use of this source code is governed by a BSD-style license that can be
     1.6 +// found in the LICENSE file.
     1.7 +
     1.8 +#include "base/stats_table.h"
     1.9 +
    1.10 +#include "base/logging.h"
    1.11 +#include "base/platform_thread.h"
    1.12 +#include "base/process_util.h"
    1.13 +#include "base/scoped_ptr.h"
    1.14 +#include "base/shared_memory.h"
    1.15 +#include "base/string_piece.h"
    1.16 +#include "base/string_util.h"
    1.17 +#include "base/sys_string_conversions.h"
    1.18 +#include "base/thread_local_storage.h"
    1.19 +
    1.20 +#if defined(OS_POSIX)
    1.21 +#include "errno.h"
    1.22 +#endif
    1.23 +
    1.24 +// The StatsTable uses a shared memory segment that is laid out as follows
    1.25 +//
    1.26 +// +-------------------------------------------+
    1.27 +// | Version | Size | MaxCounters | MaxThreads |
    1.28 +// +-------------------------------------------+
    1.29 +// | Thread names table                        |
    1.30 +// +-------------------------------------------+
    1.31 +// | Thread TID table                          |
    1.32 +// +-------------------------------------------+
    1.33 +// | Thread PID table                          |
    1.34 +// +-------------------------------------------+
    1.35 +// | Counter names table                       |
    1.36 +// +-------------------------------------------+
    1.37 +// | Data                                      |
    1.38 +// +-------------------------------------------+
    1.39 +//
    1.40 +// The data layout is a grid, where the columns are the thread_ids and the
    1.41 +// rows are the counter_ids.
    1.42 +//
    1.43 +// If the first character of the thread_name is '\0', then that column is
    1.44 +// empty.
    1.45 +// If the first character of the counter_name is '\0', then that row is
    1.46 +// empty.
    1.47 +//
    1.48 +// About Locking:
    1.49 +// This class is designed to be both multi-thread and multi-process safe.
    1.50 +// Aside from initialization, this is done by partitioning the data which
    1.51 +// each thread uses so that no locking is required.  However, to allocate
    1.52 +// the rows and columns of the table to particular threads, locking is
    1.53 +// required.
    1.54 +//
    1.55 +// At the shared-memory level, we have a lock.  This lock protects the
    1.56 +// shared-memory table only, and is used when we create new counters (e.g.
    1.57 +// use rows) or when we register new threads (e.g. use columns).  Reading
    1.58 +// data from the table does not require any locking at the shared memory
    1.59 +// level.
    1.60 +//
    1.61 +// Each process which accesses the table will create a StatsTable object.
    1.62 +// The StatsTable maintains a hash table of the existing counters in the
    1.63 +// table for faster lookup.  Since the hash table is process specific,
    1.64 +// each process maintains its own cache.  We avoid complexity here by never
    1.65 +// de-allocating from the hash table.  (Counters are dynamically added,
    1.66 +// but not dynamically removed).
    1.67 +
    1.68 +// In order for external viewers to be able to read our shared memory,
    1.69 +// we all need to use the same size ints.
    1.70 +COMPILE_ASSERT(sizeof(int)==4, expect_4_byte_ints);
    1.71 +
    1.72 +namespace {
    1.73 +
    1.74 +// An internal version in case we ever change the format of this
    1.75 +// file, and so that we can identify our table.
    1.76 +const int kTableVersion = 0x13131313;
    1.77 +
    1.78 +// The name for un-named counters and threads in the table.
    1.79 +const char kUnknownName[] = "<unknown>";
    1.80 +
    1.81 +// Calculates delta to align an offset to the size of an int
    1.82 +inline int AlignOffset(int offset) {
    1.83 +  return (sizeof(int) - (offset % sizeof(int))) % sizeof(int);
    1.84 +}
    1.85 +
    1.86 +inline int AlignedSize(int size) {
    1.87 +  return size + AlignOffset(size);
    1.88 +}
    1.89 +
    1.90 +// StatsTableTLSData carries the data stored in the TLS slots for the
    1.91 +// StatsTable.  This is used so that we can properly cleanup when the
    1.92 +// thread exits and return the table slot.
    1.93 +//
    1.94 +// Each thread that calls RegisterThread in the StatsTable will have
    1.95 +// a StatsTableTLSData stored in its TLS.
    1.96 +struct StatsTableTLSData {
    1.97 +  StatsTable* table;
    1.98 +  int slot;
    1.99 +};
   1.100 +
   1.101 +}  // namespace
   1.102 +
   1.103 +// The StatsTablePrivate maintains convenience pointers into the
   1.104 +// shared memory segment.  Use this class to keep the data structure
   1.105 +// clean and accessible.
   1.106 +class StatsTablePrivate {
   1.107 + public:
   1.108 +  // Various header information contained in the memory mapped segment.
   1.109 +  struct TableHeader {
   1.110 +    int version;
   1.111 +    int size;
   1.112 +    int max_counters;
   1.113 +    int max_threads;
   1.114 +  };
   1.115 +
   1.116 +  // Construct a new StatsTablePrivate based on expected size parameters, or
   1.117 +  // return NULL on failure.
   1.118 +  static StatsTablePrivate* New(const std::string& name, int size,
   1.119 +                                int max_threads, int max_counters);
   1.120 +
   1.121 +  base::SharedMemory* shared_memory() { return &shared_memory_; }
   1.122 +
   1.123 +  // Accessors for our header pointers
   1.124 +  TableHeader* table_header() const { return table_header_; }
   1.125 +  int version() const { return table_header_->version; }
   1.126 +  int size() const { return table_header_->size; }
   1.127 +  int max_counters() const { return table_header_->max_counters; }
   1.128 +  int max_threads() const { return table_header_->max_threads; }
   1.129 +
   1.130 +  // Accessors for our tables
   1.131 +  char* thread_name(int slot_id) const {
   1.132 +    return &thread_names_table_[
   1.133 +      (slot_id-1) * (StatsTable::kMaxThreadNameLength)];
   1.134 +  }
   1.135 +  PlatformThreadId* thread_tid(int slot_id) const {
   1.136 +    return &(thread_tid_table_[slot_id-1]);
   1.137 +  }
   1.138 +  int* thread_pid(int slot_id) const {
   1.139 +    return &(thread_pid_table_[slot_id-1]);
   1.140 +  }
   1.141 +  char* counter_name(int counter_id) const {
   1.142 +    return &counter_names_table_[
   1.143 +      (counter_id-1) * (StatsTable::kMaxCounterNameLength)];
   1.144 +  }
   1.145 +  int* row(int counter_id) const {
   1.146 +    return &data_table_[(counter_id-1) * max_threads()];
   1.147 +  }
   1.148 +
   1.149 + private:
   1.150 +  // Constructor is private because you should use New() instead.
   1.151 +  StatsTablePrivate() {}
   1.152 +
   1.153 +  // Initializes the table on first access.  Sets header values
   1.154 +  // appropriately and zeroes all counters.
   1.155 +  void InitializeTable(void* memory, int size, int max_counters,
   1.156 +                       int max_threads);
   1.157 +
   1.158 +  // Initializes our in-memory pointers into a pre-created StatsTable.
   1.159 +  void ComputeMappedPointers(void* memory);
   1.160 +
   1.161 +  base::SharedMemory shared_memory_;
   1.162 +  TableHeader* table_header_;
   1.163 +  char* thread_names_table_;
   1.164 +  PlatformThreadId* thread_tid_table_;
   1.165 +  int* thread_pid_table_;
   1.166 +  char* counter_names_table_;
   1.167 +  int* data_table_;
   1.168 +};
   1.169 +
   1.170 +// static
   1.171 +StatsTablePrivate* StatsTablePrivate::New(const std::string& name,
   1.172 +                                          int size,
   1.173 +                                          int max_threads,
   1.174 +                                          int max_counters) {
   1.175 +  scoped_ptr<StatsTablePrivate> priv(new StatsTablePrivate());
   1.176 +  if (!priv->shared_memory_.Create(name, false, true, size))
   1.177 +    return NULL;
   1.178 +  if (!priv->shared_memory_.Map(size))
   1.179 +    return NULL;
   1.180 +  void* memory = priv->shared_memory_.memory();
   1.181 +
   1.182 +  TableHeader* header = static_cast<TableHeader*>(memory);
   1.183 +
   1.184 +  // If the version does not match, then assume the table needs
   1.185 +  // to be initialized.
   1.186 +  if (header->version != kTableVersion)
   1.187 +    priv->InitializeTable(memory, size, max_counters, max_threads);
   1.188 +
   1.189 +  // We have a valid table, so compute our pointers.
   1.190 +  priv->ComputeMappedPointers(memory);
   1.191 +
   1.192 +  return priv.release();
   1.193 +}
   1.194 +
   1.195 +void StatsTablePrivate::InitializeTable(void* memory, int size,
   1.196 +                                        int max_counters,
   1.197 +                                        int max_threads) {
   1.198 +  // Zero everything.
   1.199 +  memset(memory, 0, size);
   1.200 +
   1.201 +  // Initialize the header.
   1.202 +  TableHeader* header = static_cast<TableHeader*>(memory);
   1.203 +  header->version = kTableVersion;
   1.204 +  header->size = size;
   1.205 +  header->max_counters = max_counters;
   1.206 +  header->max_threads = max_threads;
   1.207 +}
   1.208 +
   1.209 +void StatsTablePrivate::ComputeMappedPointers(void* memory) {
   1.210 +  char* data = static_cast<char*>(memory);
   1.211 +  int offset = 0;
   1.212 +
   1.213 +  table_header_ = reinterpret_cast<TableHeader*>(data);
   1.214 +  offset += sizeof(*table_header_);
   1.215 +  offset += AlignOffset(offset);
   1.216 +
   1.217 +  // Verify we're looking at a valid StatsTable.
   1.218 +  DCHECK_EQ(table_header_->version, kTableVersion);
   1.219 +
   1.220 +  thread_names_table_ = reinterpret_cast<char*>(data + offset);
   1.221 +  offset += sizeof(char) *
   1.222 +            max_threads() * StatsTable::kMaxThreadNameLength;
   1.223 +  offset += AlignOffset(offset);
   1.224 +
   1.225 +  thread_tid_table_ = reinterpret_cast<PlatformThreadId*>(data + offset);
   1.226 +  offset += sizeof(int) * max_threads();
   1.227 +  offset += AlignOffset(offset);
   1.228 +
   1.229 +  thread_pid_table_ = reinterpret_cast<int*>(data + offset);
   1.230 +  offset += sizeof(int) * max_threads();
   1.231 +  offset += AlignOffset(offset);
   1.232 +
   1.233 +  counter_names_table_ = reinterpret_cast<char*>(data + offset);
   1.234 +  offset += sizeof(char) *
   1.235 +            max_counters() * StatsTable::kMaxCounterNameLength;
   1.236 +  offset += AlignOffset(offset);
   1.237 +
   1.238 +  data_table_ = reinterpret_cast<int*>(data + offset);
   1.239 +  offset += sizeof(int) * max_threads() * max_counters();
   1.240 +
   1.241 +  DCHECK_EQ(offset, size());
   1.242 +}
   1.243 +
   1.244 +
   1.245 +
   1.246 +// We keep a singleton table which can be easily accessed.
   1.247 +StatsTable* StatsTable::global_table_ = NULL;
   1.248 +
   1.249 +StatsTable::StatsTable(const std::string& name, int max_threads,
   1.250 +                       int max_counters)
   1.251 +    : impl_(NULL),
   1.252 +      tls_index_(SlotReturnFunction) {
   1.253 +  int table_size =
   1.254 +    AlignedSize(sizeof(StatsTablePrivate::TableHeader)) +
   1.255 +    AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) +
   1.256 +    AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) +
   1.257 +    AlignedSize(max_threads * sizeof(int)) +
   1.258 +    AlignedSize(max_threads * sizeof(int)) +
   1.259 +    AlignedSize((sizeof(int) * (max_counters * max_threads)));
   1.260 +
   1.261 +  impl_ = StatsTablePrivate::New(name, table_size, max_threads, max_counters);
   1.262 +
   1.263 +  // TODO(port): clean up this error reporting.
   1.264 +#if defined(OS_WIN)
   1.265 +  if (!impl_)
   1.266 +    CHROMIUM_LOG(ERROR) << "StatsTable did not initialize:" << GetLastError();
   1.267 +#elif defined(OS_POSIX)
   1.268 +  if (!impl_)
   1.269 +    CHROMIUM_LOG(ERROR) << "StatsTable did not initialize:" << strerror(errno);
   1.270 +#endif
   1.271 +}
   1.272 +
   1.273 +StatsTable::~StatsTable() {
   1.274 +  // Before we tear down our copy of the table, be sure to
   1.275 +  // unregister our thread.
   1.276 +  UnregisterThread();
   1.277 +
   1.278 +  // Return ThreadLocalStorage.  At this point, if any registered threads
   1.279 +  // still exist, they cannot Unregister.
   1.280 +  tls_index_.Free();
   1.281 +
   1.282 +  // Cleanup our shared memory.
   1.283 +  delete impl_;
   1.284 +
   1.285 +  // If we are the global table, unregister ourselves.
   1.286 +  if (global_table_ == this)
   1.287 +    global_table_ = NULL;
   1.288 +}
   1.289 +
   1.290 +int StatsTable::RegisterThread(const std::string& name) {
   1.291 +  int slot = 0;
   1.292 +
   1.293 +  // Registering a thread requires that we lock the shared memory
   1.294 +  // so that two threads don't grab the same slot.  Fortunately,
   1.295 +  // thread creation shouldn't happen in inner loops.
   1.296 +  {
   1.297 +    base::SharedMemoryAutoLock lock(impl_->shared_memory());
   1.298 +    slot = FindEmptyThread();
   1.299 +    if (!slot) {
   1.300 +      return 0;
   1.301 +    }
   1.302 +
   1.303 +    DCHECK(impl_);
   1.304 +
   1.305 +    // We have space, so consume a column in the table.
   1.306 +    std::string thread_name = name;
   1.307 +    if (name.empty())
   1.308 +      thread_name = kUnknownName;
   1.309 +    base::strlcpy(impl_->thread_name(slot), thread_name.c_str(),
   1.310 +                  kMaxThreadNameLength);
   1.311 +    *(impl_->thread_tid(slot)) = PlatformThread::CurrentId();
   1.312 +    *(impl_->thread_pid(slot)) = base::GetCurrentProcId();
   1.313 +  }
   1.314 +
   1.315 +  // Set our thread local storage.
   1.316 +  StatsTableTLSData* data = new StatsTableTLSData;
   1.317 +  data->table = this;
   1.318 +  data->slot = slot;
   1.319 +  tls_index_.Set(data);
   1.320 +  return slot;
   1.321 +}
   1.322 +
   1.323 +StatsTableTLSData* StatsTable::GetTLSData() const {
   1.324 +  StatsTableTLSData* data =
   1.325 +    static_cast<StatsTableTLSData*>(tls_index_.Get());
   1.326 +  if (!data)
   1.327 +    return NULL;
   1.328 +
   1.329 +  DCHECK(data->slot);
   1.330 +  DCHECK_EQ(data->table, this);
   1.331 +  return data;
   1.332 +}
   1.333 +
   1.334 +void StatsTable::UnregisterThread() {
   1.335 +  UnregisterThread(GetTLSData());
   1.336 +}
   1.337 +
   1.338 +void StatsTable::UnregisterThread(StatsTableTLSData* data) {
   1.339 +  if (!data)
   1.340 +    return;
   1.341 +  DCHECK(impl_);
   1.342 +
   1.343 +  // Mark the slot free by zeroing out the thread name.
   1.344 +  char* name = impl_->thread_name(data->slot);
   1.345 +  *name = '\0';
   1.346 +
   1.347 +  // Remove the calling thread's TLS so that it cannot use the slot.
   1.348 +  tls_index_.Set(NULL);
   1.349 +  delete data;
   1.350 +}
   1.351 +
   1.352 +void StatsTable::SlotReturnFunction(void* data) {
   1.353 +  // This is called by the TLS destructor, which on some platforms has
   1.354 +  // already cleared the TLS info, so use the tls_data argument
   1.355 +  // rather than trying to fetch it ourselves.
   1.356 +  StatsTableTLSData* tls_data = static_cast<StatsTableTLSData*>(data);
   1.357 +  if (tls_data) {
   1.358 +    DCHECK(tls_data->table);
   1.359 +    tls_data->table->UnregisterThread(tls_data);
   1.360 +  }
   1.361 +}
   1.362 +
   1.363 +int StatsTable::CountThreadsRegistered() const {
   1.364 +  if (!impl_)
   1.365 +    return 0;
   1.366 +
   1.367 +  // Loop through the shared memory and count the threads that are active.
   1.368 +  // We intentionally do not lock the table during the operation.
   1.369 +  int count = 0;
   1.370 +  for (int index = 1; index <= impl_->max_threads(); index++) {
   1.371 +    char* name = impl_->thread_name(index);
   1.372 +    if (*name != '\0')
   1.373 +      count++;
   1.374 +  }
   1.375 +  return count;
   1.376 +}
   1.377 +
   1.378 +int StatsTable::GetSlot() const {
   1.379 +  StatsTableTLSData* data = GetTLSData();
   1.380 +  if (!data)
   1.381 +    return 0;
   1.382 +  return data->slot;
   1.383 +}
   1.384 +
   1.385 +int StatsTable::FindEmptyThread() const {
   1.386 +  // Note: the API returns slots numbered from 1..N, although
   1.387 +  // internally, the array is 0..N-1.  This is so that we can return
   1.388 +  // zero as "not found".
   1.389 +  //
   1.390 +  // The reason for doing this is because the thread 'slot' is stored
   1.391 +  // in TLS, which is always initialized to zero, not -1.  If 0 were
   1.392 +  // returned as a valid slot number, it would be confused with the
   1.393 +  // uninitialized state.
   1.394 +  if (!impl_)
   1.395 +    return 0;
   1.396 +
   1.397 +  int index = 1;
   1.398 +  for (; index <= impl_->max_threads(); index++) {
   1.399 +    char* name = impl_->thread_name(index);
   1.400 +    if (!*name)
   1.401 +      break;
   1.402 +  }
   1.403 +  if (index > impl_->max_threads())
   1.404 +    return 0;  // The table is full.
   1.405 +  return index;
   1.406 +}
   1.407 +
   1.408 +int StatsTable::FindCounterOrEmptyRow(const std::string& name) const {
   1.409 +  // Note: the API returns slots numbered from 1..N, although
   1.410 +  // internally, the array is 0..N-1.  This is so that we can return
   1.411 +  // zero as "not found".
   1.412 +  //
   1.413 +  // There isn't much reason for this other than to be consistent
   1.414 +  // with the way we track columns for thread slots.  (See comments
   1.415 +  // in FindEmptyThread for why it is done this way).
   1.416 +  if (!impl_)
   1.417 +    return 0;
   1.418 +
   1.419 +  int free_slot = 0;
   1.420 +  for (int index = 1; index <= impl_->max_counters(); index++) {
   1.421 +    char* row_name = impl_->counter_name(index);
   1.422 +    if (!*row_name && !free_slot)
   1.423 +      free_slot = index;  // save that we found a free slot
   1.424 +    else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength))
   1.425 +      return index;
   1.426 +  }
   1.427 +  return free_slot;
   1.428 +}
   1.429 +
   1.430 +int StatsTable::FindCounter(const std::string& name) {
   1.431 +  // Note: the API returns counters numbered from 1..N, although
   1.432 +  // internally, the array is 0..N-1.  This is so that we can return
   1.433 +  // zero as "not found".
   1.434 +  if (!impl_)
   1.435 +    return 0;
   1.436 +
   1.437 +  // Create a scope for our auto-lock.
   1.438 +  {
   1.439 +    AutoLock scoped_lock(counters_lock_);
   1.440 +
   1.441 +    // Attempt to find the counter.
   1.442 +    CountersMap::const_iterator iter;
   1.443 +    iter = counters_.find(name);
   1.444 +    if (iter != counters_.end())
   1.445 +      return iter->second;
   1.446 +  }
   1.447 +
   1.448 +  // Counter does not exist, so add it.
   1.449 +  return AddCounter(name);
   1.450 +}
   1.451 +
   1.452 +int StatsTable::AddCounter(const std::string& name) {
   1.453 +  DCHECK(impl_);
   1.454 +
   1.455 +  if (!impl_)
   1.456 +    return 0;
   1.457 +
   1.458 +  int counter_id = 0;
   1.459 +  {
   1.460 +    // To add a counter to the shared memory, we need the
   1.461 +    // shared memory lock.
   1.462 +    base::SharedMemoryAutoLock lock(impl_->shared_memory());
   1.463 +
   1.464 +    // We have space, so create a new counter.
   1.465 +    counter_id = FindCounterOrEmptyRow(name);
   1.466 +    if (!counter_id)
   1.467 +      return 0;
   1.468 +
   1.469 +    std::string counter_name = name;
   1.470 +    if (name.empty())
   1.471 +      counter_name = kUnknownName;
   1.472 +    base::strlcpy(impl_->counter_name(counter_id), counter_name.c_str(),
   1.473 +                  kMaxCounterNameLength);
   1.474 +  }
   1.475 +
   1.476 +  // now add to our in-memory cache
   1.477 +  {
   1.478 +    AutoLock lock(counters_lock_);
   1.479 +    counters_[name] = counter_id;
   1.480 +  }
   1.481 +  return counter_id;
   1.482 +}
   1.483 +
   1.484 +int* StatsTable::GetLocation(int counter_id, int slot_id) const {
   1.485 +  if (!impl_)
   1.486 +    return NULL;
   1.487 +  if (slot_id > impl_->max_threads())
   1.488 +    return NULL;
   1.489 +
   1.490 +  int* row = impl_->row(counter_id);
   1.491 +  return &(row[slot_id-1]);
   1.492 +}
   1.493 +
   1.494 +const char* StatsTable::GetRowName(int index) const {
   1.495 +  if (!impl_)
   1.496 +    return NULL;
   1.497 +
   1.498 +  return impl_->counter_name(index);
   1.499 +}
   1.500 +
   1.501 +int StatsTable::GetRowValue(int index, int pid) const {
   1.502 +  if (!impl_)
   1.503 +    return 0;
   1.504 +
   1.505 +  int rv = 0;
   1.506 +  int* row = impl_->row(index);
   1.507 +  for (int slot_id = 0; slot_id < impl_->max_threads(); slot_id++) {
   1.508 +    if (pid == 0 || *impl_->thread_pid(slot_id) == pid)
   1.509 +      rv += row[slot_id];
   1.510 +  }
   1.511 +  return rv;
   1.512 +}
   1.513 +
   1.514 +int StatsTable::GetRowValue(int index) const {
   1.515 +  return GetRowValue(index, 0);
   1.516 +}
   1.517 +
   1.518 +int StatsTable::GetCounterValue(const std::string& name, int pid) {
   1.519 +  if (!impl_)
   1.520 +    return 0;
   1.521 +
   1.522 +  int row = FindCounter(name);
   1.523 +  if (!row)
   1.524 +    return 0;
   1.525 +  return GetRowValue(row, pid);
   1.526 +}
   1.527 +
   1.528 +int StatsTable::GetCounterValue(const std::string& name) {
   1.529 +  return GetCounterValue(name, 0);
   1.530 +}
   1.531 +
   1.532 +int StatsTable::GetMaxCounters() const {
   1.533 +  if (!impl_)
   1.534 +    return 0;
   1.535 +  return impl_->max_counters();
   1.536 +}
   1.537 +
   1.538 +int StatsTable::GetMaxThreads() const {
   1.539 +  if (!impl_)
   1.540 +    return 0;
   1.541 +  return impl_->max_threads();
   1.542 +}
   1.543 +
   1.544 +int* StatsTable::FindLocation(const char* name) {
   1.545 +  // Get the static StatsTable
   1.546 +  StatsTable *table = StatsTable::current();
   1.547 +  if (!table)
   1.548 +    return NULL;
   1.549 +
   1.550 +  // Get the slot for this thread.  Try to register
   1.551 +  // it if none exists.
   1.552 +  int slot = table->GetSlot();
   1.553 +  if (!slot && !(slot = table->RegisterThread("")))
   1.554 +      return NULL;
   1.555 +
   1.556 +  // Find the counter id for the counter.
   1.557 +  std::string str_name(name);
   1.558 +  int counter = table->FindCounter(str_name);
   1.559 +
   1.560 +  // Now we can find the location in the table.
   1.561 +  return table->GetLocation(counter, slot);
   1.562 +}

mercurial