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) \