Export of internal Abseil changes.

--
7fa1107161a03dac53fb84c2b06d8092616c7b13 by Abseil Team <absl-team@google.com>:

Harden the generic stacktrace implementation for use during early program execution

PiperOrigin-RevId: 226375950

--
079f9969329f5eb66f647dd3c44b17541b1bf217 by Matt Kulukundis <kfm@google.com>:

Workaround platforms that have over-aggressive warnings on -Wexit-time-destructors

PiperOrigin-RevId: 226362948

--
1447943f509be681ca5495add0162c750ef237f1 by Matt Kulukundis <kfm@google.com>:

Switch from 64 to size_t atomics so they work on embedded platforms that do not
have 64 bit atomics.

PiperOrigin-RevId: 226210704

--
d14d49837ae2bcde74051e0c79c18ee0f43866b9 by Tom Manshreck <shreck@google.com>:

Develop initial documentation for API breaking changes process:

PiperOrigin-RevId: 226210021

--
7ea3d7fe0e86979dab83a5fc9cc3bf1d6cb3bd53 by Abseil Team <absl-team@google.com>:

Import of CCTZ from GitHub.

PiperOrigin-RevId: 226195522

--
7de873e880d7f016a4fa1e08d626f0535cc470af by Abseil Team <absl-team@google.com>:

Make Abseil LICENSE files newline terminated, with a single
trailing blank line.  Also remove line-ending whitespace.

PiperOrigin-RevId: 226182949

--
7d00643fadfad7f0d992c68bd9d2ed2e5bc960b0 by Matt Kulukundis <kfm@google.com>:

Internal cleanup

PiperOrigin-RevId: 226045282

--
c4a0a11c0ce2875271191e477f3d36eaaeca4613 by Matt Kulukundis <kfm@google.com>:

Internal cleanup

PiperOrigin-RevId: 226038273

--
8ee4ebbb1ae5cda119e436e5ff7e3aa966608c10 by Matt Kulukundis <kfm@google.com>:

Adds a global sampler which tracks a fraction of live tables for collecting
telemetry data.

PiperOrigin-RevId: 226032080

--
d576446f050518cd1b0ae447d682d8552f0e7e30 by Mark Barolak <mbar@google.com>:

Replace an internal CaseEqual function with calls to the identical absl::EqualsIgnoreCase.  This closes out a rather old TODO.

PiperOrigin-RevId: 226024779

--
6b23f1ee028a5ffa608c920424f1220a117a8f3d by Abseil Team <absl-team@google.com>:

Add December 2018 LTS branch to list of LTS branches.

PiperOrigin-RevId: 226011333

--
bb0833a43bdaef4c8c059b17bcd27ba9a085a114 by Mark Barolak <mbar@google.com>:

Explicitly state that when the SimpleAtoi family of functions encounter an error, the value of their output parameter is unspecified.

Also standardize the name of the output parameter to be `out`.

PiperOrigin-RevId: 225997035

--
46c1876b1a248eabda7545daa61a74a4cdfe9077 by Abseil Team <absl-team@google.com>:

Remove deprecated CMake function absl_test, absl_library and absl_header_library

PiperOrigin-RevId: 225950041
GitOrigin-RevId: 7fa1107161a03dac53fb84c2b06d8092616c7b13
Change-Id: I2ca9d3aada9292614527d1339a7557494139b806
This commit is contained in:
Abseil Team 2018-12-20 12:29:59 -08:00 committed by Xiaoyi Zhang
parent 3e2e9b5557
commit 968a34ffda
16 changed files with 1313 additions and 348 deletions

View file

@ -23,53 +23,8 @@ include(AbseilConfigureCopts)
# For example, Visual Studio supports folders. # For example, Visual Studio supports folders.
set(ABSL_IDE_FOLDER Abseil) set(ABSL_IDE_FOLDER Abseil)
# absl_cc_library()
# #
# create a library in the absl namespace
#
# parameters
# SOURCES : sources files for the library
# PUBLIC_LIBRARIES: targets and flags for linking phase
# PRIVATE_COMPILE_FLAGS: compile flags for the library. Will not be exported.
# EXPORT_NAME: export name for the absl:: target export
# TARGET: target name
#
# create a target associated to <NAME>
# libraries are installed under CMAKE_INSTALL_FULL_LIBDIR by default
#
function(absl_library)
cmake_parse_arguments(ABSL_LIB
"DISABLE_INSTALL" # keep that in case we want to support installation one day
"TARGET;EXPORT_NAME"
"SOURCES;PUBLIC_LIBRARIES;PRIVATE_COMPILE_FLAGS"
${ARGN}
)
set(_NAME ${ABSL_LIB_TARGET})
string(TOUPPER ${_NAME} _UPPER_NAME)
add_library(${_NAME} STATIC ${ABSL_LIB_SOURCES})
target_compile_options(${_NAME}
PRIVATE
${ABSL_LIB_PRIVATE_COMPILE_FLAGS}
${ABSL_DEFAULT_COPTS}
)
target_link_libraries(${_NAME} PUBLIC ${ABSL_LIB_PUBLIC_LIBRARIES})
target_include_directories(${_NAME}
PUBLIC ${ABSL_COMMON_INCLUDE_DIRS} ${ABSL_LIB_PUBLIC_INCLUDE_DIRS}
PRIVATE ${ABSL_LIB_PRIVATE_INCLUDE_DIRS}
)
# Add all Abseil targets to a a folder in the IDE for organization.
set_property(TARGET ${_NAME} PROPERTY FOLDER ${ABSL_IDE_FOLDER})
set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD ${ABSL_CXX_STANDARD})
set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
if(ABSL_LIB_EXPORT_NAME)
add_library(absl::${ABSL_LIB_EXPORT_NAME} ALIAS ${_NAME})
endif()
endfunction()
# CMake function to imitate Bazel's cc_library rule. # CMake function to imitate Bazel's cc_library rule.
# #
# Parameters: # Parameters:
@ -258,116 +213,10 @@ function(absl_cc_test)
add_test(NAME ${_NAME} COMMAND ${_NAME}) add_test(NAME ${_NAME} COMMAND ${_NAME})
endfunction() endfunction()
#
# header only virtual target creation
#
function(absl_header_library)
cmake_parse_arguments(ABSL_HO_LIB
"DISABLE_INSTALL"
"EXPORT_NAME;TARGET"
"PUBLIC_LIBRARIES;PRIVATE_COMPILE_FLAGS;PUBLIC_INCLUDE_DIRS;PRIVATE_INCLUDE_DIRS"
${ARGN}
)
set(_NAME ${ABSL_HO_LIB_TARGET})
set(__dummy_header_only_lib_file "${CMAKE_CURRENT_BINARY_DIR}/${_NAME}_header_only_dummy.cc")
if(NOT EXISTS ${__dummy_header_only_lib_file})
file(WRITE ${__dummy_header_only_lib_file}
"/* generated file for header-only cmake target */
namespace absl {
// single meaningless symbol
void ${_NAME}__header_fakesym() {}
} // namespace absl
"
)
endif()
add_library(${_NAME} ${__dummy_header_only_lib_file})
target_link_libraries(${_NAME} PUBLIC ${ABSL_HO_LIB_PUBLIC_LIBRARIES})
target_include_directories(${_NAME}
PUBLIC ${ABSL_COMMON_INCLUDE_DIRS} ${ABSL_HO_LIB_PUBLIC_INCLUDE_DIRS}
PRIVATE ${ABSL_HO_LIB_PRIVATE_INCLUDE_DIRS}
)
# Add all Abseil targets to a a folder in the IDE for organization.
set_property(TARGET ${_NAME} PROPERTY FOLDER ${ABSL_IDE_FOLDER})
set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD ${ABSL_CXX_STANDARD})
set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
if(ABSL_HO_LIB_EXPORT_NAME)
add_library(absl::${ABSL_HO_LIB_EXPORT_NAME} ALIAS ${_NAME})
endif()
endfunction()
#
# create an abseil unit_test and add it to the executed test list
#
# parameters
# TARGET: target name prefix
# SOURCES: sources files for the tests
# PUBLIC_LIBRARIES: targets and flags for linking phase.
# PRIVATE_COMPILE_FLAGS: compile flags for the test. Will not be exported.
#
# create a target associated to <NAME>_bin
#
# all tests will be register for execution with add_test()
#
# test compilation and execution is disable when ABSL_RUN_TESTS=OFF
#
function(absl_test)
cmake_parse_arguments(ABSL_TEST
""
"TARGET"
"SOURCES;PUBLIC_LIBRARIES;PRIVATE_COMPILE_FLAGS;PUBLIC_INCLUDE_DIRS"
${ARGN}
)
if(ABSL_RUN_TESTS)
set(_NAME "absl_${ABSL_TEST_TARGET}")
string(TOUPPER ${_NAME} _UPPER_NAME)
add_executable(${_NAME} ${ABSL_TEST_SOURCES})
target_compile_options(${_NAME}
PRIVATE
${ABSL_TEST_PRIVATE_COMPILE_FLAGS}
${ABSL_TEST_COPTS}
)
target_link_libraries(${_NAME} PUBLIC ${ABSL_TEST_PUBLIC_LIBRARIES} ${ABSL_TEST_COMMON_LIBRARIES})
target_include_directories(${_NAME}
PUBLIC ${ABSL_COMMON_INCLUDE_DIRS} ${ABSL_TEST_PUBLIC_INCLUDE_DIRS}
PRIVATE ${GMOCK_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}
)
# Add all Abseil targets to a a folder in the IDE for organization.
set_property(TARGET ${_NAME} PROPERTY FOLDER ${ABSL_IDE_FOLDER})
set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD ${ABSL_CXX_STANDARD})
set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
add_test(NAME ${_NAME} COMMAND ${_NAME})
endif(ABSL_RUN_TESTS)
endfunction()
function(check_target my_target) function(check_target my_target)
if(NOT TARGET ${my_target}) if(NOT TARGET ${my_target})
message(FATAL_ERROR " ABSL: compiling absl requires a ${my_target} CMake target in your project, message(FATAL_ERROR " ABSL: compiling absl requires a ${my_target} CMake target in your project,
see CMake/README.md for more details") see CMake/README.md for more details")
endif(NOT TARGET ${my_target}) endif(NOT TARGET ${my_target})
endfunction() endfunction()

