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