Add support for UMA
Adds HistogramMacros.h, inspired by the same header in ANGLE (and
Chromium). Platform implementation is no-op in Dawn standalone,
but will be implemented to hook into UMA in Chromium.
Bug: dawn:1087
Change-Id: Ibd2c62b069d303cc9f7fa0f1db454a4abf1c6ad7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/139262
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/include/dawn/platform/DawnPlatform.h b/include/dawn/platform/DawnPlatform.h
index 0645441..4d215bc 100644
--- a/include/dawn/platform/DawnPlatform.h
+++ b/include/dawn/platform/DawnPlatform.h
@@ -93,6 +93,22 @@
const uint64_t* argValues,
unsigned char flags);
+ // Invoked to add a UMA histogram count-based sample
+ virtual void HistogramCustomCounts(const char* name,
+ int sample,
+ int min,
+ int max,
+ int bucketCount);
+
+ // Invoked to add a UMA histogram enumeration sample
+ virtual void HistogramEnumeration(const char* name, int sample, int boundaryValue);
+
+ // Invoked to add a UMA histogram sparse sample
+ virtual void HistogramSparse(const char* name, int sample);
+
+ // Invoked to add a UMA histogram boolean sample
+ virtual void HistogramBoolean(const char* name, bool sample);
+
// The returned CachingInterface is expected to outlive the device which uses it to persistently
// cache objects.
virtual CachingInterface* GetCachingInterface();
diff --git a/src/dawn/platform/BUILD.gn b/src/dawn/platform/BUILD.gn
index c2c8144..df6679f 100644
--- a/src/dawn/platform/BUILD.gn
+++ b/src/dawn/platform/BUILD.gn
@@ -27,6 +27,7 @@
"DawnPlatform.cpp",
"WorkerThread.cpp",
"WorkerThread.h",
+ "metrics/HistogramMacros.h",
"tracing/EventTracer.cpp",
"tracing/EventTracer.h",
"tracing/TraceEvent.h",
diff --git a/src/dawn/platform/CMakeLists.txt b/src/dawn/platform/CMakeLists.txt
index d9625a7..d3a5ddf 100644
--- a/src/dawn/platform/CMakeLists.txt
+++ b/src/dawn/platform/CMakeLists.txt
@@ -26,6 +26,7 @@
"DawnPlatform.cpp"
"WorkerThread.cpp"
"WorkerThread.h"
+ "metrics/HistogramMacros.h"
"tracing/EventTracer.cpp"
"tracing/EventTracer.h"
"tracing/TraceEvent.h"
diff --git a/src/dawn/platform/DawnPlatform.cpp b/src/dawn/platform/DawnPlatform.cpp
index 3b22ade..1c6ce03 100644
--- a/src/dawn/platform/DawnPlatform.cpp
+++ b/src/dawn/platform/DawnPlatform.cpp
@@ -53,6 +53,18 @@
return 0;
}
+void Platform::HistogramCustomCounts(const char* name,
+ int sample,
+ int min,
+ int max,
+ int bucketCount) {}
+
+void Platform::HistogramEnumeration(const char* name, int sample, int boundaryValue) {}
+
+void Platform::HistogramSparse(const char* name, int sample) {}
+
+void Platform::HistogramBoolean(const char* name, bool sample) {}
+
dawn::platform::CachingInterface* Platform::GetCachingInterface() {
return nullptr;
}
diff --git a/src/dawn/platform/metrics/HistogramMacros.h b/src/dawn/platform/metrics/HistogramMacros.h
new file mode 100644
index 0000000..642a317
--- /dev/null
+++ b/src/dawn/platform/metrics/HistogramMacros.h
@@ -0,0 +1,165 @@
+// Copyright 2023 The Dawn 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 header provides macros for adding Chromium UMA histogram stats.
+// See the detailed description in the Chromium codebase:
+// https://source.chromium.org/chromium/chromium/src/+/main:base/metrics/histogram_macros.h
+
+#ifndef SRC_DAWN_PLATFORM_METRICS_HISTOGRAM_MACROS_H_
+#define SRC_DAWN_PLATFORM_METRICS_HISTOGRAM_MACROS_H_
+
+#include "dawn/platform/DawnPlatform.h"
+
+// Short timings - up to 10 seconds.
+#define DAWN_HISTOGRAM_TIMES(platform, name, sample_ms) \
+ DAWN_HISTOGRAM_CUSTOM_TIMES(platform, name, sample_ms, 1, 10'000, 50)
+
+// Medium timings - up to 3 minutes. Note this starts at 10ms (no good reason,
+// but not worth changing).
+#define DAWN_HISTOGRAM_MEDIUM_TIMES(platform, name, sample_ms) \
+ DAWN_HISTOGRAM_CUSTOM_TIMES(platform, name, sample_ms, 10, 180'000, 50)
+
+// Long timings - up to an hour.
+#define DAWN_HISTOGRAM_LONG_TIMES(platform, name, sample_ms) \
+ DAWN_HISTOGRAM_CUSTOM_TIMES(platform, name, sample_ms, 1, 3'600'000, 50)
+
+// Use this macro when times can routinely be much longer than 10 seconds and
+#define DAWN_HISTOGRAM_LONG_TIMES_100(platform, name, sample_ms) \
+ DAWN_HISTOGRAM_CUSTOM_TIMES(platform, name, sample_ms, 1, 3'600'000, 100)
+
+// This can be used when the default ranges are not sufficient. This macro lets
+// the metric developer customize the min and max of the sampled range, as well
+// as the number of buckets recorded.
+#define DAWN_HISTOGRAM_CUSTOM_TIMES(platform, name, sample_ms, min, max, bucket_count) \
+ DAWN_HISTOGRAM_CUSTOM_COUNTS(platform, name, sample_ms, min, max, bucket_count)
+
+//------------------------------------------------------------------------------
+// Count histograms. These are used for collecting numeric data. Note that we
+// have macros for more specialized use cases below (memory, time, percentages).
+
+// The number suffixes here refer to the max size of the sample, i.e. COUNT_1000
+// will be able to collect samples of counts up to 1000. The default number of
+// buckets in all default macros is 50. We recommend erring on the side of too
+// large a range versus too short a range.
+// These macros default to exponential histograms - i.e. the lengths of the
+// bucket ranges exponentially increase as the sample range increases.
+// These should *not* be used if you are interested in exact counts, i.e. a
+// bucket range of 1. In these cases, you should use the ENUMERATION macros
+// defined later. These should also not be used to capture the number of some
+// event, i.e. "button X was clicked N times". In this cases, an enum should be
+// used, ideally with an appropriate baseline enum entry included.
+// All of these macros must be called with |name| as a runtime constant.
+
+// Used for capturing generic numeric data, from 1 to 1 million.
+#define DAWN_HISTOGRAM_COUNTS(platform, name, sample) \
+ DAWN_HISTOGRAM_CUSTOM_COUNTS(platform, name, sample, 1, 1'000'000, 50)
+
+// Used for capturing generic numeric data, from 1 to 100.
+#define DAWN_HISTOGRAM_COUNTS_100(platform, name, sample) \
+ DAWN_HISTOGRAM_CUSTOM_COUNTS(platform, name, sample, 1, 100, 50)
+
+// Used for capturing generic numeric data, from 1 to 10,000.
+#define DAWN_HISTOGRAM_COUNTS_10000(platform, name, sample) \
+ DAWN_HISTOGRAM_CUSTOM_COUNTS(platform, name, sample, 1, 10'000, 50)
+
+// This macro allows the min, max, and number of buckets to be customized. Any
+// samples whose values are outside of [min, exclusive_max-1] are put in the
+// underflow or overflow buckets. Note that |min| should be >=1 as emitted 0s go
+// into the underflow bucket.
+#define DAWN_HISTOGRAM_CUSTOM_COUNTS(platformObj, name, sample, min, max, bucket_count) \
+ platformObj->HistogramCustomCounts(name, sample, min, max, bucket_count)
+
+// Used for capturing basic percentages. This will be 100 buckets of size 1.
+#define DAWN_HISTOGRAM_PERCENTAGE(platform, name, percent_as_int) \
+ DAWN_HISTOGRAM_ENUMERATION(platform, name, percent_as_int, 101)
+
+// Histogram for boolean values.
+#define DAWN_HISTOGRAM_BOOLEAN(platformObj, name, true_or_false) \
+ platformObj->HistogramBoolean(name, true_or_false)
+
+// Histogram for enumeration values.
+#define DAWN_HISTOGRAM_ENUMERATION(platformObj, name, enum_value, enum_boundary_value) \
+ platformObj->HistogramEnumeration(name, enum_value, enum_boundary_value)
+
+// Used to measure common KB-granularity memory stats. Range is up to 500000KB -
+// approximately 500M.
+#define DAWN_HISTOGRAM_MEMORY_KB(platform, name, sample_kb) \
+ DAWN_HISTOGRAM_CUSTOM_COUNTS(platform, name, sample_kb, 1000, 500'000, 50)
+
+// MB-granularity memory metric. This has a short max (1G).
+#define DAWN_HISTOGRAM_MEMORY_MB(platform, name, sample_mb) \
+ DAWN_HISTOGRAM_CUSTOM_COUNTS(platform, name, sample_mb, 1, 1000, 50)
+
+// Used to measure common MB-granularity memory stats. Range is up to 4000MiB -
+// approximately 4GiB.
+#define DAWN_HISTOGRAM_MEMORY_MEDIUM_MB(name, sample_mb) \
+ DAWN_HISTOGRAM_CUSTOM_COUNTS(name, sample_mb, 1, 4000, 100)
+
+// Used to measure common MB-granularity memory stats. Range is up to ~64G.
+#define DAWN_HISTOGRAM_MEMORY_LARGE_MB(name, sample_mb) \
+ DAWN_HISTOGRAM_CUSTOM_COUNTS(name, sample_mb, 1, 64000, 100)
+
+// Sparse histograms are well suited for recording counts of exact sample values
+// that are sparsely distributed over a relatively large range, in cases where
+// ultra-fast performance is not critical. For instance, Sqlite.Version.* are
+// sparse because for any given database, there's going to be exactly one
+// version logged.
+//
+// For important details on performance, data size, and usage, see the
+// documentation on the regular function equivalents (histogram_functions.h).
+#define DAWN_HISTOGRAM_SPARSE(platformObj, name, sparse_sample) \
+ platformObj->HistogramSparse(name, sparse_sample)
+
+// Scoped class which logs its time on this earth as a UMA statistic. This is
+// recommended for when you want a histogram which measures the time it takes
+// for a method to execute. This measures up to 10 seconds.
+#define SCOPED_DAWN_HISTOGRAM_TIMER(platform, name) \
+ SCOPED_DAWN_HISTOGRAM_TIMER_EXPANDER(platform, name, false, __COUNTER__)
+
+// Similar scoped histogram timer, but this uses DAWN_HISTOGRAM_LONG_TIMES_100,
+// which measures up to an hour, and uses 100 buckets. This is more expensive
+// to store, so only use if this often takes >10 seconds.
+#define SCOPED_DAWN_HISTOGRAM_LONG_TIMER(platform, name) \
+ SCOPED_DAWN_HISTOGRAM_TIMER_EXPANDER(platform, name, true, __COUNTER__)
+
+// This nested macro is necessary to expand __COUNTER__ to an actual value.
+#define SCOPED_DAWN_HISTOGRAM_TIMER_EXPANDER(platform, name, is_long, key) \
+ SCOPED_DAWN_HISTOGRAM_TIMER_UNIQUE(platform, name, is_long, key)
+
+// This is a helper macro used by other macros and shouldn't be used directly.
+#define SCOPED_DAWN_HISTOGRAM_TIMER_UNIQUE(platform, name, is_long, key) \
+ using PlatformType##key = std::decay_t<std::remove_pointer_t<decltype(platform)>>; \
+ class [[nodiscard]] ScopedHistogramTimer##key { \
+ public: \
+ using Platform = PlatformType##key; \
+ ScopedHistogramTimer##key(Platform* p) \
+ : platform_(p), constructed_(platform_->MonotonicallyIncreasingTime()) {} \
+ ~ScopedHistogramTimer##key() { \
+ if (constructed_ == 0) \
+ return; \
+ double elapsed = this->platform_->MonotonicallyIncreasingTime() - constructed_; \
+ int elapsedMS = static_cast<int>(elapsed * 1000.0); \
+ if (is_long) { \
+ DAWN_HISTOGRAM_LONG_TIMES_100(platform_, name, elapsedMS); \
+ } else { \
+ DAWN_HISTOGRAM_TIMES(platform_, name, elapsedMS); \
+ } \
+ } \
+ \
+ private: \
+ Platform* platform_; \
+ double constructed_; \
+ } scoped_histogram_timer_##key(platform)
+
+#endif // SRC_DAWN_PLATFORM_METRICS_HISTOGRAM_MACROS_H_
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index ac9a3e1..c464937 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -490,6 +490,7 @@
"${dawn_root}/src/dawn:proc",
"${dawn_root}/src/dawn/common",
"${dawn_root}/src/dawn/native:headers",
+ "${dawn_root}/src/dawn/platform",
"${dawn_root}/src/dawn/utils",
"${dawn_root}/src/dawn/wire",
]
@@ -536,6 +537,7 @@
"end2end/FirstIndexOffsetTests.cpp",
"end2end/FragDepthTests.cpp",
"end2end/GpuMemorySynchronizationTests.cpp",
+ "end2end/HistogramTests.cpp",
"end2end/IndexFormatTests.cpp",
"end2end/MaxLimitTests.cpp",
"end2end/MemoryAllocationStressTests.cpp",
diff --git a/src/dawn/tests/end2end/HistogramTests.cpp b/src/dawn/tests/end2end/HistogramTests.cpp
new file mode 100644
index 0000000..8da9dc4
--- /dev/null
+++ b/src/dawn/tests/end2end/HistogramTests.cpp
@@ -0,0 +1,166 @@
+// Copyright 2023 The Dawn 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 <memory>
+
+#include "dawn/platform/metrics/HistogramMacros.h"
+#include "dawn/tests/DawnTest.h"
+
+namespace dawn {
+namespace {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::NiceMock;
+
+class DawnHistogramMockPlatform : public dawn::platform::Platform {
+ public:
+ void SetTime(double time) { mTime = time; }
+
+ double MonotonicallyIncreasingTime() override { return mTime; }
+
+ MOCK_METHOD(void,
+ HistogramCustomCounts,
+ (const char* name, int sample, int min, int max, int bucketCount),
+ (override));
+
+ MOCK_METHOD(void,
+ HistogramEnumeration,
+ (const char* name, int sample, int boundaryValue),
+ (override));
+
+ MOCK_METHOD(void, HistogramSparse, (const char* name, int sample), (override));
+
+ MOCK_METHOD(void, HistogramBoolean, (const char* name, bool sample), (override));
+
+ private:
+ double mTime = 0.0;
+};
+
+class HistogramTests : public DawnTest {
+ protected:
+ void SetUp() override { DawnTest::SetUp(); }
+
+ std::unique_ptr<platform::Platform> CreateTestPlatform() override {
+ auto p = std::make_unique<NiceMock<DawnHistogramMockPlatform>>();
+ mMockPlatform = p.get();
+ return p;
+ }
+
+ NiceMock<DawnHistogramMockPlatform>* mMockPlatform;
+};
+
+TEST_P(HistogramTests, Times) {
+ InSequence seq;
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("times", 1, _, _, _));
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("medium_times", 2, _, _, _));
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("long_times", 3, _, _, _));
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("long_times_100", 4, _, _, _));
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("custom_times", 5, 0, 10, 42));
+
+ DAWN_HISTOGRAM_TIMES(mMockPlatform, "times", 1);
+ DAWN_HISTOGRAM_MEDIUM_TIMES(mMockPlatform, "medium_times", 2);
+ DAWN_HISTOGRAM_LONG_TIMES(mMockPlatform, "long_times", 3);
+ DAWN_HISTOGRAM_LONG_TIMES_100(mMockPlatform, "long_times_100", 4);
+ DAWN_HISTOGRAM_CUSTOM_TIMES(mMockPlatform, "custom_times", 5, 0, 10, 42);
+}
+
+TEST_P(HistogramTests, Percentage) {
+ InSequence seq;
+ EXPECT_CALL(*mMockPlatform, HistogramEnumeration("percentage", 0, 101));
+ EXPECT_CALL(*mMockPlatform, HistogramEnumeration("percentage", 42, 101));
+ EXPECT_CALL(*mMockPlatform, HistogramEnumeration("percentage", 100, 101));
+
+ DAWN_HISTOGRAM_PERCENTAGE(mMockPlatform, "percentage", 0);
+ DAWN_HISTOGRAM_PERCENTAGE(mMockPlatform, "percentage", 42);
+ DAWN_HISTOGRAM_PERCENTAGE(mMockPlatform, "percentage", 100);
+}
+
+TEST_P(HistogramTests, Boolean) {
+ InSequence seq;
+ EXPECT_CALL(*mMockPlatform, HistogramBoolean("boolean", false));
+ EXPECT_CALL(*mMockPlatform, HistogramBoolean("boolean", true));
+
+ DAWN_HISTOGRAM_BOOLEAN(mMockPlatform, "boolean", false);
+ DAWN_HISTOGRAM_BOOLEAN(mMockPlatform, "boolean", true);
+}
+
+TEST_P(HistogramTests, Enumeration) {
+ enum Animal { Dog, Cat, Bear, Count };
+
+ InSequence seq;
+ EXPECT_CALL(*mMockPlatform, HistogramEnumeration("animal", Animal::Dog, Animal::Count));
+ EXPECT_CALL(*mMockPlatform, HistogramEnumeration("animal", Animal::Cat, Animal::Count));
+ EXPECT_CALL(*mMockPlatform, HistogramEnumeration("animal", Animal::Bear, Animal::Count));
+
+ DAWN_HISTOGRAM_ENUMERATION(mMockPlatform, "animal", Animal::Dog, Animal::Count);
+ DAWN_HISTOGRAM_ENUMERATION(mMockPlatform, "animal", Animal::Cat, Animal::Count);
+ DAWN_HISTOGRAM_ENUMERATION(mMockPlatform, "animal", Animal::Bear, Animal::Count);
+}
+
+TEST_P(HistogramTests, Memory) {
+ InSequence seq;
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("kb", 1, _, _, _));
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("mb", 2, _, _, _));
+
+ DAWN_HISTOGRAM_MEMORY_KB(mMockPlatform, "kb", 1);
+ DAWN_HISTOGRAM_MEMORY_MB(mMockPlatform, "mb", 2);
+}
+
+TEST_P(HistogramTests, Sparse) {
+ InSequence seq;
+ EXPECT_CALL(*mMockPlatform, HistogramSparse("sparse", 1));
+ EXPECT_CALL(*mMockPlatform, HistogramSparse("sparse", 2));
+
+ DAWN_HISTOGRAM_SPARSE(mMockPlatform, "sparse", 1);
+ DAWN_HISTOGRAM_SPARSE(mMockPlatform, "sparse", 2);
+}
+
+TEST_P(HistogramTests, ScopedTimer) {
+ InSequence seq;
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("timer0", 2500, _, _, _));
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("timer1", 15500, _, _, _));
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("timer4", 2500, _, _, _));
+ EXPECT_CALL(*mMockPlatform, HistogramCustomCounts("timer3", 6000, _, _, _));
+
+ {
+ mMockPlatform->SetTime(1.0);
+ SCOPED_DAWN_HISTOGRAM_TIMER(mMockPlatform, "timer0");
+ mMockPlatform->SetTime(3.5);
+ }
+ {
+ mMockPlatform->SetTime(10.0);
+ SCOPED_DAWN_HISTOGRAM_LONG_TIMER(mMockPlatform, "timer1");
+ mMockPlatform->SetTime(25.5);
+ }
+ {
+ mMockPlatform->SetTime(1.0);
+ SCOPED_DAWN_HISTOGRAM_TIMER(mMockPlatform, "timer3");
+ mMockPlatform->SetTime(4.5);
+ SCOPED_DAWN_HISTOGRAM_LONG_TIMER(mMockPlatform, "timer4");
+ mMockPlatform->SetTime(7.0);
+ }
+}
+
+DAWN_INSTANTIATE_TEST(HistogramTests,
+ D3D11Backend(),
+ D3D12Backend(),
+ MetalBackend(),
+ NullBackend(),
+ OpenGLBackend(),
+ OpenGLESBackend(),
+ VulkanBackend());
+
+} // namespace
+} // namespace dawn