View file

@ -200,5 +200,4 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.

1
LTS.md
View file

@ -10,4 +10,5 @@ turn, use Abseil. (For more information about our releases, see the
The following lists LTS branches and the dates on which they have been released: The following lists LTS branches and the dates on which they have been released:
* [LTS Branch December 18, 2018](https://github.com/abseil/abseil-cpp/tree/lts_2018_12_18/)
* [LTS Branch June 20, 2018](https://github.com/abseil/abseil-cpp/tree/lts_2018_06_20/) * [LTS Branch June 20, 2018](https://github.com/abseil/abseil-cpp/tree/lts_2018_06_20/)

17
absl/UPGRADES.md Normal file
View file

@ -0,0 +1,17 @@
# C++ Upgrade Tools
Abseil may occassionally release API-breaking changes. As noted in our
[Compatibility Guidelines][compatibility-guide], we will aim to provide a tool
to do the work of effecting such API-breaking changes, when absolutely
necessary.
These tools will be listed on the [C++ Upgrade Tools][upgrade-tools] guide on
http://abseil.io.
For more information, the [C++ Automated Upgrade Guide][api-upgrades-guide]
outlines this process.
[compatibility-guide]: https://abseil.io/about/compatibility
[api-upgrades-guide]: https://abseil.io/docs/cpp/tools/api-upgrades
[upgrade-tools]: https://abseil.io/docs/cpp/tools/upgrades/

View file

@ -436,6 +436,35 @@ cc_library(
copts = ABSL_DEFAULT_COPTS, copts = ABSL_DEFAULT_COPTS,
) )
cc_library(
name = "hashtablez_sampler",
srcs = ["internal/hashtablez_sampler.cc"],
hdrs = ["internal/hashtablez_sampler.h"],
copts = ABSL_DEFAULT_COPTS,
deps = [
":have_sse",
"//absl/base:core_headers",
"//absl/debugging:stacktrace",
"//absl/memory",
"//absl/synchronization",
"//absl/utility",
],
)
cc_test(
name = "hashtablez_sampler_test",
srcs = ["internal/hashtablez_sampler_test.cc"],
deps = [
":hashtablez_sampler",
":have_sse",
"//absl/base:core_headers",
"//absl/synchronization",
"//absl/synchronization:thread_pool",
"//absl/time",
"@com_google_googletest//:gtest_main",
],
)
cc_library( cc_library(
name = "node_hash_policy", name = "node_hash_policy",
hdrs = ["internal/node_hash_policy.h"], hdrs = ["internal/node_hash_policy.h"],
@ -467,6 +496,7 @@ cc_library(
name = "have_sse", name = "have_sse",
hdrs = ["internal/have_sse.h"], hdrs = ["internal/have_sse.h"],
copts = ABSL_DEFAULT_COPTS, copts = ABSL_DEFAULT_COPTS,
visibility = ["//visibility:private"],
) )
cc_library( cc_library(
@ -479,6 +509,7 @@ cc_library(
":container_memory", ":container_memory",
":hash_policy_traits", ":hash_policy_traits",
":hashtable_debug_hooks", ":hashtable_debug_hooks",
":hashtablez_sampler",
":have_sse", ":have_sse",
":layout", ":layout",
"//absl/base:bits", "//absl/base:bits",

View file

@ -431,6 +431,31 @@ absl_cc_test(
gmock_main gmock_main
) )
absl_cc_library(
NAME
hashtablez_sampler
HDRS
"internal/hashtablez_sampler.h"
SRCS
"internal/hashtablez_sampler.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::have_sse
absl::synchronization
)
absl_cc_test(
NAME
hashtablez_sampler_test
SRCS
"internal/hashtablez_sampler_test.cc"
DEPS
absl::hashtablez_sampler
absl::have_sse
gmock_main
)
absl_cc_library( absl_cc_library(
NAME NAME
hashtable_debug hashtable_debug
@ -459,7 +484,6 @@ absl_cc_library(
"internal/have_sse.h" "internal/have_sse.h"
COPTS COPTS
${ABSL_DEFAULT_COPTS} ${ABSL_DEFAULT_COPTS}
PUBLIC
) )
absl_cc_library( absl_cc_library(
@ -520,6 +544,7 @@ absl_cc_library(
absl::meta absl::meta
absl::optional absl::optional
absl::utility absl::utility
absl::hashtablez_sampler
PUBLIC PUBLIC
) )

View file

@ -0,0 +1,289 @@
// 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
//
// http://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 <cassert>
#include <functional>
#include <limits>
#include "absl/base/attributes.h"
#include "absl/container/internal/have_sse.h"
#include "absl/debugging/stacktrace.h"
#include "absl/memory/memory.h"
#include "absl/synchronization/mutex.h"
namespace absl {
namespace container_internal {
constexpr int HashtablezInfo::kMaxStackDepth;
namespace {
ABSL_CONST_INIT std::atomic<bool> g_hashtablez_enabled{
false
};
ABSL_CONST_INIT std::atomic<int32_t> g_hashtablez_sample_parameter{1 << 10};
ABSL_CONST_INIT std::atomic<int32_t> g_hashtablez_max_samples{1 << 20};
// Returns the next pseudo-random value.
// pRNG is: aX+b mod c with a = 0x5DEECE66D, b = 0xB, c = 1<<48
// This is the lrand64 generator.
uint64_t NextRandom(uint64_t rnd) {
const uint64_t prng_mult = uint64_t{0x5DEECE66D};
const uint64_t prng_add = 0xB;
const uint64_t prng_mod_power = 48;
const uint64_t prng_mod_mask = ~(~uint64_t{0} << prng_mod_power);
return (prng_mult * rnd + prng_add) & prng_mod_mask;
}
// Generates a geometric variable with the specified mean.
// This is done by generating a random number between 0 and 1 and applying
// the inverse cumulative distribution function for an exponential.
// Specifically: Let m be the inverse of the sample period, then
// the probability distribution function is m*exp(-mx) so the CDF is
// p = 1 - exp(-mx), so
// q = 1 - p = exp(-mx)
// log_e(q) = -mx
// -log_e(q)/m = x
// log_2(q) * (-log_e(2) * 1/m) = x
// In the code, q is actually in the range 1 to 2**26, hence the -26 below
//
int64_t GetGeometricVariable(int64_t mean) {
#if ABSL_HAVE_THREAD_LOCAL
thread_local
#else // ABSL_HAVE_THREAD_LOCAL
// SampleSlow and hence GetGeometricVariable is guarded by a single mutex when
// there are not thread locals. Thus, a single global rng is acceptable for
// that case.
static
#endif // ABSL_HAVE_THREAD_LOCAL
uint64_t rng = []() {
// We don't get well distributed numbers from this so we call
// NextRandom() a bunch to mush the bits around. We use a global_rand
// to handle the case where the same thread (by memory address) gets
// created and destroyed repeatedly.
ABSL_CONST_INIT static std::atomic<uint32_t> global_rand(0);
uint64_t r = reinterpret_cast<uint64_t>(&rng) +
global_rand.fetch_add(1, std::memory_order_relaxed);
for (int i = 0; i < 20; ++i) {
r = NextRandom(r);
}
return r;
}();
rng = NextRandom(rng);
// Take the top 26 bits as the random number
// (This plus the 1<<58 sampling bound give a max possible step of
// 5194297183973780480 bytes.)
const uint64_t prng_mod_power = 48; // Number of bits in prng
// The uint32_t cast is to prevent a (hard-to-reproduce) NAN
// under piii debug for some binaries.
double q = static_cast<uint32_t>(rng >> (prng_mod_power - 26)) + 1.0;
// Put the computed p-value through the CDF of a geometric.
double interval = (std::log2(q) - 26) * (-std::log(2.0) * mean);
// Very large values of interval overflow int64_t. If we happen to
// hit such improbable condition, we simply cheat and clamp interval
// to largest supported value.
if (interval > static_cast<double>(std::numeric_limits<int64_t>::max() / 2)) {
return std::numeric_limits<int64_t>::max() / 2;
}
// Small values of interval are equivalent to just sampling next time.
if (interval < 1) {
return 1;
}
return static_cast<int64_t>(interval);
}
} // namespace
HashtablezSampler& HashtablezSampler::Global() {
static auto* sampler = new HashtablezSampler();
return *sampler;
}
HashtablezInfo::HashtablezInfo() { PrepareForSampling(); }
HashtablezInfo::~HashtablezInfo() = default;
void HashtablezInfo::PrepareForSampling() {
capacity.store(0, std::memory_order_relaxed);
size.store(0, std::memory_order_relaxed);
num_erases.store(0, std::memory_order_relaxed);
max_probe_length.store(0, std::memory_order_relaxed);
total_probe_length.store(0, std::memory_order_relaxed);
hashes_bitwise_or.store(0, std::memory_order_relaxed);
hashes_bitwise_and.store(~size_t{}, std::memory_order_relaxed);
create_time = absl::Now();
// The inliner makes hardcoded skip_count difficult (especially when combined
// with LTO). We use the ability to exclude stacks by regex when encoding
// instead.
depth = absl::GetStackTrace(stack, HashtablezInfo::kMaxStackDepth,
/* skip_count= */ 0);
dead = nullptr;
}
HashtablezSampler::HashtablezSampler()
: dropped_samples_(0), size_estimate_(0), all_(nullptr) {
absl::MutexLock l(&graveyard_.init_mu);
graveyard_.dead = &graveyard_;
}
HashtablezSampler::~HashtablezSampler() {
HashtablezInfo* s = all_.load(std::memory_order_acquire);
while (s != nullptr) {
HashtablezInfo* next = s->next;
delete s;
s = next;
}
}
void HashtablezSampler::PushNew(HashtablezInfo* sample) {
sample->next = all_.load(std::memory_order_relaxed);
while (!all_.compare_exchange_weak(sample->next, sample,
std::memory_order_release,
std::memory_order_relaxed)) {
}
}
void HashtablezSampler::PushDead(HashtablezInfo* sample) {
absl::MutexLock graveyard_lock(&graveyard_.init_mu);
absl::MutexLock sample_lock(&sample->init_mu);
sample->dead = graveyard_.dead;
graveyard_.dead = sample;
}
HashtablezInfo* HashtablezSampler::PopDead() {
absl::MutexLock graveyard_lock(&graveyard_.init_mu);
// The list is circular, so eventually it collapses down to
// graveyard_.dead == &graveyard_
// when it is empty.
HashtablezInfo* sample = graveyard_.dead;
if (sample == &graveyard_) return nullptr;
absl::MutexLock sample_lock(&sample->init_mu);
graveyard_.dead = sample->dead;
sample->PrepareForSampling();
return sample;
}
HashtablezInfo* HashtablezSampler::Register() {
int64_t size = size_estimate_.fetch_add(1, std::memory_order_relaxed);
if (size > g_hashtablez_max_samples.load(std::memory_order_relaxed)) {
size_estimate_.fetch_sub(1, std::memory_order_relaxed);
dropped_samples_.fetch_add(1, std::memory_order_relaxed);
return nullptr;
}
HashtablezInfo* sample = PopDead();
if (sample == nullptr) {
// Resurrection failed. Hire a new warlock.
sample = new HashtablezInfo();
PushNew(sample);
}
return sample;
}
void HashtablezSampler::Unregister(HashtablezInfo* sample) {
PushDead(sample);
size_estimate_.fetch_sub(1, std::memory_order_relaxed);
}
int64_t HashtablezSampler::Iterate(
const std::function<void(const HashtablezInfo& stack)>& f) {
HashtablezInfo* s = all_.load(std::memory_order_acquire);
while (s != nullptr) {
absl::MutexLock l(&s->init_mu);
if (s->dead == nullptr) {
f(*s);
}
s = s->next;
}
return dropped_samples_.load(std::memory_order_relaxed);
}
HashtablezInfo* SampleSlow(int64_t* next_sample) {
bool first = *next_sample < 0;
*next_sample = GetGeometricVariable(
g_hashtablez_sample_parameter.load(std::memory_order_relaxed));
// g_hashtablez_enabled can be dynamically flipped, we need to set a threshold
// low enough that we will start sampling in a reasonable time, so we just use
// the default sampling rate.
if (!g_hashtablez_enabled.load(std::memory_order_relaxed)) return nullptr;
// We will only be negative on our first count, so we should just retry in
// that case.
if (first) {
if (ABSL_PREDICT_TRUE(--*next_sample > 0)) return nullptr;
return SampleSlow(next_sample);
}
return HashtablezSampler::Global().Register();
}
void UnsampleSlow(HashtablezInfo* info) {
HashtablezSampler::Global().Unregister(info);
}
void RecordInsertSlow(HashtablezInfo* info, size_t hash,
size_t distance_from_desired) {
// SwissTables probe in groups of 16, so scale this to count items probes and
// not offset from desired.
size_t probe_length = distance_from_desired;
#if SWISSTABLE_HAVE_SSE2
probe_length /= 16;
#else
probe_length /= 8;
#endif
info->hashes_bitwise_and.fetch_and(hash, std::memory_order_relaxed);
info->hashes_bitwise_or.fetch_or(hash, std::memory_order_relaxed);
info->max_probe_length.store(
std::max(info->max_probe_length.load(std::memory_order_relaxed),
probe_length),
std::memory_order_relaxed);
info->total_probe_length.fetch_add(probe_length, std::memory_order_relaxed);
info->size.fetch_add(1, std::memory_order_relaxed);
}
void SetHashtablezEnabled(bool enabled) {
g_hashtablez_enabled.store(enabled, std::memory_order_release);
}
void SetHashtablezSampleParameter(int32_t rate) {
if (rate > 0) {
g_hashtablez_sample_parameter.store(rate, std::memory_order_release);
} else {
ABSL_RAW_LOG(ERROR, "Invalid hashtablez sample rate: %lld",
static_cast<long long>(rate)); // NOLINT(runtime/int)
}
}
void SetHashtablezMaxSamples(int32_t max) {
if (max > 0) {
g_hashtablez_max_samples.store(max, std::memory_order_release);
} else {
ABSL_RAW_LOG(ERROR, "Invalid hashtablez max samples: %lld",
static_cast<long long>(max)); // NOLINT(runtime/int)
}
}
} // namespace container_internal
} // namespace absl

View file

@ -0,0 +1,236 @@
// 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
//
// http://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.
//
// This is a low level library to sample hashtables and collect runtime
// statistics about them.
//
// `HashtablezSampler` controls the lifecycle of `HashtablezInfo` objects which
// store information about a single sample.
//
// `Record*` methods store information into samples.
// `Sample()` and `Unsample()` make use of a single global sampler with
// properties controlled by the flags hashtablez_enabled,
// hashtablez_sample_rate, and hashtablez_max_samples.
#ifndef ABSL_CONTAINER_INTERNAL_HASHTABLEZ_SAMPLER_H_
#define ABSL_CONTAINER_INTERNAL_HASHTABLEZ_SAMPLER_H_
#include <atomic>
#include <functional>
#include <memory>
#include <vector>
#include "absl/base/optimization.h"
#include "absl/synchronization/mutex.h"
#include "absl/utility/utility.h"
namespace absl {
namespace container_internal {
// Stores information about a sampled hashtable. All mutations to this *must*
// be made through `Record*` functions below. All reads from this *must* only
// occur in the callback to `HashtablezSampler::Iterate`.
struct HashtablezInfo {
// Constructs the object but does not fill in any fields.
HashtablezInfo();
~HashtablezInfo();
HashtablezInfo(const HashtablezInfo&) = delete;
HashtablezInfo& operator=(const HashtablezInfo&) = delete;
// Puts the object into a clean state, fills in the logically `const` members,
// blocking for any readers that are currently sampling the object.
void PrepareForSampling() EXCLUSIVE_LOCKS_REQUIRED(init_mu);
// These fields are mutated by the various Record* APIs and need to be
// thread-safe.
std::atomic<size_t> capacity;
std::atomic<size_t> size;
std::atomic<size_t> num_erases;
std::atomic<size_t> max_probe_length;
std::atomic<size_t> total_probe_length;
std::atomic<size_t> hashes_bitwise_or;
std::atomic<size_t> hashes_bitwise_and;
// `HashtablezSampler` maintains intrusive linked lists for all samples. See
// comments on `HashtablezSampler::all_` for details on these. `init_mu`
// guards the ability to restore the sample to a pristine state. This
// prevents races with sampling and resurrecting an object.
absl::Mutex init_mu;
HashtablezInfo* next;
HashtablezInfo* dead GUARDED_BY(init_mu);
// All of the fields below are set by `PrepareForSampling`, they must not be
// mutated in `Record*` functions. They are logically `const` in that sense.
// These are guarded by init_mu, but that is not externalized to clients, who
// can only read them during `HashtablezSampler::Iterate` which will hold the
// lock.
static constexpr int kMaxStackDepth = 64;
absl::Time create_time;
int32_t depth;
void* stack[kMaxStackDepth];
};
inline void RecordStorageChangedSlow(HashtablezInfo* info, size_t size,
size_t capacity) {
info->size.store(size, std::memory_order_relaxed);
info->capacity.store(capacity, std::memory_order_relaxed);
}
void RecordInsertSlow(HashtablezInfo* info, size_t hash,
size_t distance_from_desired);
inline void RecordEraseSlow(HashtablezInfo* info) {
info->size.fetch_sub(1, std::memory_order_relaxed);
info->num_erases.fetch_add(1, std::memory_order_relaxed);
}
HashtablezInfo* SampleSlow(int64_t* next_sample);
void UnsampleSlow(HashtablezInfo* info);
class HashtablezInfoHandle {
public:
explicit HashtablezInfoHandle() : info_(nullptr) {}
explicit HashtablezInfoHandle(HashtablezInfo* info) : info_(info) {}
~HashtablezInfoHandle() {
if (ABSL_PREDICT_TRUE(info_ == nullptr)) return;
UnsampleSlow(info_);
}
HashtablezInfoHandle(const HashtablezInfoHandle&) = delete;
HashtablezInfoHandle& operator=(const HashtablezInfoHandle&) = delete;
HashtablezInfoHandle(HashtablezInfoHandle&& o) noexcept
: info_(absl::exchange(o.info_, nullptr)) {}
HashtablezInfoHandle& operator=(HashtablezInfoHandle&& o) noexcept {
if (ABSL_PREDICT_FALSE(info_ != nullptr)) {
UnsampleSlow(info_);
}
info_ = absl::exchange(o.info_, nullptr);
return *this;
}
inline void RecordStorageChanged(size_t size, size_t capacity) {
if (ABSL_PREDICT_TRUE(info_ == nullptr)) return;
RecordStorageChangedSlow(info_, size, capacity);
}
inline void RecordInsert(size_t hash, size_t distance_from_desired) {
if (ABSL_PREDICT_TRUE(info_ == nullptr)) return;
RecordInsertSlow(info_, hash, distance_from_desired);
}
inline void RecordErase() {
if (ABSL_PREDICT_TRUE(info_ == nullptr)) return;
RecordEraseSlow(info_);
}
friend inline void swap(HashtablezInfoHandle& lhs,
HashtablezInfoHandle& rhs) {
std::swap(lhs.info_, rhs.info_);
}
private:
friend class HashtablezInfoHandlePeer;
HashtablezInfo* info_;
};
// Returns an RAII sampling handle that manages registration and unregistation
// with the global sampler.
inline HashtablezInfoHandle Sample() {
#if ABSL_HAVE_THREAD_LOCAL
thread_local int64_t next_sample = 0;
#else // ABSL_HAVE_THREAD_LOCAL
static auto* mu = new absl::Mutex;
static int64_t next_sample = 0;
absl::MutexLock l(mu);
#endif // ABSL_HAVE_THREAD_LOCAL
if (ABSL_PREDICT_TRUE(--next_sample > 0)) {
return HashtablezInfoHandle(nullptr);
}
return HashtablezInfoHandle(SampleSlow(&next_sample));
}
// Holds samples and their associated stack traces with a soft limit of
// `SetHashtablezMaxSamples()`.
//
// Thread safe.
class HashtablezSampler {
public:
// Returns a global Sampler.
static HashtablezSampler& Global();
HashtablezSampler();
~HashtablezSampler();
// Registers for sampling. Returns an opaque registration info.
HashtablezInfo* Register();
// Unregisters the sample.
void Unregister(HashtablezInfo* sample);
// Iterates over all the registered `StackInfo`s. Returning the number of
// samples that have been dropped.
int64_t Iterate(const std::function<void(const HashtablezInfo& stack)>& f);
private:
void PushNew(HashtablezInfo* sample);
void PushDead(HashtablezInfo* sample);
HashtablezInfo* PopDead();
std::atomic<size_t> dropped_samples_;
std::atomic<size_t> size_estimate_;
// Intrusive lock free linked lists for tracking samples.
//
// `all_` records all samples (they are never removed from this list) and is
// terminated with a `nullptr`.
//
// `graveyard_.dead` is a circular linked list. When it is empty,
// `graveyard_.dead == &graveyard`. The list is circular so that
// every item on it (even the last) has a non-null dead pointer. This allows
// `Iterate` to determine if a given sample is live or dead using only
// information on the sample itself.
//
// For example, nodes [A, B, C, D, E] with [A, C, E] alive and [B, D] dead
// looks like this (G is the Graveyard):
//
// +---+ +---+ +---+ +---+ +---+
// all -->| A |--->| B |--->| C |--->| D |--->| E |
// | | | | | | | | | |
// +---+ | | +->| |-+ | | +->| |-+ | |
// | G | +---+ | +---+ | +---+ | +---+ | +---+
// | | | | | |
// | | --------+ +--------+ |
// +---+ |
// ^ |
// +--------------------------------------+
//
std::atomic<HashtablezInfo*> all_;
HashtablezInfo graveyard_;
};
// Enables or disables sampling for Swiss tables.
void SetHashtablezEnabled(bool enabled);
// Sets the rate at which Swiss tables will be sampled.
void SetHashtablezSampleParameter(int32_t rate);
// Sets a soft max for the number of samples that will be kept.
void SetHashtablezMaxSamples(int32_t max);
} // namespace container_internal
} // namespace absl
#endif // ABSL_CONTAINER_INTERNAL_HASHTABLEZ_SAMPLER_H_

View file

@ -0,0 +1,307 @@
// 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
//
// http://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 SWISSTABLE_HAVE_SSE2
constexpr int kProbeLength = 16;
#else
constexpr int kProbeLength = 8;
#endif
namespace absl {
namespace container_internal {
class HashtablezInfoHandlePeer {
public:
static bool IsSampled(const HashtablezInfoHandle& h) {
return h.info_ != nullptr;
}
static HashtablezInfo* GetInfo(HashtablezInfoHandle* h) { return h->info_; }
};
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.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_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.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.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_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);
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);
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);
}
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(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;
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);
}
TEST(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();
}
} // namespace
} // namespace container_internal
} // namespace absl

View file

@ -109,6 +109,7 @@
#include "absl/container/internal/container_memory.h" #include "absl/container/internal/container_memory.h"
#include "absl/container/internal/hash_policy_traits.h" #include "absl/container/internal/hash_policy_traits.h"
#include "absl/container/internal/hashtable_debug_hooks.h" #include "absl/container/internal/hashtable_debug_hooks.h"
#include "absl/container/internal/hashtablez_sampler.h"
#include "absl/container/internal/have_sse.h" #include "absl/container/internal/have_sse.h"
#include "absl/container/internal/layout.h" #include "absl/container/internal/layout.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
@ -943,9 +944,10 @@ class raw_hash_set {
// than a full `insert`. // than a full `insert`.
for (const auto& v : that) { for (const auto& v : that) {
const size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, v); const size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, v);
const size_t i = find_first_non_full(hash); auto target = find_first_non_full(hash);
set_ctrl(i, H2(hash)); set_ctrl(target.offset, H2(hash));
emplace_at(i, v); emplace_at(target.offset, v);
infoz_.RecordInsert(hash, target.probe_length);
} }
size_ = that.size(); size_ = that.size();
growth_left() -= that.size(); growth_left() -= that.size();
@ -959,6 +961,7 @@ class raw_hash_set {
slots_(absl::exchange(that.slots_, nullptr)), slots_(absl::exchange(that.slots_, nullptr)),
size_(absl::exchange(that.size_, 0)), size_(absl::exchange(that.size_, 0)),
capacity_(absl::exchange(that.capacity_, 0)), capacity_(absl::exchange(that.capacity_, 0)),
infoz_(absl::exchange(that.infoz_, HashtablezInfoHandle())),
// Hash, equality and allocator are copied instead of moved because // Hash, equality and allocator are copied instead of moved because
// `that` must be left valid. If Hash is std::function<Key>, moving it // `that` must be left valid. If Hash is std::function<Key>, moving it
// would create a nullptr functor that cannot be called. // would create a nullptr functor that cannot be called.
@ -979,6 +982,7 @@ class raw_hash_set {
std::swap(size_, that.size_); std::swap(size_, that.size_);
std::swap(capacity_, that.capacity_); std::swap(capacity_, that.capacity_);
std::swap(growth_left(), that.growth_left()); std::swap(growth_left(), that.growth_left());
std::swap(infoz_, that.infoz_);
} else { } else {
reserve(that.size()); reserve(that.size());
// Note: this will copy elements of dense_set and unordered_set instead of // Note: this will copy elements of dense_set and unordered_set instead of
@ -1049,6 +1053,7 @@ class raw_hash_set {
growth_left() = static_cast<size_t>(capacity_ * kMaxLoadFactor); growth_left() = static_cast<size_t>(capacity_ * kMaxLoadFactor);
} }
assert(empty()); assert(empty());
infoz_.RecordStorageChanged(size_, capacity_);
} }
// This overload kicks in when the argument is an rvalue of insertable and // This overload kicks in when the argument is an rvalue of insertable and
@ -1323,6 +1328,7 @@ class raw_hash_set {
swap(growth_left(), that.growth_left()); swap(growth_left(), that.growth_left());
swap(hash_ref(), that.hash_ref()); swap(hash_ref(), that.hash_ref());
swap(eq_ref(), that.eq_ref()); swap(eq_ref(), that.eq_ref());
swap(infoz_, that.infoz_);
if (AllocTraits::propagate_on_container_swap::value) { if (AllocTraits::propagate_on_container_swap::value) {
swap(alloc_ref(), that.alloc_ref()); swap(alloc_ref(), that.alloc_ref());
} else { } else {
@ -1333,7 +1339,11 @@ class raw_hash_set {
void rehash(size_t n) { void rehash(size_t n) {
if (n == 0 && capacity_ == 0) return; if (n == 0 && capacity_ == 0) return;
if (n == 0 && size_ == 0) return destroy_slots(); if (n == 0 && size_ == 0) {
destroy_slots();
infoz_.RecordStorageChanged(size_, capacity_);
return;
}
auto m = NormalizeCapacity((std::max)(n, NumSlotsFast(size()))); auto m = NormalizeCapacity((std::max)(n, NumSlotsFast(size())));
// n == 0 unconditionally rehashes as per the standard. // n == 0 unconditionally rehashes as per the standard.
if (n == 0 || m > capacity_) { if (n == 0 || m > capacity_) {
@ -1550,10 +1560,15 @@ class raw_hash_set {
set_ctrl(index, was_never_full ? kEmpty : kDeleted); set_ctrl(index, was_never_full ? kEmpty : kDeleted);
growth_left() += was_never_full; growth_left() += was_never_full;
infoz_.RecordErase();
} }
void initialize_slots() { void initialize_slots() {
assert(capacity_); assert(capacity_);
if (slots_ == nullptr) {
infoz_ = Sample();
}
auto layout = MakeLayout(capacity_); auto layout = MakeLayout(capacity_);
char* mem = static_cast<char*>( char* mem = static_cast<char*>(
Allocate<Layout::Alignment()>(&alloc_ref(), layout.AllocSize())); Allocate<Layout::Alignment()>(&alloc_ref(), layout.AllocSize()));
@ -1561,6 +1576,7 @@ class raw_hash_set {
slots_ = layout.template Pointer<1>(mem); slots_ = layout.template Pointer<1>(mem);
reset_ctrl(); reset_ctrl();
growth_left() = static_cast<size_t>(capacity_ * kMaxLoadFactor) - size_; growth_left() = static_cast<size_t>(capacity_ * kMaxLoadFactor) - size_;
infoz_.RecordStorageChanged(size_, capacity_);
} }
void destroy_slots() { void destroy_slots() {
@ -1593,7 +1609,7 @@ class raw_hash_set {
if (IsFull(old_ctrl[i])) { if (IsFull(old_ctrl[i])) {
size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, size_t hash = PolicyTraits::apply(HashElement{hash_ref()},
PolicyTraits::element(old_slots + i)); PolicyTraits::element(old_slots + i));
size_t new_i = find_first_non_full(hash); size_t new_i = find_first_non_full(hash).offset;
set_ctrl(new_i, H2(hash)); set_ctrl(new_i, H2(hash));
PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, old_slots + i); PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, old_slots + i);
} }
@ -1633,7 +1649,7 @@ class raw_hash_set {
if (!IsDeleted(ctrl_[i])) continue; if (!IsDeleted(ctrl_[i])) continue;
size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, size_t hash = PolicyTraits::apply(HashElement{hash_ref()},
PolicyTraits::element(slots_ + i)); PolicyTraits::element(slots_ + i));
size_t new_i = find_first_non_full(hash); size_t new_i = find_first_non_full(hash).offset;
// Verify if the old and new i fall within the same group wrt the hash. // Verify if the old and new i fall within the same group wrt the hash.
// If they do, we don't need to move the object as it falls already in the // If they do, we don't need to move the object as it falls already in the
@ -1706,7 +1722,11 @@ class raw_hash_set {
// - the input is already a set // - the input is already a set
// - there are enough slots // - there are enough slots
// - the element with the hash is not in the table // - the element with the hash is not in the table
size_t find_first_non_full(size_t hash) { struct FindInfo {
size_t offset;
size_t probe_length;
};
FindInfo find_first_non_full(size_t hash) {
auto seq = probe(hash); auto seq = probe(hash);
while (true) { while (true) {
Group g{ctrl_ + seq.offset()}; Group g{ctrl_ + seq.offset()};
@ -1718,11 +1738,11 @@ class raw_hash_set {
// the group. // the group.
// TODO(kfm,sbenza): revisit after we do unconditional mixing // TODO(kfm,sbenza): revisit after we do unconditional mixing
if (ShouldInsertBackwards(hash, ctrl_)) if (ShouldInsertBackwards(hash, ctrl_))
return seq.offset(mask.HighestBitSet()); return {seq.offset(mask.HighestBitSet()), seq.index()};
else else
return seq.offset(mask.LowestBitSet()); return {seq.offset(mask.LowestBitSet()), seq.index()};
#else #else
return seq.offset(mask.LowestBitSet()); return {seq.offset(mask.LowestBitSet()), seq.index()};
#endif #endif
} }
assert(seq.index() < capacity_ && "full table!"); assert(seq.index() < capacity_ && "full table!");
@ -1762,15 +1782,17 @@ class raw_hash_set {
} }
size_t prepare_insert(size_t hash) ABSL_ATTRIBUTE_NOINLINE { size_t prepare_insert(size_t hash) ABSL_ATTRIBUTE_NOINLINE {
size_t target = find_first_non_full(hash); auto target = find_first_non_full(hash);
if (ABSL_PREDICT_FALSE(growth_left() == 0 && !IsDeleted(ctrl_[target]))) { if (ABSL_PREDICT_FALSE(growth_left() == 0 &&
!IsDeleted(ctrl_[target.offset]))) {
rehash_and_grow_if_necessary(); rehash_and_grow_if_necessary();
target = find_first_non_full(hash); target = find_first_non_full(hash);
} }
++size_; ++size_;
growth_left() -= IsEmpty(ctrl_[target]); growth_left() -= IsEmpty(ctrl_[target.offset]);
set_ctrl(target, H2(hash)); set_ctrl(target.offset, H2(hash));
return target; infoz_.RecordInsert(hash, target.probe_length);
return target.offset;
} }
// Constructs the value in the space pointed by the iterator. This only works // Constructs the value in the space pointed by the iterator. This only works
@ -1847,6 +1869,7 @@ class raw_hash_set {
slot_type* slots_ = nullptr; // [capacity * slot_type] slot_type* slots_ = nullptr; // [capacity * slot_type]
size_t size_ = 0; // number of full slots size_t size_ = 0; // number of full slots
size_t capacity_ = 0; // total number of slots size_t capacity_ = 0; // total number of slots
HashtablezInfoHandle infoz_;
absl::container_internal::CompressedTuple<size_t /* growth_left */, hasher, absl::container_internal::CompressedTuple<size_t /* growth_left */, hasher,
key_equal, allocator_type> key_equal, allocator_type>
settings_{0, hasher{}, key_equal{}, allocator_type{}}; settings_{0, hasher{}, key_equal{}, allocator_type{}};

View file

@ -342,6 +342,7 @@ TEST(Table, EmptyFunctorOptimization) {
size_t size; size_t size;
size_t capacity; size_t capacity;
size_t growth_left; size_t growth_left;
void* infoz;
}; };
struct StatelessHash { struct StatelessHash {
size_t operator()(absl::string_view) const { return 0; } size_t operator()(absl::string_view) const { return 0; }
@ -1798,6 +1799,27 @@ TEST(TableDeathTest, EraseOfEndAsserts) {
EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()), kDeathMsg); EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()), kDeathMsg);
} }
TEST(RawHashSamplerTest, Sample) {
// Enable the feature even if the prod default is off.
SetHashtablezEnabled(true);
SetHashtablezSampleParameter(100);
auto& sampler = HashtablezSampler::Global();
size_t start_size = 0;
start_size += sampler.Iterate([&](const HashtablezInfo&) { ++start_size; });
std::vector<IntTable> tables;
for (int i = 0; i < 1000000; ++i) {
tables.emplace_back();
tables.back().insert(1);
}
size_t end_size = 0;
end_size += sampler.Iterate([&](const HashtablezInfo&) { ++end_size; });
EXPECT_NEAR((end_size - start_size) / static_cast<double>(tables.size()),
0.01, 0.005);
}
#ifdef ADDRESS_SANITIZER #ifdef ADDRESS_SANITIZER
TEST(Sanitizer, PoisoningUnused) { TEST(Sanitizer, PoisoningUnused) {
IntTable t; IntTable t;

View file

@ -12,13 +12,47 @@
#define ABSL_DEBUGGING_INTERNAL_STACKTRACE_GENERIC_INL_H_ #define ABSL_DEBUGGING_INTERNAL_STACKTRACE_GENERIC_INL_H_
#include <execinfo.h> #include <execinfo.h>
#include <atomic>
#include <cstring> #include <cstring>
#include "absl/debugging/stacktrace.h" #include "absl/debugging/stacktrace.h"
// Sometimes, we can try to get a stack trace from within a stack
// trace, because we don't block signals inside this code (which would be too
// expensive: the two extra system calls per stack trace do matter here).
// That can cause a self-deadlock.
// Protect against such reentrant call by failing to get a stack trace.
//
// We use __thread here because the code here is extremely low level -- it is
// called while collecting stack traces from within malloc and mmap, and thus
// can not call anything which might call malloc or mmap itself.
static __thread int recursive = 0;
// The stack trace function might be invoked very early in the program's
// execution (e.g. from the very first malloc if using tcmalloc). Also, the
// glibc implementation itself will trigger malloc the first time it is called.
// As such, we suppress usage of backtrace during this early stage of execution.
static std::atomic<bool> disable_stacktraces(true); // Disabled until healthy.
// Waiting until static initializers run seems to be late enough.
// This file is included into stacktrace.cc so this will only run once.
static int stacktraces_enabler = []() {
void* unused_stack[1];
// Force the first backtrace to happen early to get the one-time shared lib
// loading (allocation) out of the way. After the first call it is much safer
// to use backtrace from a signal handler if we crash somewhere later.
backtrace(unused_stack, 1);
disable_stacktraces.store(false, std::memory_order_relaxed);
return 0;
}();
template <bool IS_STACK_FRAMES, bool IS_WITH_CONTEXT> template <bool IS_STACK_FRAMES, bool IS_WITH_CONTEXT>
static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count, static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count,
const void *ucp, int *min_dropped_frames) { const void *ucp, int *min_dropped_frames) {
if (recursive || disable_stacktraces.load(std::memory_order_relaxed)) {
return 0;
}
++recursive;
static_cast<void>(ucp); // Unused. static_cast<void>(ucp); // Unused.
static const int kStackLength = 64; static const int kStackLength = 64;
void * stack[kStackLength]; void * stack[kStackLength];
@ -46,6 +80,8 @@ static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count,
} }
} }
--recursive;
return result_count; return result_count;
} }

View file

@ -35,17 +35,18 @@
#include "absl/strings/ascii.h" #include "absl/strings/ascii.h"
#include "absl/strings/charconv.h" #include "absl/strings/charconv.h"
#include "absl/strings/internal/memutil.h" #include "absl/strings/internal/memutil.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
namespace absl { namespace absl {
bool SimpleAtof(absl::string_view str, float* value) { bool SimpleAtof(absl::string_view str, float* out) {
*value = 0.0; *out = 0.0;
str = StripAsciiWhitespace(str); str = StripAsciiWhitespace(str);
if (!str.empty() && str[0] == '+') { if (!str.empty() && str[0] == '+') {
str.remove_prefix(1); str.remove_prefix(1);
} }
auto result = absl::from_chars(str.data(), str.data() + str.size(), *value); auto result = absl::from_chars(str.data(), str.data() + str.size(), *out);
if (result.ec == std::errc::invalid_argument) { if (result.ec == std::errc::invalid_argument) {
return false; return false;
} }
@ -56,22 +57,22 @@ bool SimpleAtof(absl::string_view str, float* value) {
// from_chars() with DR 3801's current wording will return max() on // from_chars() with DR 3801's current wording will return max() on
// overflow. SimpleAtof returns infinity instead. // overflow. SimpleAtof returns infinity instead.
if (result.ec == std::errc::result_out_of_range) { if (result.ec == std::errc::result_out_of_range) {
if (*value > 1.0) { if (*out > 1.0) {
*value = std::numeric_limits<float>::infinity(); *out = std::numeric_limits<float>::infinity();
} else if (*value < -1.0) { } else if (*out < -1.0) {
*value = -std::numeric_limits<float>::infinity(); *out = -std::numeric_limits<float>::infinity();
} }
} }
return true; return true;
} }
bool SimpleAtod(absl::string_view str, double* value) { bool SimpleAtod(absl::string_view str, double* out) {
*value = 0.0; *out = 0.0;
str = StripAsciiWhitespace(str); str = StripAsciiWhitespace(str);
if (!str.empty() && str[0] == '+') { if (!str.empty() && str[0] == '+') {
str.remove_prefix(1); str.remove_prefix(1);
} }
auto result = absl::from_chars(str.data(), str.data() + str.size(), *value); auto result = absl::from_chars(str.data(), str.data() + str.size(), *out);
if (result.ec == std::errc::invalid_argument) { if (result.ec == std::errc::invalid_argument) {
return false; return false;
} }
@ -82,10 +83,10 @@ bool SimpleAtod(absl::string_view str, double* value) {
// from_chars() with DR 3801's current wording will return max() on // from_chars() with DR 3801's current wording will return max() on
// overflow. SimpleAtod returns infinity instead. // overflow. SimpleAtod returns infinity instead.
if (result.ec == std::errc::result_out_of_range) { if (result.ec == std::errc::result_out_of_range) {
if (*value > 1.0) { if (*out > 1.0) {
*value = std::numeric_limits<double>::infinity(); *out = std::numeric_limits<double>::infinity();
} else if (*value < -1.0) { } else if (*out < -1.0) {
*value = -std::numeric_limits<double>::infinity(); *out = -std::numeric_limits<double>::infinity();
} }
} }
return true; return true;
@ -93,14 +94,6 @@ bool SimpleAtod(absl::string_view str, double* value) {
namespace { namespace {
// TODO(rogeeff): replace with the real released thing once we figure out what
// it is.
inline bool CaseEqual(absl::string_view piece1, absl::string_view piece2) {
return (piece1.size() == piece2.size() &&
0 == strings_internal::memcasecmp(piece1.data(), piece2.data(),
piece1.size()));
}
// Writes a two-character representation of 'i' to 'buf'. 'i' must be in the // Writes a two-character representation of 'i' to 'buf'. 'i' must be in the
// range 0 <= i < 100, and buf must have space for two characters. Example: // range 0 <= i < 100, and buf must have space for two characters. Example:
// char buf[2]; // char buf[2];
@ -136,18 +129,18 @@ inline void PutTwoDigits(size_t i, char* buf) {
} // namespace } // namespace
bool SimpleAtob(absl::string_view str, bool* value) { bool SimpleAtob(absl::string_view str, bool* out) {
ABSL_RAW_CHECK(value != nullptr, "Output pointer must not be nullptr."); ABSL_RAW_CHECK(out != nullptr, "Output pointer must not be nullptr.");
if (CaseEqual(str, "true") || CaseEqual(str, "t") || if (EqualsIgnoreCase(str, "true") || EqualsIgnoreCase(str, "t") ||
CaseEqual(str, "yes") || CaseEqual(str, "y") || EqualsIgnoreCase(str, "yes") || EqualsIgnoreCase(str, "y") ||
CaseEqual(str, "1")) { EqualsIgnoreCase(str, "1")) {
*value = true; *out = true;
return true; return true;
} }
if (CaseEqual(str, "false") || CaseEqual(str, "f") || if (EqualsIgnoreCase(str, "false") || EqualsIgnoreCase(str, "f") ||
CaseEqual(str, "no") || CaseEqual(str, "n") || EqualsIgnoreCase(str, "no") || EqualsIgnoreCase(str, "n") ||
CaseEqual(str, "0")) { EqualsIgnoreCase(str, "0")) {
*value = false; *out = false;
return true; return true;
} }
return false; return false;

View file

@ -44,7 +44,8 @@ namespace absl {
// Converts the given string into an integer value, returning `true` if // Converts the given string into an integer value, returning `true` if
// successful. The string must reflect a base-10 integer (optionally followed or // successful. The string must reflect a base-10 integer (optionally followed or
// preceded by ASCII whitespace) whose value falls within the range of the // preceded by ASCII whitespace) whose value falls within the range of the
// integer type. // integer type. If any errors are encountered, this function returns `false`,
// leaving `out` in an unspecified state.
template <typename int_type> template <typename int_type>
ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view s, int_type* out); ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view s, int_type* out);
@ -53,24 +54,28 @@ ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view s, int_type* out);
// Converts the given string (optionally followed or preceded by ASCII // Converts the given string (optionally followed or preceded by ASCII
// whitespace) into a float, which may be rounded on overflow or underflow. // whitespace) into a float, which may be rounded on overflow or underflow.
// See http://en.cppreference.com/w/c/string/byte/strtof for details about the // See http://en.cppreference.com/w/c/string/byte/strtof for details about the
// allowed formats for `str`. // allowed formats for `str`. If any errors are encountered, this function
ABSL_MUST_USE_RESULT bool SimpleAtof(absl::string_view str, float* value); // returns `false`, leaving `out` in an unspecified state.
ABSL_MUST_USE_RESULT bool SimpleAtof(absl::string_view str, float* out);
// SimpleAtod() // SimpleAtod()
// //
// Converts the given string (optionally followed or preceded by ASCII // Converts the given string (optionally followed or preceded by ASCII
// whitespace) into a double, which may be rounded on overflow or underflow. // whitespace) into a double, which may be rounded on overflow or underflow.
// See http://en.cppreference.com/w/c/string/byte/strtof for details about the // See http://en.cppreference.com/w/c/string/byte/strtof for details about the
// allowed formats for `str`. // allowed formats for `str`. If any errors are encountered, this function
ABSL_MUST_USE_RESULT bool SimpleAtod(absl::string_view str, double* value); // returns `false`, leaving `out` in an unspecified state.
ABSL_MUST_USE_RESULT bool SimpleAtod(absl::string_view str, double* out);
// SimpleAtob() // SimpleAtob()
// //
// Converts the given string into a boolean, returning `true` if successful. // Converts the given string into a boolean, returning `true` if successful.
// The following case-insensitive strings are interpreted as boolean `true`: // The following case-insensitive strings are interpreted as boolean `true`:
// "true", "t", "yes", "y", "1". The following case-insensitive strings // "true", "t", "yes", "y", "1". The following case-insensitive strings
// are interpreted as boolean `false`: "false", "f", "no", "n", "0". // are interpreted as boolean `false`: "false", "f", "no", "n", "0". If any
ABSL_MUST_USE_RESULT bool SimpleAtob(absl::string_view str, bool* value); // errors are encountered, this function returns `false`, leaving `out` in an
// unspecified state.
ABSL_MUST_USE_RESULT bool SimpleAtob(absl::string_view str, bool* out);
} // namespace absl } // namespace absl

View file

@ -149,15 +149,25 @@ char* FormatOffset(char* ep, int offset, const char* mode) {
offset = -offset; // bounded by 24h so no overflow offset = -offset; // bounded by 24h so no overflow
sign = '-'; sign = '-';
} }
char sep = mode[0]; const int seconds = offset % 60;
if (sep != '\0' && mode[1] == '*') { const int minutes = (offset /= 60) % 60;
ep = Format02d(ep, offset % 60); const int hours = offset /= 60;
const char sep = mode[0];
const bool ext = (sep != '\0' && mode[1] == '*');
const bool ccc = (ext && mode[2] == ':');
if (ext && (!ccc || seconds != 0)) {
ep = Format02d(ep, seconds);
*--ep = sep; *--ep = sep;
} else {
// If we're not rendering seconds, sub-minute negative offsets
// should get a positive sign (e.g., offset=-10s => "+00:00").
if (hours == 0 && minutes == 0) sign = '+';
} }
int minutes = offset / 60; if (!ccc || minutes != 0 || seconds != 0) {
ep = Format02d(ep, minutes % 60); ep = Format02d(ep, minutes);
if (sep != '\0') *--ep = sep; if (sep != '\0') *--ep = sep;
ep = Format02d(ep, minutes / 60); }
ep = Format02d(ep, hours);
*--ep = sign; *--ep = sign;
return ep; return ep;
} }
@ -384,6 +394,44 @@ std::string format(const std::string& format, const time_point<seconds>& tp,
continue; continue;
} }
// More complex specifiers that we handle ourselves.
if (*cur == ':' && cur + 1 != end) {
if (*(cur + 1) == 'z') {
// Formats %:z.
if (cur - 1 != pending) {
FormatTM(&result, std::string(pending, cur - 1), tm);
}
bp = FormatOffset(ep, al.offset, ":");
result.append(bp, static_cast<std::size_t>(ep - bp));
pending = cur += 2;
continue;
}
if (*(cur + 1) == ':' && cur + 2 != end) {
if (*(cur + 2) == 'z') {
// Formats %::z.
if (cur - 1 != pending) {
FormatTM(&result, std::string(pending, cur - 1), tm);
}
bp = FormatOffset(ep, al.offset, ":*");
result.append(bp, static_cast<std::size_t>(ep - bp));
pending = cur += 3;
continue;
}
if (*(cur + 2) == ':' && cur + 3 != end) {
if (*(cur + 3) == 'z') {
// Formats %:::z.
if (cur - 1 != pending) {
FormatTM(&result, std::string(pending, cur - 1), tm);
}
bp = FormatOffset(ep, al.offset, ":*:");
result.append(bp, static_cast<std::size_t>(ep - bp));
pending = cur += 4;
continue;
}
}
}
}
// Loop if there is no E modifier. // Loop if there is no E modifier.
if (*cur != 'E' || ++cur == end) continue; if (*cur != 'E' || ++cur == end) continue;
@ -668,17 +716,27 @@ bool parse(const std::string& format, const std::string& input,
&percent_s); &percent_s);
if (data != nullptr) saw_percent_s = true; if (data != nullptr) saw_percent_s = true;
continue; continue;
case ':':
if (fmt[0] == 'z' ||
(fmt[0] == ':' &&
(fmt[1] == 'z' || (fmt[1] == ':' && fmt[2] == 'z')))) {
data = ParseOffset(data, ":", &offset);
if (data != nullptr) saw_offset = true;
fmt += (fmt[0] == 'z') ? 1 : (fmt[1] == 'z') ? 2 : 3;
continue;
}
break;
case '%': case '%':
data = (*data == '%' ? data + 1 : nullptr); data = (*data == '%' ? data + 1 : nullptr);
continue; continue;
case 'E': case 'E':
if (*fmt == 'z' || (*fmt == '*' && *(fmt + 1) == 'z')) { if (fmt[0] == 'z' || (fmt[0] == '*' && fmt[1] == 'z')) {
data = ParseOffset(data, ":", &offset); data = ParseOffset(data, ":", &offset);
if (data != nullptr) saw_offset = true; if (data != nullptr) saw_offset = true;
fmt += (*fmt == 'z') ? 1 : 2; fmt += (fmt[0] == 'z') ? 1 : 2;
continue; continue;
} }
if (*fmt == '*' && *(fmt + 1) == 'S') { if (fmt[0] == '*' && fmt[1] == 'S') {
data = ParseInt(data, 2, 0, 60, &tm.tm_sec); data = ParseInt(data, 2, 0, 60, &tm.tm_sec);
if (data != nullptr && *data == '.') { if (data != nullptr && *data == '.') {
data = ParseSubSeconds(data + 1, &subseconds); data = ParseSubSeconds(data + 1, &subseconds);
@ -686,14 +744,14 @@ bool parse(const std::string& format, const std::string& input,
fmt += 2; fmt += 2;
continue; continue;
} }
if (*fmt == '*' && *(fmt + 1) == 'f') { if (fmt[0] == '*' && fmt[1] == 'f') {
if (data != nullptr && std::isdigit(*data)) { if (data != nullptr && std::isdigit(*data)) {
data = ParseSubSeconds(data, &subseconds); data = ParseSubSeconds(data, &subseconds);
} }
fmt += 2; fmt += 2;
continue; continue;
} }
if (*fmt == '4' && *(fmt + 1) == 'Y') { if (fmt[0] == '4' && fmt[1] == 'Y') {
const char* bp = data; const char* bp = data;
data = ParseInt(data, 4, year_t{-999}, year_t{9999}, &year); data = ParseInt(data, 4, year_t{-999}, year_t{9999}, &year);
if (data != nullptr) { if (data != nullptr) {

View file

@ -436,51 +436,165 @@ TEST(Format, CompareExtendSecondsVsSubseconds) {
} }
TEST(Format, ExtendedOffset) { TEST(Format, ExtendedOffset) {
auto tp = chrono::system_clock::from_time_t(0); const auto tp = chrono::system_clock::from_time_t(0);
time_zone tz = utc_time_zone(); auto tz = fixed_time_zone(absl::time_internal::cctz::seconds::zero());
TestFormatSpecifier(tp, tz, "%z", "+0000");
TestFormatSpecifier(tp, tz, "%:z", "+00:00");
TestFormatSpecifier(tp, tz, "%Ez", "+00:00"); TestFormatSpecifier(tp, tz, "%Ez", "+00:00");
EXPECT_TRUE(load_time_zone("America/New_York", &tz)); tz = fixed_time_zone(chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%Ez", "-05:00"); TestFormatSpecifier(tp, tz, "%z", "+0000");
TestFormatSpecifier(tp, tz, "%:z", "+00:00");
TestFormatSpecifier(tp, tz, "%Ez", "+00:00");
EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); tz = fixed_time_zone(-chrono::seconds(56)); // NOTE: +00:00
TestFormatSpecifier(tp, tz, "%Ez", "-08:00"); TestFormatSpecifier(tp, tz, "%z", "+0000");
TestFormatSpecifier(tp, tz, "%:z", "+00:00");
TestFormatSpecifier(tp, tz, "%Ez", "+00:00");
EXPECT_TRUE(load_time_zone("Australia/Sydney", &tz)); tz = fixed_time_zone(chrono::minutes(34));
TestFormatSpecifier(tp, tz, "%Ez", "+10:00"); TestFormatSpecifier(tp, tz, "%z", "+0034");
TestFormatSpecifier(tp, tz, "%:z", "+00:34");
TestFormatSpecifier(tp, tz, "%Ez", "+00:34");
EXPECT_TRUE(load_time_zone("Africa/Monrovia", &tz)); tz = fixed_time_zone(-chrono::minutes(34));
// The true offset is -00:44:30 but %z only gives (truncated) minutes. TestFormatSpecifier(tp, tz, "%z", "-0034");
TestFormatSpecifier(tp, tz, "%z", "-0044"); TestFormatSpecifier(tp, tz, "%:z", "-00:34");
TestFormatSpecifier(tp, tz, "%Ez", "-00:44"); TestFormatSpecifier(tp, tz, "%Ez", "-00:34");
tz = fixed_time_zone(chrono::minutes(34) + chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%z", "+0034");
TestFormatSpecifier(tp, tz, "%:z", "+00:34");
TestFormatSpecifier(tp, tz, "%Ez", "+00:34");
tz = fixed_time_zone(-chrono::minutes(34) - chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%z", "-0034");
TestFormatSpecifier(tp, tz, "%:z", "-00:34");
TestFormatSpecifier(tp, tz, "%Ez", "-00:34");
tz = fixed_time_zone(chrono::hours(12));
TestFormatSpecifier(tp, tz, "%z", "+1200");
TestFormatSpecifier(tp, tz, "%:z", "+12:00");
TestFormatSpecifier(tp, tz, "%Ez", "+12:00");
tz = fixed_time_zone(-chrono::hours(12));
TestFormatSpecifier(tp, tz, "%z", "-1200");
TestFormatSpecifier(tp, tz, "%:z", "-12:00");
TestFormatSpecifier(tp, tz, "%Ez", "-12:00");
tz = fixed_time_zone(chrono::hours(12) + chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%z", "+1200");
TestFormatSpecifier(tp, tz, "%:z", "+12:00");
TestFormatSpecifier(tp, tz, "%Ez", "+12:00");
tz = fixed_time_zone(-chrono::hours(12) - chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%z", "-1200");
TestFormatSpecifier(tp, tz, "%:z", "-12:00");
TestFormatSpecifier(tp, tz, "%Ez", "-12:00");
tz = fixed_time_zone(chrono::hours(12) + chrono::minutes(34));
TestFormatSpecifier(tp, tz, "%z", "+1234");
TestFormatSpecifier(tp, tz, "%:z", "+12:34");
TestFormatSpecifier(tp, tz, "%Ez", "+12:34");
tz = fixed_time_zone(-chrono::hours(12) - chrono::minutes(34));
TestFormatSpecifier(tp, tz, "%z", "-1234");
TestFormatSpecifier(tp, tz, "%:z", "-12:34");
TestFormatSpecifier(tp, tz, "%Ez", "-12:34");
tz = fixed_time_zone(chrono::hours(12) + chrono::minutes(34) +
chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%z", "+1234");
TestFormatSpecifier(tp, tz, "%:z", "+12:34");
TestFormatSpecifier(tp, tz, "%Ez", "+12:34");
tz = fixed_time_zone(-chrono::hours(12) - chrono::minutes(34) -
chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%z", "-1234");
TestFormatSpecifier(tp, tz, "%:z", "-12:34");
TestFormatSpecifier(tp, tz, "%Ez", "-12:34");
} }
TEST(Format, ExtendedSecondOffset) { TEST(Format, ExtendedSecondOffset) {
const time_zone utc = utc_time_zone(); const auto tp = chrono::system_clock::from_time_t(0);
time_point<chrono::seconds> tp;
time_zone tz;
EXPECT_TRUE(load_time_zone("America/New_York", &tz)); auto tz = fixed_time_zone(absl::time_internal::cctz::seconds::zero());
tp = convert(civil_second(1883, 11, 18, 16, 59, 59), utc); TestFormatSpecifier(tp, tz, "%E*z", "+00:00:00");
if (tz.lookup(tp).offset == -5 * 60 * 60) { TestFormatSpecifier(tp, tz, "%::z", "+00:00:00");
// It looks like the tzdata is only 32 bit (probably macOS), TestFormatSpecifier(tp, tz, "%:::z", "+00");
// which bottoms out at 1901-12-13T20:45:52+00:00.
} else {
TestFormatSpecifier(tp, tz, "%E*z", "-04:56:02");
TestFormatSpecifier(tp, tz, "%Ez", "-04:56");
}
tp += chrono::seconds(1);
TestFormatSpecifier(tp, tz, "%E*z", "-05:00:00");
EXPECT_TRUE(load_time_zone("Europe/Moscow", &tz)); tz = fixed_time_zone(chrono::seconds(56));
tp = convert(civil_second(1919, 6, 30, 23, 59, 59), utc); TestFormatSpecifier(tp, tz, "%E*z", "+00:00:56");
if (VersionCmp(tz, "2016g") >= 0) { TestFormatSpecifier(tp, tz, "%::z", "+00:00:56");
TestFormatSpecifier(tp, tz, "%E*z", "+04:31:19"); TestFormatSpecifier(tp, tz, "%:::z", "+00:00:56");
TestFormatSpecifier(tp, tz, "%Ez", "+04:31");
} tz = fixed_time_zone(-chrono::seconds(56));
tp += chrono::seconds(1); TestFormatSpecifier(tp, tz, "%E*z", "-00:00:56");
TestFormatSpecifier(tp, tz, "%E*z", "+04:00:00"); TestFormatSpecifier(tp, tz, "%::z", "-00:00:56");
TestFormatSpecifier(tp, tz, "%:::z", "-00:00:56");
tz = fixed_time_zone(chrono::minutes(34));
TestFormatSpecifier(tp, tz, "%E*z", "+00:34:00");
TestFormatSpecifier(tp, tz, "%::z", "+00:34:00");
TestFormatSpecifier(tp, tz, "%:::z", "+00:34");
tz = fixed_time_zone(-chrono::minutes(34));
TestFormatSpecifier(tp, tz, "%E*z", "-00:34:00");
TestFormatSpecifier(tp, tz, "%::z", "-00:34:00");
TestFormatSpecifier(tp, tz, "%:::z", "-00:34");
tz = fixed_time_zone(chrono::minutes(34) + chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%E*z", "+00:34:56");
TestFormatSpecifier(tp, tz, "%::z", "+00:34:56");
TestFormatSpecifier(tp, tz, "%:::z", "+00:34:56");
tz = fixed_time_zone(-chrono::minutes(34) - chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%E*z", "-00:34:56");
TestFormatSpecifier(tp, tz, "%::z", "-00:34:56");
TestFormatSpecifier(tp, tz, "%:::z", "-00:34:56");
tz = fixed_time_zone(chrono::hours(12));
TestFormatSpecifier(tp, tz, "%E*z", "+12:00:00");
TestFormatSpecifier(tp, tz, "%::z", "+12:00:00");
TestFormatSpecifier(tp, tz, "%:::z", "+12");
tz = fixed_time_zone(-chrono::hours(12));
TestFormatSpecifier(tp, tz, "%E*z", "-12:00:00");
TestFormatSpecifier(tp, tz, "%::z", "-12:00:00");
TestFormatSpecifier(tp, tz, "%:::z", "-12");
tz = fixed_time_zone(chrono::hours(12) + chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%E*z", "+12:00:56");
TestFormatSpecifier(tp, tz, "%::z", "+12:00:56");
TestFormatSpecifier(tp, tz, "%:::z", "+12:00:56");
tz = fixed_time_zone(-chrono::hours(12) - chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%E*z", "-12:00:56");
TestFormatSpecifier(tp, tz, "%::z", "-12:00:56");
TestFormatSpecifier(tp, tz, "%:::z", "-12:00:56");
tz = fixed_time_zone(chrono::hours(12) + chrono::minutes(34));
TestFormatSpecifier(tp, tz, "%E*z", "+12:34:00");
TestFormatSpecifier(tp, tz, "%::z", "+12:34:00");
TestFormatSpecifier(tp, tz, "%:::z", "+12:34");
tz = fixed_time_zone(-chrono::hours(12) - chrono::minutes(34));
TestFormatSpecifier(tp, tz, "%E*z", "-12:34:00");
TestFormatSpecifier(tp, tz, "%::z", "-12:34:00");
TestFormatSpecifier(tp, tz, "%:::z", "-12:34");
tz = fixed_time_zone(chrono::hours(12) + chrono::minutes(34) +
chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%E*z", "+12:34:56");
TestFormatSpecifier(tp, tz, "%::z", "+12:34:56");
TestFormatSpecifier(tp, tz, "%:::z", "+12:34:56");
tz = fixed_time_zone(-chrono::hours(12) - chrono::minutes(34) -
chrono::seconds(56));
TestFormatSpecifier(tp, tz, "%E*z", "-12:34:56");
TestFormatSpecifier(tp, tz, "%::z", "-12:34:56");
TestFormatSpecifier(tp, tz, "%:::z", "-12:34:56");
} }
TEST(Format, ExtendedYears) { TEST(Format, ExtendedYears) {
@ -1160,25 +1274,6 @@ TEST(Parse, ExtendedOffset) {
const time_zone utc = utc_time_zone(); const time_zone utc = utc_time_zone();
time_point<absl::time_internal::cctz::seconds> tp; time_point<absl::time_internal::cctz::seconds> tp;
// %z against +-HHMM.
EXPECT_TRUE(parse("%z", "+0000", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_TRUE(parse("%z", "-1234", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp);
EXPECT_TRUE(parse("%z", "+1234", utc, &tp));
EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp);
EXPECT_FALSE(parse("%z", "-123", utc, &tp));
// %z against +-HH.
EXPECT_TRUE(parse("%z", "+00", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_TRUE(parse("%z", "-12", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 0, 0), utc), tp);
EXPECT_TRUE(parse("%z", "+12", utc, &tp));
EXPECT_EQ(convert(civil_second(1969, 12, 31, 12, 0, 0), utc), tp);
EXPECT_FALSE(parse("%z", "-1", utc, &tp));
// %Ez against +-HH:MM.
EXPECT_TRUE(parse("%Ez", "+00:00", utc, &tp)); EXPECT_TRUE(parse("%Ez", "+00:00", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_TRUE(parse("%Ez", "-12:34", utc, &tp)); EXPECT_TRUE(parse("%Ez", "-12:34", utc, &tp));
@ -1187,91 +1282,70 @@ TEST(Parse, ExtendedOffset) {
EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp); EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp);
EXPECT_FALSE(parse("%Ez", "-12:3", utc, &tp)); EXPECT_FALSE(parse("%Ez", "-12:3", utc, &tp));
// %Ez against +-HHMM. for (auto fmt : {"%Ez", "%z"}) {
EXPECT_TRUE(parse("%Ez", "+0000", utc, &tp)); EXPECT_TRUE(parse(fmt, "+0000", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_TRUE(parse("%Ez", "-1234", utc, &tp)); EXPECT_TRUE(parse(fmt, "-1234", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp); EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp);
EXPECT_TRUE(parse("%Ez", "+1234", utc, &tp)); EXPECT_TRUE(parse(fmt, "+1234", utc, &tp));
EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp); EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp);
EXPECT_FALSE(parse("%Ez", "-123", utc, &tp)); EXPECT_FALSE(parse(fmt, "-123", utc, &tp));
// %Ez against +-HH. EXPECT_TRUE(parse(fmt, "+00", utc, &tp));
EXPECT_TRUE(parse("%Ez", "+00", utc, &tp)); EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(fmt, "-12", utc, &tp));
EXPECT_TRUE(parse("%Ez", "-12", utc, &tp)); EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 0, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 0, 0), utc), tp); EXPECT_TRUE(parse(fmt, "+12", utc, &tp));
EXPECT_TRUE(parse("%Ez", "+12", utc, &tp)); EXPECT_EQ(convert(civil_second(1969, 12, 31, 12, 0, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1969, 12, 31, 12, 0, 0), utc), tp); EXPECT_FALSE(parse(fmt, "-1", utc, &tp));
EXPECT_FALSE(parse("%Ez", "-1", utc, &tp)); }
} }
TEST(Parse, ExtendedSecondOffset) { TEST(Parse, ExtendedSecondOffset) {
const time_zone utc = utc_time_zone(); const time_zone utc = utc_time_zone();
time_point<absl::time_internal::cctz::seconds> tp; time_point<absl::time_internal::cctz::seconds> tp;
// %Ez against +-HH:MM:SS. for (auto fmt : {"%Ez", "%E*z", "%:z", "%::z", "%:::z"}) {
EXPECT_TRUE(parse("%Ez", "+00:00:00", utc, &tp)); EXPECT_TRUE(parse(fmt, "+00:00:00", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_TRUE(parse("%Ez", "-12:34:56", utc, &tp)); EXPECT_TRUE(parse(fmt, "-12:34:56", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 56), utc), tp); EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 56), utc), tp);
EXPECT_TRUE(parse("%Ez", "+12:34:56", utc, &tp)); EXPECT_TRUE(parse(fmt, "+12:34:56", utc, &tp));
EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 25, 4), utc), tp); EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 25, 4), utc), tp);
EXPECT_FALSE(parse("%Ez", "-12:34:5", utc, &tp)); EXPECT_FALSE(parse(fmt, "-12:34:5", utc, &tp));
// %Ez against +-HHMMSS. EXPECT_TRUE(parse(fmt, "+000000", utc, &tp));
EXPECT_TRUE(parse("%Ez", "+000000", utc, &tp)); EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(fmt, "-123456", utc, &tp));
EXPECT_TRUE(parse("%Ez", "-123456", utc, &tp)); EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 56), utc), tp);
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 56), utc), tp); EXPECT_TRUE(parse(fmt, "+123456", utc, &tp));
EXPECT_TRUE(parse("%Ez", "+123456", utc, &tp)); EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 25, 4), utc), tp);
EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 25, 4), utc), tp); EXPECT_FALSE(parse(fmt, "-12345", utc, &tp));
EXPECT_FALSE(parse("%Ez", "-12345", utc, &tp));
// %E*z against +-HH:MM:SS. EXPECT_TRUE(parse(fmt, "+00:00", utc, &tp));
EXPECT_TRUE(parse("%E*z", "+00:00:00", utc, &tp)); EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(fmt, "-12:34", utc, &tp));
EXPECT_TRUE(parse("%E*z", "-12:34:56", utc, &tp)); EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 56), utc), tp); EXPECT_TRUE(parse(fmt, "+12:34", utc, &tp));
EXPECT_TRUE(parse("%E*z", "+12:34:56", utc, &tp)); EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 25, 4), utc), tp); EXPECT_FALSE(parse(fmt, "-12:3", utc, &tp));
EXPECT_FALSE(parse("%E*z", "-12:34:5", utc, &tp));
// %E*z against +-HHMMSS. EXPECT_TRUE(parse(fmt, "+0000", utc, &tp));
EXPECT_TRUE(parse("%E*z", "+000000", utc, &tp)); EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(fmt, "-1234", utc, &tp));
EXPECT_TRUE(parse("%E*z", "-123456", utc, &tp)); EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 56), utc), tp); EXPECT_TRUE(parse(fmt, "+1234", utc, &tp));
EXPECT_TRUE(parse("%E*z", "+123456", utc, &tp)); EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 25, 4), utc), tp); EXPECT_FALSE(parse(fmt, "-123", utc, &tp));
EXPECT_FALSE(parse("%E*z", "-12345", utc, &tp));
// %E*z against +-HH:MM. EXPECT_TRUE(parse(fmt, "+00", utc, &tp));
EXPECT_TRUE(parse("%E*z", "+00:00", utc, &tp)); EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp); EXPECT_TRUE(parse(fmt, "-12", utc, &tp));
EXPECT_TRUE(parse("%E*z", "-12:34", utc, &tp)); EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 0, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp); EXPECT_TRUE(parse(fmt, "+12", utc, &tp));
EXPECT_TRUE(parse("%E*z", "+12:34", utc, &tp)); EXPECT_EQ(convert(civil_second(1969, 12, 31, 12, 0, 0), utc), tp);
EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp); EXPECT_FALSE(parse(fmt, "-1", utc, &tp));
EXPECT_FALSE(parse("%E*z", "-12:3", utc, &tp)); }
// %E*z against +-HHMM.
EXPECT_TRUE(parse("%E*z", "+0000", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_TRUE(parse("%E*z", "-1234", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 34, 0), utc), tp);
EXPECT_TRUE(parse("%E*z", "+1234", utc, &tp));
EXPECT_EQ(convert(civil_second(1969, 12, 31, 11, 26, 0), utc), tp);
EXPECT_FALSE(parse("%E*z", "-123", utc, &tp));
// %E*z against +-HH.
EXPECT_TRUE(parse("%E*z", "+00", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 0, 0, 0), utc), tp);
EXPECT_TRUE(parse("%E*z", "-12", utc, &tp));
EXPECT_EQ(convert(civil_second(1970, 1, 1, 12, 0, 0), utc), tp);
EXPECT_TRUE(parse("%E*z", "+12", utc, &tp));
EXPECT_EQ(convert(civil_second(1969, 12, 31, 12, 0, 0), utc), tp);
EXPECT_FALSE(parse("%E*z", "-1", utc, &tp));
} }
TEST(Parse, ExtendedYears) { TEST(Parse, ExtendedYears) {