1 /*
2  * Copyright (C) 2013 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.app.procstats;
18 
19 import android.os.Build;
20 import android.os.Parcel;
21 import android.util.Slog;
22 import libcore.util.EmptyArray;
23 
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 
27 import com.android.internal.util.GrowingArrayUtils;
28 
29 /**
30  * Class that contains a set of tables mapping byte ids to long values.
31  *
32  * This class is used to store the ProcessStats data.  This data happens to be
33  * a set of very sparse tables, that is mostly append or overwrite, with infrequent
34  * resets of the data.
35  *
36  * Data is stored as a list of large long[] arrays containing the actual values.  There are a
37  * set of Table objects that each contain a small array mapping the byte IDs to a position
38  * in the larger arrays.
39  *
40  * The data itself is either a single long value or a range of long values which are always
41  * stored continguously in one of the long arrays. When the caller allocates a slot with
42  * getOrAddKey, an int key is returned.  That key can be re-retreived with getKey without
43  * allocating the value.  The data can then be set or retrieved with that key.
44  */
45 public class SparseMappingTable {
46     private static final String TAG = "SparseMappingTable";
47 
48     // How big each array is.
49     public static final int ARRAY_SIZE = 4096;
50 
51     public static final int INVALID_KEY = 0xffffffff;
52 
53     // Where the "type"/"state" part of the data appears in an offset integer.
54     private static final int ID_SHIFT = 0;
55     private static final int ID_MASK = 0xff;
56     // Where the "which array" part of the data appears in an offset integer.
57     private static final int ARRAY_SHIFT = 8;
58     private static final int ARRAY_MASK = 0xff;
59     // Where the "index into array" part of the data appears in an offset integer.
60     private static final int INDEX_SHIFT = 16;
61     private static final int INDEX_MASK = 0xffff;
62 
63     private int mSequence;
64     private int mNextIndex;
65     private final ArrayList<long[]> mLongs = new ArrayList<long[]>();
66 
67     /**
68      * A table of data as stored in a SparseMappingTable.
69      */
70     public static class Table {
71         private SparseMappingTable mParent;
72         private int mSequence = 1;
73         private int[] mTable;
74         private int mSize;
75 
Table(SparseMappingTable parent)76         public Table(SparseMappingTable parent) {
77             mParent = parent;
78             mSequence = parent.mSequence;
79         }
80 
81         /**
82          * Pulls the data from 'copyFrom' and stores it in our own longs table.
83          *
84          * @param copyFrom   The Table to copy from
85          * @param valueCount The number of values to copy for each key
86          */
copyFrom(Table copyFrom, int valueCount)87         public void copyFrom(Table copyFrom, int valueCount) {
88             mTable = null;
89             mSize = 0;
90 
91             final int N = copyFrom.getKeyCount();
92             for (int i=0; i<N; i++) {
93                 final int theirKey = copyFrom.getKeyAt(i);
94                 final long[] theirLongs = copyFrom.mParent.mLongs.get(getArrayFromKey(theirKey));
95 
96                 final byte id = SparseMappingTable.getIdFromKey(theirKey);
97 
98                 final int myKey = this.getOrAddKey((byte)id, valueCount);
99                 final long[] myLongs = mParent.mLongs.get(getArrayFromKey(myKey));
100 
101                 System.arraycopy(theirLongs, getIndexFromKey(theirKey),
102                         myLongs, getIndexFromKey(myKey), valueCount);
103             }
104         }
105 
106         /**
107          * Allocates data in the buffer, and stores that key in the mapping for this
108          * table.
109          *
110          * @param id    The id of the item (will be used in making the key)
111          * @param count The number of bytes to allocate.  Must be less than
112          *              SparseMappingTable.ARRAY_SIZE.
113          *
114          * @return The 'key' for this data value, which contains both the id itself
115          *         and the location in the long arrays that the data is actually stored
116          *         but should be considered opaque to the caller.
117          */
getOrAddKey(byte id, int count)118         public int getOrAddKey(byte id, int count) {
119             assertConsistency();
120 
121             final int idx = binarySearch(id);
122             if (idx >= 0) {
123                 // Found
124                 return mTable[idx];
125             } else {
126                 // Not found. Need to allocate it.
127 
128                 // Get an array with enough space to store 'count' values.
129                 final ArrayList<long[]> list = mParent.mLongs;
130                 int whichArray = list.size()-1;
131                 long[] array = list.get(whichArray);
132                 if (mParent.mNextIndex + count > array.length) {
133                     // if it won't fit then make a new array.
134                     array = new long[ARRAY_SIZE];
135                     list.add(array);
136                     whichArray++;
137                     mParent.mNextIndex = 0;
138                 }
139 
140                 // The key is a combination of whichArray, which index in that array, and
141                 // the table value itself, which will be used for lookup
142                 final int key = (whichArray << ARRAY_SHIFT)
143                         | (mParent.mNextIndex << INDEX_SHIFT)
144                         | (((int)id) << ID_SHIFT);
145 
146                 mParent.mNextIndex += count;
147 
148                 // Store the key in the sparse lookup table for this Table object.
149                 mTable = GrowingArrayUtils.insert(mTable != null ? mTable : EmptyArray.INT,
150                         mSize, ~idx, key);
151                 mSize++;
152 
153                 return key;
154             }
155         }
156 
157         /**
158          * Looks up a key in the table.
159          *
160          * @return The key from this table or INVALID_KEY if the id is not found.
161          */
getKey(byte id)162         public int getKey(byte id) {
163             assertConsistency();
164 
165             final int idx = binarySearch(id);
166             if (idx >= 0) {
167                 return mTable[idx];
168             } else {
169                 return INVALID_KEY;
170             }
171         }
172 
173         /**
174          * Get the value for the given key and offset from that key.
175          *
176          * @param key   A key as obtained from getKey or getOrAddKey.
177          * @param value The value to set.
178          */
getValue(int key)179         public long getValue(int key) {
180             return getValue(key, 0);
181         }
182 
183         /**
184          * Get the value for the given key and offset from that key.
185          *
186          * @param key   A key as obtained from getKey or getOrAddKey.
187          * @param index The offset from that key.  Must be less than the count
188          *              provided to getOrAddKey when the space was allocated.
189          * @param value The value to set.
190          *
191          * @return the value, or 0 in case of an error
192          */
getValue(int key, int index)193         public long getValue(int key, int index) {
194             assertConsistency();
195 
196             try {
197                 final long[] array = mParent.mLongs.get(getArrayFromKey(key));
198                 return array[getIndexFromKey(key) + index];
199             } catch (IndexOutOfBoundsException ex) {
200                 logOrThrow("key=0x" + Integer.toHexString(key)
201                         + " index=" + index + " -- " + dumpInternalState(), ex);
202                 return 0;
203             }
204         }
205 
206         /**
207          * Set the value for the given id at offset 0 from that id.
208          * If the id is not found, return 0 instead.
209          *
210          * @param id    The id of the item.
211          */
getValueForId(byte id)212         public long getValueForId(byte id) {
213             return getValueForId(id, 0);
214         }
215 
216         /**
217          * Set the value for the given id and index offset from that id.
218          * If the id is not found, return 0 instead.
219          *
220          * @param id    The id of the item.
221          * @param index The offset from that key.  Must be less than the count
222          *              provided to getOrAddKey when the space was allocated.
223          */
getValueForId(byte id, int index)224         public long getValueForId(byte id, int index) {
225             assertConsistency();
226 
227             final int idx = binarySearch(id);
228             if (idx >= 0) {
229                 final int key = mTable[idx];
230                 try {
231                     final long[] array = mParent.mLongs.get(getArrayFromKey(key));
232                     return array[getIndexFromKey(key) + index];
233                 } catch (IndexOutOfBoundsException ex) {
234                     logOrThrow("id=0x" + Integer.toHexString(id) + " idx=" + idx
235                             + " key=0x" + Integer.toHexString(key) + " index=" + index
236                             + " -- " + dumpInternalState(), ex);
237                     return 0;
238                 }
239             } else {
240                 return 0;
241             }
242         }
243 
244         /**
245          * Return the raw storage long[] for the given key.
246          */
getArrayForKey(int key)247         public long[] getArrayForKey(int key) {
248             assertConsistency();
249 
250             return mParent.mLongs.get(getArrayFromKey(key));
251         }
252 
253         /**
254          * Set the value for the given key and offset from that key.
255          *
256          * @param key   A key as obtained from getKey or getOrAddKey.
257          * @param value The value to set.
258          */
setValue(int key, long value)259         public void setValue(int key, long value) {
260             setValue(key, 0, value);
261         }
262 
263         /**
264          * Set the value for the given key and offset from that key.
265          *
266          * @param key   A key as obtained from getKey or getOrAddKey.
267          * @param index The offset from that key.  Must be less than the count
268          *              provided to getOrAddKey when the space was allocated.
269          * @param value The value to set.
270          */
setValue(int key, int index, long value)271         public void setValue(int key, int index, long value) {
272             assertConsistency();
273 
274             if (value < 0) {
275                 logOrThrow("can't store negative values"
276                         + " key=0x" + Integer.toHexString(key)
277                         + " index=" + index + " value=" + value
278                         + " -- " + dumpInternalState());
279                 return;
280             }
281 
282             try {
283                 final long[] array = mParent.mLongs.get(getArrayFromKey(key));
284                 array[getIndexFromKey(key) + index] = value;
285             } catch (IndexOutOfBoundsException ex) {
286                 logOrThrow("key=0x" + Integer.toHexString(key)
287                         + " index=" + index + " value=" + value
288                         + " -- " + dumpInternalState(), ex);
289                 return;
290             }
291         }
292 
293         /**
294          * Clear out the table, and reset the sequence numbers so future writes
295          * without allocations will assert.
296          */
resetTable()297         public void resetTable() {
298             // Clear out our table.
299             mTable = null;
300             mSize = 0;
301 
302             // Reset our sequence number.  This will make all read/write calls
303             // start to fail, and then when we re-allocate it will be re-synced
304             // to that of mParent.
305             mSequence = mParent.mSequence;
306         }
307 
308         /**
309          * Write the keys stored in the table to the parcel. The parent must
310          * be separately written. Does not save the actual data.
311          */
writeToParcel(Parcel out)312         public void writeToParcel(Parcel out) {
313             out.writeInt(mSequence);
314             out.writeInt(mSize);
315             for (int i=0; i<mSize; i++) {
316                 out.writeInt(mTable[i]);
317             }
318         }
319 
320         /**
321          * Read the keys from the parcel. The parent (with its long array) must
322          * have been previously initialized.
323          */
readFromParcel(Parcel in)324         public boolean readFromParcel(Parcel in) {
325             // Read the state
326             mSequence = in.readInt();
327             mSize = in.readInt();
328             if (mSize != 0) {
329                 mTable = new int[mSize];
330                 for (int i=0; i<mSize; i++) {
331                     mTable[i] = in.readInt();
332                 }
333             } else {
334                 mTable = null;
335             }
336 
337             // Make sure we're all healthy
338             if (validateKeys(true)) {
339                 return true;
340             } else {
341                 // Clear it out
342                 mSize = 0;
343                 mTable = null;
344                 return false;
345             }
346         }
347 
348         /**
349          * Return the number of keys that have been added to this Table.
350          */
getKeyCount()351         public int getKeyCount() {
352             return mSize;
353         }
354 
355         /**
356          * Get the key at the given index in our table.
357          */
getKeyAt(int i)358         public int getKeyAt(int i) {
359             return mTable[i];
360         }
361 
362         /**
363          * Throw an exception if one of a variety of internal consistency checks fails.
364          */
assertConsistency()365         private void assertConsistency() {
366             // Something with this checking isn't working and is triggering
367             // more problems than it's helping to debug.
368             //   Original bug: b/27045736
369             //   New bug: b/27960286
370             if (false) {
371                 // Assert that our sequence number matches mParent's.  If it isn't that means
372                 // we have been reset and our.  If our sequence is UNITIALIZED_SEQUENCE, then
373                 // it's possible that everything is working fine and we just haven't been
374                 // written to since the last resetTable().
375                 if (mSequence != mParent.mSequence) {
376                     if (mSequence < mParent.mSequence) {
377                         logOrThrow("Sequence mismatch. SparseMappingTable.reset()"
378                                 + " called but not Table.resetTable() -- "
379                                 + dumpInternalState());
380                         return;
381                     } else if (mSequence > mParent.mSequence) {
382                         logOrThrow("Sequence mismatch. Table.resetTable()"
383                                 + " called but not SparseMappingTable.reset() -- "
384                                 + dumpInternalState());
385                         return;
386                     }
387                 }
388             }
389         }
390 
391         /**
392          * Finds the 'id' inside the array of length size (physical size of the array
393          * is not used).
394          *
395          * @return The index of the array, or the bitwise not (~index) of where it
396          * would go if you wanted to insert 'id' into the array.
397          */
binarySearch(byte id)398         private int binarySearch(byte id) {
399             int lo = 0;
400             int hi = mSize - 1;
401 
402             while (lo <= hi) {
403                 int mid = (lo + hi) >>> 1;
404                 byte midId = (byte)((mTable[mid] >> ID_SHIFT) & ID_MASK);
405 
406                 if (midId < id) {
407                     lo = mid + 1;
408                 } else if (midId > id) {
409                     hi = mid - 1;
410                 } else {
411                     return mid;  // id found
412                 }
413             }
414             return ~lo;  // id not present
415         }
416 
417         /**
418          * Check that all the keys are valid locations in the long arrays.
419          *
420          * If any aren't, log it and return false. Else return true.
421          */
validateKeys(boolean log)422         private boolean validateKeys(boolean log) {
423             ArrayList<long[]> longs = mParent.mLongs;
424             final int longsSize = longs.size();
425 
426             final int N = mSize;
427             for (int i=0; i<N; i++) {
428                 final int key = mTable[i];
429                 final int arrayIndex = getArrayFromKey(key);
430                 final int index = getIndexFromKey(key);
431                 if (arrayIndex >= longsSize || index >= longs.get(arrayIndex).length) {
432                     if (log) {
433                         Slog.w(TAG, "Invalid stats at index " + i + " -- " + dumpInternalState());
434                     }
435                     return false;
436                 }
437             }
438 
439             return true;
440         }
441 
dumpInternalState()442         public String dumpInternalState() {
443             StringBuilder sb = new StringBuilder();
444             sb.append("SparseMappingTable.Table{mSequence=");
445             sb.append(mSequence);
446             sb.append(" mParent.mSequence=");
447             sb.append(mParent.mSequence);
448             sb.append(" mParent.mLongs.size()=");
449             sb.append(mParent.mLongs.size());
450             sb.append(" mSize=");
451             sb.append(mSize);
452             sb.append(" mTable=");
453             if (mTable == null) {
454                 sb.append("null");
455             } else {
456                 final int N = mTable.length;
457                 sb.append('[');
458                 for (int i=0; i<N; i++) {
459                     final int key = mTable[i];
460                     sb.append("0x");
461                     sb.append(Integer.toHexString((key >> ID_SHIFT) & ID_MASK));
462                     sb.append("/0x");
463                     sb.append(Integer.toHexString((key >> ARRAY_SHIFT) & ARRAY_MASK));
464                     sb.append("/0x");
465                     sb.append(Integer.toHexString((key >> INDEX_SHIFT) & INDEX_MASK));
466                     if (i != N-1) {
467                         sb.append(", ");
468                     }
469                 }
470                 sb.append(']');
471             }
472             sb.append(" clazz=");
473             sb.append(getClass().getName());
474             sb.append('}');
475 
476             return sb.toString();
477         }
478     }
479 
SparseMappingTable()480     public SparseMappingTable() {
481         mLongs.add(new long[ARRAY_SIZE]);
482     }
483 
484     /**
485      * Wipe out all the data.
486      */
reset()487     public void reset() {
488         // Clear out mLongs, and prime it with a new array of data
489         mLongs.clear();
490         mLongs.add(new long[ARRAY_SIZE]);
491         mNextIndex = 0;
492 
493         // Increment out sequence counter, because all of the tables will
494         // now be out of sync with the data.
495         mSequence++;
496     }
497 
498     /**
499      * Write the data arrays to the parcel.
500      */
writeToParcel(Parcel out)501     public void writeToParcel(Parcel out) {
502         out.writeInt(mSequence);
503         out.writeInt(mNextIndex);
504         final int N = mLongs.size();
505         out.writeInt(N);
506         for (int i=0; i<N-1; i++) {
507             final long[] array = mLongs.get(i);
508             out.writeInt(array.length);
509             writeCompactedLongArray(out, array, array.length);
510         }
511         // save less for the last one. upon re-loading they'll just start a new array.
512         final long[] lastLongs = mLongs.get(N-1);
513         out.writeInt(mNextIndex);
514         writeCompactedLongArray(out, lastLongs, mNextIndex);
515     }
516 
517     /**
518      * Read the data arrays from the parcel.
519      */
readFromParcel(Parcel in)520     public void readFromParcel(Parcel in) {
521         mSequence = in.readInt();
522         mNextIndex = in.readInt();
523 
524         mLongs.clear();
525         final int N = in.readInt();
526         for (int i=0; i<N; i++) {
527             final int size = in.readInt();
528             final long[] array = new long[size];
529             readCompactedLongArray(in, array, size);
530             mLongs.add(array);
531         }
532     }
533 
534     /**
535      * Return a string for debugging.
536      */
dumpInternalState(boolean includeData)537     public String dumpInternalState(boolean includeData) {
538         final StringBuilder sb = new StringBuilder();
539         sb.append("SparseMappingTable{");
540         sb.append("mSequence=");
541         sb.append(mSequence);
542         sb.append(" mNextIndex=");
543         sb.append(mNextIndex);
544         sb.append(" mLongs.size=");
545         final int N = mLongs.size();
546         sb.append(N);
547         sb.append("\n");
548         if (includeData) {
549             for (int i=0; i<N; i++) {
550                 final long[] array = mLongs.get(i);
551                 for (int j=0; j<array.length; j++) {
552                     if (i == N-1 && j == mNextIndex) {
553                         break;
554                     }
555                     sb.append(String.format(" %4d %d 0x%016x %-19d\n", i, j, array[j], array[j]));
556                 }
557             }
558         }
559         sb.append("}");
560         return sb.toString();
561     }
562 
563     /**
564      * Write the long array to the parcel in a compacted form.  Does not allow negative
565      * values in the array.
566      */
writeCompactedLongArray(Parcel out, long[] array, int num)567     private static void writeCompactedLongArray(Parcel out, long[] array, int num) {
568         for (int i=0; i<num; i++) {
569             long val = array[i];
570             if (val < 0) {
571                 Slog.w(TAG, "Time val negative: " + val);
572                 val = 0;
573             }
574             if (val <= Integer.MAX_VALUE) {
575                 out.writeInt((int)val);
576             } else {
577                 int top = ~((int)((val>>32)&0x7fffffff));
578                 int bottom = (int)(val&0x0ffffffffL);
579                 out.writeInt(top);
580                 out.writeInt(bottom);
581             }
582         }
583     }
584 
585     /**
586      * Read the compacted array into the long[].
587      */
readCompactedLongArray(Parcel in, long[] array, int num)588     private static void readCompactedLongArray(Parcel in, long[] array, int num) {
589         final int alen = array.length;
590         if (num > alen) {
591             logOrThrow("bad array lengths: got " + num + " array is " + alen);
592             return;
593         }
594         int i;
595         for (i=0; i<num; i++) {
596             int val = in.readInt();
597             if (val >= 0) {
598                 array[i] = val;
599             } else {
600                 int bottom = in.readInt();
601                 array[i] = (((long)~val)<<32) | bottom;
602             }
603         }
604         while (i < alen) {
605             array[i] = 0;
606             i++;
607         }
608     }
609 
610     /**
611      * Extract the id from a key.
612      */
getIdFromKey(int key)613     public static byte getIdFromKey(int key) {
614         return (byte)((key >> ID_SHIFT) & ID_MASK);
615     }
616 
617     /**
618      * Gets the index of the array in the list of arrays.
619      *
620      * Not to be confused with getIndexFromKey.
621      */
getArrayFromKey(int key)622     public static int getArrayFromKey(int key) {
623         return (key >> ARRAY_SHIFT) & ARRAY_MASK;
624     }
625 
626     /**
627      * Gets the index of a value in a long[].
628      *
629      * Not to be confused with getArrayFromKey.
630      */
getIndexFromKey(int key)631     public static int getIndexFromKey(int key) {
632         return (key >> INDEX_SHIFT) & INDEX_MASK;
633     }
634 
635     /**
636      * Do a Slog.wtf or throw an exception (thereby crashing the system process if
637      * this is a debug build.)
638      */
logOrThrow(String message)639     private static void logOrThrow(String message) {
640         logOrThrow(message, new RuntimeException("Stack trace"));
641     }
642 
643     /**
644      * Do a Slog.wtf or throw an exception (thereby crashing the system process if
645      * this is an eng build.)
646      */
logOrThrow(String message, Throwable th)647     private static void logOrThrow(String message, Throwable th) {
648         Slog.e(TAG, message, th);
649         if (Build.TYPE.equals("eng")) {
650             throw new RuntimeException(message, th);
651         }
652     }
653 }
654