1 // Copyright 2016 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base.metrics; 6 7 import org.chromium.base.library_loader.LibraryLoader; 8 9 import java.util.ArrayList; 10 import java.util.List; 11 import java.util.concurrent.TimeUnit; 12 13 /** 14 * Utility classes for recording UMA metrics before the native library 15 * may have been loaded. Metrics are cached until the library is known 16 * to be loaded, then committed to the MetricsService all at once. 17 */ 18 public class CachedMetrics { 19 /** 20 * Base class for cached metric objects. Subclasses are expected to call 21 * addToCache() when some metric state gets recorded that requires a later 22 * commit operation when the native library is loaded. 23 */ 24 private abstract static class CachedMetric { 25 private static final List<CachedMetric> sMetrics = new ArrayList<CachedMetric>(); 26 27 protected final String mName; 28 protected boolean mCached; 29 30 /** 31 * @param name Name of the metric to record. 32 */ CachedMetric(String name)33 protected CachedMetric(String name) { 34 mName = name; 35 } 36 37 /** 38 * Adds this object to the sMetrics cache, if it hasn't been added already. 39 * Must be called while holding the synchronized(sMetrics) lock. 40 * Note: The synchronization is not done inside this function because subclasses 41 * need to increment their held values under lock to ensure thread-safety. 42 */ addToCache()43 protected final void addToCache() { 44 assert Thread.holdsLock(sMetrics); 45 46 if (mCached) return; 47 sMetrics.add(this); 48 mCached = true; 49 } 50 51 /** 52 * Commits the metric. Expects the native library to be loaded. 53 * Must be called while holding the synchronized(sMetrics) lock. 54 */ commitAndClear()55 protected abstract void commitAndClear(); 56 } 57 58 /** 59 * Caches an action that will be recorded after native side is loaded. 60 */ 61 public static class ActionEvent extends CachedMetric { 62 private int mCount; 63 ActionEvent(String actionName)64 public ActionEvent(String actionName) { 65 super(actionName); 66 } 67 record()68 public void record() { 69 synchronized (CachedMetric.sMetrics) { 70 if (LibraryLoader.getInstance().isInitialized()) { 71 recordWithNative(); 72 } else { 73 mCount++; 74 addToCache(); 75 } 76 } 77 } 78 recordWithNative()79 private void recordWithNative() { 80 RecordUserAction.record(mName); 81 } 82 83 @Override commitAndClear()84 protected void commitAndClear() { 85 while (mCount > 0) { 86 recordWithNative(); 87 mCount--; 88 } 89 } 90 } 91 92 /** Caches a set of integer histogram samples. */ 93 public static class SparseHistogramSample extends CachedMetric { 94 private final List<Integer> mSamples = new ArrayList<Integer>(); 95 SparseHistogramSample(String histogramName)96 public SparseHistogramSample(String histogramName) { 97 super(histogramName); 98 } 99 record(int sample)100 public void record(int sample) { 101 synchronized (CachedMetric.sMetrics) { 102 if (LibraryLoader.getInstance().isInitialized()) { 103 recordWithNative(sample); 104 } else { 105 mSamples.add(sample); 106 addToCache(); 107 } 108 } 109 } 110 recordWithNative(int sample)111 private void recordWithNative(int sample) { 112 RecordHistogram.recordSparseSlowlyHistogram(mName, sample); 113 } 114 115 @Override commitAndClear()116 protected void commitAndClear() { 117 for (Integer sample : mSamples) { 118 recordWithNative(sample); 119 } 120 mSamples.clear(); 121 } 122 } 123 124 /** Caches a set of enumerated histogram samples. */ 125 public static class EnumeratedHistogramSample extends CachedMetric { 126 private final List<Integer> mSamples = new ArrayList<Integer>(); 127 private final int mMaxValue; 128 EnumeratedHistogramSample(String histogramName, int maxValue)129 public EnumeratedHistogramSample(String histogramName, int maxValue) { 130 super(histogramName); 131 mMaxValue = maxValue; 132 } 133 record(int sample)134 public void record(int sample) { 135 synchronized (CachedMetric.sMetrics) { 136 if (LibraryLoader.getInstance().isInitialized()) { 137 recordWithNative(sample); 138 } else { 139 mSamples.add(sample); 140 addToCache(); 141 } 142 } 143 } 144 recordWithNative(int sample)145 private void recordWithNative(int sample) { 146 RecordHistogram.recordEnumeratedHistogram(mName, sample, mMaxValue); 147 } 148 149 @Override commitAndClear()150 protected void commitAndClear() { 151 for (Integer sample : mSamples) { 152 recordWithNative(sample); 153 } 154 mSamples.clear(); 155 } 156 } 157 158 /** Caches a set of times histogram samples. */ 159 public static class TimesHistogramSample extends CachedMetric { 160 private final List<Long> mSamples = new ArrayList<Long>(); 161 private final TimeUnit mTimeUnit; 162 TimesHistogramSample(String histogramName, TimeUnit timeUnit)163 public TimesHistogramSample(String histogramName, TimeUnit timeUnit) { 164 super(histogramName); 165 RecordHistogram.assertTimesHistogramSupportsUnit(timeUnit); 166 mTimeUnit = timeUnit; 167 } 168 record(long sample)169 public void record(long sample) { 170 synchronized (CachedMetric.sMetrics) { 171 if (LibraryLoader.getInstance().isInitialized()) { 172 recordWithNative(sample); 173 } else { 174 mSamples.add(sample); 175 addToCache(); 176 } 177 } 178 } 179 recordWithNative(long sample)180 private void recordWithNative(long sample) { 181 RecordHistogram.recordTimesHistogram(mName, sample, mTimeUnit); 182 } 183 184 @Override commitAndClear()185 protected void commitAndClear() { 186 for (Long sample : mSamples) { 187 recordWithNative(sample); 188 } 189 mSamples.clear(); 190 } 191 } 192 193 /** Caches a set of boolean histogram samples. */ 194 public static class BooleanHistogramSample extends CachedMetric { 195 private final List<Boolean> mSamples = new ArrayList<Boolean>(); 196 BooleanHistogramSample(String histogramName)197 public BooleanHistogramSample(String histogramName) { 198 super(histogramName); 199 } 200 record(boolean sample)201 public void record(boolean sample) { 202 synchronized (CachedMetric.sMetrics) { 203 if (LibraryLoader.getInstance().isInitialized()) { 204 recordWithNative(sample); 205 } else { 206 mSamples.add(sample); 207 addToCache(); 208 } 209 } 210 } 211 recordWithNative(boolean sample)212 private void recordWithNative(boolean sample) { 213 RecordHistogram.recordBooleanHistogram(mName, sample); 214 } 215 216 @Override commitAndClear()217 protected void commitAndClear() { 218 for (Boolean sample : mSamples) { 219 recordWithNative(sample); 220 } 221 mSamples.clear(); 222 } 223 } 224 225 /** 226 * Caches a set of custom count histogram samples. 227 * Corresponds to UMA_HISTOGRAM_CUSTOM_COUNTS C++ macro. 228 */ 229 public static class CustomCountHistogramSample extends CachedMetric { 230 private final List<Integer> mSamples = new ArrayList<Integer>(); 231 private final int mMin; 232 private final int mMax; 233 private final int mNumBuckets; 234 CustomCountHistogramSample(String histogramName, int min, int max, int numBuckets)235 public CustomCountHistogramSample(String histogramName, int min, int max, int numBuckets) { 236 super(histogramName); 237 mMin = min; 238 mMax = max; 239 mNumBuckets = numBuckets; 240 } 241 record(int sample)242 public void record(int sample) { 243 synchronized (CachedMetric.sMetrics) { 244 if (LibraryLoader.getInstance().isInitialized()) { 245 recordWithNative(sample); 246 } else { 247 mSamples.add(sample); 248 addToCache(); 249 } 250 } 251 } 252 recordWithNative(int sample)253 private void recordWithNative(int sample) { 254 RecordHistogram.recordCustomCountHistogram(mName, sample, mMin, mMax, mNumBuckets); 255 } 256 257 @Override commitAndClear()258 protected void commitAndClear() { 259 for (Integer sample : mSamples) { 260 recordWithNative(sample); 261 } 262 mSamples.clear(); 263 } 264 } 265 266 /** 267 * Caches a set of count histogram samples in range [1, 100). 268 * Corresponds to UMA_HISTOGRAM_COUNTS_100 C++ macro. 269 */ 270 public static class Count100HistogramSample extends CustomCountHistogramSample { Count100HistogramSample(String histogramName)271 public Count100HistogramSample(String histogramName) { 272 super(histogramName, 1, 100, 50); 273 } 274 } 275 276 /** 277 * Caches a set of count histogram samples in range [1, 1000). 278 * Corresponds to UMA_HISTOGRAM_COUNTS_1000 C++ macro. 279 */ 280 public static class Count1000HistogramSample extends CustomCountHistogramSample { Count1000HistogramSample(String histogramName)281 public Count1000HistogramSample(String histogramName) { 282 super(histogramName, 1, 1000, 50); 283 } 284 } 285 286 /** 287 * Caches a set of count histogram samples in range [1, 1000000). 288 * Corresponds to UMA_HISTOGRAM_COUNTS_1M C++ macro. 289 */ 290 public static class Count1MHistogramSample extends CustomCountHistogramSample { Count1MHistogramSample(String histogramName)291 public Count1MHistogramSample(String histogramName) { 292 super(histogramName, 1, 1000000, 50); 293 } 294 } 295 296 /** 297 * Calls out to native code to commit any cached histograms and events. 298 * Should be called once the native library has been loaded. 299 */ commitCachedMetrics()300 public static void commitCachedMetrics() { 301 synchronized (CachedMetric.sMetrics) { 302 for (CachedMetric metric : CachedMetric.sMetrics) { 303 metric.commitAndClear(); 304 } 305 } 306 } 307 } 308