1 // Copyright 2016 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef BASE_METRICS_HISTOGRAM_MACROS_INTERNAL_H_ 6 #define BASE_METRICS_HISTOGRAM_MACROS_INTERNAL_H_ 7 8 #include <stdint.h> 9 10 #include <atomic> 11 #include <limits> 12 #include <memory> 13 #include <type_traits> 14 15 #include "base/dcheck_is_on.h" 16 #include "base/metrics/histogram.h" 17 #include "base/metrics/sparse_histogram.h" 18 #include "base/time/time.h" 19 #include "base/types/cxx23_to_underlying.h" 20 21 // This is for macros and helpers internal to base/metrics. They should not be 22 // used outside of this directory. For writing to UMA histograms, see 23 // histogram_macros.h. 24 25 namespace base::internal { 26 27 // Helper trait for deducing the boundary value for enums. 28 template <typename Enum> 29 requires(std::is_enum_v<Enum>) 30 struct EnumSizeTraits { CountEnumSizeTraits31 static constexpr Enum Count() { 32 if constexpr (requires { Enum::kMaxValue; }) { 33 // Since the UMA histogram macros expect a value one larger than the max 34 // defined enumerator value, add one. 35 return static_cast<Enum>(base::to_underlying(Enum::kMaxValue) + 1); 36 } else { 37 static_assert( 38 sizeof(Enum) == 0, 39 "enumerator must define kMaxValue enumerator to use this macro!"); 40 return Enum(); 41 } 42 } 43 }; 44 45 } // namespace base::internal 46 47 // TODO(rkaplow): Improve commenting of these methods. 48 //------------------------------------------------------------------------------ 49 // Histograms are often put in areas where they are called many many times, and 50 // performance is critical. As a result, they are designed to have a very low 51 // recurring cost of executing (adding additional samples). Toward that end, 52 // the macros declare a static pointer to the histogram in question, and only 53 // take a "slow path" to construct (or find) the histogram on the first run 54 // through the macro. We leak the histograms at shutdown time so that we don't 55 // have to validate using the pointers at any time during the running of the 56 // process. 57 58 // In some cases (integration into 3rd party code), it's useful to separate the 59 // definition of |atomic_histogram_pointer| from its use. To achieve this we 60 // define HISTOGRAM_POINTER_USE, which uses an |atomic_histogram_pointer|, and 61 // STATIC_HISTOGRAM_POINTER_BLOCK, which defines an |atomic_histogram_pointer| 62 // and forwards to HISTOGRAM_POINTER_USE. 63 #define HISTOGRAM_POINTER_USE( \ 64 atomic_histogram_pointer, constant_histogram_name, \ 65 histogram_add_method_invocation, histogram_factory_get_invocation) \ 66 do { \ 67 base::HistogramBase* histogram_pointer( \ 68 reinterpret_cast<base::HistogramBase*>( \ 69 atomic_histogram_pointer->load(std::memory_order_acquire))); \ 70 if (!histogram_pointer) { \ 71 /* \ 72 * This is the slow path, which will construct OR find the \ 73 * matching histogram. |histogram_factory_get_invocation| includes \ 74 * locks on a global histogram name map and is completely thread \ 75 * safe. \ 76 */ \ 77 histogram_pointer = histogram_factory_get_invocation; \ 78 \ 79 /* \ 80 * We could do this without any barrier, since FactoryGet() \ 81 * entered and exited a lock after construction, but this barrier \ 82 * makes things clear. \ 83 */ \ 84 atomic_histogram_pointer->store( \ 85 reinterpret_cast<uintptr_t>(histogram_pointer), \ 86 std::memory_order_release); \ 87 } \ 88 if (DCHECK_IS_ON()) \ 89 histogram_pointer->CheckName(constant_histogram_name); \ 90 histogram_pointer->histogram_add_method_invocation; \ 91 } while (0) 92 93 // This is a helper macro used by other macros and shouldn't be used directly. 94 // Defines the static |atomic_histogram_pointer| and forwards to 95 // HISTOGRAM_POINTER_USE. 96 #define STATIC_HISTOGRAM_POINTER_BLOCK(constant_histogram_name, \ 97 histogram_add_method_invocation, \ 98 histogram_factory_get_invocation) \ 99 do { \ 100 /* \ 101 * The pointer's presence indicates that the initialization is complete. \ 102 * Initialization is idempotent, so it can safely be atomically repeated. \ 103 */ \ 104 static std::atomic_uintptr_t atomic_histogram_pointer; \ 105 HISTOGRAM_POINTER_USE( \ 106 std::addressof(atomic_histogram_pointer), constant_histogram_name, \ 107 histogram_add_method_invocation, histogram_factory_get_invocation); \ 108 } while (0) 109 110 // This is a helper macro used by other macros and shouldn't be used directly. 111 #define INTERNAL_HISTOGRAM_CUSTOM_COUNTS_WITH_FLAG(name, sample, min, max, \ 112 bucket_count, flag) \ 113 STATIC_HISTOGRAM_POINTER_BLOCK( \ 114 name, Add(sample), \ 115 base::Histogram::FactoryGet(name, min, max, bucket_count, flag)) 116 117 // This is a helper macro used by other macros and shouldn't be used directly. 118 // The bucketing scheme is linear with a bucket size of 1. For N items, 119 // recording values in the range [0, N - 1] creates a linear histogram with N + 120 // 1 buckets: 121 // [0, 1), [1, 2), ..., [N - 1, N) 122 // and an overflow bucket [N, infinity). 123 // 124 // Code should never emit to the overflow bucket; only to the other N buckets. 125 // This allows future versions of Chrome to safely increase the boundary size. 126 // Otherwise, the histogram would have [N - 1, infinity) as its overflow bucket, 127 // and so the maximal value (N - 1) would be emitted to this overflow bucket. 128 // But, if an additional value were later added, the bucket label for 129 // the value (N - 1) would change to [N - 1, N), which would result in different 130 // versions of Chrome using different bucket labels for identical data. 131 #define INTERNAL_HISTOGRAM_EXACT_LINEAR_WITH_FLAG(name, sample, boundary, \ 132 flag) \ 133 do { \ 134 static_assert(!std::is_enum_v<std::decay_t<decltype(sample)>>, \ 135 "|sample| should not be an enum type!"); \ 136 static_assert(!std::is_enum_v<std::decay_t<decltype(boundary)>>, \ 137 "|boundary| should not be an enum type!"); \ 138 STATIC_HISTOGRAM_POINTER_BLOCK( \ 139 name, Add(sample), \ 140 base::LinearHistogram::FactoryGet(name, 1, boundary, boundary + 1, \ 141 flag)); \ 142 } while (0) 143 144 // While this behaves the same as the above macro, the wrapping of a linear 145 // histogram with another object to do the scaling means the POINTER_BLOCK 146 // macro can't be used as it is tied to HistogramBase 147 #define INTERNAL_HISTOGRAM_SCALED_EXACT_LINEAR_WITH_FLAG( \ 148 name, sample, count, boundary, scale, flag) \ 149 do { \ 150 static_assert(!std::is_enum_v<std::decay_t<decltype(sample)>>, \ 151 "|sample| should not be an enum type!"); \ 152 static_assert(!std::is_enum_v<std::decay_t<decltype(boundary)>>, \ 153 "|boundary| should not be an enum type!"); \ 154 class ScaledLinearHistogramInstance : public base::ScaledLinearHistogram { \ 155 public: \ 156 ScaledLinearHistogramInstance() \ 157 : ScaledLinearHistogram(name, \ 158 1, \ 159 boundary, \ 160 boundary + 1, \ 161 scale, \ 162 flag) {} \ 163 }; \ 164 static base::LazyInstance<ScaledLinearHistogramInstance>::Leaky \ 165 scaled_leaky; \ 166 scaled_leaky.Get().AddScaledCount(sample, count); \ 167 } while (0) 168 169 // Helper for 'overloading' UMA_HISTOGRAM_ENUMERATION with a variable number of 170 // arguments. 171 #define INTERNAL_UMA_HISTOGRAM_ENUMERATION_GET_MACRO(_1, _2, NAME, ...) NAME 172 173 #define INTERNAL_UMA_HISTOGRAM_ENUMERATION_DEDUCE_BOUNDARY(name, sample, \ 174 flags) \ 175 INTERNAL_HISTOGRAM_ENUMERATION_WITH_FLAG( \ 176 name, sample, \ 177 base::internal::EnumSizeTraits<std::decay_t<decltype(sample)>>::Count(), \ 178 flags) 179 180 // Note: The value in |sample| must be strictly less than |enum_size|. 181 #define INTERNAL_UMA_HISTOGRAM_ENUMERATION_SPECIFY_BOUNDARY(name, sample, \ 182 enum_size, flags) \ 183 INTERNAL_HISTOGRAM_ENUMERATION_WITH_FLAG(name, sample, enum_size, flags) 184 185 // Similar to the previous macro but intended for enumerations. This delegates 186 // the work to the previous macro, but supports scoped enumerations as well by 187 // forcing an explicit cast to the HistogramBase::Sample integral type. 188 // 189 // Note the range checks verify two separate issues: 190 // - that the declared enum size isn't out of range of HistogramBase::Sample 191 // - that the declared enum size is > 0 192 // 193 // TODO(dcheng): This should assert that the passed in types are actually enum 194 // types. 195 #define INTERNAL_HISTOGRAM_ENUMERATION_WITH_FLAG(name, sample, boundary, flag) \ 196 do { \ 197 using decayed_sample = std::decay<decltype(sample)>::type; \ 198 using decayed_boundary = std::decay<decltype(boundary)>::type; \ 199 static_assert( \ 200 !std::is_enum_v<decayed_boundary> || std::is_enum_v<decayed_sample>, \ 201 "Unexpected: |boundary| is enum, but |sample| is not."); \ 202 static_assert(!std::is_enum_v<decayed_sample> || \ 203 !std::is_enum_v<decayed_boundary> || \ 204 std::is_same_v<decayed_sample, decayed_boundary>, \ 205 "|sample| and |boundary| shouldn't be of different enums"); \ 206 static_assert( \ 207 static_cast<uintmax_t>(boundary) < \ 208 static_cast<uintmax_t>( \ 209 std::numeric_limits<base::HistogramBase::Sample>::max()), \ 210 "|boundary| is out of range of HistogramBase::Sample"); \ 211 INTERNAL_HISTOGRAM_EXACT_LINEAR_WITH_FLAG( \ 212 name, static_cast<base::HistogramBase::Sample>(sample), \ 213 static_cast<base::HistogramBase::Sample>(boundary), flag); \ 214 } while (0) 215 216 #define INTERNAL_HISTOGRAM_SCALED_ENUMERATION_WITH_FLAG(name, sample, count, \ 217 scale, flag) \ 218 do { \ 219 using decayed_sample = std::decay<decltype(sample)>::type; \ 220 static_assert(std::is_enum_v<decayed_sample>, \ 221 "Unexpected: |sample| is not at enum."); \ 222 constexpr auto boundary = base::internal::EnumSizeTraits< \ 223 std::decay_t<decltype(sample)>>::Count(); \ 224 static_assert( \ 225 static_cast<uintmax_t>(boundary) < \ 226 static_cast<uintmax_t>( \ 227 std::numeric_limits<base::HistogramBase::Sample>::max()), \ 228 "|boundary| is out of range of HistogramBase::Sample"); \ 229 INTERNAL_HISTOGRAM_SCALED_EXACT_LINEAR_WITH_FLAG( \ 230 name, static_cast<base::HistogramBase::Sample>(sample), count, \ 231 static_cast<base::HistogramBase::Sample>(boundary), scale, flag); \ 232 } while (0) 233 234 // This is a helper macro used by other macros and shouldn't be used directly. 235 // This is necessary to expand __COUNTER__ to an actual value. 236 #define INTERNAL_SCOPED_UMA_HISTOGRAM_TIMER_EXPANDER(name, timing, key) \ 237 INTERNAL_SCOPED_UMA_HISTOGRAM_TIMER_UNIQUE(name, timing, key) 238 239 // This is a helper macro used by other macros and shouldn't be used directly. 240 #define INTERNAL_SCOPED_UMA_HISTOGRAM_TIMER_UNIQUE(name, timing, key) \ 241 class ScopedHistogramTimer##key { \ 242 public: \ 243 ScopedHistogramTimer##key() : constructed_(base::TimeTicks::Now()) {} \ 244 ~ScopedHistogramTimer##key() { \ 245 base::TimeDelta elapsed = base::TimeTicks::Now() - constructed_; \ 246 switch (timing) { \ 247 case ScopedHistogramTiming::kMicrosecondTimes: \ 248 UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( \ 249 name, elapsed, base::Microseconds(1), base::Seconds(1), 50); \ 250 break; \ 251 case ScopedHistogramTiming::kMediumTimes: \ 252 UMA_HISTOGRAM_TIMES(name, elapsed); \ 253 break; \ 254 case ScopedHistogramTiming::kLongTimes: \ 255 UMA_HISTOGRAM_LONG_TIMES_100(name, elapsed); \ 256 break; \ 257 } \ 258 } \ 259 \ 260 private: \ 261 base::TimeTicks constructed_; \ 262 } scoped_histogram_timer_##key 263 264 #endif // BASE_METRICS_HISTOGRAM_MACROS_INTERNAL_H_ 265