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"
19 #include "hphp/runtime/base/builtin-functions.h"
24 * Set of methods to wrap around HHVM's heap graph implementation
27 * Create a heap graph in HHVM and uses it as a Resource with a
28 * set of functions that can operate on it.
31 * Call heapgraph_create, and then any of the other heapgraph
36 #include <unordered_map>
38 #include <boost/dynamic_bitset.hpp>
40 #include "hphp/runtime/base/array-init.h"
41 #include "hphp/runtime/base/collections.h"
42 #include "hphp/runtime/base/memory-manager-defs.h"
43 #include "hphp/runtime/ext/datetime/ext_datetime.h"
44 #include "hphp/runtime/ext/simplexml/ext_simplexml.h"
45 #include "hphp/runtime/ext/std/ext_std_closure.h"
46 #include "hphp/runtime/vm/class.h"
47 #include "hphp/runtime/vm/unit.h"
48 #include "hphp/util/alloc.h"
49 #include "hphp/runtime/base/heap-graph.h"
50 #include "hphp/runtime/base/heap-algorithms.h"
51 #include "hphp/runtime/base/container-functions.h"
52 #include "hphp/runtime/base/zend-string.h"
53 #include "hphp/runtime/vm/vm-regs.h"
59 TRACE_SET_MOD(heapgraph
);
61 ///////////////////////////////////////////////////////////////////////////////
67 s_root_path("root_path"),
83 ///////////////////////////////////////////////////////////////////////////////
86 // Extra information about a HeapGraph::Node.
90 const char* classname
;
93 // Extra information about a HeapGraph::Ptr
98 struct HeapGraphContext
: SweepableResourceData
{
99 explicit HeapGraphContext(const HeapGraph
& hg
) : hg(hg
) {}
100 explicit HeapGraphContext(HeapGraph
&& hg
) : hg(std::move(hg
)) {}
101 ~HeapGraphContext() {}
103 bool isInvalid() const override
{
107 CLASSNAME_IS("HeapGraphContext")
108 DECLARE_RESOURCE_ALLOCATION(HeapGraphContext
)
110 // overriding ResourceData
111 const String
& o_getClassNameHook() const override
{ return classnameof(); }
113 std::vector
<CapturedNode
> cnodes
;
114 std::vector
<CapturedPtr
> cptrs
;
118 IMPLEMENT_RESOURCE_ALLOCATION(HeapGraphContext
)
122 using HeapGraphContextPtr
= req::ptr
<HeapGraphContext
>;
123 static HeapGraphContextPtr
get_valid_heapgraph_context_resource(
124 const Resource
& resource
,
125 const char* func_name
127 auto hgcontext
= dyn_cast_or_null
<HeapGraphContext
>(resource
);
128 if (hgcontext
== nullptr || hgcontext
->isInvalid()) {
130 "%s(): supplied resource is not a valid HeapGraph Context resource",
138 ///////////////////////////////////////////////////////////////////////////////
139 // TRAVERSAL FUNCTIONS
141 bool supportsToArray(const ObjectData
* obj
) {
142 if (obj
->isCollection()) {
143 assertx(isValidCollection(obj
->collectionType()));
145 } else if (UNLIKELY(obj
->getAttribute(ObjectData::CallToImpl
))) {
146 return obj
->instanceof(SimpleXMLElement_classof());
147 } else if (UNLIKELY(obj
->instanceof(SystemLib::s_ArrayObjectClass
))) {
149 } else if (UNLIKELY(obj
->instanceof(SystemLib::s_ArrayIteratorClass
))) {
151 } else if (UNLIKELY(obj
->instanceof(c_Closure::classof()))) {
153 } else if (UNLIKELY(obj
->instanceof(DateTimeData::getClass()))) {
156 if (LIKELY(!obj
->hasInstanceDtor())) {
164 std::string
getObjectConnectionName(
165 const ObjectData
* obj
,
168 Class
* cls
= obj
->getVMClass();
169 FTRACE(5, "HG: Getting connection name for type {} at {}\n",
170 obj
->getClassName().data(),
174 if (!supportsToArray(obj
)) {
178 auto arr
= obj
->toArray(); // TODO t12985984 avoid toArray.
179 bool is_packed
= arr
->hasPackedLayout();
180 for (ArrayIter
iter(arr
); iter
; ++iter
) {
181 auto first
= iter
.first();
182 auto key
= first
.toString();
183 auto key_tv
= first
.asTypedValue();
184 auto val_tv
= iter
.secondRef().asTypedValue();
186 if (isStringType(key_tv
->m_type
)) {
187 // If the key begins with a NUL, it's a private or protected property.
188 // Read the class name from between the two NUL bytes.
190 // Note: Copied from object-data.cpp
191 if (!key
.empty() && key
[0] == '\0') {
192 int subLen
= key
.find('\0', 1) + 1;
193 key
= key
.substr(subLen
);
197 FTRACE(5, "HG: ...Iterating over object key-val {}=>{}\n",
198 key
, tname(val_tv
->m_type
)
201 // We're only interested in the property name that points to our target
202 if ((void*)val_tv
->m_data
.pobj
!= target
) {
206 bool is_declared
= isStringType(key_tv
->m_type
) &&
207 cls
->lookupDeclProp(key
.get()) != kInvalidSlot
;
209 if (!is_declared
&& !is_packed
) {
210 return std::string("Key:" + key
);
211 } else if (is_packed
) {
212 return std::string("PropertyIndex");
214 return std::string("Property:" + key
);
221 std::string
getEdgeKindName(HeapGraph::PtrKind kind
) {
223 case HeapGraph::Counted
:
224 return "Ptr:Counted";
225 case HeapGraph::Implicit
:
226 return "Ptr:Implicit";
227 case HeapGraph::Ambiguous
:
228 return "Ptr:Ambiguous";
233 std::string
getNodesConnectionName(
239 // Try to drill down and resolve the edge name
240 if (from
!= -1 && to
!= -1) {
241 auto h
= g
.nodes
[from
].h
;
242 auto th
= g
.nodes
[to
].h
;
244 // get the from/to object, if any. this deals with object kinds that
245 // have data before the object: AFWH, Native, and Closure.
246 auto h_obj
= h
->obj();
247 auto th_obj
= th
->obj();
250 // Known generalized cases that don't really need pointer kind
251 case HeaderKind::Mixed
:
252 case HeaderKind::Dict
:
253 case HeaderKind::Keyset
:
254 return "ArrayKeyValue";
256 // Obvious cases that do not need pointer type
257 case HeaderKind::AwaitAllWH
:
258 case HeaderKind::WaitHandle
:
259 case HeaderKind::AsyncFuncWH
:
260 case HeaderKind::Pair
:
261 case HeaderKind::AsyncFuncFrame
:
262 case HeaderKind::Set
:
263 case HeaderKind::ImmSet
:
264 case HeaderKind::Vector
:
265 case HeaderKind::ImmVector
:
266 case HeaderKind::Packed
:
267 case HeaderKind::VecArray
:
270 // Explicit cases that have explicit pointer name
272 case HeaderKind::Ref
:
275 case HeaderKind::Map
:
276 case HeaderKind::ImmMap
:
277 case HeaderKind::ClosureHdr
:
278 case HeaderKind::Closure
:
279 case HeaderKind::NativeData
:
280 case HeaderKind::Object
: {
281 auto conn_name
= getObjectConnectionName(h_obj
,
282 th_obj
? static_cast<const void*>(th_obj
) :
283 static_cast<const void*>(th
)
285 if (!conn_name
.empty()) {
288 // Fallback to pointer kind
292 // Unknown drilldown cases that need pointer type
293 case HeaderKind::Empty
:
294 case HeaderKind::Apc
:
295 case HeaderKind::Globals
:
296 case HeaderKind::Proxy
:
297 case HeaderKind::String
:
298 case HeaderKind::Resource
:
299 case HeaderKind::BigMalloc
:
300 case HeaderKind::SmallMalloc
:
301 case HeaderKind::Free
:
302 case HeaderKind::BigObj
:
303 case HeaderKind::Hole
:
304 // Fallback to pointer kind
307 } else if (from
== -1 && to
!= -1) {
308 return g
.ptrs
[ptr
].description
;
311 return getEdgeKindName(g
.ptrs
[ptr
].ptr_kind
);
314 void heapgraphCallback(Array fields
, const Variant
& callback
) {
316 auto params
= make_packed_array(fields
);
317 vm_call_user_func(callback
, params
);
320 void heapgraphCallback(Array fields
, Array fields2
, const Variant
& callback
) {
322 auto params
= make_packed_array(fields
, fields2
);
323 vm_call_user_func(callback
, params
);
326 Array
createPhpNode(HeapGraphContextPtr hgptr
, int index
) {
327 const auto& cnode
= hgptr
->cnodes
[index
];
329 auto node_arr
= make_map_array(
330 s_index
, Variant(index
),
331 s_kind
, Variant(header_names
[int(cnode
.kind
)]),
332 s_size
, Variant(int64_t(cnode
.size
))
335 if (cnode
.classname
!= nullptr) {
336 node_arr
.set(s_class
, Variant(cnode
.classname
));
342 Array
createPhpEdge(HeapGraphContextPtr hgptr
, int index
) {
343 const auto& ptr
= hgptr
->hg
.ptrs
[index
];
344 const auto& cptr
= hgptr
->cptrs
[index
];
346 auto ptr_arr
= make_map_array(
347 s_index
, Variant(index
),
348 s_kind
, Variant(getEdgeKindName(ptr
.ptr_kind
)),
349 s_from
, Variant(ptr
.from
),
350 s_to
, Variant(ptr
.to
),
351 s_seat
, Variant(ptr
.description
),
352 s_name
, Variant(cptr
.edgename
)
358 std::vector
<int> toBoundIntVector(const Array
& arr
, int64_t max
) {
359 std::vector
<int> result
;
360 result
.reserve(arr
.size());
361 for (ArrayIter
iter(arr
); iter
; ++iter
) {
362 auto index
= iter
.second().toInt64(); // Cannot re-enter.
363 if (index
< 0 || index
>= max
) {
367 result
.push_back(index
);
372 ///////////////////////////////////////////////////////////////////////////////
375 Resource
HHVM_FUNCTION(heapgraph_create
, void) {
376 HeapGraph hg
= makeHeapGraph();
377 std::vector
<CapturedNode
> cnodes
;
378 std::vector
<CapturedPtr
> cptrs
;
380 // Copy edges into captured edges
381 // Capturing edges first because after capturing nodes we nullify the header
382 cptrs
.reserve(hg
.ptrs
.size());
383 for (int i
= 0; i
< hg
.ptrs
.size(); ++i
) {
384 const auto& src_ptr
= hg
.ptrs
[i
];
385 auto new_ptr
= CapturedPtr
{
386 /* edgename */ getNodesConnectionName(hg
, i
, src_ptr
.from
, src_ptr
.to
)
388 cptrs
.push_back(new_ptr
);
391 // Copy nodes into captured nodes
392 cnodes
.reserve(hg
.nodes
.size());
393 for (int i
= 0; i
< hg
.nodes
.size(); ++i
) {
394 const auto& src_node
= hg
.nodes
[i
];
395 auto obj
= src_node
.h
->obj();
396 auto new_node
= CapturedNode
{
397 /* kind */ src_node
.h
->kind(),
398 /* size */ uint32_t(src_node
.h
->size()),
399 /* classname */ obj
? obj
->classname_cstr() : nullptr
401 cnodes
.push_back(new_node
);
403 // Nullify the pointers to be safe since this is a captured heap
404 hg
.nodes
[i
].h
= nullptr;
407 auto hgcontext
= req::make
<HeapGraphContext
>(std::move(hg
));
408 std::swap(hgcontext
->cnodes
, cnodes
);
409 std::swap(hgcontext
->cptrs
, cptrs
);
410 return Resource(hgcontext
);
413 void HHVM_FUNCTION(heapgraph_foreach_node
,
414 const Resource
& resource
,
415 const Variant
& callback
417 auto hgptr
= get_valid_heapgraph_context_resource(resource
, __FUNCTION__
);
418 if (!hgptr
|| callback
.isNull()) return;
419 for (int i
= 0; i
< hgptr
->hg
.nodes
.size(); i
++) {
420 auto phpnode
= createPhpNode(hgptr
, i
);
421 heapgraphCallback(phpnode
, callback
);
425 void HHVM_FUNCTION(heapgraph_foreach_edge
,
426 const Resource
& resource
,
427 const Variant
& callback
429 auto hgptr
= get_valid_heapgraph_context_resource(resource
, __FUNCTION__
);
430 if (!hgptr
|| callback
.isNull()) return;
431 for (int i
= 0; i
< hgptr
->hg
.ptrs
.size(); i
++) {
432 auto phpedge
= createPhpEdge(hgptr
, i
);
433 heapgraphCallback(phpedge
, callback
);
437 void HHVM_FUNCTION(heapgraph_foreach_root
,
438 const Resource
& resource
,
439 const Variant
& callback
441 auto hgptr
= get_valid_heapgraph_context_resource(resource
, __FUNCTION__
);
442 if (!hgptr
|| callback
.isNull()) return;
443 for (int i
= 0; i
< hgptr
->hg
.roots
.size(); i
++) {
444 auto phpedge
= createPhpEdge(hgptr
, hgptr
->hg
.roots
[i
]);
445 heapgraphCallback(phpedge
, callback
);
449 void HHVM_FUNCTION(heapgraph_dfs_nodes
,
450 const Resource
& resource
,
451 const Array
& roots_arr
,
452 const Array
& skips_arr
,
453 const Variant
& callback
455 auto hgptr
= get_valid_heapgraph_context_resource(resource
, __FUNCTION__
);
456 if (!hgptr
|| callback
.isNull()) return;
457 auto max
= hgptr
->hg
.nodes
.size();
458 auto roots
= toBoundIntVector(roots_arr
, max
);
459 auto skips
= toBoundIntVector(skips_arr
, max
);
460 dfs_nodes(hgptr
->hg
, roots
, skips
, [&](int n
) {
461 auto phpnode
= createPhpNode(hgptr
, n
);
462 heapgraphCallback(phpnode
, callback
);
466 void HHVM_FUNCTION(heapgraph_dfs_edges
,
467 const Resource
& resource
,
468 const Array
& roots_arr
,
469 const Array
& skips_arr
,
470 const Variant
& callback
472 auto hgptr
= get_valid_heapgraph_context_resource(resource
, __FUNCTION__
);
473 if (!hgptr
|| callback
.isNull()) return;
474 auto max
= hgptr
->hg
.ptrs
.size();
475 auto roots
= toBoundIntVector(roots_arr
, max
);
476 auto skips
= toBoundIntVector(skips_arr
, max
);
477 dfs_ptrs(hgptr
->hg
, roots
, skips
, [&](int n
, int p
) {
478 auto phpnode
= createPhpNode(hgptr
, n
);
479 auto phpedge
= createPhpEdge(hgptr
, p
);
480 heapgraphCallback(phpedge
, phpnode
, callback
);
484 Array
HHVM_FUNCTION(heapgraph_edge
, const Resource
& resource
, int64_t index
) {
485 auto hgptr
= get_valid_heapgraph_context_resource(resource
, __FUNCTION__
);
486 if (!hgptr
) return empty_array();
487 if (size_t(index
) >= hgptr
->hg
.ptrs
.size()) return empty_array();
488 return createPhpEdge(hgptr
, index
);
491 Array
HHVM_FUNCTION(heapgraph_node
, const Resource
& resource
, int64_t index
) {
492 auto hgptr
= get_valid_heapgraph_context_resource(resource
, __FUNCTION__
);
493 if (!hgptr
) return empty_array();
494 if (size_t(index
) >= hgptr
->hg
.nodes
.size()) return empty_array();
495 return createPhpNode(hgptr
, index
);
498 Array
HHVM_FUNCTION(heapgraph_node_out_edges
,
499 const Resource
& resource
,
502 auto hgptr
= get_valid_heapgraph_context_resource(resource
, __FUNCTION__
);
503 if (!hgptr
) return empty_array();
504 if (size_t(index
) >= hgptr
->hg
.nodes
.size()) return empty_array();
506 hgptr
->hg
.eachOutPtr(index
, [&](int) { num_edges
++; });
507 PackedArrayInit
result(num_edges
);
508 hgptr
->hg
.eachOutPtr(index
, [&](int ptr
) {
509 result
.append(createPhpEdge(hgptr
, ptr
));
511 return result
.toArray();
514 Array
HHVM_FUNCTION(heapgraph_node_in_edges
,
515 const Resource
& resource
,
518 auto hgptr
= get_valid_heapgraph_context_resource(resource
, __FUNCTION__
);
519 if (!hgptr
) return empty_array();
520 if (size_t(index
) >= hgptr
->hg
.nodes
.size()) return empty_array();
522 hgptr
->hg
.eachInPtr(index
, [&](int) { num_edges
++; });
523 PackedArrayInit
result(num_edges
);
524 hgptr
->hg
.eachInPtr(index
, [&](int ptr
) {
525 result
.append(createPhpEdge(hgptr
, ptr
));
527 return result
.toArray();
530 Array
HHVM_FUNCTION(heapgraph_stats
, const Resource
& resource
) {
531 auto hgptr
= get_valid_heapgraph_context_resource(resource
, __FUNCTION__
);
532 if (!hgptr
) return empty_array();
533 auto result
= make_map_array(
534 s_nodes
, Variant(hgptr
->hg
.nodes
.size()),
535 s_edges
, Variant(hgptr
->hg
.ptrs
.size()),
536 s_roots
, Variant(hgptr
->hg
.roots
.size())
541 ///////////////////////////////////////////////////////////////////////////////
546 struct heapgraphExtension final
: Extension
{
547 heapgraphExtension() : Extension("heapgraph", "1.0") { }
549 void moduleInit() override
{
550 HHVM_FALIAS(HH
\\heapgraph_create
, heapgraph_create
);
551 HHVM_FALIAS(HH
\\heapgraph_stats
, heapgraph_stats
);
552 HHVM_FALIAS(HH
\\heapgraph_foreach_node
, heapgraph_foreach_node
);
553 HHVM_FALIAS(HH
\\heapgraph_foreach_edge
, heapgraph_foreach_edge
);
554 HHVM_FALIAS(HH
\\heapgraph_foreach_root
, heapgraph_foreach_root
);
555 HHVM_FALIAS(HH
\\heapgraph_edge
, heapgraph_edge
);
556 HHVM_FALIAS(HH
\\heapgraph_node
, heapgraph_node
);
557 HHVM_FALIAS(HH
\\heapgraph_node_out_edges
, heapgraph_node_out_edges
);
558 HHVM_FALIAS(HH
\\heapgraph_node_in_edges
, heapgraph_node_in_edges
);
559 HHVM_FALIAS(HH
\\heapgraph_dfs_nodes
, heapgraph_dfs_nodes
);
560 HHVM_FALIAS(HH
\\heapgraph_dfs_edges
, heapgraph_dfs_edges
);
564 } s_heapgraph_extension
;
567 ///////////////////////////////////////////////////////////////////////////////