| // Protocol Buffers - Google's data interchange format |
| // Copyright 2023 Google Inc. All rights reserved. |
| // |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file or at |
| // https://developers.google.com/open-source/licenses/bsd |
| // |
| #include "google/protobuf/port.h" |
| |
| #include <array> |
| #include <atomic> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <map> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/base/attributes.h" |
| #include "absl/cleanup/cleanup.h" |
| #include "absl/log/absl_log.h" |
| #include "absl/numeric/int128.h" |
| #include "absl/strings/numbers.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/strings/string_view.h" |
| |
| // Must be included last |
| #include "google/protobuf/port_def.inc" |
| |
| |
| namespace google { |
| namespace protobuf { |
| namespace internal { |
| |
| size_t StringSpaceUsedExcludingSelfLong(const std::string& str) { |
| const void* start = &str; |
| const void* end = &str + 1; |
| if (start <= str.data() && str.data() < end) { |
| // The string's data is stored inside the string object itself. |
| return 0; |
| } else { |
| return str.capacity() + /* NULL terminator */ 1; |
| } |
| } |
| |
| // protobuf_assumption_failed() is declared and used in port_def.inc to assert |
| // PROTOBUF_ASSUME conditions in debug mode. This function avoids having |
| // port_def.inc depend on assert.h or other headers, minimizing the compilation |
| // footprint. |
| void protobuf_assumption_failed(const char* pred, const char* file, int line) { |
| fprintf(stderr, "%s: %d: Assumption failed: '%s'\n", file, line, pred); |
| abort(); |
| } |
| |
| static void PrintAllCounters(); |
| static auto& CounterMap() { |
| using Map = std::map< |
| absl::string_view, |
| std::map<std::variant<int64_t, absl::string_view>, |
| std::array<std::atomic<size_t>, RealDebugCounter::kNumBuckets>>>; |
| static auto* counter_map = new Map{}; |
| static auto print [[maybe_unused]] = absl::Cleanup(PrintAllCounters); |
| return *counter_map; |
| } |
| |
| static std::string CreateHistogram( |
| absl::string_view category_name, absl::string_view subname, size_t subtotal, |
| absl::Span<const std::atomic<size_t>> buckets) { |
| std::string result; |
| absl::StrAppendFormat(&result, "%s.%s:\n", category_name, subname); |
| size_t sub = 0; |
| for (size_t i = 0; i < buckets.size(); ++i) { |
| if (sub == subtotal) { |
| // No more buckets to print. |
| break; |
| } |
| size_t v = buckets[i].load(std::memory_order_relaxed); |
| if (sub == 0 && v == 0) { |
| // Skip until the first non-zero bucket. |
| continue; |
| } |
| |
| sub += v; |
| double num_chars = v * 100.0 / subtotal; |
| static constexpr std::array kBlocks = {"▏", "▎", "▍", "▋", "▊", "▉"}; |
| std::string chars; |
| while (num_chars > 1) { |
| chars += kBlocks.back(); |
| --num_chars; |
| } |
| int last_char = std::round(num_chars * kBlocks.size()); |
| if (last_char > 0) { |
| chars += kBlocks[last_char - 1]; |
| } |
| |
| absl::StrAppendFormat(&result, "[%2d]:%s\n", i, chars); |
| } |
| return result; |
| } |
| |
| static void PrintAllCounters() { |
| auto& counters = CounterMap(); |
| if (counters.empty()) return; |
| absl::FPrintF(stderr, "Protobuf debug counters:\n"); |
| |
| std::vector<std::string> histograms; |
| for (auto& [category_name, category_map] : counters) { |
| // Example output: |
| // |
| // Category : |
| // Value 1 : 1234 (12.34%) |
| // Value 2 : 2345 (23.45%) |
| // Total : 3579 |
| absl::FPrintF(stderr, " %-12s:\n", category_name); |
| size_t total = 0; |
| for (auto& entry : category_map) { |
| size_t subtotal = 0; |
| for (auto& counter : entry.second) { |
| subtotal += counter.load(std::memory_order_relaxed); |
| } |
| if (subtotal != entry.second[0].load(std::memory_order_relaxed)) { |
| histograms.push_back( |
| CreateHistogram(category_name, |
| std::holds_alternative<int64_t>(entry.first) |
| ? absl::StrCat(std::get<int64_t>(entry.first)) |
| : std::get<absl::string_view>(entry.first), |
| subtotal, entry.second)); |
| } |
| total += subtotal; |
| } |
| |
| for (auto& [subname, counter_array] : category_map) { |
| size_t value = 0; |
| for (auto& counter : counter_array) { |
| value += counter.load(std::memory_order_relaxed); |
| } |
| if (std::holds_alternative<int64_t>(subname)) { |
| // For integers, right align |
| absl::FPrintF(stderr, " %9d : %10zu", std::get<int64_t>(subname), |
| value); |
| } else { |
| // For strings, left align |
| absl::FPrintF(stderr, " %-10s: %10zu", |
| std::get<absl::string_view>(subname), value); |
| } |
| if (total != 0 && category_map.size() > 1) { |
| absl::FPrintF( |
| stderr, " (%5.2f%%)", |
| 100. * static_cast<double>(value) / static_cast<double>(total)); |
| } |
| absl::FPrintF(stderr, "\n"); |
| } |
| if (total != 0 && category_map.size() > 1) { |
| absl::FPrintF(stderr, " %-10s: %10zu\n", "Total", total); |
| } |
| } |
| |
| if (!histograms.empty()) { |
| absl::FPrintF(stderr, "---Histograms---\n"); |
| for (const auto& h : histograms) { |
| absl::FPrintF(stderr, "%s\n", h); |
| } |
| } |
| } |
| |
| void RealDebugCounter::Register(absl::string_view name) { |
| std::pair<absl::string_view, absl::string_view> parts = |
| absl::StrSplit(name, '.'); |
| int64_t as_int; |
| if (absl::SimpleAtoi(parts.second, &as_int)) { |
| counters_ = CounterMap()[parts.first][as_int].data(); |
| } else { |
| counters_ = CounterMap()[parts.first][parts.second].data(); |
| } |
| } |
| |
| PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT |
| PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 GlobalEmptyString |
| fixed_address_empty_string{}; |
| |
| void HandleAddOverflow(absl::int128 a, absl::int128 b) { |
| ABSL_LOG(FATAL) << "Integer overflow in CheckedAdd: " << a << " + " << b; |
| } |
| |
| } // namespace internal |
| } // namespace protobuf |
| } // namespace google |
| |
| #include "google/protobuf/port_undef.inc" |