From 5c486012c9e5ff3e2daa083bc61c42dcb7c2867a Mon Sep 17 00:00:00 2001 From: Eric Caruso Date: Tue, 2 Jul 2013 22:51:47 -0700 Subject: [PATCH] Basic heap tracing framework + debugger command Adds an extension command to the debugger which provides heap tracing functionality, and adds the framework needed to get heap traces. The heaptrace command can dump the current heap to the debugger session or it can save a GraphViz specification to a file. I'm hoping to add a backend which can put the graph in GML format, so it can be explored interactively. I also hope at some point to see this integrated into FBIDE if we can do that. --- hphp/runtime/base/array/array_data.cpp | 14 ++ hphp/runtime/base/array/array_data.h | 3 + hphp/runtime/base/array/policy_array.cpp | 1 - hphp/runtime/base/memory/tracer.h | 120 ++++++++++++ hphp/runtime/base/object_data.h | 8 + hphp/runtime/base/shared/shared_map.cpp | 13 ++ hphp/runtime/base/shared/shared_map.h | 3 + hphp/runtime/base/util/thrift_buffer.h | 28 +++ hphp/runtime/debugger/cmd/all.h | 1 + hphp/runtime/debugger/cmd/cmd_extended.cpp | 13 +- hphp/runtime/debugger/cmd/cmd_heaptrace.cpp | 196 ++++++++++++++++++++ hphp/runtime/debugger/cmd/cmd_heaptrace.h | 55 ++++++ hphp/runtime/vm/bytecode.cpp | 1 + hphp/runtime/vm/class.cpp | 7 + hphp/runtime/vm/class.h | 3 + hphp/util/trace.h | 2 + 16 files changed, 460 insertions(+), 8 deletions(-) create mode 100644 hphp/runtime/base/memory/tracer.h create mode 100644 hphp/runtime/debugger/cmd/cmd_heaptrace.cpp create mode 100644 hphp/runtime/debugger/cmd/cmd_heaptrace.h diff --git a/hphp/runtime/base/array/array_data.cpp b/hphp/runtime/base/array/array_data.cpp index f68c5fd26..66164d03b 100644 --- a/hphp/runtime/base/array/array_data.cpp +++ b/hphp/runtime/base/array/array_data.cpp @@ -581,5 +581,19 @@ void ArrayData::dump(std::ostream &out) { } } +void ArrayData::getChildren(std::vector &out) { + if (isSharedMap()) { + SharedMap *sm = static_cast(this); + sm->getChildren(out); + return; + } + for (ssize_t pos = iter_begin(); + pos != ArrayData::invalid_index; + pos = iter_advance(pos)) { + TypedValue *tv = nvGetValueRef(pos); + out.push_back(tv); + } +} + /////////////////////////////////////////////////////////////////////////////// } diff --git a/hphp/runtime/base/array/array_data.h b/hphp/runtime/base/array/array_data.h index 3ff0c723c..40fdd8661 100644 --- a/hphp/runtime/base/array/array_data.h +++ b/hphp/runtime/base/array/array_data.h @@ -494,6 +494,9 @@ protected: static uint32_t getKindOff() { return (uintptr_t)&((ArrayData*)0)->m_kind; } + + public: + void getChildren(std::vector &out); }; ALWAYS_INLINE inline diff --git a/hphp/runtime/base/array/policy_array.cpp b/hphp/runtime/base/array/policy_array.cpp index 7ae0c8d8d..5c3ca4652 100644 --- a/hphp/runtime/base/array/policy_array.cpp +++ b/hphp/runtime/base/array/policy_array.cpp @@ -941,5 +941,4 @@ ArrayData *PolicyArray::escalate() const { return ArrayData::escalate(); } - } // namespace HPHP diff --git a/hphp/runtime/base/memory/tracer.h b/hphp/runtime/base/memory/tracer.h new file mode 100644 index 000000000..dc2f57f01 --- /dev/null +++ b/hphp/runtime/base/memory/tracer.h @@ -0,0 +1,120 @@ +/* + +----------------------------------------------------------------------+ + | HipHop for PHP | + +----------------------------------------------------------------------+ + | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef incl_HPHP_TRACER_H +#define incl_HPHP_TRACER_H + +#include +#include +#include +#include +#include "hphp/runtime/base/complex_types.h" +#include "hphp/util/trace.h" + +namespace HPHP { + +template +struct Tracer { + typedef std::function NodeFunc; + typedef std::function EdgeFunc; + + static void trace(TypedValue *self, + NodeFunc atNode, + EdgeFunc atEdge, + Accum& accumulator) { + std::set visited; + traceImpl(self, visited, atNode, atEdge, accumulator); + } + + static void traceAll(std::vector &ts, + NodeFunc atNode, + EdgeFunc atEdge, + Accum& accumulator) { + std::set visited; + for (TypedValue *t : ts) { + traceImpl(t, visited, atNode, atEdge, accumulator); + } + } + +private: + static void traceImpl(TypedValue *self, + std::set &visited, + NodeFunc atNode, + EdgeFunc atEdge, + Accum& accumulator) { + std::queue tvs; + const auto traceSingle = [&](TypedValue *parent, TypedValue *child) { + atEdge(parent, child, accumulator); + tvs.push(child); + }; + const auto traceMultiple = [&](TypedValue *parent) { + std::vector children; + switch(parent->m_type) { + case KindOfArray: + parent->m_data.parr->getChildren(children); + break; + case KindOfObject: + parent->m_data.pobj->getChildren(children); + break; + default: + not_reached(); + } + for (TypedValue *child : children) { + traceSingle(parent, child); + } + }; + + tvs.push(self); + while (!tvs.empty()) { + TypedValue *top = tvs.front(); + tvs.pop(); + if (visited.find(top) != visited.end()) { + continue; + } + visited.insert(top); + atNode(top, accumulator); + switch (top->m_type) { + case KindOfUninit: + case KindOfNull: + case KindOfBoolean: + case KindOfInt64: + case KindOfDouble: + case KindOfStaticString: + case KindOfString: + break; + case KindOfArray: + case KindOfObject: + traceMultiple(top); + break; + case KindOfRef: + traceSingle(top, top->m_data.pref->tv()); + break; + case KindOfIndirect: + traceSingle(top, top->m_data.pind); + break; + default: + not_reached(); + } + } + } +}; + +} + +#undef TRACE_SINGLE_CHILD +#undef TRACE_MULTIPLE_CHILDREN + +#endif diff --git a/hphp/runtime/base/object_data.h b/hphp/runtime/base/object_data.h index eeddde01e..f63f658c0 100644 --- a/hphp/runtime/base/object_data.h +++ b/hphp/runtime/base/object_data.h @@ -279,6 +279,14 @@ class ObjectData : public CountableNF { } delete this; } + + public: + void getChildren(std::vector &out) { + ArrayData *props = o_properties.get(); + if (props) { + props->getChildren(out); + } + } } __attribute__((aligned(16))); template<> inline SmartPtr::~SmartPtr() {} diff --git a/hphp/runtime/base/shared/shared_map.cpp b/hphp/runtime/base/shared/shared_map.cpp index 2e111f2b3..7809678b0 100644 --- a/hphp/runtime/base/shared/shared_map.cpp +++ b/hphp/runtime/base/shared/shared_map.cpp @@ -251,5 +251,18 @@ ArrayData* SharedMap::loadElems(bool mapInit /* = false */) const { return elems; } +void SharedMap::getChildren(std::vector &out) { + if (m_localCache) { + TypedValue *localCacheEnd = m_localCache + size(); + for (TypedValue *tv = m_localCache; + tv < localCacheEnd; + ++tv) { + if (tv->m_type != KindOfUninit) { + out.push_back(tv); + } + } + } +} + /////////////////////////////////////////////////////////////////////////////// } diff --git a/hphp/runtime/base/shared/shared_map.h b/hphp/runtime/base/shared/shared_map.h index 1b0a4438e..9ca7096ac 100644 --- a/hphp/runtime/base/shared/shared_map.h +++ b/hphp/runtime/base/shared/shared_map.h @@ -136,6 +136,9 @@ private: }; mutable TypedValue* m_localCache; bool isVector() const { return m_isVector; } + +public: + void getChildren(std::vector &out); }; /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/base/util/thrift_buffer.h b/hphp/runtime/base/util/thrift_buffer.h index a073c0269..53839571b 100644 --- a/hphp/runtime/base/util/thrift_buffer.h +++ b/hphp/runtime/base/util/thrift_buffer.h @@ -225,6 +225,34 @@ public: write(data[i]); } } + template + void read(std::pair &data) { + read(data.first); + read(data.second); + } + template + void write(const std::pair &data) { + write(data.first); + write(data.second); + } + template + void read(std::map &data) { + int32_t size; + read(size); + for (int i = 0; i < size; i++) { + std::pair entry; + read(entry); + data.insert(entry); + } + } + template + void write(const std::map &data) { + int32_t size = data.size(); + write(size); + for (const std::pair &entry : data) { + write(entry); + } + } template void read(boost::shared_ptr &data) { bool has; diff --git a/hphp/runtime/debugger/cmd/all.h b/hphp/runtime/debugger/cmd/all.h index a4fdf03c8..af7a65638 100644 --- a/hphp/runtime/debugger/cmd/all.h +++ b/hphp/runtime/debugger/cmd/all.h @@ -51,6 +51,7 @@ #include "hphp/runtime/debugger/cmd/cmd_macro.h" #include "hphp/runtime/debugger/cmd/cmd_config.h" #include "hphp/runtime/debugger/cmd/cmd_complete.h" +#include "hphp/runtime/debugger/cmd/cmd_heaptrace.h" #include "hphp/runtime/debugger/cmd/cmd_internal_testing.h" //tag: new_cmd.php inserts new command here, do NOT remove/modify this line diff --git a/hphp/runtime/debugger/cmd/cmd_extended.cpp b/hphp/runtime/debugger/cmd/cmd_extended.cpp index 30066bb21..433f6f081 100644 --- a/hphp/runtime/debugger/cmd/cmd_extended.cpp +++ b/hphp/runtime/debugger/cmd/cmd_extended.cpp @@ -177,12 +177,11 @@ bool CmdExtended::onServer(DebuggerProxy &proxy) { /////////////////////////////////////////////////////////////////////////////// const ExtendedCommandMap &CmdExtended::GetExtendedCommandMap() { - static ExtendedCommandMap s_command_map; - if (s_command_map.empty()) { - // add one line for each command - s_command_map["ample"] = "CmdExample"; - s_command_map["tension"] = "CmdExtension"; - } + static ExtendedCommandMap s_command_map = { + { "ample" , "CmdExample" }, + { "tension" , "CmdExtension" }, + { "heaptrace", "CmdHeaptrace" } + }; return s_command_map; } @@ -196,7 +195,7 @@ DebuggerCommandPtr CmdExtended::CreateExtendedCommand(const std::string &cls) { // add one line for each command ELSE_IF_CMD(Example); ELSE_IF_CMD(Extension); - + ELSE_IF_CMD(Heaptrace); } if (ret) { diff --git a/hphp/runtime/debugger/cmd/cmd_heaptrace.cpp b/hphp/runtime/debugger/cmd/cmd_heaptrace.cpp new file mode 100644 index 000000000..cbc2658f0 --- /dev/null +++ b/hphp/runtime/debugger/cmd/cmd_heaptrace.cpp @@ -0,0 +1,196 @@ +/* + +----------------------------------------------------------------------+ + | HipHop for PHP | + +----------------------------------------------------------------------+ + | Copyright (c) 2010 Facebook, Inc. (http://www.facebook.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#include "hphp/runtime/debugger/cmd/cmd_heaptrace.h" + +#include "hphp/runtime/vm/unit.h" + +namespace HPHP { namespace Eval { +/////////////////////////////////////////////////////////////////////////////// + +void CmdHeaptrace::sendImpl(DebuggerThriftBuffer &thrift) { + DebuggerCommand::sendImpl(thrift); + thrift.write(m_accum.typesMap); + thrift.write(m_accum.adjacencyList); +} + +void CmdHeaptrace::recvImpl(DebuggerThriftBuffer &thrift) { + DebuggerCommand::recvImpl(thrift); + thrift.read(m_accum.typesMap); + thrift.read(m_accum.adjacencyList); +} + +void CmdHeaptrace::list(DebuggerClient &client) { +} + +void CmdHeaptrace::help(DebuggerClient &client) { + client.helpTitle("Heaptrace Command"); + client.helpCmds( + "[h]eaptrace", "dumps all currently reachable values", + "[h]eaptrace {filename}", "dumps heap to GraphViz graph file", + nullptr + ); + client.helpBody( + "This will print the locations and types of all reachable values " + "in the heap." + ); +} + +static const char *typeName(int8_t type) { + switch (type) { + case KindOfUninit: + return "uninit"; + case KindOfNull: + return "null"; + case KindOfBoolean: + return "boolean"; + case KindOfInt64: + return "integer"; + case KindOfDouble: + return "double"; + case KindOfStaticString: + return "string (static)"; + case KindOfString: + return "string"; + case KindOfArray: + return "array"; + case KindOfObject: + return "object"; + case KindOfRef: + return "reference"; + case KindOfIndirect: + return "indirect"; + default: + return "unknown"; + } +} + +void CmdHeaptrace::printHeap(DebuggerClient &client) { + for (const auto &pair : m_accum.typesMap) { + client.print(folly::stringPrintf("Found TV at %p with type %s (%u)", + (void *)pair.first, + typeName(pair.second), + pair.second)); + std::vector &adjList = m_accum.adjacencyList[pair.first]; + if (!adjList.empty()) { + std::string children = " -> found children: "; + bool first = true; + for (const int64_t &adjacent : adjList) { + if (!first) { + children += ", "; + } + children += folly::stringPrintf("%p", (void *)adjacent); + first = false; + } + client.print(children); + } + } +} + +void CmdHeaptrace::printGraphToFile(DebuggerClient &client, String filename) { + const char *name = filename->data(); + FILE *graphFile = fopen(name, "w"); + if (!graphFile) { + client.print("Could not open file!"); + return; + } + + fprintf(graphFile, "digraph {\n node [shape=box];\n"); + for (const auto &pair : m_accum.typesMap) { + void *ptr = (void *)pair.first; + std::string n = folly::stringPrintf( + " node%p [label=\"TV at %p\\ntype %s\"];\n", + ptr, + ptr, + typeName(pair.second) + ); + fprintf(graphFile, "%s", n.c_str()); + std::vector &adjList = m_accum.adjacencyList[pair.first]; + for (const int64_t adjacent : adjList) { + std::string e = folly::stringPrintf( + " node%p -> node%p;\n", + ptr, + (void *)adjacent + ); + fprintf(graphFile, "%s", e.c_str()); + } + } + fprintf(graphFile, "}\n"); + fclose(graphFile); + + client.print(folly::stringPrintf("Wrote heap graph to %s.", name)); +} + +void CmdHeaptrace::onClientImpl(DebuggerClient &client) { + if (DebuggerCommand::displayedHelp(client)) return; + + String file; + if (client.argCount() == 2) { + file = client.argValue(2); + } else if (client.argCount() != 1) { + help(client); + return; + } + + CmdHeaptracePtr cmd = client.xend(this); + + if (file.empty()) { + cmd->printHeap(client); + } else { + cmd->printGraphToFile(client, file); + } + +} + +bool CmdHeaptrace::onServer(DebuggerProxy &proxy) { + + // globals + std::vector roots; + CArrRef arr = g_vmContext->m_globalVarEnv->getDefinedVariables(); + arr->getChildren(roots); + + // static properties + for (AllClasses ac; !ac.empty();) { + Class *c = ac.popFront(); + c->getChildren(roots); + } + + // locals + int numFrames = proxy.getRealStackDepth(); + std::vector locs; + for (int i = 0; i < numFrames; ++i) { + locs.push_back(g_vmContext->getLocalDefinedVariables(i)); + } + for (CArrRef locArr : locs) { + locArr->getChildren(roots); + } + + Tracer::traceAll( + roots, + [](TypedValue *node, Accum &accum) { + accum.typesMap[(int64_t)node] = (int8_t)node->m_type; + }, + [](TypedValue *parent, TypedValue *child, Accum &accum) { + accum.adjacencyList[(int64_t)parent].push_back((int64_t)child); + }, + m_accum + ); + + return proxy.sendToClient(this); +} + +/////////////////////////////////////////////////////////////////////////////// +}} diff --git a/hphp/runtime/debugger/cmd/cmd_heaptrace.h b/hphp/runtime/debugger/cmd/cmd_heaptrace.h new file mode 100644 index 000000000..ac549c889 --- /dev/null +++ b/hphp/runtime/debugger/cmd/cmd_heaptrace.h @@ -0,0 +1,55 @@ +/* + +----------------------------------------------------------------------+ + | HipHop for PHP | + +----------------------------------------------------------------------+ + | Copyright (c) 2010 Facebook, Inc. (http://www.facebook.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef incl_HPHP_EVAL_DEBUGGER_CMD_HEAPTRACE_H_ +#define incl_HPHP_EVAL_DEBUGGER_CMD_HEAPTRACE_H_ + +#include +#include + +#include "hphp/runtime/debugger/cmd/cmd_extended.h" + +#include "hphp/runtime/base/memory/tracer.h" + +namespace HPHP { namespace Eval { +/////////////////////////////////////////////////////////////////////////////// + +DECLARE_BOOST_TYPES(CmdHeaptrace); +class CmdHeaptrace : public CmdExtended { +public: + virtual void list(DebuggerClient &client); + virtual void help(DebuggerClient &client); + virtual bool onServer(DebuggerProxy &proxy); + +protected: + virtual void onClientImpl(DebuggerClient &client); + virtual void sendImpl(DebuggerThriftBuffer &thrift); + virtual void recvImpl(DebuggerThriftBuffer &thrift); + +private: + void printHeap(DebuggerClient &client); + void printGraphToFile(DebuggerClient &client, String filename); + + struct Accum { + std::map typesMap; + std::map> adjacencyList; + } m_accum; +}; + +/////////////////////////////////////////////////////////////////////////////// +}} + +#endif // __HPHP_EVAL_DEBUGGER_CMD_HEAPTRACE_H__ diff --git a/hphp/runtime/vm/bytecode.cpp b/hphp/runtime/vm/bytecode.cpp index c95fa46eb..8e1b92672 100644 --- a/hphp/runtime/vm/bytecode.cpp +++ b/hphp/runtime/vm/bytecode.cpp @@ -64,6 +64,7 @@ #include "hphp/runtime/vm/type_profile.h" #include "hphp/runtime/base/server/source_root_info.h" #include "hphp/runtime/base/util/extended_logger.h" +#include "hphp/runtime/base/memory/tracer.h" #include "hphp/system/systemlib.h" #include "hphp/runtime/ext/ext_collections.h" diff --git a/hphp/runtime/vm/class.cpp b/hphp/runtime/vm/class.cpp index 3346c5b5a..0a3fa8f7b 100644 --- a/hphp/runtime/vm/class.cpp +++ b/hphp/runtime/vm/class.cpp @@ -2640,4 +2640,11 @@ void Class::setSPropData(TypedValue* sPropData) const { handleToRef(m_propSDataCache) = sPropData; } +void Class::getChildren(std::vector &out) { + for (Slot i = 0; i < m_staticProperties.size(); ++i) { + if (m_staticProperties[i].m_class != this) continue; + out.push_back(&m_staticProperties[i].m_val); + } +} + } // HPHP::VM diff --git a/hphp/runtime/vm/class.h b/hphp/runtime/vm/class.h index 740addd0e..ddcd28b8a 100644 --- a/hphp/runtime/vm/class.h +++ b/hphp/runtime/vm/class.h @@ -1027,6 +1027,9 @@ private: MethodToTraitListMap m_importMethToTraitMap; +public: + void getChildren(std::vector &out); + public: // used in Unit Class* m_nextClass; private: diff --git a/hphp/util/trace.h b/hphp/util/trace.h index 8a2dcdbc8..e795202ed 100644 --- a/hphp/util/trace.h +++ b/hphp/util/trace.h @@ -104,6 +104,8 @@ namespace Trace { TM(txOpBisectHigh) \ /* smart alloc */ \ TM(smartalloc) \ + /* Heap tracing */ \ + TM(heap) \ /* Temporary catetories, to save compilation time */ \ TM(tmp0) TM(tmp1) TM(tmp2) TM(tmp3) \ TM(tmp4) TM(tmp5) TM(tmp6) TM(tmp7) \