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