Export of internal Abseil changes

--
341670bce317dd6af8d3c066970230591a47e80c by Martijn Vels <mvels@google.com>:

Change GetStack() and GetParentStack() to return absl::Span

PiperOrigin-RevId: 368765721

--
6aaab9536d6957303c7aba100c3afaa6fb0ea2c8 by Martijn Vels <mvels@google.com>:

Remove locking from parent stack.

This change removes the need to lock all access to `parent_stack' by making the 'copy constructor' logic specify the 'copied from' CordzInfo (where available) to the TrackCord function, after which parent_stack is immutable.

PiperOrigin-RevId: 368760630

--
b19e2059cada35a8ede994833018edac94de6ddc by Martijn Vels <mvels@google.com>:

Add cordz instrumentation to Cord

PiperOrigin-RevId: 368746225

--
67b8bbf980f0f4e1db79aa32968e9a715a09b51a by Martijn Vels <mvels@google.com>:

Create ABSL_INTERNAL_CORDZ_ENABLED define controlling when Cordz code is enabled

There are specific builds and condtions under which we don't support cordz sampling, which is per this change represented by ABSL_INTERNAL_CORDZ_ENABLED being defined.

PiperOrigin-RevId: 368731603

--
8cbfe0e3169637a620f4b66ad2bc2ce340879cb0 by Martijn Vels <mvels@google.com>:

Add a `rep` property to CordzInfo to be managed by Cord logic.

This change adds a `rep` property to CordzInfo, which is intended to be used by collection logic.

Mini design:
Cord invokes TrackCord() providing the active 'root' cordrep of the newly sampled Cord, returning a CordzInfo with a weak (uncounted) reference to this root. Cord invokes `SetCordRep()` each time the root cordrep of the sampled Cord is updated while holding `mutex()`. Cord must also obtain `mutex()` _before_ removing a reference on the old root. i.e.: Cord must guarantee that the (weak) reference held in CordzInfo is at all times valid.

CordzInfo collection code can then safely obtain a (reference counted) rep pointer by adding a reference to `rep_` while holding `mutex()`. This requires only a very brief critical section inside CordzInfo logic, minimizing contention with concurrent Cord updates.

Cord code should typically obtain and hold `mutex()` for the entirety of each mutating Cord operation on a sampled cord. As Cord is thread compatible, it never competes on the lock with any other thread. The only possible concurrent access is from Cordz collection code, which should be a relatively rare event.

PiperOrigin-RevId: 368673758

--
1255120dce2bdd6b4205a34a0e555e0b74b6152f by Martijn Vels <mvels@google.com>:

Remove 'depth' from active recorded metrics.

Going forward we do not 'live' record depth (and size), but will observe these at collection time only.

PiperOrigin-RevId: 368636572

--
83e5146e35f221736b49e9f0a8805f8c159a51db by Martijn Vels <mvels@google.com>:

Make cordz targets visible in OSS

PiperOrigin-RevId: 368615010

--
dcb16a4f1239151f0a8c70a8cfeb29dabbd113b8 by Martijn Vels <mvels@google.com>:

Internal cleanup

PiperOrigin-RevId: 368514666
GitOrigin-RevId: 341670bce317dd6af8d3c066970230591a47e80c
Change-Id: I94cecfbbd441eb386f99fc5186c468a7a5538862
pull/946/head
Abseil Team 4 years ago committed by Dino Radaković
parent 46dfbfe31c
commit e20fe888fa
  1. 17
      CMake/AbseilDll.cmake
  2. 136
      absl/strings/BUILD.bazel
  3. 150
      absl/strings/CMakeLists.txt
  4. 38
      absl/strings/cord.cc
  5. 41
      absl/strings/cord.h
  6. 104
      absl/strings/internal/cordz_functions.cc
  7. 85
      absl/strings/internal/cordz_functions.h
  8. 131
      absl/strings/internal/cordz_functions_test.cc
  9. 127
      absl/strings/internal/cordz_handle.cc
  10. 97
      absl/strings/internal/cordz_handle.h
  11. 253
      absl/strings/internal/cordz_handle_test.cc
  12. 138
      absl/strings/internal/cordz_info.cc
  13. 168
      absl/strings/internal/cordz_info.h
  14. 237
      absl/strings/internal/cordz_info_test.cc
  15. 64
      absl/strings/internal/cordz_sample_token.cc
  16. 97
      absl/strings/internal/cordz_sample_token.h
  17. 209
      absl/strings/internal/cordz_sample_token_test.cc
  18. 55
      absl/strings/internal/cordz_statistics.h

@ -197,17 +197,26 @@ set(ABSL_INTERNAL_DLL_FILES
"strings/cord.h"
"strings/escaping.cc"
"strings/escaping.h"
"strings/internal/charconv_bigint.cc"
"strings/internal/charconv_bigint.h"
"strings/internal/charconv_parse.cc"
"strings/internal/charconv_parse.h"
"strings/internal/cord_internal.cc"
"strings/internal/cord_internal.h"
"strings/internal/cord_rep_flat.h"
"strings/internal/cord_rep_ring.cc"
"strings/internal/cord_rep_ring.h"
"strings/internal/cord_rep_ring_reader.h"
"strings/internal/cordz_functions.cc"
"strings/internal/cordz_functions.h"
"strings/internal/cordz_handle.cc"
"strings/internal/cordz_handle.h"
"strings/internal/cordz_info.cc"
"strings/internal/cordz_info.h"
"strings/internal/cordz_sample_token.cc"
"strings/internal/cordz_sample_token.h"
"strings/internal/cordz_statistics.h"
"strings/internal/cordz_update_tracker.h"
"strings/internal/charconv_bigint.cc"
"strings/internal/charconv_bigint.h"
"strings/internal/charconv_parse.cc"
"strings/internal/charconv_parse.h"
"strings/internal/stl_type_traits.h"
"strings/internal/string_constant.h"
"strings/match.cc"

@ -327,11 +327,15 @@ cc_library(
copts = ABSL_DEFAULT_COPTS,
deps = [
":cord_internal",
":cordz_functions",
":cordz_info",
":cordz_statistics",
":cordz_update_tracker",
":internal",
":str_format",
":strings",
"//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:endian",
"//absl/base:raw_logging_internal",
@ -343,6 +347,138 @@ cc_library(
],
)
cc_library(
name = "cordz_handle",
srcs = ["internal/cordz_handle.cc"],
hdrs = ["internal/cordz_handle.h"],
copts = ABSL_DEFAULT_COPTS,
deps = [
"//absl/base:config",
"//absl/base:raw_logging_internal",
"//absl/synchronization",
],
)
cc_library(
name = "cordz_info",
srcs = ["internal/cordz_info.cc"],
hdrs = ["internal/cordz_info.h"],
copts = ABSL_DEFAULT_COPTS,
deps = [
":cord_internal",
":cordz_handle",
":cordz_statistics",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/debugging:stacktrace",
"//absl/synchronization",
"//absl/types:span",
],
)
cc_library(
name = "cordz_sample_token",
srcs = ["internal/cordz_sample_token.cc"],
hdrs = ["internal/cordz_sample_token.h"],
copts = ABSL_DEFAULT_COPTS,
deps = [
":cordz_handle",
":cordz_info",
"//absl/base:config",
],
)
cc_library(
name = "cordz_functions",
srcs = ["internal/cordz_functions.cc"],
hdrs = ["internal/cordz_functions.h"],
copts = ABSL_DEFAULT_COPTS,
deps = [
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:exponential_biased",
"//absl/base:raw_logging_internal",
],
)
cc_library(
name = "cordz_statistics",
hdrs = ["internal/cordz_statistics.h"],
copts = ABSL_DEFAULT_COPTS,
deps = [
"//absl/base:config",
],
)
cc_test(
name = "cordz_functions_test",
srcs = [
"internal/cordz_functions_test.cc",
],
deps = [
":cord_test_helpers",
":cordz_functions",
"//absl/base:config",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "cordz_handle_test",
srcs = [
"internal/cordz_handle_test.cc",
],
deps = [
":cordz_handle",
"//absl/base:config",
"//absl/memory",
"//absl/random",
"//absl/random:distributions",
"//absl/synchronization",
"//absl/synchronization:thread_pool",
"//absl/time",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "cordz_info_test",
srcs = [
"internal/cordz_info_test.cc",
],
deps = [
":cord_internal",
":cordz_handle",
":cordz_info",
":strings",
"//absl/base:config",
"//absl/debugging:stacktrace",
"//absl/debugging:symbolize",
"//absl/types:span",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "cordz_sample_token_test",
srcs = [
"internal/cordz_sample_token_test.cc",
],
deps = [
":cord_internal",
":cordz_handle",
":cordz_info",
":cordz_sample_token",
"//absl/base:config",
"//absl/memory",
"//absl/random",
"//absl/synchronization",
"//absl/synchronization:thread_pool",
"//absl/time",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "cord_test_helpers",
testonly = 1,

@ -259,7 +259,7 @@ absl_cc_test(
absl_cc_test(
NAME
str_join_test
SRCS
ss SRCS
"str_join_test.cc"
COPTS
${ABSL_TEST_COPTS}
@ -603,6 +603,152 @@ absl_cc_test(
gmock_main
)
absl_cc_library(
NAME
cordz_functions
HDRS
"internal/cordz_functions.h"
SRCS
"internal/cordz_functions.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
absl::core_headers
absl::exponential_biased
absl::raw_logging_internal
)
absl_cc_test(
NAME
cordz_functions_test
SRCS
"internal/cordz_functions_test.cc"
DEPS
absl::config
absl::cord_test_helpers
absl::cordz_functions
gmock_main
)
absl_cc_library(
NAME
cordz_statistics
HDRS
"internal/cordz_statistics.h"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
absl::core_headers
absl::synchronization
)
absl_cc_library(
NAME
cordz_handle
HDRS
"internal/cordz_handle.h"
SRCS
"internal/cordz_handle.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
absl::synchronization
)
absl_cc_test(
NAME
cordz_handle_test
SRCS
"internal/cordz_handle_test.cc"
DEPS
absl::config
absl::cord_test_helpers
absl::cordz_handle
absl::memory
absl::random_random
absl::random_distributions
absl::synchronization
absl::time
gmock_main
)
absl_cc_library(
NAME
cordz_info
HDRS
"internal/cordz_info.h"
SRCS
"internal/cordz_info.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
absl::cord_internal
absl::cordz_handle
absl::cordz_statistics
absl::core_headers
absl::span
absl::stacktrace
absl::synchronization
)
absl_cc_test(
NAME
cordz_info_test
SRCS
"internal/cordz_info_test.cc"
DEPS
absl::config
absl::cord_internal
absl::cord_test_helpers
absl::cordz_handle
absl::cordz_info
absl::span
absl::stacktrace
absl::symbolize
gmock_main
)
absl_cc_library(
NAME
cordz_sample_token
HDRS
"internal/cordz_sample_token.h"
SRCS
"internal/cordz_sample_token.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
absl::cordz_handle
absl::cordz_info
)
absl_cc_test(
NAME
cordz_sample_token_test
SRCS
"internal/cordz_sample_token_test.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
absl::cord_internal
absl::cordz_handle
absl::cordz_info
absl::cordz_info
absl::cordz_sample_token
absl::memory
absl::random_random
absl::synchronization
absl::thread_pool
absl::time
gmock_main
)
absl_cc_library(
NAME
cord
@ -616,6 +762,8 @@ absl_cc_library(
absl::base
absl::config
absl::cord_internal
absl::cordz_functions
absl::cordz_info
absl::cordz_update_tracker
absl::core_headers
absl::endian

@ -38,6 +38,7 @@
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_flat.h"
#include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/internal/cordz_statistics.h"
#include "absl/strings/internal/resize_uninitialized.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
@ -426,6 +427,7 @@ void Cord::InlineRep::GetAppendRegion(char** region, size_t* size,
CordRep* root = force_tree(max_length);
if (PrepareAppendRegion(root, region, size, max_length)) {
UpdateCordzStatistics();
return;
}
@ -460,6 +462,7 @@ void Cord::InlineRep::GetAppendRegion(char** region, size_t* size) {
CordRep* root = force_tree(max_length);
if (PrepareAppendRegion(root, region, size, max_length)) {
UpdateCordzStatistics();
return;
}
@ -490,6 +493,27 @@ static bool RepMemoryUsageLeaf(const CordRep* rep, size_t* total_mem_usage) {
return false;
}
void Cord::InlineRep::StartProfiling() {
CordRep* tree = as_tree();
auto* cordz_info = absl::cord_internal::CordzInfo::TrackCord(tree);
set_cordz_info(cordz_info);
cordz_info->RecordMetrics(size());
}
void Cord::InlineRep::StartProfiling(const Cord::InlineRep& src) {
CordRep* tree = as_tree();
absl::cord_internal::CordzInfo* parent =
src.is_profiled() ? src.cordz_info() : nullptr;
auto* cordz_info = absl::cord_internal::CordzInfo::TrackCord(tree, parent);
set_cordz_info(cordz_info);
cordz_info->RecordMetrics(size());
}
void Cord::InlineRep::UpdateCordzStatisticsSlow() {
CordRep* tree = as_tree();
data_.cordz_info()->RecordMetrics(tree->length);
}
void Cord::InlineRep::AssignSlow(const Cord::InlineRep& src) {
UnrefTree();
@ -497,11 +521,17 @@ void Cord::InlineRep::AssignSlow(const Cord::InlineRep& src) {
if (is_tree()) {
CordRep::Ref(tree());
clear_cordz_info();
if (ABSL_PREDICT_FALSE(should_profile())) {
StartProfiling(src);
}
}
}
void Cord::InlineRep::UnrefTree() {
if (is_tree()) {
if (ABSL_PREDICT_FALSE(is_profiled())) {
absl::cord_internal::CordzInfo::UntrackCord(cordz_info());
}
CordRep::Unref(tree());
}
}
@ -552,6 +582,9 @@ template Cord::Cord(std::string&& src);
// The destruction code is separate so that the compiler can determine
// that it does not need to call the destructor on a moved-from Cord.
void Cord::DestroyCordSlow() {
if (ABSL_PREDICT_FALSE(contents_.is_profiled())) {
absl::cord_internal::CordzInfo::UntrackCord(contents_.cordz_info());
}
if (CordRep* tree = contents_.tree()) {
CordRep::Unref(VerifyTree(tree));
}
@ -567,6 +600,10 @@ void Cord::Clear() {
}
Cord& Cord::operator=(absl::string_view src) {
if (ABSL_PREDICT_FALSE(contents_.is_profiled())) {
absl::cord_internal::CordzInfo::UntrackCord(contents_.cordz_info());
contents_.clear_cordz_info();
}
const char* data = src.data();
size_t length = src.size();
@ -615,6 +652,7 @@ void Cord::InlineRep::AppendArray(const char* src_data, size_t src_size) {
char* region;
if (PrepareAppendRegion(root, &region, &appended, src_size)) {
memcpy(region, src_data, appended);
UpdateCordzStatistics();
}
} else {
// Try to fit in the inline buffer if possible.

@ -70,6 +70,7 @@
#include <string>
#include <type_traits>
#include "absl/base/config.h"
#include "absl/base/internal/endian.h"
#include "absl/base/internal/per_thread_tls.h"
#include "absl/base/macros.h"
@ -80,6 +81,9 @@
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/internal/cord_rep_ring_reader.h"
#include "absl/strings/internal/cordz_functions.h"
#include "absl/strings/internal/cordz_info.h"
#include "absl/strings/internal/cordz_statistics.h"
#include "absl/strings/internal/resize_uninitialized.h"
#include "absl/strings/internal/string_constant.h"
#include "absl/strings/string_view.h"
@ -772,6 +776,20 @@ class Cord {
// Resets the current cordz_info to null / empty.
void clear_cordz_info() { data_.clear_cordz_info(); }
// Starts profiling this cord.
void StartProfiling();
// Starts profiling this cord which has been copied from `src`.
void StartProfiling(const Cord::InlineRep& src);
// Returns true if a Cord should be profiled and false otherwise.
static bool should_profile();
// Updates the cordz statistics. info may be nullptr if the CordzInfo object
// is unknown.
void UpdateCordzStatistics();
void UpdateCordzStatisticsSlow();
private:
friend class Cord;
@ -943,6 +961,9 @@ inline Cord::InlineRep::InlineRep(const Cord::InlineRep& src)
if (is_tree()) {
data_.clear_cordz_info();
absl::cord_internal::CordRep::Ref(as_tree());
if (ABSL_PREDICT_FALSE(should_profile())) {
StartProfiling(src);
}
}
}
@ -1004,6 +1025,9 @@ inline size_t Cord::InlineRep::size() const {
inline void Cord::InlineRep::set_tree(absl::cord_internal::CordRep* rep) {
if (rep == nullptr) {
if (ABSL_PREDICT_FALSE(is_profiled())) {
absl::cord_internal::CordzInfo::UntrackCord(cordz_info());
}
ResetToEmpty();
} else {
if (data_.is_tree()) {
@ -1013,7 +1037,11 @@ inline void Cord::InlineRep::set_tree(absl::cord_internal::CordRep* rep) {
} else {
// `data_` contains inlined data: initialize data_ to tree value `rep`.
data_.make_tree(rep);
if (ABSL_PREDICT_FALSE(should_profile())) {
StartProfiling();
}
}
UpdateCordzStatistics();
}
}
@ -1024,9 +1052,13 @@ inline void Cord::InlineRep::replace_tree(absl::cord_internal::CordRep* rep) {
return;
}
data_.set_tree(rep);
UpdateCordzStatistics();
}
inline absl::cord_internal::CordRep* Cord::InlineRep::clear() {
if (ABSL_PREDICT_FALSE(is_profiled())) {
absl::cord_internal::CordzInfo::UntrackCord(cordz_info());
}
absl::cord_internal::CordRep* result = tree();
ResetToEmpty();
return result;
@ -1039,6 +1071,15 @@ inline void Cord::InlineRep::CopyToArray(char* dst) const {
cord_internal::SmallMemmove(dst, data_.as_chars(), n);
}
inline ABSL_ATTRIBUTE_ALWAYS_INLINE bool Cord::InlineRep::should_profile() {
return absl::cord_internal::cordz_should_profile();
}
inline void Cord::InlineRep::UpdateCordzStatistics() {
if (ABSL_PREDICT_TRUE(!is_profiled())) return;
UpdateCordzStatisticsSlow();
}
constexpr inline Cord::Cord() noexcept {}
template <typename T>

@ -0,0 +1,104 @@
// Copyright 2019 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/strings/internal/cordz_functions.h"
#include <atomic>
#include <cmath>
#include <limits>
#include <random>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/internal/exponential_biased.h"
#include "absl/base/internal/raw_logging.h"
// TODO(b/162942788): weak 'cordz_disabled' value.
// A strong version is in the 'cordz_disabled_hack_for_odr' library which can
// be linked in to disable cordz at compile time.
extern "C" {
bool absl_internal_cordz_disabled ABSL_ATTRIBUTE_WEAK = false;
}
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
namespace {
// The average interval until the next sample. A value of 0 disables profiling
// while a value of 1 will profile all Cords.
std::atomic<int> g_cordz_mean_interval(50000);
} // namespace
#ifdef ABSL_INTERNAL_CORDZ_ENABLED
ABSL_CONST_INIT thread_local int64_t cordz_next_sample = 0;
// kIntervalIfDisabled is the number of profile-eligible events need to occur
// before the code will confirm that cordz is still disabled.
constexpr int64_t kIntervalIfDisabled = 1 << 16;
ABSL_ATTRIBUTE_NOINLINE bool cordz_should_profile_slow() {
// TODO(b/162942788): check if profiling is disabled at compile time.
if (absl_internal_cordz_disabled) {
ABSL_RAW_LOG(WARNING, "Cordz info disabled at compile time");
// We are permanently disabled: set counter to highest possible value.
cordz_next_sample = std::numeric_limits<int64_t>::max();
return false;
}
thread_local absl::base_internal::ExponentialBiased
exponential_biased_generator;
int32_t mean_interval = get_cordz_mean_interval();
// Check if we disabled profiling. If so, set the next sample to a "large"
// number to minimize the overhead of the should_profile codepath.
if (mean_interval <= 0) {
cordz_next_sample = kIntervalIfDisabled;
return false;
}
// Check if we're always sampling.
if (mean_interval == 1) {
cordz_next_sample = 1;
return true;
}
if (cordz_next_sample <= 0) {
cordz_next_sample = exponential_biased_generator.GetStride(mean_interval);
return true;
}
--cordz_next_sample;
return false;
}
void cordz_set_next_sample_for_testing(int64_t next_sample) {
cordz_next_sample = next_sample;
}
#endif // ABSL_INTERNAL_CORDZ_ENABLED
int32_t get_cordz_mean_interval() {
return g_cordz_mean_interval.load(std::memory_order_acquire);
}
void set_cordz_mean_interval(int32_t mean_interval) {
g_cordz_mean_interval.store(mean_interval, std::memory_order_release);
}
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl

@ -0,0 +1,85 @@
// Copyright 2019 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.
#ifndef ABSL_STRINGS_CORDZ_FUNCTIONS_H_
#define ABSL_STRINGS_CORDZ_FUNCTIONS_H_
#include <stdint.h>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/optimization.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
// Returns the current sample rate. This represents the average interval
// between samples.
int32_t get_cordz_mean_interval();
// Sets the sample rate with the average interval between samples.
void set_cordz_mean_interval(int32_t mean_interval);
// Enable cordz unless any of the following applies:
// - no thread local support
// - MSVC build
// - Android build
// - Apple build
// - DLL build
// Hashtablez is turned off completely in opensource builds.
// MSVC's static atomics are dynamically initialized in debug mode, which breaks
// sampling.
#if defined(ABSL_HAVE_THREAD_LOCAL) && !defined(_MSC_VER) && \
!defined(ABSL_BUILD_DLL) && !defined(ABSL_CONSUME_DLL) && \
!defined(__ANDROID__) && !defined(__APPLE__)
#define ABSL_INTERNAL_CORDZ_ENABLED 1
#endif
#ifdef ABSL_INTERNAL_CORDZ_ENABLED
// cordz_next_sample is the number of events until the next sample event. If
// the value is 1 or less, the code will check on the next event if cordz is
// enabled, and if so, will sample the Cord. cordz is only enabled when we can
// use thread locals.
ABSL_CONST_INIT extern thread_local int64_t cordz_next_sample;
// Determines if the next sample should be profiled. If it is, the value pointed
// at by next_sample will be set with the interval until the next sample.
bool cordz_should_profile_slow();
// Returns true if the next cord should be sampled.
inline bool cordz_should_profile() {
if (ABSL_PREDICT_TRUE(cordz_next_sample > 1)) {
cordz_next_sample--;
return false;
}
return cordz_should_profile_slow();
}
// Sets the interval until the next sample (for testing only)
void cordz_set_next_sample_for_testing(int64_t next_sample);
#else // ABSL_INTERNAL_CORDZ_ENABLED
inline bool cordz_should_profile() { return false; }
inline void cordz_set_next_sample_for_testing(int64_t) {}
#endif // ABSL_INTERNAL_CORDZ_ENABLED
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_STRINGS_CORDZ_FUNCTIONS_H_

@ -0,0 +1,131 @@
// Copyright 2019 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/strings/internal/cordz_functions.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
namespace {
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Le;
TEST(CordzFunctionsTest, SampleRate) {
int32_t orig_sample_rate = get_cordz_mean_interval();
int32_t expected_sample_rate = 123;
set_cordz_mean_interval(expected_sample_rate);
EXPECT_THAT(get_cordz_mean_interval(), Eq(expected_sample_rate));
set_cordz_mean_interval(orig_sample_rate);
}
// Cordz is disabled when we don't have thread_local. All calls to
// should_profile will return false when cordz is diabled, so we might want to
// avoid those tests.
#ifdef ABSL_INTERNAL_CORDZ_ENABLED
TEST(CordzFunctionsTest, ShouldProfileDisable) {
int32_t orig_sample_rate = get_cordz_mean_interval();
set_cordz_mean_interval(0);
cordz_set_next_sample_for_testing(0);
EXPECT_FALSE(cordz_should_profile());
// 1 << 16 is from kIntervalIfDisabled in cordz_functions.cc.
EXPECT_THAT(cordz_next_sample, Eq(1 << 16));
set_cordz_mean_interval(orig_sample_rate);
}
TEST(CordzFunctionsTest, ShouldProfileAlways) {
int32_t orig_sample_rate = get_cordz_mean_interval();
set_cordz_mean_interval(1);
cordz_set_next_sample_for_testing(1);
EXPECT_TRUE(cordz_should_profile());
EXPECT_THAT(cordz_next_sample, Le(1));
set_cordz_mean_interval(orig_sample_rate);
}
TEST(CordzFunctionsTest, ShouldProfileRate) {
static constexpr int kDesiredMeanInterval = 1000;
static constexpr int kSamples = 10000;
int32_t orig_sample_rate = get_cordz_mean_interval();
set_cordz_mean_interval(kDesiredMeanInterval);
int64_t sum_of_intervals = 0;
for (int i = 0; i < kSamples; i++) {
// Setting next_sample to 0 will force cordz_should_profile to generate a
// new value for next_sample each iteration.
cordz_set_next_sample_for_testing(0);
cordz_should_profile();
sum_of_intervals += cordz_next_sample;
}
// The sum of independent exponential variables is an Erlang distribution,
// which is a gamma distribution where the shape parameter is equal to the
// number of summands. The distribution used for cordz_should_profile is
// actually floor(Exponential(1/mean)) which introduces bias. However, we can
// apply the squint-really-hard correction factor. That is, when mean is
// large, then if we squint really hard the shape of the distribution between
// N and N+1 looks like a uniform distribution. On average, each value for
// next_sample will be about 0.5 lower than we would expect from an
// exponential distribution. This squint-really-hard correction approach won't
// work when mean is smaller than about 10 but works fine when mean is 1000.
//
// We can use R to calculate a confidence interval. This
// shows how to generate a confidence interval with a false positive rate of
// one in a billion.
//
// $ R -q
// > mean = 1000
// > kSamples = 10000
// > errorRate = 1e-9
// > correction = -kSamples / 2
// > low = qgamma(errorRate/2, kSamples, 1/mean) + correction
// > high = qgamma(1 - errorRate/2, kSamples, 1/mean) + correction
// > low
// [1] 9396115
// > high
// [1] 10618100
EXPECT_THAT(sum_of_intervals, Ge(9396115));
EXPECT_THAT(sum_of_intervals, Le(10618100));
set_cordz_mean_interval(orig_sample_rate);
}
#else // ABSL_INTERNAL_CORDZ_ENABLED
TEST(CordzFunctionsTest, ShouldProfileDisabled) {
int32_t orig_sample_rate = get_cordz_mean_interval();
set_cordz_mean_interval(1);
cordz_set_next_sample_for_testing(0);
EXPECT_FALSE(cordz_should_profile());
set_cordz_mean_interval(orig_sample_rate);
}
#endif // ABSL_INTERNAL_CORDZ_ENABLED
} // namespace
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl

@ -0,0 +1,127 @@
// Copyright 2019 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/strings/internal/cordz_handle.h"
#include <atomic>
#include "absl/base/internal/raw_logging.h" // For ABSL_RAW_CHECK
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
ABSL_CONST_INIT absl::Mutex CordzHandle::mutex_(absl::kConstInit);
ABSL_CONST_INIT std::atomic<CordzHandle*> CordzHandle::dq_tail_{nullptr};
CordzHandle::CordzHandle(bool is_snapshot) : is_snapshot_(is_snapshot) {
if (is_snapshot) {
MutexLock lock(&mutex_);
CordzHandle* dq_tail = dq_tail_.load(std::memory_order_acquire);
if (dq_tail != nullptr) {
dq_prev_ = dq_tail;
dq_tail->dq_next_ = this;
}
dq_tail_.store(this, std::memory_order_release);
}
}
CordzHandle::~CordzHandle() {
if (is_snapshot_) {
std::vector<CordzHandle*> to_delete;
{
absl::MutexLock lock(&mutex_);
CordzHandle* next = dq_next_;
if (dq_prev_ == nullptr) {
// We were head of the queue, delete every CordzHandle until we reach
// either the end of the list, or a snapshot handle.
while (next && !next->is_snapshot_) {
to_delete.push_back(next);
next = next->dq_next_;
}
} else {
// Another CordzHandle existed before this one, don't delete anything.
dq_prev_->dq_next_ = next;
}
if (next) {
next->dq_prev_ = dq_prev_;
} else {
dq_tail_.store(dq_prev_, std::memory_order_release);
}
}
for (CordzHandle* handle : to_delete) {
delete handle;
}
}
}
void CordzHandle::Delete(CordzHandle* handle) {
if (handle) {
if (!handle->is_snapshot_ && !UnsafeDeleteQueueEmpty()) {
MutexLock lock(&mutex_);
CordzHandle* dq_tail = dq_tail_.load(std::memory_order_acquire);
if (dq_tail != nullptr) {
handle->dq_prev_ = dq_tail;
dq_tail->dq_next_ = handle;
dq_tail_.store(handle, std::memory_order_release);
return;
}
}
delete handle;
}
}
std::vector<const CordzHandle*> CordzHandle::DiagnosticsGetDeleteQueue() {
std::vector<const CordzHandle*> handles;
MutexLock lock(&mutex_);
CordzHandle* dq_tail = dq_tail_.load(std::memory_order_acquire);
for (const CordzHandle* p = dq_tail; p; p = p->dq_prev_) {
handles.push_back(p);
}
return handles;
}
bool CordzHandle::DiagnosticsHandleIsSafeToInspect(
const CordzHandle* handle) const {
if (!is_snapshot_) return false;
if (handle == nullptr) return true;
if (handle->is_snapshot_) return false;
bool snapshot_found = false;
MutexLock lock(&mutex_);
for (const CordzHandle* p = dq_tail_; p; p = p->dq_prev_) {
if (p == handle) return !snapshot_found;
if (p == this) snapshot_found = true;
}
ABSL_ASSERT(snapshot_found); // Assert that 'this' is in delete queue.
return true;
}
std::vector<const CordzHandle*>
CordzHandle::DiagnosticsGetSafeToInspectDeletedHandles() {
std::vector<const CordzHandle*> handles;
if (!is_snapshot()) {
return handles;
}
MutexLock lock(&mutex_);
for (const CordzHandle* p = dq_next_; p != nullptr; p = p->dq_next_) {
if (!p->is_snapshot()) {
handles.push_back(p);
}
}
return handles;
}
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl

@ -0,0 +1,97 @@
// Copyright 2019 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.
#ifndef ABSL_STRINGS_CORDZ_HANDLE_H_
#define ABSL_STRINGS_CORDZ_HANDLE_H_
#include <atomic>
#include <vector>
#include "absl/base/config.h"
#include "absl/synchronization/mutex.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
// This base class allows multiple types of object (CordzInfo and
// CordzSampleToken) to exist simultaneously on the delete queue (pointed to by
// global_dq_tail and traversed using dq_prev_ and dq_next_). The
// delete queue guarantees that once a profiler creates a CordzSampleToken and
// has gained visibility into a CordzInfo object, that CordzInfo object will not
// be deleted prematurely. This allows the profiler to inspect all CordzInfo
// objects that are alive without needing to hold a global lock.
class CordzHandle {
public:
CordzHandle() : CordzHandle(false) {}
bool is_snapshot() const { return is_snapshot_; }
// Deletes the provided instance, or puts it on the delete queue to be deleted
// once there are no more sample tokens (snapshot) instances potentially
// referencing the instance. `handle` may be null.
static void Delete(CordzHandle* handle);
// Returns the current entries in the delete queue in LIFO order.
static std::vector<const CordzHandle*> DiagnosticsGetDeleteQueue();
// Returns true if the provided handle is nullptr or guarded by this handle.
// Since the CordzSnapshot token is itself a CordzHandle, this method will
// allow tests to check if that token is keeping an arbitrary CordzHandle
// alive.
bool DiagnosticsHandleIsSafeToInspect(const CordzHandle* handle) const;
// Returns the current entries in the delete queue, in LIFO order, that are
// protected by this. CordzHandle objects are only placed on the delete queue
// after CordzHandle::Delete is called with them as an argument. Only
// CordzHandle objects that are not also CordzSnapshot objects will be
// included in the return vector. For each of the handles in the return
// vector, the earliest that their memory can be freed is when this
// CordzSnapshot object is deleted.
std::vector<const CordzHandle*> DiagnosticsGetSafeToInspectDeletedHandles();
protected:
explicit CordzHandle(bool is_snapshot);
virtual ~CordzHandle();
private:
// Returns true if the delete queue is empty. This method does not acquire the
// lock, but does a 'load acquire' observation on the delete queue tail. It
// is used inside Delete() to check for the presence of a delete queue without
// holding the lock. The assumption is that the caller is in the state of
// 'being deleted', and can not be newly discovered by a concurrent 'being
// constructed' snapshot instance. Practically, this means that any such
// discovery (`find`, 'first' or 'next', etc) must have proper 'happens before
// / after' semantics and atomic fences.
static bool UnsafeDeleteQueueEmpty() ABSL_NO_THREAD_SAFETY_ANALYSIS {
return dq_tail_.load(std::memory_order_acquire) == nullptr;
}
const bool is_snapshot_;
static absl::Mutex mutex_;
static std::atomic<CordzHandle*> dq_tail_ ABSL_GUARDED_BY(mutex_);
CordzHandle* dq_prev_ ABSL_GUARDED_BY(mutex_) = nullptr;
CordzHandle* dq_next_ ABSL_GUARDED_BY(mutex_) = nullptr;
};
class CordzSnapshot : public CordzHandle {
public:
CordzSnapshot() : CordzHandle(true) {}
};
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_STRINGS_CORDZ_HANDLE_H_

@ -0,0 +1,253 @@
// Copyright 2019 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/strings/internal/cordz_handle.h"
#include <random>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/memory/memory.h"
#include "absl/synchronization/internal/thread_pool.h"
#include "absl/synchronization/notification.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
namespace {
using ::testing::ElementsAre;
using ::testing::Gt;
using ::testing::IsEmpty;
using ::testing::SizeIs;
// Local less verbose helper
std::vector<const CordzHandle*> DeleteQueue() {
return CordzHandle::DiagnosticsGetDeleteQueue();
}
struct CordzHandleDeleteTracker : public CordzHandle {
bool* deleted;
explicit CordzHandleDeleteTracker(bool* deleted) : deleted(deleted) {}
~CordzHandleDeleteTracker() override { *deleted = true; }
};
TEST(CordzHandleTest, DeleteQueueIsEmpty) {
EXPECT_THAT(DeleteQueue(), SizeIs(0));
}
TEST(CordzHandleTest, CordzHandleCreateDelete) {
bool deleted = false;
auto* handle = new CordzHandleDeleteTracker(&deleted);
EXPECT_FALSE(handle->is_snapshot());
EXPECT_THAT(DeleteQueue(), SizeIs(0));
CordzHandle::Delete(handle);
EXPECT_THAT(DeleteQueue(), SizeIs(0));
EXPECT_TRUE(deleted);
}
TEST(CordzHandleTest, CordzSnapshotCreateDelete) {
auto* snapshot = new CordzSnapshot();
EXPECT_TRUE(snapshot->is_snapshot());
EXPECT_THAT(DeleteQueue(), ElementsAre(snapshot));
delete snapshot;
EXPECT_THAT(DeleteQueue(), SizeIs(0));
}
TEST(CordzHandleTest, CordzHandleCreateDeleteWithSnapshot) {
bool deleted = false;
auto* snapshot = new CordzSnapshot();
auto* handle = new CordzHandleDeleteTracker(&deleted);
CordzHandle::Delete(handle);
EXPECT_THAT(DeleteQueue(), ElementsAre(handle, snapshot));
EXPECT_FALSE(deleted);
delete snapshot;
EXPECT_THAT(DeleteQueue(), SizeIs(0));
EXPECT_TRUE(deleted);
}
TEST(CordzHandleTest, MultiSnapshot) {
bool deleted[3] = {false, false, false};
CordzSnapshot* snapshot[3];
CordzHandleDeleteTracker* handle[3];
for (int i = 0; i < 3; ++i) {
snapshot[i] = new CordzSnapshot();
handle[i] = new CordzHandleDeleteTracker(&deleted[i]);
CordzHandle::Delete(handle[i]);
}
EXPECT_THAT(DeleteQueue(), ElementsAre(handle[2], snapshot[2], handle[1],
snapshot[1], handle[0], snapshot[0]));
EXPECT_THAT(deleted, ElementsAre(false, false, false));
delete snapshot[1];
EXPECT_THAT(DeleteQueue(), ElementsAre(handle[2], snapshot[2], handle[1],
handle[0], snapshot[0]));
EXPECT_THAT(deleted, ElementsAre(false, false, false));
delete snapshot[0];
EXPECT_THAT(DeleteQueue(), ElementsAre(handle[2], snapshot[2]));
EXPECT_THAT(deleted, ElementsAre(true, true, false));
delete snapshot[2];
EXPECT_THAT(DeleteQueue(), SizeIs(0));
EXPECT_THAT(deleted, ElementsAre(true, true, deleted));
}
TEST(CordzHandleTest, DiagnosticsHandleIsSafeToInspect) {
CordzSnapshot snapshot1;
EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(nullptr));
auto* handle1 = new CordzHandle();
EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(handle1));
CordzHandle::Delete(handle1);
EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(handle1));
CordzSnapshot snapshot2;
auto* handle2 = new CordzHandle();
EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(handle1));
EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(handle2));
EXPECT_FALSE(snapshot2.DiagnosticsHandleIsSafeToInspect(handle1));
EXPECT_TRUE(snapshot2.DiagnosticsHandleIsSafeToInspect(handle2));
CordzHandle::Delete(handle2);
EXPECT_TRUE(snapshot1.DiagnosticsHandleIsSafeToInspect(handle1));
}
TEST(CordzHandleTest, DiagnosticsGetSafeToInspectDeletedHandles) {
EXPECT_THAT(DeleteQueue(), IsEmpty());
auto* handle = new CordzHandle();
auto* snapshot1 = new CordzSnapshot();
// snapshot1 should be able to see handle.
EXPECT_THAT(DeleteQueue(), ElementsAre(snapshot1));
EXPECT_TRUE(snapshot1->DiagnosticsHandleIsSafeToInspect(handle));
EXPECT_THAT(snapshot1->DiagnosticsGetSafeToInspectDeletedHandles(),
IsEmpty());
// This handle will be safe to inspect as long as snapshot1 is alive. However,
// since only snapshot1 can prove that it's alive, it will be hidden from
// snapshot2.
CordzHandle::Delete(handle);
// This snapshot shouldn't be able to see handle because handle was already
// sent to Delete.
auto* snapshot2 = new CordzSnapshot();
// DeleteQueue elements are LIFO order.
EXPECT_THAT(DeleteQueue(), ElementsAre(snapshot2, handle, snapshot1));
EXPECT_TRUE(snapshot1->DiagnosticsHandleIsSafeToInspect(handle));
EXPECT_FALSE(snapshot2->DiagnosticsHandleIsSafeToInspect(handle));
EXPECT_THAT(snapshot1->DiagnosticsGetSafeToInspectDeletedHandles(),
ElementsAre(handle));
EXPECT_THAT(snapshot2->DiagnosticsGetSafeToInspectDeletedHandles(),
IsEmpty());
CordzHandle::Delete(snapshot1);
EXPECT_THAT(DeleteQueue(), ElementsAre(snapshot2));
CordzHandle::Delete(snapshot2);
EXPECT_THAT(DeleteQueue(), IsEmpty());
}
// Create and delete CordzHandle and CordzSnapshot objects in multiple threads
// so that tsan has some time to chew on it and look for memory problems.
TEST(CordzHandleTest, MultiThreaded) {
Notification stop;
static constexpr int kNumThreads = 4;
// Keep the number of handles relatively small so that the test will naturally
// transition to an empty delete queue during the test. If there are, say, 100
// handles, that will virtually never happen. With 10 handles and around 50k
// iterations in each of 4 threads, the delete queue appears to become empty
// around 200 times.
static constexpr int kNumHandles = 10;
// Each thread is going to pick a random index and atomically swap its
// CordzHandle with one in handles. This way, each thread can avoid
// manipulating a CordzHandle that might be operated upon in another thread.
std::vector<std::atomic<CordzHandle*>> handles(kNumHandles);
absl::synchronization_internal::ThreadPool pool(kNumThreads);
for (int i = 0; i < kNumThreads; ++i) {
pool.Schedule([&stop, &handles]() {
std::minstd_rand gen;
std::uniform_int_distribution<int> dist_type(0, 2);
std::uniform_int_distribution<int> dist_handle(0, kNumHandles - 1);
size_t max_safe_to_inspect = 0;
while (!stop.HasBeenNotified()) {
CordzHandle* handle;
switch (dist_type(gen)) {
case 0:
handle = new CordzHandle();
break;
case 1:
handle = new CordzSnapshot();
break;
default:
handle = nullptr;
break;
}
CordzHandle* old_handle = handles[dist_handle(gen)].exchange(handle);
if (old_handle != nullptr) {
std::vector<const CordzHandle*> safe_to_inspect =
old_handle->DiagnosticsGetSafeToInspectDeletedHandles();
for (const CordzHandle* handle : safe_to_inspect) {
// We're in a tight loop, so don't generate too many error messages.
ASSERT_FALSE(handle->is_snapshot());
}
if (safe_to_inspect.size() > max_safe_to_inspect) {
max_safe_to_inspect = safe_to_inspect.size();
}
}
CordzHandle::Delete(old_handle);
}
// Confirm that the test did *something*. This check will be satisfied as
// long as this thread has delete a CordzSnapshot object and a
// non-snapshot CordzHandle was deleted after the CordzSnapshot was
// created. This max_safe_to_inspect count will often reach around 30
// (assuming 4 threads and 10 slots for live handles). Don't use a strict
// bound to avoid making this test flaky.
EXPECT_THAT(max_safe_to_inspect, Gt(0));
// Have each thread attempt to clean up everything. Some thread will be
// the last to reach this cleanup code, and it will be guaranteed to clean
// up everything because nothing remains to create new handles.
for (size_t i = 0; i < handles.size(); i++) {
CordzHandle* handle = handles[i].exchange(nullptr);
CordzHandle::Delete(handle);
}
});
}
// The threads will hammer away. Give it a little bit of time for tsan to
// spot errors.
absl::SleepFor(absl::Seconds(3));
stop.Notify();
}
} // namespace
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl

@ -0,0 +1,138 @@
// Copyright 2019 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/strings/internal/cordz_info.h"
#include "absl/base/config.h"
#include "absl/debugging/stacktrace.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cordz_handle.h"
#include "absl/strings/internal/cordz_statistics.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
constexpr int CordzInfo::kMaxStackDepth;
ABSL_CONST_INIT std::atomic<CordzInfo*> CordzInfo::ci_head_{nullptr};
ABSL_CONST_INIT absl::Mutex CordzInfo::ci_mutex_(absl::kConstInit);
CordzInfo* CordzInfo::Head(const CordzSnapshot& snapshot) {
ABSL_ASSERT(snapshot.is_snapshot());
ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(ci_head_unsafe()));
return ci_head_unsafe();
}
CordzInfo* CordzInfo::Next(const CordzSnapshot& snapshot) const {
ABSL_ASSERT(snapshot.is_snapshot());
ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(this));
ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(ci_next_unsafe()));
return ci_next_unsafe();
}
CordzInfo* CordzInfo::TrackCord(CordRep* rep, const CordzInfo* src) {
CordzInfo* ci = new CordzInfo(rep);
if (src) {
ci->parent_stack_depth_ = src->stack_depth_;
memcpy(ci->parent_stack_, src->stack_, sizeof(void*) * src->stack_depth_);
}
ci->Track();
return ci;
}
CordzInfo* CordzInfo::TrackCord(CordRep* rep) {
return TrackCord(rep, nullptr);
}
void CordzInfo::UntrackCord(CordzInfo* cordz_info) {
assert(cordz_info);
if (cordz_info) {
cordz_info->Untrack();
CordzHandle::Delete(cordz_info);
}
}
CordzInfo::CordzInfo(CordRep* rep)
: rep_(rep),
stack_depth_(absl::GetStackTrace(stack_, /*max_depth=*/kMaxStackDepth,
/*skip_count=*/1)),
parent_stack_depth_(0),
create_time_(absl::Now()) {}
CordzInfo::~CordzInfo() {
// `rep_` is potentially kept alive if CordzInfo is included
// in a collection snapshot (which should be rare).
if (ABSL_PREDICT_FALSE(rep_)) {
CordRep::Unref(rep_);
}
}
void CordzInfo::Track() {
absl::MutexLock l(&ci_mutex_);
CordzInfo* const head = ci_head_.load(std::memory_order_acquire);
if (head != nullptr) {
head->ci_prev_.store(this, std::memory_order_release);
}
ci_next_.store(head, std::memory_order_release);
ci_head_.store(this, std::memory_order_release);
}
void CordzInfo::Untrack() {
{
// TODO(b/117940323): change this to assuming ownership instead once all
// Cord logic is properly keeping `rep_` in sync with the Cord root rep.
absl::MutexLock lock(&mutex());
rep_ = nullptr;
}
absl::MutexLock l(&ci_mutex_);
CordzInfo* const head = ci_head_.load(std::memory_order_acquire);
CordzInfo* const next = ci_next_.load(std::memory_order_acquire);
CordzInfo* const prev = ci_prev_.load(std::memory_order_acquire);
if (next) {
ABSL_ASSERT(next->ci_prev_.load(std::memory_order_acquire) == this);
next->ci_prev_.store(prev, std::memory_order_release);
}
if (prev) {
ABSL_ASSERT(head != this);
ABSL_ASSERT(prev->ci_next_.load(std::memory_order_acquire) == this);
prev->ci_next_.store(next, std::memory_order_release);
} else {
ABSL_ASSERT(head == this);
ci_head_.store(next, std::memory_order_release);
}
}
void CordzInfo::SetCordRep(CordRep* rep) {
mutex().AssertHeld();
rep_ = rep;
}
absl::Span<void* const> CordzInfo::GetStack() const {
return absl::MakeConstSpan(stack_, stack_depth_);
}
absl::Span<void* const> CordzInfo::GetParentStack() const {
return absl::MakeConstSpan(parent_stack_, parent_stack_depth_);
}
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl

@ -0,0 +1,168 @@
// Copyright 2019 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.
#ifndef ABSL_STRINGS_CORDZ_INFO_H_
#define ABSL_STRINGS_CORDZ_INFO_H_
#include <atomic>
#include <cstdint>
#include <functional>
#include "absl/base/config.h"
#include "absl/base/thread_annotations.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cordz_handle.h"
#include "absl/strings/internal/cordz_statistics.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
// CordzInfo tracks a profiled Cord. Each of these objects can be in two places.
// If a Cord is alive, the CordzInfo will be in the global_cordz_infos map, and
// can also be retrieved via the linked list starting with
// global_cordz_infos_head and continued via the cordz_info_next() method. When
// a Cord has reached the end of its lifespan, the CordzInfo object will be
// migrated out of the global_cordz_infos list and the global_cordz_infos_map,
// and will either be deleted or appended to the global_delete_queue. If it is
// placed on the global_delete_queue, the CordzInfo object will be cleaned in
// the destructor of a CordzSampleToken object.
class CordzInfo : public CordzHandle {
public:
// All profiled Cords should be accompanied by a call to TrackCord.
// TrackCord creates a CordzInfo instance which tracks important metrics of
// the sampled cord. CordzInfo instances are placed in a global list which is
// used to discover and snapshot all actively tracked cords.
// Callers are responsible for calling UntrackCord() before the tracked Cord
// instance is deleted, or to stop tracking the sampled Cord.
static CordzInfo* TrackCord(CordRep* rep);
// Stops tracking changes for a sampled cord, and deletes the provided info.
// This function must be called before the sampled cord instance is deleted,
// and before the root cordrep of the sampled cord is unreffed.
// This function may extend the lifetime of the cordrep in cases where the
// CordInfo instance is being held by a concurrent collection thread.
static void UntrackCord(CordzInfo* cordz_info);
// Identical to TrackCord(), except that this function fills the
// 'parent_stack' property of the returned CordzInfo instance from the
// provided `src` instance if `src` is not null.
// This function should be used for sampling 'copy constructed' cords.
static CordzInfo* TrackCord(CordRep* rep, const CordzInfo* src);
CordzInfo() = delete;
CordzInfo(const CordzInfo&) = delete;
CordzInfo& operator=(const CordzInfo&) = delete;
// Retrieves the oldest existing CordzInfo.
static CordzInfo* Head(const CordzSnapshot& snapshot);
// Retrieves the next oldest existing CordzInfo older than 'this' instance.
CordzInfo* Next(const CordzSnapshot& snapshot) const;
// Returns a reference to the mutex guarding the `rep` property of this
// instance. CordzInfo instances hold a weak reference to the rep pointer of
// sampled cords, and rely on Cord logic to update the rep pointer when the
// underlying root tree or ring of the cord changes.
absl::Mutex& mutex() const { return mutex_; }
// Updates the `rep' property of this instance. This methods is invoked by
// Cord logic each time the root node of a sampled Cord changes, and before
// the old root reference count is deleted. This guarantees that collection
// code can always safely take a reference on the tracked cord.
// Requires `mutex()` to be held.
// TODO(b/117940323): annotate with ABSL_EXCLUSIVE_LOCKS_REQUIRED once all
// Cord code is in a state where this can be proven true by the compiler.
void SetCordRep(CordRep* rep);
// Returns the current value of `rep_` for testing purposes only.
CordRep* GetCordRepForTesting() const ABSL_NO_THREAD_SAFETY_ANALYSIS {
return rep_;
}
// Returns the stack trace for where the cord was first sampled. Cords are
// potentially sampled when they promote from an inlined cord to a tree or
// ring representation, which is not necessarily the location where the cord
// was first created. Some cords are created as inlined cords, and only as
// data is added do they become a non-inlined cord. However, typically the
// location represents reasonably well where the cord is 'created'.
absl::Span<void* const> GetStack() const;
// Returns the stack trace for a sampled cord's 'parent stack trace'. This
// value may be set if the cord is sampled (promoted) after being created
// from, or being assigned the value of an existing (sampled) cord.
absl::Span<void* const> GetParentStack() const;
// Retrieve the CordzStatistics associated with this Cord. The statistics are
// only updated when a Cord goes through a mutation, such as an Append or
// RemovePrefix. The refcounts can change due to external events, so the
// reported refcount stats might be incorrect.
CordzStatistics GetCordzStatistics() const {
CordzStatistics stats;
stats.size = size_.load(std::memory_order_relaxed);
return stats;
}
// Records size metric for this CordzInfo instance.
void RecordMetrics(int64_t size) {
size_.store(size, std::memory_order_relaxed);
}
private:
static constexpr int kMaxStackDepth = 64;
explicit CordzInfo(CordRep* tree);
~CordzInfo() override;
void Track();
void Untrack();
// 'Unsafe' head/next/prev accessors not requiring the lock being held.
// These are used exclusively for iterations (Head / Next) where we enforce
// a token being held, so reading an 'old' / deleted pointer is fine.
static CordzInfo* ci_head_unsafe() ABSL_NO_THREAD_SAFETY_ANALYSIS {
return ci_head_.load(std::memory_order_acquire);
}
CordzInfo* ci_next_unsafe() const ABSL_NO_THREAD_SAFETY_ANALYSIS {
return ci_next_.load(std::memory_order_acquire);
}
CordzInfo* ci_prev_unsafe() const ABSL_NO_THREAD_SAFETY_ANALYSIS {
return ci_prev_.load(std::memory_order_acquire);
}
static absl::Mutex ci_mutex_;
static std::atomic<CordzInfo*> ci_head_ ABSL_GUARDED_BY(ci_mutex_);
std::atomic<CordzInfo*> ci_prev_ ABSL_GUARDED_BY(ci_mutex_){nullptr};
std::atomic<CordzInfo*> ci_next_ ABSL_GUARDED_BY(ci_mutex_){nullptr};
mutable absl::Mutex mutex_;
CordRep* rep_ ABSL_GUARDED_BY(mutex());
void* stack_[kMaxStackDepth];
void* parent_stack_[kMaxStackDepth];
const int stack_depth_;
int parent_stack_depth_;
const absl::Time create_time_;
// Last recorded size for the cord.
std::atomic<int64_t> size_{0};
};
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_STRINGS_CORDZ_INFO_H_

