Bug 801780 - Part 2: Explicitly call out long strings in about:memory. r=njn
authorJustin Lebar <justin.lebar@gmail.com>
Wed, 17 Oct 2012 10:44:31 -0400
changeset 110681 b803ce91fc0ebaf17d59c913d437a8fe08759fc7
parent 110680 6d4ffa88e7d3c0e7f02fd1c13f8630b4658269db
child 110682 80fffd499dca98186393793a74836147683958b5
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersnjn
bugs801780
milestone19.0a1
Bug 801780 - Part 2: Explicitly call out long strings in about:memory. r=njn
dom/base/nsWindowMemoryReporter.cpp
js/public/MemoryMetrics.h
js/src/jsmemorymetrics.cpp
js/xpconnect/src/XPCJSRuntime.cpp
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -232,17 +232,19 @@ CollectWindowReports(nsGlobalWindow *aWi
   REPORT("/layout/pres-contexts", windowSizes.mLayoutPresContext,
          "Memory used for the PresContext in the PresShell's frame "
          "within a window.");
   aWindowTotalSizes->mLayoutPresContext += windowSizes.mLayoutPresContext;
 
   // There are many different kinds of frames, but it is very likely
   // that only a few matter.  Implement a cutoff so we don't bloat
   // about:memory with many uninteresting entries.
-  static const size_t FRAME_SUNDRIES_THRESHOLD = 8192;
+  const size_t FRAME_SUNDRIES_THRESHOLD =
+    js::MemoryReportingSundriesThreshold();
+
   size_t frameSundriesSize = 0;
 #define FRAME_ID(classname)                                             \
   {                                                                     \
     size_t frameSize                                                    \
       = windowSizes.mArenaStats.FRAME_ID_STAT_FIELD(classname);         \
     if (frameSize < FRAME_SUNDRIES_THRESHOLD) {                         \
       frameSundriesSize += frameSize;                                   \
     } else {                                                            \
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -3,47 +3,93 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef js_MemoryMetrics_h
 #define js_MemoryMetrics_h
 
-/*
- * These declarations are not within jsapi.h because they are highly likely
- * to change in the future. Depend on them at your own risk.
- */
+// These declarations are not within jsapi.h because they are highly likely to
+// change in the future. Depend on them at your own risk.
 
 #include <string.h>
 
 #include "jsalloc.h"
 #include "jspubtd.h"
 
 #include "js/Utility.h"
 #include "js/Vector.h"
 
+namespace js {
+
+// In memory reporting, we have concept of "sundries", line items which are too
+// small to be worth reporting individually.  Under some circumstances, a memory
+// reporter gets tossed into the sundries bucket if it's smaller than
+// MemoryReportingSundriesThreshold() bytes.
+//
+// We need to define this value here, rather than in the code which actually
+// generates the memory reports, because HugeStringInfo uses this value.
+JS_FRIEND_API(size_t) MemoryReportingSundriesThreshold();
+
+} // namespace js
+
 namespace JS {
 
-/* Data for tracking analysis/inference memory usage. */
+// Data for tracking analysis/inference memory usage.
 struct TypeInferenceSizes
 {
+    TypeInferenceSizes()
+      : scripts(0)
+      , objects(0)
+      , tables(0)
+      , temporary(0)
+    {}
+
     size_t scripts;
     size_t objects;
     size_t tables;
     size_t temporary;
 
     void add(TypeInferenceSizes &sizes) {
         this->scripts   += sizes.scripts;
         this->objects   += sizes.objects;
         this->tables    += sizes.tables;
         this->temporary += sizes.temporary;
     }
 };
 
+// Holds data about a huge string (one which uses more HugeStringInfo::MinSize
+// bytes of memory), so we can report it individually.
+struct HugeStringInfo
+{
+    HugeStringInfo()
+      : length(0)
+      , size(0)
+    {
+        memset(&buffer, 0, sizeof(buffer));
+    }
+
+    // A string needs to take up this many bytes of storage before we consider
+    // it to be "huge".
+    static size_t MinSize()
+    {
+      return js::MemoryReportingSundriesThreshold();
+    }
+
+    // A string's size in memory is not necessarily equal to twice its length
+    // because the allocator and the JS engine both may round up.
+    size_t length;
+    size_t size;
+
+    // We record the first 32 chars of the escaped string here.  (We escape the
+    // string so we can use a char[] instead of a jschar[] here.
+    char buffer[32];
+};
+
 // These measurements relate directly to the JSRuntime, and not to
 // compartments within it.
 struct RuntimeSizes
 {
     RuntimeSizes()
       : object(0)
       , atomsTable(0)
       , contexts(0)
@@ -73,26 +119,95 @@ struct RuntimeSizes
     size_t gcMarker;
     size_t mathCache;
     size_t scriptFilenames;
     size_t scriptSources;
 };
 
 struct CompartmentStats
 {
-    CompartmentStats() {
-        memset(this, 0, sizeof(*this));
+    CompartmentStats()
+      : extra1(0)
+      , extra2(0)
+      , gcHeapArenaAdmin(0)
+      , gcHeapUnusedGcThings(0)
+      , gcHeapObjectsNonFunction(0)
+      , gcHeapObjectsFunction(0)
+      , gcHeapStrings(0)
+      , gcHeapShapesTree(0)
+      , gcHeapShapesDict(0)
+      , gcHeapShapesBase(0)
+      , gcHeapScripts(0)
+      , gcHeapTypeObjects(0)
+      , gcHeapIonCodes(0)
+#if JS_HAS_XML_SUPPORT
+      , gcHeapXML(0)
+#endif
+      , objectSlots(0)
+      , objectElements(0)
+      , objectMisc(0)
+      , objectPrivate(0)
+      , nonHugeStringChars(0)
+      , shapesExtraTreeTables(0)
+      , shapesExtraDictTables(0)
+      , shapesExtraTreeShapeKids(0)
+      , shapesCompartmentTables(0)
+      , scriptData(0)
+      , jaegerData(0)
+      , ionData(0)
+      , compartmentObject(0)
+      , crossCompartmentWrappers(0)
+      , regexpCompartment(0)
+      , debuggeesSet(0)
+    {}
+
+    CompartmentStats(const CompartmentStats &other)
+      : extra1(other.extra1)
+      , extra2(other.extra2)
+      , gcHeapArenaAdmin(other.gcHeapArenaAdmin)
+      , gcHeapUnusedGcThings(other.gcHeapUnusedGcThings)
+      , gcHeapObjectsNonFunction(other.gcHeapObjectsNonFunction)
+      , gcHeapObjectsFunction(other.gcHeapObjectsFunction)
+      , gcHeapStrings(other.gcHeapStrings)
+      , gcHeapShapesTree(other.gcHeapShapesTree)
+      , gcHeapShapesDict(other.gcHeapShapesDict)
+      , gcHeapShapesBase(other.gcHeapShapesBase)
+      , gcHeapScripts(other.gcHeapScripts)
+      , gcHeapTypeObjects(other.gcHeapTypeObjects)
+      , gcHeapIonCodes(other.gcHeapIonCodes)
+#if JS_HAS_XML_SUPPORT
+      , gcHeapXML(other.gcHeapXML)
+#endif
+      , objectSlots(other.objectSlots)
+      , objectElements(other.objectElements)
+      , objectMisc(other.objectMisc)
+      , objectPrivate(other.objectPrivate)
+      , nonHugeStringChars(other.nonHugeStringChars)
+      , shapesExtraTreeTables(other.shapesExtraTreeTables)
+      , shapesExtraDictTables(other.shapesExtraDictTables)
+      , shapesExtraTreeShapeKids(other.shapesExtraTreeShapeKids)
+      , shapesCompartmentTables(other.shapesCompartmentTables)
+      , scriptData(other.scriptData)
+      , jaegerData(other.jaegerData)
+      , ionData(other.ionData)
+      , compartmentObject(other.compartmentObject)
+      , crossCompartmentWrappers(other.crossCompartmentWrappers)
+      , regexpCompartment(other.regexpCompartment)
+      , debuggeesSet(other.debuggeesSet)
+      , typeInferenceSizes(other.typeInferenceSizes)
+    {
+      hugeStrings.append(other.hugeStrings);
     }
 
     // These fields can be used by embedders.
     void   *extra1;
     void   *extra2;
 
-    // If you add a new number, remember to update add() and maybe
-    // gcHeapThingsSize()!
+    // If you add a new number, remember to update the constructors, add(), and
+    // maybe gcHeapThingsSize()!
     size_t gcHeapArenaAdmin;
     size_t gcHeapUnusedGcThings;
 
     size_t gcHeapObjectsNonFunction;
     size_t gcHeapObjectsFunction;
     size_t gcHeapStrings;
     size_t gcHeapShapesTree;
     size_t gcHeapShapesDict;
@@ -103,33 +218,35 @@ struct CompartmentStats
 #if JS_HAS_XML_SUPPORT
     size_t gcHeapXML;
 #endif
 
     size_t objectSlots;
     size_t objectElements;
     size_t objectMisc;
     size_t objectPrivate;
-    size_t stringChars;
+    size_t nonHugeStringChars;
     size_t shapesExtraTreeTables;
     size_t shapesExtraDictTables;
     size_t shapesExtraTreeShapeKids;
     size_t shapesCompartmentTables;
     size_t scriptData;
     size_t jaegerData;
     size_t ionData;
     size_t compartmentObject;
     size_t crossCompartmentWrappers;
     size_t regexpCompartment;
     size_t debuggeesSet;
 
     TypeInferenceSizes typeInferenceSizes;
+    js::Vector<HugeStringInfo, 0, js::SystemAllocPolicy> hugeStrings;
 
     // Add cStats's numbers to this object's numbers.
-    void add(CompartmentStats &cStats) {
+    void add(CompartmentStats &cStats)
+    {
         #define ADD(x)  this->x += cStats.x
 
         ADD(gcHeapArenaAdmin);
         ADD(gcHeapUnusedGcThings);
 
         ADD(gcHeapObjectsNonFunction);
         ADD(gcHeapObjectsFunction);
         ADD(gcHeapStrings);
@@ -142,32 +259,33 @@ struct CompartmentStats
     #if JS_HAS_XML_SUPPORT
         ADD(gcHeapXML);
     #endif
 
         ADD(objectSlots);
         ADD(objectElements);
         ADD(objectMisc);
         ADD(objectPrivate);
-        ADD(stringChars);
+        ADD(nonHugeStringChars);
         ADD(shapesExtraTreeTables);
         ADD(shapesExtraDictTables);
         ADD(shapesExtraTreeShapeKids);
         ADD(shapesCompartmentTables);
         ADD(scriptData);
         ADD(jaegerData);
         ADD(ionData);
         ADD(compartmentObject);
         ADD(crossCompartmentWrappers);
         ADD(regexpCompartment);
         ADD(debuggeesSet);
 
         #undef ADD
 
         typeInferenceSizes.add(cStats.typeInferenceSizes);
+        hugeStrings.append(cStats.hugeStrings);
     }
 
     // The size of all the live things in the GC heap.
     size_t gcHeapThingsSize();
 };
 
 struct RuntimeStats
 {
@@ -239,17 +357,17 @@ public:
 };
 
 extern JS_PUBLIC_API(bool)
 CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats, ObjectPrivateVisitor *opv);
 
 extern JS_PUBLIC_API(int64_t)
 GetExplicitNonHeapForRuntime(JSRuntime *rt, JSMallocSizeOfFun mallocSizeOf);
 
-#endif /* JS_THREADSAFE */
+#endif // JS_THREADSAFE
 
 extern JS_PUBLIC_API(size_t)
 SystemCompartmentCount(const JSRuntime *rt);
 
 extern JS_PUBLIC_API(size_t)
 UserCompartmentCount(const JSRuntime *rt);
 
 } // namespace JS
--- a/js/src/jsmemorymetrics.cpp
+++ b/js/src/jsmemorymetrics.cpp
@@ -16,16 +16,25 @@
 #include "jsobj.h"
 #include "jsscope.h"
 #include "jsscript.h"
 
 #include "jsobjinlines.h"
 
 #include "ion/IonCode.h"
 
+namespace js {
+
+size_t MemoryReportingSundriesThreshold()
+{
+    return 8 * 1024;
+}
+
+} // namespace js
+
 #ifdef JS_THREADSAFE
 
 namespace JS {
 
 using namespace js;
 
 typedef HashSet<ScriptSource *, DefaultHasher<ScriptSource *>, SystemAllocPolicy> SourceSet;
 
@@ -151,17 +160,29 @@ StatsCellCallback(JSRuntime *rt, void *d
             }
         }
         break;
     }
     case JSTRACE_STRING:
     {
         JSString *str = static_cast<JSString *>(thing);
         cStats->gcHeapStrings += thingSize;
-        cStats->stringChars += str->sizeOfExcludingThis(rtStats->mallocSizeOf);
+
+        size_t strSize = str->sizeOfExcludingThis(rtStats->mallocSizeOf);
+
+        // If we can't grow hugeStrings, let's just call this string non-huge.
+        // We're probably about to OOM anyway.
+        if (strSize >= HugeStringInfo::MinSize() && cStats->hugeStrings.growBy(1)) {
+            HugeStringInfo &info = cStats->hugeStrings.back();
+            info.length = str->length();
+            info.size = str->sizeOfExcludingThis(rtStats->mallocSizeOf);
+            PutEscapedString(info.buffer, sizeof(info.buffer), &str->asLinear(), 0);
+        } else {
+          cStats->nonHugeStringChars += strSize;
+        }
         break;
     }
     case JSTRACE_SHAPE:
     {
         Shape *shape = static_cast<Shape*>(thing);
         size_t propTableSize, kidsSize;
         shape->sizeOfExcludingThis(rtStats->mallocSizeOf, &propTableSize, &kidsSize);
         if (shape->inDictionary()) {
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1385,17 +1385,17 @@ NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJS
     "and 'js-compartments-system' might not match the number of compartments "
     "listed under 'js' if a garbage collection occurs at an inopportune time, "
     "but such cases should be rare.")
 
 // The REPORT* macros do an unconditional report.  The CREPORT* macros are for
 // compartments;  they aggregate any entries smaller than SUNDRIES_THRESHOLD
 // into "gc-heap/sundries" and "other-sundries" entries for the compartment.
 
-static const size_t SUNDRIES_THRESHOLD = 8192;
+#define SUNDRIES_THRESHOLD js::MemoryReportingSundriesThreshold()
 
 #define REPORT(_path, _kind, _units, _amount, _desc)                          \
     do {                                                                      \
         nsresult rv;                                                          \
         rv = cb->Callback(EmptyCString(), _path, _kind, _units, _amount,      \
                           NS_LITERAL_CSTRING(_desc), closure);                \
         NS_ENSURE_SUCCESS(rv, rv);                                            \
     } while (0)
@@ -1410,27 +1410,40 @@ static const size_t SUNDRIES_THRESHOLD =
         rv = cb->Callback(EmptyCString(), _path,                              \
                           nsIMemoryReporter::KIND_NONHEAP,                    \
                           nsIMemoryReporter::UNITS_BYTES, amount,             \
                           NS_LITERAL_CSTRING(_desc), closure);                \
         NS_ENSURE_SUCCESS(rv, rv);                                            \
         gcTotal += amount;                                                    \
     } while (0)
 
+// Report compartment bytes.  Note that _descLiteral must be a literal string.
+//
 // Nb: all non-GC compartment reports are currently KIND_HEAP, and this macro
 // relies on that.
-#define CREPORT_BYTES(_path, _amount, _desc)                                  \
+#define CREPORT_BYTES(_path, _amount, _descLiteral)                           \
+    do {                                                                      \
+        /* Assign _descLiteral plus "" into a char* to prove that it's */     \
+        /* actually a literal. */                                             \
+        const char* unusedDesc = _descLiteral "";                             \
+        (void) unusedDesc;                                                    \
+        CREPORT_BYTES2(_path, _amount, NS_LITERAL_CSTRING(_descLiteral));     \
+    } while (0)
+
+// CREPORT_BYTES2 is identical to CREPORT_BYTES, except the description is a
+// nsCString instead of a literal string.
+#define CREPORT_BYTES2(_path, _amount, _desc)                                 \
     do {                                                                      \
         size_t amount = _amount;  /* evaluate _amount only once */            \
         if (amount >= SUNDRIES_THRESHOLD) {                                   \
             nsresult rv;                                                      \
             rv = cb->Callback(EmptyCString(), _path,                          \
                               nsIMemoryReporter::KIND_HEAP,                   \
                               nsIMemoryReporter::UNITS_BYTES, amount,         \
-                              NS_LITERAL_CSTRING(_desc), closure);            \
+                              _desc, closure);                                \
             NS_ENSURE_SUCCESS(rv, rv);                                        \
         } else {                                                              \
             otherSundries += amount;                                          \
         }                                                                     \
     } while (0)
 
 #define CREPORT_GC_BYTES(_path, _amount, _desc)                               \
     do {                                                                      \
@@ -1566,25 +1579,16 @@ ReportCompartmentStats(const JS::Compart
     // Note that we use cDOMPathPrefix here.  This is because we measure orphan
     // DOM nodes in the JS multi-reporter, but we want to report them in a
     // "dom" sub-tree rather than a "js" sub-tree.
     CREPORT_BYTES(cDOMPathPrefix + NS_LITERAL_CSTRING("orphan-nodes"),
                   cStats.objectPrivate,
                   "Memory used by orphan DOM nodes that are only reachable "
                   "from JavaScript objects.");
 
-    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("string-chars"),
-                  cStats.stringChars,
-                  "Memory allocated to hold string "
-                  "characters.  Sometimes more memory is allocated than "
-                  "necessary, to simplify string concatenation.  Each string "
-                  "also includes a header which is stored on the "
-                  "compartment's JavaScript heap;  that header is not counted "
-                  "here, but in 'gc-heap/strings' instead.");
-
     CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes-extra/tree-tables"),
                   cStats.shapesExtraTreeTables,
                   "Memory allocated for the property tables "
                   "that belong to shapes that are in a property tree.");
 
     CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes-extra/dict-tables"),
                   cStats.shapesExtraDictTables,
                   "Memory allocated for the property tables "
@@ -1646,16 +1650,52 @@ ReportCompartmentStats(const JS::Compart
                   "Memory used during type inference for compartment-wide "
                   "tables.");
 
     CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("analysis-temporary"),
                   cStats.typeInferenceSizes.temporary,
                   "Memory used during type inference and compilation to hold "
                   "transient analysis information.  Cleared on GC.");
 
+    CREPORT_BYTES2(cJSPathPrefix + NS_LITERAL_CSTRING("string-chars/non-huge"),
+                   cStats.nonHugeStringChars, nsPrintfCString(
+                   "Memory allocated to hold characters of strings whose "
+                   "characters take up less than than %d bytes of memory.\n\n"
+                   "Sometimes more memory is allocated than necessary, to "
+                   "simplify string concatenation.  Each string also includes a "
+                   "header which is stored on the compartment's JavaScript heap; "
+                   "that header is not counted here, but in 'gc-heap/strings' "
+                   "instead.",
+                   JS::HugeStringInfo::MinSize()));
+
+    for (size_t i = 0; i < cStats.hugeStrings.length(); i++) {
+        const JS::HugeStringInfo& info = cStats.hugeStrings[i];
+
+        nsDependentCString hugeString(info.buffer);
+
+        // Escape / to \/ before we put hugeString into the memory reporter
+        // path, because we don't want any forward slashes in the string to
+        // count as path separators.
+        nsCString escapedString(hugeString);
+        escapedString.ReplaceSubstring("/", "\\/");
+
+        CREPORT_BYTES2(
+            cJSPathPrefix +
+            nsPrintfCString("string-chars/huge/string(length=%d, \"%s...\")",
+                            info.length, escapedString.get()),
+            info.size,
+            nsPrintfCString("Memory allocated to hold characters of "
+            "a length-%d string which begins \"%s\".\n\n"
+            "Sometimes more memory is allocated than necessary, to simplify "
+            "string concatenation.  Each string also includes a header which is "
+            "stored on the compartment's JavaScript heap; that header is not "
+            "counted here, but in 'gc-heap/strings' instead.",
+            info.length, hugeString.get()));
+    }
+
     if (gcHeapSundries > 0) {
         // We deliberately don't use CREPORT_GC_BYTES here.
         REPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/sundries"),
                         gcHeapSundries,
                         "The sum of all the gc-heap "
                         "measurements that are too small to be worth showing "
                         "individually.");
     }