Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: src/heap-profiler.cc

Issue 200132: Add initial version of retainers heap profile. (Closed)
Patch Set: Comments addressed Created 11 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/heap-profiler.h ('k') | src/log.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2009 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are
4 // met:
5 //
6 // * Redistributions of source code must retain the above copyright
7 // notice, this list of conditions and the following disclaimer.
8 // * Redistributions in binary form must reproduce the above
9 // copyright notice, this list of conditions and the following
10 // disclaimer in the documentation and/or other materials provided
11 // with the distribution.
12 // * Neither the name of Google Inc. nor the names of its
13 // contributors may be used to endorse or promote products derived
14 // from this software without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28 #include "v8.h"
29
30 #include "heap-profiler.h"
31 #include "string-stream.h"
32
33 namespace v8 {
34 namespace internal {
35
36
37 #ifdef ENABLE_LOGGING_AND_PROFILING
38 namespace {
39
40 // JSStatsHelper provides service functions for examining
41 // JS objects allocated on heap. It is run during garbage
42 // collection cycle, thus it doesn't need to use handles.
43 class JSStatsHelper {
44 public:
45 static int CalculateNetworkSize(JSObject* obj);
46 private:
47 DISALLOW_IMPLICIT_CONSTRUCTORS(JSStatsHelper);
48 };
49
50
51 int JSStatsHelper::CalculateNetworkSize(JSObject* obj) {
52 int size = obj->Size();
53 // If 'properties' and 'elements' are non-empty (thus, non-shared),
54 // take their size into account.
55 if (FixedArray::cast(obj->properties())->length() != 0) {
56 size += obj->properties()->Size();
57 }
58 if (FixedArray::cast(obj->elements())->length() != 0) {
59 size += obj->elements()->Size();
60 }
61 return size;
62 }
63
64
65 // A helper class for recording back references.
66 class ReferencesExtractor : public ObjectVisitor {
67 public:
68 ReferencesExtractor(
69 const JSObjectsCluster& cluster, RetainerHeapProfile* profile)
70 : cluster_(cluster),
71 profile_(profile),
72 insideArray_(false) {
73 }
74
75 void VisitPointer(Object** o) {
76 if ((*o)->IsJSObject() || (*o)->IsString()) {
77 profile_->StoreReference(cluster_, *o);
78 } else if ((*o)->IsFixedArray() && !insideArray_) {
79 // Traverse one level deep for data members that are fixed arrays.
80 // This covers the case of 'elements' and 'properties' of JSObject,
81 // and function contexts.
82 insideArray_ = true;
83 FixedArray::cast(*o)->Iterate(this);
84 insideArray_ = false;
85 }
86 }
87
88 void VisitPointers(Object** start, Object** end) {
89 for (Object** p = start; p < end; p++) VisitPointer(p);
90 }
91
92 private:
93 const JSObjectsCluster& cluster_;
94 RetainerHeapProfile* profile_;
95 bool insideArray_;
96 };
97
98
99 // A printer interface implementation for the Retainers profile.
100 class RetainersPrinter : public RetainerHeapProfile::Printer {
101 public:
102 void PrintRetainers(const StringStream& retainers) {
103 LOG(HeapSampleJSRetainersEvent(*(retainers.ToCString())));
104 }
105 };
106
107 } // namespace
108
109
110 const ConstructorHeapProfile::TreeConfig::Key
111 ConstructorHeapProfile::TreeConfig::kNoKey = NULL;
112 const ConstructorHeapProfile::TreeConfig::Value
113 ConstructorHeapProfile::TreeConfig::kNoValue;
114
115
116 ConstructorHeapProfile::ConstructorHeapProfile()
117 : zscope_(DELETE_ON_EXIT) {
118 }
119
120
121 void ConstructorHeapProfile::Call(String* name,
122 const NumberAndSizeInfo& number_and_size) {
123 ASSERT(name != NULL);
124 SmartPointer<char> s_name(
125 name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
126 LOG(HeapSampleJSConstructorEvent(*s_name,
127 number_and_size.number(),
128 number_and_size.bytes()));
129 }
130
131
132 void ConstructorHeapProfile::CollectStats(HeapObject* obj) {
133 String* constructor = NULL;
134 int size;
135 if (obj->IsString()) {
136 constructor = Heap::String_symbol();
137 size = obj->Size();
138 } else if (obj->IsJSObject()) {
139 JSObject* js_obj = JSObject::cast(obj);
140 constructor = js_obj->constructor_name();
141 size = JSStatsHelper::CalculateNetworkSize(js_obj);
142 } else {
143 return;
144 }
145
146 JSObjectsInfoTree::Locator loc;
147 if (!js_objects_info_tree_.Find(constructor, &loc)) {
148 js_objects_info_tree_.Insert(constructor, &loc);
149 }
150 NumberAndSizeInfo number_and_size = loc.value();
151 number_and_size.increment_number(1);
152 number_and_size.increment_bytes(size);
153 loc.set_value(number_and_size);
154 }
155
156
157 void ConstructorHeapProfile::PrintStats() {
158 js_objects_info_tree_.ForEach(this);
159 }
160
161
162 void JSObjectsCluster::Print(StringStream* accumulator) const {
163 ASSERT(!is_null());
164 if (constructor_ == FromSpecialCase(ROOTS)) {
165 accumulator->Add("(roots)");
166 } else if (constructor_ == FromSpecialCase(GLOBAL_PROPERTY)) {
167 accumulator->Add("(global property)");
168 } else {
169 SmartPointer<char> s_name(
170 constructor_->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
171 accumulator->Add("%s", (*s_name)[0] != '\0' ? *s_name : "(anonymous)");
172 if (instance_ != NULL) {
173 accumulator->Add(":%p", static_cast<void*>(instance_));
174 }
175 }
176 }
177
178
179 void JSObjectsCluster::DebugPrint(StringStream* accumulator) const {
180 if (!is_null()) {
181 Print(accumulator);
182 } else {
183 accumulator->Add("(null cluster)");
184 }
185 }
186
187
188 inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs(
189 const JSObjectsCluster& cluster_)
190 : cluster(cluster_), refs(INITIAL_BACKREFS_LIST_CAPACITY) {
191 }
192
193
194 inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs(
195 const ClustersCoarser::ClusterBackRefs::ClusterBackRefs& src)
196 : cluster(src.cluster), refs(src.refs.capacity()) {
197 refs.AddAll(src.refs);
198 }
199
200
201 inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs&
202 ClustersCoarser::ClusterBackRefs::operator=(
203 const ClustersCoarser::ClusterBackRefs::ClusterBackRefs& src) {
204 if (this == &src) return *this;
205 cluster = src.cluster;
206 refs.Clear();
207 refs.AddAll(src.refs);
208 return *this;
209 }
210
211
212 inline int ClustersCoarser::ClusterBackRefs::Compare(
213 const ClustersCoarser::ClusterBackRefs::ClusterBackRefs& a,
214 const ClustersCoarser::ClusterBackRefs::ClusterBackRefs& b) {
215 int cmp = JSObjectsCluster::CompareConstructors(a.cluster, b.cluster);
216 if (cmp != 0) return cmp;
217 if (a.refs.length() < b.refs.length()) return -1;
218 if (a.refs.length() > b.refs.length()) return 1;
219 for (int i = 0; i < a.refs.length(); ++i) {
220 int cmp = JSObjectsCluster::Compare(a.refs[i], b.refs[i]);
221 if (cmp != 0) return cmp;
222 }
223 return 0;
224 }
225
226
227 ClustersCoarser::ClustersCoarser()
228 : zscope_(DELETE_ON_EXIT),
229 simList_(ClustersCoarser::INITIAL_SIMILARITY_LIST_CAPACITY),
230 currentPair_(NULL) {
231 }
232
233
234 void ClustersCoarser::Call(
235 const JSObjectsCluster& cluster, JSObjectsClusterTree* tree) {
236 if (tree != NULL) {
237 // First level of retainer graph.
238 if (!cluster.can_be_coarsed()) return;
239 ClusterBackRefs pair(cluster);
240 ASSERT(currentPair_ == NULL);
241 currentPair_ = &pair;
242 currentSet_ = new JSObjectsClusterTree();
243 tree->ForEach(this);
244 simList_.Add(pair);
245 currentPair_ = NULL;
246 currentSet_ = NULL;
247 } else {
248 // Second level of retainer graph.
249 ASSERT(currentPair_ != NULL);
250 ASSERT(currentSet_ != NULL);
251 JSObjectsCluster eq = GetCoarseEquivalent(cluster);
252 JSObjectsClusterTree::Locator loc;
253 if (!eq.is_null()) {
254 if (currentSet_->Find(eq, &loc)) return;
255 currentPair_->refs.Add(eq);
256 currentSet_->Insert(eq, &loc);
257 } else {
258 currentPair_->refs.Add(cluster);
259 }
260 }
261 }
262
263
264 void ClustersCoarser::Process(JSObjectsClusterTree* tree) {
265 int last_eq_clusters = -1;
266 for (int i = 0; i < MAX_PASSES_COUNT; ++i) {
267 simList_.Clear();
268 const int curr_eq_clusters = DoProcess(tree);
269 // If no new cluster equivalents discovered, abort processing.
270 if (last_eq_clusters == curr_eq_clusters) break;
271 last_eq_clusters = curr_eq_clusters;
272 }
273 }
274
275
276 int ClustersCoarser::DoProcess(JSObjectsClusterTree* tree) {
277 tree->ForEach(this);
278 // To sort similarity list properly, references list of a cluster is
279 // required to be sorted, thus 'O1 <- A, B' and 'O2 <- B, A' would
280 // be considered equivalent. But we don't sort them explicitly
281 // because we know that they come from a splay tree traversal, so
282 // they are already sorted.
283 simList_.Sort(ClusterBackRefsCmp);
284 return FillEqualityTree();
285 }
286
287
288 JSObjectsCluster ClustersCoarser::GetCoarseEquivalent(
289 const JSObjectsCluster& cluster) {
290 if (!cluster.can_be_coarsed()) return JSObjectsCluster();
291 EqualityTree::Locator loc;
292 return eqTree_.Find(cluster, &loc) ? loc.value() : JSObjectsCluster();
293 }
294
295
296 bool ClustersCoarser::HasAnEquivalent(const JSObjectsCluster& cluster) {
297 // Return true for coarsible clusters that have a non-identical equivalent.
298 return cluster.can_be_coarsed() &&
299 JSObjectsCluster::Compare(cluster, GetCoarseEquivalent(cluster)) != 0;
300 }
301
302
303 int ClustersCoarser::FillEqualityTree() {
304 int eqClustersCount = 0;
305 int eqTo = 0;
306 bool firstAdded = false;
307 for (int i = 1; i < simList_.length(); ++i) {
308 if (ClusterBackRefs::Compare(simList_[i], simList_[eqTo]) == 0) {
309 EqualityTree::Locator loc;
310 if (!firstAdded) {
311 // Add self-equivalence, if we have more than one item in this
312 // equivalence class.
313 eqTree_.Insert(simList_[eqTo].cluster, &loc);
314 loc.set_value(simList_[eqTo].cluster);
315 firstAdded = true;
316 }
317 eqTree_.Insert(simList_[i].cluster, &loc);
318 loc.set_value(simList_[eqTo].cluster);
319 ++eqClustersCount;
320 } else {
321 eqTo = i;
322 firstAdded = false;
323 }
324 }
325 return eqClustersCount;
326 }
327
328
329 const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoKey;
330 const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoValue;
331 const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
332 const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue =
333 NULL;
334
335
336 RetainerHeapProfile::RetainerHeapProfile()
337 : zscope_(DELETE_ON_EXIT),
338 coarse_cluster_tree_(NULL),
339 retainers_printed_(0),
340 current_printer_(NULL),
341 current_stream_(NULL) {
342 ReferencesExtractor extractor(
343 JSObjectsCluster(JSObjectsCluster::ROOTS), this);
344 Heap::IterateRoots(&extractor);
345 }
346
347
348 JSObjectsCluster RetainerHeapProfile::Clusterize(Object* obj) {
349 if (obj->IsJSObject()) {
350 String* constructor = JSObject::cast(obj)->constructor_name();
351 // Differentiate Object and Array instances.
352 if (constructor == Heap::Object_symbol() ||
353 constructor == Heap::Array_symbol()) {
354 return JSObjectsCluster(constructor, obj);
355 } else {
356 return JSObjectsCluster(constructor);
357 }
358 } else if (obj->IsString()) {
359 return JSObjectsCluster(Heap::String_symbol());
360 } else {
361 UNREACHABLE();
362 return JSObjectsCluster();
363 }
364 }
365
366
367 void RetainerHeapProfile::StoreReference(
368 const JSObjectsCluster& cluster,
369 Object* ref) {
370 JSObjectsCluster ref_cluster = Clusterize(ref);
371 JSObjectsClusterTree::Locator ref_loc;
372 if (retainers_tree_.Insert(ref_cluster, &ref_loc)) {
373 ref_loc.set_value(new JSObjectsClusterTree());
374 }
375 JSObjectsClusterTree* referencedBy = ref_loc.value();
376 JSObjectsClusterTree::Locator obj_loc;
377 referencedBy->Insert(cluster, &obj_loc);
378 }
379
380
381 void RetainerHeapProfile::CollectStats(HeapObject* obj) {
382 if (obj->IsJSObject()) {
383 const JSObjectsCluster cluster = Clusterize(JSObject::cast(obj));
384 ReferencesExtractor extractor(cluster, this);
385 obj->Iterate(&extractor);
386 } else if (obj->IsJSGlobalPropertyCell()) {
387 ReferencesExtractor extractor(
388 JSObjectsCluster(JSObjectsCluster::GLOBAL_PROPERTY), this);
389 obj->Iterate(&extractor);
390 }
391 }
392
393
394 void RetainerHeapProfile::DebugPrintStats(
395 RetainerHeapProfile::Printer* printer) {
396 coarser_.Process(&retainers_tree_);
397 ASSERT(current_printer_ == NULL);
398 current_printer_ = printer;
399 retainers_tree_.ForEach(this);
400 current_printer_ = NULL;
401 }
402
403
404 void RetainerHeapProfile::PrintStats() {
405 RetainersPrinter printer;
406 DebugPrintStats(&printer);
407 }
408
409
410 void RetainerHeapProfile::Call(
411 const JSObjectsCluster& cluster,
412 JSObjectsClusterTree* tree) {
413 ASSERT(current_printer_ != NULL);
414 if (tree != NULL) {
415 // First level of retainer graph.
416 if (coarser_.HasAnEquivalent(cluster)) return;
417 ASSERT(current_stream_ == NULL);
418 HeapStringAllocator allocator;
419 StringStream stream(&allocator);
420 current_stream_ = &stream;
421 cluster.Print(current_stream_);
422 ASSERT(coarse_cluster_tree_ == NULL);
423 coarse_cluster_tree_ = new JSObjectsClusterTree();
424 retainers_printed_ = 0;
425 tree->ForEach(this);
426 coarse_cluster_tree_ = NULL;
427 current_printer_->PrintRetainers(stream);
428 current_stream_ = NULL;
429 } else {
430 // Second level of retainer graph.
431 ASSERT(coarse_cluster_tree_ != NULL);
432 ASSERT(current_stream_ != NULL);
433 if (retainers_printed_ >= MAX_RETAINERS_TO_PRINT) {
434 if (retainers_printed_ == MAX_RETAINERS_TO_PRINT) {
435 // TODO(mnaganov): Print the exact count.
436 current_stream_->Add(",...");
437 ++retainers_printed_; // avoid printing ellipsis next time.
438 }
439 return;
440 }
441 JSObjectsCluster eq = coarser_.GetCoarseEquivalent(cluster);
442 if (eq.is_null()) {
443 current_stream_->Put(',');
444 cluster.Print(current_stream_);
445 ++retainers_printed_;
446 } else {
447 JSObjectsClusterTree::Locator loc;
448 if (!coarse_cluster_tree_->Find(eq, &loc)) {
449 coarse_cluster_tree_->Insert(eq, &loc);
450 current_stream_->Put(',');
451 eq.Print(current_stream_);
452 ++retainers_printed_;
453 }
454 }
455 }
456 }
457
458
459 //
460 // HeapProfiler class implementation.
461 //
462 void HeapProfiler::CollectStats(HeapObject* obj, HistogramInfo* info) {
463 InstanceType type = obj->map()->instance_type();
464 ASSERT(0 <= type && type <= LAST_TYPE);
465 info[type].increment_number(1);
466 info[type].increment_bytes(obj->Size());
467 }
468
469
470 void HeapProfiler::WriteSample() {
471 LOG(HeapSampleBeginEvent("Heap", "allocated"));
472 LOG(HeapSampleStats(
473 "Heap", "allocated", Heap::Capacity(), Heap::SizeOfObjects()));
474
475 HistogramInfo info[LAST_TYPE+1];
476 #define DEF_TYPE_NAME(name) info[name].set_name(#name);
477 INSTANCE_TYPE_LIST(DEF_TYPE_NAME)
478 #undef DEF_TYPE_NAME
479
480 ConstructorHeapProfile js_cons_profile;
481 RetainerHeapProfile js_retainer_profile;
482 HeapIterator iterator;
483 while (iterator.has_next()) {
484 HeapObject* obj = iterator.next();
485 CollectStats(obj, info);
486 js_cons_profile.CollectStats(obj);
487 js_retainer_profile.CollectStats(obj);
488 }
489
490 // Lump all the string types together.
491 int string_number = 0;
492 int string_bytes = 0;
493 #define INCREMENT_SIZE(type, size, name, camel_name) \
494 string_number += info[type].number(); \
495 string_bytes += info[type].bytes();
496 STRING_TYPE_LIST(INCREMENT_SIZE)
497 #undef INCREMENT_SIZE
498 if (string_bytes > 0) {
499 LOG(HeapSampleItemEvent("STRING_TYPE", string_number, string_bytes));
500 }
501
502 for (int i = FIRST_NONSTRING_TYPE; i <= LAST_TYPE; ++i) {
503 if (info[i].bytes() > 0) {
504 LOG(HeapSampleItemEvent(info[i].name(), info[i].number(),
505 info[i].bytes()));
506 }
507 }
508
509 js_cons_profile.PrintStats();
510 js_retainer_profile.PrintStats();
511
512 LOG(HeapSampleEndEvent("Heap", "allocated"));
513 }
514
515
516 #endif // ENABLE_LOGGING_AND_PROFILING
517
518
519 } } // namespace v8::internal
OLDNEW
« no previous file with comments | « src/heap-profiler.h ('k') | src/log.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698