1 /*
2  * Copyright (C) 2021 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.internal.os;
18 
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 
22 import com.android.internal.util.Preconditions;
23 
24 import dalvik.annotation.optimization.CriticalNative;
25 import dalvik.annotation.optimization.FastNative;
26 
27 import libcore.util.NativeAllocationRegistry;
28 
29 import java.util.Arrays;
30 import java.util.concurrent.atomic.AtomicReference;
31 
32 /**
33  * Performs per-state counting of multi-element values over time. The class' behavior is illustrated
34  * by this example:
35  * <pre>
36  *   // At 0 ms, the state of the tracked object is 0
37  *   counter.setState(0, 0);
38  *
39  *   // At 1000 ms, the state changes to 1
40  *   counter.setState(1, 1000);
41  *
42  *   // At 3000 ms, the tracked values are updated to {30, 300}
43  *   arrayContainer.setValues(new long[]{{30, 300}};
44  *   counter.updateValues(arrayContainer, 3000);
45  *
46  *   // The values are distributed between states 0 and 1 according to the time
47  *   // spent in those respective states. In this specific case, 1000 and 2000 ms.
48  *   counter.getValues(arrayContainer, 0);
49  *   // arrayContainer now has values {10, 100}
50  *   counter.getValues(arrayContainer, 1);
51  *   // arrayContainer now has values {20, 200}
52  * </pre>
53  *
54  * The tracked values are expected to increase monotonically.
55  *
56  * @hide
57  */
58 public final class LongArrayMultiStateCounter implements Parcelable {
59 
60     /**
61      * Container for a native equivalent of a long[].
62      */
63     public static class LongArrayContainer {
64         private static final NativeAllocationRegistry sRegistry =
65                 NativeAllocationRegistry.createMalloced(
66                         LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
67 
68         // Visible to other objects in this package so that it can be passed to @CriticalNative
69         // methods.
70         final long mNativeObject;
71         private final int mLength;
72 
LongArrayContainer(int length)73         public LongArrayContainer(int length) {
74             mLength = length;
75             mNativeObject = native_init(length);
76             sRegistry.registerNativeAllocation(this, mNativeObject);
77         }
78 
79         /**
80          * Copies the supplied values into the underlying native array.
81          */
setValues(long[] array)82         public void setValues(long[] array) {
83             if (array.length != mLength) {
84                 throw new IllegalArgumentException(
85                         "Invalid array length: " + mLength + ", expected: " + mLength);
86             }
87             native_setValues(mNativeObject, array);
88         }
89 
90         /**
91          * Copies the underlying native array values to the supplied array.
92          */
getValues(long[] array)93         public void getValues(long[] array) {
94             if (array.length != mLength) {
95                 throw new IllegalArgumentException(
96                         "Invalid array length: " + mLength + ", expected: " + mLength);
97             }
98             native_getValues(mNativeObject, array);
99         }
100 
101         /**
102          * Combines contained values into a smaller array by aggregating them
103          * according to an index map.
104          */
combineValues(long[] array, int[] indexMap)105         public boolean combineValues(long[] array, int[] indexMap) {
106             if (indexMap.length != mLength) {
107                 throw new IllegalArgumentException(
108                         "Wrong index map size " + indexMap.length + ", expected " + mLength);
109             }
110             return native_combineValues(mNativeObject, array, indexMap);
111         }
112 
113         @Override
toString()114         public String toString() {
115             final long[] array = new long[mLength];
116             getValues(array);
117             return Arrays.toString(array);
118         }
119 
120         @CriticalNative
native_init(int length)121         private static native long native_init(int length);
122 
123         @CriticalNative
native_getReleaseFunc()124         private static native long native_getReleaseFunc();
125 
126         @FastNative
native_setValues(long nativeObject, long[] array)127         private native void native_setValues(long nativeObject, long[] array);
128 
129         @FastNative
native_getValues(long nativeObject, long[] array)130         private native void native_getValues(long nativeObject, long[] array);
131 
132         @FastNative
native_combineValues(long nativeObject, long[] array, int[] indexMap)133         private native boolean native_combineValues(long nativeObject, long[] array,
134                 int[] indexMap);
135     }
136 
137     private static final NativeAllocationRegistry sRegistry =
138             NativeAllocationRegistry.createMalloced(
139                     LongArrayMultiStateCounter.class.getClassLoader(), native_getReleaseFunc());
140     private static final AtomicReference<LongArrayContainer> sTmpArrayContainer =
141             new AtomicReference<>();
142 
143     private final int mStateCount;
144     private final int mLength;
145 
146     // Visible to other objects in this package so that it can be passed to @CriticalNative
147     // methods.
148     final long mNativeObject;
149 
LongArrayMultiStateCounter(int stateCount, int arrayLength)150     public LongArrayMultiStateCounter(int stateCount, int arrayLength) {
151         Preconditions.checkArgumentPositive(stateCount, "stateCount must be greater than 0");
152         mStateCount = stateCount;
153         mLength = arrayLength;
154         mNativeObject = native_init(stateCount, arrayLength);
155         sRegistry.registerNativeAllocation(this, mNativeObject);
156     }
157 
LongArrayMultiStateCounter(Parcel in)158     private LongArrayMultiStateCounter(Parcel in) {
159         mNativeObject = native_initFromParcel(in);
160         sRegistry.registerNativeAllocation(this, mNativeObject);
161 
162         mStateCount = native_getStateCount(mNativeObject);
163         mLength = native_getArrayLength(mNativeObject);
164     }
165 
getStateCount()166     public int getStateCount() {
167         return mStateCount;
168     }
169 
getArrayLength()170     public int getArrayLength() {
171         return mLength;
172     }
173 
174     /**
175      * Enables or disables the counter.  When the counter is disabled, it does not
176      * accumulate counts supplied by the {@link #updateValues} method.
177      */
setEnabled(boolean enabled, long timestampMs)178     public void setEnabled(boolean enabled, long timestampMs) {
179         native_setEnabled(mNativeObject, enabled, timestampMs);
180     }
181 
182     /**
183      * Sets the current state to the supplied value.
184      */
setState(int state, long timestampMs)185     public void setState(int state, long timestampMs) {
186         if (state < 0 || state >= mStateCount) {
187             throw new IllegalArgumentException(
188                     "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]");
189         }
190         native_setState(mNativeObject, state, timestampMs);
191     }
192 
193     /**
194      * Sets the new values.  The delta between the previously set values and these values
195      * is distributed among the state according to the time the object spent in those states
196      * since the previous call to updateValues.
197      */
updateValues(LongArrayContainer longArrayContainer, long timestampMs)198     public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) {
199         if (longArrayContainer.mLength != mLength) {
200             throw new IllegalArgumentException(
201                     "Invalid array length: " + longArrayContainer.mLength + ", expected: "
202                             + mLength);
203         }
204         native_updateValues(mNativeObject, longArrayContainer.mNativeObject, timestampMs);
205     }
206 
207     /**
208      * Adds the supplied values to the current accumulated values in the counter.
209      */
addCounts(LongArrayContainer counts)210     public void addCounts(LongArrayContainer counts) {
211         if (counts.mLength != mLength) {
212             throw new IllegalArgumentException(
213                     "Invalid array length: " + counts.mLength + ", expected: " + mLength);
214         }
215         native_addCounts(mNativeObject, counts.mNativeObject);
216     }
217 
218     /**
219      * Resets the accumulated counts to 0.
220      */
reset()221     public void reset() {
222         native_reset(mNativeObject);
223     }
224 
225     /**
226      * Populates the array with the accumulated counts for the specified state.
227      */
getCounts(long[] counts, int state)228     public void getCounts(long[] counts, int state) {
229         LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
230         if (container == null || container.mLength != counts.length) {
231             container = new LongArrayContainer(counts.length);
232         }
233         getCounts(container, state);
234         container.getValues(counts);
235         sTmpArrayContainer.set(container);
236     }
237 
238     /**
239      * Populates longArrayContainer with the accumulated counts for the specified state.
240      */
getCounts(LongArrayContainer longArrayContainer, int state)241     public void getCounts(LongArrayContainer longArrayContainer, int state) {
242         if (state < 0 || state >= mStateCount) {
243             throw new IllegalArgumentException(
244                     "State: " + state + ", outside the range: [0-" + mStateCount + "]");
245         }
246         native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state);
247     }
248 
249     @Override
toString()250     public String toString() {
251         return native_toString(mNativeObject);
252     }
253 
254     @Override
writeToParcel(Parcel dest, int flags)255     public void writeToParcel(Parcel dest, int flags) {
256         native_writeToParcel(mNativeObject, dest, flags);
257     }
258 
259     @Override
describeContents()260     public int describeContents() {
261         return 0;
262     }
263 
264     public static final Creator<LongArrayMultiStateCounter> CREATOR =
265             new Creator<LongArrayMultiStateCounter>() {
266                 @Override
267                 public LongArrayMultiStateCounter createFromParcel(Parcel in) {
268                     return new LongArrayMultiStateCounter(in);
269                 }
270 
271                 @Override
272                 public LongArrayMultiStateCounter[] newArray(int size) {
273                     return new LongArrayMultiStateCounter[size];
274                 }
275             };
276 
277 
278     @CriticalNative
native_init(int stateCount, int arrayLength)279     private static native long native_init(int stateCount, int arrayLength);
280 
281     @CriticalNative
native_getReleaseFunc()282     private static native long native_getReleaseFunc();
283 
284     @CriticalNative
native_setEnabled(long nativeObject, boolean enabled, long timestampMs)285     private static native void native_setEnabled(long nativeObject, boolean enabled,
286             long timestampMs);
287 
288     @CriticalNative
native_setState(long nativeObject, int state, long timestampMs)289     private static native void native_setState(long nativeObject, int state, long timestampMs);
290 
291     @CriticalNative
native_updateValues(long nativeObject, long longArrayContainerNativeObject, long timestampMs)292     private static native void native_updateValues(long nativeObject,
293             long longArrayContainerNativeObject, long timestampMs);
294 
295     @CriticalNative
native_addCounts(long nativeObject, long longArrayContainerNativeObject)296     private static native void native_addCounts(long nativeObject,
297             long longArrayContainerNativeObject);
298 
299     @CriticalNative
native_reset(long nativeObject)300     private static native void native_reset(long nativeObject);
301 
302     @CriticalNative
native_getCounts(long nativeObject, long longArrayContainerNativeObject, int state)303     private static native void native_getCounts(long nativeObject,
304             long longArrayContainerNativeObject, int state);
305 
306     @FastNative
native_toString(long nativeObject)307     private native String native_toString(long nativeObject);
308 
309     @FastNative
native_writeToParcel(long nativeObject, Parcel dest, int flags)310     private native void native_writeToParcel(long nativeObject, Parcel dest, int flags);
311 
312     @FastNative
native_initFromParcel(Parcel parcel)313     private static native long native_initFromParcel(Parcel parcel);
314 
315     @CriticalNative
native_getStateCount(long nativeObject)316     private static native int native_getStateCount(long nativeObject);
317 
318     @CriticalNative
native_getArrayLength(long nativeObject)319     private static native int native_getArrayLength(long nativeObject);
320 }
321