@ -0,0 +1,237 @@
// Copyright 2019 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/strings/internal/cordz_info.h"
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/debugging/stacktrace.h"
#include "absl/debugging/symbolize.h"
#include "absl/strings/internal/cord_rep_flat.h"
#include "absl/strings/internal/cordz_handle.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
namespace {
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::Ne;
struct TestCordRep {
CordRepFlat* rep;
TestCordRep() {
rep = CordRepFlat::New(100);
rep->length = 100;
memset(rep->Data(), 1, 100);
}
~TestCordRep() { CordRepFlat::Delete(rep); }
};
// Local less verbose helper
std::vector<const CordzHandle*> DeleteQueue() {
return CordzHandle::DiagnosticsGetDeleteQueue();
}
std::string FormatStack(absl::Span<void* const> raw_stack) {
static constexpr size_t buf_size = 1 << 14;
std::unique_ptr<char[]> buf(new char[buf_size]);
std::string output;
for (void* stackp : raw_stack) {
if (absl::Symbolize(stackp, buf.get(), buf_size)) {
absl::StrAppend(&output, " ", buf.get(), "\n");
}
}
return output;
}
TEST(CordzInfoTest, TrackCord) {
TestCordRep rep;
CordzInfo* info = CordzInfo::TrackCord(rep.rep);
ASSERT_THAT(info, Ne(nullptr));
EXPECT_FALSE(info->is_snapshot());
EXPECT_THAT(CordzInfo::Head(CordzSnapshot()), Eq(info));
EXPECT_THAT(info->GetCordRepForTesting(), Eq(rep.rep));
CordzInfo::UntrackCord(info);
}
TEST(CordzInfoTest, UntrackCord) {
TestCordRep rep;
CordzInfo* info = CordzInfo::TrackCord(rep.rep);
CordzSnapshot snapshot;
CordzInfo::UntrackCord(info);
EXPECT_THAT(CordzInfo::Head(CordzSnapshot()), Eq(nullptr));
EXPECT_THAT(info->GetCordRepForTesting(), Eq(nullptr));
EXPECT_THAT(DeleteQueue(), ElementsAre(info, &snapshot));
}
TEST(CordzInfoTest, SetCordRep) {
TestCordRep rep;
CordzInfo* info = CordzInfo::TrackCord(rep.rep);
TestCordRep rep2;
{
absl::MutexLock lock(&info->mutex());
info->SetCordRep(rep2.rep);
}
EXPECT_THAT(info->GetCordRepForTesting(), Eq(rep2.rep));
CordzInfo::UntrackCord(info);
}
#if GTEST_HAS_DEATH_TEST
TEST(CordzInfoTest, SetCordRepRequiresMutex) {
TestCordRep rep;
CordzInfo* info = CordzInfo::TrackCord(rep.rep);
TestCordRep rep2;
EXPECT_DEATH(info->SetCordRep(rep2.rep), ".*");
CordzInfo::UntrackCord(info);
}
#endif // GTEST_HAS_DEATH_TEST
TEST(CordzInfoTest, TrackUntrackHeadFirstV2) {
TestCordRep rep;
CordzSnapshot snapshot;
EXPECT_THAT(CordzInfo::Head(snapshot), Eq(nullptr));
CordzInfo* info1 = CordzInfo::TrackCord(rep.rep);
ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info1));
EXPECT_THAT(info1->Next(snapshot), Eq(nullptr));
CordzInfo* info2 = CordzInfo::TrackCord(rep.rep);
ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info2));
EXPECT_THAT(info2->Next(snapshot), Eq(info1));
EXPECT_THAT(info1->Next(snapshot), Eq(nullptr));
CordzInfo::UntrackCord(info2);
ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info1));
EXPECT_THAT(info1->Next(snapshot), Eq(nullptr));
CordzInfo::UntrackCord(info1);
ASSERT_THAT(CordzInfo::Head(snapshot), Eq(nullptr));
}
TEST(CordzInfoTest, TrackUntrackTailFirstV2) {
TestCordRep rep;
CordzSnapshot snapshot;
EXPECT_THAT(CordzInfo::Head(snapshot), Eq(nullptr));
CordzInfo* info1 = CordzInfo::TrackCord(rep.rep);
ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info1));
EXPECT_THAT(info1->Next(snapshot), Eq(nullptr));
CordzInfo* info2 = CordzInfo::TrackCord(rep.rep);
ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info2));
EXPECT_THAT(info2->Next(snapshot), Eq(info1));
EXPECT_THAT(info1->Next(snapshot), Eq(nullptr));
CordzInfo::UntrackCord(info1);
ASSERT_THAT(CordzInfo::Head(snapshot), Eq(info2));
EXPECT_THAT(info2->Next(snapshot), Eq(nullptr));
CordzInfo::UntrackCord(info2);
ASSERT_THAT(CordzInfo::Head(snapshot), Eq(nullptr));
}
TEST(CordzInfoTest, StackV2) {
TestCordRep rep;
// kMaxStackDepth is intentionally less than 64 (which is the max depth that
// Cordz will record) because if the actual stack depth is over 64
// (which it is on Apple platforms) then the expected_stack will end up
// catching a few frames at the end that the actual_stack didn't get and
// it will no longer be subset. At the time of this writing 58 is the max
// that will allow this test to pass (with a minimum os version of iOS 9), so
// rounded down to 50 to hopefully not run into this in the future if Apple
// makes small modifications to its testing stack. 50 is sufficient to prove
// that we got a decent stack.
static constexpr int kMaxStackDepth = 50;
CordzInfo* info = CordzInfo::TrackCord(rep.rep);
std::vector<void*> local_stack;
local_stack.resize(kMaxStackDepth);
// In some environments we don't get stack traces. For example in Android
// absl::GetStackTrace will return 0 indicating it didn't find any stack. The
// resultant formatted stack will be "", but that still equals the stack
// recorded in CordzInfo, which is also empty. The skip_count is 1 so that the
// line number of the current stack isn't included in the HasSubstr check.
local_stack.resize(absl::GetStackTrace(local_stack.data(), kMaxStackDepth,
/*skip_count=*/1));
std::string got_stack = FormatStack(info->GetStack());
std::string expected_stack = FormatStack(local_stack);
// If TrackCord is inlined, got_stack should match expected_stack. If it isn't
// inlined, got_stack should include an additional frame not present in
// expected_stack. Either way, expected_stack should be a substring of
// got_stack.
EXPECT_THAT(got_stack, HasSubstr(expected_stack));
CordzInfo::UntrackCord(info);
}
// Local helper functions to get different stacks for child and parent.
CordzInfo* TrackChildCord(CordRep* rep, const CordzInfo* parent) {
return CordzInfo::TrackCord(rep, parent);
}
CordzInfo* TrackParentCord(CordRep* rep) {
return CordzInfo::TrackCord(rep);
}
TEST(CordzInfoTest, ParentStackV2) {
TestCordRep rep;
CordzInfo* info_parent = TrackParentCord(rep.rep);
CordzInfo* info_child = TrackChildCord(rep.rep, info_parent);
std::string stack = FormatStack(info_parent->GetStack());
std::string parent_stack = FormatStack(info_child->GetParentStack());
EXPECT_THAT(stack, Eq(parent_stack));
CordzInfo::UntrackCord(info_parent);
CordzInfo::UntrackCord(info_child);
}
TEST(CordzInfoTest, ParentStackEmpty) {
TestCordRep rep;
CordzInfo* info = TrackChildCord(rep.rep, nullptr);
EXPECT_TRUE(info->GetParentStack().empty());
CordzInfo::UntrackCord(info);
}
TEST(CordzInfoTest, CordzStatisticsV2) {
TestCordRep rep;
CordzInfo* info = TrackParentCord(rep.rep);
CordzStatistics expected;
expected.size = 100;
info->RecordMetrics(expected.size);
CordzStatistics actual = info->GetCordzStatistics();
EXPECT_EQ(actual.size, expected.size);
CordzInfo::UntrackCord(info);
}
} // namespace
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl

