codemod 2010-2016 to 2010-present
[hiphop-php.git] / hphp / runtime / ext / objprof / ext_heapgraph.cpp
blobb9decb0f54e6178ae03bbbd009ad569c8a1cb55d
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"
19 #include "hphp/runtime/base/builtin-functions.h"
22 * Heapgraph Extension
23 * What is it?
24 * Set of methods to wrap around HHVM's heap graph implementation
26 * How does it work?
27 * Create a heap graph in HHVM and uses it as a Resource with a
28 * set of functions that can operate on it.
30 * How do I use it?
31 * Call heapgraph_create, and then any of the other heapgraph
32 * function
35 #include <set>
36 #include <unordered_map>
37 #include <vector>
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"
55 namespace HPHP {
57 namespace {
59 TRACE_SET_MOD(heapgraph);
61 ///////////////////////////////////////////////////////////////////////////////
63 const StaticString
64 s_nodes("nodes"),
65 s_edges("edges"),
66 s_roots("roots"),
67 s_root_path("root_path"),
69 // Node description
70 s_kind("kind"),
71 s_size("size"),
72 s_index("index"),
73 s_class("class"),
75 // Edge description
76 s_seat("seat"),
77 s_name("name"),
78 s_from("from"),
79 s_to("to");
83 ///////////////////////////////////////////////////////////////////////////////
84 // CONTEXT OBJECTS
86 // Extra information about a HeapGraph::Node.
87 struct CapturedNode {
88 HeaderKind kind;
89 uint32_t size;
90 const char* classname;
93 // Extra information about a HeapGraph::Ptr
94 struct CapturedPtr {
95 std::string edgename;
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 {
104 return false;
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;
115 const HeapGraph hg;
118 IMPLEMENT_RESOURCE_ALLOCATION(HeapGraphContext)
120 namespace {
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()) {
129 raise_warning(
130 "%s(): supplied resource is not a valid HeapGraph Context resource",
131 func_name + 2
133 return nullptr;
135 return hgcontext;
138 ///////////////////////////////////////////////////////////////////////////////
139 // TRAVERSAL FUNCTIONS
141 bool supportsToArray(const ObjectData* obj) {
142 if (obj->isCollection()) {
143 assertx(isValidCollection(obj->collectionType()));
144 return true;
145 } else if (UNLIKELY(obj->getAttribute(ObjectData::CallToImpl))) {
146 return obj->instanceof(SimpleXMLElement_classof());
147 } else if (UNLIKELY(obj->instanceof(SystemLib::s_ArrayObjectClass))) {
148 return true;
149 } else if (UNLIKELY(obj->instanceof(SystemLib::s_ArrayIteratorClass))) {
150 return true;
151 } else if (UNLIKELY(obj->instanceof(c_Closure::classof()))) {
152 return true;
153 } else if (UNLIKELY(obj->instanceof(DateTimeData::getClass()))) {
154 return true;
155 } else {
156 if (LIKELY(!obj->hasInstanceDtor())) {
157 return true;
160 return false;
164 std::string getObjectConnectionName(
165 const ObjectData* obj,
166 const void* target
168 Class* cls = obj->getVMClass();
169 FTRACE(5, "HG: Getting connection name for type {} at {}\n",
170 obj->getClassName().data(),
174 if (!supportsToArray(obj)) {
175 return "";
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) {
203 continue;
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");
213 } else {
214 return std::string("Property:" + key);
218 return "";
221 std::string getEdgeKindName(HeapGraph::PtrKind kind) {
222 switch (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";
230 not_reached();
233 std::string getNodesConnectionName(
234 const HeapGraph& g,
235 int ptr,
236 int from,
237 int to
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();
249 switch (h->kind()) {
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:
268 return "";
270 // Explicit cases that have explicit pointer name
272 case HeaderKind::Ref:
273 return "";
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()) {
286 return conn_name;
288 // Fallback to pointer kind
289 break;
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
305 break;
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) {
315 VMRegAnchor _;
316 auto params = make_packed_array(fields);
317 vm_call_user_func(callback, params);
320 void heapgraphCallback(Array fields, Array fields2, const Variant& callback) {
321 VMRegAnchor _;
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));
339 return node_arr;
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)
355 return ptr_arr;
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) {
364 continue;
367 result.push_back(index);
369 return result;
372 ///////////////////////////////////////////////////////////////////////////////
373 // Exports
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,
500 int64_t index
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();
505 size_t num_edges{0};
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,
516 int64_t index
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();
521 size_t num_edges{0};
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())
538 return result;
541 ///////////////////////////////////////////////////////////////////////////////
542 // Extension
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);
562 loadSystemlib();
564 } s_heapgraph_extension;
567 ///////////////////////////////////////////////////////////////////////////////