forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			750 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			750 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* 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/. */
 | 
						|
 | 
						|
#include "mozilla/telemetry/Stopwatch.h"
 | 
						|
 | 
						|
#include "TelemetryHistogram.h"
 | 
						|
#include "TelemetryUserInteraction.h"
 | 
						|
 | 
						|
#include "js/MapAndSet.h"
 | 
						|
#include "js/WeakMap.h"
 | 
						|
#include "mozilla/dom/ScriptSettings.h"
 | 
						|
#include "mozilla/BackgroundHangMonitor.h"
 | 
						|
#include "mozilla/ClearOnShutdown.h"
 | 
						|
#include "mozilla/HangAnnotations.h"
 | 
						|
#include "mozilla/ProfilerMarkers.h"
 | 
						|
#include "mozilla/DataMutex.h"
 | 
						|
#include "mozilla/TimeStamp.h"
 | 
						|
#include "nsHashKeys.h"
 | 
						|
#include "nsContentUtils.h"
 | 
						|
#include "nsPrintfCString.h"
 | 
						|
#include "nsQueryObject.h"
 | 
						|
#include "nsString.h"
 | 
						|
#include "xpcpublic.h"
 | 
						|
 | 
						|
using mozilla::DataMutex;
 | 
						|
using mozilla::dom::AutoJSAPI;
 | 
						|
 | 
						|
#define USER_INTERACTION_VALUE_MAX_LENGTH 50  // bytes
 | 
						|
 | 
						|
static inline nsQueryObject<nsISupports> do_QueryReflector(
 | 
						|
    JSObject* aReflector) {
 | 
						|
  // None of the types we query to are implemented by Window or Location.
 | 
						|
  nsCOMPtr<nsISupports> reflector = xpc::ReflectorToISupportsStatic(aReflector);
 | 
						|
  return do_QueryObject(reflector);
 | 
						|
}
 | 
						|
 | 
						|
static inline nsQueryObject<nsISupports> do_QueryReflector(
 | 
						|
    const JS::Value& aReflector) {
 | 
						|
  return do_QueryReflector(&aReflector.toObject());
 | 
						|
}
 | 
						|
 | 
						|
