Home | History | Annotate | Download | only in procstats
      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 
     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          */
     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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    351         public int getKeyCount() {
    352             return mSize;
    353         }
    354 
    355         /**
    356          * Get the key at the given index in our table.
    357          */
    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          */
    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          */
    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          */
    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 
    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 
    480     public SparseMappingTable() {
    481         mLongs.add(new long[ARRAY_SIZE]);
    482     }
    483 
    484     /**
    485      * Wipe out all the data.
    486      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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