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