/*
 * 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.
 */

#include <fcntl.h>
#include <malloc.h>

#include "Dalvik.h"
#include "HeapInternal.h"
#include "HeapSource.h"
#include "Float12.h"

int dvmGetHeapDebugInfo(HeapDebugInfoType info)
{
    switch (info) {
    case kVirtualHeapSize:
        return (int)dvmHeapSourceGetValue(HS_FOOTPRINT, NULL, 0);
    case kVirtualHeapAllocated:
        return (int)dvmHeapSourceGetValue(HS_BYTES_ALLOCATED, NULL, 0);
    default:
        return -1;
    }
}

/* Looks up the cmdline for the process and tries to find
 * the most descriptive five characters, then inserts the
 * short name into the provided event value.
 */
#define PROC_NAME_LEN 5
static void insertProcessName(long long *ep)
{
    static bool foundRealName = false;
    static char name[PROC_NAME_LEN] = { 'X', 'X', 'X', 'X', 'X' };
    long long event = *ep;

    if (!foundRealName) {
        int fd = open("/proc/self/cmdline", O_RDONLY);
        if (fd > 0) {
            char buf[128];
            ssize_t n = read(fd, buf, sizeof(buf) - 1);
            close(fd);
            if (n > 0) {
                memset(name, 0, sizeof(name));
                if (n <= PROC_NAME_LEN) {
                    // The whole name fits.
                    memcpy(name, buf, n);
                } else {
                    /* We need to truncate.  The name will look something
                     * like "com.android.home".  Favor the characters
                     * immediately following the last dot.
                     */
                    buf[n] = '\0';
                    char *dot = strrchr(buf, '.');
                    if (dot == NULL) {
                        /* Or, look for a slash, in case it's something like
                         * "/system/bin/runtime".
                         */
                        dot = strrchr(buf, '/');
                    }
                    if (dot != NULL) {
                        dot++;  // Skip the dot
                        size_t dotlen = strlen(dot);
                        if (dotlen < PROC_NAME_LEN) {
                            /* Use all available characters.  We know that
                             * n > PROC_NAME_LEN from the check above.
                             */
                            dot -= PROC_NAME_LEN - dotlen;
                        }
                        strncpy(name, dot, PROC_NAME_LEN);
                    } else {
                        // No dot; just use the leading characters.
                        memcpy(name, buf, PROC_NAME_LEN);
                    }
                }
                if (strcmp(buf, "zygote") != 0) {
                    /* If the process is no longer called "zygote",
                     * cache this name.
                     */
                    foundRealName = true;
                }
            }
        }
    }

    event &= ~(0xffffffffffLL << 24);
    event |= (long long)name[0] << 56;
    event |= (long long)name[1] << 48;
    event |= (long long)name[2] << 40;
    event |= (long long)name[3] << 32;
    event |= (long long)name[4] << 24;

    *ep = event;
}

// See device/data/etc/event-log-tags
#define EVENT_LOG_TAG_dvm_gc_info 20001
#define EVENT_LOG_TAG_dvm_gc_madvise_info 20002

