1 /* 2 * Copyright (C) 2008 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 * DDM-related heap functions 18 */ 19 #include <sys/time.h> 20 #include <time.h> 21 22 #include "Dalvik.h" 23 #include "alloc/Heap.h" 24 #include "alloc/HeapInternal.h" 25 #include "alloc/DdmHeap.h" 26 #include "alloc/DlMalloc.h" 27 #include "alloc/HeapSource.h" 28 29 #define DEFAULT_HEAP_ID 1 30 31 enum HpifWhen { 32 HPIF_WHEN_NEVER = 0, 33 HPIF_WHEN_NOW = 1, 34 HPIF_WHEN_NEXT_GC = 2, 35 HPIF_WHEN_EVERY_GC = 3 36 }; 37 38 /* 39 * Chunk HPIF (client --> server) 40 * 41 * Heap Info. General information about the heap, 42 * suitable for a summary display. 43 * 44 * [u4]: number of heaps 45 * 46 * For each heap: 47 * [u4]: heap ID 48 * [u8]: timestamp in ms since Unix epoch 49 * [u1]: capture reason (same as 'when' value from server) 50 * [u4]: max heap size in bytes (-Xmx) 51 * [u4]: current heap size in bytes 52 * [u4]: current number of bytes allocated 53 * [u4]: current number of objects allocated 54 */ 55 #define HPIF_SIZE(numHeaps) \ 56 (sizeof(u4) + (numHeaps) * (5 * sizeof(u4) + sizeof(u1) + sizeof(u8))) 57 void dvmDdmSendHeapInfo(int reason, bool shouldLock) 58 { 59 struct timeval now; 60 u8 nowMs; 61 u1 *buf, *b; 62 63 buf = (u1 *)malloc(HPIF_SIZE(1)); 64 if (buf == NULL) { 65 return; 66 } 67 b = buf; 68 69 /* If there's a one-shot 'when', reset it. 70 */ 71 if (reason == gDvm.gcHeap->ddmHpifWhen) { 72 if (shouldLock && ! dvmLockHeap()) { 73 ALOGW("%s(): can't lock heap to clear when", __func__); 74 goto skip_when; 75 } 76 if (reason == gDvm.gcHeap->ddmHpifWhen) { 77 if (gDvm.gcHeap->ddmHpifWhen == HPIF_WHEN_NEXT_GC) { 78 gDvm.gcHeap->ddmHpifWhen = HPIF_WHEN_NEVER; 79 } 80 } 81 if (shouldLock) { 82 dvmUnlockHeap(); 83 } 84 } 85 skip_when: 86 87 /* The current time, in milliseconds since 0:00 GMT, 1/1/70. 88 */ 89 if (gettimeofday(&now, NULL) < 0) { 90 nowMs = 0; 91 } else { 92 nowMs = (u8)now.tv_sec * 1000 + now.tv_usec / 1000; 93 } 94 95 /* number of heaps */ 96 set4BE(b, 1); b += 4; 97 98 /* For each heap (of which there is one) */ 99 { 100 /* heap ID */ 101 set4BE(b, DEFAULT_HEAP_ID); b += 4; 102 103 /* timestamp */ 104 set8BE(b, nowMs); b += 8; 105 106 /* 'when' value */ 107 *b++ = (u1)reason; 108 109 /* max allowed heap size in bytes */ 110 set4BE(b, dvmHeapSourceGetMaximumSize()); b += 4; 111 112 /* current heap size in bytes */ 113 set4BE(b, dvmHeapSourceGetValue(HS_FOOTPRINT, NULL, 0)); b += 4; 114 115 /* number of bytes allocated */ 116 set4BE(b, dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, NULL, 0)); b += 4; 117 118 /* number of objects allocated */ 119 set4BE(b, dvmHeapSourceGetValue(HS_OBJECTS_ALLOCATED, NULL, 0)); b += 4; 120 } 121 assert((intptr_t)b == (intptr_t)buf + (intptr_t)HPIF_SIZE(1)); 122 123 dvmDbgDdmSendChunk(CHUNK_TYPE("HPIF"), b - buf, buf); 124 } 125 126 bool dvmDdmHandleHpifChunk(int when) 127 { 128 switch (when) { 129 case HPIF_WHEN_NOW: 130 dvmDdmSendHeapInfo(when, true); 131 break; 132 case HPIF_WHEN_NEVER: 133 case HPIF_WHEN_NEXT_GC: 134 case HPIF_WHEN_EVERY_GC: 135 if (dvmLockHeap()) { 136 gDvm.gcHeap->ddmHpifWhen = when; 137 dvmUnlockHeap(); 138 } else { 139 ALOGI("%s(): can't lock heap to set when", __func__); 140 return false; 141 } 142 break; 143 default: 144 ALOGI("%s(): bad when value 0x%08x", __func__, when); 145 return false; 146 } 147 148 return true; 149 } 150 151 enum HpsgSolidity { 152 SOLIDITY_FREE = 0, 153 SOLIDITY_HARD = 1, 154 SOLIDITY_SOFT = 2, 155 SOLIDITY_WEAK = 3, 156 SOLIDITY_PHANTOM = 4, 157 SOLIDITY_FINALIZABLE = 5, 158 SOLIDITY_SWEEP = 6, 159 }; 160 161 enum HpsgKind { 162 KIND_OBJECT = 0, 163 KIND_CLASS_OBJECT = 1, 164 KIND_ARRAY_1 = 2, 165 KIND_ARRAY_2 = 3, 166 KIND_ARRAY_4 = 4, 167 KIND_ARRAY_8 = 5, 168 KIND_UNKNOWN = 6, 169 KIND_NATIVE = 7, 170 }; 171 172 #define HPSG_PARTIAL (1<<7) 173 #define HPSG_STATE(solidity, kind) \ 174 ((u1)((((kind) & 0x7) << 3) | ((solidity) & 0x7))) 175 176 struct HeapChunkContext { 177 void* startOfNextMemoryChunk; 178 u1 *buf; 179 u1 *p; 180 u1 *pieceLenField; 181 size_t bufLen; 182 size_t totalAllocationUnits; 183 int type; 184 bool merge; 185 bool needHeader; 186 }; 187 188 #define ALLOCATION_UNIT_SIZE 8 189 190 static void flush_hpsg_chunk(HeapChunkContext *ctx) 191 { 192 /* Patch the "length of piece" field. 193 */ 194 assert(ctx->buf <= ctx->pieceLenField && 195 ctx->pieceLenField <= ctx->p); 196 set4BE(ctx->pieceLenField, ctx->totalAllocationUnits); 197 198 /* Send the chunk. 199 */ 200 dvmDbgDdmSendChunk(ctx->type, ctx->p - ctx->buf, ctx->buf); 201 202 /* Reset the context. 203 */ 204 ctx->p = ctx->buf; 205 ctx->totalAllocationUnits = 0; 206 ctx->needHeader = true; 207 ctx->pieceLenField = NULL; 208 } 209 210 static void append_chunk(HeapChunkContext *ctx, u1 state, void* ptr, size_t length) { 211 /* Make sure there's enough room left in the buffer. 212 * We need to use two bytes for every fractional 256 213 * allocation units used by the chunk and 17 bytes for 214 * any header. 215 */ 216 { 217 size_t needed = (((length/ALLOCATION_UNIT_SIZE + 255) / 256) * 2) + 17; 218 size_t bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf); 219 if (bytesLeft < needed) { 220 flush_hpsg_chunk(ctx); 221 } 222 bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf); 223 if (bytesLeft < needed) { 224 ALOGW("chunk is too big to transmit (length=%zd, %zd bytes)", 225 length, needed); 226 return; 227 } 228 } 229 if (ctx->needHeader) { 230 /* 231 * Start a new HPSx chunk. 232 */ 233 234 /* [u4]: heap ID */ 235 set4BE(ctx->p, DEFAULT_HEAP_ID); ctx->p += 4; 236 237 /* [u1]: size of allocation unit, in bytes */ 238 *ctx->p++ = 8; 239 240 /* [u4]: virtual address of segment start */ 241 set4BE(ctx->p, (uintptr_t)ptr); ctx->p += 4; 242 243 /* [u4]: offset of this piece (relative to the virtual address) */ 244 set4BE(ctx->p, 0); ctx->p += 4; 245 246 /* [u4]: length of piece, in allocation units 247 * We won't know this until we're done, so save the offset 248 * and stuff in a dummy value. 249 */ 250 ctx->pieceLenField = ctx->p; 251 set4BE(ctx->p, 0x55555555); ctx->p += 4; 252 253 ctx->needHeader = false; 254 } 255 /* Write out the chunk description. 256 */ 257 length /= ALLOCATION_UNIT_SIZE; // convert to allocation units 258 ctx->totalAllocationUnits += length; 259 while (length > 256) { 260 *ctx->p++ = state | HPSG_PARTIAL; 261 *ctx->p++ = 255; // length - 1 262 length -= 256; 263 } 264 *ctx->p++ = state; 265 *ctx->p++ = length - 1; 266 } 267 268 /* 269 * Called by dlmalloc_inspect_all. If used_bytes != 0 then start is 270 * the start of a malloc-ed piece of memory of size used_bytes. If 271 * start is 0 then start is the beginning of any free space not 272 * including dlmalloc's book keeping and end the start of the next 273 * dlmalloc chunk. Regions purely containing book keeping don't 274 * callback. 275 */ 276 static void heap_chunk_callback(void* start, void* end, size_t used_bytes, 277 void* arg) 278 { 279 u1 state; 280 HeapChunkContext *ctx = (HeapChunkContext *)arg; 281 UNUSED_PARAMETER(end); 282 283 if (used_bytes == 0) { 284 if (start == NULL) { 285 // Reset for start of new heap. 286 ctx->startOfNextMemoryChunk = NULL; 287 flush_hpsg_chunk(ctx); 288 } 289 // Only process in use memory so that free region information 290 // also includes dlmalloc book keeping. 291 return; 292 } 293 294 /* If we're looking at the native heap, we'll just return 295 * (SOLIDITY_HARD, KIND_NATIVE) for all allocated chunks 296 */ 297 bool native = ctx->type == CHUNK_TYPE("NHSG"); 298 299 if (ctx->startOfNextMemoryChunk != NULL) { 300 // Transmit any pending free memory. Native free memory of 301 // over kMaxFreeLen could be because of the use of mmaps, so 302 // don't report. If not free memory then start a new segment. 303 bool flush = true; 304 if (start > ctx->startOfNextMemoryChunk) { 305 const size_t kMaxFreeLen = 2 * SYSTEM_PAGE_SIZE; 306 void* freeStart = ctx->startOfNextMemoryChunk; 307 void* freeEnd = start; 308 size_t freeLen = (char*)freeEnd - (char*)freeStart; 309 if (!native || freeLen < kMaxFreeLen) { 310 append_chunk(ctx, HPSG_STATE(SOLIDITY_FREE, 0), 311 freeStart, freeLen); 312 flush = false; 313 } 314 } 315 if (flush) { 316 ctx->startOfNextMemoryChunk = NULL; 317 flush_hpsg_chunk(ctx); 318 } 319 } 320 const Object *obj = (const Object *)start; 321 322 /* It's an allocated chunk. Figure out what it is. 323 */ 324 //TODO: if ctx.merge, see if this chunk is different from the last chunk. 325 // If it's the same, we should combine them. 326 if (!native && dvmIsValidObject(obj)) { 327 ClassObject *clazz = obj->clazz; 328 if (clazz == NULL) { 329 /* The object was probably just created 330 * but hasn't been initialized yet. 331 */ 332 state = HPSG_STATE(SOLIDITY_HARD, KIND_OBJECT); 333 } else if (dvmIsTheClassClass(clazz)) { 334 state = HPSG_STATE(SOLIDITY_HARD, KIND_CLASS_OBJECT); 335 } else if (IS_CLASS_FLAG_SET(clazz, CLASS_ISARRAY)) { 336 if (IS_CLASS_FLAG_SET(clazz, CLASS_ISOBJECTARRAY)) { 337 state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_4); 338 } else { 339 switch (clazz->elementClass->primitiveType) { 340 case PRIM_BOOLEAN: 341 case PRIM_BYTE: 342 state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_1); 343 break; 344 case PRIM_CHAR: 345 case PRIM_SHORT: 346 state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_2); 347 break; 348 case PRIM_INT: 349 case PRIM_FLOAT: 350 state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_4); 351 break; 352 case PRIM_DOUBLE: 353 case PRIM_LONG: 354 state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_8); 355 break; 356 default: 357 assert(!"Unknown GC heap object type"); 358 state = HPSG_STATE(SOLIDITY_HARD, KIND_UNKNOWN); 359 break; 360 } 361 } 362 } else { 363 state = HPSG_STATE(SOLIDITY_HARD, KIND_OBJECT); 364 } 365 } else { 366 obj = NULL; // it's not actually an object 367 state = HPSG_STATE(SOLIDITY_HARD, KIND_NATIVE); 368 } 369 append_chunk(ctx, state, start, used_bytes + HEAP_SOURCE_CHUNK_OVERHEAD); 370 ctx->startOfNextMemoryChunk = 371 (char*)start + used_bytes + HEAP_SOURCE_CHUNK_OVERHEAD; 372 } 373 374 enum HpsgWhen { 375 HPSG_WHEN_NEVER = 0, 376 HPSG_WHEN_EVERY_GC = 1, 377 }; 378 enum HpsgWhat { 379 HPSG_WHAT_MERGED_OBJECTS = 0, 380 HPSG_WHAT_DISTINCT_OBJECTS = 1, 381 }; 382 383 /* 384 * Maximum chunk size. Obtain this from the formula: 385 * 386 * (((maximum_heap_size / ALLOCATION_UNIT_SIZE) + 255) / 256) * 2 387 */ 388 #define HPSx_CHUNK_SIZE (16384 - 16) 389 390 static void walkHeap(bool merge, bool native) 391 { 392 HeapChunkContext ctx; 393 394 memset(&ctx, 0, sizeof(ctx)); 395 ctx.bufLen = HPSx_CHUNK_SIZE; 396 ctx.buf = (u1 *)malloc(ctx.bufLen); 397 if (ctx.buf == NULL) { 398 return; 399 } 400 401 ctx.merge = merge; 402 if (native) { 403 ctx.type = CHUNK_TYPE("NHSG"); 404 } else { 405 if (ctx.merge) { 406 ctx.type = CHUNK_TYPE("HPSG"); 407 } else { 408 ctx.type = CHUNK_TYPE("HPSO"); 409 } 410 } 411 412 ctx.p = ctx.buf; 413 ctx.needHeader = true; 414 if (native) { 415 dlmalloc_inspect_all(heap_chunk_callback, (void*)&ctx); 416 } else { 417 dvmHeapSourceWalk(heap_chunk_callback, (void *)&ctx); 418 } 419 if (ctx.p > ctx.buf) { 420 flush_hpsg_chunk(&ctx); 421 } 422 423 free(ctx.buf); 424 } 425 426 void dvmDdmSendHeapSegments(bool shouldLock, bool native) 427 { 428 u1 heapId[sizeof(u4)]; 429 GcHeap *gcHeap = gDvm.gcHeap; 430 int when, what; 431 bool merge; 432 433 /* Don't even grab the lock if there's nothing to do when we're called. 434 */ 435 if (!native) { 436 when = gcHeap->ddmHpsgWhen; 437 what = gcHeap->ddmHpsgWhat; 438 if (when == HPSG_WHEN_NEVER) { 439 return; 440 } 441 } else { 442 when = gcHeap->ddmNhsgWhen; 443 what = gcHeap->ddmNhsgWhat; 444 if (when == HPSG_WHEN_NEVER) { 445 return; 446 } 447 } 448 if (shouldLock && !dvmLockHeap()) { 449 ALOGW("Can't lock heap for DDM HPSx dump"); 450 return; 451 } 452 453 /* Figure out what kind of chunks we'll be sending. 454 */ 455 if (what == HPSG_WHAT_MERGED_OBJECTS) { 456 merge = true; 457 } else if (what == HPSG_WHAT_DISTINCT_OBJECTS) { 458 merge = false; 459 } else { 460 assert(!"bad HPSG.what value"); 461 return; 462 } 463 464 /* First, send a heap start chunk. 465 */ 466 set4BE(heapId, DEFAULT_HEAP_ID); 467 dvmDbgDdmSendChunk(native ? CHUNK_TYPE("NHST") : CHUNK_TYPE("HPST"), 468 sizeof(u4), heapId); 469 470 /* Send a series of heap segment chunks. 471 */ 472 walkHeap(merge, native); 473 474 /* Finally, send a heap end chunk. 475 */ 476 dvmDbgDdmSendChunk(native ? CHUNK_TYPE("NHEN") : CHUNK_TYPE("HPEN"), 477 sizeof(u4), heapId); 478 479 if (shouldLock) { 480 dvmUnlockHeap(); 481 } 482 } 483 484 bool dvmDdmHandleHpsgNhsgChunk(int when, int what, bool native) 485 { 486 ALOGI("dvmDdmHandleHpsgChunk(when %d, what %d, heap %d)", when, what, 487 native); 488 switch (when) { 489 case HPSG_WHEN_NEVER: 490 case HPSG_WHEN_EVERY_GC: 491 break; 492 default: 493 ALOGI("%s(): bad when value 0x%08x", __func__, when); 494 return false; 495 } 496 497 switch (what) { 498 case HPSG_WHAT_MERGED_OBJECTS: 499 case HPSG_WHAT_DISTINCT_OBJECTS: 500 break; 501 default: 502 ALOGI("%s(): bad what value 0x%08x", __func__, what); 503 return false; 504 } 505 506 if (dvmLockHeap()) { 507 if (!native) { 508 gDvm.gcHeap->ddmHpsgWhen = when; 509 gDvm.gcHeap->ddmHpsgWhat = what; 510 } else { 511 gDvm.gcHeap->ddmNhsgWhen = when; 512 gDvm.gcHeap->ddmNhsgWhat = what; 513 } 514 //TODO: if what says we should dump immediately, signal (or do) it from here 515 dvmUnlockHeap(); 516 } else { 517 ALOGI("%s(): can't lock heap to set when/what", __func__); 518 return false; 519 } 520 521 return true; 522 } 523