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