| // Copyright 2025 The Abseil Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may | |
| // obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "absl/profiling/hashtable.h" | |
| #include <atomic> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/base/casts.h" | |
| #include "absl/base/config.h" | |
| #include "absl/container/internal/hashtablez_sampler.h" | |
| #include "absl/profiling/internal/profile_builder.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/time/clock.h" | |
| #include "absl/time/time.h" | |
| #include "absl/types/span.h" | |
| namespace absl { | |
| ABSL_NAMESPACE_BEGIN | |
| StatusOr<std::string> MarshalHashtableProfile() { | |
| return debugging_internal::MarshalHashtableProfile( | |
| container_internal::GlobalHashtablezSampler(), Now()); | |
| } | |
| namespace debugging_internal { | |
| static void DroppedHashtableSample() {} | |
| StatusOr<std::string> MarshalHashtableProfile( | |
| container_internal::HashtablezSampler& sampler, Time now) { | |
| static constexpr absl::string_view kDropFrames = | |
| "(::)?absl::container_internal::.*|" | |
| "(::)?absl::(flat|node)_hash_(map|set).*"; | |
| ProfileBuilder builder; | |
| StringId drop_frames_id = builder.InternString(kDropFrames); | |
| builder.set_drop_frames_id(drop_frames_id); | |
| builder.AddSampleType(builder.InternString("capacity"), | |
| builder.InternString("count")); | |
| builder.set_default_sample_type_id(builder.InternString("capacity")); | |
| const auto capacity_id = builder.InternString("capacity"); | |
| const auto size_id = builder.InternString("size"); | |
| const auto num_erases_id = builder.InternString("num_erases"); | |
| const auto num_rehashes_id = builder.InternString("num_rehashes"); | |
| const auto max_probe_length_id = builder.InternString("max_probe_length"); | |
| const auto total_probe_length_id = builder.InternString("total_probe_length"); | |
| const auto stuck_bits_id = builder.InternString("stuck_bits"); | |
| const auto inline_element_size_id = | |
| builder.InternString("inline_element_size"); | |
| const auto key_size_id = builder.InternString("key_size"); | |
| const auto value_size_id = builder.InternString("value_size"); | |
| const auto soo_capacity_id = builder.InternString("soo_capacity"); | |
| const auto table_age_id = builder.InternString("table_age"); | |
| const auto max_reserve_id = builder.InternString("max_reserve"); | |
| size_t dropped = | |
| sampler.Iterate([&](const container_internal::HashtablezInfo& info) { | |
| const size_t capacity = info.capacity.load(std::memory_order_relaxed); | |
| std::vector<std::pair<StringId, int64_t>> labels; | |
| auto add_label = [&](StringId tag, uint64_t value) { | |
| if (value == 0) { | |
| return; | |
| } | |
| labels.emplace_back(tag, static_cast<int64_t>(value)); | |
| }; | |
| add_label(capacity_id, capacity); | |
| add_label(size_id, info.size.load(std::memory_order_relaxed)); | |
| add_label(num_erases_id, | |
| info.num_erases.load(std::memory_order_relaxed)); | |
| add_label(num_rehashes_id, | |
| info.num_rehashes.load(std::memory_order_relaxed)); | |
| add_label(max_probe_length_id, | |
| info.max_probe_length.load(std::memory_order_relaxed)); | |
| add_label(total_probe_length_id, | |
| info.total_probe_length.load(std::memory_order_relaxed)); | |
| add_label(stuck_bits_id, | |
| (info.hashes_bitwise_and.load(std::memory_order_relaxed) | | |
| ~info.hashes_bitwise_or.load(std::memory_order_relaxed))); | |
| add_label(inline_element_size_id, info.inline_element_size); | |
| add_label(key_size_id, info.key_size); | |
| add_label(value_size_id, info.value_size); | |
| add_label(soo_capacity_id, info.soo_capacity); | |
| add_label( | |
| table_age_id, | |
| static_cast<uint64_t>(ToInt64Microseconds(now - info.create_time))); | |
| add_label(max_reserve_id, | |
| info.max_reserve.load(std::memory_order_relaxed)); | |
| builder.AddSample(static_cast<int64_t>(capacity) * info.weight, | |
| MakeSpan(info.stack, info.depth), labels); | |
| }); | |
| if (dropped > 0) { | |
| // If we dropped samples, we don't have information for them, including | |
| // their sizes. The non-zero weight allows it to be noticed in the profile | |
| // and examined more closely. | |
| // | |
| // We compensate for the fixup done by AddSample by adjusting the address | |
| // here. | |
| const void* kFakeStack[] = { | |
| absl::bit_cast<void*>( | |
| reinterpret_cast<uintptr_t>(&DroppedHashtableSample)+1)}; | |
| builder.AddSample(static_cast<int64_t>(dropped), kFakeStack, {}); | |
| } | |
| builder.AddCurrentMappings(); | |
| return std::move(builder).Emit(); | |
| } | |
| } // namespace debugging_internal | |
| ABSL_NAMESPACE_END | |
| } // namespace absl |