/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.app.procstats; import android.os.Build; import android.os.Parcel; import android.util.Slog; import libcore.util.EmptyArray; import java.util.ArrayList; import java.util.Arrays; import com.android.internal.util.GrowingArrayUtils; /** * Class that contains a set of tables mapping byte ids to long values. * * This class is used to store the ProcessStats data. This data happens to be * a set of very sparse tables, that is mostly append or overwrite, with infrequent * resets of the data. * * Data is stored as a list of large long[] arrays containing the actual values. There are a * set of Table objects that each contain a small array mapping the byte IDs to a position * in the larger arrays. * * The data itself is either a single long value or a range of long values which are always * stored continguously in one of the long arrays. When the caller allocates a slot with * getOrAddKey, an int key is returned. That key can be re-retreived with getKey without * allocating the value. The data can then be set or retrieved with that key. */ public class SparseMappingTable { private static final String TAG = "SparseMappingTable"; // How big each array is. public static final int ARRAY_SIZE = 4096; public static final int INVALID_KEY = 0xffffffff; // Where the "type"/"state" part of the data appears in an offset integer. private static final int ID_SHIFT = 0; private static final int ID_MASK = 0xff; // Where the "which array" part of the data appears in an offset integer. private static final int ARRAY_SHIFT = 8; private static final int ARRAY_MASK = 0xff; // Where the "index into array" part of the data appears in an offset integer. private static final int INDEX_SHIFT = 16; private static final int INDEX_MASK = 0xffff; private int mSequence; private int mNextIndex; private final ArrayList mLongs = new ArrayList(); /** * A table of data as stored in a SparseMappingTable. */ public static class Table { private SparseMappingTable mParent; private int mSequence = 1; private int[] mTable; private int mSize; public Table(SparseMappingTable parent) { mParent = parent; mSequence = parent.mSequence; } /** * Pulls the data from 'copyFrom' and stores it in our own longs table. * * @param copyFrom The Table to copy from * @param valueCount The number of values to copy for each key */ public void copyFrom(Table copyFrom, int valueCount) { mTable = null; mSize = 0; final int N = copyFrom.getKeyCount(); for (int i=0; i= 0) { // Found return mTable[idx]; } else { // Not found. Need to allocate it. // Get an array with enough space to store 'count' values. final ArrayList list = mParent.mLongs; int whichArray = list.size()-1; long[] array = list.get(whichArray); if (mParent.mNextIndex + count > array.length) { // if it won't fit then make a new array. array = new long[ARRAY_SIZE]; list.add(array); whichArray++; mParent.mNextIndex = 0; } // The key is a combination of whichArray, which index in that array, and // the table value itself, which will be used for lookup final int key = (whichArray << ARRAY_SHIFT) | (mParent.mNextIndex << INDEX_SHIFT) | (((int)id) << ID_SHIFT); mParent.mNextIndex += count; // Store the key in the sparse lookup table for this Table object. mTable = GrowingArrayUtils.insert(mTable != null ? mTable : EmptyArray.INT, mSize, ~idx, key); mSize++; return key; } } /** * Looks up a key in the table. * * @return The key from this table or INVALID_KEY if the id is not found. */ public int getKey(byte id) { assertConsistency(); final int idx = binarySearch(id); if (idx >= 0) { return mTable[idx]; } else { return INVALID_KEY; } } /** * Get the value for the given key and offset from that key. * * @param key A key as obtained from getKey or getOrAddKey. * @param value The value to set. */ public long getValue(int key) { return getValue(key, 0); } /** * Get the value for the given key and offset from that key. * * @param key A key as obtained from getKey or getOrAddKey. * @param index The offset from that key. Must be less than the count * provided to getOrAddKey when the space was allocated. * @param value The value to set. * * @return the value, or 0 in case of an error */ public long getValue(int key, int index) { assertConsistency(); try { final long[] array = mParent.mLongs.get(getArrayFromKey(key)); return array[getIndexFromKey(key) + index]; } catch (IndexOutOfBoundsException ex) { logOrThrow("key=0x" + Integer.toHexString(key) + " index=" + index + " -- " + dumpInternalState(), ex); return 0; } } /** * Set the value for the given id at offset 0 from that id. * If the id is not found, return 0 instead. * * @param id The id of the item. */ public long getValueForId(byte id) { return getValueForId(id, 0); } /** * Set the value for the given id and index offset from that id. * If the id is not found, return 0 instead. * * @param id The id of the item. * @param index The offset from that key. Must be less than the count * provided to getOrAddKey when the space was allocated. */ public long getValueForId(byte id, int index) { assertConsistency(); final int idx = binarySearch(id); if (idx >= 0) { final int key = mTable[idx]; try { final long[] array = mParent.mLongs.get(getArrayFromKey(key)); return array[getIndexFromKey(key) + index]; } catch (IndexOutOfBoundsException ex) { logOrThrow("id=0x" + Integer.toHexString(id) + " idx=" + idx + " key=0x" + Integer.toHexString(key) + " index=" + index + " -- " + dumpInternalState(), ex); return 0; } } else { return 0; } } /** * Return the raw storage long[] for the given key. */ public long[] getArrayForKey(int key) { assertConsistency(); return mParent.mLongs.get(getArrayFromKey(key)); } /** * Set the value for the given key and offset from that key. * * @param key A key as obtained from getKey or getOrAddKey. * @param value The value to set. */ public void setValue(int key, long value) { setValue(key, 0, value); } /** * Set the value for the given key and offset from that key. * * @param key A key as obtained from getKey or getOrAddKey. * @param index The offset from that key. Must be less than the count * provided to getOrAddKey when the space was allocated. * @param value The value to set. */ public void setValue(int key, int index, long value) { assertConsistency(); if (value < 0) { logOrThrow("can't store negative values" + " key=0x" + Integer.toHexString(key) + " index=" + index + " value=" + value + " -- " + dumpInternalState()); return; } try { final long[] array = mParent.mLongs.get(getArrayFromKey(key)); array[getIndexFromKey(key) + index] = value; } catch (IndexOutOfBoundsException ex) { logOrThrow("key=0x" + Integer.toHexString(key) + " index=" + index + " value=" + value + " -- " + dumpInternalState(), ex); return; } } /** * Clear out the table, and reset the sequence numbers so future writes * without allocations will assert. */ public void resetTable() { // Clear out our table. mTable = null; mSize = 0; // Reset our sequence number. This will make all read/write calls // start to fail, and then when we re-allocate it will be re-synced // to that of mParent. mSequence = mParent.mSequence; } /** * Write the keys stored in the table to the parcel. The parent must * be separately written. Does not save the actual data. */ public void writeToParcel(Parcel out) { out.writeInt(mSequence); out.writeInt(mSize); for (int i=0; i mParent.mSequence) { logOrThrow("Sequence mismatch. Table.resetTable()" + " called but not SparseMappingTable.reset() -- " + dumpInternalState()); return; } } } } /** * Finds the 'id' inside the array of length size (physical size of the array * is not used). * * @return The index of the array, or the bitwise not (~index) of where it * would go if you wanted to insert 'id' into the array. */ private int binarySearch(byte id) { int lo = 0; int hi = mSize - 1; while (lo <= hi) { int mid = (lo + hi) >>> 1; byte midId = (byte)((mTable[mid] >> ID_SHIFT) & ID_MASK); if (midId < id) { lo = mid + 1; } else if (midId > id) { hi = mid - 1; } else { return mid; // id found } } return ~lo; // id not present } /** * Check that all the keys are valid locations in the long arrays. * * If any aren't, log it and return false. Else return true. */ private boolean validateKeys(boolean log) { ArrayList longs = mParent.mLongs; final int longsSize = longs.size(); final int N = mSize; for (int i=0; i= longsSize || index >= longs.get(arrayIndex).length) { if (log) { Slog.w(TAG, "Invalid stats at index " + i + " -- " + dumpInternalState()); } return false; } } return true; } public String dumpInternalState() { StringBuilder sb = new StringBuilder(); sb.append("SparseMappingTable.Table{mSequence="); sb.append(mSequence); sb.append(" mParent.mSequence="); sb.append(mParent.mSequence); sb.append(" mParent.mLongs.size()="); sb.append(mParent.mLongs.size()); sb.append(" mSize="); sb.append(mSize); sb.append(" mTable="); if (mTable == null) { sb.append("null"); } else { final int N = mTable.length; sb.append('['); for (int i=0; i> ID_SHIFT) & ID_MASK)); sb.append("/0x"); sb.append(Integer.toHexString((key >> ARRAY_SHIFT) & ARRAY_MASK)); sb.append("/0x"); sb.append(Integer.toHexString((key >> INDEX_SHIFT) & INDEX_MASK)); if (i != N-1) { sb.append(", "); } } sb.append(']'); } sb.append(" clazz="); sb.append(getClass().getName()); sb.append('}'); return sb.toString(); } } public SparseMappingTable() { mLongs.add(new long[ARRAY_SIZE]); } /** * Wipe out all the data. */ public void reset() { // Clear out mLongs, and prime it with a new array of data mLongs.clear(); mLongs.add(new long[ARRAY_SIZE]); mNextIndex = 0; // Increment out sequence counter, because all of the tables will // now be out of sync with the data. mSequence++; } /** * Write the data arrays to the parcel. */ public void writeToParcel(Parcel out) { out.writeInt(mSequence); out.writeInt(mNextIndex); final int N = mLongs.size(); out.writeInt(N); for (int i=0; i>32)&0x7fffffff)); int bottom = (int)(val&0x0ffffffffL); out.writeInt(top); out.writeInt(bottom); } } } /** * Read the compacted array into the long[]. */ private static void readCompactedLongArray(Parcel in, long[] array, int num) { final int alen = array.length; if (num > alen) { logOrThrow("bad array lengths: got " + num + " array is " + alen); return; } int i; for (i=0; i= 0) { array[i] = val; } else { int bottom = in.readInt(); array[i] = (((long)~val)<<32) | bottom; } } while (i < alen) { array[i] = 0; i++; } } /** * Extract the id from a key. */ public static byte getIdFromKey(int key) { return (byte)((key >> ID_SHIFT) & ID_MASK); } /** * Gets the index of the array in the list of arrays. * * Not to be confused with getIndexFromKey. */ public static int getArrayFromKey(int key) { return (key >> ARRAY_SHIFT) & ARRAY_MASK; } /** * Gets the index of a value in a long[]. * * Not to be confused with getArrayFromKey. */ public static int getIndexFromKey(int key) { return (key >> INDEX_SHIFT) & INDEX_MASK; } /** * Do a Slog.wtf or throw an exception (thereby crashing the system process if * this is a debug build.) */ private static void logOrThrow(String message) { logOrThrow(message, new RuntimeException("Stack trace")); } /** * Do a Slog.wtf or throw an exception (thereby crashing the system process if * this is an eng build.) */ private static void logOrThrow(String message, Throwable th) { Slog.e(TAG, message, th); if (Build.TYPE.equals("eng")) { throw new RuntimeException(message, th); } } }