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/*
18 * Preparation and completion of hprof data generation.  The output is
19 * written into two files and then combined.  This is necessary because
20 * we generate some of the data (strings and classes) while we dump the
21 * heap, and some analysis tools require that the class and string data
22 * appear first.
23 */
24#include "Hprof.h"
25
26#include <string.h>
27#include <unistd.h>
28#include <errno.h>
29#include <sys/time.h>
30#include <time.h>
31
32
33#define kHeadSuffix "-hptemp"
34
35hprof_context_t *
36hprofStartup(const char *outputFileName, bool directToDdms)
37{
38    FILE* fp = NULL;
39
40    if (!directToDdms) {
41        int len = strlen(outputFileName);
42        char fileName[len + sizeof(kHeadSuffix)];
43
44        /* Construct the temp file name.  This wasn't handed to us by the
45         * application, so we need to be careful about stomping on it.
46         */
47        sprintf(fileName, "%s" kHeadSuffix, outputFileName);
48        if (access(fileName, F_OK) == 0) {
49            LOGE("hprof: temp file %s exists, bailing\n", fileName);
50            return NULL;
51        }
52
53        fp = fopen(fileName, "w+");
54        if (fp == NULL) {
55            LOGE("hprof: can't open %s: %s.\n", fileName, strerror(errno));
56            return NULL;
57        }
58        if (unlink(fileName) != 0) {
59            LOGW("hprof: WARNING: unable to remove temp file %s\n", fileName);
60            /* keep going */
61        }
62        LOGI("hprof: dumping VM heap to \"%s\".\n", fileName);
63    }
64
65    hprofStartup_String();
66    hprofStartup_Class();
67#if WITH_HPROF_STACK
68    hprofStartup_StackFrame();
69    hprofStartup_Stack();
70#endif
71
72    hprof_context_t *ctx = malloc(sizeof(*ctx));
73    if (ctx == NULL) {
74        LOGE("hprof: can't allocate context.\n");
75        if (fp != NULL)
76            fclose(fp);
77        return NULL;
78    }
79
80    /* pass in "fp" for the temp file, and the name of the output file */
81    hprofContextInit(ctx, strdup(outputFileName), fp, false, directToDdms);
82
83    assert(ctx->fp != NULL);
84
85    return ctx;
86}
87
88/*
89 * Copy the entire contents of "srcFp" to "dstFp".
90 *
91 * Returns "true" on success.
92 */
93static bool
94copyFileToFile(FILE *dstFp, FILE *srcFp)
95{
96    char buf[65536];
97    size_t dataRead, dataWritten;
98
99    while (true) {
100        dataRead = fread(buf, 1, sizeof(buf), srcFp);
101        if (dataRead > 0) {
102            dataWritten = fwrite(buf, 1, dataRead, dstFp);
103            if (dataWritten != dataRead) {
104                LOGE("hprof: failed writing data (%d of %d): %s\n",
105                    dataWritten, dataRead, strerror(errno));
106                return false;
107            }
108        } else {
109            if (feof(srcFp))
110                return true;
111            LOGE("hprof: failed reading data (res=%d): %s\n",
112                dataRead, strerror(errno));
113            return false;
114        }
115    }
116}
117
118/*
119 * Finish up the hprof dump.  Returns true on success.
120 */
121bool
122hprofShutdown(hprof_context_t *tailCtx)
123{
124    FILE *fp = NULL;
125
126    /* flush output to the temp file, then prepare the output file */
127    hprofFlushCurrentRecord(tailCtx);
128
129    LOGI("hprof: dumping heap strings to \"%s\".\n", tailCtx->fileName);
130    if (!tailCtx->directToDdms) {
131        fp = fopen(tailCtx->fileName, "w");
132        if (fp == NULL) {
133            LOGE("can't open %s: %s\n", tailCtx->fileName, strerror(errno));
134            hprofFreeContext(tailCtx);
135            return false;
136        }
137    }
138
139    /*
140     * Create a new context struct for the start of the file.  We
141     * heap-allocate it so we can share the "free" function.
142     */
143    hprof_context_t *headCtx = malloc(sizeof(*headCtx));
144    if (headCtx == NULL) {
145        LOGE("hprof: can't allocate context.\n");
146        if (fp != NULL)
147            fclose(fp);
148        hprofFreeContext(tailCtx);
149        return NULL;
150    }
151    hprofContextInit(headCtx, strdup(tailCtx->fileName), fp, true,
152        tailCtx->directToDdms);
153
154    hprofDumpStrings(headCtx);
155    hprofDumpClasses(headCtx);
156
157    /* Write a dummy stack trace record so the analysis
158     * tools don't freak out.
159     */
160    hprofStartNewRecord(headCtx, HPROF_TAG_STACK_TRACE, HPROF_TIME);
161    hprofAddU4ToRecord(&headCtx->curRec, HPROF_NULL_STACK_TRACE);
162    hprofAddU4ToRecord(&headCtx->curRec, HPROF_NULL_THREAD);
163    hprofAddU4ToRecord(&headCtx->curRec, 0);    // no frames
164
165#if WITH_HPROF_STACK
166    hprofDumpStackFrames(headCtx);
167    hprofDumpStacks(headCtx);
168#endif
169
170    hprofFlushCurrentRecord(headCtx);
171
172    hprofShutdown_Class();
173    hprofShutdown_String();
174#if WITH_HPROF_STACK
175    hprofShutdown_Stack();
176    hprofShutdown_StackFrame();
177#endif
178
179    if (tailCtx->directToDdms) {
180        /* flush to ensure memstream pointer and size are updated */
181        fflush(headCtx->fp);
182        fflush(tailCtx->fp);
183
184        /* send the data off to DDMS */
185        struct iovec iov[2];
186        iov[0].iov_base = headCtx->fileDataPtr;
187        iov[0].iov_len = headCtx->fileDataSize;
188        iov[1].iov_base = tailCtx->fileDataPtr;
189        iov[1].iov_len = tailCtx->fileDataSize;
190        dvmDbgDdmSendChunkV(CHUNK_TYPE("HPDS"), iov, 2);
191    } else {
192        /*
193         * Append the contents of the temp file to the output file.  The temp
194         * file was removed immediately after being opened, so it will vanish
195         * when we close it.
196         */
197        rewind(tailCtx->fp);
198        if (!copyFileToFile(headCtx->fp, tailCtx->fp)) {
199            LOGW("hprof: file copy failed, hprof data may be incomplete\n");
200            /* finish up anyway */
201        }
202    }
203
204    hprofFreeContext(headCtx);
205    hprofFreeContext(tailCtx);
206
207    /* throw out a log message for the benefit of "runhat" */
208    LOGI("hprof: heap dump completed, temp file removed\n");
209    return true;
210}
211
212/*
213 * Free any heap-allocated items in "ctx", and then free "ctx" itself.
214 */
215void
216hprofFreeContext(hprof_context_t *ctx)
217{
218    assert(ctx != NULL);
219
220    if (ctx->fp != NULL)
221        fclose(ctx->fp);
222    free(ctx->curRec.body);
223    free(ctx->fileName);
224    free(ctx->fileDataPtr);
225    free(ctx);
226}
227
228