2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
19 #include "hphp/runtime/base/heap-graph.h"
20 #include "hphp/runtime/base/heap-algorithms.h"
21 #include "hphp/runtime/base/header-kind.h"
22 #include "hphp/util/trace.h"
23 #include "hphp/util/assertions.h"
24 #include "hphp/runtime/base/memory-manager-defs.h"
25 #include "hphp/runtime/base/container-functions.h"
27 TRACE_SET_MOD(heapreport
);
32 size_t count_reached(const HeapGraph
& g
, const std::vector
<int>& root_nodes
) {
34 dfs_nodes(g
, root_nodes
, [&](int /*n*/) { count
++; });
38 // make a short string describing an object
39 DEBUG_ONLY
std::string
describe(const HeapGraph
& g
, int n
) {
40 std::ostringstream out
;
41 auto h
= g
.nodes
[n
].h
;
43 if (haveCount(h
->kind())) {
44 out
<< "#" << static_cast<const MaybeCountable
*>(h
)->count();
46 out
<< ":" << header_names
[int(h
->kind())];
48 case HeaderKind::Packed
:
49 case HeaderKind::Mixed
:
50 case HeaderKind::Dict
:
51 case HeaderKind::Empty
:
52 case HeaderKind::VecArray
:
53 case HeaderKind::Keyset
:
55 case HeaderKind::Globals
:
56 case HeaderKind::RecordArray
:
57 out
<< "[" << static_cast<const ArrayData
*>(h
)->size() << "]";
59 case HeaderKind::String
:
60 out
<< "[" << static_cast<const StringData
*>(h
)->size() << "]";
62 case HeaderKind::Resource
:
63 case HeaderKind::ClsMeth
:
65 case HeaderKind::Object
:
66 case HeaderKind::NativeObject
:
67 case HeaderKind::Closure
:
68 case HeaderKind::WaitHandle
:
69 case HeaderKind::AsyncFuncWH
:
70 case HeaderKind::AwaitAllWH
:
71 out
<< ":" << static_cast<const ObjectData
*>(h
)->classname_cstr();
73 case HeaderKind::Record
:
75 static_cast<const RecordData
*>(h
)->record()->name()->data();
76 case HeaderKind::Vector
:
79 case HeaderKind::Pair
:
80 case HeaderKind::ImmVector
:
81 case HeaderKind::ImmMap
:
82 case HeaderKind::ImmSet
: {
83 auto obj
= const_cast<ObjectData
*>(
84 static_cast<const ObjectData
*>(h
)
86 out
<< "[" << getContainerSize(make_tv
<KindOfObject
>(obj
)) << "]";
90 case HeaderKind::BigMalloc
:
91 case HeaderKind::SmallMalloc
:
92 out
<< "[" << static_cast<const MallocNode
*>(h
)->nbytes
<< "]";
94 case HeaderKind::AsyncFuncFrame
:
95 case HeaderKind::NativeData
:
96 case HeaderKind::ClosureHdr
:
97 case HeaderKind::MemoData
:
99 case HeaderKind::Free
:
100 out
<< "[" << static_cast<const FreeNode
*>(h
)->size() << "]";
102 case HeaderKind::Slab
:
103 case HeaderKind::Hole
:
106 out
<< " " << (void*)h
;
110 const char* ptrSym
[] = {
116 std::string
describePtr(const HeapGraph
& g
, const HeapGraph::Ptr
& ptr
) {
117 std::ostringstream out
;
118 out
<< " " << ptrSym
[(unsigned)ptr
.ptr_kind
];
119 auto& from
= g
.nodes
[ptr
.from
];
120 if (!from
.is_root
) out
<< describe(g
, ptr
.from
);
121 else out
<< type_scan::getName(from
.tyindex
);
125 void reportUndead(const HeapGraph
& g
, int node
) {
126 TRACE(3, "HG: undead object %s\n",
127 describe(g
, node
).c_str());
128 g
.eachPred(node
, [&](const HeapGraph::Ptr
& ptr
) {
129 TRACE(3, " %s\n", describePtr(g
, ptr
).c_str());
133 // print path from root to n
134 void reportPathFromRoot(const HeapGraph
& g
, std::vector
<int>& parents
, int n
) {
135 TRACE(2, " %s", describe(g
, n
).c_str());
136 walkParents(g
, parents
, n
, [&](const HeapGraph::Ptr
& ptr
) {
137 TRACE(2, "%s", describePtr(g
, ptr
).c_str());
143 void printHeapReport(const HeapGraph
& g
, const char* phase
) {
144 TRACE(2, "HG: printHeapReport %s\n", phase
);
145 size_t allocd
= 0; // non-free nodes in the heap
146 size_t freed
= 0; // free in the heap
147 size_t live
= 0; // non-free reachable nodes
148 size_t undead
= 0; // free but still reachable
149 auto count
= [&](int n
, size_t& c1
, size_t& c2
) {
150 if (g
.nodes
[n
].h
->kind() != HeaderKind::Free
) c1
++;
153 for (int i
= 0; i
< g
.nodes
.size(); i
++) {
154 count(i
, allocd
, freed
);
156 std::vector
<int> parents(g
.nodes
.size(), -1);
157 dfs_ptrs(g
, g
.root_ptrs
, [&](int node
, int ptr
) {
159 count(node
, live
, undead
);
160 auto h
= g
.nodes
[node
].h
;
161 if (h
->kind() == HeaderKind::Free
) {
162 reportUndead(g
, node
);
165 TRACE(2, "HG: allocd %lu freed %lu live %lu undead %lu leaked %lu\n",
166 allocd
, freed
, live
, undead
, allocd
-live
);
167 auto report_cycle
= [&](const char* kind
, NodeRange cycle
) {
168 TRACE(2, "HG: %s cycle of %lu nodes:\n", kind
, cycle
.size());
169 reportPathFromRoot(g
, parents
, cycle
[0]);
170 for (size_t i
= 1; i
< cycle
.size(); ++i
) {
171 TRACE(2, " %s\n", describe(g
, cycle
[i
]).c_str());
175 std::vector
<int> live_cycle_nodes
, leaked_cycle_nodes
;
176 size_t num_live_cycles
{0}, num_leaked_cycles
{0};
178 /* live */ [&](NodeRange cycle
) {
179 report_cycle("live", cycle
);
181 live_cycle_nodes
.insert(live_cycle_nodes
.end(),
182 cycle
.begin(), cycle
.end());
184 /* leaked */ [&](NodeRange cycle
) {
185 report_cycle("leaked", cycle
);
187 leaked_cycle_nodes
.insert(leaked_cycle_nodes
.end(),
188 cycle
.begin(), cycle
.end());
190 if (!live_cycle_nodes
.empty()) {
191 DEBUG_ONLY
auto reached
= count_reached(g
, live_cycle_nodes
);
192 TRACE(2, "HG: %ld live in %lu cycles hold %lu/%lu(%.0f)%% "
194 live_cycle_nodes
.size(), num_live_cycles
, reached
, live
,
197 TRACE(2, "HG: no live cycles found\n");
199 if (!leaked_cycle_nodes
.empty()) {
200 DEBUG_ONLY
auto leaked
= allocd
- live
;
201 DEBUG_ONLY
auto reached
= count_reached(g
, leaked_cycle_nodes
);
202 TRACE(2, "HG: %ld leaked in %lu cycles hold %lu/%lu(%.0f)%%"
203 "of leaked objects\n",
204 leaked_cycle_nodes
.size(), num_leaked_cycles
, reached
, leaked
,
205 100.0*reached
/leaked
);
207 TRACE(2, "HG: no leaked cycles found.\n");
211 static void traceToRoot(const HeapGraph
& g
, int n
, const std::string
& ind
) {
212 const std::string
indent(ind
+ " ");
213 if (indent
.length() > 200) {
214 TRACE(1, "%s ## level limit exceeded ##\n", indent
.c_str());
217 g
.eachPred(n
, [&](const HeapGraph::Ptr
& ptr
) {
218 TRACE(1, "%s%s\n", indent
.c_str(), describePtr(g
, ptr
).c_str());
219 if (ptr
.from
== -1) return;
220 if (ptr
.ptr_kind
!= HeapGraph::Counted
) {
221 TRACE(1, "%s ## only tracing ref-counted references ##\n",
224 traceToRoot(g
, ptr
.from
, indent
);
229 bool checkPointers(const HeapGraph
& g
, const char* phase
) {
230 UNUSED
auto found_dangling
= false;
231 for (size_t n
= 0; n
< g
.nodes
.size(); ++n
) {
232 auto& node
= g
.nodes
[n
];
233 if (!haveCount(node
.h
->kind())) continue;
234 auto count
= static_cast<const MaybeCountable
*>(node
.h
)->count();
235 assertx(count
>= 0); // static things shouldn't be in the heap.
236 unsigned num_counted
{0}, num_ambig
{0};
237 g
.eachPred(n
, [&](const HeapGraph::Ptr
& ptr
) {
238 switch (ptr
.ptr_kind
) {
239 case HeapGraph::Counted
: num_counted
++; break;
240 case HeapGraph::Ambiguous
: num_ambig
++; break;
241 case HeapGraph::Weak
: break;
244 auto num_ptrs
= num_counted
+ num_ambig
;
245 if (num_ptrs
< count
) {
246 // missed at least one counted pointer, or refcount too high
247 // no assert, because without gc the only effect is a leak.
248 TRACE(1, "HG: %s missing %d pointers to %s\n", phase
, count
- num_ptrs
,
249 describe(g
, n
).c_str());
250 g
.eachPred(n
, [&](const HeapGraph::Ptr
& ptr
) {
251 TRACE(1, " %s\n", describePtr(g
, ptr
).c_str());
253 } else if (num_counted
> count
) {
254 // traced too many exact ptrs. buggy scan() or refcount is too low.
255 found_dangling
= true;
256 TRACE(1, "HG: %s dangling %d pointers to %s\n",
257 phase
, num_counted
- count
, describe(g
, n
).c_str());
258 traceToRoot(g
, n
, "");
261 assertx(!found_dangling
&& "found dangling pointers");