@ -0,0 +1,64 @@
// Copyright 2019 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/strings/internal/cordz_sample_token.h"
#include "absl/base/config.h"
#include "absl/strings/internal/cordz_handle.h"
#include "absl/strings/internal/cordz_info.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
CordzSampleToken::Iterator& CordzSampleToken::Iterator::operator++() {
if (current_) {
current_ = current_->Next(*token_);
}
return *this;
}
CordzSampleToken::Iterator CordzSampleToken::Iterator::operator++(int) {
Iterator it(*this);
operator++();
return it;
}
bool operator==(const CordzSampleToken::Iterator& lhs,
const CordzSampleToken::Iterator& rhs) {
return lhs.current_ == rhs.current_ &&
(lhs.current_ == nullptr || lhs.token_ == rhs.token_);
}
bool operator!=(const CordzSampleToken::Iterator& lhs,
const CordzSampleToken::Iterator& rhs) {
return !(lhs == rhs);
}
CordzSampleToken::Iterator::reference CordzSampleToken::Iterator::operator*()
const {
return *current_;
}
CordzSampleToken::Iterator::pointer CordzSampleToken::Iterator::operator->()
const {
return current_;
}
CordzSampleToken::Iterator::Iterator(const CordzSampleToken* token)
: token_(token), current_(CordzInfo::Head(*token)) {}
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl

