OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 #include "platform/globals.h" | |
6 | |
7 #if defined(DART_USE_TCMALLOC) && !defined(PRODUCT) && \ | |
8 !defined(TARGET_ARCH_DBC) && !defined(HOST_OS_FUCHSIA) | |
9 | |
10 #include "vm/malloc_hooks.h" | |
11 | |
12 #include "gperftools/malloc_hook.h" | |
13 | |
14 #include "platform/assert.h" | |
15 #include "vm/hash_map.h" | |
16 #include "vm/json_stream.h" | |
17 #include "vm/os_thread.h" | |
18 #include "vm/profiler.h" | |
19 | |
20 namespace dart { | |
21 | |
22 class AddressMap; | |
23 | |
24 // MallocHooksState contains all of the state related to the configuration of | |
25 // the malloc hooks, allocation information, and locks. | |
26 class MallocHooksState : public AllStatic { | |
27 public: | |
28 static void RecordAllocHook(const void* ptr, size_t size); | |
29 static void RecordFreeHook(const void* ptr); | |
30 | |
31 static bool Active() { | |
32 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); | |
33 return active_; | |
34 } | |
35 static void Init(); | |
36 | |
37 static bool ProfilingEnabled() { return (OSThread::TryCurrent() != NULL); } | |
38 | |
39 static bool stack_trace_collection_enabled() { | |
40 return stack_trace_collection_enabled_; | |
41 } | |
42 | |
43 static void set_stack_trace_collection_enabled(bool enabled) { | |
44 stack_trace_collection_enabled_ = enabled; | |
45 } | |
46 | |
47 static bool IsOriginalProcess() { | |
48 ASSERT(original_pid_ != kInvalidPid); | |
49 return original_pid_ == OS::ProcessId(); | |
50 } | |
51 | |
52 static Mutex* malloc_hook_mutex() { return malloc_hook_mutex_; } | |
53 static ThreadId* malloc_hook_mutex_owner() { | |
54 return &malloc_hook_mutex_owner_; | |
55 } | |
56 static bool IsLockHeldByCurrentThread() { | |
57 return (malloc_hook_mutex_owner_ == OSThread::GetCurrentThreadId()); | |
58 } | |
59 | |
60 static intptr_t allocation_count() { return allocation_count_; } | |
61 | |
62 static intptr_t heap_allocated_memory_in_bytes() { | |
63 return heap_allocated_memory_in_bytes_; | |
64 } | |
65 | |
66 static void IncrementHeapAllocatedMemoryInBytes(intptr_t size) { | |
67 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); | |
68 ASSERT(size >= 0); | |
69 heap_allocated_memory_in_bytes_ += size; | |
70 ++allocation_count_; | |
71 } | |
72 | |
73 static void DecrementHeapAllocatedMemoryInBytes(intptr_t size) { | |
74 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); | |
75 ASSERT(size >= 0); | |
76 ASSERT(heap_allocated_memory_in_bytes_ >= size); | |
77 heap_allocated_memory_in_bytes_ -= size; | |
78 --allocation_count_; | |
79 ASSERT(allocation_count_ >= 0); | |
80 } | |
81 | |
82 static AddressMap* address_map() { return address_map_; } | |
83 | |
84 static void ResetStats(); | |
85 static void TearDown(); | |
86 | |
87 private: | |
88 static Mutex* malloc_hook_mutex_; | |
89 static ThreadId malloc_hook_mutex_owner_; | |
90 | |
91 // Variables protected by malloc_hook_mutex_. | |
92 static bool active_; | |
93 static bool stack_trace_collection_enabled_; | |
94 static intptr_t allocation_count_; | |
95 static intptr_t heap_allocated_memory_in_bytes_; | |
96 static AddressMap* address_map_; | |
97 // End protected variables. | |
98 | |
99 static intptr_t original_pid_; | |
100 static const intptr_t kInvalidPid = -1; | |
101 }; | |
102 | |
103 // A locker-type class similar to MutexLocker which tracks which thread | |
104 // currently holds the lock. We use this instead of MutexLocker and | |
105 // mutex->IsOwnedByCurrentThread() since IsOwnedByCurrentThread() is only | |
106 // enabled for debug mode. | |
107 class MallocLocker : public ValueObject { | |
108 public: | |
109 explicit MallocLocker(Mutex* mutex, ThreadId* owner) | |
110 : mutex_(mutex), owner_(owner) { | |
111 ASSERT(owner != NULL); | |
112 mutex_->Lock(); | |
113 ASSERT(*owner_ == OSThread::kInvalidThreadId); | |
114 *owner_ = OSThread::GetCurrentThreadId(); | |
115 } | |
116 | |
117 virtual ~MallocLocker() { | |
118 ASSERT(*owner_ == OSThread::GetCurrentThreadId()); | |
119 *owner_ = OSThread::kInvalidThreadId; | |
120 mutex_->Unlock(); | |
121 } | |
122 | |
123 private: | |
124 Mutex* mutex_; | |
125 ThreadId* owner_; | |
126 }; | |
127 | |
128 // AllocationInfo contains all information related to a given allocation | |
129 // including: | |
130 // -Allocation size in bytes | |
131 // -Stack trace corresponding to the location of allocation, if applicable | |
132 class AllocationInfo { | |
133 public: | |
134 AllocationInfo(uword address, intptr_t allocation_size) | |
135 : sample_(NULL), address_(address), allocation_size_(allocation_size) { | |
136 // Stack trace collection is disabled when we are in the process of creating | |
137 // the first OSThread in order to prevent deadlocks. | |
138 if (MallocHooksState::ProfilingEnabled() && | |
139 MallocHooksState::stack_trace_collection_enabled()) { | |
140 sample_ = Profiler::SampleNativeAllocation(kSkipCount, address, | |
141 allocation_size); | |
142 ASSERT(sample_->native_allocation_address() == address_); | |
143 } | |
144 } | |
145 | |
146 Sample* sample() const { return sample_; } | |
147 intptr_t allocation_size() const { return allocation_size_; } | |
148 | |
149 private: | |
150 // Note: sample_ is not owned by AllocationInfo, but by the SampleBuffer | |
151 // created by the profiler. As such, this is only here to track if the sample | |
152 // is still associated with a native allocation, and its fields are never | |
153 // accessed from this class. | |
154 Sample* sample_; | |
155 uword address_; | |
156 intptr_t allocation_size_; | |
157 }; | |
158 | |
159 | |
160 // Custom key/value trait specifically for address/size pairs. Unlike | |
161 // RawPointerKeyValueTrait, the default value is -1 as 0 can be a valid entry. | |
162 class AddressKeyValueTrait : public AllStatic { | |
163 public: | |
164 typedef const void* Key; | |
165 typedef AllocationInfo* Value; | |
166 | |
167 struct Pair { | |
168 Key key; | |
169 Value value; | |
170 Pair() : key(NULL), value(NULL) {} | |
171 Pair(const Key key, const Value& value) : key(key), value(value) {} | |
172 Pair(const Pair& other) : key(other.key), value(other.value) {} | |
173 }; | |
174 | |
175 static Key KeyOf(Pair kv) { return kv.key; } | |
176 static Value ValueOf(Pair kv) { return kv.value; } | |
177 static intptr_t Hashcode(Key key) { return reinterpret_cast<intptr_t>(key); } | |
178 static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; } | |
179 }; | |
180 | |
181 | |
182 // Map class that will be used to store mappings between ptr -> allocation size. | |
183 class AddressMap : public MallocDirectChainedHashMap<AddressKeyValueTrait> { | |
184 public: | |
185 typedef AddressKeyValueTrait::Key Key; | |
186 typedef AddressKeyValueTrait::Value Value; | |
187 typedef AddressKeyValueTrait::Pair Pair; | |
188 | |
189 virtual ~AddressMap() { Clear(); } | |
190 | |
191 void Insert(const Key& key, const Value& value) { | |
192 Pair pair(key, value); | |
193 MallocDirectChainedHashMap<AddressKeyValueTrait>::Insert(pair); | |
194 } | |
195 | |
196 bool Lookup(const Key& key, Value* value) { | |
197 ASSERT(value != NULL); | |
198 Pair* pair = MallocDirectChainedHashMap<AddressKeyValueTrait>::Lookup(key); | |
199 if (pair == NULL) { | |
200 return false; | |
201 } else { | |
202 *value = pair->value; | |
203 return true; | |
204 } | |
205 } | |
206 | |
207 void Clear() { | |
208 Iterator iter = GetIterator(); | |
209 Pair* result = iter.Next(); | |
210 while (result != NULL) { | |
211 delete result->value; | |
212 result->value = NULL; | |
213 result = iter.Next(); | |
214 } | |
215 MallocDirectChainedHashMap<AddressKeyValueTrait>::Clear(); | |
216 } | |
217 }; | |
218 | |
219 | |
220 // MallocHooks state / locks. | |
221 bool MallocHooksState::active_ = false; | |
222 bool MallocHooksState::stack_trace_collection_enabled_ = false; | |
223 intptr_t MallocHooksState::original_pid_ = MallocHooksState::kInvalidPid; | |
224 Mutex* MallocHooksState::malloc_hook_mutex_ = new Mutex(); | |
225 ThreadId MallocHooksState::malloc_hook_mutex_owner_ = | |
226 OSThread::kInvalidThreadId; | |
227 | |
228 // Memory allocation state information. | |
229 intptr_t MallocHooksState::allocation_count_ = 0; | |
230 intptr_t MallocHooksState::heap_allocated_memory_in_bytes_ = 0; | |
231 AddressMap* MallocHooksState::address_map_ = NULL; | |
232 | |
233 | |
234 void MallocHooksState::Init() { | |
235 address_map_ = new AddressMap(); | |
236 active_ = true; | |
237 #if defined(DEBUG) | |
238 stack_trace_collection_enabled_ = true; | |
239 #else | |
240 stack_trace_collection_enabled_ = false; | |
241 #endif // defined(DEBUG) | |
242 original_pid_ = OS::ProcessId(); | |
243 } | |
244 | |
245 | |
246 void MallocHooksState::ResetStats() { | |
247 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); | |
248 allocation_count_ = 0; | |
249 heap_allocated_memory_in_bytes_ = 0; | |
250 address_map_->Clear(); | |
251 } | |
252 | |
253 | |
254 void MallocHooksState::TearDown() { | |
255 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); | |
256 active_ = false; | |
257 original_pid_ = kInvalidPid; | |
258 ResetStats(); | |
259 delete address_map_; | |
260 address_map_ = NULL; | |
261 } | |
262 | |
263 | |
264 void MallocHooks::InitOnce() { | |
265 if (!FLAG_enable_malloc_hooks || MallocHooks::Active()) { | |
266 return; | |
267 } | |
268 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
269 MallocHooksState::malloc_hook_mutex_owner()); | |
270 ASSERT(!MallocHooksState::Active()); | |
271 | |
272 MallocHooksState::Init(); | |
273 | |
274 // Register malloc hooks. | |
275 bool success = false; | |
276 success = MallocHook::AddNewHook(&MallocHooksState::RecordAllocHook); | |
277 ASSERT(success); | |
278 success = MallocHook::AddDeleteHook(&MallocHooksState::RecordFreeHook); | |
279 ASSERT(success); | |
280 } | |
281 | |
282 | |
283 void MallocHooks::TearDown() { | |
284 if (!FLAG_enable_malloc_hooks || !MallocHooks::Active()) { | |
285 return; | |
286 } | |
287 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
288 MallocHooksState::malloc_hook_mutex_owner()); | |
289 ASSERT(MallocHooksState::Active()); | |
290 | |
291 // Remove malloc hooks. | |
292 bool success = false; | |
293 success = MallocHook::RemoveNewHook(&MallocHooksState::RecordAllocHook); | |
294 ASSERT(success); | |
295 success = MallocHook::RemoveDeleteHook(&MallocHooksState::RecordFreeHook); | |
296 ASSERT(success); | |
297 | |
298 MallocHooksState::TearDown(); | |
299 } | |
300 | |
301 | |
302 bool MallocHooks::ProfilingEnabled() { | |
303 return MallocHooksState::ProfilingEnabled(); | |
304 } | |
305 | |
306 | |
307 bool MallocHooks::stack_trace_collection_enabled() { | |
308 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
309 MallocHooksState::malloc_hook_mutex_owner()); | |
310 return MallocHooksState::stack_trace_collection_enabled(); | |
311 } | |
312 | |
313 | |
314 void MallocHooks::set_stack_trace_collection_enabled(bool enabled) { | |
315 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
316 MallocHooksState::malloc_hook_mutex_owner()); | |
317 MallocHooksState::set_stack_trace_collection_enabled(enabled); | |
318 } | |
319 | |
320 | |
321 void MallocHooks::ResetStats() { | |
322 if (!FLAG_enable_malloc_hooks) { | |
323 return; | |
324 } | |
325 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
326 MallocHooksState::malloc_hook_mutex_owner()); | |
327 if (MallocHooksState::Active()) { | |
328 MallocHooksState::ResetStats(); | |
329 } | |
330 } | |
331 | |
332 | |
333 bool MallocHooks::Active() { | |
334 if (!FLAG_enable_malloc_hooks) { | |
335 return false; | |
336 } | |
337 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
338 MallocHooksState::malloc_hook_mutex_owner()); | |
339 | |
340 return MallocHooksState::Active(); | |
341 } | |
342 | |
343 | |
344 void MallocHooks::PrintToJSONObject(JSONObject* jsobj) { | |
345 if (!FLAG_enable_malloc_hooks) { | |
346 return; | |
347 } | |
348 intptr_t allocated_memory = 0; | |
349 intptr_t allocation_count = 0; | |
350 bool add_usage = false; | |
351 // AddProperty may call malloc which would result in an attempt | |
352 // to acquire the lock recursively so we extract the values first | |
353 // and then add the JSON properties. | |
354 { | |
355 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
356 MallocHooksState::malloc_hook_mutex_owner()); | |
357 if (MallocHooksState::Active()) { | |
358 allocated_memory = MallocHooksState::heap_allocated_memory_in_bytes(); | |
359 allocation_count = MallocHooksState::allocation_count(); | |
360 add_usage = true; | |
361 } | |
362 } | |
363 if (add_usage) { | |
364 jsobj->AddProperty("_heapAllocatedMemoryUsage", allocated_memory); | |
365 jsobj->AddProperty("_heapAllocationCount", allocation_count); | |
366 } | |
367 } | |
368 | |
369 | |
370 intptr_t MallocHooks::allocation_count() { | |
371 if (!FLAG_enable_malloc_hooks) { | |
372 return 0; | |
373 } | |
374 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
375 MallocHooksState::malloc_hook_mutex_owner()); | |
376 return MallocHooksState::allocation_count(); | |
377 } | |
378 | |
379 | |
380 intptr_t MallocHooks::heap_allocated_memory_in_bytes() { | |
381 if (!FLAG_enable_malloc_hooks) { | |
382 return 0; | |
383 } | |
384 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
385 MallocHooksState::malloc_hook_mutex_owner()); | |
386 return MallocHooksState::heap_allocated_memory_in_bytes(); | |
387 } | |
388 | |
389 | |
390 Sample* MallocHooks::GetSample(const void* ptr) { | |
391 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
392 MallocHooksState::malloc_hook_mutex_owner()); | |
393 | |
394 ASSERT(MallocHooksState::Active()); | |
395 | |
396 if (ptr != NULL) { | |
397 AllocationInfo* allocation_info = NULL; | |
398 if (MallocHooksState::address_map()->Lookup(ptr, &allocation_info)) { | |
399 ASSERT(allocation_info != NULL); | |
400 return allocation_info->sample(); | |
401 } | |
402 } | |
403 return NULL; | |
404 } | |
405 | |
406 | |
407 void MallocHooksState::RecordAllocHook(const void* ptr, size_t size) { | |
408 if (MallocHooksState::IsLockHeldByCurrentThread() || | |
409 !MallocHooksState::IsOriginalProcess()) { | |
410 return; | |
411 } | |
412 | |
413 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
414 MallocHooksState::malloc_hook_mutex_owner()); | |
415 // Now that we hold the lock, check to make sure everything is still active. | |
416 if ((ptr != NULL) && MallocHooksState::Active()) { | |
417 MallocHooksState::IncrementHeapAllocatedMemoryInBytes(size); | |
418 MallocHooksState::address_map()->Insert( | |
419 ptr, new AllocationInfo(reinterpret_cast<uword>(ptr), size)); | |
420 } | |
421 } | |
422 | |
423 | |
424 void MallocHooksState::RecordFreeHook(const void* ptr) { | |
425 if (MallocHooksState::IsLockHeldByCurrentThread() || | |
426 !MallocHooksState::IsOriginalProcess()) { | |
427 return; | |
428 } | |
429 | |
430 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | |
431 MallocHooksState::malloc_hook_mutex_owner()); | |
432 // Now that we hold the lock, check to make sure everything is still active. | |
433 if ((ptr != NULL) && MallocHooksState::Active()) { | |
434 AllocationInfo* allocation_info = NULL; | |
435 if (MallocHooksState::address_map()->Lookup(ptr, &allocation_info)) { | |
436 MallocHooksState::DecrementHeapAllocatedMemoryInBytes( | |
437 allocation_info->allocation_size()); | |
438 const bool result = MallocHooksState::address_map()->Remove(ptr); | |
439 ASSERT(result); | |
440 delete allocation_info; | |
441 } | |
442 } | |
443 } | |
444 | |
445 } // namespace dart | |
446 | |
447 #endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT) && | |
448 // !defined(TARGET_ARCH_DBC) && !defined(HOST_OS_FUCHSIA) | |
OLD | NEW |