• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.modules.expresslog;
18 
19 import android.annotation.FloatRange;
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 
23 import com.android.modules.expresslog.StatsExpressLog;
24 
25 import java.util.Arrays;
26 
27 /** Histogram encapsulates StatsD write API calls */
28 public final class Histogram {
29 
30     private final long mMetricIdHash;
31     private final BinOptions mBinOptions;
32 
33     /**
34      * Creates Histogram metric logging wrapper
35      *
36      * @param metricId   to log, logging will be no-op if metricId is not defined in the TeX catalog
37      * @param binOptions to calculate bin index for samples
38      */
Histogram(@onNull String metricId, @NonNull BinOptions binOptions)39     public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) {
40         mMetricIdHash = Utils.hashString(metricId);
41         mBinOptions = binOptions;
42     }
43 
44     /**
45      * Logs increment sample count for automatically calculated bin
46      *
47      * @param sample value
48      */
logSample(float sample)49     public void logSample(float sample) {
50         final int binIndex = mBinOptions.getBinForSample(sample);
51         StatsExpressLog.write(StatsExpressLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash,
52                 /*count*/ 1, binIndex);
53     }
54 
55     /**
56      * Logs increment sample count for automatically calculated bin
57      *
58      * @param uid used as a dimension for the count metric
59      * @param sample value
60      */
logSampleWithUid(int uid, float sample)61     public void logSampleWithUid(int uid, float sample) {
62         final int binIndex = mBinOptions.getBinForSample(sample);
63         StatsExpressLog.write(StatsExpressLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
64                 mMetricIdHash, /*count*/ 1, binIndex, uid);
65     }
66 
67     /** Used by Histogram to map data sample to corresponding bin */
68     public interface BinOptions {
69         /**
70          * Returns bins count to be used by a histogram
71          *
72          * @return bins count used to initialize Options, including overflow & underflow bins
73          */
getBinsCount()74         int getBinsCount();
75 
76         /**
77          * Returns bin index for the input sample value
78          * index == 0 stands for underflow
79          * index == getBinsCount() - 1 stands for overflow
80          *
81          * @return zero based index
82          */
getBinForSample(float sample)83         int getBinForSample(float sample);
84     }
85 
86     /** Used by Histogram to map data sample to corresponding bin for uniform bins */
87     public static final class UniformOptions implements BinOptions {
88 
89         private final int mBinCount;
90         private final float mMinValue;
91         private final float mExclusiveMaxValue;
92         private final float mBinSize;
93 
94         /**
95          * Creates options for uniform (linear) sized bins
96          *
97          * @param binCount          amount of histogram bins. 2 bin indexes will be calculated
98          *                          automatically to represent underflow & overflow bins
99          * @param minValue          is included in the first bin, values less than minValue
100          *                          go to underflow bin
101          * @param exclusiveMaxValue is included in the overflow bucket. For accurate
102          *                          measure up to kMax, then exclusiveMaxValue
103          *                          should be set to kMax + 1
104          */
UniformOptions(@ntRangefrom = 1) int binCount, float minValue, float exclusiveMaxValue)105         public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
106                 float exclusiveMaxValue) {
107             if (binCount < 1) {
108                 throw new IllegalArgumentException("Bin count should be positive number");
109             }
110 
111             if (exclusiveMaxValue <= minValue) {
112                 throw new IllegalArgumentException("Bins range invalid (maxValue < minValue)");
113             }
114 
115             mMinValue = minValue;
116             mExclusiveMaxValue = exclusiveMaxValue;
117             mBinSize = (mExclusiveMaxValue - minValue) / binCount;
118 
119             // Implicitly add 2 for the extra underflow & overflow bins
120             mBinCount = binCount + 2;
121         }
122 
123         @Override
getBinsCount()124         public int getBinsCount() {
125             return mBinCount;
126         }
127 
128         @Override
getBinForSample(float sample)129         public int getBinForSample(float sample) {
130             if (sample < mMinValue) {
131                 // goes to underflow
132                 return 0;
133             } else if (sample >= mExclusiveMaxValue) {
134                 // goes to overflow
135                 return mBinCount - 1;
136             }
137             return (int) ((sample - mMinValue) / mBinSize + 1);
138         }
139     }
140 
141     /** Used by Histogram to map data sample to corresponding bin for scaled bins */
142     public static final class ScaledRangeOptions implements BinOptions {
143         // store minimum value per bin
144         final long[] mBins;
145 
146         /**
147          * Creates options for scaled range bins
148          *
149          * @param binCount      amount of histogram bins. 2 bin indexes will be calculated
150          *                      automatically to represent underflow & overflow bins
151          * @param minValue      is included in the first bin, values less than minValue
152          *                      go to underflow bin
153          * @param firstBinWidth used to represent first bin width and as a reference to calculate
154          *                      width for consecutive bins
155          * @param scaleFactor   used to calculate width for consecutive bins
156          */
ScaledRangeOptions(@ntRangefrom = 1) int binCount, int minValue, @FloatRange(from = 1.f) float firstBinWidth, @FloatRange(from = 1.f) float scaleFactor)157         public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
158                 @FloatRange(from = 1.f) float firstBinWidth,
159                 @FloatRange(from = 1.f) float scaleFactor) {
160             if (binCount < 1) {
161                 throw new IllegalArgumentException("Bin count should be positive number");
162             }
163 
164             if (firstBinWidth < 1.f) {
165                 throw new IllegalArgumentException(
166                         "First bin width invalid (should be 1.f at minimum)");
167             }
168 
169             if (scaleFactor < 1.f) {
170                 throw new IllegalArgumentException(
171                         "Scaled factor invalid (should be 1.f at minimum)");
172             }
173 
174             // precalculating bins ranges (no need to create a bin for underflow reference value)
175             mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
176         }
177 
178         @Override
getBinsCount()179         public int getBinsCount() {
180             return mBins.length + 1;
181         }
182 
183         @Override
getBinForSample(float sample)184         public int getBinForSample(float sample) {
185             if (sample < mBins[0]) {
186                 // goes to underflow
187                 return 0;
188             } else if (sample >= mBins[mBins.length - 1]) {
189                 // goes to overflow
190                 return mBins.length;
191             }
192 
193             return lower_bound(mBins, (long) sample) + 1;
194         }
195 
196         // To find lower bound using binary search implementation of Arrays utility class
lower_bound(long[] array, long sample)197         private static int lower_bound(long[] array, long sample) {
198             int index = Arrays.binarySearch(array, sample);
199             // If key is not present in the array
200             if (index < 0) {
201                 // Index specify the position of the key when inserted in the sorted array
202                 // so the element currently present at this position will be the lower bound
203                 return Math.abs(index) - 2;
204             }
205             return index;
206         }
207 
initBins(int count, int minValue, float firstBinWidth, float scaleFactor)208         private static long[] initBins(int count, int minValue, float firstBinWidth,
209                 float scaleFactor) {
210             long[] bins = new long[count];
211             bins[0] = minValue;
212             double lastWidth = firstBinWidth;
213             for (int i = 1; i < count; i++) {
214                 // current bin minValue = previous bin width * scaleFactor
215                 double currentBinMinValue = bins[i - 1] + lastWidth;
216                 if (currentBinMinValue > Integer.MAX_VALUE) {
217                     throw new IllegalArgumentException(
218                         "Attempted to create a bucket larger than maxint");
219                 }
220 
221                 bins[i] = (long) currentBinMinValue;
222                 lastWidth *= scaleFactor;
223             }
224             return bins;
225         }
226     }
227 }
228