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 110549 b803ce91fc0ebaf17d59c913d437a8fe08759fc7
parent 110548 6d4ffa88e7d3c0e7f02fd1c13f8630b4658269db
child 110550 80fffd499dca98186393793a74836147683958b5
push id23700
push userryanvm@gmail.com
push dateThu, 18 Oct 2012 02:10:26 +0000
treeherdermozilla-central@5142bbd4da12 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs801780
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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.");
     }