Add DebugCounter utility for measuring internal behavior of the

runtime that does not change observable behavior of the APIs. For example, fast-vs-slow path decisions.
Has to be turned on at compile time, and otherwise it is a noop.

PiperOrigin-RevId: 648412330
pull/17263/head
Protobuf Team Bot 8 months ago committed by Copybara-Service
parent a3a49d0620
commit 685df5d4db
  1. 5
      src/google/protobuf/BUILD.bazel
  2. 90
      src/google/protobuf/debug_counter_test.cc
  3. 60
      src/google/protobuf/port.cc
  4. 36
      src/google/protobuf/port.h
  5. 19
      src/google/protobuf/port_def.inc
  6. 1
      src/google/protobuf/port_undef.inc

@ -257,7 +257,11 @@ cc_library(
deps = [
"@com_google_absl//absl/base:config",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/base:prefetch",
"@com_google_absl//absl/meta:type_traits",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:optional",
],
)
@ -498,7 +502,6 @@ cc_library(
"message_lite.h",
"metadata_lite.h",
"parse_context.h",
"port.h",
"raw_ptr.h",
"repeated_field.h",
"repeated_ptr_field.h",

@ -0,0 +1,90 @@
// 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 <cstdlib>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "google/protobuf/port.h"
// Must be included last.
#include "google/protobuf/port_def.inc"
namespace {
using testing::AllOf;
using testing::ExitedWithCode;
using testing::HasSubstr;
using testing::Not;
auto MatchOutput(bool expect_output) {
const auto header = HasSubstr("Protobuf debug counters:");
const auto foo = HasSubstr("Foo :");
const auto bar = HasSubstr("Bar : 1 (33.33%)");
const auto baz = HasSubstr("Baz : 2 (66.67%)");
const auto total = HasSubstr("Total : 3");
return expect_output ? testing::Matcher<const std::string&>(
AllOf(header, foo, bar, baz, total))
: testing::Matcher<const std::string&>(Not(header));
}
#ifdef GTEST_HAS_DEATH_TEST
TEST(DebugCounterTest, RealProvidesReportAtExit) {
EXPECT_EXIT(
{
static google::protobuf::internal::RealDebugCounter counter1("Foo.Bar");
static google::protobuf::internal::RealDebugCounter counter2("Foo.Baz");
counter1.Inc();
counter2.Inc();
counter2.Inc();
exit(0);
},
ExitedWithCode(0), MatchOutput(true));
}
TEST(DebugCounterTest, NoopDoesNotProvidesReportAtExit) {
EXPECT_EXIT(
{
static google::protobuf::internal::NoopDebugCounter counter1;
static google::protobuf::internal::NoopDebugCounter counter2;
counter1.Inc();
counter2.Inc();
counter2.Inc();
exit(0);
},
ExitedWithCode(0), MatchOutput(false));
// and test that the operations have no side effects.
static_assert((google::protobuf::internal::NoopDebugCounter().Inc(), true), "");
}
TEST(DebugCounterTest, MacroProvidesReportAtExitDependingOnBuild) {
#if defined(PROTOBUF_INTERNAL_ENABLE_DEBUG_COUNTERS)
constexpr bool match_output = true;
#else
constexpr bool match_output = false;
// and test that the operations have no side effects.
static_assert((PROTOBUF_DEBUG_COUNTER("Foo.Bar").Inc(), true), "");
#endif
EXPECT_EXIT(
{
PROTOBUF_DEBUG_COUNTER("Foo.Bar").Inc();
for (int i = 0; i < 2; ++i) {
PROTOBUF_DEBUG_COUNTER("Foo.Baz").Inc();
}
exit(0);
},
ExitedWithCode(0), MatchOutput(match_output));
}
#endif // GTEST_HAS_DEATH_TEST
} // namespace
#include "google/protobuf/port_undef.inc"

@ -5,8 +5,16 @@
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
//
#include <stdio.h>
#include <stdlib.h>
#include "google/protobuf/port.h"
#include <cstdio>
#include <cstdlib>
#include <map>
#include <utility>
#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"
@ -24,6 +32,54 @@ void protobuf_assumption_failed(const char* pred, const char* file, int line) {
abort();
}
static void PrintAllCounters();
static auto& CounterMap() {
using Map = std::map<absl::string_view,
std::map<absl::string_view, const RealDebugCounter*>>;
static auto* counter_map = new Map{};
static bool dummy = std::atexit(PrintAllCounters);
(void)dummy;
return *counter_map;
}
static void PrintAllCounters() {
auto& counters = CounterMap();
if (counters.empty()) return;
absl::FPrintF(stderr, "Protobuf debug counters:\n");
for (auto& category : counters) {
// Example output:
//
// Category :
// Value 1 : 1234 (12.34%)
// Value 2 : 2345 (23.45%)
// Total : 3579
absl::FPrintF(stderr, " %-12s:\n", category.first);
size_t total = 0;
for (auto& count : category.second) {
total += count.second->value();
}
for (auto& count : category.second) {
size_t value = count.second->value();
absl::FPrintF(stderr, " %-10s: %10zu", count.first, value);
if (total != 0 && category.second.size() > 1) {
absl::FPrintF(
stderr, " (%5.2f%%)",
100. * static_cast<double>(value) / static_cast<double>(total));
}
absl::FPrintF(stderr, "\n");
}
if (total != 0 && category.second.size() > 1) {
absl::FPrintF(stderr, " %-10s: %10zu\n", "Total", total);
}
}
}
void RealDebugCounter::Register(absl::string_view name) {
std::pair<absl::string_view, absl::string_view> parts =
absl::StrSplit(name, '.');
CounterMap()[parts.first][parts.second] = this;
}
} // namespace internal
} // namespace protobuf
} // namespace google

