/* * Copyright (C) 2008 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. */ /* * DDM-related heap functions */ #include <sys/time.h> #include <time.h> #include "Dalvik.h" #include "alloc/Heap.h" #include "alloc/HeapInternal.h" #include "alloc/DdmHeap.h" #include "alloc/DlMalloc.h" #include "alloc/HeapSource.h" #define DEFAULT_HEAP_ID 1 enum HpifWhen { HPIF_WHEN_NEVER = 0, HPIF_WHEN_NOW = 1, HPIF_WHEN_NEXT_GC = 2, HPIF_WHEN_EVERY_GC = 3 }; /* * Chunk HPIF (client --> server) * * Heap Info. General information about the heap, * suitable for a summary display. * * [u4]: number of heaps * * For each heap: * [u4]: heap ID * [u8]: timestamp in ms since Unix epoch * [u1]: capture reason (same as 'when' value from server) * [u4]: max heap size in bytes (-Xmx) * [u4]: current heap size in bytes * [u4]: current number of bytes allocated * [u4]: current number of objects allocated */ #define HPIF_SIZE(numHeaps) \ (sizeof(u4) + (numHeaps) * (5 * sizeof(u4) + sizeof(u1) + sizeof(u8))) void dvmDdmSendHeapInfo(int reason, bool shouldLock) { struct timeval now; u8 nowMs; u1 *buf, *b; buf = (u1 *)malloc(HPIF_SIZE(1)); if (buf == NULL) { return; } b = buf; /* If there's a one-shot 'when', reset it. */ if (reason == gDvm.gcHeap->ddmHpifWhen) { if (shouldLock && ! dvmLockHeap()) { ALOGW("%s(): can't lock heap to clear when", __func__); goto skip_when; } if (reason == gDvm.gcHeap->ddmHpifWhen) { if (gDvm.gcHeap->ddmHpifWhen == HPIF_WHEN_NEXT_GC) { gDvm.gcHeap->ddmHpifWhen = HPIF_WHEN_NEVER; } } if (shouldLock) { dvmUnlockHeap(); } } skip_when: /* The current time, in milliseconds since 0:00 GMT, 1/1/70. */ if (gettimeofday(&now, NULL) < 0) { nowMs = 0; } else { nowMs = (u8)now.tv_sec * 1000 + now.tv_usec / 1000; } /* number of heaps */ set4BE(b, 1); b += 4; /* For each heap (of which there is one) */ { /* heap ID */ set4BE(b, DEFAULT_HEAP_ID); b += 4; /* timestamp */ set8BE(b, nowMs); b += 8; /* 'when' value */ *b++ = (u1)reason; /* max allowed heap size in bytes */ set4BE(b, dvmHeapSourceGetMaximumSize()); b += 4; /* current heap size in bytes */ set4BE(b, dvmHeapSourceGetValue(HS_FOOTPRINT, NULL, 0)); b += 4; /* number of bytes allocated */ set4BE(b, dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, NULL, 0)); b += 4; /* number of objects allocated */ set4BE(b, dvmHeapSourceGetValue(HS_OBJECTS_ALLOCATED, NULL, 0)); b += 4; } assert((intptr_t)b == (intptr_t)buf + (intptr_t)HPIF_SIZE(1)); dvmDbgDdmSendChunk(CHUNK_TYPE("HPIF"), b - buf, buf); } bool dvmDdmHandleHpifChunk(int when) { switch (when) { case HPIF_WHEN_NOW: dvmDdmSendHeapInfo(when, true); break; case HPIF_WHEN_NEVER: case HPIF_WHEN_NEXT_GC: case HPIF_WHEN_EVERY_GC: if (dvmLockHeap()) { gDvm.gcHeap->ddmHpifWhen = when; dvmUnlockHeap(); } else { ALOGI("%s(): can't lock heap to set when", __func__); return false; } break; default: ALOGI("%s(): bad when value 0x%08x", __func__, when); return false; } return true; } enum HpsgSolidity { SOLIDITY_FREE = 0, SOLIDITY_HARD = 1, SOLIDITY_SOFT = 2, SOLIDITY_WEAK = 3, SOLIDITY_PHANTOM = 4, SOLIDITY_FINALIZABLE = 5, SOLIDITY_SWEEP = 6, }; enum HpsgKind { KIND_OBJECT = 0, KIND_CLASS_OBJECT = 1, KIND_ARRAY_1 = 2, KIND_ARRAY_2 = 3, KIND_ARRAY_4 = 4, KIND_ARRAY_8 = 5, KIND_UNKNOWN = 6, KIND_NATIVE = 7, }; #define HPSG_PARTIAL (1<<7) #define HPSG_STATE(solidity, kind) \ ((u1)((((kind) & 0x7) << 3) | ((solidity) & 0x7))) struct HeapChunkContext { void* startOfNextMemoryChunk; u1 *buf; u1 *p; u1 *pieceLenField; size_t bufLen; size_t totalAllocationUnits; int type; bool merge; bool needHeader; }; #define ALLOCATION_UNIT_SIZE 8 static void flush_hpsg_chunk(HeapChunkContext *ctx) { if (ctx->pieceLenField == NULL && ctx->needHeader) { /* Already flushed */ return; } /* Patch the "length of piece" field. */ assert(ctx->buf <= ctx->pieceLenField && ctx->pieceLenField <= ctx->p); set4BE(ctx->pieceLenField, ctx->totalAllocationUnits); /* Send the chunk. */ dvmDbgDdmSendChunk(ctx->type, ctx->p - ctx->buf, ctx->buf); /* Reset the context. */ ctx->p = ctx->buf; ctx->totalAllocationUnits = 0; ctx->needHeader = true; ctx->pieceLenField = NULL; } static void append_chunk(HeapChunkContext *ctx, u1 state, void* ptr, size_t length) { /* Make sure there's enough room left in the buffer. * We need to use two bytes for every fractional 256 * allocation units used by the chunk and 17 bytes for * any header. */ { size_t needed = (((length/ALLOCATION_UNIT_SIZE + 255) / 256) * 2) + 17; size_t bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf); if (bytesLeft < needed) { flush_hpsg_chunk(ctx); } bytesLeft = ctx->bufLen - (size_t)(ctx->p - ctx->buf); if (bytesLeft < needed) { ALOGW("chunk is too big to transmit (length=%zd, %zd bytes)", length, needed); return; } } if (ctx->needHeader) { /* * Start a new HPSx chunk. */ /* [u4]: heap ID */ set4BE(ctx->p, DEFAULT_HEAP_ID); ctx->p += 4; /* [u1]: size of allocation unit, in bytes */ *ctx->p++ = 8; /* [u4]: virtual address of segment start */ set4BE(ctx->p, (uintptr_t)ptr); ctx->p += 4; /* [u4]: offset of this piece (relative to the virtual address) */ set4BE(ctx->p, 0); ctx->p += 4; /* [u4]: length of piece, in allocation units * We won't know this until we're done, so save the offset * and stuff in a dummy value. */ ctx->pieceLenField = ctx->p; set4BE(ctx->p, 0x55555555); ctx->p += 4; ctx->needHeader = false; } /* Write out the chunk description. */ length /= ALLOCATION_UNIT_SIZE; // convert to allocation units ctx->totalAllocationUnits += length; while (length > 256) { *ctx->p++ = state | HPSG_PARTIAL; *ctx->p++ = 255; // length - 1 length -= 256; } *ctx->p++ = state; *ctx->p++ = length - 1; } /* * Called by dlmalloc_inspect_all. If used_bytes != 0 then start is * the start of a malloc-ed piece of memory of size used_bytes. If * start is 0 then start is the beginning of any free space not * including dlmalloc's book keeping and end the start of the next * dlmalloc chunk. Regions purely containing book keeping don't * callback. */ static void heap_chunk_callback(void* start, void* end, size_t used_bytes, void* arg) { u1 state; HeapChunkContext *ctx = (HeapChunkContext *)arg; UNUSED_PARAMETER(end); if (used_bytes == 0) { if (start == NULL) { // Reset for start of new heap. ctx->startOfNextMemoryChunk = NULL; flush_hpsg_chunk(ctx); } // Only process in use memory so that free region information // also includes dlmalloc book keeping. return; } /* If we're looking at the native heap, we'll just return * (SOLIDITY_HARD, KIND_NATIVE) for all allocated chunks */ bool native = ctx->type == CHUNK_TYPE("NHSG"); if (ctx->startOfNextMemoryChunk != NULL) { // Transmit any pending free memory. Native free memory of // over kMaxFreeLen could be because of the use of mmaps, so // don't report. If not free memory then start a new segment. bool flush = true; if (start > ctx->startOfNextMemoryChunk) { const size_t kMaxFreeLen = 2 * SYSTEM_PAGE_SIZE; void* freeStart = ctx->startOfNextMemoryChunk; void* freeEnd = start; size_t freeLen = (char*)freeEnd - (char*)freeStart; if (!native || freeLen < kMaxFreeLen) { append_chunk(ctx, HPSG_STATE(SOLIDITY_FREE, 0), freeStart, freeLen); flush = false; } } if (flush) { ctx->startOfNextMemoryChunk = NULL; flush_hpsg_chunk(ctx); } } const Object *obj = (const Object *)start; /* It's an allocated chunk. Figure out what it is. */ //TODO: if ctx.merge, see if this chunk is different from the last chunk. // If it's the same, we should combine them. if (!native && dvmIsValidObject(obj)) { ClassObject *clazz = obj->clazz; if (clazz == NULL) { /* The object was probably just created * but hasn't been initialized yet. */ state = HPSG_STATE(SOLIDITY_HARD, KIND_OBJECT); } else if (dvmIsTheClassClass(clazz)) { state = HPSG_STATE(SOLIDITY_HARD, KIND_CLASS_OBJECT); } else if (IS_CLASS_FLAG_SET(clazz, CLASS_ISARRAY)) { if (IS_CLASS_FLAG_SET(clazz, CLASS_ISOBJECTARRAY)) { state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_4); } else { switch (clazz->elementClass->primitiveType) { case PRIM_BOOLEAN: case PRIM_BYTE: state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_1); break; case PRIM_CHAR: case PRIM_SHORT: state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_2); break; case PRIM_INT: case PRIM_FLOAT: state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_4); break; case PRIM_DOUBLE: case PRIM_LONG: state = HPSG_STATE(SOLIDITY_HARD, KIND_ARRAY_8); break; default: assert(!"Unknown GC heap object type"); state = HPSG_STATE(SOLIDITY_HARD, KIND_UNKNOWN); break; } } } else { state = HPSG_STATE(SOLIDITY_HARD, KIND_OBJECT); } } else { obj = NULL; // it's not actually an object state = HPSG_STATE(SOLIDITY_HARD, KIND_NATIVE); } append_chunk(ctx, state, start, used_bytes + HEAP_SOURCE_CHUNK_OVERHEAD); ctx->startOfNextMemoryChunk = (char*)start + used_bytes + HEAP_SOURCE_CHUNK_OVERHEAD; } enum HpsgWhen { HPSG_WHEN_NEVER = 0, HPSG_WHEN_EVERY_GC = 1, }; enum HpsgWhat { HPSG_WHAT_MERGED_OBJECTS = 0, HPSG_WHAT_DISTINCT_OBJECTS = 1, }; /* * Maximum chunk size. Obtain this from the formula: * * (((maximum_heap_size / ALLOCATION_UNIT_SIZE) + 255) / 256) * 2 */ #define HPSx_CHUNK_SIZE (16384 - 16) static void walkHeap(bool merge, bool native) { HeapChunkContext ctx; memset(&ctx, 0, sizeof(ctx)); ctx.bufLen = HPSx_CHUNK_SIZE; ctx.buf = (u1 *)malloc(ctx.bufLen); if (ctx.buf == NULL) { return; } ctx.merge = merge; if (native) { ctx.type = CHUNK_TYPE("NHSG"); } else { if (ctx.merge) { ctx.type = CHUNK_TYPE("HPSG"); } else { ctx.type = CHUNK_TYPE("HPSO"); } } ctx.p = ctx.buf; ctx.needHeader = true; if (native) { dlmalloc_inspect_all(heap_chunk_callback, (void*)&ctx); } else { dvmHeapSourceWalk(heap_chunk_callback, (void *)&ctx); } if (ctx.p > ctx.buf) { flush_hpsg_chunk(&ctx); } free(ctx.buf); } void dvmDdmSendHeapSegments(bool shouldLock, bool native) { u1 heapId[sizeof(u4)]; GcHeap *gcHeap = gDvm.gcHeap; int when, what; bool merge; /* Don't even grab the lock if there's nothing to do when we're called. */ if (!native) { when = gcHeap->ddmHpsgWhen; what = gcHeap->ddmHpsgWhat; if (when == HPSG_WHEN_NEVER) { return; } } else { when = gcHeap->ddmNhsgWhen; what = gcHeap->ddmNhsgWhat; if (when == HPSG_WHEN_NEVER) { return; } } if (shouldLock && !dvmLockHeap()) { ALOGW("Can't lock heap for DDM HPSx dump"); return; } /* Figure out what kind of chunks we'll be sending. */ if (what == HPSG_WHAT_MERGED_OBJECTS) { merge = true; } else if (what == HPSG_WHAT_DISTINCT_OBJECTS) { merge = false; } else { assert(!"bad HPSG.what value"); return; } /* First, send a heap start chunk. */ set4BE(heapId, DEFAULT_HEAP_ID); dvmDbgDdmSendChunk(native ? CHUNK_TYPE("NHST") : CHUNK_TYPE("HPST"), sizeof(u4), heapId); /* Send a series of heap segment chunks. */ walkHeap(merge, native); /* Finally, send a heap end chunk. */ dvmDbgDdmSendChunk(native ? CHUNK_TYPE("NHEN") : CHUNK_TYPE("HPEN"), sizeof(u4), heapId); if (shouldLock) { dvmUnlockHeap(); } } bool dvmDdmHandleHpsgNhsgChunk(int when, int what, bool native) { ALOGI("dvmDdmHandleHpsgChunk(when %d, what %d, heap %d)", when, what, native); switch (when) { case HPSG_WHEN_NEVER: case HPSG_WHEN_EVERY_GC: break; default: ALOGI("%s(): bad when value 0x%08x", __func__, when); return false; } switch (what) { case HPSG_WHAT_MERGED_OBJECTS: case HPSG_WHAT_DISTINCT_OBJECTS: break; default: ALOGI("%s(): bad what value 0x%08x", __func__, what); return false; } if (dvmLockHeap()) { if (!native) { gDvm.gcHeap->ddmHpsgWhen = when; gDvm.gcHeap->ddmHpsgWhat = what; } else { gDvm.gcHeap->ddmNhsgWhen = when; gDvm.gcHeap->ddmNhsgWhat = what; } //TODO: if what says we should dump immediately, signal (or do) it from here dvmUnlockHeap(); } else { ALOGI("%s(): can't lock heap to set when/what", __func__); return false; } return true; }