@ -0,0 +1,97 @@
// Copyright 2019 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/base/config.h"
#include "absl/strings/internal/cordz_handle.h"
#include "absl/strings/internal/cordz_info.h"
#ifndef ABSL_STRINGS_CORDZ_SAMPLE_TOKEN_H_
#define ABSL_STRINGS_CORDZ_SAMPLE_TOKEN_H_
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
// The existence of a CordzSampleToken guarantees that a reader can traverse the
// global_cordz_infos_head linked-list without needing to hold a mutex. When a
// CordzSampleToken exists, all CordzInfo objects that would be destroyed are
// instead appended to a deletion queue. When the CordzSampleToken is destroyed,
// it will also clean up any of these CordzInfo objects.
//
// E.g., ST are CordzSampleToken objects and CH are CordzHandle objects.
// ST1 <- CH1 <- CH2 <- ST2 <- CH3 <- global_delete_queue_tail
//
// This list tracks that CH1 and CH2 were created after ST1, so the thread
// holding ST1 might have a referece to CH1, CH2, ST2, and CH3. However, ST2 was
// created later, so the thread holding the ST2 token cannot have a reference to
// ST1, CH1, or CH2. If ST1 is cleaned up first, that thread will delete ST1,
// CH1, and CH2. If instead ST2 is cleaned up first, that thread will only
// delete ST2.
//
// If ST1 is cleaned up first, the new list will be:
// ST2 <- CH3 <- global_delete_queue_tail
//
// If ST2 is cleaned up first, the new list will be:
// ST1 <- CH1 <- CH2 <- CH3 <- global_delete_queue_tail
//
// All new CordzHandle objects are appended to the list, so if a new thread
// comes along before either ST1 or ST2 are cleaned up, the new list will be:
// ST1 <- CH1 <- CH2 <- ST2 <- CH3 <- ST3 <- global_delete_queue_tail
//
// A thread must hold the global_delete_queue_mu mutex whenever it's altering
// this list.
//
// It is safe for thread that holds a CordzSampleToken to read
// global_cordz_infos at any time since the objects it is able to retrieve will
// not be deleted while the CordzSampleToken exists.
class CordzSampleToken : public CordzSnapshot {
public:
class Iterator {
public:
using iterator_category = std::input_iterator_tag;
using value_type = const CordzInfo&;
using difference_type = ptrdiff_t;
using pointer = const CordzInfo*;
using reference = value_type;
Iterator() = default;
Iterator& operator++();
Iterator operator++(int);
friend bool operator==(const Iterator& lhs, const Iterator& rhs);
friend bool operator!=(const Iterator& lhs, const Iterator& rhs);
reference operator*() const;
pointer operator->() const;
private:
friend class CordzSampleToken;
explicit Iterator(const CordzSampleToken* token);
const CordzSampleToken* token_ = nullptr;
pointer current_ = nullptr;
};
CordzSampleToken() = default;
CordzSampleToken(const CordzSampleToken&) = delete;
CordzSampleToken& operator=(const CordzSampleToken&) = delete;
Iterator begin() { return Iterator(this); }
Iterator end() { return Iterator(); }
};
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_STRINGS_CORDZ_SAMPLE_TOKEN_H_

