EvalEmitDVArray: varray
[hiphop-php.git] / hphp / runtime / base / heap-report.cpp
blob70d255c3e85a8f5e8f9c0ac09af447ad809b38c2
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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 +----------------------------------------------------------------------+
17 #include <sstream>
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);
29 namespace HPHP {
30 namespace {
32 size_t count_reached(const HeapGraph& g, const std::vector<int>& root_nodes) {
33 size_t count{0};
34 dfs_nodes(g, root_nodes, [&](int /*n*/) { count++; });
35 return 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;
42 out << n;
43 if (haveCount(h->kind())) {
44 out << "#" << static_cast<const MaybeCountable*>(h)->count();
46 out << ":" << header_names[int(h->kind())];
47 switch (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:
54 case HeaderKind::Apc:
55 case HeaderKind::Globals:
56 case HeaderKind::RecordArray:
57 out << "[" << static_cast<const ArrayData*>(h)->size() << "]";
58 break;
59 case HeaderKind::String:
60 out << "[" << static_cast<const StringData*>(h)->size() << "]";
61 break;
62 case HeaderKind::Resource:
63 case HeaderKind::ClsMeth:
64 break;
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();
72 break;
73 case HeaderKind::Record:
74 out << ":" <<
75 static_cast<const RecordData*>(h)->record()->name()->data();
76 case HeaderKind::Vector:
77 case HeaderKind::Map:
78 case HeaderKind::Set:
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)) << "]";
87 break;
89 case HeaderKind::Cpp:
90 case HeaderKind::BigMalloc:
91 case HeaderKind::SmallMalloc:
92 out << "[" << static_cast<const MallocNode*>(h)->nbytes << "]";
93 break;
94 case HeaderKind::AsyncFuncFrame:
95 case HeaderKind::NativeData:
96 case HeaderKind::ClosureHdr:
97 case HeaderKind::MemoData:
98 break;
99 case HeaderKind::Free:
100 out << "[" << static_cast<const FreeNode*>(h)->size() << "]";
101 break;
102 case HeaderKind::Slab:
103 case HeaderKind::Hole:
104 not_reached();
106 out << " " << (void*)h;
107 return out.str();
110 const char* ptrSym[] = {
111 "<--", // Counted
112 "<~~", // Ambiguous
115 DEBUG_ONLY
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);
122 return out.str();
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());
139 TRACE(2, "\n");
141 } // anon namespace
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++;
151 else c2++;
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) {
158 parents[node] = 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());
174 // Cycle analysis:
175 std::vector<int> live_cycle_nodes, leaked_cycle_nodes;
176 size_t num_live_cycles{0}, num_leaked_cycles{0};
177 findHeapCycles(g,
178 /* live */ [&](NodeRange cycle) {
179 report_cycle("live", cycle);
180 num_live_cycles++;
181 live_cycle_nodes.insert(live_cycle_nodes.end(),
182 cycle.begin(), cycle.end());
184 /* leaked */ [&](NodeRange cycle) {
185 report_cycle("leaked", cycle);
186 num_leaked_cycles++;
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)%% "
193 "of live objects\n",
194 live_cycle_nodes.size(), num_live_cycles, reached, live,
195 100.0*reached/live);
196 } else {
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);
206 } else {
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());
215 return;
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",
222 indent.c_str());
223 } else {
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");
262 return true;