LCOV - code coverage report
Current view: top level - toolkit/components/telemetry - ThreadHangStats.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 165 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 10 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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/. */
       5             : 
       6             : #include "mozilla/HangAnnotations.h"
       7             : #include "ThreadHangStats.h"
       8             : #include "nsITelemetry.h"
       9             : #include "HangReports.h"
      10             : #include "jsapi.h"
      11             : 
      12             : namespace {
      13             : 
      14             : using namespace mozilla;
      15             : using namespace mozilla::HangMonitor;
      16             : using namespace mozilla::Telemetry;
      17             : 
      18             : static JSObject*
      19           0 : CreateJSTimeHistogram(JSContext* cx, const Telemetry::TimeHistogram& time)
      20             : {
      21             :   /* Create JS representation of TimeHistogram,
      22             :      in the format of Chromium-style histograms. */
      23           0 :   JS::RootedObject ret(cx, JS_NewPlainObject(cx));
      24           0 :   if (!ret) {
      25           0 :     return nullptr;
      26             :   }
      27             : 
      28           0 :   if (!JS_DefineProperty(cx, ret, "min", time.GetBucketMin(0),
      29           0 :                          JSPROP_ENUMERATE) ||
      30           0 :       !JS_DefineProperty(cx, ret, "max",
      31           0 :                          time.GetBucketMax(ArrayLength(time) - 1),
      32           0 :                          JSPROP_ENUMERATE) ||
      33           0 :       !JS_DefineProperty(cx, ret, "histogram_type",
      34             :                          nsITelemetry::HISTOGRAM_EXPONENTIAL,
      35             :                          JSPROP_ENUMERATE)) {
      36           0 :     return nullptr;
      37             :   }
      38             :   // TODO: calculate "sum"
      39           0 :   if (!JS_DefineProperty(cx, ret, "sum", 0, JSPROP_ENUMERATE)) {
      40           0 :     return nullptr;
      41             :   }
      42             : 
      43             :   JS::RootedObject ranges(
      44           0 :     cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
      45             :   JS::RootedObject counts(
      46           0 :     cx, JS_NewArrayObject(cx, ArrayLength(time) + 1));
      47           0 :   if (!ranges || !counts) {
      48           0 :     return nullptr;
      49             :   }
      50             :   /* In a Chromium-style histogram, the first bucket is an "under" bucket
      51             :      that represents all values below the histogram's range. */
      52           0 :   if (!JS_DefineElement(cx, ranges, 0, time.GetBucketMin(0), JSPROP_ENUMERATE) ||
      53           0 :       !JS_DefineElement(cx, counts, 0, 0, JSPROP_ENUMERATE)) {
      54           0 :     return nullptr;
      55             :   }
      56           0 :   for (size_t i = 0; i < ArrayLength(time); i++) {
      57           0 :     if (!JS_DefineElement(cx, ranges, i + 1, time.GetBucketMax(i),
      58           0 :                           JSPROP_ENUMERATE) ||
      59           0 :         !JS_DefineElement(cx, counts, i + 1, time[i], JSPROP_ENUMERATE)) {
      60           0 :       return nullptr;
      61             :     }
      62             :   }
      63           0 :   if (!JS_DefineProperty(cx, ret, "ranges", ranges, JSPROP_ENUMERATE) ||
      64           0 :       !JS_DefineProperty(cx, ret, "counts", counts, JSPROP_ENUMERATE)) {
      65           0 :     return nullptr;
      66             :   }
      67           0 :   return ret;
      68             : }
      69             : 
      70             : static JSObject*
      71           0 : CreateJSHangStack(JSContext* cx, const Telemetry::HangStack& stack)
      72             : {
      73           0 :   JS::RootedObject ret(cx, JS_NewArrayObject(cx, stack.length()));
      74           0 :   if (!ret) {
      75           0 :     return nullptr;
      76             :   }
      77           0 :   for (size_t i = 0; i < stack.length(); i++) {
      78           0 :     JS::RootedString string(cx, JS_NewStringCopyZ(cx, stack[i]));
      79           0 :     if (!JS_DefineElement(cx, ret, i, string, JSPROP_ENUMERATE)) {
      80           0 :       return nullptr;
      81             :     }
      82             :   }
      83           0 :   return ret;
      84             : }
      85             : 
      86             : static void
      87           0 : CreateJSHangAnnotations(JSContext* cx, const HangAnnotationsVector& annotations,
      88             :                         JS::MutableHandleObject returnedObject)
      89             : {
      90           0 :   JS::RootedObject annotationsArray(cx, JS_NewArrayObject(cx, 0));
      91           0 :   if (!annotationsArray) {
      92           0 :     returnedObject.set(nullptr);
      93           0 :     return;
      94             :   }
      95             :   // We keep track of the annotations we reported in this hash set, so we can
      96             :   // discard duplicated ones.
      97           0 :   nsTHashtable<nsStringHashKey> reportedAnnotations;
      98           0 :   size_t annotationIndex = 0;
      99           0 :   for (const auto & curAnnotations : annotations) {
     100           0 :     JS::RootedObject jsAnnotation(cx, JS_NewPlainObject(cx));
     101           0 :     if (!jsAnnotation) {
     102           0 :       continue;
     103             :     }
     104             :     // Build a key to index the current annotations in our hash set.
     105           0 :     nsAutoString annotationsKey;
     106           0 :     nsresult rv = ComputeAnnotationsKey(curAnnotations, annotationsKey);
     107           0 :     if (NS_FAILED(rv)) {
     108           0 :       continue;
     109             :     }
     110             :     // Check if the annotations are in the set. If that's the case, don't double report.
     111           0 :     if (reportedAnnotations.GetEntry(annotationsKey)) {
     112           0 :       continue;
     113             :     }
     114             :     // If not, report them.
     115           0 :     reportedAnnotations.PutEntry(annotationsKey);
     116             :     UniquePtr<HangAnnotations::Enumerator> annotationsEnum =
     117           0 :       curAnnotations->GetEnumerator();
     118           0 :     if (!annotationsEnum) {
     119           0 :       continue;
     120             :     }
     121           0 :     nsAutoString key;
     122           0 :     nsAutoString value;
     123           0 :     while (annotationsEnum->Next(key, value)) {
     124           0 :       JS::RootedValue jsValue(cx);
     125           0 :       jsValue.setString(JS_NewUCStringCopyN(cx, value.get(), value.Length()));
     126           0 :       if (!JS_DefineUCProperty(cx, jsAnnotation, key.get(), key.Length(),
     127             :                                jsValue, JSPROP_ENUMERATE)) {
     128           0 :         returnedObject.set(nullptr);
     129           0 :         return;
     130             :       }
     131             :     }
     132           0 :     if (!JS_SetElement(cx, annotationsArray, annotationIndex, jsAnnotation)) {
     133           0 :       continue;
     134             :     }
     135           0 :     ++annotationIndex;
     136             :   }
     137             :   // Return the array using a |MutableHandleObject| to avoid triggering a false
     138             :   // positive rooting issue in the hazard analysis build.
     139           0 :   returnedObject.set(annotationsArray);
     140             : }
     141             : 
     142             : static JSObject*
     143           0 : CreateJSHangHistogram(JSContext* cx, const Telemetry::HangHistogram& hang)
     144             : {
     145           0 :   JS::RootedObject ret(cx, JS_NewPlainObject(cx));
     146           0 :   if (!ret) {
     147           0 :     return nullptr;
     148             :   }
     149             : 
     150           0 :   JS::RootedObject stack(cx, CreateJSHangStack(cx, hang.GetStack()));
     151           0 :   JS::RootedObject time(cx, CreateJSTimeHistogram(cx, hang));
     152           0 :   auto& hangAnnotations = hang.GetAnnotations();
     153           0 :   JS::RootedObject annotations(cx);
     154           0 :   CreateJSHangAnnotations(cx, hangAnnotations, &annotations);
     155             : 
     156           0 :   if (!stack ||
     157           0 :       !time ||
     158           0 :       !annotations ||
     159           0 :       !JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) ||
     160           0 :       !JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE) ||
     161           0 :       (!hangAnnotations.empty() && // <-- Only define annotations when nonempty
     162           0 :         !JS_DefineProperty(cx, ret, "annotations", annotations, JSPROP_ENUMERATE))) {
     163           0 :     return nullptr;
     164             :   }
     165             : 
     166           0 :   return ret;
     167             : }
     168             : 
     169             : } // namespace
     170             : 
     171             : namespace mozilla {
     172             : namespace Telemetry {
     173             : 
     174             : JSObject*
     175           0 : CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
     176             : {
     177           0 :   JS::RootedObject ret(cx, JS_NewPlainObject(cx));
     178           0 :   if (!ret) {
     179           0 :     return nullptr;
     180             :   }
     181           0 :   JS::RootedString name(cx, JS_NewStringCopyZ(cx, thread.GetName()));
     182           0 :   if (!name ||
     183           0 :       !JS_DefineProperty(cx, ret, "name", name, JSPROP_ENUMERATE)) {
     184           0 :     return nullptr;
     185             :   }
     186             : 
     187           0 :   JS::RootedObject activity(cx, CreateJSTimeHistogram(cx, thread.mActivity));
     188           0 :   if (!activity ||
     189           0 :       !JS_DefineProperty(cx, ret, "activity", activity, JSPROP_ENUMERATE)) {
     190           0 :     return nullptr;
     191             :   }
     192             : 
     193             :   // Process the hangs into a hangs object.
     194           0 :   JS::RootedObject hangs(cx, JS_NewArrayObject(cx, 0));
     195           0 :   if (!hangs) {
     196           0 :     return nullptr;
     197             :   }
     198           0 :   for (size_t i = 0; i < thread.mHangs.length(); i++) {
     199           0 :     JS::RootedObject obj(cx, CreateJSHangHistogram(cx, thread.mHangs[i]));
     200           0 :     if (!ret) {
     201           0 :       return nullptr;
     202             :     }
     203             : 
     204           0 :     JS::RootedString runnableName(cx, JS_NewStringCopyZ(cx, thread.mHangs[i].GetRunnableName()));
     205           0 :     if (!runnableName ||
     206           0 :         !JS_DefineProperty(cx, ret, "runnableName", runnableName, JSPROP_ENUMERATE)) {
     207           0 :       return nullptr;
     208             :     }
     209             : 
     210             :     // Check if we have a cached native stack index, and if we do record it.
     211           0 :     uint32_t index = thread.mHangs[i].GetNativeStackIndex();
     212           0 :     if (index != Telemetry::HangHistogram::NO_NATIVE_STACK_INDEX) {
     213           0 :       if (!JS_DefineProperty(cx, obj, "nativeStack", index, JSPROP_ENUMERATE)) {
     214           0 :         return nullptr;
     215             :       }
     216             :     }
     217             : 
     218           0 :     if (!JS_DefineElement(cx, hangs, i, obj, JSPROP_ENUMERATE)) {
     219           0 :       return nullptr;
     220             :     }
     221             :   }
     222           0 :   if (!JS_DefineProperty(cx, ret, "hangs", hangs, JSPROP_ENUMERATE)) {
     223           0 :     return nullptr;
     224             :   }
     225             : 
     226             :   // We should already have a CombinedStacks object on the ThreadHangStats, so
     227             :   // add that one.
     228           0 :   JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, thread.mCombinedStacks));
     229           0 :   if (!fullReportObj) {
     230           0 :     return nullptr;
     231             :   }
     232             : 
     233           0 :   if (!JS_DefineProperty(cx, ret, "nativeStacks", fullReportObj, JSPROP_ENUMERATE)) {
     234           0 :     return nullptr;
     235             :   }
     236             : 
     237           0 :   return ret;
     238             : }
     239             : 
     240             : void
     241           0 : TimeHistogram::Add(PRIntervalTime aTime)
     242             : {
     243           0 :   uint32_t timeMs = PR_IntervalToMilliseconds(aTime);
     244           0 :   size_t index = mozilla::FloorLog2(timeMs);
     245           0 :   operator[](index)++;
     246           0 : }
     247             : 
     248             : const char*
     249           0 : HangStack::InfallibleAppendViaBuffer(const char* aText, size_t aLength)
     250             : {
     251           0 :   MOZ_ASSERT(this->canAppendWithoutRealloc(1));
     252             :   // Include null-terminator in length count.
     253           0 :   MOZ_ASSERT(mBuffer.canAppendWithoutRealloc(aLength + 1));
     254             : 
     255           0 :   const char* const entry = mBuffer.end();
     256           0 :   mBuffer.infallibleAppend(aText, aLength);
     257           0 :   mBuffer.infallibleAppend('\0'); // Explicitly append null-terminator
     258           0 :   this->infallibleAppend(entry);
     259           0 :   return entry;
     260             : }
     261             : 
     262             : const char*
     263           0 : HangStack::AppendViaBuffer(const char* aText, size_t aLength)
     264             : {
     265           0 :   if (!this->reserve(this->length() + 1)) {
     266           0 :     return nullptr;
     267             :   }
     268             : 
     269             :   // Keep track of the previous buffer in case we need to adjust pointers later.
     270           0 :   const char* const prevStart = mBuffer.begin();
     271           0 :   const char* const prevEnd = mBuffer.end();
     272             : 
     273             :   // Include null-terminator in length count.
     274           0 :   if (!mBuffer.reserve(mBuffer.length() + aLength + 1)) {
     275           0 :     return nullptr;
     276             :   }
     277             : 
     278           0 :   if (prevStart != mBuffer.begin()) {
     279             :     // The buffer has moved; we have to adjust pointers in the stack.
     280           0 :     for (auto & entry : *this) {
     281           0 :       if (entry >= prevStart && entry < prevEnd) {
     282             :         // Move from old buffer to new buffer.
     283           0 :         entry += mBuffer.begin() - prevStart;
     284             :       }
     285             :     }
     286             :   }
     287             : 
     288           0 :   return InfallibleAppendViaBuffer(aText, aLength);
     289             : }
     290             : 
     291             : uint32_t
     292           0 : HangHistogram::GetHash(const HangStack& aStack)
     293             : {
     294           0 :   uint32_t hash = 0;
     295           0 :   for (const char* const* label = aStack.begin();
     296           0 :        label != aStack.end(); label++) {
     297             :     /* If the string is within our buffer, we need to hash its content.
     298             :        Otherwise, the string is statically allocated, and we only need
     299             :        to hash the pointer instead of the content. */
     300           0 :     if (aStack.IsInBuffer(*label)) {
     301           0 :       hash = AddToHash(hash, HashString(*label));
     302             :     } else {
     303           0 :       hash = AddToHash(hash, *label);
     304             :     }
     305             :   }
     306           0 :   return hash;
     307             : }
     308             : 
     309             : bool
     310           0 : HangHistogram::operator==(const HangHistogram& aOther) const
     311             : {
     312           0 :   if (mHash != aOther.mHash) {
     313           0 :     return false;
     314             :   }
     315           0 :   if (mStack.length() != aOther.mStack.length()) {
     316           0 :     return false;
     317             :   }
     318           0 :   return mStack == aOther.mStack;
     319             : }
     320             : 
     321             : } // namespace Telemetry
     322             : } // namespace mozilla

Generated by: LCOV version 1.13