@ -0,0 +1,209 @@
// Copyright 2019 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/strings/internal/cordz_sample_token.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/memory/memory.h"
#include "absl/random/random.h"
#include "absl/strings/internal/cord_rep_flat.h"
#include "absl/strings/internal/cordz_handle.h"
#include "absl/strings/internal/cordz_info.h"
#include "absl/synchronization/internal/thread_pool.h"
#include "absl/synchronization/notification.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
namespace {
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Ne;
struct TestCordRep {
CordRepFlat* rep;
TestCordRep() {
rep = CordRepFlat::New(100);
rep->length = 100;
memset(rep->Data(), 1, 100);
}
~TestCordRep() { CordRepFlat::Delete(rep); }
};
TEST(CordzSampleTokenTest, IteratorTraits) {
static_assert(std::is_copy_constructible<CordzSampleToken::Iterator>::value,
"");
static_assert(std::is_copy_assignable<CordzSampleToken::Iterator>::value, "");
static_assert(std::is_move_constructible<CordzSampleToken::Iterator>::value,
"");
static_assert(std::is_move_assignable<CordzSampleToken::Iterator>::value, "");
static_assert(
std::is_same<
std::iterator_traits<CordzSampleToken::Iterator>::iterator_category,
std::input_iterator_tag>::value,
"");
static_assert(
std::is_same<std::iterator_traits<CordzSampleToken::Iterator>::value_type,
const CordzInfo&>::value,
"");
static_assert(
std::is_same<
std::iterator_traits<CordzSampleToken::Iterator>::difference_type,
ptrdiff_t>::value,
"");
static_assert(
std::is_same<std::iterator_traits<CordzSampleToken::Iterator>::pointer,
const CordzInfo*>::value,
"");
static_assert(
std::is_same<std::iterator_traits<CordzSampleToken::Iterator>::reference,
const CordzInfo&>::value,
"");
}
TEST(CordzSampleTokenTest, IteratorEmpty) {
CordzSampleToken token;
EXPECT_THAT(token.begin(), Eq(token.end()));
}
TEST(CordzSampleTokenTest, Iterator) {
TestCordRep rep1;
TestCordRep rep2;
TestCordRep rep3;
CordzInfo* info1 = CordzInfo::TrackCord(rep1.rep);
CordzInfo* info2 = CordzInfo::TrackCord(rep2.rep);
CordzInfo* info3 = CordzInfo::TrackCord(rep3.rep);
CordzSampleToken token;
std::vector<const CordzInfo*> found;
for (const CordzInfo& cord_info : token) {
found.push_back(&cord_info);
}
EXPECT_THAT(found, ElementsAre(info3, info2, info1));
CordzInfo::UntrackCord(info1);
CordzInfo::UntrackCord(info2);
CordzInfo::UntrackCord(info3);
}
TEST(CordzSampleTokenTest, IteratorEquality) {
TestCordRep rep1;
TestCordRep rep2;
TestCordRep rep3;
CordzInfo* info1 = CordzInfo::TrackCord(rep1.rep);
CordzSampleToken token1;
// lhs starts with the CordzInfo corresponding to cord1 at the head.
CordzSampleToken::Iterator lhs = token1.begin();
CordzInfo* info2 = CordzInfo::TrackCord(rep2.rep);
CordzSampleToken token2;
// rhs starts with the CordzInfo corresponding to cord2 at the head.
CordzSampleToken::Iterator rhs = token2.begin();
CordzInfo* info3 = CordzInfo::TrackCord(rep3.rep);
// lhs is on cord1 while rhs is on cord2.
EXPECT_THAT(lhs, Ne(rhs));
rhs++;
// lhs and rhs are both on cord1, but they didn't come from the same
// CordzSampleToken.
EXPECT_THAT(lhs, Ne(rhs));
lhs++;
rhs++;
// Both lhs and rhs are done, so they are on nullptr.
EXPECT_THAT(lhs, Eq(rhs));
CordzInfo::UntrackCord(info1);
CordzInfo::UntrackCord(info2);
CordzInfo::UntrackCord(info3);
}
TEST(CordzSampleTokenTest, MultiThreaded) {
Notification stop;
static constexpr int kNumThreads = 4;
static constexpr int kNumCords = 3;
static constexpr int kNumTokens = 3;
absl::synchronization_internal::ThreadPool pool(kNumThreads);
for (int i = 0; i < kNumThreads; ++i) {
pool.Schedule([&stop]() {
absl::BitGen gen;
TestCordRep reps[kNumCords];
CordzInfo* infos[kNumCords] = {nullptr};
std::vector<std::unique_ptr<CordzSampleToken>> tokens;
tokens.resize(kNumTokens);
while (!stop.HasBeenNotified()) {
// Randomly perform one of five actions:
// 1) Untrack
// 2) Track
// 3) Iterate over Cords visible to a token.
// 4) Unsample
// 5) Sample
int index = absl::Uniform(gen, 0, kNumCords);
if (absl::Bernoulli(gen, 0.5)) {
// Track/untrack.
if (infos[index]) {
// 1) Untrack
CordzInfo::UntrackCord(infos[index]);
infos[index] = nullptr;
} else {
// 2) Track
infos[index] = CordzInfo::TrackCord(reps[index].rep);
}
} else {
if (tokens[index]) {
if (absl::Bernoulli(gen, 0.5)) {
// 3) Iterate over Cords visible to a token.
for (const CordzInfo& info : *tokens[index]) {
// This is trivial work to allow us to compile the loop.
EXPECT_THAT(info.Next(*tokens[index]), Ne(&info));
}
} else {
// 4) Unsample
tokens[index].reset();
}
} else {
// 5) Sample
tokens[index] = absl::make_unique<CordzSampleToken>();
}
}
}
for (CordzInfo* info : infos) {
if (info != nullptr) {
CordzInfo::UntrackCord(info);
}
}
});
}
// The threads will hammer away. Give it a little bit of time for tsan to
// spot errors.
absl::SleepFor(absl::Seconds(3));
stop.Notify();
}
} // namespace
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl

