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