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 Table(SparseMappingTable parent)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 */ copyFrom(Table copyFrom, int valueCount)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 */ getOrAddKey(byte id, int count)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 */ getKey(byte id)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 */ getValue(int key)179 public long getValue(int key) { 180 return getValue(key, 0); 181 } 182 183 /** 184 * Get the value for the given key and offset from that key. 185 * 186 * @param key A key as obtained from getKey or getOrAddKey. 187 * @param index The offset from that key. Must be less than the count 188 * provided to getOrAddKey when the space was allocated. 189 * @param value The value to set. 190 * 191 * @return the value, or 0 in case of an error 192 */ getValue(int key, int index)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 */ getValueForId(byte id)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 */ getValueForId(byte id, int index)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 */ getArrayForKey(int key)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 */ setValue(int key, long value)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 */ setValue(int key, int index, long value)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 */ resetTable()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 */ writeToParcel(Parcel out)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 */ readFromParcel(Parcel in)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 */ getKeyCount()351 public int getKeyCount() { 352 return mSize; 353 } 354 355 /** 356 * Get the key at the given index in our table. 357 */ getKeyAt(int i)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 */ assertConsistency()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 */ binarySearch(byte id)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 */ validateKeys(boolean log)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 dumpInternalState()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 SparseMappingTable()480 public SparseMappingTable() { 481 mLongs.add(new long[ARRAY_SIZE]); 482 } 483 484 /** 485 * Wipe out all the data. 486 */ reset()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 */ writeToParcel(Parcel out)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 */ readFromParcel(Parcel in)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 */ dumpInternalState(boolean includeData)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 */ writeCompactedLongArray(Parcel out, long[] array, int num)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 */ readCompactedLongArray(Parcel in, long[] array, int num)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 */ getIdFromKey(int key)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 */ getArrayFromKey(int key)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 */ getIndexFromKey(int key)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 */ logOrThrow(String message)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 */ logOrThrow(String message, Throwable th)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