void dvmLogGcStats(size_t numFreed, size_t sizeFreed, size_t gcTimeMs)
{
    const GcHeap *gcHeap = gDvm.gcHeap;
    size_t perHeapActualSize[HEAP_SOURCE_MAX_HEAP_COUNT],
           perHeapAllowedSize[HEAP_SOURCE_MAX_HEAP_COUNT],
           perHeapNumAllocated[HEAP_SOURCE_MAX_HEAP_COUNT],
           perHeapSizeAllocated[HEAP_SOURCE_MAX_HEAP_COUNT];
    unsigned char eventBuf[1 + (1 + sizeof(long long)) * 4];
    size_t actualSize, allowedSize, numAllocated, sizeAllocated;
    size_t i;
    size_t softLimit = dvmHeapSourceGetIdealFootprint();
    size_t nHeaps = dvmHeapSourceGetNumHeaps();

    /* Enough to quiet down gcc for unitialized variable check */
    perHeapActualSize[0] = perHeapAllowedSize[0] = perHeapNumAllocated[0] =
                           perHeapSizeAllocated[0] = 0;
    actualSize = dvmHeapSourceGetValue(HS_FOOTPRINT, perHeapActualSize, 
                                       HEAP_SOURCE_MAX_HEAP_COUNT);
    allowedSize = dvmHeapSourceGetValue(HS_ALLOWED_FOOTPRINT, 
                      perHeapAllowedSize, HEAP_SOURCE_MAX_HEAP_COUNT);
    numAllocated = dvmHeapSourceGetValue(HS_OBJECTS_ALLOCATED,
                      perHeapNumAllocated, HEAP_SOURCE_MAX_HEAP_COUNT);
    sizeAllocated = dvmHeapSourceGetValue(HS_BYTES_ALLOCATED,
                      perHeapSizeAllocated, HEAP_SOURCE_MAX_HEAP_COUNT);

    /* 
     * Construct the the first 64-bit value to write to the log. 
     * Global information:
     *
     * [63   ] Must be zero
     * [62-24] ASCII process identifier
     * [23-12] GC time in ms
     * [11- 0] Bytes freed
     *
     */
    long long event0;
    event0 = 0LL << 63 |
            (long long)intToFloat12(gcTimeMs) << 12 |
            (long long)intToFloat12(sizeFreed);
    insertProcessName(&event0);

    /*
     * Aggregated heap stats:
     *
     * [63-62] 10
     * [61-60] Reserved; must be zero
     * [59-48] Objects freed
     * [47-36] Actual size (current footprint)
     * [35-24] Allowed size (current hard max)
     * [23-12] Objects allocated
     * [11- 0] Bytes allocated
     */
    long long event1;
    event1 = 2LL << 62 |
            (long long)intToFloat12(numFreed) << 48 |
            (long long)intToFloat12(actualSize) << 36 |
            (long long)intToFloat12(allowedSize) << 24 |
            (long long)intToFloat12(numAllocated) << 12 |
            (long long)intToFloat12(sizeAllocated);

    /*
     * Report the current state of the zygote heap(s).
     *
     * The active heap is always heap[0].  We can be in one of three states
     * at present:
     *
     *  (1) Still in the zygote.  Zygote using heap[0].
     *  (2) In the zygote, when the first child is started.  We created a
     *      new heap just before the first fork() call, so the original
     *      "zygote heap" is now heap[1], and we have a small heap[0] for
     *      anything we do from here on.
     *  (3) In an app process.  The app gets a new heap[0], and can also
     *      see the two zygote heaps [1] and [2] (probably unwise to
     *      assume any specific ordering).
     *
     * So if nHeaps == 1, we want the stats from heap[0]; else we want
     * the sum of the values from heap[1] to heap[nHeaps-1].
     *
     *
     * Zygote heap stats (except for the soft limit, which belongs to the
     * active heap):
     *
     * [63-62] 11
     * [61-60] Reserved; must be zero
     * [59-48] Soft Limit (for the active heap)
     * [47-36] Actual size (current footprint)
     * [35-24] Allowed size (current hard max)
     * [23-12] Objects allocated
     * [11- 0] Bytes allocated
     */
    long long event2;
    size_t zActualSize, zAllowedSize, zNumAllocated, zSizeAllocated;
    int firstHeap = (nHeaps == 1) ? 0 : 1;
    size_t hh;

    zActualSize = zAllowedSize = zNumAllocated = zSizeAllocated = 0;
    for (hh = firstHeap; hh < nHeaps; hh++) {
        zActualSize += perHeapActualSize[hh];
        zAllowedSize += perHeapAllowedSize[hh];
        zNumAllocated += perHeapNumAllocated[hh];
        zSizeAllocated += perHeapSizeAllocated[hh];
    }
    event2 = 3LL << 62 |
            (long long)intToFloat12(softLimit) << 48 |
            (long long)intToFloat12(zActualSize) << 36 |
            (long long)intToFloat12(zAllowedSize) << 24 |
            (long long)intToFloat12(zNumAllocated) << 12 |
            (long long)intToFloat12(zSizeAllocated);

    /*
     * Report the current external allocation stats and the native heap
     * summary.
     *
     * [63-48] Reserved; must be zero (TODO: put new data in these slots)
     * [47-36] dlmalloc_footprint
     * [35-24] mallinfo: total allocated space
     * [23-12] External byte limit
     * [11- 0] External bytes allocated
     */
    long long event3;
    size_t externalLimit, externalBytesAllocated;
    size_t uordblks, footprint;

#if 0
    /*
     * This adds 2-5msec to the GC cost on a DVT, or about 2-3% of the cost
     * of a GC, so it's not horribly expensive but it's not free either.
     */
    extern size_t dlmalloc_footprint(void);
    struct mallinfo mi;
    //u8 start, end;

    //start = dvmGetRelativeTimeNsec();
    mi = mallinfo();
    uordblks = mi.uordblks;
    footprint = dlmalloc_footprint();
    //end = dvmGetRelativeTimeNsec();
    //LOGD("mallinfo+footprint took %dusec; used=%zd footprint=%zd\n",
    //    (int)((end - start) / 1000), mi.uordblks, footprint);
#else
    uordblks = footprint = 0;
#endif

    externalLimit =
            dvmHeapSourceGetValue(HS_EXTERNAL_LIMIT, NULL, 0);
    externalBytesAllocated =
            dvmHeapSourceGetValue(HS_EXTERNAL_BYTES_ALLOCATED, NULL, 0);
    event3 =
            (long long)intToFloat12(footprint) << 36 |
            (long long)intToFloat12(uordblks) << 24 |
            (long long)intToFloat12(externalLimit) << 12 |
            (long long)intToFloat12(externalBytesAllocated);

    /* Build the event data.
     * [ 0: 0] item count (4)
     * [ 1: 1] EVENT_TYPE_LONG
     * [ 2: 9] event0
     * [10:10] EVENT_TYPE_LONG
     * [11:18] event1
     * [19:19] EVENT_TYPE_LONG
     * [20:27] event2
     * [28:28] EVENT_TYPE_LONG
     * [29:36] event2
     */
    unsigned char *c = eventBuf;
    *c++ = 4;
    *c++ = EVENT_TYPE_LONG;
    memcpy(c, &event0, sizeof(event0));
    c += sizeof(event0);
    *c++ = EVENT_TYPE_LONG;
    memcpy(c, &event1, sizeof(event1));
    c += sizeof(event1);
    *c++ = EVENT_TYPE_LONG;
    memcpy(c, &event2, sizeof(event2));
    c += sizeof(event2);
    *c++ = EVENT_TYPE_LONG;
    memcpy(c, &event3, sizeof(event3));

    (void) android_btWriteLog(EVENT_LOG_TAG_dvm_gc_info, EVENT_TYPE_LIST,
            eventBuf, sizeof(eventBuf));
}

void dvmLogMadviseStats(size_t madvisedSizes[], size_t arrayLen)
{
    unsigned char eventBuf[1 + (1 + sizeof(int)) * 2];
    size_t total, zyg;
    size_t firstHeap, i;
    size_t nHeaps = dvmHeapSourceGetNumHeaps();

    assert(arrayLen >= nHeaps);

    firstHeap = nHeaps > 1 ? 1 : 0;
    total = 0;
    zyg = 0;
    for (i = 0; i < nHeaps; i++) {
        total += madvisedSizes[i];
        if (i >= firstHeap) {
            zyg += madvisedSizes[i];
        }
    }

    /* Build the event data.
     * [ 0: 0] item count (2)
     * [ 1: 1] EVENT_TYPE_INT
     * [ 2: 5] total madvise byte count
     * [ 6: 6] EVENT_TYPE_INT
     * [ 7:10] zygote heap madvise byte count
     */
    unsigned char *c = eventBuf;
    *c++ = 2;
    *c++ = EVENT_TYPE_INT;
    memcpy(c, &total, sizeof(total));
    c += sizeof(total);
    *c++ = EVENT_TYPE_INT;
    memcpy(c, &zyg, sizeof(zyg));
    c += sizeof(zyg);

    (void) android_btWriteLog(EVENT_LOG_TAG_dvm_gc_madvise_info,
            EVENT_TYPE_LIST, eventBuf, sizeof(eventBuf));
}

#if 0
#include <errno.h>
#include <stdio.h>

typedef struct HeapDumpContext {
    FILE *fp;
    void *chunkStart;
    size_t chunkLen;
    bool chunkFree;
} HeapDumpContext;

static void
dump_context(const HeapDumpContext *ctx)
{
    fprintf(ctx->fp, "0x%08x %12.12zd %s\n", (uintptr_t)ctx->chunkStart,
            ctx->chunkLen, ctx->chunkFree ? "FREE" : "USED");
}

static void
heap_chunk_callback(const void *chunkptr, size_t chunklen,
                    const void *userptr, size_t userlen, void *arg)
{
    HeapDumpContext *ctx = (HeapDumpContext *)arg;
    bool chunkFree = (userptr == NULL);

    if (chunkFree != ctx->chunkFree ||
            ((char *)ctx->chunkStart + ctx->chunkLen) != chunkptr)
    {
        /* The new chunk is of a different type or isn't
         * contiguous with the current chunk.  Dump the
         * old one and start a new one.
         */
        if (ctx->chunkStart != NULL) {
            /* It's not the first chunk. */
            dump_context(ctx);
        }
        ctx->chunkStart = (void *)chunkptr;
        ctx->chunkLen = chunklen;
        ctx->chunkFree = chunkFree;
    } else {
        /* Extend the current chunk.
         */
        ctx->chunkLen += chunklen;
    }
}

/* Dumps free and used ranges, as text, to the named file.
 */
void dvmDumpHeapToFile(const char *fileName)
{
    HeapDumpContext ctx;
    FILE *fp;

    fp = fopen(fileName, "w+");
    if (fp == NULL) {
        LOGE("Can't open %s for writing: %s\n", fileName, strerror(errno));
        return;
    }
    LOGW("Dumping heap to %s...\n", fileName);

    fprintf(fp, "==== Dalvik heap dump ====\n");
    memset(&ctx, 0, sizeof(ctx));
    ctx.fp = fp;
    dvmHeapSourceWalk(heap_chunk_callback, (void *)&ctx);
    dump_context(&ctx);
    fprintf(fp, "==== end heap dump ====\n");

    LOGW("Dumped heap to %s.\n", fileName);

    fclose(fp);
}
#endif
