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 */ 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 * 190 * @return the value, or 0 in case of an error 191 */ getValue(int key, int index)192 public long getValue(int key, int index) { 193 assertConsistency(); 194 195 try { 196 final long[] array = mParent.mLongs.get(getArrayFromKey(key)); 197 return array[getIndexFromKey(key) + index]; 198 } catch (IndexOutOfBoundsException ex) { 199 logOrThrow("key=0x" + Integer.toHexString(key) 200 + " index=" + index + " -- " + dumpInternalState(), ex); 201 return 0; 202 } 203 } 204 205 /** 206 * Set the value for the given id at offset 0 from that id. 207 * If the id is not found, return 0 instead. 208 * 209 * @param id The id of the item. 210 */ getValueForId(byte id)211 public long getValueForId(byte id) { 212 return getValueForId(id, 0); 213 } 214 215 /** 216 * Set the value for the given id and index offset from that id. 217 * If the id is not found, return 0 instead. 218 * 219 * @param id The id of the item. 220 * @param index The offset from that key. Must be less than the count 221 * provided to getOrAddKey when the space was allocated. 222 */ getValueForId(byte id, int index)223 public long getValueForId(byte id, int index) { 224 assertConsistency(); 225 226 final int idx = binarySearch(id); 227 if (idx >= 0) { 228 final int key = mTable[idx]; 229 try { 230 final long[] array = mParent.mLongs.get(getArrayFromKey(key)); 231 return array[getIndexFromKey(key) + index]; 232 } catch (IndexOutOfBoundsException ex) { 233 logOrThrow("id=0x" + Integer.toHexString(id) + " idx=" + idx 234 + " key=0x" + Integer.toHexString(key) + " index=" + index 235 + " -- " + dumpInternalState(), ex); 236 return 0; 237 } 238 } else { 239 return 0; 240 } 241 } 242 243 /** 244 * Return the raw storage long[] for the given key. 245 */ getArrayForKey(int key)246 public long[] getArrayForKey(int key) { 247 assertConsistency(); 248 249 return mParent.mLongs.get(getArrayFromKey(key)); 250 } 251 252 /** 253 * Set the value for the given key and offset from that key. 254 * 255 * @param key A key as obtained from getKey or getOrAddKey. 256 * @param value The value to set. 257 */ setValue(int key, long value)258 public void setValue(int key, long value) { 259 setValue(key, 0, value); 260 } 261 262 /** 263 * Set the value for the given key and offset from that key. 264 * 265 * @param key A key as obtained from getKey or getOrAddKey. 266 * @param index The offset from that key. Must be less than the count 267 * provided to getOrAddKey when the space was allocated. 268 * @param value The value to set. 269 */ setValue(int key, int index, long value)270 public void setValue(int key, int index, long value) { 271 assertConsistency(); 272 273 if (value < 0) { 274 logOrThrow("can't store negative values" 275 + " key=0x" + Integer.toHexString(key) 276 + " index=" + index + " value=" + value 277 + " -- " + dumpInternalState()); 278 return; 279 } 280 281 try { 282 final long[] array = mParent.mLongs.get(getArrayFromKey(key)); 283 array[getIndexFromKey(key) + index] = value; 284 } catch (IndexOutOfBoundsException ex) { 285 logOrThrow("key=0x" + Integer.toHexString(key) 286 + " index=" + index + " value=" + value 287 + " -- " + dumpInternalState(), ex); 288 return; 289 } 290 } 291 292 /** 293 * Clear out the table, and reset the sequence numbers so future writes 294 * without allocations will assert. 295 */ resetTable()296 public void resetTable() { 297 // Clear out our table. 298 mTable = null; 299 mSize = 0; 300 301 // Reset our sequence number. This will make all read/write calls 302 // start to fail, and then when we re-allocate it will be re-synced 303 // to that of mParent. 304 mSequence = mParent.mSequence; 305 } 306 307 /** 308 * Write the keys stored in the table to the parcel. The parent must 309 * be separately written. Does not save the actual data. 310 */ writeToParcel(Parcel out)311 public void writeToParcel(Parcel out) { 312 out.writeInt(mSequence); 313 out.writeInt(mSize); 314 for (int i=0; i<mSize; i++) { 315 out.writeInt(mTable[i]); 316 } 317 } 318 319 /** 320 * Read the keys from the parcel. The parent (with its long array) must 321 * have been previously initialized. 322 */ readFromParcel(Parcel in)323 public boolean readFromParcel(Parcel in) { 324 // Read the state 325 mSequence = in.readInt(); 326 mSize = in.readInt(); 327 if (mSize != 0) { 328 mTable = new int[mSize]; 329 for (int i=0; i<mSize; i++) { 330 mTable[i] = in.readInt(); 331 } 332 } else { 333 mTable = null; 334 } 335 336 // Make sure we're all healthy 337 if (validateKeys(true)) { 338 return true; 339 } else { 340 // Clear it out 341 mSize = 0; 342 mTable = null; 343 return false; 344 } 345 } 346 347 /** 348 * Return the number of keys that have been added to this Table. 349 */ getKeyCount()350 public int getKeyCount() { 351 return mSize; 352 } 353 354 /** 355 * Get the key at the given index in our table. 356 */ getKeyAt(int i)357 public int getKeyAt(int i) { 358 return mTable[i]; 359 } 360 361 /** 362 * Throw an exception if one of a variety of internal consistency checks fails. 363 */ assertConsistency()364 private void assertConsistency() { 365 // Something with this checking isn't working and is triggering 366 // more problems than it's helping to debug. 367 // Original bug: b/27045736 368 // New bug: b/27960286 369 if (false) { 370 // Assert that our sequence number matches mParent's. If it isn't that means 371 // we have been reset and our. If our sequence is UNITIALIZED_SEQUENCE, then 372 // it's possible that everything is working fine and we just haven't been 373 // written to since the last resetTable(). 374 if (mSequence != mParent.mSequence) { 375 if (mSequence < mParent.mSequence) { 376 logOrThrow("Sequence mismatch. SparseMappingTable.reset()" 377 + " called but not Table.resetTable() -- " 378 + dumpInternalState()); 379 return; 380 } else if (mSequence > mParent.mSequence) { 381 logOrThrow("Sequence mismatch. Table.resetTable()" 382 + " called but not SparseMappingTable.reset() -- " 383 + dumpInternalState()); 384 return; 385 } 386 } 387 } 388 } 389 390 /** 391 * Finds the 'id' inside the array of length size (physical size of the array 392 * is not used). 393 * 394 * @return The index of the array, or the bitwise not (~index) of where it 395 * would go if you wanted to insert 'id' into the array. 396 */ binarySearch(byte id)397 private int binarySearch(byte id) { 398 int lo = 0; 399 int hi = mSize - 1; 400 401 while (lo <= hi) { 402 int mid = (lo + hi) >>> 1; 403 byte midId = (byte)((mTable[mid] >> ID_SHIFT) & ID_MASK); 404 405 if (midId < id) { 406 lo = mid + 1; 407 } else if (midId > id) { 408 hi = mid - 1; 409 } else { 410 return mid; // id found 411 } 412 } 413 return ~lo; // id not present 414 } 415 416 /** 417 * Check that all the keys are valid locations in the long arrays. 418 * 419 * If any aren't, log it and return false. Else return true. 420 */ validateKeys(boolean log)421 private boolean validateKeys(boolean log) { 422 ArrayList<long[]> longs = mParent.mLongs; 423 final int longsSize = longs.size(); 424 425 final int N = mSize; 426 for (int i=0; i<N; i++) { 427 final int key = mTable[i]; 428 final int arrayIndex = getArrayFromKey(key); 429 final int index = getIndexFromKey(key); 430 if (arrayIndex >= longsSize || index >= longs.get(arrayIndex).length) { 431 if (log) { 432 Slog.w(TAG, "Invalid stats at index " + i + " -- " + dumpInternalState()); 433 } 434 return false; 435 } 436 } 437 438 return true; 439 } 440 dumpInternalState()441 public String dumpInternalState() { 442 StringBuilder sb = new StringBuilder(); 443 sb.append("SparseMappingTable.Table{mSequence="); 444 sb.append(mSequence); 445 sb.append(" mParent.mSequence="); 446 sb.append(mParent.mSequence); 447 sb.append(" mParent.mLongs.size()="); 448 sb.append(mParent.mLongs.size()); 449 sb.append(" mSize="); 450 sb.append(mSize); 451 sb.append(" mTable="); 452 if (mTable == null) { 453 sb.append("null"); 454 } else { 455 final int N = mTable.length; 456 sb.append('['); 457 for (int i=0; i<N; i++) { 458 final int key = mTable[i]; 459 sb.append("0x"); 460 sb.append(Integer.toHexString((key >> ID_SHIFT) & ID_MASK)); 461 sb.append("/0x"); 462 sb.append(Integer.toHexString((key >> ARRAY_SHIFT) & ARRAY_MASK)); 463 sb.append("/0x"); 464 sb.append(Integer.toHexString((key >> INDEX_SHIFT) & INDEX_MASK)); 465 if (i != N-1) { 466 sb.append(", "); 467 } 468 } 469 sb.append(']'); 470 } 471 sb.append(" clazz="); 472 sb.append(getClass().getName()); 473 sb.append('}'); 474 475 return sb.toString(); 476 } 477 } 478 SparseMappingTable()479 public SparseMappingTable() { 480 mLongs.add(new long[ARRAY_SIZE]); 481 } 482 483 /** 484 * Wipe out all the data. 485 */ reset()486 public void reset() { 487 // Clear out mLongs, and prime it with a new array of data 488 mLongs.clear(); 489 mLongs.add(new long[ARRAY_SIZE]); 490 mNextIndex = 0; 491 492 // Increment out sequence counter, because all of the tables will 493 // now be out of sync with the data. 494 mSequence++; 495 } 496 497 /** 498 * Write the data arrays to the parcel. 499 */ writeToParcel(Parcel out)500 public void writeToParcel(Parcel out) { 501 out.writeInt(mSequence); 502 out.writeInt(mNextIndex); 503 final int N = mLongs.size(); 504 out.writeInt(N); 505 for (int i=0; i<N-1; i++) { 506 final long[] array = mLongs.get(i); 507 out.writeInt(array.length); 508 writeCompactedLongArray(out, array, array.length); 509 } 510 // save less for the last one. upon re-loading they'll just start a new array. 511 final long[] lastLongs = mLongs.get(N-1); 512 out.writeInt(mNextIndex); 513 writeCompactedLongArray(out, lastLongs, mNextIndex); 514 } 515 516 /** 517 * Read the data arrays from the parcel. 518 */ readFromParcel(Parcel in)519 public void readFromParcel(Parcel in) { 520 mSequence = in.readInt(); 521 mNextIndex = in.readInt(); 522 523 mLongs.clear(); 524 final int N = in.readInt(); 525 for (int i=0; i<N; i++) { 526 final int size = in.readInt(); 527 final long[] array = new long[size]; 528 readCompactedLongArray(in, array, size); 529 mLongs.add(array); 530 } 531 // Verify that last array's length is consistent with writeToParcel 532 if (N > 0 && mLongs.get(N - 1).length != mNextIndex) { 533 EventLog.writeEvent(0x534e4554, "73252178", -1, ""); 534 throw new IllegalStateException("Expected array of length " + mNextIndex + " but was " 535 + mLongs.get(N - 1).length); 536 } 537 } 538 539 /** 540 * Return a string for debugging. 541 */ dumpInternalState(boolean includeData)542 public String dumpInternalState(boolean includeData) { 543 final StringBuilder sb = new StringBuilder(); 544 sb.append("SparseMappingTable{"); 545 sb.append("mSequence="); 546 sb.append(mSequence); 547 sb.append(" mNextIndex="); 548 sb.append(mNextIndex); 549 sb.append(" mLongs.size="); 550 final int N = mLongs.size(); 551 sb.append(N); 552 sb.append("\n"); 553 if (includeData) { 554 for (int i=0; i<N; i++) { 555 final long[] array = mLongs.get(i); 556 for (int j=0; j<array.length; j++) { 557 if (i == N-1 && j == mNextIndex) { 558 break; 559 } 560 sb.append(String.format(" %4d %d 0x%016x %-19d\n", i, j, array[j], array[j])); 561 } 562 } 563 } 564 sb.append("}"); 565 return sb.toString(); 566 } 567 568 /** 569 * Write the long array to the parcel in a compacted form. Does not allow negative 570 * values in the array. 571 */ writeCompactedLongArray(Parcel out, long[] array, int num)572 private static void writeCompactedLongArray(Parcel out, long[] array, int num) { 573 for (int i=0; i<num; i++) { 574 long val = array[i]; 575 if (val < 0) { 576 Slog.w(TAG, "Time val negative: " + val); 577 val = 0; 578 } 579 if (val <= Integer.MAX_VALUE) { 580 out.writeInt((int)val); 581 } else { 582 int top = ~((int)((val>>32)&0x7fffffff)); 583 int bottom = (int)(val&0x0ffffffffL); 584 out.writeInt(top); 585 out.writeInt(bottom); 586 } 587 } 588 } 589 590 /** 591 * Read the compacted array into the long[]. 592 */ readCompactedLongArray(Parcel in, long[] array, int num)593 private static void readCompactedLongArray(Parcel in, long[] array, int num) { 594 final int alen = array.length; 595 if (num > alen) { 596 logOrThrow("bad array lengths: got " + num + " array is " + alen); 597 return; 598 } 599 int i; 600 for (i=0; i<num; i++) { 601 int val = in.readInt(); 602 if (val >= 0) { 603 array[i] = val; 604 } else { 605 int bottom = in.readInt(); 606 array[i] = (((long)~val)<<32) | bottom; 607 } 608 } 609 while (i < alen) { 610 array[i] = 0; 611 i++; 612 } 613 } 614 615 /** 616 * Extract the id from a key. 617 */ getIdFromKey(int key)618 public static byte getIdFromKey(int key) { 619 return (byte)((key >> ID_SHIFT) & ID_MASK); 620 } 621 622 /** 623 * Gets the index of the array in the list of arrays. 624 * 625 * Not to be confused with getIndexFromKey. 626 */ getArrayFromKey(int key)627 public static int getArrayFromKey(int key) { 628 return (key >> ARRAY_SHIFT) & ARRAY_MASK; 629 } 630 631 /** 632 * Gets the index of a value in a long[]. 633 * 634 * Not to be confused with getArrayFromKey. 635 */ getIndexFromKey(int key)636 public static int getIndexFromKey(int key) { 637 return (key >> INDEX_SHIFT) & INDEX_MASK; 638 } 639 640 /** 641 * Do a Slog.wtf or throw an exception (thereby crashing the system process if 642 * this is a debug build.) 643 */ logOrThrow(String message)644 private static void logOrThrow(String message) { 645 logOrThrow(message, new RuntimeException("Stack trace")); 646 } 647 648 /** 649 * Do a Slog.wtf or throw an exception (thereby crashing the system process if 650 * this is an eng build.) 651 */ logOrThrow(String message, Throwable th)652 private static void logOrThrow(String message, Throwable th) { 653 Slog.e(TAG, message, th); 654 if (Build.IS_ENG) { 655 throw new RuntimeException(message, th); 656 } 657 } 658 } 659