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:
parent
3e2e9b5557
commit
968a34ffda
16 changed files with 1313 additions and 348 deletions
|
@ -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()
|
||||||
|
|
3
LICENSE
3
LICENSE
|
@ -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
1
LTS.md
|
@ -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
17
absl/UPGRADES.md
Normal 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/
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
289
absl/container/internal/hashtablez_sampler.cc
Normal file
289
absl/container/internal/hashtablez_sampler.cc
Normal 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
|
236
absl/container/internal/hashtablez_sampler.h
Normal file
236
absl/container/internal/hashtablez_sampler.h
Normal 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_
|
307
absl/container/internal/hashtablez_sampler_test.cc
Normal file
307
absl/container/internal/hashtablez_sampler_test.cc
Normal 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
|
|
@ -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{}};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue