/*
 * Dump the stack traces for all threads to the supplied file, putting
 * a timestamp header on it.
 */
static void logThreadStacks(FILE* fp)
{
    DebugOutputTarget target;

    dvmCreateFileOutputTarget(&target, fp);

    pid_t pid = getpid();
    time_t now = time(NULL);
    struct tm* ptm;
#ifdef HAVE_LOCALTIME_R
    struct tm tmbuf;
    ptm = localtime_r(&now, &tmbuf);
#else
    ptm = localtime(&now);
#endif
    dvmPrintDebugMessage(&target,
        "\n\n----- pid %d at %04d-%02d-%02d %02d:%02d:%02d -----\n",
        pid, ptm->tm_year + 1900, ptm->tm_mon+1, ptm->tm_mday,
        ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    printProcessName(&target);
    dvmPrintDebugMessage(&target, "\n");
    dvmDumpJniStats(&target);
    dvmDumpAllThreadsEx(&target, true);
    fprintf(fp, "----- end %d -----\n", pid);
}
/*
 * Dump the stack traces for all threads to the log or to a file.  If it's
 * to a file we have a little setup to do.
 */
static void logThreadStacks(void)
{
    DebugOutputTarget target;

    if (gDvm.stackTraceFile == NULL) {
        /* just dump to log file */
        dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG);
        dvmDumpAllThreadsEx(&target, true);
    } else {
        FILE* fp = NULL;
        int cc, fd;

        /*
         * Open the stack trace output file, creating it if necessary.  It
         * needs to be world-writable so other processes can write to it.
         */
        fd = open(gDvm.stackTraceFile, O_WRONLY | O_APPEND | O_CREAT, 0666);
        if (fd < 0) {
            LOGE("Unable to open stack trace file '%s': %s\n",
                gDvm.stackTraceFile, strerror(errno));
            return;
        }

        /* gain exclusive access to the file */
        cc = flock(fd, LOCK_EX | LOCK_UN);
        if (cc != 0) {
            LOGV("Sleeping on flock(%s)\n", gDvm.stackTraceFile);
            cc = flock(fd, LOCK_EX);
        }
        if (cc != 0) {
            LOGE("Unable to lock stack trace file '%s': %s\n",
                gDvm.stackTraceFile, strerror(errno));
            close(fd);
            return;
        }

        fp = fdopen(fd, "a");
        if (fp == NULL) {
            LOGE("Unable to fdopen '%s' (%d): %s\n",
                gDvm.stackTraceFile, fd, strerror(errno));
            flock(fd, LOCK_UN);
            close(fd);
            return;
        }

        dvmCreateFileOutputTarget(&target, fp);

        pid_t pid = getpid();
        time_t now = time(NULL);
        struct tm* ptm;
#ifdef HAVE_LOCALTIME_R
        struct tm tmbuf;
        ptm = localtime_r(&now, &tmbuf);
#else
        ptm = localtime(&now);
#endif
        dvmPrintDebugMessage(&target,
            "\n\n----- pid %d at %04d-%02d-%02d %02d:%02d:%02d -----\n",
            pid, ptm->tm_year + 1900, ptm->tm_mon+1, ptm->tm_mday,
            ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
        printProcessName(&target);
        dvmPrintDebugMessage(&target, "\n");
        fflush(fp);     /* emit at least the header if we crash during dump */
        dvmDumpAllThreadsEx(&target, true);
        fprintf(fp, "----- end %d -----\n", pid);

        /*
         * Unlock and close the file, flushing pending data before we unlock
         * it.  The fclose() will close the underyling fd.
         */
        fflush(fp);
        flock(fd, LOCK_UN);
        fclose(fp);

        LOGI("Wrote stack trace to '%s'\n", gDvm.stackTraceFile);
    }
}
/*
 * Respond to a SIGQUIT by dumping the thread stacks.  Optionally dump
 * a few other things while we're at it.
 *
 * Thread stacks can either go to the log or to a file designated for holding
 * ANR traces.  If we're writing to a file, we want to do it in one shot,
 * so we can use a single O_APPEND write instead of contending for exclusive
 * access with flock().  There may be an advantage in resuming the VM
 * before doing the file write, so we don't stall the VM if disk I/O is
 * bottlenecked.
 *
 * If JIT tuning is compiled in, dump compiler stats as well.
 */
static void handleSigQuit()
{
    char* traceBuf = NULL;
    size_t traceLen;

    dvmSuspendAllThreads(SUSPEND_FOR_STACK_DUMP);

    dvmDumpLoaderStats("sig");

    if (gDvm.stackTraceFile == NULL) {
        /* just dump to log */
        DebugOutputTarget target;
        dvmCreateLogOutputTarget(&target, ANDROID_LOG_INFO, LOG_TAG);
        dvmDumpJniStats(&target);
        dvmDumpAllThreadsEx(&target, true);
    } else {
        /* write to memory buffer */
        FILE* memfp = open_memstream(&traceBuf, &traceLen);
        if (memfp == NULL) {
            ALOGE("Unable to create memstream for stack traces");
            traceBuf = NULL;        /* make sure it didn't touch this */
            /* continue on */
        } else {
            logThreadStacks(memfp);
            fclose(memfp);
        }
    }

#if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
    dvmCompilerDumpStats();
#endif

    if (false) dvmDumpTrackedAllocations(true);

    dvmResumeAllThreads(SUSPEND_FOR_STACK_DUMP);

    if (traceBuf != NULL) {
        /*
         * We don't know how long it will take to do the disk I/O, so put us
         * into VMWAIT for the duration.
         */
        ThreadStatus oldStatus = dvmChangeStatus(dvmThreadSelf(), THREAD_VMWAIT);

        /*
         * Open the stack trace output file, creating it if necessary.  It
         * needs to be world-writable so other processes can write to it.
         */
        int fd = open(gDvm.stackTraceFile, O_WRONLY | O_APPEND | O_CREAT, 0666);
        if (fd < 0) {
            ALOGE("Unable to open stack trace file '%s': %s",
                gDvm.stackTraceFile, strerror(errno));
        } else {
            ssize_t actual = write(fd, traceBuf, traceLen);
            if (actual != (ssize_t) traceLen) {
                ALOGE("Failed to write stack traces to %s (%d of %zd): %s",
                    gDvm.stackTraceFile, (int) actual, traceLen,
                    strerror(errno));
            } else {
                ALOGI("Wrote stack traces to '%s'", gDvm.stackTraceFile);
            }
            close(fd);
        }

        free(traceBuf);
        dvmChangeStatus(dvmThreadSelf(), oldStatus);
    }
}