| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 | // Copyright 2018 The Abseil Authors.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at////      https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.#include "absl/container/internal/hashtablez_sampler.h"#include <atomic>#include <limits>#include <random>#include "gmock/gmock.h"#include "gtest/gtest.h"#include "absl/base/attributes.h"#include "absl/container/internal/have_sse.h"#include "absl/synchronization/blocking_counter.h"#include "absl/synchronization/internal/thread_pool.h"#include "absl/synchronization/mutex.h"#include "absl/synchronization/notification.h"#include "absl/time/clock.h"#include "absl/time/time.h"#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2constexpr int kProbeLength = 16;#elseconstexpr int kProbeLength = 8;#endifnamespace absl {ABSL_NAMESPACE_BEGINnamespace container_internal {#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)class HashtablezInfoHandlePeer { public:  static bool IsSampled(const HashtablezInfoHandle& h) {    return h.info_ != nullptr;  }  static HashtablezInfo* GetInfo(HashtablezInfoHandle* h) { return h->info_; }};#elseclass HashtablezInfoHandlePeer { public:  static bool IsSampled(const HashtablezInfoHandle&) { return false; }  static HashtablezInfo* GetInfo(HashtablezInfoHandle*) { return nullptr; }};#endif  // defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)namespace {using ::absl::synchronization_internal::ThreadPool;using ::testing::IsEmpty;using ::testing::UnorderedElementsAre;std::vector<size_t> GetSizes(HashtablezSampler* s) {  std::vector<size_t> res;  s->Iterate([&](const HashtablezInfo& info) {    res.push_back(info.size.load(std::memory_order_acquire));  });  return res;}HashtablezInfo* Register(HashtablezSampler* s, size_t size) {  auto* info = s->Register();  assert(info != nullptr);  info->size.store(size);  return info;}TEST(HashtablezInfoTest, PrepareForSampling) {  absl::Time test_start = absl::Now();  HashtablezInfo info;  absl::MutexLock l(&info.init_mu);  info.PrepareForSampling();  EXPECT_EQ(info.capacity.load(), 0);  EXPECT_EQ(info.size.load(), 0);  EXPECT_EQ(info.num_erases.load(), 0);  EXPECT_EQ(info.num_rehashes.load(), 0);  EXPECT_EQ(info.max_probe_length.load(), 0);  EXPECT_EQ(info.total_probe_length.load(), 0);  EXPECT_EQ(info.hashes_bitwise_or.load(), 0);  EXPECT_EQ(info.hashes_bitwise_and.load(), ~size_t{});  EXPECT_EQ(info.hashes_bitwise_xor.load(), 0);  EXPECT_GE(info.create_time, test_start);  info.capacity.store(1, std::memory_order_relaxed);  info.size.store(1, std::memory_order_relaxed);  info.num_erases.store(1, std::memory_order_relaxed);  info.max_probe_length.store(1, std::memory_order_relaxed);  info.total_probe_length.store(1, std::memory_order_relaxed);  info.hashes_bitwise_or.store(1, std::memory_order_relaxed);  info.hashes_bitwise_and.store(1, std::memory_order_relaxed);  info.hashes_bitwise_xor.store(1, std::memory_order_relaxed);  info.create_time = test_start - absl::Hours(20);  info.PrepareForSampling();  EXPECT_EQ(info.capacity.load(), 0);  EXPECT_EQ(info.size.load(), 0);  EXPECT_EQ(info.num_erases.load(), 0);  EXPECT_EQ(info.num_rehashes.load(), 0);  EXPECT_EQ(info.max_probe_length.load(), 0);  EXPECT_EQ(info.total_probe_length.load(), 0);  EXPECT_EQ(info.hashes_bitwise_or.load(), 0);  EXPECT_EQ(info.hashes_bitwise_and.load(), ~size_t{});  EXPECT_EQ(info.hashes_bitwise_xor.load(), 0);  EXPECT_GE(info.create_time, test_start);}TEST(HashtablezInfoTest, RecordStorageChanged) {  HashtablezInfo info;  absl::MutexLock l(&info.init_mu);  info.PrepareForSampling();  RecordStorageChangedSlow(&info, 17, 47);  EXPECT_EQ(info.size.load(), 17);  EXPECT_EQ(info.capacity.load(), 47);  RecordStorageChangedSlow(&info, 20, 20);  EXPECT_EQ(info.size.load(), 20);  EXPECT_EQ(info.capacity.load(), 20);}TEST(HashtablezInfoTest, RecordInsert) {  HashtablezInfo info;  absl::MutexLock l(&info.init_mu);  info.PrepareForSampling();  EXPECT_EQ(info.max_probe_length.load(), 0);  RecordInsertSlow(&info, 0x0000FF00, 6 * kProbeLength);  EXPECT_EQ(info.max_probe_length.load(), 6);  EXPECT_EQ(info.hashes_bitwise_and.load(), 0x0000FF00);  EXPECT_EQ(info.hashes_bitwise_or.load(), 0x0000FF00);  EXPECT_EQ(info.hashes_bitwise_xor.load(), 0x0000FF00);  RecordInsertSlow(&info, 0x000FF000, 4 * kProbeLength);  EXPECT_EQ(info.max_probe_length.load(), 6);  EXPECT_EQ(info.hashes_bitwise_and.load(), 0x0000F000);  EXPECT_EQ(info.hashes_bitwise_or.load(), 0x000FFF00);  EXPECT_EQ(info.hashes_bitwise_xor.load(), 0x000F0F00);  RecordInsertSlow(&info, 0x00FF0000, 12 * kProbeLength);  EXPECT_EQ(info.max_probe_length.load(), 12);  EXPECT_EQ(info.hashes_bitwise_and.load(), 0x00000000);  EXPECT_EQ(info.hashes_bitwise_or.load(), 0x00FFFF00);  EXPECT_EQ(info.hashes_bitwise_xor.load(), 0x00F00F00);}TEST(HashtablezInfoTest, RecordErase) {  HashtablezInfo info;  absl::MutexLock l(&info.init_mu);  info.PrepareForSampling();  EXPECT_EQ(info.num_erases.load(), 0);  EXPECT_EQ(info.size.load(), 0);  RecordInsertSlow(&info, 0x0000FF00, 6 * kProbeLength);  EXPECT_EQ(info.size.load(), 1);  RecordEraseSlow(&info);  EXPECT_EQ(info.size.load(), 0);  EXPECT_EQ(info.num_erases.load(), 1);}TEST(HashtablezInfoTest, RecordRehash) {  HashtablezInfo info;  absl::MutexLock l(&info.init_mu);  info.PrepareForSampling();  RecordInsertSlow(&info, 0x1, 0);  RecordInsertSlow(&info, 0x2, kProbeLength);  RecordInsertSlow(&info, 0x4, kProbeLength);  RecordInsertSlow(&info, 0x8, 2 * kProbeLength);  EXPECT_EQ(info.size.load(), 4);  EXPECT_EQ(info.total_probe_length.load(), 4);  RecordEraseSlow(&info);  RecordEraseSlow(&info);  EXPECT_EQ(info.size.load(), 2);  EXPECT_EQ(info.total_probe_length.load(), 4);  EXPECT_EQ(info.num_erases.load(), 2);  RecordRehashSlow(&info, 3 * kProbeLength);  EXPECT_EQ(info.size.load(), 2);  EXPECT_EQ(info.total_probe_length.load(), 3);  EXPECT_EQ(info.num_erases.load(), 0);  EXPECT_EQ(info.num_rehashes.load(), 1);}#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)TEST(HashtablezSamplerTest, SmallSampleParameter) {  SetHashtablezEnabled(true);  SetHashtablezSampleParameter(100);  for (int i = 0; i < 1000; ++i) {    int64_t next_sample = 0;    HashtablezInfo* sample = SampleSlow(&next_sample);    EXPECT_GT(next_sample, 0);    EXPECT_NE(sample, nullptr);    UnsampleSlow(sample);  }}TEST(HashtablezSamplerTest, LargeSampleParameter) {  SetHashtablezEnabled(true);  SetHashtablezSampleParameter(std::numeric_limits<int32_t>::max());  for (int i = 0; i < 1000; ++i) {    int64_t next_sample = 0;    HashtablezInfo* sample = SampleSlow(&next_sample);    EXPECT_GT(next_sample, 0);    EXPECT_NE(sample, nullptr);    UnsampleSlow(sample);  }}TEST(HashtablezSamplerTest, Sample) {  SetHashtablezEnabled(true);  SetHashtablezSampleParameter(100);  int64_t num_sampled = 0;  int64_t total = 0;  double sample_rate = 0.0;  for (int i = 0; i < 1000000; ++i) {    HashtablezInfoHandle h = Sample();    ++total;    if (HashtablezInfoHandlePeer::IsSampled(h)) {      ++num_sampled;    }    sample_rate = static_cast<double>(num_sampled) / total;    if (0.005 < sample_rate && sample_rate < 0.015) break;  }  EXPECT_NEAR(sample_rate, 0.01, 0.005);}TEST(HashtablezSamplerTest, Handle) {  auto& sampler = HashtablezSampler::Global();  HashtablezInfoHandle h(sampler.Register());  auto* info = HashtablezInfoHandlePeer::GetInfo(&h);  info->hashes_bitwise_and.store(0x12345678, std::memory_order_relaxed);  bool found = false;  sampler.Iterate([&](const HashtablezInfo& h) {    if (&h == info) {      EXPECT_EQ(h.hashes_bitwise_and.load(), 0x12345678);      found = true;    }  });  EXPECT_TRUE(found);  h = HashtablezInfoHandle();  found = false;  sampler.Iterate([&](const HashtablezInfo& h) {    if (&h == info) {      // this will only happen if some other thread has resurrected the info      // the old handle was using.      if (h.hashes_bitwise_and.load() == 0x12345678) {        found = true;      }    }  });  EXPECT_FALSE(found);}#endifTEST(HashtablezSamplerTest, Registration) {  HashtablezSampler sampler;  auto* info1 = Register(&sampler, 1);  EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(1));  auto* info2 = Register(&sampler, 2);  EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(1, 2));  info1->size.store(3);  EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(3, 2));  sampler.Unregister(info1);  sampler.Unregister(info2);}TEST(HashtablezSamplerTest, Unregistration) {  HashtablezSampler sampler;  std::vector<HashtablezInfo*> infos;  for (size_t i = 0; i < 3; ++i) {    infos.push_back(Register(&sampler, i));  }  EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(0, 1, 2));  sampler.Unregister(infos[1]);  EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(0, 2));  infos.push_back(Register(&sampler, 3));  infos.push_back(Register(&sampler, 4));  EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(0, 2, 3, 4));  sampler.Unregister(infos[3]);  EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(0, 2, 4));  sampler.Unregister(infos[0]);  sampler.Unregister(infos[2]);  sampler.Unregister(infos[4]);  EXPECT_THAT(GetSizes(&sampler), IsEmpty());}TEST(HashtablezSamplerTest, MultiThreaded) {  HashtablezSampler sampler;  Notification stop;  ThreadPool pool(10);  for (int i = 0; i < 10; ++i) {    pool.Schedule([&sampler, &stop]() {      std::random_device rd;      std::mt19937 gen(rd());      std::vector<HashtablezInfo*> infoz;      while (!stop.HasBeenNotified()) {        if (infoz.empty()) {          infoz.push_back(sampler.Register());        }        switch (std::uniform_int_distribution<>(0, 2)(gen)) {          case 0: {            infoz.push_back(sampler.Register());            break;          }          case 1: {            size_t p =                std::uniform_int_distribution<>(0, infoz.size() - 1)(gen);            HashtablezInfo* info = infoz[p];            infoz[p] = infoz.back();            infoz.pop_back();            sampler.Unregister(info);            break;          }          case 2: {            absl::Duration oldest = absl::ZeroDuration();            sampler.Iterate([&](const HashtablezInfo& info) {              oldest = std::max(oldest, absl::Now() - info.create_time);            });            ASSERT_GE(oldest, absl::ZeroDuration());            break;          }        }      }    });  }  // The threads will hammer away.  Give it a little bit of time for tsan to  // spot errors.  absl::SleepFor(absl::Seconds(3));  stop.Notify();}TEST(HashtablezSamplerTest, Callback) {  HashtablezSampler sampler;  auto* info1 = Register(&sampler, 1);  auto* info2 = Register(&sampler, 2);  static const HashtablezInfo* expected;  auto callback = [](const HashtablezInfo& info) {    // We can't use `info` outside of this callback because the object will be    // disposed as soon as we return from here.    EXPECT_EQ(&info, expected);  };  // Set the callback.  EXPECT_EQ(sampler.SetDisposeCallback(callback), nullptr);  expected = info1;  sampler.Unregister(info1);  // Unset the callback.  EXPECT_EQ(callback, sampler.SetDisposeCallback(nullptr));  expected = nullptr;  // no more calls.  sampler.Unregister(info2);}}  // namespace}  // namespace container_internalABSL_NAMESPACE_END}  // namespace absl
 |