Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 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 | |
| 25 | #include "hprof.h" |
| 26 | #include "heap.h" |
| 27 | #include "debugger.h" |
| 28 | #include "stringprintf.h" |
| 29 | #include "thread_list.h" |
| 30 | #include "logging.h" |
| 31 | |
| 32 | #include <sys/uio.h> |
| 33 | #include <string.h> |
| 34 | #include <unistd.h> |
| 35 | #include <fcntl.h> |
| 36 | #include <errno.h> |
| 37 | #include <sys/time.h> |
| 38 | #include <time.h> |
| 39 | |
| 40 | namespace art { |
| 41 | |
| 42 | namespace hprof { |
| 43 | |
| 44 | #define kHeadSuffix "-hptemp" |
| 45 | |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 46 | // TODO: use File::WriteFully |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 47 | int sysWriteFully(int fd, const void* buf, size_t count, const char* logMsg) { |
| 48 | while (count != 0) { |
| 49 | ssize_t actual = TEMP_FAILURE_RETRY(write(fd, buf, count)); |
| 50 | if (actual < 0) { |
| 51 | int err = errno; |
| 52 | LOG(ERROR) << StringPrintf("%s: write failed: %s", logMsg, strerror(err)); |
| 53 | return err; |
| 54 | } else if (actual != (ssize_t) count) { |
| 55 | LOG(DEBUG) << StringPrintf("%s: partial write (will retry): (%d of %zd)", |
| 56 | logMsg, (int) actual, count); |
| 57 | buf = (const void*) (((const uint8_t*) buf) + actual); |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 58 | } |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 59 | count -= actual; |
| 60 | } |
| 61 | return 0; |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | /* |
| 65 | * Finish up the hprof dump. Returns true on success. |
| 66 | */ |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 67 | bool Hprof::Finish() { |
| 68 | // flush the "tail" portion of the output |
| 69 | StartNewRecord(HPROF_TAG_HEAP_DUMP_END, HPROF_TIME); |
| 70 | FlushCurrentRecord(); |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 71 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 72 | // create a new Hprof for the start of the file (as opposed to this, which is the tail) |
| 73 | Hprof headCtx(file_name_, fd_, true, direct_to_ddms_); |
| 74 | headCtx.classes_ = classes_; |
| 75 | headCtx.strings_ = strings_; |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 76 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 77 | LOG(INFO) << StringPrintf("hprof: dumping heap strings to \"%s\".", file_name_); |
| 78 | headCtx.DumpStrings(); |
| 79 | headCtx.DumpClasses(); |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 80 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 81 | // write a dummy stack trace record so the analysis tools don't freak out |
| 82 | headCtx.StartNewRecord(HPROF_TAG_STACK_TRACE, HPROF_TIME); |
| 83 | headCtx.current_record_.AddU4(HPROF_NULL_STACK_TRACE); |
| 84 | headCtx.current_record_.AddU4(HPROF_NULL_THREAD); |
| 85 | headCtx.current_record_.AddU4(0); // no frames |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 86 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 87 | headCtx.FlushCurrentRecord(); |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 88 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 89 | // flush to ensure memstream pointer and size are updated |
| 90 | fflush(headCtx.mem_fp_); |
| 91 | fflush(mem_fp_); |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 92 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 93 | if (direct_to_ddms_) { |
| 94 | // send the data off to DDMS |
| 95 | struct iovec iov[2]; |
| 96 | iov[0].iov_base = headCtx.file_data_ptr_; |
| 97 | iov[0].iov_len = headCtx.file_data_size_; |
| 98 | iov[1].iov_base = file_data_ptr_; |
| 99 | iov[1].iov_len = file_data_size_; |
| 100 | Dbg::DdmSendChunkV(CHUNK_TYPE("HPDS"), iov, 2); |
| 101 | } else { |
| 102 | // open the output file, and copy the head and tail to it. |
| 103 | CHECK_EQ(headCtx.fd_, fd_); |
| 104 | |
| 105 | int outFd; |
| 106 | if (headCtx.fd_ >= 0) { |
| 107 | outFd = dup(headCtx.fd_); |
| 108 | if (outFd < 0) { |
| 109 | LOG(ERROR) << StringPrintf("dup(%d) failed: %s", headCtx.fd_, strerror(errno)); |
| 110 | // continue to fail-handler below |
| 111 | } |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 112 | } else { |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 113 | outFd = open(file_name_, O_WRONLY|O_CREAT|O_TRUNC, 0644); |
| 114 | if (outFd < 0) { |
| 115 | LOG(ERROR) << StringPrintf("can't open %s: %s", headCtx.file_name_, strerror(errno)); |
| 116 | // continue to fail-handler below |
| 117 | } |
| 118 | } |
| 119 | if (outFd < 0) { |
| 120 | return false; |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 121 | } |
| 122 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 123 | int result = sysWriteFully(outFd, headCtx.file_data_ptr_, |
| 124 | headCtx.file_data_size_, "hprof-head"); |
| 125 | result |= sysWriteFully(outFd, file_data_ptr_, file_data_size_, "hprof-tail"); |
| 126 | close(outFd); |
| 127 | if (result != 0) { |
| 128 | return false; |
| 129 | } |
| 130 | } |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 131 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 132 | // throw out a log message for the benefit of "runhat" |
| 133 | LOG(INFO) << StringPrintf("hprof: heap dump completed (%dKB)", |
| 134 | (headCtx.file_data_size_ + file_data_size_ + 1023) / 1024); |
| 135 | |
| 136 | return true; |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 137 | } |
| 138 | |
Jesse Wilson | 3aa66fd | 2011-11-08 20:53:40 -0500 | [diff] [blame] | 139 | Hprof::~Hprof() { |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 140 | // we don't own ctx->fd_, do not close |
| 141 | if (mem_fp_ != NULL) { |
| 142 | fclose(mem_fp_); |
| 143 | } |
| 144 | free(current_record_.body_); |
| 145 | free(file_name_); |
| 146 | free(file_data_ptr_); |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 147 | } |
| 148 | |
| 149 | /* |
| 150 | * Visitor invoked on every root reference. |
| 151 | */ |
| 152 | void HprofRootVisitor(const Object* obj, void* arg) { |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 153 | CHECK(arg != NULL); |
| 154 | Hprof* hprof = (Hprof*)arg; |
| 155 | hprof->VisitRoot(obj); |
Jesse Wilson | 3aa66fd | 2011-11-08 20:53:40 -0500 | [diff] [blame] | 156 | } |
| 157 | |
| 158 | void Hprof::VisitRoot(const Object* obj) { |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 159 | uint32_t threadId = 0; // TODO |
| 160 | /*RootType */ size_t type = 0; // TODO |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 161 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 162 | static const HprofHeapTag xlate[] = { |
| 163 | HPROF_ROOT_UNKNOWN, |
| 164 | HPROF_ROOT_JNI_GLOBAL, |
| 165 | HPROF_ROOT_JNI_LOCAL, |
| 166 | HPROF_ROOT_JAVA_FRAME, |
| 167 | HPROF_ROOT_NATIVE_STACK, |
| 168 | HPROF_ROOT_STICKY_CLASS, |
| 169 | HPROF_ROOT_THREAD_BLOCK, |
| 170 | HPROF_ROOT_MONITOR_USED, |
| 171 | HPROF_ROOT_THREAD_OBJECT, |
| 172 | HPROF_ROOT_INTERNED_STRING, |
| 173 | HPROF_ROOT_FINALIZING, |
| 174 | HPROF_ROOT_DEBUGGER, |
| 175 | HPROF_ROOT_REFERENCE_CLEANUP, |
| 176 | HPROF_ROOT_VM_INTERNAL, |
| 177 | HPROF_ROOT_JNI_MONITOR, |
| 178 | }; |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 179 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 180 | CHECK_LT(type, sizeof(xlate) / sizeof(HprofHeapTag)); |
| 181 | if (obj == NULL) { |
| 182 | return; |
| 183 | } |
| 184 | gc_scan_state_ = xlate[type]; |
| 185 | gc_thread_serial_number_ = threadId; |
| 186 | MarkRootObject(obj, 0); |
| 187 | gc_scan_state_ = 0; |
| 188 | gc_thread_serial_number_ = 0; |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 189 | } |
| 190 | |
| 191 | /* |
| 192 | * Visitor invoked on every heap object. |
| 193 | */ |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 194 | static void HprofBitmapCallback(Object *obj, void *arg) { |
| 195 | CHECK(obj != NULL); |
| 196 | CHECK(arg != NULL); |
| 197 | Hprof *hprof = (Hprof*)arg; |
| 198 | hprof->DumpHeapObject(obj); |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 199 | } |
| 200 | |
| 201 | /* |
| 202 | * Walk the roots and heap writing heap information to the specified |
| 203 | * file. |
| 204 | * |
| 205 | * If "fd" is >= 0, the output will be written to that file descriptor. |
Jesse Wilson | 3aa66fd | 2011-11-08 20:53:40 -0500 | [diff] [blame] | 206 | * Otherwise, "file_name_" is used to create an output file. |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 207 | * |
Jesse Wilson | 3aa66fd | 2011-11-08 20:53:40 -0500 | [diff] [blame] | 208 | * If "direct_to_ddms_" is set, the other arguments are ignored, and data is |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 209 | * sent directly to DDMS. |
| 210 | * |
| 211 | * Returns 0 on success, or an error code on failure. |
| 212 | */ |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 213 | int DumpHeap(const char* fileName, int fd, bool directToDdms) { |
| 214 | CHECK(fileName != NULL); |
| 215 | ScopedHeapLock lock; |
| 216 | ScopedThreadStateChange tsc(Thread::Current(), Thread::kRunnable); |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 217 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 218 | ThreadList* thread_list = Runtime::Current()->GetThreadList(); |
| 219 | thread_list->SuspendAll(); |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 220 | |
Jesse Wilson | 0b075f1 | 2011-11-09 10:57:41 -0500 | [diff] [blame^] | 221 | Hprof hprof(fileName, fd, false, directToDdms); |
| 222 | Runtime::Current()->VisitRoots(HprofRootVisitor, &hprof); |
| 223 | Heap::GetLiveBits()->Walk(HprofBitmapCallback, &hprof); |
| 224 | // TODO: write a HEAP_SUMMARY record |
| 225 | int success = hprof.Finish() ? 0 : -1; |
| 226 | thread_list->ResumeAll(); |
| 227 | return success; |
Jesse Wilson | c4824e6 | 2011-11-01 14:39:04 -0400 | [diff] [blame] | 228 | } |
| 229 | |
| 230 | } // namespace hprof |
| 231 | |
| 232 | } // namespace art |