/*
* 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/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 {
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)
{
/* 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 heap_chunk_callback(const void *chunkptr, size_t chunklen,
const void *userptr, size_t userlen, void *arg)
{
HeapChunkContext *ctx = (HeapChunkContext *)arg;
u1 state;
UNUSED_PARAMETER(userlen);
assert((chunklen & (ALLOCATION_UNIT_SIZE-1)) == 0);
/* 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.
*/
{
size_t needed = (((chunklen/ALLOCATION_UNIT_SIZE + 255) / 256) * 2);
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 (chunklen=%zd, %zd bytes)",
chunklen, needed);
return;
}
}
//TODO: notice when there's a gap and start a new heap, or at least a new range.
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)chunkptr); 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;
}
/* Determine the type of this chunk.
*/
if (userptr == NULL) {
/* It's a free chunk.
*/
state = HPSG_STATE(SOLIDITY_FREE, 0);
} else {
const Object *obj = (const Object *)userptr;
/* 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");
/* 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);
}
}
/* Write out the chunk description.
*/
chunklen /= ALLOCATION_UNIT_SIZE; // convert to allocation units
ctx->totalAllocationUnits += chunklen;
while (chunklen > 256) {
*ctx->p++ = state | HPSG_PARTIAL;
*ctx->p++ = 255; // length - 1
chunklen -= 256;
}
*ctx->p++ = state;
*ctx->p++ = chunklen - 1;
}
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)
extern "C" void dlmalloc_walk_heap(void(*)(const void*, size_t, const void*, size_t, void*),void*);
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_walk_heap(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;
}