static void LogError(JSContext* aCx, const nsCString& aMessage) {
 | 
						|
  // This is a bit of a hack to report an error with the current JS caller's
 | 
						|
  // location. We create an AutoJSAPI object bound to the current caller
 | 
						|
  // global, report a JS error, and then let AutoJSAPI's destructor report the
 | 
						|
  // error.
 | 
						|
  //
 | 
						|
  // Unfortunately, there isn't currently a more straightforward way to do
 | 
						|
  // this from C++.
 | 
						|
  JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
 | 
						|
 | 
						|
  AutoJSAPI jsapi;
 | 
						|
  if (jsapi.Init(global)) {
 | 
						|
    JS_ReportErrorUTF8(jsapi.cx(), "%s", aMessage.get());
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
namespace mozilla::telemetry {
 | 
						|
 | 
						|
class Timer final : public mozilla::LinkedListElement<RefPtr<Timer>> {
 | 
						|
 public:
 | 
						|
  NS_INLINE_DECL_REFCOUNTING(Timer)
 | 
						|
 | 
						|
  Timer() = default;
 | 
						|
 | 
						|
  void Start(bool aInSeconds) {
 | 
						|
    mStartTime = TimeStamp::Now();
 | 
						|
    mInSeconds = aInSeconds;
 | 
						|
  }
 | 
						|
 | 
						|
  bool Started() { return !mStartTime.IsNull(); }
 | 
						|
 | 
						|
  uint32_t Elapsed() {
 | 
						|
    auto delta = TimeStamp::Now() - mStartTime;
 | 
						|
    return mInSeconds ? delta.ToSeconds() : delta.ToMilliseconds();
 | 
						|
  }
 | 
						|
 | 
						|
  TimeStamp& StartTime() { return mStartTime; }
 | 
						|
 | 
						|
  bool& InSeconds() { return mInSeconds; }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Note that these values will want to be read from the
 | 
						|
   * BackgroundHangAnnotator thread. Callers should take a lock
 | 
						|
   * on Timers::mBHRAnnotationTimers before calling this.
 | 
						|
   */
 | 
						|
  void SetBHRAnnotation(const nsAString& aBHRAnnotationKey,
 | 
						|
                        const nsACString& aBHRAnnotationValue) {
 | 
						|
    mBHRAnnotationKey = aBHRAnnotationKey;
 | 
						|
    mBHRAnnotationValue = aBHRAnnotationValue;
 | 
						|
  }
 | 
						|
 | 
						|
  const nsString& GetBHRAnnotationKey() const { return mBHRAnnotationKey; }
 | 
						|
  const nsCString& GetBHRAnnotationValue() const { return mBHRAnnotationValue; }
 | 
						|
 | 
						|
 private:
 | 
						|
  ~Timer() = default;
 | 
						|
  TimeStamp mStartTime{};
 | 
						|
  nsString mBHRAnnotationKey;
 | 
						|
  nsCString mBHRAnnotationValue;
 | 
						|
  bool mInSeconds;
 | 
						|
};
 | 
						|
 | 
						|
#define TIMER_KEYS_IID                               \
 | 
						|
  {                                                  \
 | 
						|
    0xef707178, 0x1544, 0x46e2, {                    \
 | 
						|
      0xa3, 0xf5, 0x98, 0x38, 0xba, 0x60, 0xfd, 0x8f \
 | 
						|
    }                                                \
 | 
						|
  }
 | 
						|
 | 
						|
class TimerKeys final : public nsISupports {
 | 
						|
 public:
 | 
						|
  NS_DECL_ISUPPORTS
 | 
						|
  NS_DECLARE_STATIC_IID_ACCESSOR(TIMER_KEYS_IID)
 | 
						|
 | 
						|
  Timer* Get(const nsAString& aKey, bool aCreate = true);
 | 
						|
 | 
						|
  already_AddRefed<Timer> GetAndDelete(const nsAString& aKey) {
 | 
						|
    RefPtr<Timer> timer;
 | 
						|
    mTimers.Remove(aKey, getter_AddRefs(timer));
 | 
						|
    return timer.forget();
 | 
						|
  }
 | 
						|
 | 
						|
  bool Delete(const nsAString& aKey) { return mTimers.Remove(aKey); }
 | 
						|
 | 
						|
 private:
 | 
						|
  ~TimerKeys() = default;
 | 
						|
 | 
						|
  nsRefPtrHashtable<nsStringHashKey, Timer> mTimers;
 | 
						|
};
 | 
						|
 | 
						|
NS_DEFINE_STATIC_IID_ACCESSOR(TimerKeys, TIMER_KEYS_IID)
 | 
						|
 | 
						|
NS_IMPL_ISUPPORTS(TimerKeys, TimerKeys)
 | 
						|
 | 
						|
Timer* TimerKeys::Get(const nsAString& aKey, bool aCreate) {
 | 
						|
  if (aCreate) {
 | 
						|
    return mTimers.GetOrInsertNew(aKey);
 | 
						|
  }
 | 
						|
  return mTimers.GetWeak(aKey);
 | 
						|
}
 | 
						|
 | 
						|
class Timers final : public BackgroundHangAnnotator {
 | 
						|
 public:
 | 
						|
  Timers();
 | 
						|
 | 
						|
  static Timers& Singleton();
 | 
						|
 | 
						|
  NS_INLINE_DECL_REFCOUNTING(Timers)
 | 
						|
 | 
						|
  JSObject* Get(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                bool aCreate = true);
 | 
						|
 | 
						|
  TimerKeys* Get(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                 JS::Handle<JSObject*> aObj, bool aCreate = true);
 | 
						|
 | 
						|
  Timer* Get(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
             JS::Handle<JSObject*> aObj, const nsAString& aKey,
 | 
						|
             bool aCreate = true);
 | 
						|
 | 
						|
  already_AddRefed<Timer> GetAndDelete(JSContext* aCx,
 | 
						|
                                       const nsAString& aHistogram,
 | 
						|
                                       JS::Handle<JSObject*> aObj,
 | 
						|
                                       const nsAString& aKey);
 | 
						|
 | 
						|
  bool Delete(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
              JS::Handle<JSObject*> aObj, const nsAString& aKey);
 | 
						|
 | 
						|
  int32_t TimeElapsed(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                      JS::Handle<JSObject*> aObj, const nsAString& aKey,
 | 
						|
                      bool aCanceledOkay = false);
 | 
						|
 | 
						|
  bool Start(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
             JS::Handle<JSObject*> aObj, const nsAString& aKey,
 | 
						|
             bool aInSeconds = false);
 | 
						|
 | 
						|
  int32_t Finish(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                 JS::Handle<JSObject*> aObj, const nsAString& aKey,
 | 
						|
                 bool aCanceledOkay = false);
 | 
						|
 | 
						|
  bool& SuppressErrors() { return mSuppressErrors; }
 | 
						|
 | 
						|
  bool StartUserInteraction(JSContext* aCx, const nsAString& aUserInteraction,
 | 
						|
                            const nsACString& aValue,
 | 
						|
                            JS::Handle<JSObject*> aObj);
 | 
						|
  bool RunningUserInteraction(JSContext* aCx, const nsAString& aUserInteraction,
 | 
						|
                              JS::Handle<JSObject*> aObj);
 | 
						|
  bool UpdateUserInteraction(JSContext* aCx, const nsAString& aUserInteraction,
 | 
						|
                             const nsACString& aValue,
 | 
						|
                             JS::Handle<JSObject*> aObj);
 | 
						|
  bool FinishUserInteraction(JSContext* aCx, const nsAString& aUserInteraction,
 | 
						|
                             JS::Handle<JSObject*> aObj,
 | 
						|
                             const dom::Optional<nsACString>& aAdditionalText);
 | 
						|
  bool CancelUserInteraction(JSContext* aCx, const nsAString& aUserInteraction,
 | 
						|
                             JS::Handle<JSObject*> aObj);
 | 
						|
 | 
						|
  void AnnotateHang(BackgroundHangAnnotations& aAnnotations) final;
 | 
						|
 | 
						|
 private:
 | 
						|
  ~Timers();
 | 
						|
 | 
						|
  JS::PersistentRooted<JSObject*> mTimers;
 | 
						|
  DataMutex<mozilla::LinkedList<RefPtr<Timer>>> mBHRAnnotationTimers;
 | 
						|
  bool mSuppressErrors = false;
 | 
						|
 | 
						|
  static StaticRefPtr<Timers> sSingleton;
 | 
						|
};
 | 
						|
 | 
						|
StaticRefPtr<Timers> Timers::sSingleton;
 | 
						|
 | 
						|
/* static */ Timers& Timers::Singleton() {
 | 
						|
  if (!sSingleton) {
 | 
						|
    sSingleton = new Timers();
 | 
						|
    ClearOnShutdown(&sSingleton);
 | 
						|
  }
 | 
						|
  return *sSingleton;
 | 
						|
}
 | 
						|
 | 
						|
Timers::Timers()
 | 
						|
    : mTimers(dom::RootingCx()), mBHRAnnotationTimers("BHRAnnotationTimers") {
 | 
						|
  AutoJSAPI jsapi;
 | 
						|
  MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
 | 
						|
 | 
						|
  mTimers = JS::NewMapObject(jsapi.cx());
 | 
						|
  MOZ_RELEASE_ASSERT(mTimers);
 | 
						|
 | 
						|
  BackgroundHangMonitor::RegisterAnnotator(*this);
 | 
						|
}
 | 
						|
 | 
						|
Timers::~Timers() {
 | 
						|
  // We use a scope here to prevent a deadlock with the mutex that locks
 | 
						|
  // inside of ::UnregisterAnnotator.
 | 
						|
  {
 | 
						|
    auto annotationTimers = mBHRAnnotationTimers.Lock();
 | 
						|
    annotationTimers->clear();
 | 
						|
  }
 | 
						|
  BackgroundHangMonitor::UnregisterAnnotator(*this);
 | 
						|
}
 | 
						|
 | 
						|
JSObject* Timers::Get(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                      bool aCreate) {
 | 
						|
  JSAutoRealm ar(aCx, mTimers);
 | 
						|
 | 
						|
  JS::Rooted<JS::Value> histogram(aCx);
 | 
						|
  JS::Rooted<JS::Value> objs(aCx);
 | 
						|
 | 
						|
  if (!xpc::NonVoidStringToJsval(aCx, aHistogram, &histogram) ||
 | 
						|
      !JS::MapGet(aCx, mTimers, histogram, &objs)) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
  if (!objs.isObject()) {
 | 
						|
    if (aCreate) {
 | 
						|
      objs = JS::ObjectOrNullValue(JS::NewWeakMapObject(aCx));
 | 
						|
    }
 | 
						|
    if (!objs.isObject() || !JS::MapSet(aCx, mTimers, histogram, objs)) {
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return &objs.toObject();
 | 
						|
}
 | 
						|
 | 
						|
TimerKeys* Timers::Get(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                       JS::Handle<JSObject*> aObj, bool aCreate) {
 | 
						|
  JSAutoRealm ar(aCx, mTimers);
 | 
						|
 | 
						|
  JS::Rooted<JSObject*> objs(aCx, Get(aCx, aHistogram, aCreate));
 | 
						|
  if (!objs) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // If no object is passed, use mTimers as a stand-in for a null object
 | 
						|
  // (which cannot be used as a weak map key).
 | 
						|
  JS::Rooted<JSObject*> obj(aCx, aObj ? aObj : mTimers);
 | 
						|
  if (!JS_WrapObject(aCx, &obj)) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<TimerKeys> keys;
 | 
						|
  JS::Rooted<JS::Value> keysObj(aCx);
 | 
						|
  if (!JS::GetWeakMapEntry(aCx, objs, obj, &keysObj)) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
  if (!keysObj.isObject()) {
 | 
						|
    if (aCreate) {
 | 
						|
      keys = new TimerKeys();
 | 
						|
      Unused << nsContentUtils::WrapNative(aCx, keys, &keysObj);
 | 
						|
    }
 | 
						|
    if (!keysObj.isObject() || !JS::SetWeakMapEntry(aCx, objs, obj, keysObj)) {
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  keys = do_QueryReflector(keysObj);
 | 
						|
  return keys;
 | 
						|
}
 | 
						|
 | 
						|
Timer* Timers::Get(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                   JS::Handle<JSObject*> aObj, const nsAString& aKey,
 | 
						|
                   bool aCreate) {
 | 
						|
  if (RefPtr<TimerKeys> keys = Get(aCx, aHistogram, aObj, aCreate)) {
 | 
						|
    return keys->Get(aKey, aCreate);
 | 
						|
  }
 | 
						|
  return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
already_AddRefed<Timer> Timers::GetAndDelete(JSContext* aCx,
 | 
						|
                                             const nsAString& aHistogram,
 | 
						|
                                             JS::Handle<JSObject*> aObj,
 | 
						|
                                             const nsAString& aKey) {
 | 
						|
  if (RefPtr<TimerKeys> keys = Get(aCx, aHistogram, aObj, false)) {
 | 
						|
    return keys->GetAndDelete(aKey);
 | 
						|
  }
 | 
						|
  return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
bool Timers::Delete(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                    JS::Handle<JSObject*> aObj, const nsAString& aKey) {
 | 
						|
  if (RefPtr<TimerKeys> keys = Get(aCx, aHistogram, aObj, false)) {
 | 
						|
    return keys->Delete(aKey);
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
int32_t Timers::TimeElapsed(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                            JS::Handle<JSObject*> aObj, const nsAString& aKey,
 | 
						|
                            bool aCanceledOkay) {
 | 
						|
  RefPtr<Timer> timer = Get(aCx, aHistogram, aObj, aKey, false);
 | 
						|
  if (!timer) {
 | 
						|
    if (!aCanceledOkay && !mSuppressErrors) {
 | 
						|
      LogError(aCx, nsPrintfCString(
 | 
						|
                        "TelemetryStopwatch: requesting elapsed time for "
 | 
						|
                        "nonexisting stopwatch. Histogram: \"%s\", key: \"%s\"",
 | 
						|
                        NS_ConvertUTF16toUTF8(aHistogram).get(),
 | 
						|
                        NS_ConvertUTF16toUTF8(aKey).get()));
 | 
						|
    }
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  return timer->Elapsed();
 | 
						|
}
 | 
						|
 | 
						|
bool Timers::Start(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                   JS::Handle<JSObject*> aObj, const nsAString& aKey,
 | 
						|
                   bool aInSeconds) {
 | 
						|
  if (RefPtr<Timer> timer = Get(aCx, aHistogram, aObj, aKey)) {
 | 
						|
    if (timer->Started()) {
 | 
						|
      if (!mSuppressErrors) {
 | 
						|
        LogError(aCx,
 | 
						|
                 nsPrintfCString(
 | 
						|
                     "TelemetryStopwatch: key \"%s\" was already initialized",
 | 
						|
                     NS_ConvertUTF16toUTF8(aHistogram).get()));
 | 
						|
      }
 | 
						|
      Delete(aCx, aHistogram, aObj, aKey);
 | 
						|
    } else {
 | 
						|
      timer->Start(aInSeconds);
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
int32_t Timers::Finish(JSContext* aCx, const nsAString& aHistogram,
 | 
						|
                       JS::Handle<JSObject*> aObj, const nsAString& aKey,
 | 
						|
                       bool aCanceledOkay) {
 | 
						|
  RefPtr<Timer> timer = GetAndDelete(aCx, aHistogram, aObj, aKey);
 | 
						|
  if (!timer) {
 | 
						|
    if (!aCanceledOkay && !mSuppressErrors) {
 | 
						|
      LogError(aCx, nsPrintfCString(
 | 
						|
                        "TelemetryStopwatch: finishing nonexisting stopwatch. "
 | 
						|
                        "Histogram: \"%s\", key: \"%s\"",
 | 
						|
                        NS_ConvertUTF16toUTF8(aHistogram).get(),
 | 
						|
                        NS_ConvertUTF16toUTF8(aKey).get()));
 | 
						|
    }
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  int32_t delta = timer->Elapsed();
 | 
						|
  NS_ConvertUTF16toUTF8 histogram(aHistogram);
 | 
						|
  nsresult rv;
 | 
						|
  if (!aKey.IsVoid()) {
 | 
						|
    NS_ConvertUTF16toUTF8 key(aKey);
 | 
						|
    rv = TelemetryHistogram::Accumulate(histogram.get(), key, delta);
 | 
						|
  } else {
 | 
						|
    rv = TelemetryHistogram::Accumulate(histogram.get(), delta);
 | 
						|
  }
 | 
						|
  if (profiler_thread_is_being_profiled_for_markers()) {
 | 
						|
    nsCString markerText = histogram;
 | 
						|
    if (!aKey.IsVoid()) {
 | 
						|
      markerText.AppendLiteral(":");
 | 
						|
      markerText.Append(NS_ConvertUTF16toUTF8(aKey));
 | 
						|
    }
 | 
						|
    PROFILER_MARKER_TEXT("TelemetryStopwatch", OTHER,
 | 
						|
                         MarkerTiming::IntervalUntilNowFrom(timer->StartTime()),
 | 
						|
                         markerText);
 | 
						|
  }
 | 
						|
  if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE && !mSuppressErrors) {
 | 
						|
    LogError(aCx, nsPrintfCString(
 | 
						|
                      "TelemetryStopwatch: failed to update the Histogram "
 | 
						|
                      "\"%s\", using key: \"%s\"",
 | 
						|
                      NS_ConvertUTF16toUTF8(aHistogram).get(),
 | 
						|
                      NS_ConvertUTF16toUTF8(aKey).get()));
 | 
						|
  }
 | 
						|
  return NS_SUCCEEDED(rv) ? delta : -1;
 | 
						|
}
 | 
						|
 | 
						|
bool Timers::StartUserInteraction(JSContext* aCx,
 | 
						|
                                  const nsAString& aUserInteraction,
 | 
						|
                                  const nsACString& aValue,
 | 
						|
                                  JS::Handle<JSObject*> aObj) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  // Ensure that this ID maps to a UserInteraction that can be recorded
 | 
						|
  // for this product.
 | 
						|
  if (!TelemetryUserInteraction::CanRecord(aUserInteraction)) {
 | 
						|
    if (!mSuppressErrors) {
 | 
						|
      LogError(aCx, nsPrintfCString(
 | 
						|
                        "UserInteraction with name \"%s\" cannot be recorded.",
 | 
						|
                        NS_ConvertUTF16toUTF8(aUserInteraction).get()));
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (aValue.Length() > USER_INTERACTION_VALUE_MAX_LENGTH) {
 | 
						|
    if (!mSuppressErrors) {
 | 
						|
      LogError(aCx,
 | 
						|
               nsPrintfCString(
 | 
						|
                   "UserInteraction with name \"%s\" cannot be recorded with"
 | 
						|
                   "a value of length greater than %d (%s)",
 | 
						|
                   NS_ConvertUTF16toUTF8(aUserInteraction).get(),
 | 
						|
                   USER_INTERACTION_VALUE_MAX_LENGTH,
 | 
						|
                   PromiseFlatCString(aValue).get()));
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (RefPtr<Timer> timer = Get(aCx, aUserInteraction, aObj, VoidString())) {
 | 
						|
    auto annotationTimers = mBHRAnnotationTimers.Lock();
 | 
						|
 | 
						|
    if (timer->Started()) {
 | 
						|
      if (!mSuppressErrors) {
 | 
						|
        LogError(aCx,
 | 
						|
                 nsPrintfCString(
 | 
						|
                     "UserInteraction with name \"%s\" was already initialized",
 | 
						|
                     NS_ConvertUTF16toUTF8(aUserInteraction).get()));
 | 
						|
      }
 | 
						|
      timer->removeFrom(*annotationTimers);
 | 
						|
      Delete(aCx, aUserInteraction, aObj, VoidString());
 | 
						|
      timer = Get(aCx, aUserInteraction, aObj, VoidString());
 | 
						|
 | 
						|
      nsAutoString clobberText(aUserInteraction);
 | 
						|
      clobberText.AppendLiteral(u" (clobbered)");
 | 
						|
      timer->SetBHRAnnotation(clobberText, aValue);
 | 
						|
    } else {
 | 
						|
      timer->SetBHRAnnotation(aUserInteraction, aValue);
 | 
						|
    }
 | 
						|
 | 
						|
    annotationTimers->insertBack(timer);
 | 
						|
    timer->Start(false);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Timers::RunningUserInteraction(JSContext* aCx,
 | 
						|
                                    const nsAString& aUserInteraction,
 | 
						|
                                    JS::Handle<JSObject*> aObj) {
 | 
						|
  if (RefPtr<Timer> timer =
 | 
						|
          Get(aCx, aUserInteraction, aObj, VoidString(), false /* aCreate */)) {
 | 
						|
    return timer->Started();
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Timers::UpdateUserInteraction(JSContext* aCx,
 | 
						|
                                   const nsAString& aUserInteraction,
 | 
						|
                                   const nsACString& aValue,
 | 
						|
                                   JS::Handle<JSObject*> aObj) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  // Ensure that this ID maps to a UserInteraction that can be recorded
 | 
						|
  // for this product.
 | 
						|
  if (!TelemetryUserInteraction::CanRecord(aUserInteraction)) {
 | 
						|
    if (!mSuppressErrors) {
 | 
						|
      LogError(aCx, nsPrintfCString(
 | 
						|
                        "UserInteraction with name \"%s\" cannot be recorded.",
 | 
						|
                        NS_ConvertUTF16toUTF8(aUserInteraction).get()));
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  auto lock = mBHRAnnotationTimers.Lock();
 | 
						|
  if (RefPtr<Timer> timer = Get(aCx, aUserInteraction, aObj, VoidString())) {
 | 
						|
    if (!timer->Started()) {
 | 
						|
      if (!mSuppressErrors) {
 | 
						|
        LogError(aCx, nsPrintfCString(
 | 
						|
                          "UserInteraction with id \"%s\" was not initialized",
 | 
						|
                          NS_ConvertUTF16toUTF8(aUserInteraction).get()));
 | 
						|
      }
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    timer->SetBHRAnnotation(aUserInteraction, aValue);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Timers::FinishUserInteraction(
 | 
						|
    JSContext* aCx, const nsAString& aUserInteraction,
 | 
						|
    JS::Handle<JSObject*> aObj,
 | 
						|
    const dom::Optional<nsACString>& aAdditionalText) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  // Ensure that this ID maps to a UserInteraction that can be recorded
 | 
						|
  // for this product.
 | 
						|
  if (!TelemetryUserInteraction::CanRecord(aUserInteraction)) {
 | 
						|
    if (!mSuppressErrors) {
 | 
						|
      LogError(aCx, nsPrintfCString(
 | 
						|
                        "UserInteraction with id \"%s\" cannot be recorded.",
 | 
						|
                        NS_ConvertUTF16toUTF8(aUserInteraction).get()));
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<Timer> timer = GetAndDelete(aCx, aUserInteraction, aObj, VoidString());
 | 
						|
  if (!timer) {
 | 
						|
    if (!mSuppressErrors) {
 | 
						|
      LogError(aCx, nsPrintfCString(
 | 
						|
                        "UserInteraction: finishing nonexisting stopwatch. "
 | 
						|
                        "name: \"%s\"",
 | 
						|
                        NS_ConvertUTF16toUTF8(aUserInteraction).get()));
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (profiler_thread_is_being_profiled_for_markers()) {
 | 
						|
    nsAutoCString markerText(timer->GetBHRAnnotationValue());
 | 
						|
    if (aAdditionalText.WasPassed()) {
 | 
						|
      markerText.Append(",");
 | 
						|
      markerText.Append(aAdditionalText.Value());
 | 
						|
    }
 | 
						|
 | 
						|
    PROFILER_MARKER_TEXT(NS_ConvertUTF16toUTF8(aUserInteraction), OTHER,
 | 
						|
                         MarkerTiming::IntervalUntilNowFrom(timer->StartTime()),
 | 
						|
                         markerText);
 | 
						|
  }
 | 
						|
 | 
						|
  // The Timer will be held alive by the RefPtr that's still in the LinkedList,
 | 
						|
  // so the automatic removal from the LinkedList from the LinkedListElement
 | 
						|
  // destructor will not occur. We must remove it manually from the LinkedList
 | 
						|
  // instead.
 | 
						|
  {
 | 
						|
    auto annotationTimers = mBHRAnnotationTimers.Lock();
 | 
						|
    timer->removeFrom(*annotationTimers);
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Timers::CancelUserInteraction(JSContext* aCx,
 | 
						|
                                   const nsAString& aUserInteraction,
 | 
						|
                                   JS::Handle<JSObject*> aObj) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  // Ensure that this ID maps to a UserInteraction that can be recorded
 | 
						|
  // for this product.
 | 
						|
  if (!TelemetryUserInteraction::CanRecord(aUserInteraction)) {
 | 
						|
    if (!mSuppressErrors) {
 | 
						|
      LogError(aCx, nsPrintfCString(
 | 
						|
                        "UserInteraction with id \"%s\" cannot be recorded.",
 | 
						|
                        NS_ConvertUTF16toUTF8(aUserInteraction).get()));
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<Timer> timer = GetAndDelete(aCx, aUserInteraction, aObj, VoidString());
 | 
						|
  if (!timer) {
 | 
						|
    if (!mSuppressErrors) {
 | 
						|
      LogError(aCx, nsPrintfCString(
 | 
						|
                        "UserInteraction: cancelling nonexisting stopwatch. "
 | 
						|
                        "name: \"%s\"",
 | 
						|
                        NS_ConvertUTF16toUTF8(aUserInteraction).get()));
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  // The Timer will be held alive by the RefPtr that's still in the LinkedList,
 | 
						|
  // so the automatic removal from the LinkedList from the LinkedListElement
 | 
						|
  // destructor will not occur. We must remove it manually from the LinkedList
 | 
						|
  // instead.
 | 
						|
  {
 | 
						|
    auto annotationTimers = mBHRAnnotationTimers.Lock();
 | 
						|
    timer->removeFrom(*annotationTimers);
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void Timers::AnnotateHang(mozilla::BackgroundHangAnnotations& aAnnotations) {
 | 
						|
  auto annotationTimers = mBHRAnnotationTimers.Lock();
 | 
						|
  for (Timer* bhrAnnotationTimer : *annotationTimers) {
 | 
						|
    aAnnotations.AddAnnotation(bhrAnnotationTimer->GetBHRAnnotationKey(),
 | 
						|
                               bhrAnnotationTimer->GetBHRAnnotationValue());
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool Stopwatch::Start(const dom::GlobalObject& aGlobal,
 | 
						|
                      const nsAString& aHistogram, JS::Handle<JSObject*> aObj,
 | 
						|
                      const dom::TelemetryStopwatchOptions& aOptions) {
 | 
						|
  return StartKeyed(aGlobal, aHistogram, VoidString(), aObj, aOptions);
 | 
						|
}
 | 
						|
/* static */
 | 
						|
bool Stopwatch::StartKeyed(const dom::GlobalObject& aGlobal,
 | 
						|
                           const nsAString& aHistogram, const nsAString& aKey,
 | 
						|
                           JS::Handle<JSObject*> aObj,
 | 
						|
                           const dom::TelemetryStopwatchOptions& aOptions) {
 | 
						|
  return Timers::Singleton().Start(aGlobal.Context(), aHistogram, aObj, aKey,
 | 
						|
                                   aOptions.mInSeconds);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool Stopwatch::Running(const dom::GlobalObject& aGlobal,
 | 
						|
                        const nsAString& aHistogram,
 | 
						|
                        JS::Handle<JSObject*> aObj) {
 | 
						|
  return RunningKeyed(aGlobal, aHistogram, VoidString(), aObj);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool Stopwatch::RunningKeyed(const dom::GlobalObject& aGlobal,
 | 
						|
                             const nsAString& aHistogram, const nsAString& aKey,
 | 
						|
                             JS::Handle<JSObject*> aObj) {
 | 
						|
  return TimeElapsedKeyed(aGlobal, aHistogram, aKey, aObj, true) != -1;
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
int32_t Stopwatch::TimeElapsed(const dom::GlobalObject& aGlobal,
 | 
						|
                               const nsAString& aHistogram,
 | 
						|
                               JS::Handle<JSObject*> aObj, bool aCanceledOkay) {
 | 
						|
  return TimeElapsedKeyed(aGlobal, aHistogram, VoidString(), aObj,
 | 
						|
                          aCanceledOkay);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
int32_t Stopwatch::TimeElapsedKeyed(const dom::GlobalObject& aGlobal,
 | 
						|
                                    const nsAString& aHistogram,
 | 
						|
                                    const nsAString& aKey,
 | 
						|
                                    JS::Handle<JSObject*> aObj,
 | 
						|
                                    bool aCanceledOkay) {
 | 
						|
  return Timers::Singleton().TimeElapsed(aGlobal.Context(), aHistogram, aObj,
 | 
						|
                                         aKey, aCanceledOkay);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool Stopwatch::Finish(const dom::GlobalObject& aGlobal,
 | 
						|
                       const nsAString& aHistogram, JS::Handle<JSObject*> aObj,
 | 
						|
                       bool aCanceledOkay) {
 | 
						|
  return FinishKeyed(aGlobal, aHistogram, VoidString(), aObj, aCanceledOkay);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool Stopwatch::FinishKeyed(const dom::GlobalObject& aGlobal,
 | 
						|
                            const nsAString& aHistogram, const nsAString& aKey,
 | 
						|
                            JS::Handle<JSObject*> aObj, bool aCanceledOkay) {
 | 
						|
  return Timers::Singleton().Finish(aGlobal.Context(), aHistogram, aObj, aKey,
 | 
						|
                                    aCanceledOkay) != -1;
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool Stopwatch::Cancel(const dom::GlobalObject& aGlobal,
 | 
						|
                       const nsAString& aHistogram,
 | 
						|
                       JS::Handle<JSObject*> aObj) {
 | 
						|
  return CancelKeyed(aGlobal, aHistogram, VoidString(), aObj);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool Stopwatch::CancelKeyed(const dom::GlobalObject& aGlobal,
 | 
						|
                            const nsAString& aHistogram, const nsAString& aKey,
 | 
						|
                            JS::Handle<JSObject*> aObj) {
 | 
						|
  return Timers::Singleton().Delete(aGlobal.Context(), aHistogram, aObj, aKey);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
void Stopwatch::SetTestModeEnabled(const dom::GlobalObject& aGlobal,
 | 
						|
                                   bool aTesting) {
 | 
						|
  Timers::Singleton().SuppressErrors() = aTesting;
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool UserInteractionStopwatch::Start(const dom::GlobalObject& aGlobal,
 | 
						|
                                     const nsAString& aUserInteraction,
 | 
						|
                                     const nsACString& aValue,
 | 
						|
                                     JS::Handle<JSObject*> aObj) {
 | 
						|
  if (!NS_IsMainThread()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  return Timers::Singleton().StartUserInteraction(
 | 
						|
      aGlobal.Context(), aUserInteraction, aValue, aObj);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool UserInteractionStopwatch::Running(const dom::GlobalObject& aGlobal,
 | 
						|
                                       const nsAString& aUserInteraction,
 | 
						|
                                       JS::Handle<JSObject*> aObj) {
 | 
						|
  if (!NS_IsMainThread()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  return Timers::Singleton().RunningUserInteraction(aGlobal.Context(),
 | 
						|
                                                    aUserInteraction, aObj);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool UserInteractionStopwatch::Update(const dom::GlobalObject& aGlobal,
 | 
						|
                                      const nsAString& aUserInteraction,
 | 
						|
                                      const nsACString& aValue,
 | 
						|
                                      JS::Handle<JSObject*> aObj) {
 | 
						|
  if (!NS_IsMainThread()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  return Timers::Singleton().UpdateUserInteraction(
 | 
						|
      aGlobal.Context(), aUserInteraction, aValue, aObj);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool UserInteractionStopwatch::Cancel(const dom::GlobalObject& aGlobal,
 | 
						|
                                      const nsAString& aUserInteraction,
 | 
						|
                                      JS::Handle<JSObject*> aObj) {
 | 
						|
  if (!NS_IsMainThread()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  return Timers::Singleton().CancelUserInteraction(aGlobal.Context(),
 | 
						|
                                                   aUserInteraction, aObj);
 | 
						|
}
 | 
						|
 | 
						|
/* static */
 | 
						|
bool UserInteractionStopwatch::Finish(
 | 
						|
    const dom::GlobalObject& aGlobal, const nsAString& aUserInteraction,
 | 
						|
    JS::Handle<JSObject*> aObj,
 | 
						|
    const dom::Optional<nsACString>& aAdditionalText) {
 | 
						|
  if (!NS_IsMainThread()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  return Timers::Singleton().FinishUserInteraction(
 | 
						|
      aGlobal.Context(), aUserInteraction, aObj, aAdditionalText);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla::telemetry
 |