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          * @param value The value to set.
179          */
getValue(int key)180         public long getValue(int key) {
181             return getValue(key, 0);
182         }
183 
184         /**
185          * Get the value for the given key and offset from that key.
186          *
187          * @param key   A key as obtained from getKey or getOrAddKey.
188          * @param index The offset from that key.  Must be less than the count
189          *              provided to getOrAddKey when the space was allocated.
190          * @param value The value to set.
191          *
192          * @return the value, or 0 in case of an error
193          */
getValue(int key, int index)194         public long getValue(int key, int index) {
195             assertConsistency();
196 
197             try {
198                 final long[] array = mParent.mLongs.get(getArrayFromKey(key));
199                 return array[getIndexFromKey(key) + index];
200             } catch (IndexOutOfBoundsException ex) {
201                 logOrThrow("key=0x" + Integer.toHexString(key)
202                         + " index=" + index + " -- " + dumpInternalState(), ex);
203                 return 0;
204             }
205         }
206 
207         /**
208          * Set the value for the given id at offset 0 from that id.
209          * If the id is not found, return 0 instead.
210          *
211          * @param id    The id of the item.
212          */
getValueForId(byte id)213         public long getValueForId(byte id) {
214             return getValueForId(id, 0);
215         }
216 
217         /**
218          * Set the value for the given id and index offset from that id.
219          * If the id is not found, return 0 instead.
220          *
221          * @param id    The id of the item.
222          * @param index The offset from that key.  Must be less than the count
223          *              provided to getOrAddKey when the space was allocated.
224          */
getValueForId(byte id, int index)225         public long getValueForId(byte id, int index) {
226             assertConsistency();
227 
228             final int idx = binarySearch(id);
229             if (idx >= 0) {
230                 final int key = mTable[idx];
231                 try {
232                     final long[] array = mParent.mLongs.get(getArrayFromKey(key));
233                     return array[getIndexFromKey(key) + index];
234                 } catch (IndexOutOfBoundsException ex) {
235                     logOrThrow("id=0x" + Integer.toHexString(id) + " idx=" + idx
236                             + " key=0x" + Integer.toHexString(key) + " index=" + index
237                             + " -- " + dumpInternalState(), ex);
238                     return 0;
239                 }
240             } else {
241                 return 0;
242             }
243         }
244 
245         /**
246          * Return the raw storage long[] for the given key.
247          */
getArrayForKey(int key)248         public long[] getArrayForKey(int key) {
249             assertConsistency();
250 
251             return mParent.mLongs.get(getArrayFromKey(key));
252         }
253 
254         /**
255          * Set the value for the given key and offset from that key.
256          *
257          * @param key   A key as obtained from getKey or getOrAddKey.
258          * @param value The value to set.
259          */
setValue(int key, long value)260         public void setValue(int key, long value) {
261             setValue(key, 0, value);
262         }
263 
264         /**
265          * Set the value for the given key and offset from that key.
266          *
267          * @param key   A key as obtained from getKey or getOrAddKey.
268          * @param index The offset from that key.  Must be less than the count
269          *              provided to getOrAddKey when the space was allocated.
270          * @param value The value to set.
271          */
setValue(int key, int index, long value)272         public void setValue(int key, int index, long value) {
273             assertConsistency();
274 
275             if (value < 0) {
276                 logOrThrow("can't store negative values"
277                         + " key=0x" + Integer.toHexString(key)
278                         + " index=" + index + " value=" + value
279                         + " -- " + dumpInternalState());
280                 return;
281             }
282 
283             try {
284                 final long[] array = mParent.mLongs.get(getArrayFromKey(key));
285                 array[getIndexFromKey(key) + index] = value;
286             } catch (IndexOutOfBoundsException ex) {
287                 logOrThrow("key=0x" + Integer.toHexString(key)
288                         + " index=" + index + " value=" + value
289                         + " -- " + dumpInternalState(), ex);
290                 return;
291             }
292         }
293 
294         /**
295          * Clear out the table, and reset the sequence numbers so future writes
296          * without allocations will assert.
297          */
resetTable()298         public void resetTable() {
299             // Clear out our table.
300             mTable = null;
301             mSize = 0;
302 
303             // Reset our sequence number.  This will make all read/write calls
304             // start to fail, and then when we re-allocate it will be re-synced
305             // to that of mParent.
306             mSequence = mParent.mSequence;
307         }
308 
309         /**
310          * Write the keys stored in the table to the parcel. The parent must
311          * be separately written. Does not save the actual data.
312          */
writeToParcel(Parcel out)313         public void writeToParcel(Parcel out) {
314             out.writeInt(mSequence);
315             out.writeInt(mSize);
316             for (int i=0; i<mSize; i++) {
317                 out.writeInt(mTable[i]);
318             }
319         }
320 
321         /**
322          * Read the keys from the parcel. The parent (with its long array) must
323          * have been previously initialized.
324          */
readFromParcel(Parcel in)325         public boolean readFromParcel(Parcel in) {
326             // Read the state
327             mSequence = in.readInt();
328             mSize = in.readInt();
329             if (mSize != 0) {
330                 mTable = new int[mSize];
331                 for (int i=0; i<mSize; i++) {
332                     mTable[i] = in.readInt();
333                 }
334             } else {
335                 mTable = null;
336             }
337 
338             // Make sure we're all healthy
339             if (validateKeys(true)) {
340                 return true;
341             } else {
342                 // Clear it out
343                 mSize = 0;
344                 mTable = null;
345                 return false;
346             }
347         }
348 
349         /**
350          * Return the number of keys that have been added to this Table.
351          */
getKeyCount()352         public int getKeyCount() {
353             return mSize;
354         }
355 
356         /**
357          * Get the key at the given index in our table.
358          */
getKeyAt(int i)359         public int getKeyAt(int i) {
360             return mTable[i];
361         }
362 
363         /**
364          * Throw an exception if one of a variety of internal consistency checks fails.
365          */
assertConsistency()366         private void assertConsistency() {
367             // Something with this checking isn't working and is triggering
368             // more problems than it's helping to debug.
369             //   Original bug: b/27045736
370             //   New bug: b/27960286
371             if (false) {
372                 // Assert that our sequence number matches mParent's.  If it isn't that means
373                 // we have been reset and our.  If our sequence is UNITIALIZED_SEQUENCE, then
374                 // it's possible that everything is working fine and we just haven't been
375                 // written to since the last resetTable().
376                 if (mSequence != mParent.mSequence) {
377                     if (mSequence < mParent.mSequence) {
378                         logOrThrow("Sequence mismatch. SparseMappingTable.reset()"
379                                 + " called but not Table.resetTable() -- "
380                                 + dumpInternalState());
381                         return;
382                     } else if (mSequence > mParent.mSequence) {
383                         logOrThrow("Sequence mismatch. Table.resetTable()"
384                                 + " called but not SparseMappingTable.reset() -- "
385                                 + dumpInternalState());
386                         return;
387                     }
388                 }
389             }
390         }
391 
392         /**
393          * Finds the 'id' inside the array of length size (physical size of the array
394          * is not used).
395          *
396          * @return The index of the array, or the bitwise not (~index) of where it
397          * would go if you wanted to insert 'id' into the array.
398          */
binarySearch(byte id)399         private int binarySearch(byte id) {
400             int lo = 0;
401             int hi = mSize - 1;
402 
403             while (lo <= hi) {
404                 int mid = (lo + hi) >>> 1;
405                 byte midId = (byte)((mTable[mid] >> ID_SHIFT) & ID_MASK);
406 
407                 if (midId < id) {
408                     lo = mid + 1;
409                 } else if (midId > id) {
410                     hi = mid - 1;
411                 } else {
412                     return mid;  // id found
413                 }
414             }
415             return ~lo;  // id not present
416         }
417 
418         /**
419          * Check that all the keys are valid locations in the long arrays.
420          *
421          * If any aren't, log it and return false. Else return true.
422          */
validateKeys(boolean log)423         private boolean validateKeys(boolean log) {
424             ArrayList<long[]> longs = mParent.mLongs;
425             final int longsSize = longs.size();
426 
427             final int N = mSize;
428             for (int i=0; i<N; i++) {
429                 final int key = mTable[i];
430                 final int arrayIndex = getArrayFromKey(key);
431                 final int index = getIndexFromKey(key);
432                 if (arrayIndex >= longsSize || index >= longs.get(arrayIndex).length) {
433                     if (log) {
434                         Slog.w(TAG, "Invalid stats at index " + i + " -- " + dumpInternalState());
435                     }
436                     return false;
437                 }
438             }
439 
440             return true;
441         }
442 
dumpInternalState()443         public String dumpInternalState() {
444             StringBuilder sb = new StringBuilder();
445             sb.append("SparseMappingTable.Table{mSequence=");
446             sb.append(mSequence);
447             sb.append(" mParent.mSequence=");
448             sb.append(mParent.mSequence);
449             sb.append(" mParent.mLongs.size()=");
450             sb.append(mParent.mLongs.size());
451             sb.append(" mSize=");
452             sb.append(mSize);
453             sb.append(" mTable=");
454             if (mTable == null) {
455                 sb.append("null");
456             } else {
457                 final int N = mTable.length;
458                 sb.append('[');
459                 for (int i=0; i<N; i++) {
460                     final int key = mTable[i];
461                     sb.append("0x");
462                     sb.append(Integer.toHexString((key >> ID_SHIFT) & ID_MASK));
463                     sb.append("/0x");
464                     sb.append(Integer.toHexString((key >> ARRAY_SHIFT) & ARRAY_MASK));
465                     sb.append("/0x");
466                     sb.append(Integer.toHexString((key >> INDEX_SHIFT) & INDEX_MASK));
467                     if (i != N-1) {
468                         sb.append(", ");
469                     }
470                 }
471                 sb.append(']');
472             }
473             sb.append(" clazz=");
474             sb.append(getClass().getName());
475             sb.append('}');
476 
477             return sb.toString();
478         }
479     }
480 
SparseMappingTable()481     public SparseMappingTable() {
482         mLongs.add(new long[ARRAY_SIZE]);
483     }
484 
485     /**
486      * Wipe out all the data.
487      */
reset()488     public void reset() {
489         // Clear out mLongs, and prime it with a new array of data
490         mLongs.clear();
491         mLongs.add(new long[ARRAY_SIZE]);
492         mNextIndex = 0;
493 
494         // Increment out sequence counter, because all of the tables will
495         // now be out of sync with the data.
496         mSequence++;
497     }
498 
499     /**
500      * Write the data arrays to the parcel.
501      */
writeToParcel(Parcel out)502     public void writeToParcel(Parcel out) {
503         out.writeInt(mSequence);
504         out.writeInt(mNextIndex);
505         final int N = mLongs.size();
506         out.writeInt(N);
507         for (int i=0; i<N-1; i++) {
508             final long[] array = mLongs.get(i);
509             out.writeInt(array.length);
510             writeCompactedLongArray(out, array, array.length);
511         }
512         // save less for the last one. upon re-loading they'll just start a new array.
513         final long[] lastLongs = mLongs.get(N-1);
514         out.writeInt(mNextIndex);
515         writeCompactedLongArray(out, lastLongs, mNextIndex);
516     }
517 
518     /**
519      * Read the data arrays from the parcel.
520      */
readFromParcel(Parcel in)521     public void readFromParcel(Parcel in) {
522         mSequence = in.readInt();
523         mNextIndex = in.readInt();
524 
525         mLongs.clear();
526         final int N = in.readInt();
527         for (int i=0; i<N; i++) {
528             final int size = in.readInt();
529             final long[] array = new long[size];
530             readCompactedLongArray(in, array, size);
531             mLongs.add(array);
532         }
533         // Verify that last array's length is consistent with writeToParcel
534         if (N > 0 && mLongs.get(N - 1).length != mNextIndex) {
535             EventLog.writeEvent(0x534e4554, "73252178", -1, "");
536             throw new IllegalStateException("Expected array of length " + mNextIndex + " but was "
537                     + mLongs.get(N - 1).length);
538         }
539     }
540 
541     /**
542      * Return a string for debugging.
543      */
dumpInternalState(boolean includeData)544     public String dumpInternalState(boolean includeData) {
545         final StringBuilder sb = new StringBuilder();
546         sb.append("SparseMappingTable{");
547         sb.append("mSequence=");
548         sb.append(mSequence);
549         sb.append(" mNextIndex=");
550         sb.append(mNextIndex);
551         sb.append(" mLongs.size=");
552         final int N = mLongs.size();
553         sb.append(N);
554         sb.append("\n");
555         if (includeData) {
556             for (int i=0; i<N; i++) {
557                 final long[] array = mLongs.get(i);
558                 for (int j=0; j<array.length; j++) {
559                     if (i == N-1 && j == mNextIndex) {
560                         break;
561                     }
562                     sb.append(String.format(" %4d %d 0x%016x %-19d\n", i, j, array[j], array[j]));
563                 }
564             }
565         }
566         sb.append("}");
567         return sb.toString();
568     }
569 
570     /**
571      * Write the long array to the parcel in a compacted form.  Does not allow negative
572      * values in the array.
573      */
writeCompactedLongArray(Parcel out, long[] array, int num)574     private static void writeCompactedLongArray(Parcel out, long[] array, int num) {
575         for (int i=0; i<num; i++) {
576             long val = array[i];
577             if (val < 0) {
578                 Slog.w(TAG, "Time val negative: " + val);
579                 val = 0;
580             }
581             if (val <= Integer.MAX_VALUE) {
582                 out.writeInt((int)val);
583             } else {
584                 int top = ~((int)((val>>32)&0x7fffffff));
585                 int bottom = (int)(val&0x0ffffffffL);
586                 out.writeInt(top);
587                 out.writeInt(bottom);
588             }
589         }
590     }
591 
592     /**
593      * Read the compacted array into the long[].
594      */
readCompactedLongArray(Parcel in, long[] array, int num)595     private static void readCompactedLongArray(Parcel in, long[] array, int num) {
596         final int alen = array.length;
597         if (num > alen) {
598             logOrThrow("bad array lengths: got " + num + " array is " + alen);
599             return;
600         }
601         int i;
602         for (i=0; i<num; i++) {
603             int val = in.readInt();
604             if (val >= 0) {
605                 array[i] = val;
606             } else {
607                 int bottom = in.readInt();
608                 array[i] = (((long)~val)<<32) | bottom;
609             }
610         }
611         while (i < alen) {
612             array[i] = 0;
613             i++;
614         }
615     }
616 
617     /**
618      * Extract the id from a key.
619      */
getIdFromKey(int key)620     public static byte getIdFromKey(int key) {
621         return (byte)((key >> ID_SHIFT) & ID_MASK);
622     }
623 
624     /**
625      * Gets the index of the array in the list of arrays.
626      *
627      * Not to be confused with getIndexFromKey.
628      */
getArrayFromKey(int key)629     public static int getArrayFromKey(int key) {
630         return (key >> ARRAY_SHIFT) & ARRAY_MASK;
631     }
632 
633     /**
634      * Gets the index of a value in a long[].
635      *
636      * Not to be confused with getArrayFromKey.
637      */
getIndexFromKey(int key)638     public static int getIndexFromKey(int key) {
639         return (key >> INDEX_SHIFT) & INDEX_MASK;
640     }
641 
642     /**
643      * Do a Slog.wtf or throw an exception (thereby crashing the system process if
644      * this is a debug build.)
645      */
logOrThrow(String message)646     private static void logOrThrow(String message) {
647         logOrThrow(message, new RuntimeException("Stack trace"));
648     }
649 
650     /**
651      * Do a Slog.wtf or throw an exception (thereby crashing the system process if
652      * this is an eng build.)
653      */
logOrThrow(String message, Throwable th)654     private static void logOrThrow(String message, Throwable th) {
655         Slog.e(TAG, message, th);
656         if (Build.IS_ENG) {
657             throw new RuntimeException(message, th);
658         }
659     }
660 }
661