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