tools/profiler/ProfileEntry.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include <ostream>
     7 #include <sstream>
     8 #include "platform.h"
     9 #include "nsThreadUtils.h"
    10 #include "nsXULAppAPI.h"
    11 #include "jsapi.h"
    13 // JSON
    14 #include "JSStreamWriter.h"
    16 // Self
    17 #include "ProfileEntry.h"
    19 #if _MSC_VER
    20  #define snprintf _snprintf
    21 #endif
    23 ////////////////////////////////////////////////////////////////////////
    24 // BEGIN ProfileEntry
    26 ProfileEntry::ProfileEntry()
    27   : mTagData(nullptr)
    28   , mTagName(0)
    29 { }
    31 // aTagData must not need release (i.e. be a string from the text segment)
    32 ProfileEntry::ProfileEntry(char aTagName, const char *aTagData)
    33   : mTagData(aTagData)
    34   , mTagName(aTagName)
    35 { }
    37 ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker)
    38   : mTagMarker(aTagMarker)
    39   , mTagName(aTagName)
    40 { }
    42 ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr)
    43   : mTagPtr(aTagPtr)
    44   , mTagName(aTagName)
    45 { }
    47 ProfileEntry::ProfileEntry(char aTagName, float aTagFloat)
    48   : mTagFloat(aTagFloat)
    49   , mTagName(aTagName)
    50 { }
    52 ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset)
    53   : mTagOffset(aTagOffset)
    54   , mTagName(aTagName)
    55 { }
    57 ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress)
    58   : mTagAddress(aTagAddress)
    59   , mTagName(aTagName)
    60 { }
    62 ProfileEntry::ProfileEntry(char aTagName, int aTagLine)
    63   : mTagLine(aTagLine)
    64   , mTagName(aTagName)
    65 { }
    67 ProfileEntry::ProfileEntry(char aTagName, char aTagChar)
    68   : mTagChar(aTagChar)
    69   , mTagName(aTagName)
    70 { }
    72 bool ProfileEntry::is_ent_hint(char hintChar) {
    73   return mTagName == 'h' && mTagChar == hintChar;
    74 }
    76 bool ProfileEntry::is_ent_hint() {
    77   return mTagName == 'h';
    78 }
    80 bool ProfileEntry::is_ent(char tagChar) {
    81   return mTagName == tagChar;
    82 }
    84 void* ProfileEntry::get_tagPtr() {
    85   // No consistency checking.  Oh well.
    86   return mTagPtr;
    87 }
    89 void ProfileEntry::log()
    90 {
    91   // There is no compiler enforced mapping between tag chars
    92   // and union variant fields, so the following was derived
    93   // by looking through all the use points of TableTicker.cpp.
    94   //   mTagMarker (ProfilerMarker*) m
    95   //   mTagData   (const char*)  c,s
    96   //   mTagPtr    (void*)        d,l,L,B (immediate backtrace), S(start-of-stack)
    97   //   mTagLine   (int)          n,f
    98   //   mTagChar   (char)         h
    99   //   mTagFloat  (double)       r,t,p
   100   switch (mTagName) {
   101     case 'm':
   102       LOGF("%c \"%s\"", mTagName, mTagMarker->GetMarkerName()); break;
   103     case 'c': case 's':
   104       LOGF("%c \"%s\"", mTagName, mTagData); break;
   105     case 'd': case 'l': case 'L': case 'B': case 'S':
   106       LOGF("%c %p", mTagName, mTagPtr); break;
   107     case 'n': case 'f':
   108       LOGF("%c %d", mTagName, mTagLine); break;
   109     case 'h':
   110       LOGF("%c \'%c\'", mTagName, mTagChar); break;
   111     case 'r': case 't': case 'p':
   112       LOGF("%c %f", mTagName, mTagFloat); break;
   113     default:
   114       LOGF("'%c' unknown_tag", mTagName); break;
   115   }
   116 }
   118 std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry)
   119 {
   120   if (entry.mTagName == 'r' || entry.mTagName == 't') {
   121     stream << entry.mTagName << "-" << std::fixed << entry.mTagFloat << "\n";
   122   } else if (entry.mTagName == 'l' || entry.mTagName == 'L') {
   123     // Bug 739800 - Force l-tag addresses to have a "0x" prefix on all platforms
   124     // Additionally, stringstream seemed to be ignoring formatter flags.
   125     char tagBuff[1024];
   126     unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
   127     snprintf(tagBuff, 1024, "%c-%#llx\n", entry.mTagName, pc);
   128     stream << tagBuff;
   129   } else if (entry.mTagName == 'd') {
   130     // TODO implement 'd' tag for text profile
   131   } else {
   132     stream << entry.mTagName << "-" << entry.mTagData << "\n";
   133   }
   134   return stream;
   135 }
   137 // END ProfileEntry
   138 ////////////////////////////////////////////////////////////////////////
   141 ////////////////////////////////////////////////////////////////////////
   142 // BEGIN ThreadProfile
   144 #define DYNAMIC_MAX_STRING 512
   146 ThreadProfile::ThreadProfile(const char* aName, int aEntrySize,
   147                              PseudoStack *aStack, Thread::tid_t aThreadId,
   148                              PlatformData* aPlatform,
   149                              bool aIsMainThread, void *aStackTop)
   150   : mWritePos(0)
   151   , mLastFlushPos(0)
   152   , mReadPos(0)
   153   , mEntrySize(aEntrySize)
   154   , mPseudoStack(aStack)
   155   , mMutex("ThreadProfile::mMutex")
   156   , mName(strdup(aName))
   157   , mThreadId(aThreadId)
   158   , mIsMainThread(aIsMainThread)
   159   , mPlatformData(aPlatform)
   160   , mGeneration(0)
   161   , mPendingGenerationFlush(0)
   162   , mStackTop(aStackTop)
   163 {
   164   mEntries = new ProfileEntry[mEntrySize];
   165 }
   167 ThreadProfile::~ThreadProfile()
   168 {
   169   free(mName);
   170   delete[] mEntries;
   171 }
   173 void ThreadProfile::addTag(ProfileEntry aTag)
   174 {
   175   // Called from signal, call only reentrant functions
   176   mEntries[mWritePos] = aTag;
   177   mWritePos = mWritePos + 1;
   178   if (mWritePos >= mEntrySize) {
   179     mPendingGenerationFlush++;
   180     mWritePos = mWritePos % mEntrySize;
   181   }
   182   if (mWritePos == mReadPos) {
   183     // Keep one slot open
   184     mEntries[mReadPos] = ProfileEntry();
   185     mReadPos = (mReadPos + 1) % mEntrySize;
   186   }
   187   // we also need to move the flush pos to ensure we
   188   // do not pass it
   189   if (mWritePos == mLastFlushPos) {
   190     mLastFlushPos = (mLastFlushPos + 1) % mEntrySize;
   191   }
   192 }
   194 // flush the new entries
   195 void ThreadProfile::flush()
   196 {
   197   mLastFlushPos = mWritePos;
   198   mGeneration += mPendingGenerationFlush;
   199   mPendingGenerationFlush = 0;
   200 }
   202 // discards all of the entries since the last flush()
   203 // NOTE: that if mWritePos happens to wrap around past
   204 // mLastFlushPos we actually only discard mWritePos - mLastFlushPos entries
   205 //
   206 // r = mReadPos
   207 // w = mWritePos
   208 // f = mLastFlushPos
   209 //
   210 //     r          f    w
   211 // |-----------------------------|
   212 // |   abcdefghijklmnopq         | -> 'abcdefghijklmnopq'
   213 // |-----------------------------|
   214 //
   215 //
   216 // mWritePos and mReadPos have passed mLastFlushPos
   217 //                      f
   218 //                    w r
   219 // |-----------------------------|
   220 // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz|
   221 // |-----------------------------|
   222 //                       w
   223 //                       r
   224 // |-----------------------------|
   225 // |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -> ''
   226 // |-----------------------------|
   227 //
   228 //
   229 // mWritePos will end up the same as mReadPos
   230 //                r
   231 //              w f
   232 // |-----------------------------|
   233 // |ABCDEFGHIJKLMklmnopqrstuvwxyz|
   234 // |-----------------------------|
   235 //                r
   236 //                w
   237 // |-----------------------------|
   238 // |ABCDEFGHIJKLMklmnopqrstuvwxyz| -> ''
   239 // |-----------------------------|
   240 //
   241 //
   242 // mWritePos has moved past mReadPos
   243 //      w r       f
   244 // |-----------------------------|
   245 // |ABCDEFdefghijklmnopqrstuvwxyz|
   246 // |-----------------------------|
   247 //        r       w
   248 // |-----------------------------|
   249 // |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl'
   250 // |-----------------------------|
   252 void ThreadProfile::erase()
   253 {
   254   mWritePos = mLastFlushPos;
   255   mPendingGenerationFlush = 0;
   256 }
   258 char* ThreadProfile::processDynamicTag(int readPos,
   259                                        int* tagsConsumed, char* tagBuff)
   260 {
   261   int readAheadPos = (readPos + 1) % mEntrySize;
   262   int tagBuffPos = 0;
   264   // Read the string stored in mTagData until the null character is seen
   265   bool seenNullByte = false;
   266   while (readAheadPos != mLastFlushPos && !seenNullByte) {
   267     (*tagsConsumed)++;
   268     ProfileEntry readAheadEntry = mEntries[readAheadPos];
   269     for (size_t pos = 0; pos < sizeof(void*); pos++) {
   270       tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos];
   271       if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) {
   272         seenNullByte = true;
   273         break;
   274       }
   275       tagBuffPos++;
   276     }
   277     if (!seenNullByte)
   278       readAheadPos = (readAheadPos + 1) % mEntrySize;
   279   }
   280   return tagBuff;
   281 }
   283 void ThreadProfile::IterateTags(IterateTagsCallback aCallback)
   284 {
   285   MOZ_ASSERT(aCallback);
   287   int readPos = mReadPos;
   288   while (readPos != mLastFlushPos) {
   289     // Number of tag consumed
   290     int incBy = 1;
   291     const ProfileEntry& entry = mEntries[readPos];
   293     // Read ahead to the next tag, if it's a 'd' tag process it now
   294     const char* tagStringData = entry.mTagData;
   295     int readAheadPos = (readPos + 1) % mEntrySize;
   296     char tagBuff[DYNAMIC_MAX_STRING];
   297     // Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2
   298     tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
   300     if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
   301       tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
   302     }
   304     aCallback(entry, tagStringData);
   306     readPos = (readPos + incBy) % mEntrySize;
   307   }
   308 }
   310 void ThreadProfile::ToStreamAsJSON(std::ostream& stream)
   311 {
   312   JSStreamWriter b(stream);
   313   StreamJSObject(b);
   314 }
   316 void ThreadProfile::StreamJSObject(JSStreamWriter& b)
   317 {
   318   b.BeginObject();
   319     // Thread meta data
   320     if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
   321       // TODO Add the proper plugin name
   322       b.NameValue("name", "Plugin");
   323     } else {
   324       b.NameValue("name", mName);
   325     }
   326     b.NameValue("tid", static_cast<int>(mThreadId));
   328     b.Name("samples");
   329     b.BeginArray();
   331       bool sample = false;
   332       int readPos = mReadPos;
   333       while (readPos != mLastFlushPos) {
   334         // Number of tag consumed
   335         ProfileEntry entry = mEntries[readPos];
   337         switch (entry.mTagName) {
   338           case 'r':
   339             {
   340               if (sample) {
   341                 b.NameValue("responsiveness", entry.mTagFloat);
   342               }
   343             }
   344             break;
   345           case 'p':
   346             {
   347               if (sample) {
   348                 b.NameValue("power", entry.mTagFloat);
   349               }
   350             }
   351             break;
   352           case 'f':
   353             {
   354               if (sample) {
   355                 b.NameValue("frameNumber", entry.mTagLine);
   356               }
   357             }
   358             break;
   359           case 't':
   360             {
   361               if (sample) {
   362                 b.NameValue("time", entry.mTagFloat);
   363               }
   364             }
   365             break;
   366           case 's':
   367             {
   368               // end the previous sample if there was one
   369               if (sample) {
   370                 b.EndObject();
   371               }
   372               // begin the next sample
   373               b.BeginObject();
   375               sample = true;
   377               // Seek forward through the entire sample, looking for frames
   378               // this is an easier approach to reason about than adding more
   379               // control variables and cases to the loop that goes through the buffer once
   380               b.Name("frames");
   381               b.BeginArray();
   383                 b.BeginObject();
   384                   b.NameValue("location", "(root)");
   385                 b.EndObject();
   387                 int framePos = (readPos + 1) % mEntrySize;
   388                 ProfileEntry frame = mEntries[framePos];
   389                 while (framePos != mLastFlushPos && frame.mTagName != 's') {
   390                   int incBy = 1;
   391                   frame = mEntries[framePos];
   392                   // Read ahead to the next tag, if it's a 'd' tag process it now
   393                   const char* tagStringData = frame.mTagData;
   394                   int readAheadPos = (framePos + 1) % mEntrySize;
   395                   char tagBuff[DYNAMIC_MAX_STRING];
   396                   // Make sure the string is always null terminated if it fills up
   397                   // DYNAMIC_MAX_STRING-2
   398                   tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
   400                   if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
   401                     tagStringData = processDynamicTag(framePos, &incBy, tagBuff);
   402                   }
   404                   // Write one frame. It can have either
   405                   // 1. only location - 'l' containing a memory address
   406                   // 2. location and line number - 'c' followed by 'd's and an optional 'n'
   407                   if (frame.mTagName == 'l') {
   408                     b.BeginObject();
   409                       // Bug 753041
   410                       // We need a double cast here to tell GCC that we don't want to sign
   411                       // extend 32-bit addresses starting with 0xFXXXXXX.
   412                       unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr;
   413                       snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
   414                       b.NameValue("location", tagBuff);
   415                     b.EndObject();
   416                   } else if (frame.mTagName == 'c') {
   417                     b.BeginObject();
   418                       b.NameValue("location", tagStringData);
   419                       readAheadPos = (framePos + incBy) % mEntrySize;
   420                       if (readAheadPos != mLastFlushPos &&
   421                           mEntries[readAheadPos].mTagName == 'n') {
   422                         b.NameValue("line", mEntries[readAheadPos].mTagLine);
   423                         incBy++;
   424                       }
   425                     b.EndObject();
   426                   }
   427                   framePos = (framePos + incBy) % mEntrySize;
   428                 }
   429               b.EndArray();
   430             }
   431             break;
   432         }
   433         readPos = (readPos + 1) % mEntrySize;
   434       }
   435       if (sample) {
   436         b.EndObject();
   437       }
   438     b.EndArray();
   440     b.Name("markers");
   441     b.BeginArray();
   442       readPos = mReadPos;
   443       while (readPos != mLastFlushPos) {
   444         ProfileEntry entry = mEntries[readPos];
   445         if (entry.mTagName == 'm') {
   446            entry.getMarker()->StreamJSObject(b);
   447         }
   448         readPos = (readPos + 1) % mEntrySize;
   449       }
   450     b.EndArray();
   451   b.EndObject();
   452 }
   454 JSObject* ThreadProfile::ToJSObject(JSContext *aCx)
   455 {
   456   JS::RootedValue val(aCx);
   457   std::stringstream ss;
   458   {
   459     // Define a scope to prevent a moving GC during ~JSStreamWriter from
   460     // trashing the return value.
   461     JSStreamWriter b(ss);
   462     StreamJSObject(b);
   463     NS_ConvertUTF8toUTF16 js_string(nsDependentCString(ss.str().c_str()));
   464     JS_ParseJSON(aCx, static_cast<const jschar*>(js_string.get()), js_string.Length(), &val);
   465   }
   466   return &val.toObject();
   467 }
   469 PseudoStack* ThreadProfile::GetPseudoStack()
   470 {
   471   return mPseudoStack;
   472 }
   474 void ThreadProfile::BeginUnwind()
   475 {
   476   mMutex.Lock();
   477 }
   479 void ThreadProfile::EndUnwind()
   480 {
   481   mMutex.Unlock();
   482 }
   484 mozilla::Mutex* ThreadProfile::GetMutex()
   485 {
   486   return &mMutex;
   487 }
   489 void ThreadProfile::DuplicateLastSample() {
   490   // Scan the whole buffer (even unflushed parts)
   491   // Adding mEntrySize makes the result of the modulus positive
   492   // We search backwards from mWritePos-1 to mReadPos
   493   for (int readPos  = (mWritePos + mEntrySize - 1) % mEntrySize;
   494            readPos !=  (mReadPos + mEntrySize - 1) % mEntrySize;
   495            readPos  =   (readPos + mEntrySize - 1) % mEntrySize) {
   496     if (mEntries[readPos].mTagName == 's') {
   497       // Found the start of the last entry at position readPos
   498       int copyEndIdx = mWritePos;
   499       // Go through the whole entry and duplicate it
   500       for (;readPos != copyEndIdx; readPos = (readPos + 1) % mEntrySize) {
   501         switch (mEntries[readPos].mTagName) {
   502           // Copy with new time
   503           case 't':
   504             addTag(ProfileEntry('t', static_cast<float>((mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds())));
   505             break;
   506           // Don't copy markers
   507           case 'm':
   508             break;
   509           // Copy anything else we don't know about
   510           // L, B, S, c, s, d, l, f, h, r, t, p
   511           default:
   512             addTag(mEntries[readPos]);
   513             break;
   514         }
   515       }
   516       break;
   517     }
   518   }
   519 }
   521 std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile)
   522 {
   523   int readPos = profile.mReadPos;
   524   while (readPos != profile.mLastFlushPos) {
   525     stream << profile.mEntries[readPos];
   526     readPos = (readPos + 1) % profile.mEntrySize;
   527   }
   528   return stream;
   529 }
   531 // END ThreadProfile
   532 ////////////////////////////////////////////////////////////////////////

mercurial