• 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 java.util.Arrays;
24 
25 /** Histogram encapsulates StatsD write API calls */
26 public final class Histogram {
27 
28     private final String mMetricId;
29     private final BinOptions mBinOptions;
30 
31     /**
32      * Creates Histogram metric logging wrapper
33      *
34      * @param metricId   to log, logging will be no-op if metricId is not defined in the TeX catalog
35      * @param binOptions to calculate bin index for samples
36      */
Histogram(@onNull String metricId, @NonNull BinOptions binOptions)37     public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) {
38         mMetricId = metricId;
39         mBinOptions = binOptions;
40     }
41 
42     /**
43      * Logs increment sample count for automatically calculated bin
44      *
45      * @param sample value
46      */
logSample(float sample)47     public void logSample(float sample) {
48         final long hash = MetricIds.getMetricIdHash(mMetricId, MetricIds.METRIC_TYPE_HISTOGRAM);
49         if (hash != MetricIds.INVALID_METRIC_ID) {
50           final int binIndex = mBinOptions.getBinForSample(sample);
51           StatsExpressLog.write(
52               StatsExpressLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, hash, /*count*/ 1, binIndex);
53         }
54     }
55 
56     /**
57      * Logs increment sample count for automatically calculated bin
58      *
59      * @param uid used as a dimension for the count metric
60      * @param sample value
61      */
logSampleWithUid(int uid, float sample)62     public void logSampleWithUid(int uid, float sample) {
63         final long hash =
64                 MetricIds.getMetricIdHash(mMetricId, MetricIds.METRIC_TYPE_HISTOGRAM_WITH_UID);
65         if (hash != MetricIds.INVALID_METRIC_ID) {
66           final int binIndex = mBinOptions.getBinForSample(sample);
67           StatsExpressLog.write(StatsExpressLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
68               hash, /*count*/
69               1, binIndex, uid);
70         }
71     }
72 
73     /** Used by Histogram to map data sample to corresponding bin */
74     public interface BinOptions {
75         /**
76          * Returns bins count to be used by a histogram
77          *
78          * @return bins count used to initialize Options, including overflow & underflow bins
79          */
getBinsCount()80         int getBinsCount();
81 
82         /**
83          * Returns bin index for the input sample value
84          * index == 0 stands for underflow
85          * index == getBinsCount() - 1 stands for overflow
86          *
87          * @return zero based index
88          */
getBinForSample(float sample)89         int getBinForSample(float sample);
90     }
91 
92     /** Used by Histogram to map data sample to corresponding bin for uniform bins */
93     public static final class UniformOptions implements BinOptions {
94 
95         private final int mBinCount;
96         private final float mMinValue;
97         private final float mExclusiveMaxValue;
98         private final float mBinSize;
99 
100         /**
101          * Creates options for uniform (linear) sized bins
102          *
103          * @param binCount          amount of histogram bins. 2 bin indexes will be calculated
104          *                          automatically to represent underflow & overflow bins
105          * @param minValue          is included in the first bin, values less than minValue
106          *                          go to underflow bin
107          * @param exclusiveMaxValue is included in the overflow bucket. For accurate
108          *                          measure up to kMax, then exclusiveMaxValue
109          *                          should be set to kMax + 1
110          */
UniformOptions(@ntRangefrom = 1) int binCount, float minValue, float exclusiveMaxValue)111         public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
112                 float exclusiveMaxValue) {
113             if (binCount < 1) {
114                 throw new IllegalArgumentException("Bin count should be positive number");
115             }
116 
117             if (exclusiveMaxValue <= minValue) {
118                 throw new IllegalArgumentException("Bins range invalid (maxValue < minValue)");
119             }
120 
121             mMinValue = minValue;
122             mExclusiveMaxValue = exclusiveMaxValue;
123             mBinSize = (mExclusiveMaxValue - minValue) / binCount;
124 
125             // Implicitly add 2 for the extra underflow & overflow bins
126             mBinCount = binCount + 2;
127         }
128 
129         @Override
getBinsCount()130         public int getBinsCount() {
131             return mBinCount;
132         }
133 
134         @Override
getBinForSample(float sample)135         public int getBinForSample(float sample) {
136             if (sample < mMinValue) {
137                 // goes to underflow
138                 return 0;
139             } else if (sample >= mExclusiveMaxValue) {
140                 // goes to overflow
141                 return mBinCount - 1;
142             }
143             return (int) ((sample - mMinValue) / mBinSize + 1);
144         }
145     }
146 
147     /** Used by Histogram to map data sample to corresponding bin for scaled bins */
148     public static final class ScaledRangeOptions implements BinOptions {
149         // store minimum value per bin
150         final long[] mBins;
151 
152         /**
153          * Creates options for scaled range bins
154          *
155          * @param binCount      amount of histogram bins. 2 bin indexes will be calculated
156          *                      automatically to represent underflow & overflow bins
157          * @param minValue      is included in the first bin, values less than minValue
158          *                      go to underflow bin
159          * @param firstBinWidth used to represent first bin width and as a reference to calculate
160          *                      width for consecutive bins
161          * @param scaleFactor   used to calculate width for consecutive bins
162          */
ScaledRangeOptions(@ntRangefrom = 1) int binCount, int minValue, @FloatRange(from = 1.f) float firstBinWidth, @FloatRange(from = 1.f) float scaleFactor)163         public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
164                 @FloatRange(from = 1.f) float firstBinWidth,
165                 @FloatRange(from = 1.f) float scaleFactor) {
166             if (binCount < 1) {
167                 throw new IllegalArgumentException("Bin count should be positive number");
168             }
169 
170             if (firstBinWidth < 1.f) {
171                 throw new IllegalArgumentException(
172                         "First bin width invalid (should be 1.f at minimum)");
173             }
174 
175             if (scaleFactor < 1.f) {
176                 throw new IllegalArgumentException(
177                         "Scaled factor invalid (should be 1.f at minimum)");
178             }
179 
180             // precalculating bins ranges (no need to create a bin for underflow reference value)
181             mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
182         }
183 
184         @Override
getBinsCount()185         public int getBinsCount() {
186             return mBins.length + 1;
187         }
188 
189         @Override
getBinForSample(float sample)190         public int getBinForSample(float sample) {
191             if (sample < mBins[0]) {
192                 // goes to underflow
193                 return 0;
194             } else if (sample >= mBins[mBins.length - 1]) {
195                 // goes to overflow
196                 return mBins.length;
197             }
198 
199             return lower_bound(mBins, (long) sample) + 1;
200         }
201 
202         // To find lower bound using binary search implementation of Arrays utility class
lower_bound(long[] array, long sample)203         private static int lower_bound(long[] array, long sample) {
204             int index = Arrays.binarySearch(array, sample);
205             // If key is not present in the array
206             if (index < 0) {
207                 // Index specify the position of the key when inserted in the sorted array
208                 // so the element currently present at this position will be the lower bound
209                 return Math.abs(index) - 2;
210             }
211             return index;
212         }
213 
initBins(int count, int minValue, float firstBinWidth, float scaleFactor)214         private static long[] initBins(int count, int minValue, float firstBinWidth,
215                 float scaleFactor) {
216             long[] bins = new long[count];
217             bins[0] = minValue;
218             double lastWidth = firstBinWidth;
219             for (int i = 1; i < count; i++) {
220                 // current bin minValue = previous bin width * scaleFactor
221                 double currentBinMinValue = bins[i - 1] + lastWidth;
222                 if (currentBinMinValue > Integer.MAX_VALUE) {
223                     throw new IllegalArgumentException(
224                         "Attempted to create a bucket larger than maxint");
225                 }
226 
227                 bins[i] = (long) currentBinMinValue;
228                 lastWidth *= scaleFactor;
229             }
230             return bins;
231         }
232     }
233 }
234