Fix use after free in ext_objprof
[hiphop-php.git] / hphp / runtime / ext / objprof / ext_objprof.cpp
blobba84433b6e4c3e5a818854e0b81e17685e5d41a0
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/extension.h"
21 * Project ObjProf
22 * What is it?
23 * Breakdown of allocated memory by object types.
25 * How does it work?
26 * We traverse all instances of ObjectData* to measure their memory.
28 * How do I use it?
29 * Call objprof_get_data to trigger the scan.
32 #include <unordered_set>
33 #include <unordered_map>
34 #include <vector>
35 #include <algorithm>
37 #include "hphp/runtime/base/array-init.h"
38 #include "hphp/runtime/base/collections.h"
39 #include "hphp/runtime/base/memory-manager-defs.h"
40 #include "hphp/runtime/base/tv-refcount.h"
41 #include "hphp/runtime/ext/datetime/ext_datetime.h"
42 #include "hphp/runtime/ext/simplexml/ext_simplexml.h"
43 #include "hphp/runtime/ext/std/ext_std_closure.h"
44 #include "hphp/runtime/vm/class.h"
45 #include "hphp/runtime/vm/unit.h"
46 #include "hphp/runtime/vm/named-entity-defs.h"
47 #include "hphp/util/alloc.h"
49 namespace HPHP {
50 size_t asio_object_size(const ObjectData*);
52 namespace {
54 TRACE_SET_MOD(objprof);
56 ///////////////////////////////////////////////////////////////////////////////
58 const StaticString
59 s_cpp_stack("cpp_stack"),
60 s_cpp_stack_peak("cpp_stack_peak"),
61 s_dups("dups"),
62 s_refs("refs"),
63 s_srefs("srefs"),
64 s_path("path"),
65 s_paths("paths"),
66 s_bytes("bytes"),
67 s_bytes_rel("bytes_normalized"),
68 s_instances("instances");
70 struct ObjprofObjectReferral {
71 uint64_t refs{0};
72 std::unordered_set<const ObjectData*> sources;
74 struct ObjprofClassReferral {
75 uint64_t refs{0};
76 std::unordered_set<Class*> sources;
79 struct ObjprofMetrics {
80 uint64_t instances{0};
81 uint64_t bytes{0};
82 double bytes_rel{0};
84 using PathsToObject = std::unordered_map<
85 ObjectData*, std::unordered_map<std::string, ObjprofObjectReferral>>;
86 using PathsToClass = std::unordered_map<
87 Class*, std::unordered_map<std::string, ObjprofClassReferral>>;
89 struct ObjprofStringAgg {
90 uint64_t dups{0};
91 uint64_t refs{0};
92 uint64_t srefs{0};
93 String path;
96 using ObjprofStrings = std::unordered_map<
97 String,
98 ObjprofStringAgg,
99 hphp_string_hash,
100 hphp_string_same
102 using ObjprofStack = std::vector<std::string>;
103 using ClassProp = std::pair<Class*, std::string>;
105 // Stack of pointers used for avoiding back-links when performing a DFS scan
106 // starting at a root node.
107 using ObjprofValuePtrStack = std::vector<const void*>;
109 enum ObjprofFlags {
110 DEFAULT = 1,
111 USER_TYPES_ONLY = 2,
112 PER_PROPERTY = 4
115 std::pair<int, double> tvGetSize(
116 TypedValue tv,
117 int ref_adjust,
118 const ObjectData* source,
119 ObjprofStack* stack,
120 PathsToObject* paths,
121 ObjprofValuePtrStack* val_stack,
122 const std::unordered_set<std::string>& exclude_classes,
123 ObjprofFlags flags
125 void tvGetStrings(
126 TypedValue tv,
127 ObjprofStrings* metrics,
128 ObjprofStack* path,
129 std::unordered_set<void*>* pointers);
131 std::pair<int, double> getObjSize(
132 const ObjectData* obj,
133 const ObjectData* source,
134 ObjprofStack* stack,
135 PathsToObject* paths,
136 ObjprofValuePtrStack* val_stack,
137 const std::unordered_set<std::string>& exclude_classes,
138 std::unordered_map<ClassProp, ObjprofMetrics>* histogram,
139 ObjprofFlags flags
142 String pathString(ObjprofStack* stack, const char* sep) {
143 assert(stack->size() < 100000000);
144 StringBuffer sb;
145 for (size_t i = 0; i < stack->size(); ++i) {
146 if (i != 0) sb.append(sep);
147 sb.append((*stack)[i]);
149 return sb.detach();
153 * Determines whether the given object is a "root" node. Class instances
154 * are considered root nodes, with the exception of classes starting with
155 * "HH\\" (collections, wait handles etc)
157 bool isObjprofRoot(
158 const ObjectData* obj,
159 ObjprofFlags flags,
160 const std::unordered_set<std::string>& exclude_classes
162 Class* cls = obj->getVMClass();
163 auto cls_name = cls->name()->toCppString();
164 // Classes in exclude_classes not considered root
165 if (exclude_classes.find(cls_name) != exclude_classes.end()) return false;
166 // In USER_TYPES_ONLY mode, Classes with "HH\\" prefix not considered root
167 if ((flags & ObjprofFlags::USER_TYPES_ONLY) != 0) {
168 if (cls_name.compare(0, 3, "HH\\") == 0) return false;
170 return true;
174 * Measures the size of the array and referenced objects without going
175 * into ObjectData* references.
177 * These are not measured:
178 * kApcKind // APCArray
179 * kGlobalsKind // GlobalsArray
180 * kProxyKind // ProxyArray
182 std::pair<int, double> sizeOfArray(
183 const ArrayData* ad,
184 const ObjectData* source,
185 ObjprofStack* stack,
186 PathsToObject* paths,
187 ObjprofValuePtrStack* val_stack,
188 const std::unordered_set<std::string>& exclude_classes,
189 Class* cls,
190 std::unordered_map<ClassProp, ObjprofMetrics>* histogram,
191 ObjprofFlags flags
193 auto arrKind = ad->kind();
194 if (
195 arrKind == ArrayData::ArrayKind::kApcKind ||
196 arrKind == ArrayData::ArrayKind::kGlobalsKind ||
197 arrKind == ArrayData::ArrayKind::kProxyKind
199 return std::make_pair(0, 0);
202 auto ptr_begin = val_stack->begin();
203 auto ptr_end = val_stack->end();
204 if (std::find(ptr_begin, ptr_end, ad) != ptr_end) {
205 FTRACE(3, "Cycle found for ArrayData*({})\n", ad);
206 return std::make_pair(0, 0);
208 FTRACE(3, "\n\nInserting ArrayData*({})\n", ad);
209 val_stack->push_back(ad);
211 int size = 0;
212 double sized = 0;
213 if (ad->hasPackedLayout()) {
214 FTRACE(2, "Iterating packed array\n");
215 if (stack) stack->push_back("ArrayIndex");
217 IterateV(ad, [&] (TypedValue v) {
218 auto val_size_pair = tvGetSize(
220 0, /* ref_adjust */
221 source,
222 stack,
223 paths,
224 val_stack,
225 exclude_classes,
226 flags
228 if (histogram) {
229 auto histogram_key = std::make_pair(cls, "<index>");
230 auto& metrics = (*histogram)[histogram_key];
231 metrics.instances += 1;
232 metrics.bytes += val_size_pair.first;
233 metrics.bytes_rel += val_size_pair.second;
235 size += val_size_pair.first;
236 sized += val_size_pair.second;
237 FTRACE(2, "Value size for item was {}\n", val_size_pair.first);
238 return false;
241 if (stack) stack->pop_back();
242 } else {
243 FTRACE(2, "Iterating mixed array\n");
244 IterateKV(ad, [&] (Cell k, TypedValue v) {
246 std::pair<int, double> key_size_pair;
247 switch (k.m_type) {
248 case KindOfPersistentString:
249 case KindOfString: {
250 auto const str = k.m_data.pstr;
251 if (stack) {
252 stack->push_back(
253 std::string("ArrayKeyString:" + str->toCppString()));
255 key_size_pair = tvGetSize(
257 0, /* ref_adjust */
258 source,
259 stack,
260 paths,
261 val_stack,
262 exclude_classes,
263 flags
265 FTRACE(2, " Iterating str-key {} with size {}:{}\n",
266 str->data(), key_size_pair.first, key_size_pair.second);
267 break;
269 case KindOfInt64: {
270 int64_t num = k.m_data.num;
271 if (stack) {
272 stack->push_back(std::string("ArrayKeyInt:" + std::to_string(num)));
274 key_size_pair = tvGetSize(
276 0, /* ref_adjust */
277 source,
278 stack,
279 paths,
280 val_stack,
281 exclude_classes,
282 flags
284 FTRACE(2, " Iterating num-key {} with size {}:{}\n",
285 num, key_size_pair.first, key_size_pair.second);
286 break;
288 case KindOfUninit:
289 case KindOfNull:
290 case KindOfPersistentVec:
291 case KindOfBoolean:
292 case KindOfPersistentDict:
293 case KindOfDouble:
294 case KindOfPersistentArray:
295 case KindOfPersistentKeyset:
296 case KindOfObject:
297 case KindOfResource:
298 case KindOfVec:
299 case KindOfDict:
300 case KindOfRef:
301 case KindOfArray:
302 case KindOfKeyset:
303 always_assert(false);
306 auto val_size_pair = tvGetSize(
308 0, /* ref_adjust */
309 source,
310 stack,
311 paths,
312 val_stack,
313 exclude_classes,
314 flags
316 FTRACE(2, " Value size for that key was {}:{}\n",
317 val_size_pair.first, val_size_pair.second);
318 if (histogram) {
319 auto const k_str = String::attach(tvCastToString(k));
320 auto const histogram_key = std::make_pair(cls, k_str.toCppString());
321 auto& metrics = (*histogram)[histogram_key];
322 metrics.instances += 1;
323 metrics.bytes += val_size_pair.first + key_size_pair.first;
324 metrics.bytes_rel += val_size_pair.second + key_size_pair.second;
326 size += val_size_pair.first + key_size_pair.first;
327 sized += val_size_pair.second + key_size_pair.second;
329 if (stack) stack->pop_back();
330 return false;
334 FTRACE(3, "Popping {} frm stack in sizeOfArray. Stack size before pop {}\n",
335 val_stack->back(), val_stack->size()
337 val_stack->pop_back();
339 return std::make_pair(size, sized);
342 void stringsOfArray(
343 const ArrayData* ad,
344 ObjprofStrings* metrics,
345 ObjprofStack* path,
346 std::unordered_set<void*>* pointers
348 path->push_back(std::string("array()"));
350 if (ad->hasPackedLayout()) {
351 path->push_back(std::string("[]"));
352 IterateV(ad, [&] (TypedValue v) {
353 tvGetStrings(v, metrics, path, pointers);
354 return false;
356 path->pop_back();
357 } else {
358 IterateKV(ad, [&] (Cell k, TypedValue v) {
359 switch (k.m_type) {
360 case KindOfPersistentString:
361 case KindOfString: {
362 auto const str = k.m_data.pstr;
363 tvGetStrings(k, metrics, path, pointers);
364 path->push_back(std::string("[\"" + str->toCppString() + "\"]"));
365 break;
367 case KindOfInt64: {
368 auto const num = k.m_data.num;
369 auto key_str = std::to_string(num);
370 path->push_back(std::string(key_str));
371 tvGetStrings(k, metrics, path, pointers);
372 path->pop_back();
373 path->push_back(std::string("[" + key_str + "]"));
374 break;
376 case KindOfUninit:
377 case KindOfNull:
378 case KindOfPersistentVec:
379 case KindOfBoolean:
380 case KindOfPersistentDict:
381 case KindOfDouble:
382 case KindOfPersistentArray:
383 case KindOfPersistentKeyset:
384 case KindOfObject:
385 case KindOfResource:
386 case KindOfVec:
387 case KindOfDict:
388 case KindOfRef:
389 case KindOfArray:
390 case KindOfKeyset:
391 // this should be an always_assert(false), but that appears to trigger
392 // a gcc-4.9 bug (t16350411); even after t16350411 is fixed, we
393 // can't always_assert(false) here until we stop supporting gcc-4.9
394 // for open source users, since they may be using an older version
395 assert(false);
398 tvGetStrings(v, metrics, path, pointers);
399 path->pop_back();
400 return false;
404 path->pop_back();
408 * Measures the size of the typed value and referenced objects without going
409 * into ObjectData* references.
411 * These are not measured:
412 * kEmptyKind // The singleton static empty array
413 * kSharedKind // SharedArray
414 * kGlobalsKind // GlobalsArray
415 * kProxyKind // ProxyArray
417 std::pair<int, double> tvGetSize(
418 TypedValue tv,
419 int ref_adjust,
420 const ObjectData* source,
421 ObjprofStack* stack,
422 PathsToObject* paths,
423 ObjprofValuePtrStack* val_stack,
424 const std::unordered_set<std::string>& exclude_classes,
425 ObjprofFlags flags
427 int size = sizeof(tv);
428 double sized = size;
430 switch (tv.m_type) {
431 case KindOfUninit:
432 case KindOfNull:
433 case KindOfBoolean:
434 case KindOfInt64:
435 case KindOfDouble: {
436 // Counted as part sizeof(TypedValue)
437 break;
439 case KindOfObject: {
440 ObjectData* obj = tv.m_data.pobj;
441 // If its not a root node, recurse into the object to determine its size
442 if (!isObjprofRoot(obj, flags, exclude_classes)) {
443 auto obj_size_pair = getObjSize(
444 obj,
445 source,
446 stack,
447 paths,
448 val_stack,
449 exclude_classes,
450 nullptr, /* histogram */
451 flags
453 size += obj_size_pair.first;
454 if (obj->isRefCounted()) {
455 auto obj_ref_count = tvGetCount(tv) + ref_adjust;
456 FTRACE(3, " ObjectData tv: at {} with ref count {} after adjust {}\n",
457 (void*)obj,
458 obj_ref_count,
459 ref_adjust
461 if (obj_ref_count > 0) {
462 sized += obj_size_pair.second / (double)(obj_ref_count);
465 } else if (stack && paths) {
466 // notice we might have multiple OBJ->path->OBJ for same path
467 // (e.g. packed array where we omit the index number)
468 auto cls = obj->getVMClass();
469 stack->push_back(std::string("Object:" + cls->name()->toCppString()));
470 auto pathStr = pathString(stack, "->");
471 stack->pop_back();
473 auto& pathsToTv = (*paths)[obj];
474 auto& referral = pathsToTv[pathStr.toCppString()];
475 if (source) {
476 referral.sources.insert(source);
478 referral.refs += 1;
480 FTRACE(3, " ObjectData tv: at {} of type {} at path {}, refs {}\n",
481 (void*)obj,
482 obj->getClassName().data(),
483 pathStr.data(),
484 referral.refs
487 break;
489 case KindOfPersistentVec:
490 case KindOfVec:
491 case KindOfPersistentDict:
492 case KindOfDict:
493 case KindOfPersistentKeyset:
494 case KindOfKeyset:
495 case KindOfPersistentArray:
496 case KindOfArray: {
497 ArrayData* arr = tv.m_data.parr;
498 auto size_of_array_pair = sizeOfArray(
499 arr,
500 source,
501 stack,
502 paths,
503 val_stack,
504 exclude_classes,
505 nullptr, /* cls */
506 nullptr, /* histogram */
507 flags
509 if (arr->isRefCounted()) {
510 auto arr_ref_count = tvGetCount(tv) + ref_adjust;
511 FTRACE(3, " ArrayData tv: at {} with ref count {} after adjust {}\n",
512 (void*)arr,
513 arr_ref_count,
514 ref_adjust
516 size += sizeof(*arr);
517 size += size_of_array_pair.first;
518 if (arr_ref_count > 0) {
519 sized += sizeof(*arr) / (double)arr_ref_count;
520 sized += size_of_array_pair.second / (double)(arr_ref_count);
522 } else {
523 // static or uncounted array
524 FTRACE(3, " ArrayData tv: at {} not refcounted, after adjust {}\n",
525 (void*)arr, ref_adjust
527 size += sizeof(*arr);
528 size += size_of_array_pair.first;
531 break;
533 case KindOfResource: {
534 auto res_ref_count = tvGetCount(tv) + ref_adjust;
535 auto resource = tv.m_data.pres;
536 auto resource_size = resource->heapSize();
537 size += resource_size;
538 if (res_ref_count > 0) {
539 sized += resource_size / (double)(res_ref_count);
541 break;
543 case KindOfRef: {
544 RefData* ref = tv.m_data.pref;
545 size += sizeof(*ref);
546 sized += sizeof(*ref);
548 auto ref_ref_count = ref->getRealCount() + ref_adjust;
549 FTRACE(3, " RefData tv at {} that with ref count {} after adjust {}\n",
550 (void*)ref,
551 ref_ref_count,
552 ref_adjust
555 Cell* cell = ref->tv();
556 auto size_of_tv_pair = tvGetSize(
557 *cell,
558 0, /* ref_adjust */
559 source,
560 stack,
561 paths,
562 val_stack,
563 exclude_classes,
564 flags
566 size += size_of_tv_pair.first;
568 if (ref_ref_count > 0) {
569 sized += size_of_tv_pair.second / (double)(ref_ref_count);
571 break;
573 case KindOfPersistentString:
574 case KindOfString: {
575 StringData* str = tv.m_data.pstr;
576 size += str->size();
577 if (str->isRefCounted()) {
578 auto str_ref_count = tvGetCount(tv) + ref_adjust;
579 FTRACE(3, " String tv: {} string at {} ref count: {} after adjust {}\n",
580 str->data(),
581 (void*)str,
582 str_ref_count,
583 ref_adjust
585 if (str_ref_count > 0) {
586 sized += (str->size() / (double)(str_ref_count));
588 } else {
589 // static or uncounted string
590 FTRACE(3, " String tv: {} string at {} uncounted, after adjust {}\n",
591 str->data(), (void*)str, ref_adjust
594 break;
598 return std::make_pair(size, sized);
601 void tvGetStrings(
602 TypedValue tv,
603 ObjprofStrings* metrics,
604 ObjprofStack* path,
605 std::unordered_set<void*>* pointers
607 switch (tv.m_type) {
608 case HPHP::KindOfUninit:
609 case HPHP::KindOfResource:
610 case HPHP::KindOfNull:
611 case HPHP::KindOfBoolean:
612 case HPHP::KindOfInt64:
613 case HPHP::KindOfDouble: {
614 // Not strings
615 break;
617 case HPHP::KindOfObject: {
618 // This is a shallow size function, not a recursive one
619 break;
621 case HPHP::KindOfPersistentVec:
622 case HPHP::KindOfVec:
623 case HPHP::KindOfPersistentDict:
624 case HPHP::KindOfDict:
625 case HPHP::KindOfPersistentKeyset:
626 case HPHP::KindOfKeyset:
627 case HPHP::KindOfPersistentArray:
628 case HPHP::KindOfArray: {
629 ArrayData* arr = tv.m_data.parr;
630 stringsOfArray(arr, metrics, path, pointers);
631 break;
633 case HPHP::KindOfRef: {
634 RefData* ref = tv.m_data.pref;
635 Cell* cell = ref->tv();
636 tvGetStrings(*cell, metrics, path, pointers);
637 break;
639 case HPHP::KindOfPersistentString:
640 case HPHP::KindOfString: {
641 StringData* str = tv.m_data.pstr;
643 // Obtain aggregation object
644 auto &str_agg = (*metrics)[StrNR(str)];
645 if (!str_agg.path.get()) {
646 str_agg.path = pathString(path, ":");
649 // Increment aggregation metrics
650 str_agg.refs++;
651 if (pointers->find(str) == pointers->end()) {
652 pointers->insert(str);
653 str_agg.dups++;
655 if (!str->isRefCounted()) {
656 str_agg.srefs++;
659 FTRACE(3, " String: {} = {} \n",
660 pathString(path, ":").get()->data(),
661 str->data()
663 break;
668 bool supportsToArray(const ObjectData* obj) {
669 if (obj->isCollection()) {
670 // we never want to toArray on a collection; if we're asking if we can,
671 // then something has gone horribly wrong
672 always_assert(false);
673 } else if (UNLIKELY(obj->getAttribute(ObjectData::CallToImpl))) {
674 return obj->instanceof(SimpleXMLElement_classof());
675 } else if (UNLIKELY(obj->instanceof(SystemLib::s_ArrayObjectClass))) {
676 return true;
677 } else if (UNLIKELY(obj->instanceof(SystemLib::s_ArrayIteratorClass))) {
678 return true;
679 } else if (UNLIKELY(obj->instanceof(c_Closure::classof()))) {
680 return true;
681 } else if (UNLIKELY(obj->instanceof(DateTimeData::getClass()))) {
682 return true;
683 } else {
684 if (LIKELY(!obj->hasInstanceDtor())) {
685 return true;
688 return false;
692 std::pair<int, double> getObjSize(
693 const ObjectData* obj,
694 const ObjectData* source,
695 ObjprofStack* stack,
696 PathsToObject* paths,
697 ObjprofValuePtrStack* val_stack,
698 const std::unordered_set<std::string>& exclude_classes,
699 std::unordered_map<ClassProp, ObjprofMetrics>* histogram,
700 ObjprofFlags flags
702 Class* cls = obj->getVMClass();
704 auto ptr_begin = val_stack->begin();
705 auto ptr_end = val_stack->end();
706 if (std::find(ptr_begin, ptr_end, obj) != ptr_end) {
707 FTRACE(3, "Cycle found for {}*({})\n", obj->getClassName().data(), obj);
708 return std::make_pair(0, 0);
710 FTRACE(3, "\n\nInserting {}*({})\n", obj->getClassName().data(), obj);
711 val_stack->push_back(obj);
713 FTRACE(1, "Getting object size for type {} at {}\n",
714 obj->getClassName().data(),
717 int size;
718 if (UNLIKELY(obj->getAttribute(ObjectData::IsWaitHandle))) {
719 size = asio_object_size(obj);
720 } else {
721 size = sizeof(ObjectData);
723 double sized = size;
725 if (stack) stack->push_back(
726 std::string("Object:" + cls->name()->toCppString())
729 if (obj->isCollection()) {
730 auto const arr = collections::asArray(obj);
731 if (arr) {
732 auto array_size_pair = sizeOfArray(
733 arr,
734 source,
735 stack,
736 paths,
737 val_stack,
738 exclude_classes,
739 cls,
740 histogram,
741 flags
743 size += array_size_pair.first;
744 sized += array_size_pair.second;
745 } else {
746 assertx(collections::isType(cls, CollectionType::Pair));
747 auto pair = static_cast<const c_Pair*>(obj);
748 auto elm_size_pair = tvGetSize(
749 *pair->get(0),
750 0, /* ref_adjust */
751 source,
752 stack,
753 paths,
754 val_stack,
755 exclude_classes,
756 flags
758 size += elm_size_pair.first;
759 sized += elm_size_pair.second;
760 elm_size_pair = tvGetSize(
761 *pair->get(1),
762 0, /* ref_adjust */
763 source,
764 stack,
765 paths,
766 val_stack,
767 exclude_classes,
768 flags
770 size += elm_size_pair.first;
771 sized += elm_size_pair.second;
774 if (stack) stack->pop_back();
775 return std::make_pair(size, sized);
778 if (!supportsToArray(obj)) {
779 if (stack) stack->pop_back();
780 return std::make_pair(size, sized);
783 // We're increasing ref count by calling toArray, need to adjust it later
784 auto arr = obj->toArray(); // TODO t12985984 avoid toArray.
785 bool is_packed = arr->hasPackedLayout();
787 for (ArrayIter iter(arr); iter; ++iter) {
788 auto const key_tv = *iter.first().asTypedValue();
789 auto const val_tv = iter.secondVal();
790 auto key = tvAsCVarRef(&key_tv).toString();
791 if (isStringType(key_tv.m_type)) {
792 // If the key begins with a NUL, it's a private or protected property.
793 // Read the class name from between the two NUL bytes.
795 // Note: Copied from object-data.cpp
796 if (!key.empty() && key[0] == '\0') {
797 int subLen = key.find('\0', 1) + 1;
798 key = key.substr(subLen);
799 FTRACE(3, "Resolved private prop name: {}\n", key.c_str());
803 bool is_declared = isStringType(key_tv.m_type) &&
804 cls->lookupDeclProp(key.get()) != kInvalidSlot;
806 int key_size = 0;
807 double key_sized = 0;
808 if (!is_declared && !is_packed) {
809 FTRACE(2, "Counting string key {} because it's non-declared/packed\n",
810 key.c_str());
811 auto key_size_pair = tvGetSize(
812 key_tv,
813 -1, /* ref_adjust */
814 source,
815 stack,
816 paths,
817 val_stack,
818 exclude_classes,
819 flags
821 key_size = key_size_pair.first;
822 key_sized = key_size_pair.second;
823 if (stack) stack->push_back(std::string("Key:"+key));
824 } else {
825 FTRACE(2, "Skipping key {} because it's declared/packed\n", key.c_str());
826 if (is_packed) {
827 if (stack) stack->push_back(std::string("PropertyIndex"));
828 } else {
829 if (stack) stack->push_back(std::string("Property:" + key));
833 FTRACE(2, "Counting value for key {}\n", key.c_str());
834 auto val_size_pair = tvGetSize(
835 val_tv,
836 -1, /* ref_adjust */
837 source,
838 stack,
839 paths,
840 val_stack,
841 exclude_classes,
842 flags
845 FTRACE(2, " Summary for key {} with size key={}:{}, val={}:{}\n",
846 key.c_str(),
847 key_size,
848 key_sized,
849 val_size_pair.first,
850 val_size_pair.second
853 if (histogram) {
854 auto histogram_key = std::make_pair(
855 cls,
856 is_packed ? "<index>" : std::string(key.c_str())
858 auto& metrics = (*histogram)[histogram_key];
859 metrics.instances += 1;
860 metrics.bytes += val_size_pair.first + key_size;
861 metrics.bytes_rel += val_size_pair.second + key_sized;
864 size += val_size_pair.first + key_size;
865 sized += val_size_pair.second + key_sized;
866 if (stack) stack->pop_back();
868 if (stack) stack->pop_back();
870 FTRACE(3, "Popping {} frm stack in getObjSize. Stack size before pop {}\n",
871 val_stack->back(), val_stack->size()
873 val_stack->pop_back();
875 return std::make_pair(size, sized);
878 void getObjStrings(
879 const ObjectData* obj,
880 ObjprofStrings* metrics,
881 ObjprofStack* path,
882 std::unordered_set<void*>* pointers
884 Class* cls = obj->getVMClass();
885 FTRACE(1, "Getting strings for type {} at {}\n",
886 obj->getClassName().data(),
890 if (obj->isCollection()) {
891 auto const arr = collections::asArray(obj);
892 path->push_back(obj->getClassName().data());
893 if (arr) {
894 stringsOfArray(arr, metrics, path, pointers);
895 } else {
896 assertx(collections::isType(cls, CollectionType::Pair));
897 auto pair = static_cast<const c_Pair*>(obj);
898 tvGetStrings(*pair->get(0), metrics, path, pointers);
899 tvGetStrings(*pair->get(1), metrics, path, pointers);
901 path->pop_back();
902 return;
905 if (!supportsToArray(obj)) {
906 return;
909 path->push_back(obj->getClassName().data());
910 auto arr = obj->toArray(); // TODO t12985984 avoid toArray.
911 bool is_packed = arr->hasPackedLayout();
913 for (ArrayIter iter(arr); iter; ++iter) {
914 auto first = iter.first();
915 auto key = first.toString();
916 auto key_tv = *first.asTypedValue();
917 auto val_tv = iter.secondVal();
919 if (isStringType(key_tv.m_type)) {
920 // If the key begins with a NUL, it's a private or protected property.
921 // Read the class name from between the two NUL bytes.
923 // Note: Copied from object-data.cpp
924 if (!key.empty() && key[0] == '\0') {
925 int subLen = key.find('\0', 1) + 1;
926 key = key.substr(subLen);
930 bool is_declared = isStringType(key_tv.m_type) &&
931 cls->lookupDeclProp(key.get()) != kInvalidSlot;
933 if (!is_declared && !is_packed) {
934 FTRACE(2, "Inspecting key {} because it's non-declared/packed\n",
935 key.c_str());
936 tvGetStrings(key_tv, metrics, path, pointers);
937 path->push_back(std::string("[\"" + key + "\"]"));
938 } else {
939 FTRACE(2, "Skipping key {} because it's declared/packed\n", key.c_str());
940 path->push_back(std::string(key));
943 FTRACE(2, "Inspecting value for key {}\n", key.c_str());
944 tvGetStrings(val_tv, metrics, path, pointers);
945 path->pop_back();
946 FTRACE(2, " Finished for key/val {}\n",
947 key.c_str()
950 path->pop_back();
954 ///////////////////////////////////////////////////////////////////////////////
955 // Function that traverses objects and counts metrics per strings
957 Array HHVM_FUNCTION(objprof_get_strings, int min_dup) {
958 ObjprofStrings metrics;
960 std::unordered_set<void*> pointers;
961 MM().forEachObject([&](const ObjectData* obj) {
962 ObjprofStack path;
963 getObjStrings(obj, &metrics, &path, &pointers);
966 // Create response
967 ArrayInit objs(metrics.size(), ArrayInit::Map{});
968 for (auto& it : metrics) {
969 if (it.second.dups < min_dup) continue;
971 auto metrics_val = make_map_array(
972 s_dups, Variant(it.second.dups),
973 s_refs, Variant(it.second.refs),
974 s_srefs, Variant(it.second.srefs),
975 s_path, Variant(it.second.path)
978 const Variant str = Variant(it.first);
979 objs.setValidKey(str, Variant(metrics_val));
982 return objs.toArray();
985 ///////////////////////////////////////////////////////////////////////////////
986 // Function that inits the scan of the memory and count of class pointers
988 Array HHVM_FUNCTION(objprof_get_data,
989 int flags = ObjprofFlags::DEFAULT,
990 const Array& exclude_list = Array()
992 std::unordered_map<ClassProp, ObjprofMetrics> histogram;
993 auto objprof_props_mode = (flags & ObjprofFlags::PER_PROPERTY) != 0;
995 // Create a set of std::strings from the exclude_list provided. This de-dups
996 // the exclude_list, and also provides for fast lookup when determining
997 // whether a given class is in the exclude_list
998 std::unordered_set<std::string> exclude_classes;
999 for (ArrayIter iter(exclude_list); iter; ++iter) {
1000 exclude_classes.insert(iter.second().toString().data());
1003 MM().forEachObject([&](const ObjectData* obj) {
1004 if (!isObjprofRoot(obj, (ObjprofFlags)flags, exclude_classes)) return;
1005 std::vector<const void*> val_stack;
1006 auto objsizePair = getObjSize(
1007 obj,
1008 nullptr, /* source */
1009 nullptr, /* stack */
1010 nullptr, /* paths */
1011 &val_stack,
1012 exclude_classes,
1013 objprof_props_mode ? &histogram : nullptr,
1014 (ObjprofFlags)flags
1017 if (!objprof_props_mode) {
1018 auto cls = obj->getVMClass();
1019 auto cls_name = cls->name()->toCppString();
1020 auto& metrics = histogram[std::make_pair(cls, "")];
1021 metrics.instances += 1;
1022 metrics.bytes += objsizePair.first;
1023 metrics.bytes_rel += objsizePair.second;
1025 FTRACE(1, "ObjectData* at {} ({}) size={}:{}\n",
1026 obj,
1027 cls_name,
1028 objsizePair.first,
1029 objsizePair.second
1034 // Create response
1035 ArrayInit objs(histogram.size(), ArrayInit::Map{});
1036 for (auto const& it : histogram) {
1037 auto c = it.first;
1038 auto cls = c.first;
1039 auto prop = c.second;
1040 auto key = cls->name()->toCppString();
1041 if (prop != "") {
1042 key += "::" + c.second;
1045 auto metrics_val = make_map_array(
1046 s_instances, Variant(it.second.instances),
1047 s_bytes, Variant(it.second.bytes),
1048 s_bytes_rel, it.second.bytes_rel,
1049 s_paths, init_null()
1052 objs.set(StrNR(key), Variant(metrics_val));
1055 return objs.toArray();
1058 Array HHVM_FUNCTION(objprof_get_paths,
1059 int flags = ObjprofFlags::DEFAULT,
1060 const Array& exclude_list = Array()
1062 std::unordered_map<ClassProp, ObjprofMetrics> histogram;
1063 PathsToClass pathsToClass;
1065 // Create a set of std::strings from the exclude_list provided. This de-dups
1066 // the exclude_list, and also provides for fast lookup when determining
1067 // whether a given class is in the exclude_list
1068 std::unordered_set<std::string> exclude_classes;
1069 for (ArrayIter iter(exclude_list); iter; ++iter) {
1070 exclude_classes.insert(iter.second().toString().data());
1073 MM().forEachObject([&](const ObjectData* obj) {
1074 if (!isObjprofRoot(obj, (ObjprofFlags)flags, exclude_classes)) return;
1075 auto cls = obj->getVMClass();
1076 auto& metrics = histogram[std::make_pair(cls, "")];
1077 ObjprofStack stack;
1078 PathsToObject pathsToObject;
1079 std::vector<const void*> val_stack;
1080 auto objsizePair = getObjSize(
1081 obj, /* obj */
1082 obj, /* source */
1083 &stack,
1084 &pathsToObject,
1085 &val_stack,
1086 exclude_classes,
1087 nullptr, /* histogram */
1088 (ObjprofFlags)flags
1090 metrics.instances += 1;
1091 metrics.bytes += objsizePair.first;
1092 metrics.bytes_rel += objsizePair.second;
1093 for (auto const& pathsIt : pathsToObject) {
1094 auto cls = pathsIt.first->getVMClass();
1095 auto& paths = pathsIt.second;
1096 auto& aggPaths = pathsToClass[cls];
1097 for (auto const& pathKV : paths) {
1098 auto& path = pathKV.first;
1099 auto& referral = pathKV.second;
1100 auto& aggReferral = aggPaths[path];
1101 aggReferral.refs += referral.refs;
1102 aggReferral.sources.insert(obj->getVMClass());
1106 FTRACE(1, "ObjectData* at {} ({}) size={}:{}\n",
1107 obj,
1108 obj->getClassName().data(),
1109 objsizePair.first,
1110 objsizePair.second
1112 assert(stack.size() == 0);
1115 NamedEntity::foreach_class([&](Class* cls) {
1116 if (cls->needsInitSProps()) {
1117 return;
1119 std::vector<const void*> val_stack;
1120 auto const staticProps = cls->staticProperties();
1121 auto const nSProps = cls->numStaticProperties();
1122 for (Slot i = 0; i < nSProps; ++i) {
1123 auto const& prop = staticProps[i];
1124 auto tv = cls->getSPropData(i);
1125 if (tv == nullptr) {
1126 continue;
1129 FTRACE(2, "Traversing static prop {}::{}\n",
1130 cls->name()->data(),
1131 StrNR(prop.name).data()
1134 ObjprofStack stack;
1135 PathsToObject pathsToObject;
1137 auto refname = std::string(
1138 "ClassProperty:" +
1139 cls->name()->toCppString() + ":" +
1140 StrNR(prop.name).data()
1143 if (tv->m_data.num == 0) {
1144 continue;
1147 stack.push_back(refname);
1148 tvGetSize(
1149 *tv,
1150 -1, /* ref_adjust */
1151 nullptr, /* source */
1152 &stack,
1153 &pathsToObject,
1154 &val_stack,
1155 exclude_classes,
1156 (ObjprofFlags)flags
1158 stack.pop_back();
1160 for (auto const& pathsIt : pathsToObject) {
1161 auto cls = pathsIt.first->getVMClass();
1162 auto& paths = pathsIt.second;
1163 auto& aggPaths = pathsToClass[cls];
1164 for (auto const& pathKV : paths) {
1165 auto& path = pathKV.first;
1166 auto& referral = pathKV.second;
1167 auto& aggReferral = aggPaths[path];
1168 aggReferral.refs += referral.refs;
1169 aggReferral.sources.insert(cls);
1172 assert(stack.size() == 0);
1176 // Create response
1177 ArrayInit objs(histogram.size(), ArrayInit::Map{});
1178 for (auto const& it : histogram) {
1179 auto c = it.first;
1180 auto clsPaths = pathsToClass[c.first];
1181 ArrayInit pathsArr(clsPaths.size(), ArrayInit::Map{});
1182 for (auto const& pathIt : clsPaths) {
1183 auto pathStr = pathIt.first;
1184 auto path_metrics_val = make_map_array(
1185 s_refs, pathIt.second.refs
1188 pathsArr.setValidKey(Variant(pathStr), Variant(path_metrics_val));
1191 auto metrics_val = make_map_array(
1192 s_instances, Variant(it.second.instances),
1193 s_bytes, Variant(it.second.bytes),
1194 s_bytes_rel, it.second.bytes_rel,
1195 s_paths, Variant(pathsArr.toArray())
1198 objs.set(c.first->nameStr(), Variant(metrics_val));
1201 return objs.toArray();
1204 ///////////////////////////////////////////////////////////////////////////////
1206 size_t get_thread_stack_size() {
1207 auto sp = stack_top_ptr();
1208 return s_stackLimit + s_stackSize - uintptr_t(sp);
1211 size_t get_thread_stack_peak_size() {
1212 size_t consecutive = 0;
1213 size_t total = 0;
1214 uint8_t marker = 0x00;
1215 uint8_t* cursor = &marker;
1216 uintptr_t cursor_p = uintptr_t(&marker);
1217 for (;cursor_p > s_stackLimit; cursor_p--, cursor--) {
1218 total++;
1219 if (*cursor == 0x00) {
1220 if (++consecutive == s_pageSize) {
1221 return get_thread_stack_size() + total - consecutive;
1223 } else {
1224 consecutive = 0;
1228 return s_stackSize;
1231 void HHVM_FUNCTION(thread_mark_stack, void) {
1232 size_t consecutive = 0;
1233 uint8_t marker = 0x00;
1234 uint8_t* cursor = &marker;
1235 uintptr_t cursor_p = uintptr_t(&marker);
1236 for (;cursor_p > s_stackLimit; cursor_p--, cursor--) {
1237 if (*cursor == 0x00) {
1238 if (++consecutive == s_pageSize) {
1239 return;
1241 } else {
1242 consecutive = 0;
1243 *cursor = 0x00;
1248 Array HHVM_FUNCTION(thread_memory_stats, void) {
1249 auto stack_size = get_thread_stack_size();
1250 auto stack_size_peak = get_thread_stack_peak_size();
1252 auto stats = make_map_array(
1253 s_cpp_stack, Variant(stack_size),
1254 s_cpp_stack_peak, Variant(stack_size_peak)
1257 return stats;
1260 ///////////////////////////////////////////////////////////////////////////////
1262 void HHVM_FUNCTION(set_mem_threshold_callback,
1263 int64_t threshold,
1264 const Variant& callback
1266 // In a similar way to fb_setprofile storing in m_setprofileCallback
1267 g_context->m_memThresholdCallback = callback;
1269 // Notify MM that surprise flag should be set upon reaching the threshold
1270 MM().setMemThresholdCallback(threshold);
1273 ///////////////////////////////////////////////////////////////////////////////
1277 struct objprofExtension final : Extension {
1278 objprofExtension() : Extension("objprof", "1.0") { }
1280 void moduleInit() override {
1281 HHVM_FALIAS(HH\\objprof_get_data, objprof_get_data);
1282 HHVM_FALIAS(HH\\objprof_get_strings, objprof_get_strings);
1283 HHVM_FALIAS(HH\\objprof_get_paths, objprof_get_paths);
1284 HHVM_FALIAS(HH\\thread_memory_stats, thread_memory_stats);
1285 HHVM_FALIAS(HH\\thread_mark_stack, thread_mark_stack);
1286 HHVM_FALIAS(HH\\set_mem_threshold_callback, set_mem_threshold_callback);
1287 HHVM_RC_INT(OBJPROF_FLAGS_DEFAULT, ObjprofFlags::DEFAULT);
1288 HHVM_RC_INT(OBJPROF_FLAGS_USER_TYPES_ONLY, ObjprofFlags::USER_TYPES_ONLY);
1289 HHVM_RC_INT(OBJPROF_FLAGS_PER_PROPERTY, ObjprofFlags::PER_PROPERTY);
1290 loadSystemlib();
1292 } s_objprof_extension;
1295 ///////////////////////////////////////////////////////////////////////////////