| OLD | NEW |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 #include "platform/globals.h" | 5 #include "platform/globals.h" |
| 6 | 6 |
| 7 #if defined(DART_USE_TCMALLOC) && !defined(PRODUCT) | 7 #if defined(DART_USE_TCMALLOC) && !defined(PRODUCT) |
| 8 | 8 |
| 9 #include "vm/malloc_hooks.h" | 9 #include "vm/malloc_hooks.h" |
| 10 | 10 |
| 11 #include "gperftools/malloc_hook.h" | 11 #include "gperftools/malloc_hook.h" |
| 12 | 12 |
| 13 #include "platform/assert.h" | 13 #include "platform/assert.h" |
| 14 #include "vm/hash_map.h" | 14 #include "vm/hash_map.h" |
| 15 #include "vm/json_stream.h" | 15 #include "vm/json_stream.h" |
| 16 #include "vm/lockers.h" | 16 #include "vm/os_thread.h" |
| 17 | 17 |
| 18 namespace dart { | 18 namespace dart { |
| 19 | 19 |
| 20 // A locker-type class to automatically grab and release the | 20 // A locker-type class similar to MutexLocker which tracks which thread |
| 21 // in_malloc_hook_flag_. | 21 // currently holds the lock. We use this instead of MutexLocker and |
| 22 class MallocHookScope { | 22 // mutex->IsOwnedByCurrentThread() since IsOwnedByCurrentThread() is only |
| 23 // enabled for debug mode. |
| 24 class MallocLocker : public ValueObject { |
| 23 public: | 25 public: |
| 24 static void InitMallocHookFlag() { | 26 explicit MallocLocker(Mutex* mutex, ThreadId* owner) |
| 25 MutexLocker ml(malloc_hook_scope_mutex_); | 27 : mutex_(mutex), owner_(owner) { |
| 26 ASSERT(in_malloc_hook_flag_ == kUnsetThreadLocalKey); | 28 ASSERT(owner != NULL); |
| 27 in_malloc_hook_flag_ = OSThread::CreateThreadLocal(); | 29 mutex_->Lock(); |
| 28 OSThread::SetThreadLocal(in_malloc_hook_flag_, 0); | 30 ASSERT(*owner_ == OSThread::kInvalidThreadId); |
| 31 *owner_ = OSThread::GetCurrentThreadId(); |
| 29 } | 32 } |
| 30 | 33 |
| 31 static void DestroyMallocHookFlag() { | 34 virtual ~MallocLocker() { |
| 32 MutexLocker ml(malloc_hook_scope_mutex_); | 35 ASSERT(*owner_ == OSThread::GetCurrentThreadId()); |
| 33 ASSERT(in_malloc_hook_flag_ != kUnsetThreadLocalKey); | 36 *owner_ = OSThread::kInvalidThreadId; |
| 34 OSThread::DeleteThreadLocal(in_malloc_hook_flag_); | 37 mutex_->Unlock(); |
| 35 in_malloc_hook_flag_ = kUnsetThreadLocalKey; | |
| 36 } | |
| 37 | |
| 38 MallocHookScope() { | |
| 39 MutexLocker ml(malloc_hook_scope_mutex_); | |
| 40 ASSERT(in_malloc_hook_flag_ != kUnsetThreadLocalKey); | |
| 41 OSThread::SetThreadLocal(in_malloc_hook_flag_, 1); | |
| 42 } | |
| 43 | |
| 44 ~MallocHookScope() { | |
| 45 MutexLocker ml(malloc_hook_scope_mutex_); | |
| 46 ASSERT(in_malloc_hook_flag_ != kUnsetThreadLocalKey); | |
| 47 OSThread::SetThreadLocal(in_malloc_hook_flag_, 0); | |
| 48 } | |
| 49 | |
| 50 static bool IsInHook() { | |
| 51 MutexLocker ml(malloc_hook_scope_mutex_); | |
| 52 if (in_malloc_hook_flag_ == kUnsetThreadLocalKey) { | |
| 53 // Bail out if the malloc hook flag is invalid. This means that | |
| 54 // MallocHookState::TearDown() has been called and MallocHookScope is no | |
| 55 // longer intitialized. Don't worry if MallocHookState::TearDown() is | |
| 56 // called before the hooks grab the mutex, since | |
| 57 // MallocHooksState::Active() is checked after the lock is taken before | |
| 58 // proceeding to act on the allocation/free. | |
| 59 return false; | |
| 60 } | |
| 61 return OSThread::GetThreadLocal(in_malloc_hook_flag_); | |
| 62 } | 38 } |
| 63 | 39 |
| 64 private: | 40 private: |
| 65 static Mutex* malloc_hook_scope_mutex_; | 41 Mutex* mutex_; |
| 66 static ThreadLocalKey in_malloc_hook_flag_; | 42 ThreadId* owner_; |
| 67 | |
| 68 DISALLOW_ALLOCATION(); | |
| 69 DISALLOW_COPY_AND_ASSIGN(MallocHookScope); | |
| 70 }; | 43 }; |
| 71 | 44 |
| 72 | 45 |
| 73 // Custom key/value trait specifically for address/size pairs. Unlike | 46 // Custom key/value trait specifically for address/size pairs. Unlike |
| 74 // RawPointerKeyValueTrait, the default value is -1 as 0 can be a valid entry. | 47 // RawPointerKeyValueTrait, the default value is -1 as 0 can be a valid entry. |
| 75 class AddressKeyValueTrait { | 48 class AddressKeyValueTrait { |
| 76 public: | 49 public: |
| 77 typedef const void* Key; | 50 typedef const void* Key; |
| 78 typedef intptr_t Value; | 51 typedef intptr_t Value; |
| 79 | 52 |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 128 active_ = true; | 101 active_ = true; |
| 129 original_pid_ = OS::ProcessId(); | 102 original_pid_ = OS::ProcessId(); |
| 130 } | 103 } |
| 131 | 104 |
| 132 static bool IsOriginalProcess() { | 105 static bool IsOriginalProcess() { |
| 133 ASSERT(original_pid_ != kInvalidPid); | 106 ASSERT(original_pid_ != kInvalidPid); |
| 134 return original_pid_ == OS::ProcessId(); | 107 return original_pid_ == OS::ProcessId(); |
| 135 } | 108 } |
| 136 | 109 |
| 137 static Mutex* malloc_hook_mutex() { return malloc_hook_mutex_; } | 110 static Mutex* malloc_hook_mutex() { return malloc_hook_mutex_; } |
| 111 static ThreadId* malloc_hook_mutex_owner() { |
| 112 return &malloc_hook_mutex_owner_; |
| 113 } |
| 114 static bool IsLockHeldByCurrentThread() { |
| 115 return (malloc_hook_mutex_owner_ == OSThread::GetCurrentThreadId()); |
| 116 } |
| 138 | 117 |
| 139 static intptr_t allocation_count() { return allocation_count_; } | 118 static intptr_t allocation_count() { return allocation_count_; } |
| 140 | 119 |
| 141 static intptr_t heap_allocated_memory_in_bytes() { | 120 static intptr_t heap_allocated_memory_in_bytes() { |
| 142 return heap_allocated_memory_in_bytes_; | 121 return heap_allocated_memory_in_bytes_; |
| 143 } | 122 } |
| 144 | 123 |
| 145 static void IncrementHeapAllocatedMemoryInBytes(intptr_t size) { | 124 static void IncrementHeapAllocatedMemoryInBytes(intptr_t size) { |
| 146 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); | 125 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); |
| 147 ASSERT(size >= 0); | 126 ASSERT(size >= 0); |
| (...skipping 24 matching lines...) Expand all Loading... |
| 172 active_ = false; | 151 active_ = false; |
| 173 original_pid_ = kInvalidPid; | 152 original_pid_ = kInvalidPid; |
| 174 ResetStats(); | 153 ResetStats(); |
| 175 delete address_map_; | 154 delete address_map_; |
| 176 } | 155 } |
| 177 | 156 |
| 178 private: | 157 private: |
| 179 static bool active_; | 158 static bool active_; |
| 180 static intptr_t original_pid_; | 159 static intptr_t original_pid_; |
| 181 static Mutex* malloc_hook_mutex_; | 160 static Mutex* malloc_hook_mutex_; |
| 161 static ThreadId malloc_hook_mutex_owner_; |
| 182 static intptr_t allocation_count_; | 162 static intptr_t allocation_count_; |
| 183 static intptr_t heap_allocated_memory_in_bytes_; | 163 static intptr_t heap_allocated_memory_in_bytes_; |
| 184 static AddressMap* address_map_; | 164 static AddressMap* address_map_; |
| 185 | 165 |
| 186 static const intptr_t kInvalidPid = -1; | 166 static const intptr_t kInvalidPid = -1; |
| 187 }; | 167 }; |
| 188 | 168 |
| 189 | 169 |
| 190 // MallocHookScope state. | |
| 191 Mutex* MallocHookScope::malloc_hook_scope_mutex_ = new Mutex(); | |
| 192 ThreadLocalKey MallocHookScope::in_malloc_hook_flag_ = kUnsetThreadLocalKey; | |
| 193 | |
| 194 // MallocHooks state / locks. | 170 // MallocHooks state / locks. |
| 195 bool MallocHooksState::active_ = false; | 171 bool MallocHooksState::active_ = false; |
| 196 intptr_t MallocHooksState::original_pid_ = MallocHooksState::kInvalidPid; | 172 intptr_t MallocHooksState::original_pid_ = MallocHooksState::kInvalidPid; |
| 197 Mutex* MallocHooksState::malloc_hook_mutex_ = new Mutex(); | 173 Mutex* MallocHooksState::malloc_hook_mutex_ = new Mutex(); |
| 174 ThreadId MallocHooksState::malloc_hook_mutex_owner_ = |
| 175 OSThread::kInvalidThreadId; |
| 198 | 176 |
| 199 // Memory allocation state information. | 177 // Memory allocation state information. |
| 200 intptr_t MallocHooksState::allocation_count_ = 0; | 178 intptr_t MallocHooksState::allocation_count_ = 0; |
| 201 intptr_t MallocHooksState::heap_allocated_memory_in_bytes_ = 0; | 179 intptr_t MallocHooksState::heap_allocated_memory_in_bytes_ = 0; |
| 202 AddressMap* MallocHooksState::address_map_ = NULL; | 180 AddressMap* MallocHooksState::address_map_ = NULL; |
| 203 | 181 |
| 204 | 182 |
| 205 void MallocHooks::InitOnce() { | 183 void MallocHooks::InitOnce() { |
| 206 if (!FLAG_enable_malloc_hooks) { | 184 if (!FLAG_enable_malloc_hooks) { |
| 207 return; | 185 return; |
| 208 } | 186 } |
| 209 MutexLocker ml(MallocHooksState::malloc_hook_mutex()); | 187 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 188 MallocHooksState::malloc_hook_mutex_owner()); |
| 210 ASSERT(!MallocHooksState::Active()); | 189 ASSERT(!MallocHooksState::Active()); |
| 211 | 190 |
| 212 MallocHookScope::InitMallocHookFlag(); | |
| 213 MallocHooksState::Init(); | 191 MallocHooksState::Init(); |
| 214 | 192 |
| 215 // Register malloc hooks. | 193 // Register malloc hooks. |
| 216 bool success = false; | 194 bool success = false; |
| 217 success = MallocHook::AddNewHook(&MallocHooksState::RecordAllocHook); | 195 success = MallocHook::AddNewHook(&MallocHooksState::RecordAllocHook); |
| 218 ASSERT(success); | 196 ASSERT(success); |
| 219 success = MallocHook::AddDeleteHook(&MallocHooksState::RecordFreeHook); | 197 success = MallocHook::AddDeleteHook(&MallocHooksState::RecordFreeHook); |
| 220 ASSERT(success); | 198 ASSERT(success); |
| 221 } | 199 } |
| 222 | 200 |
| 223 | 201 |
| 224 void MallocHooks::TearDown() { | 202 void MallocHooks::TearDown() { |
| 225 if (!FLAG_enable_malloc_hooks) { | 203 if (!FLAG_enable_malloc_hooks) { |
| 226 return; | 204 return; |
| 227 } | 205 } |
| 228 MutexLocker ml(MallocHooksState::malloc_hook_mutex()); | 206 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 207 MallocHooksState::malloc_hook_mutex_owner()); |
| 229 ASSERT(MallocHooksState::Active()); | 208 ASSERT(MallocHooksState::Active()); |
| 230 | 209 |
| 231 // Remove malloc hooks. | 210 // Remove malloc hooks. |
| 232 bool success = false; | 211 bool success = false; |
| 233 success = MallocHook::RemoveNewHook(&MallocHooksState::RecordAllocHook); | 212 success = MallocHook::RemoveNewHook(&MallocHooksState::RecordAllocHook); |
| 234 ASSERT(success); | 213 ASSERT(success); |
| 235 success = MallocHook::RemoveDeleteHook(&MallocHooksState::RecordFreeHook); | 214 success = MallocHook::RemoveDeleteHook(&MallocHooksState::RecordFreeHook); |
| 236 ASSERT(success); | 215 ASSERT(success); |
| 237 | 216 |
| 238 MallocHooksState::TearDown(); | 217 MallocHooksState::TearDown(); |
| 239 MallocHookScope::DestroyMallocHookFlag(); | |
| 240 } | 218 } |
| 241 | 219 |
| 242 | 220 |
| 243 void MallocHooks::ResetStats() { | 221 void MallocHooks::ResetStats() { |
| 244 if (!FLAG_enable_malloc_hooks) { | 222 if (!FLAG_enable_malloc_hooks) { |
| 245 return; | 223 return; |
| 246 } | 224 } |
| 247 MutexLocker ml(MallocHooksState::malloc_hook_mutex()); | 225 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 226 MallocHooksState::malloc_hook_mutex_owner()); |
| 248 if (MallocHooksState::Active()) { | 227 if (MallocHooksState::Active()) { |
| 249 MallocHooksState::ResetStats(); | 228 MallocHooksState::ResetStats(); |
| 250 } | 229 } |
| 251 } | 230 } |
| 252 | 231 |
| 253 | 232 |
| 254 bool MallocHooks::Active() { | 233 bool MallocHooks::Active() { |
| 255 if (!FLAG_enable_malloc_hooks) { | 234 if (!FLAG_enable_malloc_hooks) { |
| 256 return false; | 235 return false; |
| 257 } | 236 } |
| 258 ASSERT(MallocHooksState::malloc_hook_mutex()->IsOwnedByCurrentThread()); | 237 ASSERT(MallocHooksState::malloc_hook_mutex()->IsOwnedByCurrentThread()); |
| 259 return MallocHooksState::Active(); | 238 return MallocHooksState::Active(); |
| 260 } | 239 } |
| 261 | 240 |
| 262 | 241 |
| 263 void MallocHooks::PrintToJSONObject(JSONObject* jsobj) { | 242 void MallocHooks::PrintToJSONObject(JSONObject* jsobj) { |
| 264 if (!FLAG_enable_malloc_hooks) { | 243 if (!FLAG_enable_malloc_hooks) { |
| 265 return; | 244 return; |
| 266 } | 245 } |
| 267 intptr_t allocated_memory = 0; | 246 intptr_t allocated_memory = 0; |
| 268 intptr_t allocation_count = 0; | 247 intptr_t allocation_count = 0; |
| 269 bool add_usage = false; | 248 bool add_usage = false; |
| 270 // AddProperty may call malloc which would result in an attempt | 249 // AddProperty may call malloc which would result in an attempt |
| 271 // to acquire the lock recursively so we extract the values first | 250 // to acquire the lock recursively so we extract the values first |
| 272 // and then add the JSON properties. | 251 // and then add the JSON properties. |
| 273 { | 252 { |
| 274 MutexLocker ml(MallocHooksState::malloc_hook_mutex()); | 253 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 254 MallocHooksState::malloc_hook_mutex_owner()); |
| 275 if (Active()) { | 255 if (Active()) { |
| 276 allocated_memory = MallocHooksState::heap_allocated_memory_in_bytes(); | 256 allocated_memory = MallocHooksState::heap_allocated_memory_in_bytes(); |
| 277 allocation_count = MallocHooksState::allocation_count(); | 257 allocation_count = MallocHooksState::allocation_count(); |
| 278 add_usage = true; | 258 add_usage = true; |
| 279 } | 259 } |
| 280 } | 260 } |
| 281 if (add_usage) { | 261 if (add_usage) { |
| 282 jsobj->AddProperty("_heapAllocatedMemoryUsage", allocated_memory); | 262 jsobj->AddProperty("_heapAllocatedMemoryUsage", allocated_memory); |
| 283 jsobj->AddProperty("_heapAllocationCount", allocation_count); | 263 jsobj->AddProperty("_heapAllocationCount", allocation_count); |
| 284 } | 264 } |
| 285 } | 265 } |
| 286 | 266 |
| 287 | 267 |
| 288 intptr_t MallocHooks::allocation_count() { | 268 intptr_t MallocHooks::allocation_count() { |
| 289 if (!FLAG_enable_malloc_hooks) { | 269 if (!FLAG_enable_malloc_hooks) { |
| 290 return 0; | 270 return 0; |
| 291 } | 271 } |
| 292 MutexLocker ml(MallocHooksState::malloc_hook_mutex()); | 272 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 273 MallocHooksState::malloc_hook_mutex_owner()); |
| 293 return MallocHooksState::allocation_count(); | 274 return MallocHooksState::allocation_count(); |
| 294 } | 275 } |
| 295 | 276 |
| 296 | 277 |
| 297 intptr_t MallocHooks::heap_allocated_memory_in_bytes() { | 278 intptr_t MallocHooks::heap_allocated_memory_in_bytes() { |
| 298 if (!FLAG_enable_malloc_hooks) { | 279 if (!FLAG_enable_malloc_hooks) { |
| 299 return 0; | 280 return 0; |
| 300 } | 281 } |
| 301 MutexLocker ml(MallocHooksState::malloc_hook_mutex()); | 282 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 283 MallocHooksState::malloc_hook_mutex_owner()); |
| 302 return MallocHooksState::heap_allocated_memory_in_bytes(); | 284 return MallocHooksState::heap_allocated_memory_in_bytes(); |
| 303 } | 285 } |
| 304 | 286 |
| 305 | 287 |
| 306 void MallocHooksState::RecordAllocHook(const void* ptr, size_t size) { | 288 void MallocHooksState::RecordAllocHook(const void* ptr, size_t size) { |
| 307 if (MallocHookScope::IsInHook() || !MallocHooksState::IsOriginalProcess()) { | 289 if (MallocHooksState::IsLockHeldByCurrentThread() || |
| 290 !MallocHooksState::IsOriginalProcess()) { |
| 308 return; | 291 return; |
| 309 } | 292 } |
| 310 | 293 |
| 311 MutexLocker ml(MallocHooksState::malloc_hook_mutex()); | 294 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 295 MallocHooksState::malloc_hook_mutex_owner()); |
| 312 // Now that we hold the lock, check to make sure everything is still active. | 296 // Now that we hold the lock, check to make sure everything is still active. |
| 313 if ((ptr != NULL) && MallocHooksState::Active()) { | 297 if ((ptr != NULL) && MallocHooksState::Active()) { |
| 314 // Set the malloc hook flag to avoid calling hooks again if memory is | |
| 315 // allocated/freed below. | |
| 316 MallocHookScope mhs; | |
| 317 MallocHooksState::IncrementHeapAllocatedMemoryInBytes(size); | 298 MallocHooksState::IncrementHeapAllocatedMemoryInBytes(size); |
| 318 MallocHooksState::address_map()->Insert(ptr, size); | 299 MallocHooksState::address_map()->Insert(ptr, size); |
| 319 } | 300 } |
| 320 } | 301 } |
| 321 | 302 |
| 322 | 303 |
| 323 void MallocHooksState::RecordFreeHook(const void* ptr) { | 304 void MallocHooksState::RecordFreeHook(const void* ptr) { |
| 324 if (MallocHookScope::IsInHook() || !MallocHooksState::IsOriginalProcess()) { | 305 if (MallocHooksState::IsLockHeldByCurrentThread() || |
| 306 !MallocHooksState::IsOriginalProcess()) { |
| 325 return; | 307 return; |
| 326 } | 308 } |
| 327 | 309 |
| 328 MutexLocker ml(MallocHooksState::malloc_hook_mutex()); | 310 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 311 MallocHooksState::malloc_hook_mutex_owner()); |
| 329 // Now that we hold the lock, check to make sure everything is still active. | 312 // Now that we hold the lock, check to make sure everything is still active. |
| 330 if ((ptr != NULL) && MallocHooksState::Active()) { | 313 if ((ptr != NULL) && MallocHooksState::Active()) { |
| 331 // Set the malloc hook flag to avoid calling hooks again if memory is | |
| 332 // allocated/freed below. | |
| 333 MallocHookScope mhs; | |
| 334 intptr_t size = 0; | 314 intptr_t size = 0; |
| 335 if (MallocHooksState::address_map()->Lookup(ptr, &size)) { | 315 if (MallocHooksState::address_map()->Lookup(ptr, &size)) { |
| 336 MallocHooksState::DecrementHeapAllocatedMemoryInBytes(size); | 316 MallocHooksState::DecrementHeapAllocatedMemoryInBytes(size); |
| 337 MallocHooksState::address_map()->Remove(ptr); | 317 MallocHooksState::address_map()->Remove(ptr); |
| 338 } | 318 } |
| 339 } | 319 } |
| 340 } | 320 } |
| 341 | 321 |
| 342 } // namespace dart | 322 } // namespace dart |
| 343 | 323 |
| 344 #endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT) | 324 #endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT) |
| OLD | NEW |