@ -0,0 +1,55 @@
// Copyright 2019 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.
#ifndef ABSL_STRINGS_INTERNAL_CORDZ_STATISTICS_H_
#define ABSL_STRINGS_INTERNAL_CORDZ_STATISTICS_H_
#include <cstdint>
#include "absl/base/config.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
// CordzStatistics captures some meta information about a Cord's shape.
struct CordzStatistics {
// The size of the cord in bytes. This matches the result of Cord::size().
int64_t size = 0;
// The estimated memory used by the sampled cord. This value matches the
// value as reported by Cord::EstimatedMemoryUsage().
// A value of 0 implies the property has not been recorded.
int64_t estimated_memory_usage = 0;
// The effective memory used by the sampled cord, inversely weighted by the
// effective indegree of each allocated node. This is a representation of the
// fair share of memory usage that should be attributed to the sampled cord.
// This value is more useful for cases where one or more nodes are referenced
// by multiple Cord instances, and for cases where a Cord includes the same
// node multiple times (either directly or indirectly).
// A value of 0 implies the property has not been recorded.
int64_t estimated_fair_share_memory_usage = 0;
// The total number of nodes referenced by this cord.
// For ring buffer Cords, this includes the 'ring buffer' node.
// A value of 0 implies the property has not been recorded.
int64_t node_count = 0;
};
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_STRINGS_INTERNAL_CORDZ_STATISTICS_H_
Loading…
Cancel
Save