Bug 1339897 - Rename PROFILER_LABEL_PRINTF to PROFILER_LABEL_DYNAMIC and make it really cheap. r?ehsan, r?njn draft
authorMarkus Stange <mstange@themasta.com>
Wed, 22 Mar 2017 19:37:33 -0400
changeset 503177 c1e4a5d79bc67a4e7b40a3a92ecc394eedbe2ae8
parent 502328 9fb5e850ab7ab0b2b90640c604f66038407b411d
child 503187 e42cde200c4dfa1d744ea9f4799fdb799fb99b04
child 503319 2f5b8c8747ab81c7a46014bb6f932bdbc4ef5b59
push id50520
push userbmo:mstange@themasta.com
push dateWed, 22 Mar 2017 23:38:11 +0000
reviewersehsan, njn
bugs1339897
milestone55.0a1
Bug 1339897 - Rename PROFILER_LABEL_PRINTF to PROFILER_LABEL_DYNAMIC and make it really cheap. r?ehsan, r?njn Instead of copying and concatenating strings into an mDest buffer in SamplerStackFramePrintfRAII, require callers to keep the string buffer alive for the duration of the current scope, and store the pointer to the annotation string in the ProfileEntry. During stackwalking, concatenate the label and the annotation (separated by a space) and store the resulting string in the profile buffer. MozReview-Commit-ID: GEjcLrhhdvb
dom/base/nsJSEnvironment.cpp
dom/events/EventListenerManager.cpp
image/RasterImage.cpp
js/public/ProfilingStack.h
js/src/vm/GeckoProfiler.cpp
layout/base/GeckoRestyleManager.cpp
layout/base/PresShell.cpp
layout/base/RestyleTracker.cpp
layout/painting/FrameLayerBuilder.cpp
tools/profiler/core/platform.cpp
tools/profiler/public/GeckoProfiler.h
tools/profiler/public/PseudoStack.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1178,19 +1178,19 @@ FullGCTimerFired(nsITimer* aTimer, void*
 
 //static
 void
 nsJSContext::GarbageCollectNow(JS::gcreason::Reason aReason,
                                IsIncremental aIncremental,
                                IsShrinking aShrinking,
                                int64_t aSliceMillis)
 {
-  PROFILER_LABEL_PRINTF("nsJSContext", "GarbageCollectNow",
-                        js::ProfileEntry::Category::GC,
-                        "%s", JS::gcreason::ExplainReason(aReason));
+  PROFILER_LABEL_DYNAMIC("nsJSContext", "GarbageCollectNow",
+                         js::ProfileEntry::Category::GC,
+                         JS::gcreason::ExplainReason(aReason));
 
   MOZ_ASSERT_IF(aSliceMillis, aIncremental == IncrementalGC);
 
   KillGCTimer();
 
   // Reset sPendingLoadCount in case the timer that fired was a
   // timer we scheduled due to a normal GC timer firing while
   // documents were loading. If this happens we're waiting for a
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1270,20 +1270,19 @@ EventListenerManager::HandleEventInterna
             if (profiler_is_active()) {
 #ifdef MOZ_GECKO_PROFILER
               // Add a profiler label and a profiler marker for the actual
               // dispatch of the event.
               // This is a very hot code path, so we need to make sure not to
               // do this extra work when we're not profiling.
               nsAutoString typeStr;
               (*aDOMEvent)->GetType(typeStr);
-              PROFILER_LABEL_PRINTF("EventListenerManager", "HandleEventInternal",
-                                    js::ProfileEntry::Category::EVENTS,
-                                    "%s",
-                                    NS_LossyConvertUTF16toASCII(typeStr).get());
+              PROFILER_LABEL_DYNAMIC("EventListenerManager", "HandleEventInternal",
+                                     js::ProfileEntry::Category::EVENTS,
+                                     NS_LossyConvertUTF16toASCII(typeStr));
               TimeStamp startTime = TimeStamp::Now();
 
               rv = HandleEventSubType(listener, *aDOMEvent, aCurrentTarget);
 
               TimeStamp endTime = TimeStamp::Now();
               uint16_t phase;
               (*aDOMEvent)->GetEventPhase(&phase);
               PROFILER_MARKER_PAYLOAD("DOMEvent",
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -1127,29 +1127,29 @@ RasterImage::RequestDecodeForSizeInterna
 
 static bool
 LaunchDecodingTask(IDecodingTask* aTask,
                    RasterImage* aImage,
                    uint32_t aFlags,
                    bool aHaveSourceData)
 {
   if (aHaveSourceData) {
+    nsCString uri(aImage->GetURIString());
+
     // If we have all the data, we can sync decode if requested.
     if (aFlags & imgIContainer::FLAG_SYNC_DECODE) {
-      PROFILER_LABEL_PRINTF("DecodePool", "SyncRunIfPossible",
-        js::ProfileEntry::Category::GRAPHICS,
-        "%s", aImage->GetURIString().get());
+      PROFILER_LABEL_DYNAMIC("DecodePool", "SyncRunIfPossible",
+        js::ProfileEntry::Category::GRAPHICS, uri.get());
       DecodePool::Singleton()->SyncRunIfPossible(aTask);
       return true;
     }
 
     if (aFlags & imgIContainer::FLAG_SYNC_DECODE_IF_FAST) {
-      PROFILER_LABEL_PRINTF("DecodePool", "SyncRunIfPreferred",
-        js::ProfileEntry::Category::GRAPHICS,
-        "%s", aImage->GetURIString().get());
+      PROFILER_LABEL_DYNAMIC("DecodePool", "SyncRunIfPreferred",
+        js::ProfileEntry::Category::GRAPHICS, uri.get());
       return DecodePool::Singleton()->SyncRunIfPreferred(aTask);
     }
   }
 
   // Perform an async decode. We also take this path if we don't have all the
   // source data yet, since sync decoding is impossible in that situation.
   DecodePool::Singleton()->AsyncRun(aTask);
   return false;
--- a/js/public/ProfilingStack.h
+++ b/js/public/ProfilingStack.h
@@ -31,19 +31,24 @@ class ProfileEntry
     //    entry[size] = ...;
     //    size++;
     //
     // If the size modification were somehow reordered before the stores, then
     // if a sample were taken it would be examining bogus information.
     //
     // A ProfileEntry represents both a C++ profile entry and a JS one.
 
-    // Descriptive string of this entry.
+    // Descriptive string of this entry. Can be a static string or a dynamic
+    // string. If it's a dynamic string (which will need to be copied during
+    // sampling), then isCopyLabel() needs to return true.
     const char * volatile string;
 
+    // An additional descriptive string of this entry. Can be null.
+    const char * volatile dynamicString;
+
     // Stack pointer for non-JS entries, the script pointer otherwise.
     void * volatile spOrScript;
 
     // Line number for non-JS entries, the bytecode offset otherwise.
     int32_t volatile lineOrPcOffset;
 
     // General purpose storage describing this frame.
     uint32_t volatile flags_;
@@ -52,18 +57,18 @@ class ProfileEntry
     // These traits are bit masks. Make sure they're powers of 2.
     enum Flags : uint32_t {
         // Indicate whether a profile entry represents a CPP frame. If not set,
         // a JS frame is assumed by default. You're not allowed to publicly
         // change the frame type. Instead, initialize the ProfileEntry as either
         // a JS or CPP frame with `initJsFrame` or `initCppFrame` respectively.
         IS_CPP_ENTRY = 0x01,
 
-        // Indicate that copying the frame label is not necessary when taking a
-        // sample of the pseudostack.
+        // Indicates that the label string is not a static string and needs to
+        // be copied during sampling.
         FRAME_LABEL_COPY = 0x02,
 
         // This ProfileEntry is a dummy entry indicating the start of a run
         // of JS pseudostack entries.
         BEGIN_PSEUDO_JS = 0x04,
 
         // This flag is used to indicate that an interpreter JS entry has OSR-ed
         // into baseline.
@@ -103,16 +108,19 @@ class ProfileEntry
     bool isCpp() const volatile { return hasFlag(IS_CPP_ENTRY); }
     bool isJs() const volatile { return !isCpp(); }
 
     bool isCopyLabel() const volatile { return hasFlag(FRAME_LABEL_COPY); }
 
     void setLabel(const char* aString) volatile { string = aString; }
     const char* label() const volatile { return string; }
 
+    void setDynamicString(const char* aDynamicString) volatile { dynamicString = aDynamicString; }
+    const char* getDynamicString() const volatile { return dynamicString; }
+
     void initJsFrame(JSScript* aScript, jsbytecode* aPc) volatile {
         flags_ = 0;
         spOrScript = aScript;
         setPC(aPc);
     }
     void initCppFrame(void* aSp, uint32_t aLine) volatile {
         flags_ = IS_CPP_ENTRY;
         spOrScript = aSp;
--- a/js/src/vm/GeckoProfiler.cpp
+++ b/js/src/vm/GeckoProfiler.cpp
@@ -284,16 +284,17 @@ GeckoProfiler::push(const char* string, 
             MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
         }
         else {
             entry.initJsFrame(script, pc);
             MOZ_ASSERT(entry.flags() == 0);
         }
 
         entry.setLabel(string);
+        entry.setDynamicString(nullptr);
         entry.setCategory(category);
 
         // Track if mLabel needs a copy.
         if (copy)
             entry.setFlag(js::ProfileEntry::FRAME_LABEL_COPY);
         else
             entry.unsetFlag(js::ProfileEntry::FRAME_LABEL_COPY);
     }
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -3043,26 +3043,24 @@ ElementRestyler::ComputeStyleChangeFor(n
                                        nsRestyleHint      aRestyleHint,
                                        const RestyleHintData& aRestyleHintData,
                                        nsTArray<ContextToClear>&
                                          aContextsToClear,
                                        nsTArray<RefPtr<nsStyleContext>>&
                                          aSwappedStructOwners)
 {
   nsIContent* content = aFrame->GetContent();
-  nsAutoCString localDescriptor;
+  std::string elemDesc;
   if (profiler_is_active() && content) {
-    std::string elemDesc = ToString(*content);
-    localDescriptor.Assign(elemDesc.c_str());
+    elemDesc = ToString(*content);
   }
 
-  PROFILER_LABEL_PRINTF("ElementRestyler", "ComputeStyleChangeFor",
-                        js::ProfileEntry::Category::CSS,
-                        content ? "Element: %s" : "%s",
-                        content ? localDescriptor.get() : "");
+  PROFILER_LABEL_DYNAMIC("ElementRestyler", "ComputeStyleChangeFor",
+                         js::ProfileEntry::Category::CSS,
+                         content ? elemDesc.c_str() : "<unknown>");
   if (aMinChange) {
     aChangeList->AppendChange(aFrame, content, aMinChange);
   }
 
   NS_ASSERTION(!aFrame->GetPrevContinuation(),
                "must start with the first continuation");
 
   // We want to start with this frame and walk all its next-in-flows,
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -4083,18 +4083,18 @@ PresShell::DoFlushPendingNotifications(m
     "Content",
     "ContentAndNotify",
     "Style",
     "InterruptibleLayout",
     "Layout",
     "Display"
   };
 
-  PROFILER_LABEL_PRINTF("PresShell", "Flush",
-    js::ProfileEntry::Category::GRAPHICS, "(FlushType::%s)",
+  PROFILER_LABEL_DYNAMIC("PresShell", "Flush",
+    js::ProfileEntry::Category::GRAPHICS,
     flushTypeNames[flushType]);
 #endif
 
 #ifdef ACCESSIBILITY
 #ifdef DEBUG
   nsAccessibilityService* accService = GetAccService();
   if (accService) {
     NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(),
@@ -6352,19 +6352,19 @@ PresShell::Paint(nsView*        aViewToP
                  uint32_t        aFlags)
 {
 #ifdef MOZ_GECKO_PROFILER
   nsIURI* uri = mDocument->GetDocumentURI();
   nsIDocument* contentRoot = GetPrimaryContentDocument();
   if (contentRoot) {
     uri = contentRoot->GetDocumentURI();
   }
-  PROFILER_LABEL_PRINTF("PresShell", "Paint",
-    js::ProfileEntry::Category::GRAPHICS, "(%s)",
-    uri ? uri->GetSpecOrDefault().get() : "N/A");
+  nsCString uriString = uri ? uri->GetSpecOrDefault() : NS_LITERAL_CSTRING("N/A");
+  PROFILER_LABEL_DYNAMIC("PresShell", "Paint",
+    js::ProfileEntry::Category::GRAPHICS, Move(uriString));
 #endif
 
   Maybe<js::AutoAssertNoContentJS> nojs;
 
   // On Android, Flash can call into content JS during painting, so we can't
   // assert there. However, we don't rely on this assertion on Android because
   // we don't paint while JS is running.
 #if !defined(MOZ_WIDGET_ANDROID)
@@ -9171,19 +9171,19 @@ PresShell::DoReflow(nsIFrame* target, bo
   nsIFrame *parent = nsLayoutUtils::GetCrossDocParentFrame(target);
   while (parent) {
     nsSVGEffects::InvalidateDirectRenderingObservers(parent);
     parent = nsLayoutUtils::GetCrossDocParentFrame(parent);
   }
 
 #ifdef MOZ_GECKO_PROFILER
   nsIURI* uri = mDocument->GetDocumentURI();
-  PROFILER_LABEL_PRINTF("PresShell", "DoReflow",
-    js::ProfileEntry::Category::GRAPHICS, "(%s)",
-    uri ? uri->GetSpecOrDefault().get() : "N/A");
+  nsCString uriString = uri ? uri->GetSpecOrDefault() : NS_LITERAL_CSTRING("N/A");
+  PROFILER_LABEL_DYNAMIC("PresShell", "DoReflow",
+    js::ProfileEntry::Category::GRAPHICS, Move(uriString));
 #endif
 
   nsDocShell* docShell = static_cast<nsDocShell*>(GetPresContext()->GetDocShell());
   RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
   bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
 
   if (isTimelineRecording) {
     timelines->AddMarkerForDocShell(docShell, "Reflow", MarkerTracingType::START);
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -109,18 +109,18 @@ RestyleTracker::DoProcessRestyles()
 {
   nsAutoCString docURL("N/A");
   if (profiler_is_active()) {
     nsIURI *uri = Document()->GetDocumentURI();
     if (uri) {
       docURL = uri->GetSpecOrDefault();
     }
   }
-  PROFILER_LABEL_PRINTF("RestyleTracker", "ProcessRestyles",
-                        js::ProfileEntry::Category::CSS, "(%s)", docURL.get());
+  PROFILER_LABEL_DYNAMIC("RestyleTracker", "ProcessRestyles",
+                         js::ProfileEntry::Category::CSS, docURL.get());
 
   nsDocShell* docShell = static_cast<nsDocShell*>(mRestyleManager->PresContext()->GetDocShell());
   RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
   bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
 
   // Create a AnimationsWithDestroyedFrame during restyling process to
   // stop animations and transitions on elements that have no frame at the end
   // of the restyling process.
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -6002,18 +6002,18 @@ FrameLayerBuilder::PaintItems(nsTArray<C
   for (uint32_t i = 0; i < aItems.Length(); ++i) {
     ClippedDisplayItem* cdi = &aItems[i];
 
     nsRect paintRect = cdi->mItem->GetVisibleRect().Intersect(boundRect);
     if (paintRect.IsEmpty())
       continue;
 
 #ifdef MOZ_DUMP_PAINTING
-    PROFILER_LABEL_PRINTF("DisplayList", "Draw",
-      js::ProfileEntry::Category::GRAPHICS, "%s", cdi->mItem->Name());
+    PROFILER_LABEL_DYNAMIC("DisplayList", "Draw",
+      js::ProfileEntry::Category::GRAPHICS, cdi->mItem->Name());
 #else
     PROFILER_LABEL("DisplayList", "Draw",
       js::ProfileEntry::Category::GRAPHICS);
 #endif
 
     // If the new desired clip state is different from the current state,
     // update the clip.
     const DisplayItemClip* clip = &cdi->mItem->GetClip();
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -406,33 +406,44 @@ AddDynamicCodeLocationTag(ProfileBuffer*
     memcpy(text, &aStr[j], len);
     j += sizeof(void*) / sizeof(char);
 
     // Cast to *((void**) to pass the text data to a void*.
     aBuffer->addTag(ProfileBufferEntry::EmbeddedString(*((void**)(&text[0]))));
   }
 }
 
+static const int SAMPLER_MAX_STRING_LENGTH = 128;
+
 static void
 AddPseudoEntry(ProfileBuffer* aBuffer, volatile js::ProfileEntry& entry,
                PseudoStack* stack, void* lastpc)
 {
   // Pseudo-frames with the BEGIN_PSEUDO_JS flag are just annotations and
   // should not be recorded in the profile.
   if (entry.hasFlag(js::ProfileEntry::BEGIN_PSEUDO_JS)) {
     return;
   }
 
   int lineno = -1;
 
   // First entry has kind CodeLocation. Check for magic pointer bit 1 to
   // indicate copy.
   const char* sampleLabel = entry.label();
-
-  if (entry.isCopyLabel()) {
+  const char* dynamicString = entry.getDynamicString();
+  char combinedStringBuffer[SAMPLER_MAX_STRING_LENGTH];
+
+  if (entry.isCopyLabel() || dynamicString) {
+    if (dynamicString) {
+      int bytesWritten =
+        SprintfLiteral(combinedStringBuffer, "%s %s", sampleLabel, dynamicString);
+      if (bytesWritten > 0) {
+        sampleLabel = combinedStringBuffer;
+      }
+    }
     // Store the string using 1 or more EmbeddedString tags.
     // That will happen to the preceding tag.
     AddDynamicCodeLocationTag(aBuffer, sampleLabel);
     if (entry.isJs()) {
       JSScript* script = entry.script();
       if (script) {
         if (!entry.pc()) {
           // The JIT only allows the top-most entry to have a nullptr pc.
@@ -2862,21 +2873,38 @@ profiler_get_backtrace_noalloc(char *out
   if (!pseudoStack) {
     return;
   }
 
   volatile js::ProfileEntry *pseudoFrames = pseudoStack->mStack;
   uint32_t pseudoCount = pseudoStack->stackSize();
 
   for (uint32_t i = 0; i < pseudoCount; i++) {
-    size_t len = strlen(pseudoFrames[i].label());
-    if (output + len >= bound)
-      break;
-    strcpy(output, pseudoFrames[i].label());
-    output += len;
+    const char* label = pseudoFrames[i].label();
+    const char* dynamicString = pseudoFrames[i].getDynamicString();
+    size_t labelLength = strlen(label);
+    if (dynamicString) {
+      // Put the label, a space, and the dynamic string into output.
+      size_t dynamicStringLength = strlen(dynamicString);
+      if (output + labelLength + 1 + dynamicStringLength >= bound) {
+        break;
+      }
+      strcpy(output, label);
+      output += labelLength;
+      *output++ = ' ';
+      strcpy(output, dynamicString);
+      output += dynamicStringLength;
+    } else {
+      // Only put the label into output.
+      if (output + labelLength >= bound) {
+        break;
+      }
+      strcpy(output, label);
+      output += labelLength;
+    }
     *output++ = '\0';
     *output = '\0';
   }
 }
 
 static void
 locked_profiler_add_marker(PS::LockRef aLock, const char* aMarker,
                            ProfilerMarkerPayload* aPayload)
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -22,16 +22,17 @@
 
 #include "MainThreadUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "js/TypeDecls.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Vector.h"
+#include "nsString.h"
 
 namespace mozilla {
 class TimeStamp;
 
 namespace dom {
 class Promise;
 } // namespace dom
 
@@ -61,30 +62,44 @@ using UniqueProfilerBacktrace =
 
 // Use these for functions below that must be visible whether the profiler is
 // enabled or not. When the profiler is disabled they are static inline
 // functions (with a simple return value if they are non-void) that should be
 // optimized away during compilation.
 #define PROFILER_FUNC(decl, rv)  static inline decl { return rv; }
 #define PROFILER_FUNC_VOID(decl) static inline void decl {}
 
-// Insert a RAII in this scope to active a pseudo label. Any samples collected
-// in this scope will contain this annotation. For dynamic strings use
-// PROFILER_LABEL_PRINTF. Arguments must be string literals.
+// Insert an RAII object in this scope to enter a pseudo stack frame. Any
+// samples collected in this scope will contain this label in their pseudo
+// stack. The name_space and info arguments must be string literals.
+// Use PROFILER_LABEL_DYNAMIC if you want to add additional / dynamic
+// information to the pseudo stack frame.
 #define PROFILER_LABEL(name_space, info, category) do {} while (0)
 
 // Similar to PROFILER_LABEL, PROFILER_LABEL_FUNC will push/pop the enclosing
 // functon name as the pseudostack label.
 #define PROFILER_LABEL_FUNC(category) do {} while (0)
 
-// Format a dynamic string as a pseudo label. These labels will a considerable
-// storage size in the circular buffer compared to regular labels. This function
-// can be used to annotate custom information such as URL for the resource being
-// decoded or the size of the paint.
-#define PROFILER_LABEL_PRINTF(name_space, info, category, format, ...) do {} while (0)
+// Enter a pseudo stack frame in this scope and associate it with an
+// additional string.
+// This macro generates an RAII object. This RAII object stores the str
+// pointer in a field; it does not copy the string. This means that the string
+// you pass to this macro needs to live at least until the end of the current
+// scope.
+// If the profiler samples the current thread and walks the pseudo stack while
+// this RAII object is on the stack, it will copy the supplied string into the
+// profile buffer. So there's one string copy operation, and it happens at
+// sample time.
+// Compare this to the plain PROFILER_LABEL macro, which only accepts literal
+// strings: When the pseudo stack frames generated by PROFILER_LABEL are
+// sampled, no string copy needs to be made because the profile buffer can
+// just store the raw pointers to the literal strings. Consequently,
+// PROFILER_LABEL frames take up considerably less space in the profile buffer
+// than PROFILER_LABEL_DYNAMIC frames.
+#define PROFILER_LABEL_DYNAMIC(name_space, info, category, str) do {} while (0)
 
 // Insert a marker in the profile timeline. This is useful to delimit something
 // important happening such as the first paint. Unlike profiler_label that are
 // only recorded if a sample is collected while it is active, marker will always
 // be collected.
 #define PROFILER_MARKER(info) do {} while (0)
 #define PROFILER_MARKER_PAYLOAD(info, payload) do { mozilla::UniquePtr<ProfilerMarkerPayload> payloadDeletor(payload); } while (0)
 
@@ -95,17 +110,17 @@ using UniqueProfilerBacktrace =
 
 // we want the class and function name but can't easily get that using preprocessor macros
 // __func__ doesn't have the class name and __PRETTY_FUNCTION__ has the parameters
 
 #define PROFILER_LABEL(name_space, info, category) MOZ_PLATFORM_TRACING(name_space "::" info) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__)
 
 #define PROFILER_LABEL_FUNC(category) MOZ_PLATFORM_TRACING(SAMPLE_FUNCTION_NAME) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(SAMPLE_FUNCTION_NAME, category, __LINE__)
 
-#define PROFILER_LABEL_PRINTF(name_space, info, category, ...) MOZ_PLATFORM_TRACING(name_space "::" info) mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__, __VA_ARGS__)
+#define PROFILER_LABEL_DYNAMIC(name_space, info, category, str) MOZ_PLATFORM_TRACING(name_space "::" info) mozilla::SamplerStackFrameDynamicRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, category, __LINE__, str)
 
 #define PROFILER_MARKER(info) profiler_add_marker(info)
 #define PROFILER_MARKER_PAYLOAD(info, payload) profiler_add_marker(info, payload)
 
 #endif  // defined(MOZ_GECKO_PROFILER)
 
 // These functions are defined whether the profiler is enabled or not.
 
@@ -338,27 +353,28 @@ extern ProfilerState* gPS;
 # endif
 #endif
 
 // Returns a handle to pass on exit. This can check that we are popping the
 // correct callstack. Operates the same whether the profiler is active or not.
 static inline void*
 profiler_call_enter(const char* aInfo,
                     js::ProfileEntry::Category aCategory,
-                    void *aFrameAddress, bool aCopy, uint32_t line)
+                    void *aFrameAddress, bool aCopy, uint32_t line,
+                    const char* aAnnotationString = nullptr)
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(gPS);
 
   PseudoStack* stack = tlsPseudoStack.get();
   if (!stack) {
     return stack;
   }
-  stack->push(aInfo, aCategory, aFrameAddress, aCopy, line);
+  stack->push(aInfo, aCategory, aFrameAddress, aCopy, line, aAnnotationString);
 
   // The handle is meant to support future changes but for now it is simply
   // used to avoid having to call tlsPseudoStack.get() in profiler_call_exit().
   return stack;
 }
 
 static inline void
 profiler_call_exit(void* aHandle)
@@ -472,45 +488,51 @@ public:
   ~SamplerStackFrameRAII() {
     profiler_call_exit(mHandle);
   }
 private:
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   void* mHandle;
 };
 
-static const int SAMPLER_MAX_STRING = 128;
-class MOZ_RAII SamplerStackFramePrintfRAII {
+class MOZ_RAII SamplerStackFrameDynamicRAII {
 public:
-  // We only copy the strings at save time, so to take multiple parameters we'd
-  // need to copy them then.
-  SamplerStackFramePrintfRAII(const char *aInfo,
-    js::ProfileEntry::Category aCategory, uint32_t line, const char *aFormat, ...)
-    : mHandle(nullptr)
+  SamplerStackFrameDynamicRAII(const char* aInfo,
+    js::ProfileEntry::Category aCategory, uint32_t aLine,
+    const char* aDynamicString)
+  {
+    mHandle = Enter(aInfo, aCategory, aLine, aDynamicString);
+  }
+
+  // An alternative constructor that accepts an rvalue string and moves it
+  // into this object (without copying!).
+  SamplerStackFrameDynamicRAII(const char* aInfo,
+    js::ProfileEntry::Category aCategory, uint32_t aLine,
+    nsCString&& aDynamicString)
+    : mDynamicStorage(aDynamicString)
+  {
+    mHandle = Enter(aInfo, aCategory, aLine, mDynamicStorage.get());
+  }
+
+  ~SamplerStackFrameDynamicRAII() {
+    profiler_call_exit(mHandle);
+  }
+
+private:
+  void* Enter(const char* aInfo, js::ProfileEntry::Category aCategory,
+              uint32_t aLine, const char* aDynamicString)
   {
     if (profiler_is_active_and_not_in_privacy_mode()) {
-      va_list args;
-      va_start(args, aFormat);
-      char buff[SAMPLER_MAX_STRING];
-
-      // We have to use separate printfs because we're using the vargs.
-      VsprintfLiteral(buff, aFormat, args);
-      SprintfLiteral(mDest, "%s %s", aInfo, buff);
-
-      mHandle = profiler_call_enter(mDest, aCategory, this, true, line);
-      va_end(args);
+      return profiler_call_enter(aInfo, aCategory, this, true, aLine, aDynamicString);
     } else {
-      mHandle = profiler_call_enter(aInfo, aCategory, this, false, line);
+      return profiler_call_enter(aInfo, aCategory, this, false, aLine);
     }
   }
-  ~SamplerStackFramePrintfRAII() {
-    profiler_call_exit(mHandle);
-  }
-private:
-  char mDest[SAMPLER_MAX_STRING];
+
+  nsCString mDynamicStorage;
   void* mHandle;
 };
 
 } // namespace mozilla
 
 inline PseudoStack*
 profiler_get_pseudo_stack(void)
 {
--- a/tools/profiler/public/PseudoStack.h
+++ b/tools/profiler/public/PseudoStack.h
@@ -244,33 +244,35 @@ public:
     // Unless the profiled thread was in the middle of changing the list when
     // we interrupted it - in that case, accessList() will return null.
     return mPendingMarkers.accessList();
   }
 
   void push(const char* aName, js::ProfileEntry::Category aCategory,
             uint32_t line)
   {
-    push(aName, aCategory, nullptr, false, line);
+    push(aName, aCategory, nullptr, false, line, nullptr);
   }
 
   void push(const char* aName, js::ProfileEntry::Category aCategory,
-            void* aStackAddress, bool aCopy, uint32_t line)
+            void* aStackAddress, bool aCopy, uint32_t line,
+            const char* aDynamicString)
   {
     if (size_t(mStackPointer) >= mozilla::ArrayLength(mStack)) {
       mStackPointer++;
       return;
     }
 
     volatile js::ProfileEntry& entry = mStack[mStackPointer];
 
     // Make sure we increment the pointer after the name has been written such
     // that mStack is always consistent.
     entry.initCppFrame(aStackAddress, line);
     entry.setLabel(aName);
+    entry.setDynamicString(aDynamicString);
     MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
     entry.setCategory(aCategory);
 
     // Track if mLabel needs a copy.
     if (aCopy) {
       entry.setFlag(js::ProfileEntry::FRAME_LABEL_COPY);
     } else {
       entry.unsetFlag(js::ProfileEntry::FRAME_LABEL_COPY);