2 +----------------------------------------------------------------------+
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"
23 * Breakdown of allocated memory by object types.
26 * We traverse all instances of ObjectData* to measure their memory.
29 * Call objprof_get_data to trigger the scan.
32 #include <unordered_set>
33 #include <unordered_map>
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"
50 size_t asio_object_size(const ObjectData
*);
54 TRACE_SET_MOD(objprof
);
56 ///////////////////////////////////////////////////////////////////////////////
59 s_cpp_stack("cpp_stack"),
60 s_cpp_stack_peak("cpp_stack_peak"),
67 s_bytes_rel("bytes_normalized"),
68 s_instances("instances");
70 struct ObjprofObjectReferral
{
72 std::unordered_set
<const ObjectData
*> sources
;
74 struct ObjprofClassReferral
{
76 std::unordered_set
<Class
*> sources
;
79 struct ObjprofMetrics
{
80 uint64_t instances
{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
{
96 using ObjprofStrings
= std::unordered_map
<
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*>;
115 std::pair
<int, double> tvGetSize(
118 const ObjectData
* source
,
120 PathsToObject
* paths
,
121 ObjprofValuePtrStack
* val_stack
,
122 const std::unordered_set
<std::string
>& exclude_classes
,
127 ObjprofStrings
* metrics
,
129 std::unordered_set
<void*>* pointers
);
131 std::pair
<int, double> getObjSize(
132 const ObjectData
* obj
,
133 const ObjectData
* source
,
135 PathsToObject
* paths
,
136 ObjprofValuePtrStack
* val_stack
,
137 const std::unordered_set
<std::string
>& exclude_classes
,
138 std::unordered_map
<ClassProp
, ObjprofMetrics
>* histogram
,
142 String
pathString(ObjprofStack
* stack
, const char* sep
) {
143 assert(stack
->size() < 100000000);
145 for (size_t i
= 0; i
< stack
->size(); ++i
) {
146 if (i
!= 0) sb
.append(sep
);
147 sb
.append((*stack
)[i
]);
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)
158 const ObjectData
* obj
,
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;
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(
184 const ObjectData
* source
,
186 PathsToObject
* paths
,
187 ObjprofValuePtrStack
* val_stack
,
188 const std::unordered_set
<std::string
>& exclude_classes
,
190 std::unordered_map
<ClassProp
, ObjprofMetrics
>* histogram
,
193 auto arrKind
= ad
->kind();
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
);
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(
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
);
241 if (stack
) stack
->pop_back();
243 FTRACE(2, "Iterating mixed array\n");
244 IterateKV(ad
, [&] (Cell k
, TypedValue v
) {
246 std::pair
<int, double> key_size_pair
;
248 case KindOfPersistentString
:
250 auto const str
= k
.m_data
.pstr
;
253 std::string("ArrayKeyString:" + str
->toCppString()));
255 key_size_pair
= tvGetSize(
265 FTRACE(2, " Iterating str-key {} with size {}:{}\n",
266 str
->data(), key_size_pair
.first
, key_size_pair
.second
);
270 int64_t num
= k
.m_data
.num
;
272 stack
->push_back(std::string("ArrayKeyInt:" + std::to_string(num
)));
274 key_size_pair
= tvGetSize(
284 FTRACE(2, " Iterating num-key {} with size {}:{}\n",
285 num
, key_size_pair
.first
, key_size_pair
.second
);
290 case KindOfPersistentVec
:
292 case KindOfPersistentDict
:
294 case KindOfPersistentArray
:
295 case KindOfPersistentKeyset
:
303 always_assert(false);
306 auto val_size_pair
= tvGetSize(
316 FTRACE(2, " Value size for that key was {}:{}\n",
317 val_size_pair
.first
, val_size_pair
.second
);
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();
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
);
344 ObjprofStrings
* metrics
,
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
);
358 IterateKV(ad
, [&] (Cell k
, TypedValue v
) {
360 case KindOfPersistentString
:
362 auto const str
= k
.m_data
.pstr
;
363 tvGetStrings(k
, metrics
, path
, pointers
);
364 path
->push_back(std::string("[\"" + str
->toCppString() + "\"]"));
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
);
373 path
->push_back(std::string("[" + key_str
+ "]"));
378 case KindOfPersistentVec
:
380 case KindOfPersistentDict
:
382 case KindOfPersistentArray
:
383 case KindOfPersistentKeyset
:
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
398 tvGetStrings(v
, metrics
, path
, pointers
);
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(
420 const ObjectData
* source
,
422 PathsToObject
* paths
,
423 ObjprofValuePtrStack
* val_stack
,
424 const std::unordered_set
<std::string
>& exclude_classes
,
427 int size
= sizeof(tv
);
436 // Counted as part sizeof(TypedValue)
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(
450 nullptr, /* histogram */
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",
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
, "->");
473 auto& pathsToTv
= (*paths
)[obj
];
474 auto& referral
= pathsToTv
[pathStr
.toCppString()];
476 referral
.sources
.insert(source
);
480 FTRACE(3, " ObjectData tv: at {} of type {} at path {}, refs {}\n",
482 obj
->getClassName().data(),
489 case KindOfPersistentVec
:
491 case KindOfPersistentDict
:
493 case KindOfPersistentKeyset
:
495 case KindOfPersistentArray
:
497 ArrayData
* arr
= tv
.m_data
.parr
;
498 auto size_of_array_pair
= sizeOfArray(
506 nullptr, /* histogram */
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",
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
);
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
;
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
);
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",
555 Cell
* cell
= ref
->tv();
556 auto size_of_tv_pair
= tvGetSize(
566 size
+= size_of_tv_pair
.first
;
568 if (ref_ref_count
> 0) {
569 sized
+= size_of_tv_pair
.second
/ (double)(ref_ref_count
);
573 case KindOfPersistentString
:
575 StringData
* str
= tv
.m_data
.pstr
;
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",
585 if (str_ref_count
> 0) {
586 sized
+= (str
->size() / (double)(str_ref_count
));
589 // static or uncounted string
590 FTRACE(3, " String tv: {} string at {} uncounted, after adjust {}\n",
591 str
->data(), (void*)str
, ref_adjust
598 return std::make_pair(size
, sized
);
603 ObjprofStrings
* metrics
,
605 std::unordered_set
<void*>* pointers
608 case HPHP::KindOfUninit
:
609 case HPHP::KindOfResource
:
610 case HPHP::KindOfNull
:
611 case HPHP::KindOfBoolean
:
612 case HPHP::KindOfInt64
:
613 case HPHP::KindOfDouble
: {
617 case HPHP::KindOfObject
: {
618 // This is a shallow size function, not a recursive one
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
);
633 case HPHP::KindOfRef
: {
634 RefData
* ref
= tv
.m_data
.pref
;
635 Cell
* cell
= ref
->tv();
636 tvGetStrings(*cell
, metrics
, path
, pointers
);
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
651 if (pointers
->find(str
) == pointers
->end()) {
652 pointers
->insert(str
);
655 if (!str
->isRefCounted()) {
659 FTRACE(3, " String: {} = {} \n",
660 pathString(path
, ":").get()->data(),
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
))) {
677 } else if (UNLIKELY(obj
->instanceof(SystemLib::s_ArrayIteratorClass
))) {
679 } else if (UNLIKELY(obj
->instanceof(c_Closure::classof()))) {
681 } else if (UNLIKELY(obj
->instanceof(DateTimeData::getClass()))) {
684 if (LIKELY(!obj
->hasInstanceDtor())) {
692 std::pair
<int, double> getObjSize(
693 const ObjectData
* obj
,
694 const ObjectData
* source
,
696 PathsToObject
* paths
,
697 ObjprofValuePtrStack
* val_stack
,
698 const std::unordered_set
<std::string
>& exclude_classes
,
699 std::unordered_map
<ClassProp
, ObjprofMetrics
>* histogram
,
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(),
718 if (UNLIKELY(obj
->getAttribute(ObjectData::IsWaitHandle
))) {
719 size
= asio_object_size(obj
);
721 size
= sizeof(ObjectData
);
725 if (stack
) stack
->push_back(
726 std::string("Object:" + cls
->name()->toCppString())
729 if (obj
->isCollection()) {
730 auto const arr
= collections::asArray(obj
);
732 auto array_size_pair
= sizeOfArray(
743 size
+= array_size_pair
.first
;
744 sized
+= array_size_pair
.second
;
746 assertx(collections::isType(cls
, CollectionType::Pair
));
747 auto pair
= static_cast<const c_Pair
*>(obj
);
748 auto elm_size_pair
= tvGetSize(
758 size
+= elm_size_pair
.first
;
759 sized
+= elm_size_pair
.second
;
760 elm_size_pair
= tvGetSize(
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
;
807 double key_sized
= 0;
808 if (!is_declared
&& !is_packed
) {
809 FTRACE(2, "Counting string key {} because it's non-declared/packed\n",
811 auto key_size_pair
= tvGetSize(
821 key_size
= key_size_pair
.first
;
822 key_sized
= key_size_pair
.second
;
823 if (stack
) stack
->push_back(std::string("Key:"+key
));
825 FTRACE(2, "Skipping key {} because it's declared/packed\n", key
.c_str());
827 if (stack
) stack
->push_back(std::string("PropertyIndex"));
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(
845 FTRACE(2, " Summary for key {} with size key={}:{}, val={}:{}\n",
854 auto histogram_key
= std::make_pair(
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
);
879 const ObjectData
* obj
,
880 ObjprofStrings
* metrics
,
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());
894 stringsOfArray(arr
, metrics
, path
, pointers
);
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
);
905 if (!supportsToArray(obj
)) {
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",
936 tvGetStrings(key_tv
, metrics
, path
, pointers
);
937 path
->push_back(std::string("[\"" + key
+ "\"]"));
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
);
946 FTRACE(2, " Finished for key/val {}\n",
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
) {
963 getObjStrings(obj
, &metrics
, &path
, &pointers
);
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(
1008 nullptr, /* source */
1009 nullptr, /* stack */
1010 nullptr, /* paths */
1013 objprof_props_mode
? &histogram
: nullptr,
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",
1035 ArrayInit
objs(histogram
.size(), ArrayInit::Map
{});
1036 for (auto const& it
: histogram
) {
1039 auto prop
= c
.second
;
1040 auto key
= cls
->name()->toCppString();
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
, "")];
1078 PathsToObject pathsToObject
;
1079 std::vector
<const void*> val_stack
;
1080 auto objsizePair
= getObjSize(
1087 nullptr, /* histogram */
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",
1108 obj
->getClassName().data(),
1112 assert(stack
.size() == 0);
1115 NamedEntity::foreach_class([&](Class
* cls
) {
1116 if (cls
->needsInitSProps()) {
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) {
1129 FTRACE(2, "Traversing static prop {}::{}\n",
1130 cls
->name()->data(),
1131 StrNR(prop
.name
).data()
1135 PathsToObject pathsToObject
;
1137 auto refname
= std::string(
1139 cls
->name()->toCppString() + ":" +
1140 StrNR(prop
.name
).data()
1143 if (tv
->m_data
.num
== 0) {
1147 stack
.push_back(refname
);
1150 -1, /* ref_adjust */
1151 nullptr, /* source */
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);
1177 ArrayInit
objs(histogram
.size(), ArrayInit::Map
{});
1178 for (auto const& it
: histogram
) {
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;
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
--) {
1219 if (*cursor
== 0x00) {
1220 if (++consecutive
== s_pageSize
) {
1221 return get_thread_stack_size() + total
- consecutive
;
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
) {
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
)
1260 ///////////////////////////////////////////////////////////////////////////////
1262 void HHVM_FUNCTION(set_mem_threshold_callback
,
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
);
1292 } s_objprof_extension
;
1295 ///////////////////////////////////////////////////////////////////////////////