@ -13,6 +13,7 @@
#ifndef GOOGLE_PROTOBUF_PORT_H__
#define GOOGLE_PROTOBUF_PORT_H__
#include <atomic>
#include <cassert>
#include <cstddef>
#include <cstdint>
@ -326,6 +327,41 @@ inline void PrefetchToLocalCache(const void* ptr) {
constexpr bool IsOss() { return true; }
// Counter library for debugging internal protobuf logic.
// It allows instrumenting code that has different options (eg fast vs slow
// path) to get visibility into how much we are hitting each path.
// When compiled with -DPROTOBUF_INTERNAL_ENABLE_DEBUG_COUNTERS, the counters
// register an atexit handler to dump the table. Otherwise, they are a noop and
// have not runtime cost.
//
// Usage:
//
// if (do_fast) {
// PROTOBUF_DEBUG_COUNTER("Foo.Fast").Inc();
// ...
// } else {
// PROTOBUF_DEBUG_COUNTER("Foo.Slow").Inc();
// ...
// }
class PROTOBUF_EXPORT RealDebugCounter {
public:
explicit RealDebugCounter(absl::string_view name) { Register(name); }
// Lossy increment.
void Inc() { counter_.store(value() + 1, std::memory_order_relaxed); }
size_t value() const { return counter_.load(std::memory_order_relaxed); }
private:
void Register(absl::string_view name);
std::atomic<size_t> counter_{};
};
// When the feature is not enabled, the type is a noop.
class NoopDebugCounter {
public:
explicit constexpr NoopDebugCounter() = default;
constexpr void Inc() {}
};
} // namespace internal
} // namespace protobuf
} // namespace google

@ -987,3 +987,22 @@ namespace internal {
#define PROTOBUF_DEPRECATE_AND_INLINE() [[deprecated]]
#endif
namespace google {
namespace protobuf {
namespace internal {
#if defined(PROTOBUF_INTERNAL_ENABLE_DEBUG_COUNTERS)
#define PROTOBUF_DEBUG_COUNTER(name) \
([]() -> auto & { \
static constexpr const char *counter_name = name; \
static ::google::protobuf::internal::RealDebugCounter counter(counter_name); \
return counter; \
}())
#else // PROTOBUF_ENABLE_DEBUG_COUNTERS
#define PROTOBUF_DEBUG_COUNTER(name) \
::google::protobuf::internal::NoopDebugCounter {}
#endif // PROTOBUF_ENABLE_DEBUG_COUNTERS
} // namespace internal
} // namespace protobuf
} // namespace google

@ -75,6 +75,7 @@
#undef PROTOBUF_DESCRIPTOR_WEAK_MESSAGES_ALLOWED
#undef PROTOBUF_PREFETCH_PARSE_TABLE
#undef PROTOBUF_PREFETCH_WITH_OFFSET
#undef PROTOBUF_DEBUG_COUNTER
#undef PROTOBUF_TC_PARAM_DECL
#undef PROTOBUF_DEBUG
#undef PROTOBUF_NO_THREADLOCAL

Loading…
Cancel
Save