diff options
Diffstat (limited to 'toolkit/components/telemetry/core')
27 files changed, 18651 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/core/EventInfo.h b/toolkit/components/telemetry/core/EventInfo.h new file mode 100644 index 0000000000..b80f85af92 --- /dev/null +++ b/toolkit/components/telemetry/core/EventInfo.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryEventInfo_h__ +#define TelemetryEventInfo_h__ + +#include "TelemetryCommon.h" + +// This module is internal to Telemetry. The structures here hold data that +// describe events. +// It should only be used by TelemetryEventData.h and TelemetryEvent.cpp. +// +// For the public interface to Telemetry functionality, see Telemetry.h. + +namespace { + +struct CommonEventInfo { + // Indices for the category and expiration strings. + uint32_t category_offset; + uint32_t expiration_version_offset; + + // The index and count for the extra key offsets in the extra table. + uint32_t extra_index; + uint32_t extra_count; + + // The dataset this event is recorded in. + uint32_t dataset; + + // Which processes to record this event in. + mozilla::Telemetry::Common::RecordedProcessType record_in_processes; + + // Which products to record this event on. + mozilla::Telemetry::Common::SupportedProduct products; + + // Convenience functions for accessing event strings. + const nsDependentCString expiration_version() const; + const nsDependentCString category() const; + const nsDependentCString extra_key(uint32_t index) const; +}; + +struct EventInfo { + // The corresponding CommonEventInfo. + const CommonEventInfo& common_info; + + // Indices for the method & object strings. + uint32_t method_offset; + uint32_t object_offset; + + const nsDependentCString method() const; + const nsDependentCString object() const; +}; + +} // namespace + +#endif // TelemetryEventInfo_h__ diff --git a/toolkit/components/telemetry/core/ScalarInfo.h b/toolkit/components/telemetry/core/ScalarInfo.h new file mode 100644 index 0000000000..125ea7e88e --- /dev/null +++ b/toolkit/components/telemetry/core/ScalarInfo.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryScalarInfo_h__ +#define TelemetryScalarInfo_h__ + +#include "TelemetryCommon.h" + +// This module is internal to Telemetry. It defines a structure that holds the +// scalar info. It should only be used by TelemetryScalarData.h automatically +// generated file and TelemetryScalar.cpp. This should not be used anywhere +// else. For the public interface to Telemetry functionality, see Telemetry.h. + +namespace { + +/** + * Base scalar information, common to both "static" and dynamic scalars. + */ +struct BaseScalarInfo { + uint32_t kind; + uint32_t dataset; + mozilla::Telemetry::Common::RecordedProcessType record_in_processes; + bool keyed; + uint32_t key_count; + uint32_t key_offset; + mozilla::Telemetry::Common::SupportedProduct products; + bool builtin; + + constexpr BaseScalarInfo( + uint32_t aKind, uint32_t aDataset, + mozilla::Telemetry::Common::RecordedProcessType aRecordInProcess, + bool aKeyed, uint32_t aKeyCount, uint32_t aKeyOffset, + mozilla::Telemetry::Common::SupportedProduct aProducts, + bool aBuiltin = true) + : kind(aKind), + dataset(aDataset), + record_in_processes(aRecordInProcess), + keyed(aKeyed), + key_count(aKeyCount), + key_offset(aKeyOffset), + products(aProducts), + builtin(aBuiltin) {} + virtual ~BaseScalarInfo() = default; + + virtual const char* name() const = 0; + virtual const char* expiration() const = 0; + + virtual uint32_t storeOffset() const = 0; + virtual uint32_t storeCount() const = 0; +}; + +/** + * "Static" scalar definition: these are the ones riding + * the trains. + */ +struct ScalarInfo : BaseScalarInfo { + uint32_t name_offset; + uint32_t expiration_offset; + uint32_t store_count; + uint16_t store_offset; + + // In order to cleanly support dynamic scalars in TelemetryScalar.cpp, we need + // to use virtual functions for |name| and |expiration|, as they won't be + // looked up in the static tables in that case. However, using virtual + // functions makes |ScalarInfo| non-aggregate and prevents from using + // aggregate initialization (curly brackets) in the generated + // TelemetryScalarData.h. To work around this problem we define a constructor + // that takes the exact number of parameters we need. + constexpr ScalarInfo( + uint32_t aKind, uint32_t aNameOffset, uint32_t aExpirationOffset, + uint32_t aDataset, + mozilla::Telemetry::Common::RecordedProcessType aRecordInProcess, + bool aKeyed, uint32_t aKeyCount, uint32_t aKeyOffset, + mozilla::Telemetry::Common::SupportedProduct aProducts, + uint32_t aStoreCount, uint32_t aStoreOffset) + : BaseScalarInfo(aKind, aDataset, aRecordInProcess, aKeyed, aKeyCount, + aKeyOffset, aProducts), + name_offset(aNameOffset), + expiration_offset(aExpirationOffset), + store_count(aStoreCount), + store_offset(aStoreOffset) {} + + const char* name() const override; + const char* expiration() const override; + + uint32_t storeOffset() const override { return store_offset; }; + uint32_t storeCount() const override { return store_count; }; +}; + +} // namespace + +#endif // TelemetryScalarInfo_h__ diff --git a/toolkit/components/telemetry/core/Stopwatch.cpp b/toolkit/components/telemetry/core/Stopwatch.cpp new file mode 100644 index 0000000000..98d9e5fa6d --- /dev/null +++ b/toolkit/components/telemetry/core/Stopwatch.cpp @@ -0,0 +1,750 @@ +/* -*- 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 diff --git a/toolkit/components/telemetry/core/Stopwatch.h b/toolkit/components/telemetry/core/Stopwatch.h new file mode 100644 index 0000000000..070872e250 --- /dev/null +++ b/toolkit/components/telemetry/core/Stopwatch.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Stopwatch_h__ +#define Stopwatch_h__ + +#include "mozilla/dom/TelemetryStopwatchBinding.h" + +namespace mozilla { +namespace telemetry { + +class Stopwatch { + using GlobalObject = mozilla::dom::GlobalObject; + + public: + static bool Start(const GlobalObject& global, const nsAString& histogram, + JS::Handle<JSObject*> obj, + const dom::TelemetryStopwatchOptions& options); + + static bool Running(const GlobalObject& global, const nsAString& histogram, + JS::Handle<JSObject*> obj); + + static bool Cancel(const GlobalObject& global, const nsAString& histogram, + JS::Handle<JSObject*> obj); + + static int32_t TimeElapsed(const GlobalObject& global, + const nsAString& histogram, + JS::Handle<JSObject*> obj, bool canceledOkay); + + static bool Finish(const GlobalObject& global, const nsAString& histogram, + JS::Handle<JSObject*> obj, bool canceledOkay); + + static bool StartKeyed(const GlobalObject& global, const nsAString& histogram, + const nsAString& key, JS::Handle<JSObject*> obj, + const dom::TelemetryStopwatchOptions& options); + + static bool RunningKeyed(const GlobalObject& global, + const nsAString& histogram, const nsAString& key, + JS::Handle<JSObject*> obj); + + static bool CancelKeyed(const GlobalObject& global, + const nsAString& histogram, const nsAString& key, + JS::Handle<JSObject*> obj); + + static int32_t TimeElapsedKeyed(const GlobalObject& global, + const nsAString& histogram, + const nsAString& key, + JS::Handle<JSObject*> obj, bool canceledOkay); + + static bool FinishKeyed(const GlobalObject& global, + const nsAString& histogram, const nsAString& key, + JS::Handle<JSObject*> obj, bool canceledOkay); + + static void SetTestModeEnabled(const GlobalObject& global, bool testing); +}; + +class UserInteractionStopwatch { + using GlobalObject = mozilla::dom::GlobalObject; + + public: + static bool Start(const GlobalObject& aGlobal, + const nsAString& aUserInteraction, const nsACString& aValue, + JS::Handle<JSObject*> aObj); + static bool Running(const GlobalObject& aGlobal, + const nsAString& aUserInteraction, + JS::Handle<JSObject*> aObj); + static bool Update(const GlobalObject& aGlobal, + const nsAString& aUserInteraction, + const nsACString& aValue, JS::Handle<JSObject*> aObj); + static bool Cancel(const GlobalObject& aGlobal, + const nsAString& aUserInteraction, + JS::Handle<JSObject*> aObj); + static bool Finish(const GlobalObject& aGlobal, + const nsAString& aUserInteraction, + JS::Handle<JSObject*> aObj, + const dom::Optional<nsACString>& aAdditionalText); +}; + +} // namespace telemetry +} // namespace mozilla + +#endif // Stopwatch_h__ diff --git a/toolkit/components/telemetry/core/Telemetry.cpp b/toolkit/components/telemetry/core/Telemetry.cpp new file mode 100644 index 0000000000..2c44de7fc8 --- /dev/null +++ b/toolkit/components/telemetry/core/Telemetry.cpp @@ -0,0 +1,2079 @@ +/* -*- 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 "Telemetry.h" + +#include <algorithm> +#include <prio.h> +#include <prproces.h> +#if defined(XP_UNIX) && !defined(XP_DARWIN) +# include <time.h> +#else +# include <chrono> +#endif +#include "base/pickle.h" +#include "base/process_util.h" +#if defined(MOZ_TELEMETRY_GECKOVIEW) +# include "geckoview/TelemetryGeckoViewPersistence.h" +#endif +#include "ipc/TelemetryIPCAccumulator.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/Array.h" // JS::NewArrayObject +#include "js/GCAPI.h" +#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/BackgroundHangMonitor.h" +#ifdef MOZ_BACKGROUNDTASKS +# include "mozilla/BackgroundTasks.h" +#endif +#include "mozilla/Components.h" +#include "mozilla/DataMutex.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FStream.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Likely.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/MemoryTelemetry.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/Mutex.h" +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/Preferences.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#if defined(XP_WIN) +# include "mozilla/WinDllServices.h" +#endif +#include "nsAppDirectoryServiceDefs.h" +#include "nsBaseHashtable.h" +#include "nsClassHashtable.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsIDirectoryEnumerator.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFileStreams.h" +#include "nsIMemoryReporter.h" +#include "nsISeekableStream.h" +#include "nsITelemetry.h" +#if defined(XP_WIN) +# include "other/UntrustedModules.h" +#endif +#include "nsJSUtils.h" +#include "nsLocalFile.h" +#include "nsNativeCharsetUtils.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsProxyRelease.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsTHashtable.h" +#include "nsThreadUtils.h" +#if defined(XP_WIN) +# include "nsUnicharUtils.h" +#endif +#include "nsVersionComparator.h" +#include "nsXPCOMCIDInternal.h" +#include "other/CombinedStacks.h" +#include "other/TelemetryIOInterposeObserver.h" +#include "plstr.h" +#include "TelemetryCommon.h" +#include "TelemetryEvent.h" +#include "TelemetryHistogram.h" +#include "TelemetryOrigin.h" +#include "TelemetryScalar.h" +#include "TelemetryUserInteraction.h" + +namespace { + +using namespace mozilla; +using mozilla::dom::AutoJSAPI; +using mozilla::dom::Promise; +using mozilla::Telemetry::CombinedStacks; +using mozilla::Telemetry::EventExtraEntry; +using mozilla::Telemetry::TelemetryIOInterposeObserver; +using Telemetry::Common::AutoHashtable; +using Telemetry::Common::GetCurrentProduct; +using Telemetry::Common::StringHashSet; +using Telemetry::Common::SupportedProduct; +using Telemetry::Common::ToJSString; + +// This is not a member of TelemetryImpl because we want to record I/O during +// startup. +StaticAutoPtr<TelemetryIOInterposeObserver> sTelemetryIOObserver; + +void ClearIOReporting() { + if (!sTelemetryIOObserver) { + return; + } + IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging, + sTelemetryIOObserver); + sTelemetryIOObserver = nullptr; +} + +class TelemetryImpl final : public nsITelemetry, public nsIMemoryReporter { + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITELEMETRY + NS_DECL_NSIMEMORYREPORTER + + public: + void InitMemoryReporter(); + + static already_AddRefed<nsITelemetry> CreateTelemetryInstance(); + static void ShutdownTelemetry(); + static void RecordSlowStatement(const nsACString& sql, + const nsACString& dbName, uint32_t delay); + struct Stat { + uint32_t hitCount; + uint32_t totalTime; + }; + struct StmtStats { + struct Stat mainThread; + struct Stat otherThreads; + }; + typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType; + + static void RecordIceCandidates(const uint32_t iceCandidateBitmask, + const bool success); + static bool CanRecordBase(); + static bool CanRecordExtended(); + static bool CanRecordReleaseData(); + static bool CanRecordPrereleaseData(); + + private: + TelemetryImpl(); + ~TelemetryImpl(); + + static nsCString SanitizeSQL(const nsACString& sql); + + enum SanitizedState { Sanitized, Unsanitized }; + + static void StoreSlowSQL(const nsACString& offender, uint32_t delay, + SanitizedState state); + + static bool ReflectMainThreadSQL(SlowSQLEntryType* entry, JSContext* cx, + JS::Handle<JSObject*> obj); + static bool ReflectOtherThreadsSQL(SlowSQLEntryType* entry, JSContext* cx, + JS::Handle<JSObject*> obj); + static bool ReflectSQL(const SlowSQLEntryType* entry, const Stat* stat, + JSContext* cx, JS::Handle<JSObject*> obj); + + bool AddSQLInfo(JSContext* cx, JS::Handle<JSObject*> rootObj, bool mainThread, + bool privateSQL); + bool GetSQLStats(JSContext* cx, JS::MutableHandle<JS::Value> ret, + bool includePrivateSql); + + void ReadLateWritesStacks(nsIFile* aProfileDir); + + static StaticDataMutex<TelemetryImpl*> sTelemetry; + AutoHashtable<SlowSQLEntryType> mPrivateSQL; + AutoHashtable<SlowSQLEntryType> mSanitizedSQL; + Mutex mHashMutex MOZ_UNANNOTATED; + Atomic<bool, SequentiallyConsistent> mCanRecordBase; + Atomic<bool, SequentiallyConsistent> mCanRecordExtended; + + CombinedStacks + mLateWritesStacks; // This is collected out of the main thread. + bool mCachedTelemetryData; + uint32_t mLastShutdownTime; + uint32_t mFailedLockCount; + nsCOMArray<nsIFetchTelemetryDataCallback> mCallbacks; + friend class nsFetchTelemetryData; +}; + +StaticDataMutex<TelemetryImpl*> TelemetryImpl::sTelemetry(nullptr, nullptr); + +MOZ_DEFINE_MALLOC_SIZE_OF(TelemetryMallocSizeOf) + +NS_IMETHODIMP +TelemetryImpl::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + mozilla::MallocSizeOf aMallocSizeOf = TelemetryMallocSizeOf; + +#define COLLECT_REPORT(name, size, desc) \ + MOZ_COLLECT_REPORT(name, KIND_HEAP, UNITS_BYTES, size, desc) + + COLLECT_REPORT("explicit/telemetry/impl", aMallocSizeOf(this), + "Memory used by the Telemetry core implemenation"); + + COLLECT_REPORT( + "explicit/telemetry/scalar/shallow", + TelemetryScalar::GetMapShallowSizesOfExcludingThis(aMallocSizeOf), + "Memory used by the Telemetry Scalar implemenation"); + + { // Scope for mHashMutex lock + MutexAutoLock lock(mHashMutex); + COLLECT_REPORT("explicit/telemetry/PrivateSQL", + mPrivateSQL.SizeOfExcludingThis(aMallocSizeOf), + "Memory used by the PrivateSQL Telemetry"); + + COLLECT_REPORT("explicit/telemetry/SanitizedSQL", + mSanitizedSQL.SizeOfExcludingThis(aMallocSizeOf), + "Memory used by the SanitizedSQL Telemetry"); + } + + if (sTelemetryIOObserver) { + COLLECT_REPORT("explicit/telemetry/IOObserver", + sTelemetryIOObserver->SizeOfIncludingThis(aMallocSizeOf), + "Memory used by the Telemetry IO Observer"); + } + + COLLECT_REPORT("explicit/telemetry/LateWritesStacks", + mLateWritesStacks.SizeOfExcludingThis(), + "Memory used by the Telemetry LateWrites Stack capturer"); + + COLLECT_REPORT("explicit/telemetry/Callbacks", + mCallbacks.ShallowSizeOfExcludingThis(aMallocSizeOf), + "Memory used by the Telemetry Callbacks array (shallow)"); + + COLLECT_REPORT( + "explicit/telemetry/histogram/data", + TelemetryHistogram::GetHistogramSizesOfIncludingThis(aMallocSizeOf), + "Memory used by Telemetry Histogram data"); + + COLLECT_REPORT("explicit/telemetry/scalar/data", + TelemetryScalar::GetScalarSizesOfIncludingThis(aMallocSizeOf), + "Memory used by Telemetry Scalar data"); + + COLLECT_REPORT("explicit/telemetry/event/data", + TelemetryEvent::SizeOfIncludingThis(aMallocSizeOf), + "Memory used by Telemetry Event data"); + + COLLECT_REPORT("explicit/telemetry/origin/data", + TelemetryOrigin::SizeOfIncludingThis(aMallocSizeOf), + "Memory used by Telemetry Origin data"); + +#undef COLLECT_REPORT + + return NS_OK; +} + +void InitHistogramRecordingEnabled() { + TelemetryHistogram::InitHistogramRecordingEnabled(); +} + +using PathChar = filesystem::Path::value_type; +using PathCharPtr = const PathChar*; + +static uint32_t ReadLastShutdownDuration(PathCharPtr filename) { + RefPtr<nsLocalFile> file = + new nsLocalFile(nsTDependentString<PathChar>(filename)); + FILE* f; + if (NS_FAILED(file->OpenANSIFileDesc("r", &f)) || !f) { + return 0; + } + + int shutdownTime; + int r = fscanf(f, "%d\n", &shutdownTime); + fclose(f); + if (r != 1) { + return 0; + } + + return shutdownTime; +} + +const int32_t kMaxFailedProfileLockFileSize = 10; + +bool GetFailedLockCount(nsIInputStream* inStream, uint32_t aCount, + unsigned int& result) { + nsAutoCString bufStr; + nsresult rv; + rv = NS_ReadInputStreamToString(inStream, bufStr, aCount); + NS_ENSURE_SUCCESS(rv, false); + result = bufStr.ToInteger(&rv); + return NS_SUCCEEDED(rv) && result > 0; +} + +nsresult GetFailedProfileLockFile(nsIFile** aFile, nsIFile* aProfileDir) { + NS_ENSURE_ARG_POINTER(aProfileDir); + + nsresult rv = aProfileDir->Clone(aFile); + NS_ENSURE_SUCCESS(rv, rv); + + (*aFile)->AppendNative("Telemetry.FailedProfileLocks.txt"_ns); + return NS_OK; +} + +class nsFetchTelemetryData : public Runnable { + public: + nsFetchTelemetryData(PathCharPtr aShutdownTimeFilename, + nsIFile* aFailedProfileLockFile, nsIFile* aProfileDir) + : mozilla::Runnable("nsFetchTelemetryData"), + mShutdownTimeFilename(aShutdownTimeFilename), + mFailedProfileLockFile(aFailedProfileLockFile), + mProfileDir(aProfileDir) {} + + private: + PathCharPtr mShutdownTimeFilename; + nsCOMPtr<nsIFile> mFailedProfileLockFile; + nsCOMPtr<nsIFile> mProfileDir; + + public: + void MainThread() { + auto lock = TelemetryImpl::sTelemetry.Lock(); + auto telemetry = lock.ref(); + telemetry->mCachedTelemetryData = true; + for (unsigned int i = 0, n = telemetry->mCallbacks.Count(); i < n; ++i) { + telemetry->mCallbacks[i]->Complete(); + } + telemetry->mCallbacks.Clear(); + } + + NS_IMETHOD Run() override { + uint32_t failedLockCount = 0; + uint32_t lastShutdownDuration = 0; + LoadFailedLockCount(failedLockCount); + lastShutdownDuration = ReadLastShutdownDuration(mShutdownTimeFilename); + { + auto lock = TelemetryImpl::sTelemetry.Lock(); + auto telemetry = lock.ref(); + telemetry->mFailedLockCount = failedLockCount; + telemetry->mLastShutdownTime = lastShutdownDuration; + telemetry->ReadLateWritesStacks(mProfileDir); + } + + TelemetryScalar::Set(Telemetry::ScalarID::BROWSER_TIMINGS_LAST_SHUTDOWN, + lastShutdownDuration); + + nsCOMPtr<nsIRunnable> e = + NewRunnableMethod("nsFetchTelemetryData::MainThread", this, + &nsFetchTelemetryData::MainThread); + NS_ENSURE_STATE(e); + NS_DispatchToMainThread(e); + return NS_OK; + } + + private: + nsresult LoadFailedLockCount(uint32_t& failedLockCount) { + failedLockCount = 0; + int64_t fileSize = 0; + nsresult rv = mFailedProfileLockFile->GetFileSize(&fileSize); + if (NS_FAILED(rv)) { + return rv; + } + NS_ENSURE_TRUE(fileSize <= kMaxFailedProfileLockFileSize, + NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIInputStream> inStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), + mFailedProfileLockFile, PR_RDONLY); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(GetFailedLockCount(inStream, fileSize, failedLockCount), + NS_ERROR_UNEXPECTED); + inStream->Close(); + + mFailedProfileLockFile->Remove(false); + return NS_OK; + } +}; + +static TimeStamp gRecordedShutdownStartTime; +static bool gAlreadyFreedShutdownTimeFileName = false; +static PathCharPtr gRecordedShutdownTimeFileName = nullptr; + +static PathCharPtr GetShutdownTimeFileName() { + if (gAlreadyFreedShutdownTimeFileName) { + return nullptr; + } + + if (!gRecordedShutdownTimeFileName) { + nsCOMPtr<nsIFile> mozFile; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile)); + if (!mozFile) return nullptr; + + mozFile->AppendNative("Telemetry.ShutdownTime.txt"_ns); + + gRecordedShutdownTimeFileName = NS_xstrdup(mozFile->NativePath().get()); + } + + return gRecordedShutdownTimeFileName; +} + +NS_IMETHODIMP +TelemetryImpl::GetLastShutdownDuration(uint32_t* aResult) { + // The user must call AsyncFetchTelemetryData first. We return zero instead of + // reporting a failure so that the rest of telemetry can uniformly handle + // the read not being available yet. + if (!mCachedTelemetryData) { + *aResult = 0; + return NS_OK; + } + + *aResult = mLastShutdownTime; + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::GetFailedProfileLockCount(uint32_t* aResult) { + // The user must call AsyncFetchTelemetryData first. We return zero instead of + // reporting a failure so that the rest of telemetry can uniformly handle + // the read not being available yet. + if (!mCachedTelemetryData) { + *aResult = 0; + return NS_OK; + } + + *aResult = mFailedLockCount; + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::AsyncFetchTelemetryData( + nsIFetchTelemetryDataCallback* aCallback) { + // We have finished reading the data already, just call the callback. + if (mCachedTelemetryData) { + aCallback->Complete(); + return NS_OK; + } + + // We already have a read request running, just remember the callback. + if (mCallbacks.Count() != 0) { + mCallbacks.AppendObject(aCallback); + return NS_OK; + } + + // We make this check so that GetShutdownTimeFileName() doesn't get + // called; calling that function without telemetry enabled violates + // assumptions that the write-the-shutdown-timestamp machinery makes. + if (!Telemetry::CanRecordExtended()) { + mCachedTelemetryData = true; + aCallback->Complete(); + return NS_OK; + } + + // Send the read to a background thread provided by the stream transport + // service to avoid a read in the main thread. + nsCOMPtr<nsIEventTarget> targetThread = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + if (!targetThread) { + mCachedTelemetryData = true; + aCallback->Complete(); + return NS_OK; + } + + // We have to get the filename from the main thread. + PathCharPtr shutdownTimeFilename = GetShutdownTimeFileName(); + if (!shutdownTimeFilename) { + mCachedTelemetryData = true; + aCallback->Complete(); + return NS_OK; + } + + nsCOMPtr<nsIFile> profileDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDir)); + if (NS_FAILED(rv)) { + mCachedTelemetryData = true; + aCallback->Complete(); + return NS_OK; + } + + nsCOMPtr<nsIFile> failedProfileLockFile; + rv = GetFailedProfileLockFile(getter_AddRefs(failedProfileLockFile), + profileDir); + if (NS_FAILED(rv)) { + mCachedTelemetryData = true; + aCallback->Complete(); + return NS_OK; + } + + mCallbacks.AppendObject(aCallback); + + nsCOMPtr<nsIRunnable> event = new nsFetchTelemetryData( + shutdownTimeFilename, failedProfileLockFile, profileDir); + + targetThread->Dispatch(event, NS_DISPATCH_NORMAL); + return NS_OK; +} + +TelemetryImpl::TelemetryImpl() + : mHashMutex("Telemetry::mHashMutex"), + mCanRecordBase(false), + mCanRecordExtended(false), + mCachedTelemetryData(false), + mLastShutdownTime(0), + mFailedLockCount(0) { + // We expect TelemetryHistogram::InitializeGlobalState() to have been + // called before we get to this point. + MOZ_ASSERT(TelemetryHistogram::GlobalStateHasBeenInitialized()); +} + +TelemetryImpl::~TelemetryImpl() { + UnregisterWeakMemoryReporter(this); + + // This is still racey as access to these collections is guarded using + // sTelemetry. We will fix this in bug 1367344. + MutexAutoLock hashLock(mHashMutex); +} + +void TelemetryImpl::InitMemoryReporter() { RegisterWeakMemoryReporter(this); } + +bool TelemetryImpl::ReflectSQL(const SlowSQLEntryType* entry, const Stat* stat, + JSContext* cx, JS::Handle<JSObject*> obj) { + if (stat->hitCount == 0) return true; + + const nsACString& sql = entry->GetKey(); + + JS::Rooted<JSObject*> arrayObj(cx, JS::NewArrayObject(cx, 0)); + if (!arrayObj) { + return false; + } + return ( + JS_DefineElement(cx, arrayObj, 0, stat->hitCount, JSPROP_ENUMERATE) && + JS_DefineElement(cx, arrayObj, 1, stat->totalTime, JSPROP_ENUMERATE) && + JS_DefineProperty(cx, obj, sql.BeginReading(), arrayObj, + JSPROP_ENUMERATE)); +} + +bool TelemetryImpl::ReflectMainThreadSQL(SlowSQLEntryType* entry, JSContext* cx, + JS::Handle<JSObject*> obj) { + return ReflectSQL(entry, &entry->GetModifiableData()->mainThread, cx, obj); +} + +bool TelemetryImpl::ReflectOtherThreadsSQL(SlowSQLEntryType* entry, + JSContext* cx, + JS::Handle<JSObject*> obj) { + return ReflectSQL(entry, &entry->GetModifiableData()->otherThreads, cx, obj); +} + +bool TelemetryImpl::AddSQLInfo(JSContext* cx, JS::Handle<JSObject*> rootObj, + bool mainThread, bool privateSQL) { + JS::Rooted<JSObject*> statsObj(cx, JS_NewPlainObject(cx)); + if (!statsObj) return false; + + AutoHashtable<SlowSQLEntryType>& sqlMap = + (privateSQL ? mPrivateSQL : mSanitizedSQL); + AutoHashtable<SlowSQLEntryType>::ReflectEntryFunc reflectFunction = + (mainThread ? ReflectMainThreadSQL : ReflectOtherThreadsSQL); + if (!sqlMap.ReflectIntoJS(reflectFunction, cx, statsObj)) { + return false; + } + + return JS_DefineProperty(cx, rootObj, + mainThread ? "mainThread" : "otherThreads", statsObj, + JSPROP_ENUMERATE); +} + +NS_IMETHODIMP +TelemetryImpl::SetHistogramRecordingEnabled(const nsACString& id, + bool aEnabled) { + return TelemetryHistogram::SetHistogramRecordingEnabled(id, aEnabled); +} + +NS_IMETHODIMP +TelemetryImpl::GetSnapshotForHistograms(const nsACString& aStoreName, + bool aClearStore, bool aFilterTest, + JSContext* aCx, + JS::MutableHandle<JS::Value> aResult) { + constexpr auto defaultStore = "main"_ns; + unsigned int dataset = mCanRecordExtended + ? nsITelemetry::DATASET_PRERELEASE_CHANNELS + : nsITelemetry::DATASET_ALL_CHANNELS; + return TelemetryHistogram::CreateHistogramSnapshots( + aCx, aResult, aStoreName.IsVoid() ? defaultStore : aStoreName, dataset, + aClearStore, aFilterTest); +} + +NS_IMETHODIMP +TelemetryImpl::GetSnapshotForKeyedHistograms( + const nsACString& aStoreName, bool aClearStore, bool aFilterTest, + JSContext* aCx, JS::MutableHandle<JS::Value> aResult) { + constexpr auto defaultStore = "main"_ns; + unsigned int dataset = mCanRecordExtended + ? nsITelemetry::DATASET_PRERELEASE_CHANNELS + : nsITelemetry::DATASET_ALL_CHANNELS; + return TelemetryHistogram::GetKeyedHistogramSnapshots( + aCx, aResult, aStoreName.IsVoid() ? defaultStore : aStoreName, dataset, + aClearStore, aFilterTest); +} + +NS_IMETHODIMP +TelemetryImpl::GetCategoricalLabels(JSContext* aCx, + JS::MutableHandle<JS::Value> aResult) { + return TelemetryHistogram::GetCategoricalHistogramLabels(aCx, aResult); +} + +NS_IMETHODIMP +TelemetryImpl::GetSnapshotForScalars(const nsACString& aStoreName, + bool aClearStore, bool aFilterTest, + JSContext* aCx, + JS::MutableHandle<JS::Value> aResult) { + constexpr auto defaultStore = "main"_ns; + unsigned int dataset = mCanRecordExtended + ? nsITelemetry::DATASET_PRERELEASE_CHANNELS + : nsITelemetry::DATASET_ALL_CHANNELS; + return TelemetryScalar::CreateSnapshots( + dataset, aClearStore, aCx, 1, aResult, aFilterTest, + aStoreName.IsVoid() ? defaultStore : aStoreName); +} + +NS_IMETHODIMP +TelemetryImpl::GetSnapshotForKeyedScalars( + const nsACString& aStoreName, bool aClearStore, bool aFilterTest, + JSContext* aCx, JS::MutableHandle<JS::Value> aResult) { + constexpr auto defaultStore = "main"_ns; + unsigned int dataset = mCanRecordExtended + ? nsITelemetry::DATASET_PRERELEASE_CHANNELS + : nsITelemetry::DATASET_ALL_CHANNELS; + return TelemetryScalar::CreateKeyedSnapshots( + dataset, aClearStore, aCx, 1, aResult, aFilterTest, + aStoreName.IsVoid() ? defaultStore : aStoreName); +} + +bool TelemetryImpl::GetSQLStats(JSContext* cx, JS::MutableHandle<JS::Value> ret, + bool includePrivateSql) { + JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx)); + if (!root_obj) return false; + ret.setObject(*root_obj); + + MutexAutoLock hashMutex(mHashMutex); + // Add info about slow SQL queries on the main thread + if (!AddSQLInfo(cx, root_obj, true, includePrivateSql)) return false; + // Add info about slow SQL queries on other threads + if (!AddSQLInfo(cx, root_obj, false, includePrivateSql)) return false; + + return true; +} + +NS_IMETHODIMP +TelemetryImpl::GetSlowSQL(JSContext* cx, JS::MutableHandle<JS::Value> ret) { + if (GetSQLStats(cx, ret, false)) return NS_OK; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +TelemetryImpl::GetDebugSlowSQL(JSContext* cx, + JS::MutableHandle<JS::Value> ret) { + bool revealPrivateSql = + Preferences::GetBool("toolkit.telemetry.debugSlowSql", false); + if (GetSQLStats(cx, ret, revealPrivateSql)) return NS_OK; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +TelemetryImpl::GetUntrustedModuleLoadEvents(uint32_t aFlags, JSContext* cx, + Promise** aPromise) { +#if defined(XP_WIN) + return Telemetry::GetUntrustedModuleLoadEvents(aFlags, cx, aPromise); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +#if defined(MOZ_GECKO_PROFILER) +class GetLoadedModulesResultRunnable final : public Runnable { + nsMainThreadPtrHandle<Promise> mPromise; + SharedLibraryInfo mRawModules; + nsCOMPtr<nsIThread> mWorkerThread; +# if defined(XP_WIN) + nsTHashMap<nsStringHashKey, nsString> mCertSubjects; +# endif // defined(XP_WIN) + + public: + GetLoadedModulesResultRunnable(const nsMainThreadPtrHandle<Promise>& aPromise, + const SharedLibraryInfo& rawModules) + : mozilla::Runnable("GetLoadedModulesResultRunnable"), + mPromise(aPromise), + mRawModules(rawModules), + mWorkerThread(do_GetCurrentThread()) { + MOZ_ASSERT(!NS_IsMainThread()); +# if defined(XP_WIN) + ObtainCertSubjects(); +# endif // defined(XP_WIN) + } + + NS_IMETHOD + Run() override { + MOZ_ASSERT(NS_IsMainThread()); + + mWorkerThread->Shutdown(); + + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(mPromise->GetGlobalObject()))) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + JSContext* cx = jsapi.cx(); + + JS::Rooted<JSObject*> moduleArray(cx, JS::NewArrayObject(cx, 0)); + if (!moduleArray) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + for (unsigned int i = 0, n = mRawModules.GetSize(); i != n; i++) { + const SharedLibrary& info = mRawModules.GetEntry(i); + + JS::Rooted<JSObject*> moduleObj(cx, JS_NewPlainObject(cx)); + if (!moduleObj) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + // Module name. + JS::Rooted<JSString*> moduleName( + cx, JS_NewUCStringCopyZ(cx, info.GetModuleName().get())); + if (!moduleName || !JS_DefineProperty(cx, moduleObj, "name", moduleName, + JSPROP_ENUMERATE)) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + // Module debug name. + JS::Rooted<JS::Value> moduleDebugName(cx); + + if (!info.GetDebugName().IsEmpty()) { + JS::Rooted<JSString*> str_moduleDebugName( + cx, JS_NewUCStringCopyZ(cx, info.GetDebugName().get())); + if (!str_moduleDebugName) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + moduleDebugName.setString(str_moduleDebugName); + } else { + moduleDebugName.setNull(); + } + + if (!JS_DefineProperty(cx, moduleObj, "debugName", moduleDebugName, + JSPROP_ENUMERATE)) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + // Module Breakpad identifier. + JS::Rooted<JS::Value> id(cx); + + if (!info.GetBreakpadId().IsEmpty()) { + JS::Rooted<JSString*> str_id( + cx, JS_NewStringCopyZ(cx, info.GetBreakpadId().get())); + if (!str_id) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + id.setString(str_id); + } else { + id.setNull(); + } + + if (!JS_DefineProperty(cx, moduleObj, "debugID", id, JSPROP_ENUMERATE)) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + // Module version. + JS::Rooted<JS::Value> version(cx); + + if (!info.GetVersion().IsEmpty()) { + JS::Rooted<JSString*> v( + cx, JS_NewStringCopyZ(cx, info.GetVersion().BeginReading())); + if (!v) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + version.setString(v); + } else { + version.setNull(); + } + + if (!JS_DefineProperty(cx, moduleObj, "version", version, + JSPROP_ENUMERATE)) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + +# if defined(XP_WIN) + // Cert Subject. + if (auto subject = mCertSubjects.Lookup(info.GetModulePath())) { + JS::Rooted<JSString*> jsOrg(cx, ToJSString(cx, *subject)); + if (!jsOrg) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + JS::Rooted<JS::Value> certSubject(cx); + certSubject.setString(jsOrg); + + if (!JS_DefineProperty(cx, moduleObj, "certSubject", certSubject, + JSPROP_ENUMERATE)) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + } +# endif // defined(XP_WIN) + + if (!JS_DefineElement(cx, moduleArray, i, moduleObj, JSPROP_ENUMERATE)) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + } + + mPromise->MaybeResolve(moduleArray); + return NS_OK; + } + + private: +# if defined(XP_WIN) + void ObtainCertSubjects() { + MOZ_ASSERT(!NS_IsMainThread()); + + // NB: Currently we cannot lower this down to the profiler layer due to + // differing startup dependencies between the profiler and DllServices. + RefPtr<DllServices> dllSvc(DllServices::Get()); + + for (unsigned int i = 0, n = mRawModules.GetSize(); i != n; i++) { + const SharedLibrary& info = mRawModules.GetEntry(i); + + auto orgName = dllSvc->GetBinaryOrgName(info.GetModulePath().get()); + if (orgName) { + mCertSubjects.InsertOrUpdate(info.GetModulePath(), + nsDependentString(orgName.get())); + } + } + } +# endif // defined(XP_WIN) +}; + +class GetLoadedModulesRunnable final : public Runnable { + nsMainThreadPtrHandle<Promise> mPromise; + + public: + explicit GetLoadedModulesRunnable( + const nsMainThreadPtrHandle<Promise>& aPromise) + : mozilla::Runnable("GetLoadedModulesRunnable"), mPromise(aPromise) {} + + NS_IMETHOD + Run() override { + nsCOMPtr<nsIRunnable> resultRunnable = new GetLoadedModulesResultRunnable( + mPromise, SharedLibraryInfo::GetInfoForSelf()); + return NS_DispatchToMainThread(resultRunnable); + } +}; +#endif // MOZ_GECKO_PROFILER + +NS_IMETHODIMP +TelemetryImpl::GetLoadedModules(JSContext* cx, Promise** aPromise) { +#if defined(MOZ_GECKO_PROFILER) + nsIGlobalObject* global = xpc::CurrentNativeGlobal(cx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult result; + RefPtr<Promise> promise = Promise::Create(global, result); + if (NS_WARN_IF(result.Failed())) { + return result.StealNSResult(); + } + + nsCOMPtr<nsIThread> getModulesThread; + nsresult rv = + NS_NewNamedThread("TelemetryModule", getter_AddRefs(getModulesThread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->MaybeReject(NS_ERROR_FAILURE); + return NS_OK; + } + + nsMainThreadPtrHandle<Promise> mainThreadPromise( + new nsMainThreadPtrHolder<Promise>( + "TelemetryImpl::GetLoadedModules::Promise", promise)); + nsCOMPtr<nsIRunnable> runnable = + new GetLoadedModulesRunnable(mainThreadPromise); + promise.forget(aPromise); + + return getModulesThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL); +#else // MOZ_GECKO_PROFILER + return NS_ERROR_NOT_IMPLEMENTED; +#endif // MOZ_GECKO_PROFILER +} + +static bool IsValidBreakpadId(const std::string& breakpadId) { + if (breakpadId.size() < 33) { + return false; + } + for (char c : breakpadId) { + if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) { + return false; + } + } + return true; +} + +// Read a stack from the given file name. In case of any error, aStack is +// unchanged. +static void ReadStack(PathCharPtr aFileName, + Telemetry::ProcessedStack& aStack) { + IFStream file(aFileName); + + size_t numModules; + file >> numModules; + if (file.fail()) { + return; + } + + char newline = file.get(); + if (file.fail() || newline != '\n') { + return; + } + + Telemetry::ProcessedStack stack; + for (size_t i = 0; i < numModules; ++i) { + std::string breakpadId; + file >> breakpadId; + if (file.fail() || !IsValidBreakpadId(breakpadId)) { + return; + } + + char space = file.get(); + if (file.fail() || space != ' ') { + return; + } + + std::string moduleName; + getline(file, moduleName); + if (file.fail() || moduleName[0] == ' ') { + return; + } + + Telemetry::ProcessedStack::Module module = { + NS_ConvertUTF8toUTF16(moduleName.c_str()), + nsCString(breakpadId.c_str(), breakpadId.size()), + }; + stack.AddModule(module); + } + + size_t numFrames; + file >> numFrames; + if (file.fail()) { + return; + } + + newline = file.get(); + if (file.fail() || newline != '\n') { + return; + } + + for (size_t i = 0; i < numFrames; ++i) { + uint16_t index; + file >> index; + uintptr_t offset; + file >> std::hex >> offset >> std::dec; + if (file.fail()) { + return; + } + + Telemetry::ProcessedStack::Frame frame = {offset, index}; + stack.AddFrame(frame); + } + + aStack = stack; +} + +void TelemetryImpl::ReadLateWritesStacks(nsIFile* aProfileDir) { + nsCOMPtr<nsIDirectoryEnumerator> files; + if (NS_FAILED(aProfileDir->GetDirectoryEntries(getter_AddRefs(files)))) { + return; + } + + constexpr auto prefix = u"Telemetry.LateWriteFinal-"_ns; + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) { + nsAutoString leafName; + if (NS_FAILED(file->GetLeafName(leafName)) || + !StringBeginsWith(leafName, prefix)) { + continue; + } + + Telemetry::ProcessedStack stack; + ReadStack(file->NativePath().get(), stack); + if (stack.GetStackSize() != 0) { + mLateWritesStacks.AddStack(stack); + } + // Delete the file so that we don't report it again on the next run. + file->Remove(false); + } +} + +NS_IMETHODIMP +TelemetryImpl::GetLateWrites(JSContext* cx, JS::MutableHandle<JS::Value> ret) { + // The user must call AsyncReadTelemetryData first. We return an empty list + // instead of reporting a failure so that the rest of telemetry can uniformly + // handle the read not being available yet. + + // FIXME: we allocate the js object again and again in the getter. We should + // figure out a way to cache it. In order to do that we have to call + // JS_AddNamedObjectRoot. A natural place to do so is in the TelemetryImpl + // constructor, but it is not clear how to get a JSContext in there. + // Another option would be to call it in here when we first call + // CreateJSStackObject, but we would still need to figure out where to call + // JS_RemoveObjectRoot. Would it be ok to never call JS_RemoveObjectRoot + // and just set the pointer to nullptr is the telemetry destructor? + + JSObject* report; + if (!mCachedTelemetryData) { + CombinedStacks empty; + report = CreateJSStackObject(cx, empty); + } else { + report = CreateJSStackObject(cx, mLateWritesStacks); + } + + if (report == nullptr) { + return NS_ERROR_FAILURE; + } + + ret.setObject(*report); + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::GetHistogramById(const nsACString& name, JSContext* cx, + JS::MutableHandle<JS::Value> ret) { + return TelemetryHistogram::GetHistogramById(name, cx, ret); +} + +NS_IMETHODIMP +TelemetryImpl::GetKeyedHistogramById(const nsACString& name, JSContext* cx, + JS::MutableHandle<JS::Value> ret) { + return TelemetryHistogram::GetKeyedHistogramById(name, cx, ret); +} + +/** + * Indicates if Telemetry can record base data (FHR data). This is true if the + * FHR data reporting service or self-support are enabled. + * + * In the unlikely event that adding a new base probe is needed, please check + * the data collection wiki at https://wiki.mozilla.org/Firefox/Data_Collection + * and talk to the Telemetry team. + */ +NS_IMETHODIMP +TelemetryImpl::GetCanRecordBase(bool* ret) { + *ret = mCanRecordBase; + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::SetCanRecordBase(bool canRecord) { +#ifndef FUZZING + if (canRecord != mCanRecordBase) { + TelemetryHistogram::SetCanRecordBase(canRecord); + TelemetryScalar::SetCanRecordBase(canRecord); + TelemetryEvent::SetCanRecordBase(canRecord); + mCanRecordBase = canRecord; + } +#endif + return NS_OK; +} + +/** + * Indicates if Telemetry is allowed to record extended data. Returns false if + * the user hasn't opted into "extended Telemetry" on the Release channel, when + * the user has explicitly opted out of Telemetry on Nightly/Aurora/Beta or if + * manually set to false during tests. If the returned value is false, gathering + * of extended telemetry statistics is disabled. + */ +NS_IMETHODIMP +TelemetryImpl::GetCanRecordExtended(bool* ret) { + *ret = mCanRecordExtended; + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::SetCanRecordExtended(bool canRecord) { +#ifndef FUZZING + if (canRecord != mCanRecordExtended) { + TelemetryHistogram::SetCanRecordExtended(canRecord); + TelemetryScalar::SetCanRecordExtended(canRecord); + TelemetryEvent::SetCanRecordExtended(canRecord); + mCanRecordExtended = canRecord; + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::GetCanRecordReleaseData(bool* ret) { + *ret = mCanRecordBase; + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::GetCanRecordPrereleaseData(bool* ret) { + *ret = mCanRecordExtended; + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::GetIsOfficialTelemetry(bool* ret) { +#if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING) && \ + !defined(DEBUG) + *ret = true; +#else + *ret = false; +#endif + return NS_OK; +} + +already_AddRefed<nsITelemetry> TelemetryImpl::CreateTelemetryInstance() { + { + auto lock = sTelemetry.Lock(); + MOZ_ASSERT( + *lock == nullptr, + "CreateTelemetryInstance may only be called once, via GetService()"); + } + + bool useTelemetry = false; +#ifndef FUZZING + if (XRE_IsParentProcess() || XRE_IsContentProcess() || XRE_IsGPUProcess() || + XRE_IsSocketProcess() || XRE_IsUtilityProcess()) { + useTelemetry = true; + } +#endif +#ifdef MOZ_BACKGROUNDTASKS + if (BackgroundTasks::IsBackgroundTaskMode()) { + // Background tasks collect per-task metrics with Glean. + useTelemetry = false; + } +#endif + + // First, initialize the TelemetryHistogram and TelemetryScalar global states. + TelemetryHistogram::InitializeGlobalState(useTelemetry, useTelemetry); + TelemetryScalar::InitializeGlobalState(useTelemetry, useTelemetry); + + // Only record events from the parent process. + TelemetryEvent::InitializeGlobalState(XRE_IsParentProcess(), + XRE_IsParentProcess()); + TelemetryOrigin::InitializeGlobalState(); + // Currently, only UserInteractions from the parent process are recorded. + TelemetryUserInteraction::InitializeGlobalState(useTelemetry, useTelemetry); + + // Now, create and initialize the Telemetry global state. + TelemetryImpl* telemetry = new TelemetryImpl(); + { + auto lock = sTelemetry.Lock(); + *lock = telemetry; + // AddRef for the local reference before releasing the lock. + NS_ADDREF(telemetry); + } + + // AddRef for the caller + nsCOMPtr<nsITelemetry> ret = telemetry; + + telemetry->mCanRecordBase = useTelemetry; + telemetry->mCanRecordExtended = useTelemetry; + + telemetry->InitMemoryReporter(); + InitHistogramRecordingEnabled(); // requires sTelemetry to exist + + return ret.forget(); +} + +void TelemetryImpl::ShutdownTelemetry() { + // No point in collecting IO beyond this point + ClearIOReporting(); + { + auto lock = sTelemetry.Lock(); + NS_IF_RELEASE(lock.ref()); + } + + // Lastly, de-initialise the TelemetryHistogram and TelemetryScalar global + // states, so as to release any heap storage that would otherwise be kept + // alive by it. + TelemetryHistogram::DeInitializeGlobalState(); + TelemetryScalar::DeInitializeGlobalState(); + TelemetryEvent::DeInitializeGlobalState(); + TelemetryOrigin::DeInitializeGlobalState(); + TelemetryUserInteraction::DeInitializeGlobalState(); + TelemetryIPCAccumulator::DeInitializeGlobalState(); +} + +void TelemetryImpl::StoreSlowSQL(const nsACString& sql, uint32_t delay, + SanitizedState state) { + auto lock = sTelemetry.Lock(); + auto telemetry = lock.ref(); + AutoHashtable<SlowSQLEntryType>* slowSQLMap = nullptr; + if (state == Sanitized) + slowSQLMap = &(telemetry->mSanitizedSQL); + else + slowSQLMap = &(telemetry->mPrivateSQL); + + MutexAutoLock hashMutex(telemetry->mHashMutex); + + SlowSQLEntryType* entry = slowSQLMap->GetEntry(sql); + if (!entry) { + entry = slowSQLMap->PutEntry(sql); + if (MOZ_UNLIKELY(!entry)) return; + entry->GetModifiableData()->mainThread.hitCount = 0; + entry->GetModifiableData()->mainThread.totalTime = 0; + entry->GetModifiableData()->otherThreads.hitCount = 0; + entry->GetModifiableData()->otherThreads.totalTime = 0; + } + + if (NS_IsMainThread()) { + entry->GetModifiableData()->mainThread.hitCount++; + entry->GetModifiableData()->mainThread.totalTime += delay; + } else { + entry->GetModifiableData()->otherThreads.hitCount++; + entry->GetModifiableData()->otherThreads.totalTime += delay; + } +} + +/** + * This method replaces string literals in SQL strings with the word :private + * + * States used in this state machine: + * + * NORMAL: + * - This is the active state when not iterating over a string literal or + * comment + * + * SINGLE_QUOTE: + * - Defined here: http://www.sqlite.org/lang_expr.html + * - This state represents iterating over a string literal opened with + * a single quote. + * - A single quote within the string can be encoded by putting 2 single quotes + * in a row, e.g. 'This literal contains an escaped quote ''' + * - Any double quotes found within a single-quoted literal are ignored + * - This state covers BLOB literals, e.g. X'ABC123' + * - The string literal and the enclosing quotes will be replaced with + * the text :private + * + * DOUBLE_QUOTE: + * - Same rules as the SINGLE_QUOTE state. + * - According to http://www.sqlite.org/lang_keywords.html, + * SQLite interprets text in double quotes as an identifier unless it's used in + * a context where it cannot be resolved to an identifier and a string literal + * is allowed. This method removes text in double-quotes for safety. + * + * DASH_COMMENT: + * - http://www.sqlite.org/lang_comment.html + * - A dash comment starts with two dashes in a row, + * e.g. DROP TABLE foo -- a comment + * - Any text following two dashes in a row is interpreted as a comment until + * end of input or a newline character + * - Any quotes found within the comment are ignored and no replacements made + * + * C_STYLE_COMMENT: + * - http://www.sqlite.org/lang_comment.html + * - A C-style comment starts with a forward slash and an asterisk, and ends + * with an asterisk and a forward slash + * - Any text following comment start is interpreted as a comment up to end of + * input or comment end + * - Any quotes found within the comment are ignored and no replacements made + */ +nsCString TelemetryImpl::SanitizeSQL(const nsACString& sql) { + nsCString output; + int length = sql.Length(); + + typedef enum { + NORMAL, + SINGLE_QUOTE, + DOUBLE_QUOTE, + DASH_COMMENT, + C_STYLE_COMMENT, + } State; + + State state = NORMAL; + int fragmentStart = 0; + for (int i = 0; i < length; i++) { + char character = sql[i]; + char nextCharacter = (i + 1 < length) ? sql[i + 1] : '\0'; + + switch (character) { + case '\'': + case '"': + if (state == NORMAL) { + state = (character == '\'') ? SINGLE_QUOTE : DOUBLE_QUOTE; + output += + nsDependentCSubstring(sql, fragmentStart, i - fragmentStart); + output += ":private"; + fragmentStart = -1; + } else if ((state == SINGLE_QUOTE && character == '\'') || + (state == DOUBLE_QUOTE && character == '"')) { + if (nextCharacter == character) { + // Two consecutive quotes within a string literal are a single + // escaped quote + i++; + } else { + state = NORMAL; + fragmentStart = i + 1; + } + } + break; + case '-': + if (state == NORMAL) { + if (nextCharacter == '-') { + state = DASH_COMMENT; + i++; + } + } + break; + case '\n': + if (state == DASH_COMMENT) { + state = NORMAL; + } + break; + case '/': + if (state == NORMAL) { + if (nextCharacter == '*') { + state = C_STYLE_COMMENT; + i++; + } + } + break; + case '*': + if (state == C_STYLE_COMMENT) { + if (nextCharacter == '/') { + state = NORMAL; + } + } + break; + default: + continue; + } + } + + if ((fragmentStart >= 0) && fragmentStart < length) + output += nsDependentCSubstring(sql, fragmentStart, length - fragmentStart); + + return output; +} + +// An allowlist mechanism to prevent Telemetry reporting on Addon & Thunderbird +// DBs. +struct TrackedDBEntry { + const char* mName; + const uint32_t mNameLength; + + // This struct isn't meant to be used beyond the static arrays below. + constexpr TrackedDBEntry(const char* aName, uint32_t aNameLength) + : mName(aName), mNameLength(aNameLength) {} + + TrackedDBEntry() = delete; + TrackedDBEntry(TrackedDBEntry&) = delete; +}; + +#define TRACKEDDB_ENTRY(_name) \ + { _name, (sizeof(_name) - 1) } + +// An allowlist of database names. If the database name exactly matches one of +// these then its SQL statements will always be recorded. +static constexpr TrackedDBEntry kTrackedDBs[] = { + // IndexedDB for about:home, see aboutHome.js + TRACKEDDB_ENTRY("818200132aebmoouht.sqlite"), + TRACKEDDB_ENTRY("addons.sqlite"), + TRACKEDDB_ENTRY("content-prefs.sqlite"), + TRACKEDDB_ENTRY("cookies.sqlite"), + TRACKEDDB_ENTRY("extensions.sqlite"), + TRACKEDDB_ENTRY("favicons.sqlite"), + TRACKEDDB_ENTRY("formhistory.sqlite"), + TRACKEDDB_ENTRY("index.sqlite"), + TRACKEDDB_ENTRY("netpredictions.sqlite"), + TRACKEDDB_ENTRY("permissions.sqlite"), + TRACKEDDB_ENTRY("places.sqlite"), + TRACKEDDB_ENTRY("reading-list.sqlite"), + TRACKEDDB_ENTRY("search.sqlite"), + TRACKEDDB_ENTRY("signons.sqlite"), + TRACKEDDB_ENTRY("urlclassifier3.sqlite"), + TRACKEDDB_ENTRY("webappsstore.sqlite")}; + +// An allowlist of database name prefixes. If the database name begins with +// one of these prefixes then its SQL statements will always be recorded. +static const TrackedDBEntry kTrackedDBPrefixes[] = { + TRACKEDDB_ENTRY("indexedDB-")}; + +#undef TRACKEDDB_ENTRY + +// Slow SQL statements will be automatically +// trimmed to kMaxSlowStatementLength characters. +// This limit doesn't include the ellipsis and DB name, +// that are appended at the end of the stored statement. +const uint32_t kMaxSlowStatementLength = 1000; + +void TelemetryImpl::RecordSlowStatement(const nsACString& sql, + const nsACString& dbName, + uint32_t delay) { + MOZ_ASSERT(!sql.IsEmpty()); + MOZ_ASSERT(!dbName.IsEmpty()); + + { + auto lock = sTelemetry.Lock(); + if (!lock.ref() || !TelemetryHistogram::CanRecordExtended()) { + return; + } + } + + bool recordStatement = false; + + for (const TrackedDBEntry& nameEntry : kTrackedDBs) { + MOZ_ASSERT(nameEntry.mNameLength); + const nsDependentCString name(nameEntry.mName, nameEntry.mNameLength); + if (dbName == name) { + recordStatement = true; + break; + } + } + + if (!recordStatement) { + for (const TrackedDBEntry& prefixEntry : kTrackedDBPrefixes) { + MOZ_ASSERT(prefixEntry.mNameLength); + const nsDependentCString prefix(prefixEntry.mName, + prefixEntry.mNameLength); + if (StringBeginsWith(dbName, prefix)) { + recordStatement = true; + break; + } + } + } + + if (recordStatement) { + nsAutoCString sanitizedSQL(SanitizeSQL(sql)); + if (sanitizedSQL.Length() > kMaxSlowStatementLength) { + sanitizedSQL.SetLength(kMaxSlowStatementLength); + sanitizedSQL += "..."; + } + sanitizedSQL.AppendPrintf(" /* %s */", nsPromiseFlatCString(dbName).get()); + StoreSlowSQL(sanitizedSQL, delay, Sanitized); + } else { + // Report aggregate DB-level statistics for addon DBs + nsAutoCString aggregate; + aggregate.AppendPrintf("Untracked SQL for %s", + nsPromiseFlatCString(dbName).get()); + StoreSlowSQL(aggregate, delay, Sanitized); + } + + nsAutoCString fullSQL; + fullSQL.AppendPrintf("%s /* %s */", nsPromiseFlatCString(sql).get(), + nsPromiseFlatCString(dbName).get()); + StoreSlowSQL(fullSQL, delay, Unsanitized); +} + +bool TelemetryImpl::CanRecordBase() { + auto lock = sTelemetry.Lock(); + auto telemetry = lock.ref(); + if (!telemetry) { + return false; + } + bool canRecordBase; + nsresult rv = telemetry->GetCanRecordBase(&canRecordBase); + return NS_SUCCEEDED(rv) && canRecordBase; +} + +bool TelemetryImpl::CanRecordExtended() { + auto lock = sTelemetry.Lock(); + auto telemetry = lock.ref(); + if (!telemetry) { + return false; + } + bool canRecordExtended; + nsresult rv = telemetry->GetCanRecordExtended(&canRecordExtended); + return NS_SUCCEEDED(rv) && canRecordExtended; +} + +bool TelemetryImpl::CanRecordReleaseData() { return CanRecordBase(); } + +bool TelemetryImpl::CanRecordPrereleaseData() { return CanRecordExtended(); } + +NS_IMPL_ISUPPORTS(TelemetryImpl, nsITelemetry, nsIMemoryReporter) + +NS_IMETHODIMP +TelemetryImpl::GetFileIOReports(JSContext* cx, + JS::MutableHandle<JS::Value> ret) { + if (sTelemetryIOObserver) { + JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx)); + if (!obj) { + return NS_ERROR_FAILURE; + } + + if (!sTelemetryIOObserver->ReflectIntoJS(cx, obj)) { + return NS_ERROR_FAILURE; + } + ret.setObject(*obj); + return NS_OK; + } + ret.setNull(); + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::MsSinceProcessStart(double* aResult) { + return Telemetry::Common::MsSinceProcessStart(aResult); +} + +NS_IMETHODIMP +TelemetryImpl::MsSinceProcessStartIncludingSuspend(double* aResult) { + return Telemetry::Common::MsSinceProcessStartIncludingSuspend(aResult); +} + +NS_IMETHODIMP +TelemetryImpl::MsSinceProcessStartExcludingSuspend(double* aResult) { + return Telemetry::Common::MsSinceProcessStartExcludingSuspend(aResult); +} + +NS_IMETHODIMP +TelemetryImpl::MsSystemNow(double* aResult) { +#if defined(XP_UNIX) && !defined(XP_DARWIN) + timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + *aResult = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +#else + using namespace std::chrono; + milliseconds ms = + duration_cast<milliseconds>(system_clock::now().time_since_epoch()); + *aResult = static_cast<double>(ms.count()); +#endif // XP_UNIX && !XP_DARWIN + + return NS_OK; +} + +// Telemetry Scalars IDL Implementation + +NS_IMETHODIMP +TelemetryImpl::ScalarAdd(const nsACString& aName, JS::Handle<JS::Value> aVal, + JSContext* aCx) { + return TelemetryScalar::Add(aName, aVal, aCx); +} + +NS_IMETHODIMP +TelemetryImpl::ScalarSet(const nsACString& aName, JS::Handle<JS::Value> aVal, + JSContext* aCx) { + return TelemetryScalar::Set(aName, aVal, aCx); +} + +NS_IMETHODIMP +TelemetryImpl::ScalarSetMaximum(const nsACString& aName, + JS::Handle<JS::Value> aVal, JSContext* aCx) { + return TelemetryScalar::SetMaximum(aName, aVal, aCx); +} + +NS_IMETHODIMP +TelemetryImpl::KeyedScalarAdd(const nsACString& aName, const nsAString& aKey, + JS::Handle<JS::Value> aVal, JSContext* aCx) { + return TelemetryScalar::Add(aName, aKey, aVal, aCx); +} + +NS_IMETHODIMP +TelemetryImpl::KeyedScalarSet(const nsACString& aName, const nsAString& aKey, + JS::Handle<JS::Value> aVal, JSContext* aCx) { + return TelemetryScalar::Set(aName, aKey, aVal, aCx); +} + +NS_IMETHODIMP +TelemetryImpl::KeyedScalarSetMaximum(const nsACString& aName, + const nsAString& aKey, + JS::Handle<JS::Value> aVal, + JSContext* aCx) { + return TelemetryScalar::SetMaximum(aName, aKey, aVal, aCx); +} + +NS_IMETHODIMP +TelemetryImpl::RegisterScalars(const nsACString& aCategoryName, + JS::Handle<JS::Value> aScalarData, + JSContext* cx) { + return TelemetryScalar::RegisterScalars(aCategoryName, aScalarData, false, + cx); +} + +NS_IMETHODIMP +TelemetryImpl::RegisterBuiltinScalars(const nsACString& aCategoryName, + JS::Handle<JS::Value> aScalarData, + JSContext* cx) { + return TelemetryScalar::RegisterScalars(aCategoryName, aScalarData, true, cx); +} + +NS_IMETHODIMP +TelemetryImpl::ClearScalars() { + TelemetryScalar::ClearScalars(); + return NS_OK; +} + +// Telemetry Event IDL implementation. + +NS_IMETHODIMP +TelemetryImpl::RecordEvent(const nsACString& aCategory, + const nsACString& aMethod, const nsACString& aObject, + JS::Handle<JS::Value> aValue, + JS::Handle<JS::Value> aExtra, JSContext* aCx, + uint8_t optional_argc) { + return TelemetryEvent::RecordEvent(aCategory, aMethod, aObject, aValue, + aExtra, aCx, optional_argc); +} + +NS_IMETHODIMP +TelemetryImpl::SnapshotEvents(uint32_t aDataset, bool aClear, + uint32_t aEventLimit, JSContext* aCx, + uint8_t optional_argc, + JS::MutableHandle<JS::Value> aResult) { + return TelemetryEvent::CreateSnapshots(aDataset, aClear, aEventLimit, aCx, + optional_argc, aResult); +} + +NS_IMETHODIMP +TelemetryImpl::RegisterEvents(const nsACString& aCategory, + JS::Handle<JS::Value> aEventData, JSContext* cx) { + return TelemetryEvent::RegisterEvents(aCategory, aEventData, false, cx); +} + +NS_IMETHODIMP +TelemetryImpl::RegisterBuiltinEvents(const nsACString& aCategory, + JS::Handle<JS::Value> aEventData, + JSContext* cx) { + return TelemetryEvent::RegisterEvents(aCategory, aEventData, true, cx); +} + +NS_IMETHODIMP +TelemetryImpl::ClearEvents() { + TelemetryEvent::ClearEvents(); + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::SetEventRecordingEnabled(const nsACString& aCategory, + bool aEnabled) { + TelemetryEvent::SetEventRecordingEnabled(aCategory, aEnabled); + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::GetOriginSnapshot(bool aClear, JSContext* aCx, + JS::MutableHandle<JS::Value> aResult) { + return TelemetryOrigin::GetOriginSnapshot(aClear, aCx, aResult); +} + +NS_IMETHODIMP +TelemetryImpl::GetEncodedOriginSnapshot(bool aClear, JSContext* aCx, + Promise** aResult) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + NS_ENSURE_ARG_POINTER(aResult); + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + ErrorResult erv; + RefPtr<Promise> promise = Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + // TODO: Put this all on a Worker Thread + + JS::Rooted<JS::Value> snapshot(aCx); + nsresult rv; + rv = TelemetryOrigin::GetEncodedOriginSnapshot(aClear, aCx, &snapshot); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + promise->MaybeResolve(snapshot); + promise.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::ClearOrigins() { + TelemetryOrigin::ClearOrigins(); + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::FlushBatchedChildTelemetry() { + TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr); + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::EarlyInit() { + Unused << MemoryTelemetry::Get(); + + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::DelayedInit() { + MemoryTelemetry::Get().DelayedInit(); + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::Shutdown() { + MemoryTelemetry::Get().Shutdown(); + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::GatherMemory(JSContext* aCx, Promise** aResult) { + ErrorResult rv; + RefPtr<Promise> promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + MemoryTelemetry::Get().GatherReports( + [promise]() { promise->MaybeResolve(JS::UndefinedHandleValue); }); + + promise.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::GetAllStores(JSContext* aCx, + JS::MutableHandle<JS::Value> aResult) { + StringHashSet stores; + nsresult rv; + + rv = TelemetryHistogram::GetAllStores(stores); + if (NS_FAILED(rv)) { + return rv; + } + rv = TelemetryScalar::GetAllStores(stores); + if (NS_FAILED(rv)) { + return rv; + } + + JS::RootedVector<JS::Value> allStores(aCx); + if (!allStores.reserve(stores.Count())) { + return NS_ERROR_FAILURE; + } + + for (const auto& value : stores) { + JS::Rooted<JS::Value> store(aCx); + + store.setString(ToJSString(aCx, value)); + if (!allStores.append(store)) { + return NS_ERROR_FAILURE; + } + } + + JS::Rooted<JSObject*> rarray(aCx, JS::NewArrayObject(aCx, allStores)); + if (rarray == nullptr) { + return NS_ERROR_FAILURE; + } + aResult.setObject(*rarray); + + return NS_OK; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS in no name space +// These are NOT listed in Telemetry.h + +/** + * The XRE_TelemetryAdd function is to be used by embedding applications + * that can't use mozilla::Telemetry::Accumulate() directly. + */ +void XRE_TelemetryAccumulate(int aID, uint32_t aSample) { + mozilla::Telemetry::Accumulate((mozilla::Telemetry::HistogramID)aID, aSample); +} + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS in mozilla:: +// These are NOT listed in Telemetry.h + +namespace mozilla { + +void RecordShutdownStartTimeStamp() { +#ifdef DEBUG + // FIXME: this function should only be called once, since it should be called + // at the earliest point we *know* we are shutting down. Unfortunately + // this assert has been firing. Given that if we are called multiple times + // we just keep the last timestamp, the assert is commented for now. + static bool recorded = false; + // MOZ_ASSERT(!recorded); + (void) + recorded; // Silence unused-var warnings (remove when assert re-enabled) + recorded = true; +#endif + + if (!Telemetry::CanRecordExtended()) return; + + gRecordedShutdownStartTime = TimeStamp::Now(); + + GetShutdownTimeFileName(); +} + +void RecordShutdownEndTimeStamp() { + if (!gRecordedShutdownTimeFileName || gAlreadyFreedShutdownTimeFileName) + return; + + PathString name(gRecordedShutdownTimeFileName); + free(const_cast<PathChar*>(gRecordedShutdownTimeFileName)); + gRecordedShutdownTimeFileName = nullptr; + gAlreadyFreedShutdownTimeFileName = true; + + if (gRecordedShutdownStartTime.IsNull()) { + // If |CanRecordExtended()| is true before |AsyncFetchTelemetryData| is + // called and then disabled before shutdown, |RecordShutdownStartTimeStamp| + // will bail out and we will end up with a null |gRecordedShutdownStartTime| + // here. This can happen during tests. + return; + } + + nsTAutoString<PathChar> tmpName(name); + tmpName.AppendLiteral(".tmp"); + RefPtr<nsLocalFile> tmpFile = new nsLocalFile(tmpName); + FILE* f; + if (NS_FAILED(tmpFile->OpenANSIFileDesc("w", &f)) || !f) return; + // On a normal release build this should be called just before + // calling _exit, but on a debug build or when the user forces a full + // shutdown this is called as late as possible, so we have to + // allow this write as write poisoning will be enabled. + MozillaRegisterDebugFILE(f); + + TimeStamp now = TimeStamp::Now(); + MOZ_ASSERT(now >= gRecordedShutdownStartTime); + TimeDuration diff = now - gRecordedShutdownStartTime; + uint32_t diff2 = diff.ToMilliseconds(); + int written = fprintf(f, "%d\n", diff2); + MozillaUnRegisterDebugFILE(f); + int rv = fclose(f); + if (written < 0 || rv != 0) { + tmpFile->Remove(false); + return; + } + RefPtr<nsLocalFile> file = new nsLocalFile(name); + nsAutoString leafName; + file->GetLeafName(leafName); + tmpFile->RenameTo(nullptr, leafName); +} + +} // namespace mozilla + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS in mozilla::Telemetry:: +// These are listed in Telemetry.h + +namespace mozilla::Telemetry { + +// The external API for controlling recording state +void SetHistogramRecordingEnabled(HistogramID aID, bool aEnabled) { + TelemetryHistogram::SetHistogramRecordingEnabled(aID, aEnabled); +} + +void Accumulate(HistogramID aHistogram, uint32_t aSample) { + TelemetryHistogram::Accumulate(aHistogram, aSample); +} + +void Accumulate(HistogramID aHistogram, const nsTArray<uint32_t>& aSamples) { + TelemetryHistogram::Accumulate(aHistogram, aSamples); +} + +void Accumulate(HistogramID aID, const nsCString& aKey, uint32_t aSample) { + TelemetryHistogram::Accumulate(aID, aKey, aSample); +} + +void Accumulate(HistogramID aID, const nsCString& aKey, + const nsTArray<uint32_t>& aSamples) { + TelemetryHistogram::Accumulate(aID, aKey, aSamples); +} + +void Accumulate(const char* name, uint32_t sample) { + TelemetryHistogram::Accumulate(name, sample); +} + +void Accumulate(const char* name, const nsCString& key, uint32_t sample) { + TelemetryHistogram::Accumulate(name, key, sample); +} + +void AccumulateCategorical(HistogramID id, const nsCString& label) { + TelemetryHistogram::AccumulateCategorical(id, label); +} + +void AccumulateCategorical(HistogramID id, const nsTArray<nsCString>& labels) { + TelemetryHistogram::AccumulateCategorical(id, labels); +} + +void AccumulateTimeDelta(HistogramID aHistogram, TimeStamp start, + TimeStamp end) { + if (start > end) { + Accumulate(aHistogram, 0); + return; + } + Accumulate(aHistogram, static_cast<uint32_t>((end - start).ToMilliseconds())); +} + +void AccumulateTimeDelta(HistogramID aHistogram, const nsCString& key, + TimeStamp start, TimeStamp end) { + if (start > end) { + Accumulate(aHistogram, key, 0); + return; + } + Accumulate(aHistogram, key, + static_cast<uint32_t>((end - start).ToMilliseconds())); +} +const char* GetHistogramName(HistogramID id) { + return TelemetryHistogram::GetHistogramName(id); +} + +bool CanRecordBase() { return TelemetryImpl::CanRecordBase(); } + +bool CanRecordExtended() { return TelemetryImpl::CanRecordExtended(); } + +bool CanRecordReleaseData() { return TelemetryImpl::CanRecordReleaseData(); } + +bool CanRecordPrereleaseData() { + return TelemetryImpl::CanRecordPrereleaseData(); +} + +void RecordSlowSQLStatement(const nsACString& statement, + const nsACString& dbName, uint32_t delay) { + TelemetryImpl::RecordSlowStatement(statement, dbName, delay); +} + +void Init() { + // Make the service manager hold a long-lived reference to the service + nsCOMPtr<nsITelemetry> telemetryService = + do_GetService("@mozilla.org/base/telemetry;1"); + MOZ_ASSERT(telemetryService); +} + +void WriteFailedProfileLock(nsIFile* aProfileDir) { + nsCOMPtr<nsIFile> file; + nsresult rv = GetFailedProfileLockFile(getter_AddRefs(file), aProfileDir); + NS_ENSURE_SUCCESS_VOID(rv); + int64_t fileSize = 0; + rv = file->GetFileSize(&fileSize); + // It's expected that the file might not exist yet + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { + return; + } + nsCOMPtr<nsIRandomAccessStream> fileRandomAccessStream; + rv = NS_NewLocalFileRandomAccessStream(getter_AddRefs(fileRandomAccessStream), + file, PR_RDWR | PR_CREATE_FILE, 0640); + NS_ENSURE_SUCCESS_VOID(rv); + NS_ENSURE_TRUE_VOID(fileSize <= kMaxFailedProfileLockFileSize); + unsigned int failedLockCount = 0; + if (fileSize > 0) { + nsCOMPtr<nsIInputStream> inStream = + do_QueryInterface(fileRandomAccessStream); + NS_ENSURE_TRUE_VOID(inStream); + if (!GetFailedLockCount(inStream, fileSize, failedLockCount)) { + failedLockCount = 0; + } + } + ++failedLockCount; + nsAutoCString bufStr; + bufStr.AppendInt(static_cast<int>(failedLockCount)); + // If we read in an existing failed lock count, we need to reset the file ptr + if (fileSize > 0) { + rv = fileRandomAccessStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); + NS_ENSURE_SUCCESS_VOID(rv); + } + nsCOMPtr<nsIOutputStream> outStream = + do_QueryInterface(fileRandomAccessStream); + uint32_t bytesLeft = bufStr.Length(); + const char* bytes = bufStr.get(); + do { + uint32_t written = 0; + rv = outStream->Write(bytes, bytesLeft, &written); + if (NS_FAILED(rv)) { + break; + } + bytes += written; + bytesLeft -= written; + } while (bytesLeft > 0); + fileRandomAccessStream->SetEOF(); +} + +void InitIOReporting(nsIFile* aXreDir) { + // Never initialize twice + if (sTelemetryIOObserver) { + return; + } + + sTelemetryIOObserver = new TelemetryIOInterposeObserver(aXreDir); + IOInterposer::Register(IOInterposeObserver::OpAllWithStaging, + sTelemetryIOObserver); +} + +void SetProfileDir(nsIFile* aProfD) { + if (!sTelemetryIOObserver || !aProfD) { + return; + } + nsAutoString profDirPath; + nsresult rv = aProfD->GetPath(profDirPath); + if (NS_FAILED(rv)) { + return; + } + sTelemetryIOObserver->AddPath(profDirPath, u"{profile}"_ns); +} + +// Scalar API C++ Endpoints + +void ScalarAdd(mozilla::Telemetry::ScalarID aId, uint32_t aVal) { + TelemetryScalar::Add(aId, aVal); +} + +void ScalarSet(mozilla::Telemetry::ScalarID aId, uint32_t aVal) { + TelemetryScalar::Set(aId, aVal); +} + +void ScalarSet(mozilla::Telemetry::ScalarID aId, bool aVal) { + TelemetryScalar::Set(aId, aVal); +} + +void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aVal) { + TelemetryScalar::Set(aId, aVal); +} + +void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aVal) { + TelemetryScalar::SetMaximum(aId, aVal); +} + +void ScalarAdd(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + uint32_t aVal) { + TelemetryScalar::Add(aId, aKey, aVal); +} + +void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + uint32_t aVal) { + TelemetryScalar::Set(aId, aKey, aVal); +} + +void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + bool aVal) { + TelemetryScalar::Set(aId, aKey, aVal); +} + +void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + uint32_t aVal) { + TelemetryScalar::SetMaximum(aId, aKey, aVal); +} + +void RecordEvent( + mozilla::Telemetry::EventID aId, const mozilla::Maybe<nsCString>& aValue, + const mozilla::Maybe<CopyableTArray<EventExtraEntry>>& aExtra) { + TelemetryEvent::RecordEventNative(aId, aValue, aExtra); +} + +void SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled) { + TelemetryEvent::SetEventRecordingEnabled(aCategory, aEnabled); +} + +void RecordOrigin(mozilla::Telemetry::OriginMetricID aId, + const nsACString& aOrigin) { + TelemetryOrigin::RecordOrigin(aId, aOrigin); +} + +void ShutdownTelemetry() { TelemetryImpl::ShutdownTelemetry(); } + +} // namespace mozilla::Telemetry + +NS_IMPL_COMPONENT_FACTORY(nsITelemetry) { + return TelemetryImpl::CreateTelemetryInstance().downcast<nsISupports>(); +} diff --git a/toolkit/components/telemetry/core/Telemetry.h b/toolkit/components/telemetry/core/Telemetry.h new file mode 100644 index 0000000000..285516bdec --- /dev/null +++ b/toolkit/components/telemetry/core/Telemetry.h @@ -0,0 +1,590 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Telemetry_h__ +#define Telemetry_h__ + +#include "mozilla/Maybe.h" +#include "mozilla/TelemetryEventEnums.h" +#include "mozilla/TelemetryHistogramEnums.h" +#include "mozilla/TelemetryOriginEnums.h" +#include "mozilla/TelemetryScalarEnums.h" +#include "mozilla/TimeStamp.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsXULAppAPI.h" + +/****************************************************************************** + * This implements the Telemetry system. + * It allows recording into histograms as well some more specialized data + * points and gives access to the data. + * + * For documentation on how to add and use new Telemetry probes, see: + * https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/start/adding-a-new-probe.html + * + * For more general information on Telemetry see: + * https://wiki.mozilla.org/Telemetry + *****************************************************************************/ + +namespace mozilla { +namespace Telemetry { + +struct HistogramAccumulation; +struct KeyedHistogramAccumulation; +struct ScalarAction; +struct KeyedScalarAction; +struct ChildEventData; + +struct EventExtraEntry { + nsCString key; + nsCString value; +}; + +/** + * Initialize the Telemetry service on the main thread at startup. + */ +void Init(); + +/** + * Shutdown the Telemetry service. + */ +void ShutdownTelemetry(); + +/** + * Adds sample to a histogram defined in TelemetryHistogramEnums.h + * + * @param id - histogram id + * @param sample - value to record. + */ +void Accumulate(HistogramID id, uint32_t sample); + +/** + * Adds an array of samples to a histogram defined in TelemetryHistograms.h + * @param id - histogram id + * @param samples - values to record. + */ +void Accumulate(HistogramID id, const nsTArray<uint32_t>& samples); + +/** + * Adds sample to a keyed histogram defined in TelemetryHistogramEnums.h + * + * @param id - keyed histogram id + * @param key - the string key + * @param sample - (optional) value to record, defaults to 1. + */ +void Accumulate(HistogramID id, const nsCString& key, uint32_t sample = 1); + +/** + * Adds an array of samples to a histogram defined in TelemetryHistograms.h + * @param id - histogram id + * @param samples - values to record. + * @param key - the string key + */ +void Accumulate(HistogramID id, const nsCString& key, + const nsTArray<uint32_t>& samples); + +/** + * Adds a sample to a histogram defined in TelemetryHistogramEnums.h. + * This function is here to support telemetry measurements from Java, + * where we have only names and not numeric IDs. You should almost + * certainly be using the by-enum-id version instead of this one. + * + * @param name - histogram name + * @param sample - value to record + */ +void Accumulate(const char* name, uint32_t sample); + +/** + * Adds a sample to a histogram defined in TelemetryHistogramEnums.h. + * This function is here to support telemetry measurements from Java, + * where we have only names and not numeric IDs. You should almost + * certainly be using the by-enum-id version instead of this one. + * + * @param name - histogram name + * @param key - the string key + * @param sample - sample - (optional) value to record, defaults to 1. + */ +void Accumulate(const char* name, const nsCString& key, uint32_t sample = 1); + +/** + * Adds sample to a categorical histogram defined in TelemetryHistogramEnums.h + * This is the typesafe - and preferred - way to use the categorical histograms + * by passing values from the corresponding Telemetry::LABELS_* enum. + * + * @param enumValue - Label value from one of the Telemetry::LABELS_* enums. + */ +template <class E> +void AccumulateCategorical(E enumValue) { + static_assert(IsCategoricalLabelEnum<E>::value, + "Only categorical label enum types are supported."); + Accumulate(static_cast<HistogramID>(CategoricalLabelId<E>::value), + static_cast<uint32_t>(enumValue)); +}; + +/** + * Adds an array of samples to categorical histograms defined in + * TelemetryHistogramEnums.h This is the typesafe - and preferred - way to use + * the categorical histograms by passing values from the corresponding + * Telemetry::LABELS_* enums. + * + * @param enumValues - Array of labels from Telemetry::LABELS_* enums. + */ +template <class E> +void AccumulateCategorical(const nsTArray<E>& enumValues) { + static_assert(IsCategoricalLabelEnum<E>::value, + "Only categorical label enum types are supported."); + nsTArray<uint32_t> intSamples(enumValues.Length()); + + for (E aValue : enumValues) { + intSamples.AppendElement(static_cast<uint32_t>(aValue)); + } + + HistogramID categoricalId = + static_cast<HistogramID>(CategoricalLabelId<E>::value); + + Accumulate(categoricalId, intSamples); +} + +/** + * Adds sample to a keyed categorical histogram defined in + * TelemetryHistogramEnums.h This is the typesafe - and preferred - way to use + * the keyed categorical histograms by passing values from the corresponding + * Telemetry::LABELS_* enum. + * + * @param key - the string key + * @param enumValue - Label value from one of the Telemetry::LABELS_* enums. + */ +template <class E> +void AccumulateCategoricalKeyed(const nsCString& key, E enumValue) { + static_assert(IsCategoricalLabelEnum<E>::value, + "Only categorical label enum types are supported."); + Accumulate(static_cast<HistogramID>(CategoricalLabelId<E>::value), key, + static_cast<uint32_t>(enumValue)); +}; + +/** + * Adds an array of samples to a keyed categorical histogram defined in + * TelemetryHistogramEnums.h. This is the typesafe - and preferred - way to use + * the keyed categorical histograms by passing values from the corresponding + * Telemetry::LABELS_*enum. + * + * @param key - the string key + * @param enumValue - Label value from one of the Telemetry::LABELS_* enums. + */ +template <class E> +void AccumulateCategoricalKeyed(const nsCString& key, + const nsTArray<E>& enumValues) { + static_assert(IsCategoricalLabelEnum<E>::value, + "Only categorical label enum types are supported."); + nsTArray<uint32_t> intSamples(enumValues.Length()); + + for (E aValue : enumValues) { + intSamples.AppendElement(static_cast<uint32_t>(aValue)); + } + + Accumulate(static_cast<HistogramID>(CategoricalLabelId<E>::value), key, + intSamples); +}; + +/** + * Adds sample to a categorical histogram defined in TelemetryHistogramEnums.h + * This string will be matched against the labels defined in Histograms.json. + * If the string does not match a label defined for the histogram, nothing will + * be recorded. + * + * @param id - The histogram id. + * @param label - A string label value that is defined in Histograms.json for + * this histogram. + */ +void AccumulateCategorical(HistogramID id, const nsCString& label); + +/** + * Adds an array of samples to a categorical histogram defined in + * Histograms.json + * + * @param id - The histogram id + * @param labels - The array of labels to accumulate + */ +void AccumulateCategorical(HistogramID id, const nsTArray<nsCString>& labels); + +/** + * Adds time delta in milliseconds to a histogram defined in + * TelemetryHistogramEnums.h + * + * @param id - histogram id + * @param start - start time + * @param end - end time + */ +void AccumulateTimeDelta(HistogramID id, TimeStamp start, + TimeStamp end = TimeStamp::Now()); + +/** + * Adds time delta in milliseconds to a keyed histogram defined in + * TelemetryHistogramEnums.h + * + * @param id - histogram id + * @param key - the string key + * @param start - start time + * @param end - end time + */ +void AccumulateTimeDelta(HistogramID id, const nsCString& key, TimeStamp start, + TimeStamp end = TimeStamp::Now()); + +/** + * Enable/disable recording for this histogram in this process at runtime. + * Recording is enabled by default, unless listed at + * kRecordingInitiallyDisabledIDs[]. id must be a valid telemetry enum, + * + * @param id - histogram id + * @param enabled - whether or not to enable recording from now on. + */ +void SetHistogramRecordingEnabled(HistogramID id, bool enabled); + +const char* GetHistogramName(HistogramID id); + +class MOZ_RAII RuntimeAutoTimer { + public: + explicit RuntimeAutoTimer(Telemetry::HistogramID aId, + TimeStamp aStart = TimeStamp::Now()) + : id(aId), start(aStart) {} + explicit RuntimeAutoTimer(Telemetry::HistogramID aId, const nsCString& aKey, + TimeStamp aStart = TimeStamp::Now()) + : id(aId), key(aKey), start(aStart) { + MOZ_ASSERT(!aKey.IsEmpty(), "The key must not be empty."); + } + + ~RuntimeAutoTimer() { + if (key.IsEmpty()) { + AccumulateTimeDelta(id, start); + } else { + AccumulateTimeDelta(id, key, start); + } + } + + private: + Telemetry::HistogramID id; + const nsCString key; + const TimeStamp start; +}; + +template <HistogramID id> +class MOZ_RAII AutoTimer { + public: + explicit AutoTimer(TimeStamp aStart = TimeStamp::Now()) : start(aStart) {} + + explicit AutoTimer(const nsCString& aKey, TimeStamp aStart = TimeStamp::Now()) + : start(aStart), key(aKey) { + MOZ_ASSERT(!aKey.IsEmpty(), "The key must not be empty."); + } + + ~AutoTimer() { + if (key.IsEmpty()) { + AccumulateTimeDelta(id, start); + } else { + AccumulateTimeDelta(id, key, start); + } + } + + private: + const TimeStamp start; + const nsCString key; +}; + +class MOZ_RAII RuntimeAutoCounter { + public: + explicit RuntimeAutoCounter(HistogramID aId, uint32_t counterStart = 0) + : id(aId), counter(counterStart) {} + + ~RuntimeAutoCounter() { Accumulate(id, counter); } + + // Prefix increment only, to encourage good habits. + void operator++() { + if (NS_WARN_IF(counter == std::numeric_limits<uint32_t>::max())) { + return; + } + ++counter; + } + + // Chaining doesn't make any sense, don't return anything. + void operator+=(int increment) { + if (NS_WARN_IF(increment > 0 && + static_cast<uint32_t>(increment) > + (std::numeric_limits<uint32_t>::max() - counter))) { + counter = std::numeric_limits<uint32_t>::max(); + return; + } + if (NS_WARN_IF(increment < 0 && + static_cast<uint32_t>(-increment) > counter)) { + counter = std::numeric_limits<uint32_t>::min(); + return; + } + counter += increment; + } + + private: + HistogramID id; + uint32_t counter; +}; + +template <HistogramID id> +class MOZ_RAII AutoCounter { + public: + explicit AutoCounter(uint32_t counterStart = 0) : counter(counterStart) {} + + ~AutoCounter() { Accumulate(id, counter); } + + // Prefix increment only, to encourage good habits. + void operator++() { + if (NS_WARN_IF(counter == std::numeric_limits<uint32_t>::max())) { + return; + } + ++counter; + } + + // Chaining doesn't make any sense, don't return anything. + void operator+=(int increment) { + if (NS_WARN_IF(increment > 0 && + static_cast<uint32_t>(increment) > + (std::numeric_limits<uint32_t>::max() - counter))) { + counter = std::numeric_limits<uint32_t>::max(); + return; + } + if (NS_WARN_IF(increment < 0 && + static_cast<uint32_t>(-increment) > counter)) { + counter = std::numeric_limits<uint32_t>::min(); + return; + } + counter += increment; + } + + private: + uint32_t counter; +}; + +/** + * Indicates whether Telemetry base data recording is turned on. Added for + * future uses. + */ +bool CanRecordBase(); + +/** + * Indicates whether Telemetry extended data recording is turned on. This is + * intended to guard calls to Accumulate when the statistic being recorded is + * expensive to compute. + */ +bool CanRecordExtended(); + +/** + * Indicates whether Telemetry release data recording is turned on. Usually + * true. + * + * @see nsITelemetry.canRecordReleaseData + */ +bool CanRecordReleaseData(); + +/** + * Indicates whether Telemetry pre-release data recording is turned on. Tends + * to be true on pre-release channels. + * + * @see nsITelemetry.canRecordPrereleaseData + */ +bool CanRecordPrereleaseData(); + +/** + * Records slow SQL statements for Telemetry reporting. + * + * @param statement - offending SQL statement to record + * @param dbName - DB filename + * @param delay - execution time in milliseconds + */ +void RecordSlowSQLStatement(const nsACString& statement, + const nsACString& dbName, uint32_t delay); + +/** + * Initialize I/O Reporting + * Initially this only records I/O for files in the binary directory. + * + * @param aXreDir - XRE directory + */ +void InitIOReporting(nsIFile* aXreDir); + +/** + * Set the profile directory. Once called, files in the profile directory will + * be included in I/O reporting. We can't use the directory + * service to obtain this information because it isn't running yet. + */ +void SetProfileDir(nsIFile* aProfD); + +/** + * Called to inform Telemetry that startup has completed. + */ +void LeavingStartupStage(); + +/** + * Called to inform Telemetry that shutdown is commencing. + */ +void EnteringShutdownStage(); + +/** + * Thresholds for a statement to be considered slow, in milliseconds + */ +const uint32_t kSlowSQLThresholdForMainThread = 50; +const uint32_t kSlowSQLThresholdForHelperThreads = 100; + +/** + * Record a failed attempt at locking the user's profile. + * + * @param aProfileDir The profile directory whose lock attempt failed + */ +void WriteFailedProfileLock(nsIFile* aProfileDir); + +/** + * Adds the value to the given scalar. + * + * @param aId The scalar enum id. + * @param aValue The value to add to the scalar. + */ +void ScalarAdd(mozilla::Telemetry::ScalarID aId, uint32_t aValue); + +/** + * Sets the scalar to the given value. + * + * @param aId The scalar enum id. + * @param aValue The value to set the scalar to. + */ +void ScalarSet(mozilla::Telemetry::ScalarID aId, uint32_t aValue); + +/** + * Sets the scalar to the given value. + * + * @param aId The scalar enum id. + * @param aValue The value to set the scalar to. + */ +void ScalarSet(mozilla::Telemetry::ScalarID aId, bool aValue); + +/** + * Sets the scalar to the given value. + * + * @param aId The scalar enum id. + * @param aValue The value to set the scalar to, truncated to + * 50 characters if exceeding that length. + */ +void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aValue); + +/** + * Sets the scalar to the maximum of the current and the passed value. + * + * @param aId The scalar enum id. + * @param aValue The value the scalar is set to if its greater + * than the current value. + */ +void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue); + +/** + * Adds the value to the given scalar. + * + * @param aId The scalar enum id. + * @param aKey The scalar key. + * @param aValue The value to add to the scalar. + */ +void ScalarAdd(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + uint32_t aValue); + +/** + * Sets the scalar to the given value. + * + * @param aId The scalar enum id. + * @param aKey The scalar key. + * @param aValue The value to set the scalar to. + */ +void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + uint32_t aValue); + +/** + * Sets the scalar to the given value. + * + * @param aId The scalar enum id. + * @param aKey The scalar key. + * @param aValue The value to set the scalar to. + */ +void ScalarSet(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + bool aValue); + +/** + * Sets the scalar to the maximum of the current and the passed value. + * + * @param aId The scalar enum id. + * @param aKey The scalar key. + * @param aValue The value the scalar is set to if its greater + * than the current value. + */ +void ScalarSetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + uint32_t aValue); + +template <ScalarID id> +class MOZ_RAII AutoScalarTimer { + public: + explicit AutoScalarTimer(TimeStamp aStart = TimeStamp::Now()) + : start(aStart) {} + + explicit AutoScalarTimer(const nsAString& aKey, + TimeStamp aStart = TimeStamp::Now()) + : start(aStart), key(aKey) { + MOZ_ASSERT(!aKey.IsEmpty(), "The key must not be empty."); + } + + ~AutoScalarTimer() { + TimeStamp end = TimeStamp::Now(); + uint32_t delta = static_cast<uint32_t>((end - start).ToMilliseconds()); + if (key.IsEmpty()) { + mozilla::Telemetry::ScalarSet(id, delta); + } else { + mozilla::Telemetry::ScalarSet(id, key, delta); + } + } + + private: + const TimeStamp start; + const nsString key; +}; + +/** + * Records an event. See the Event documentation for more information: + * https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/events.html + * + * @param aId The event enum id. + * @param aValue Optional. The event value. + * @param aExtra Optional. The event's extra key/value pairs. + */ +void RecordEvent(mozilla::Telemetry::EventID aId, + const mozilla::Maybe<nsCString>& aValue, + const mozilla::Maybe<CopyableTArray<EventExtraEntry>>& aExtra); + +/** + * Enables recording of events in a category. + * Events default to recording disabled. + * This toggles recording for all events in the specified category. + * + * @param aCategory The category name. + * @param aEnabled Whether recording should be enabled or disabled. + */ +void SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled); + +/** + * YOU PROBABLY SHOULDN'T USE THIS. + * THIS IS AN EXPERIMENTAL API NOT YET READY FOR GENERAL USE. + * + * Records that the metric is true for the stated origin. + * + * @param aId the metric. + * @param aOrigin the origin on which to record the metric as true. + */ +void RecordOrigin(mozilla::Telemetry::OriginMetricID aId, + const nsACString& aOrigin); + +} // namespace Telemetry +} // namespace mozilla + +#endif // Telemetry_h__ diff --git a/toolkit/components/telemetry/core/TelemetryCommon.cpp b/toolkit/components/telemetry/core/TelemetryCommon.cpp new file mode 100644 index 0000000000..7113a682c9 --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryCommon.cpp @@ -0,0 +1,209 @@ +/* -*- 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 "TelemetryCommon.h" + +#include <cstring> +#include "js/String.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/StaticPrefs_toolkit.h" +#include "nsComponentManagerUtils.h" +#include "nsIConsoleService.h" +#include "nsITelemetry.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsVersionComparator.h" +#include "TelemetryProcessData.h" +#include "Telemetry.h" +#include "mozilla/Uptime.h" + +namespace mozilla::Telemetry::Common { + +bool IsExpiredVersion(const char* aExpiration) { + MOZ_ASSERT(aExpiration); + // Note: We intentionally don't construct a static Version object here as we + // saw odd crashes around this (see bug 1334105). + return strcmp(aExpiration, "never") && strcmp(aExpiration, "default") && + (mozilla::Version(aExpiration) <= MOZ_APP_VERSION); +} + +bool IsInDataset(uint32_t aDataset, uint32_t aContainingDataset) { + if (aDataset == aContainingDataset) { + return true; + } + + // The "optin on release channel" dataset is a superset of the + // "optout on release channel one". + if (aContainingDataset == nsITelemetry::DATASET_PRERELEASE_CHANNELS && + aDataset == nsITelemetry::DATASET_ALL_CHANNELS) { + return true; + } + + return false; +} + +bool CanRecordDataset(uint32_t aDataset, bool aCanRecordBase, + bool aCanRecordExtended) { + // If we are extended telemetry is enabled, we are allowed to record + // regardless of the dataset. + if (aCanRecordExtended) { + return true; + } + + // If base telemetry data is enabled and we're trying to record base + // telemetry, allow it. + if (aCanRecordBase && + IsInDataset(aDataset, nsITelemetry::DATASET_ALL_CHANNELS)) { + return true; + } + + // We're not recording extended telemetry or this is not the base + // dataset. Bail out. + return false; +} + +bool CanRecordInProcess(RecordedProcessType processes, + GeckoProcessType processType) { + // We can use (1 << ProcessType) due to the way RecordedProcessType is + // defined. + bool canRecordProcess = + !!(processes & static_cast<RecordedProcessType>(1 << processType)); + + return canRecordProcess; +} + +bool CanRecordInProcess(RecordedProcessType processes, ProcessID processId) { + return CanRecordInProcess(processes, GetGeckoProcessType(processId)); +} + +bool CanRecordProduct(SupportedProduct aProducts) { + return mozilla::StaticPrefs:: + toolkit_telemetry_testing_overrideProductsCheck() || + !!(aProducts & GetCurrentProduct()); +} + +nsresult MsSinceProcessStart(double* aResult) { + *aResult = + (TimeStamp::NowLoRes() - TimeStamp::ProcessCreation()).ToMilliseconds(); + return NS_OK; +} + +nsresult MsSinceProcessStartIncludingSuspend(double* aResult) { + auto rv = mozilla::ProcessUptimeMs(); + if (rv) { + *aResult = rv.value(); + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult MsSinceProcessStartExcludingSuspend(double* aResult) { + auto rv = mozilla::ProcessUptimeExcludingSuspendMs(); + if (rv) { + *aResult = rv.value(); + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; +} + +void LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg) { + if (!NS_IsMainThread()) { + nsString msg(aMsg); + nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction( + "Telemetry::Common::LogToBrowserConsole", + [aLogLevel, msg]() { LogToBrowserConsole(aLogLevel, msg); }); + NS_DispatchToMainThread(task.forget(), NS_DISPATCH_NORMAL); + return; + } + + nsCOMPtr<nsIConsoleService> console( + do_GetService("@mozilla.org/consoleservice;1")); + if (!console) { + NS_WARNING("Failed to log message to console."); + return; + } + + nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + error->Init(aMsg, u""_ns, u""_ns, 0, 0, aLogLevel, "chrome javascript"_ns, + false /* from private window */, true /* from chrome context */); + console->LogMessage(error); +} + +const char* GetNameForProcessID(ProcessID process) { + MOZ_ASSERT(process < ProcessID::Count); + return ProcessIDToString[static_cast<uint32_t>(process)]; +} + +ProcessID GetIDForProcessName(const char* aProcessName) { + for (uint32_t id = 0; id < static_cast<uint32_t>(ProcessID::Count); id++) { + if (!strcmp(GetNameForProcessID(ProcessID(id)), aProcessName)) { + return ProcessID(id); + } + } + + return ProcessID::Count; +} + +GeckoProcessType GetGeckoProcessType(ProcessID process) { + MOZ_ASSERT(process < ProcessID::Count); + return ProcessIDToGeckoProcessType[static_cast<uint32_t>(process)]; +} + +bool IsStringCharValid(const char aChar, const bool aAllowInfixPeriod, + const bool aAllowInfixUnderscore) { + return (aChar >= 'A' && aChar <= 'Z') || (aChar >= 'a' && aChar <= 'z') || + (aChar >= '0' && aChar <= '9') || + (aAllowInfixPeriod && (aChar == '.')) || + (aAllowInfixUnderscore && (aChar == '_')); +} + +bool IsValidIdentifierString(const nsACString& aStr, const size_t aMaxLength, + const bool aAllowInfixPeriod, + const bool aAllowInfixUnderscore) { + // Check string length. + if (aStr.Length() > aMaxLength) { + return false; + } + + // Check string characters. + const char* first = aStr.BeginReading(); + const char* end = aStr.EndReading(); + + for (const char* cur = first; cur < end; ++cur) { + const bool infix = (cur != first) && (cur != (end - 1)); + if (!IsStringCharValid(*cur, aAllowInfixPeriod && infix, + aAllowInfixUnderscore && infix)) { + return false; + } + } + + return true; +} + +JSString* ToJSString(JSContext* cx, const nsACString& aStr) { + const NS_ConvertUTF8toUTF16 wide(aStr); + return JS_NewUCStringCopyN(cx, wide.Data(), wide.Length()); +} + +JSString* ToJSString(JSContext* cx, const nsAString& aStr) { + return JS_NewUCStringCopyN(cx, aStr.Data(), aStr.Length()); +} + +SupportedProduct GetCurrentProduct() { +#if defined(MOZ_WIDGET_ANDROID) + if (mozilla::StaticPrefs::toolkit_telemetry_geckoview_streaming()) { + return SupportedProduct::GeckoviewStreaming; + } else { + return SupportedProduct::Fennec; + } +#elif defined(MOZ_THUNDERBIRD) + return SupportedProduct::Thunderbird; +#else + return SupportedProduct::Firefox; +#endif +} + +} // namespace mozilla::Telemetry::Common diff --git a/toolkit/components/telemetry/core/TelemetryCommon.h b/toolkit/components/telemetry/core/TelemetryCommon.h new file mode 100644 index 0000000000..3251dc25d4 --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryCommon.h @@ -0,0 +1,197 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryCommon_h__ +#define TelemetryCommon_h__ + +#include "PLDHashTable.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" +#include "mozilla/TypedEnumBits.h" +#include "mozilla/TelemetryProcessEnums.h" +#include "nsHashtablesFwd.h" +#include "nsTHashSet.h" +#include "nsTHashtable.h" +#include "nsIScriptError.h" +#include "nsXULAppAPI.h" + +namespace mozilla { +namespace Telemetry { +namespace Common { + +typedef nsTHashSet<nsCString> StringHashSet; + +enum class RecordedProcessType : uint16_t { + Main = (1 << GeckoProcessType_Default), // Also known as "parent process" + Content = (1 << GeckoProcessType_Content), + Gpu = (1 << GeckoProcessType_GPU), + Socket = (1 << GeckoProcessType_Socket), + Utility = (1 << GeckoProcessType_Utility), + AllChildren = 0xFFFF - 1, // All the child processes (i.e. content, gpu, ...) + // Always `All-Main` to allow easy matching. + All = 0xFFFF // All the processes +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(RecordedProcessType); +static_assert(static_cast<uint16_t>(RecordedProcessType::Main) == 1, + "Main process type must be equal to 1 to allow easy matching in " + "CanRecordInProcess"); + +enum class SupportedProduct : uint8_t { + Firefox = (1 << 0), + Fennec = (1 << 1), + // Note that `1 << 2` (former GeckoView) is missing in the representation + // but isn't necessary to be maintained, but we see no point in filling it + // at this time. + GeckoviewStreaming = (1 << 3), + Thunderbird = (1 << 4), +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SupportedProduct); + +template <class EntryType> +class AutoHashtable : public nsTHashtable<EntryType> { + public: + explicit AutoHashtable( + uint32_t initLength = PLDHashTable::kDefaultInitialLength); + typedef bool (*ReflectEntryFunc)(EntryType* entry, JSContext* cx, + JS::Handle<JSObject*> obj); + bool ReflectIntoJS(ReflectEntryFunc entryFunc, JSContext* cx, + JS::Handle<JSObject*> obj); +}; + +template <class EntryType> +AutoHashtable<EntryType>::AutoHashtable(uint32_t initLength) + : nsTHashtable<EntryType>(initLength) {} + +/** + * Reflect the individual entries of table into JS, usually by defining + * some property and value of obj. entryFunc is called for each entry. + */ +template <typename EntryType> +bool AutoHashtable<EntryType>::ReflectIntoJS(ReflectEntryFunc entryFunc, + JSContext* cx, + JS::Handle<JSObject*> obj) { + for (auto iter = this->Iter(); !iter.Done(); iter.Next()) { + if (!entryFunc(iter.Get(), cx, obj)) { + return false; + } + } + return true; +} + +bool IsExpiredVersion(const char* aExpiration); +bool IsInDataset(uint32_t aDataset, uint32_t aContainingDataset); +bool CanRecordDataset(uint32_t aDataset, bool aCanRecordBase, + bool aCanRecordExtended); +bool CanRecordInProcess(RecordedProcessType aProcesses, + GeckoProcessType aProcess); +bool CanRecordInProcess(RecordedProcessType aProcesses, ProcessID aProcess); +bool CanRecordProduct(SupportedProduct aProducts); + +/** + * Return the number of milliseconds since process start using monotonic + * timestamps (unaffected by system clock changes). Depending on the platform, + * this can include the time the device was suspended (Windows) or not (Linux, + * macOS). + * + * @return NS_OK on success. + */ +nsresult MsSinceProcessStart(double* aResult); + +/** + * Return the number of milliseconds since process start using monotonic + * timestamps (unaffected by system clock changes), including the time the + * system was suspended. + * + * @return NS_OK on success, NS_ERROR_NOT_AVAILABLE if the data is unavailable + * (this can happen on old operating systems). + */ +nsresult MsSinceProcessStartIncludingSuspend(double* aResult); + +/** + * Return the number of milliseconds since process start using monotonic + * timestamps (unaffected by system clock changes), excluding the time the + * system was suspended. + * + * @return NS_OK on success, NS_ERROR_NOT_AVAILABLE if the data is unavailable + * (this can happen on old operating systems). + */ +nsresult MsSinceProcessStartExcludingSuspend(double* aResult); + +/** + * Dumps a log message to the Browser Console using the provided level. + * + * @param aLogLevel The level to use when displaying the message in the browser + * console (e.g. nsIScriptError::warningFlag, ...). + * @param aMsg The text message to print to the console. + */ +void LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg); + +/** + * Get the name string for a ProcessID. + * This is the name we use for the Telemetry payloads. + */ +const char* GetNameForProcessID(ProcessID process); + +/** + * Get the process id give a process name. + * + * @param aProcessName - the name of the process. + * @returns {ProcessID} one value from ProcessID::* or ProcessID::Count if the + * name of the process was not found. + */ +ProcessID GetIDForProcessName(const char* aProcessName); + +/** + * Get the GeckoProcessType for a ProcessID. + * Telemetry distinguishes between more process types than the GeckoProcessType, + * so the mapping is not direct. + */ +GeckoProcessType GetGeckoProcessType(ProcessID process); + +/** + * Check if the passed telemetry identifier is valid. + * + * @param aStr The string identifier. + * @param aMaxLength The maximum length of the identifier. + * @param aAllowInfixPeriod Whether or not to allow infix dots. + * @param aAllowInfixUnderscore Whether or not to allow infix underscores. + * @returns true if the string validates correctly, false otherwise. + */ +bool IsValidIdentifierString(const nsACString& aStr, const size_t aMaxLength, + const bool aAllowInfixPeriod, + const bool aAllowInfixUnderscore); + +/** + * Convert the given UTF8 string to a JavaScript string. The returned + * string's contents will be the UTF16 conversion of the given string. + * + * @param cx The JS context. + * @param aStr The UTF8 string. + * @returns a JavaScript string. + */ +JSString* ToJSString(JSContext* cx, const nsACString& aStr); + +/** + * Convert the given UTF16 string to a JavaScript string. + * + * @param cx The JS context. + * @param aStr The UTF16 string. + * @returns a JavaScript string. + */ +JSString* ToJSString(JSContext* cx, const nsAString& aStr); + +/** + * Get an identifier for the currently-running product. + * This is not stable over time and may change. + * + * @returns the product identifier + */ +SupportedProduct GetCurrentProduct(); + +} // namespace Common +} // namespace Telemetry +} // namespace mozilla + +#endif // TelemetryCommon_h__ diff --git a/toolkit/components/telemetry/core/TelemetryEvent.cpp b/toolkit/components/telemetry/core/TelemetryEvent.cpp new file mode 100644 index 0000000000..ad6a3fd2c7 --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryEvent.cpp @@ -0,0 +1,1387 @@ +/* -*- 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 "Telemetry.h" +#include "TelemetryEvent.h" +#include <limits> +#include "ipc/TelemetryIPCAccumulator.h" +#include "jsapi.h" +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject +#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty, JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_HasProperty +#include "mozilla/Maybe.h" +#include "mozilla/Services.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsIObserverService.h" +#include "nsITelemetry.h" +#include "nsJSUtils.h" +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "nsUTF8Utils.h" +#include "nsXULAppAPI.h" +#include "TelemetryCommon.h" +#include "TelemetryEventData.h" +#include "TelemetryScalar.h" + +using mozilla::MakeUnique; +using mozilla::Maybe; +using mozilla::StaticAutoPtr; +using mozilla::StaticMutex; +using mozilla::StaticMutexAutoLock; +using mozilla::TimeStamp; +using mozilla::UniquePtr; +using mozilla::Telemetry::ChildEventData; +using mozilla::Telemetry::EventExtraEntry; +using mozilla::Telemetry::LABELS_TELEMETRY_EVENT_RECORDING_ERROR; +using mozilla::Telemetry::LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR; +using mozilla::Telemetry::ProcessID; +using mozilla::Telemetry::Common::CanRecordDataset; +using mozilla::Telemetry::Common::CanRecordInProcess; +using mozilla::Telemetry::Common::CanRecordProduct; +using mozilla::Telemetry::Common::GetNameForProcessID; +using mozilla::Telemetry::Common::IsExpiredVersion; +using mozilla::Telemetry::Common::IsInDataset; +using mozilla::Telemetry::Common::IsValidIdentifierString; +using mozilla::Telemetry::Common::LogToBrowserConsole; +using mozilla::Telemetry::Common::MsSinceProcessStart; +using mozilla::Telemetry::Common::ToJSString; + +namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator; + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// Naming: there are two kinds of functions in this file: +// +// * Functions taking a StaticMutexAutoLock: these can only be reached via +// an interface function (TelemetryEvent::*). They expect the interface +// function to have acquired |gTelemetryEventsMutex|, so they do not +// have to be thread-safe. +// +// * Functions named TelemetryEvent::*. This is the external interface. +// Entries and exits to these functions are serialised using +// |gTelemetryEventsMutex|. +// +// Avoiding races and deadlocks: +// +// All functions in the external interface (TelemetryEvent::*) are +// serialised using the mutex |gTelemetryEventsMutex|. This means +// that the external interface is thread-safe, and the internal +// functions can ignore thread safety. But it also brings a danger +// of deadlock if any function in the external interface can get back +// to that interface. That is, we will deadlock on any call chain like +// this: +// +// TelemetryEvent::* -> .. any functions .. -> TelemetryEvent::* +// +// To reduce the danger of that happening, observe the following rules: +// +// * No function in TelemetryEvent::* may directly call, nor take the +// address of, any other function in TelemetryEvent::*. +// +// * No internal function may call, nor take the address +// of, any function in TelemetryEvent::*. + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE TYPES + +namespace { + +const uint32_t kEventCount = + static_cast<uint32_t>(mozilla::Telemetry::EventID::EventCount); +// This is a special event id used to mark expired events, to make expiry checks +// cheap at runtime. +const uint32_t kExpiredEventId = std::numeric_limits<uint32_t>::max(); +static_assert(kExpiredEventId > kEventCount, + "Built-in event count should be less than the expired event id."); + +// Maximum length of any passed value string, in UTF8 byte sequence length. +const uint32_t kMaxValueByteLength = 80; +// Maximum length of any string value in the extra dictionary, in UTF8 byte +// sequence length. +const uint32_t kMaxExtraValueByteLength = 80; +// Maximum length of dynamic method names, in UTF8 byte sequence length. +const uint32_t kMaxMethodNameByteLength = 20; +// Maximum length of dynamic object names, in UTF8 byte sequence length. +const uint32_t kMaxObjectNameByteLength = 20; +// Maximum length of extra key names, in UTF8 byte sequence length. +const uint32_t kMaxExtraKeyNameByteLength = 15; +// The maximum number of valid extra keys for an event. +const uint32_t kMaxExtraKeyCount = 10; +// The number of event records allowed in an event ping. +const uint32_t kEventPingLimit = 1000; + +struct EventKey { + uint32_t id; + bool dynamic; + + EventKey() : id(kExpiredEventId), dynamic(false) {} + EventKey(uint32_t id_, bool dynamic_) : id(id_), dynamic(dynamic_) {} +}; + +struct DynamicEventInfo { + DynamicEventInfo(const nsACString& category, const nsACString& method, + const nsACString& object, nsTArray<nsCString>& extra_keys, + bool recordOnRelease, bool builtin) + : category(category), + method(method), + object(object), + extra_keys(extra_keys.Clone()), + recordOnRelease(recordOnRelease), + builtin(builtin) {} + + DynamicEventInfo(const DynamicEventInfo&) = default; + DynamicEventInfo& operator=(const DynamicEventInfo&) = delete; + + const nsCString category; + const nsCString method; + const nsCString object; + const CopyableTArray<nsCString> extra_keys; + const bool recordOnRelease; + const bool builtin; + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + + n += category.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + n += method.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + n += object.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + n += extra_keys.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto& key : extra_keys) { + n += key.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + return n; + } +}; + +enum class RecordEventResult { + Ok, + UnknownEvent, + InvalidExtraKey, + StorageLimitReached, + ExpiredEvent, + WrongProcess, + CannotRecord, +}; + +typedef CopyableTArray<EventExtraEntry> ExtraArray; + +class EventRecord { + public: + EventRecord(double timestamp, const EventKey& key, + const Maybe<nsCString>& value, const ExtraArray& extra) + : mTimestamp(timestamp), + mEventKey(key), + mValue(value), + mExtra(extra.Clone()) {} + + EventRecord(const EventRecord& other) = default; + + EventRecord& operator=(const EventRecord& other) = delete; + + double Timestamp() const { return mTimestamp; } + const EventKey& GetEventKey() const { return mEventKey; } + const Maybe<nsCString>& Value() const { return mValue; } + const ExtraArray& Extra() const { return mExtra; } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + const double mTimestamp; + const EventKey mEventKey; + const Maybe<nsCString> mValue; + const ExtraArray mExtra; +}; + +// Implements the methods for EventInfo. +const nsDependentCString EventInfo::method() const { + return nsDependentCString(&gEventsStringTable[this->method_offset]); +} + +const nsDependentCString EventInfo::object() const { + return nsDependentCString(&gEventsStringTable[this->object_offset]); +} + +// Implements the methods for CommonEventInfo. +const nsDependentCString CommonEventInfo::category() const { + return nsDependentCString(&gEventsStringTable[this->category_offset]); +} + +const nsDependentCString CommonEventInfo::expiration_version() const { + return nsDependentCString( + &gEventsStringTable[this->expiration_version_offset]); +} + +const nsDependentCString CommonEventInfo::extra_key(uint32_t index) const { + MOZ_ASSERT(index < this->extra_count); + uint32_t key_index = gExtraKeysTable[this->extra_index + index]; + return nsDependentCString(&gEventsStringTable[key_index]); +} + +// Implementation for the EventRecord class. +size_t EventRecord::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + + if (mValue) { + n += mValue.value().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + n += mExtra.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (uint32_t i = 0; i < mExtra.Length(); ++i) { + n += mExtra[i].key.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + n += mExtra[i].value.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + return n; +} + +nsCString UniqueEventName(const nsACString& category, const nsACString& method, + const nsACString& object) { + nsCString name; + name.Append(category); + name.AppendLiteral("#"); + name.Append(method); + name.AppendLiteral("#"); + name.Append(object); + return name; +} + +nsCString UniqueEventName(const EventInfo& info) { + return UniqueEventName(info.common_info.category(), info.method(), + info.object()); +} + +nsCString UniqueEventName(const DynamicEventInfo& info) { + return UniqueEventName(info.category, info.method, info.object); +} + +void TruncateToByteLength(nsCString& str, uint32_t length) { + // last will be the index of the first byte of the current multi-byte + // sequence. + uint32_t last = RewindToPriorUTF8Codepoint(str.get(), length); + str.Truncate(last); +} + +} // anonymous namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE STATE, SHARED BY ALL THREADS + +namespace { + +// Set to true once this global state has been initialized. +bool gInitDone = false; + +bool gCanRecordBase; +bool gCanRecordExtended; + +// The EventName -> EventKey cache map. +nsTHashMap<nsCStringHashKey, EventKey> gEventNameIDMap(kEventCount); + +// The CategoryName set. +nsTHashSet<nsCString> gCategoryNames; + +// This tracks the IDs of the categories for which recording is enabled. +nsTHashSet<nsCString> gEnabledCategories; + +// The main event storage. Events are inserted here, keyed by process id and +// in recording order. +typedef nsUint32HashKey ProcessIDHashKey; +typedef nsTArray<EventRecord> EventRecordArray; +typedef nsClassHashtable<ProcessIDHashKey, EventRecordArray> + EventRecordsMapType; + +EventRecordsMapType gEventRecords; + +// The details on dynamic events that are recorded from addons are registered +// here. +StaticAutoPtr<nsTArray<DynamicEventInfo>> gDynamicEventInfo; + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: thread-safe helpers for event recording. + +namespace { + +unsigned int GetDataset(const StaticMutexAutoLock& lock, + const EventKey& eventKey) { + if (!eventKey.dynamic) { + return gEventInfo[eventKey.id].common_info.dataset; + } + + if (!gDynamicEventInfo) { + return nsITelemetry::DATASET_PRERELEASE_CHANNELS; + } + + return (*gDynamicEventInfo)[eventKey.id].recordOnRelease + ? nsITelemetry::DATASET_ALL_CHANNELS + : nsITelemetry::DATASET_PRERELEASE_CHANNELS; +} + +nsCString GetCategory(const StaticMutexAutoLock& lock, + const EventKey& eventKey) { + if (!eventKey.dynamic) { + return gEventInfo[eventKey.id].common_info.category(); + } + + if (!gDynamicEventInfo) { + return ""_ns; + } + + return (*gDynamicEventInfo)[eventKey.id].category; +} + +bool CanRecordEvent(const StaticMutexAutoLock& lock, const EventKey& eventKey, + ProcessID process) { + if (!gCanRecordBase) { + return false; + } + + if (!CanRecordDataset(GetDataset(lock, eventKey), gCanRecordBase, + gCanRecordExtended)) { + return false; + } + + // We don't allow specifying a process to record in for dynamic events. + if (!eventKey.dynamic) { + const CommonEventInfo& info = gEventInfo[eventKey.id].common_info; + + if (!CanRecordProduct(info.products)) { + return false; + } + + if (!CanRecordInProcess(info.record_in_processes, process)) { + return false; + } + } + + return true; +} + +bool IsExpired(const EventKey& key) { return key.id == kExpiredEventId; } + +EventRecordArray* GetEventRecordsForProcess(const StaticMutexAutoLock& lock, + ProcessID processType) { + return gEventRecords.GetOrInsertNew(uint32_t(processType)); +} + +bool GetEventKey(const StaticMutexAutoLock& lock, const nsACString& category, + const nsACString& method, const nsACString& object, + EventKey* aEventKey) { + const nsCString& name = UniqueEventName(category, method, object); + return gEventNameIDMap.Get(name, aEventKey); +} + +static bool CheckExtraKeysValid(const EventKey& eventKey, + const ExtraArray& extra) { + nsTHashSet<nsCString> validExtraKeys; + if (!eventKey.dynamic) { + const CommonEventInfo& common = gEventInfo[eventKey.id].common_info; + for (uint32_t i = 0; i < common.extra_count; ++i) { + validExtraKeys.Insert(common.extra_key(i)); + } + } else if (gDynamicEventInfo) { + const DynamicEventInfo& info = (*gDynamicEventInfo)[eventKey.id]; + for (uint32_t i = 0, len = info.extra_keys.Length(); i < len; ++i) { + validExtraKeys.Insert(info.extra_keys[i]); + } + } + + for (uint32_t i = 0; i < extra.Length(); ++i) { + if (!validExtraKeys.Contains(extra[i].key)) { + return false; + } + } + + return true; +} + +RecordEventResult RecordEvent(const StaticMutexAutoLock& lock, + ProcessID processType, double timestamp, + const nsACString& category, + const nsACString& method, + const nsACString& object, + const Maybe<nsCString>& value, + const ExtraArray& extra) { + // Look up the event id. + EventKey eventKey; + if (!GetEventKey(lock, category, method, object, &eventKey)) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_RECORDING_ERROR::UnknownEvent); + return RecordEventResult::UnknownEvent; + } + + // If the event is expired or not enabled for this process, we silently drop + // this call. We don't want recording for expired probes to be an error so + // code doesn't have to be removed at a specific time or version. Even logging + // warnings would become very noisy. + if (IsExpired(eventKey)) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Expired); + return RecordEventResult::ExpiredEvent; + } + + // Fixup the process id only for non-builtin (e.g. supporting build faster) + // dynamic events. + auto dynamicNonBuiltin = + eventKey.dynamic && !(*gDynamicEventInfo)[eventKey.id].builtin; + if (dynamicNonBuiltin) { + processType = ProcessID::Dynamic; + } + + // Check whether the extra keys passed are valid. + if (!CheckExtraKeysValid(eventKey, extra)) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_RECORDING_ERROR::ExtraKey); + return RecordEventResult::InvalidExtraKey; + } + + // Check whether we can record this event. + if (!CanRecordEvent(lock, eventKey, processType)) { + return RecordEventResult::CannotRecord; + } + + // Count the number of times this event has been recorded, even if its + // category does not have recording enabled. + TelemetryScalar::SummarizeEvent(UniqueEventName(category, method, object), + processType, dynamicNonBuiltin); + + // Check whether this event's category has recording enabled + if (!gEnabledCategories.Contains(GetCategory(lock, eventKey))) { + return RecordEventResult::Ok; + } + + EventRecordArray* eventRecords = GetEventRecordsForProcess(lock, processType); + eventRecords->AppendElement(EventRecord(timestamp, eventKey, value, extra)); + + // Notify observers when we hit the "event" ping event record limit. + if (eventRecords->Length() == kEventPingLimit) { + return RecordEventResult::StorageLimitReached; + } + + return RecordEventResult::Ok; +} + +RecordEventResult ShouldRecordChildEvent(const StaticMutexAutoLock& lock, + const nsACString& category, + const nsACString& method, + const nsACString& object) { + EventKey eventKey; + if (!GetEventKey(lock, category, method, object, &eventKey)) { + // This event is unknown in this process, but it might be a dynamic event + // that was registered in the parent process. + return RecordEventResult::Ok; + } + + if (IsExpired(eventKey)) { + return RecordEventResult::ExpiredEvent; + } + + const auto processes = + gEventInfo[eventKey.id].common_info.record_in_processes; + if (!CanRecordInProcess(processes, XRE_GetProcessType())) { + return RecordEventResult::WrongProcess; + } + + return RecordEventResult::Ok; +} + +void RegisterEvents(const StaticMutexAutoLock& lock, const nsACString& category, + const nsTArray<DynamicEventInfo>& eventInfos, + const nsTArray<bool>& eventExpired, bool aBuiltin) { + MOZ_ASSERT(eventInfos.Length() == eventExpired.Length(), + "Event data array sizes should match."); + + // Register the new events. + if (!gDynamicEventInfo) { + gDynamicEventInfo = new nsTArray<DynamicEventInfo>(); + } + + for (uint32_t i = 0, len = eventInfos.Length(); i < len; ++i) { + const nsCString& eventName = UniqueEventName(eventInfos[i]); + + // Re-registering events can happen for two reasons and we don't print + // warnings: + // + // * When add-ons update. + // We don't support changing their definition, but the expiry might have + // changed. + // * When dynamic builtins ("build faster") events are registered. + // The dynamic definition takes precedence then. + EventKey existing; + if (!aBuiltin && gEventNameIDMap.Get(eventName, &existing)) { + if (eventExpired[i]) { + existing.id = kExpiredEventId; + } + continue; + } + + gDynamicEventInfo->AppendElement(eventInfos[i]); + uint32_t eventId = + eventExpired[i] ? kExpiredEventId : gDynamicEventInfo->Length() - 1; + gEventNameIDMap.InsertOrUpdate(eventName, EventKey{eventId, true}); + } + + // If it is a builtin, add the category name in order to enable it later. + if (aBuiltin) { + gCategoryNames.Insert(category); + } + + if (!aBuiltin) { + // Now after successful registration enable recording for this category + // (if not a dynamic builtin). + gEnabledCategories.Insert(category); + } +} + +} // anonymous namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: thread-unsafe helpers for event handling. + +namespace { + +nsresult SerializeEventsArray(const EventRecordArray& events, JSContext* cx, + JS::MutableHandle<JSObject*> result, + unsigned int dataset) { + // We serialize the events to a JS array. + JS::Rooted<JSObject*> eventsArray(cx, + JS::NewArrayObject(cx, events.Length())); + if (!eventsArray) { + return NS_ERROR_FAILURE; + } + + for (uint32_t i = 0; i < events.Length(); ++i) { + const EventRecord& record = events[i]; + + // Each entry is an array of one of the forms: + // [timestamp, category, method, object, value] + // [timestamp, category, method, object, null, extra] + // [timestamp, category, method, object, value, extra] + JS::RootedVector<JS::Value> items(cx); + + // Add timestamp. + JS::Rooted<JS::Value> val(cx); + if (!items.append(JS::NumberValue(floor(record.Timestamp())))) { + return NS_ERROR_FAILURE; + } + + // Add category, method, object. + auto addCategoryMethodObjectValues = [&](const nsACString& category, + const nsACString& method, + const nsACString& object) -> bool { + return items.append(JS::StringValue(ToJSString(cx, category))) && + items.append(JS::StringValue(ToJSString(cx, method))) && + items.append(JS::StringValue(ToJSString(cx, object))); + }; + + const EventKey& eventKey = record.GetEventKey(); + if (!eventKey.dynamic) { + const EventInfo& info = gEventInfo[eventKey.id]; + if (!addCategoryMethodObjectValues(info.common_info.category(), + info.method(), info.object())) { + return NS_ERROR_FAILURE; + } + } else if (gDynamicEventInfo) { + const DynamicEventInfo& info = (*gDynamicEventInfo)[eventKey.id]; + if (!addCategoryMethodObjectValues(info.category, info.method, + info.object)) { + return NS_ERROR_FAILURE; + } + } + + // Add the optional string value only when needed. + // When the value field is empty and extra is not set, we can save a little + // space that way. We still need to submit a null value if extra is set, to + // match the form: [ts, category, method, object, null, extra] + if (record.Value()) { + if (!items.append( + JS::StringValue(ToJSString(cx, record.Value().value())))) { + return NS_ERROR_FAILURE; + } + } else if (!record.Extra().IsEmpty()) { + if (!items.append(JS::NullValue())) { + return NS_ERROR_FAILURE; + } + } + + // Add the optional extra dictionary. + // To save a little space, only add it when it is not empty. + if (!record.Extra().IsEmpty()) { + JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx)); + if (!obj) { + return NS_ERROR_FAILURE; + } + + // Add extra key & value entries. + const ExtraArray& extra = record.Extra(); + for (uint32_t i = 0; i < extra.Length(); ++i) { + JS::Rooted<JS::Value> value(cx); + value.setString(ToJSString(cx, extra[i].value)); + + if (!JS_DefineProperty(cx, obj, extra[i].key.get(), value, + JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + val.setObject(*obj); + + if (!items.append(val)) { + return NS_ERROR_FAILURE; + } + } + + // Add the record to the events array. + JS::Rooted<JSObject*> itemsArray(cx, JS::NewArrayObject(cx, items)); + if (!JS_DefineElement(cx, eventsArray, i, itemsArray, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + + result.set(eventsArray); + return NS_OK; +} + +} // anonymous namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryEvents:: + +// This is a StaticMutex rather than a plain Mutex (1) so that +// it gets initialised in a thread-safe manner the first time +// it is used, and (2) because it is never de-initialised, and +// a normal Mutex would show up as a leak in BloatView. StaticMutex +// also has the "OffTheBooks" property, so it won't show as a leak +// in BloatView. +// Another reason to use a StaticMutex instead of a plain Mutex is +// that, due to the nature of Telemetry, we cannot rely on having a +// mutex initialized in InitializeGlobalState. Unfortunately, we +// cannot make sure that no other function is called before this point. +static StaticMutex gTelemetryEventsMutex MOZ_UNANNOTATED; + +void TelemetryEvent::InitializeGlobalState(bool aCanRecordBase, + bool aCanRecordExtended) { + StaticMutexAutoLock locker(gTelemetryEventsMutex); + MOZ_ASSERT(!gInitDone, + "TelemetryEvent::InitializeGlobalState " + "may only be called once"); + + gCanRecordBase = aCanRecordBase; + gCanRecordExtended = aCanRecordExtended; + + // Populate the static event name->id cache. Note that the event names are + // statically allocated and come from the automatically generated + // TelemetryEventData.h. + const uint32_t eventCount = + static_cast<uint32_t>(mozilla::Telemetry::EventID::EventCount); + for (uint32_t i = 0; i < eventCount; ++i) { + const EventInfo& info = gEventInfo[i]; + uint32_t eventId = i; + + // If this event is expired or not recorded in this process, mark it with + // a special event id. + // This avoids doing repeated checks at runtime. + if (IsExpiredVersion(info.common_info.expiration_version().get())) { + eventId = kExpiredEventId; + } + + gEventNameIDMap.InsertOrUpdate(UniqueEventName(info), + EventKey{eventId, false}); + gCategoryNames.Insert(info.common_info.category()); + } + + // A hack until bug 1691156 is fixed + gEnabledCategories.Insert("avif"_ns); + + gInitDone = true; +} + +void TelemetryEvent::DeInitializeGlobalState() { + StaticMutexAutoLock locker(gTelemetryEventsMutex); + MOZ_ASSERT(gInitDone); + + gCanRecordBase = false; + gCanRecordExtended = false; + + gEventNameIDMap.Clear(); + gCategoryNames.Clear(); + gEnabledCategories.Clear(); + gEventRecords.Clear(); + + gDynamicEventInfo = nullptr; + + gInitDone = false; +} + +void TelemetryEvent::SetCanRecordBase(bool b) { + StaticMutexAutoLock locker(gTelemetryEventsMutex); + gCanRecordBase = b; +} + +void TelemetryEvent::SetCanRecordExtended(bool b) { + StaticMutexAutoLock locker(gTelemetryEventsMutex); + gCanRecordExtended = b; +} + +nsresult TelemetryEvent::RecordChildEvents( + ProcessID aProcessType, + const nsTArray<mozilla::Telemetry::ChildEventData>& aEvents) { + MOZ_ASSERT(XRE_IsParentProcess()); + StaticMutexAutoLock locker(gTelemetryEventsMutex); + for (uint32_t i = 0; i < aEvents.Length(); ++i) { + const mozilla::Telemetry::ChildEventData& e = aEvents[i]; + + // Timestamps from child processes are absolute. We fix them up here to be + // relative to the main process start time. + // This allows us to put events from all processes on the same timeline. + double relativeTimestamp = + (e.timestamp - TimeStamp::ProcessCreation()).ToMilliseconds(); + + ::RecordEvent(locker, aProcessType, relativeTimestamp, e.category, e.method, + e.object, e.value, e.extra); + } + return NS_OK; +} + +nsresult TelemetryEvent::RecordEvent(const nsACString& aCategory, + const nsACString& aMethod, + const nsACString& aObject, + JS::Handle<JS::Value> aValue, + JS::Handle<JS::Value> aExtra, + JSContext* cx, uint8_t optional_argc) { + // Check value argument. + if ((optional_argc > 0) && !aValue.isNull() && !aValue.isString()) { + LogToBrowserConsole(nsIScriptError::warningFlag, + u"Invalid type for value parameter."_ns); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Value); + return NS_OK; + } + + // Extract value parameter. + Maybe<nsCString> value; + if (aValue.isString()) { + nsAutoJSString jsStr; + if (!jsStr.init(cx, aValue)) { + LogToBrowserConsole(nsIScriptError::warningFlag, + u"Invalid string value for value parameter."_ns); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Value); + return NS_OK; + } + + nsCString str = NS_ConvertUTF16toUTF8(jsStr); + if (str.Length() > kMaxValueByteLength) { + LogToBrowserConsole( + nsIScriptError::warningFlag, + nsLiteralString( + u"Value parameter exceeds maximum string length, truncating.")); + TruncateToByteLength(str, kMaxValueByteLength); + } + value = mozilla::Some(str); + } + + // Check extra argument. + if ((optional_argc > 1) && !aExtra.isNull() && !aExtra.isObject()) { + LogToBrowserConsole(nsIScriptError::warningFlag, + u"Invalid type for extra parameter."_ns); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Extra); + return NS_OK; + } + + // Extract extra dictionary. + ExtraArray extra; + if (aExtra.isObject()) { + JS::Rooted<JSObject*> obj(cx, &aExtra.toObject()); + JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx)); + if (!JS_Enumerate(cx, obj, &ids)) { + LogToBrowserConsole(nsIScriptError::warningFlag, + u"Failed to enumerate object."_ns); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Extra); + return NS_OK; + } + + for (size_t i = 0, n = ids.length(); i < n; i++) { + nsAutoJSString key; + if (!key.init(cx, ids[i])) { + LogToBrowserConsole( + nsIScriptError::warningFlag, + nsLiteralString( + u"Extra dictionary should only contain string keys.")); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Extra); + return NS_OK; + } + + JS::Rooted<JS::Value> value(cx); + if (!JS_GetPropertyById(cx, obj, ids[i], &value)) { + LogToBrowserConsole(nsIScriptError::warningFlag, + u"Failed to get extra property."_ns); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Extra); + return NS_OK; + } + + nsAutoJSString jsStr; + if (!value.isString() || !jsStr.init(cx, value)) { + LogToBrowserConsole(nsIScriptError::warningFlag, + u"Extra properties should have string values."_ns); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Extra); + return NS_OK; + } + + nsCString str = NS_ConvertUTF16toUTF8(jsStr); + if (str.Length() > kMaxExtraValueByteLength) { + LogToBrowserConsole( + nsIScriptError::warningFlag, + nsLiteralString( + u"Extra value exceeds maximum string length, truncating.")); + TruncateToByteLength(str, kMaxExtraValueByteLength); + } + + extra.AppendElement(EventExtraEntry{NS_ConvertUTF16toUTF8(key), str}); + } + } + + // Lock for accessing internal data. + // While the lock is being held, no complex calls like JS calls can be made, + // as all of these could record Telemetry, which would result in deadlock. + RecordEventResult res; + if (!XRE_IsParentProcess()) { + { + StaticMutexAutoLock lock(gTelemetryEventsMutex); + res = ::ShouldRecordChildEvent(lock, aCategory, aMethod, aObject); + } + + if (res == RecordEventResult::Ok) { + TelemetryIPCAccumulator::RecordChildEvent( + TimeStamp::NowLoRes(), aCategory, aMethod, aObject, value, extra); + } + } else { + StaticMutexAutoLock lock(gTelemetryEventsMutex); + + if (!gInitDone) { + return NS_ERROR_FAILURE; + } + + // Get the current time. + double timestamp = -1; + if (NS_WARN_IF(NS_FAILED(MsSinceProcessStart(×tamp)))) { + return NS_ERROR_FAILURE; + } + + res = ::RecordEvent(lock, ProcessID::Parent, timestamp, aCategory, aMethod, + aObject, value, extra); + } + + // Trigger warnings or errors where needed. + switch (res) { + case RecordEventResult::UnknownEvent: { + nsPrintfCString msg(R"(Unknown event: ["%s", "%s", "%s"])", + PromiseFlatCString(aCategory).get(), + PromiseFlatCString(aMethod).get(), + PromiseFlatCString(aObject).get()); + LogToBrowserConsole(nsIScriptError::errorFlag, + NS_ConvertUTF8toUTF16(msg)); + return NS_OK; + } + case RecordEventResult::InvalidExtraKey: { + nsPrintfCString msg(R"(Invalid extra key for event ["%s", "%s", "%s"].)", + PromiseFlatCString(aCategory).get(), + PromiseFlatCString(aMethod).get(), + PromiseFlatCString(aObject).get()); + LogToBrowserConsole(nsIScriptError::warningFlag, + NS_ConvertUTF8toUTF16(msg)); + return NS_OK; + } + case RecordEventResult::StorageLimitReached: { + LogToBrowserConsole(nsIScriptError::warningFlag, + u"Event storage limit reached."_ns); + nsCOMPtr<nsIObserverService> serv = + mozilla::services::GetObserverService(); + if (serv) { + serv->NotifyObservers(nullptr, "event-telemetry-storage-limit-reached", + nullptr); + } + return NS_OK; + } + default: + return NS_OK; + } +} + +void TelemetryEvent::RecordEventNative( + mozilla::Telemetry::EventID aId, const mozilla::Maybe<nsCString>& aValue, + const mozilla::Maybe<ExtraArray>& aExtra) { + // Truncate aValue if present and necessary. + mozilla::Maybe<nsCString> value; + if (aValue) { + nsCString valueStr = aValue.ref(); + if (valueStr.Length() > kMaxValueByteLength) { + TruncateToByteLength(valueStr, kMaxValueByteLength); + } + value = mozilla::Some(valueStr); + } + + // Truncate any over-long extra values. + ExtraArray extra; + if (aExtra) { + extra = aExtra.value(); + for (auto& item : extra) { + if (item.value.Length() > kMaxExtraValueByteLength) { + TruncateToByteLength(item.value, kMaxExtraValueByteLength); + } + } + } + + const EventInfo& info = gEventInfo[static_cast<uint32_t>(aId)]; + const nsCString category(info.common_info.category()); + const nsCString method(info.method()); + const nsCString object(info.object()); + if (!XRE_IsParentProcess()) { + RecordEventResult res; + { + StaticMutexAutoLock lock(gTelemetryEventsMutex); + res = ::ShouldRecordChildEvent(lock, category, method, object); + } + + if (res == RecordEventResult::Ok) { + TelemetryIPCAccumulator::RecordChildEvent(TimeStamp::NowLoRes(), category, + method, object, value, extra); + } + } else { + StaticMutexAutoLock lock(gTelemetryEventsMutex); + + if (!gInitDone) { + return; + } + + // Get the current time. + double timestamp = -1; + if (NS_WARN_IF(NS_FAILED(MsSinceProcessStart(×tamp)))) { + return; + } + + ::RecordEvent(lock, ProcessID::Parent, timestamp, category, method, object, + value, extra); + } +} + +static bool GetArrayPropertyValues(JSContext* cx, JS::Handle<JSObject*> obj, + const char* property, + nsTArray<nsCString>* results) { + JS::Rooted<JS::Value> value(cx); + if (!JS_GetProperty(cx, obj, property, &value)) { + JS_ReportErrorASCII(cx, R"(Missing required property "%s" for event)", + property); + return false; + } + + bool isArray = false; + if (!JS::IsArrayObject(cx, value, &isArray) || !isArray) { + JS_ReportErrorASCII(cx, R"(Property "%s" for event should be an array)", + property); + return false; + } + + JS::Rooted<JSObject*> arrayObj(cx, &value.toObject()); + uint32_t arrayLength; + if (!JS::GetArrayLength(cx, arrayObj, &arrayLength)) { + return false; + } + + for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; ++arrayIdx) { + JS::Rooted<JS::Value> element(cx); + if (!JS_GetElement(cx, arrayObj, arrayIdx, &element)) { + return false; + } + + if (!element.isString()) { + JS_ReportErrorASCII( + cx, R"(Array entries for event property "%s" should be strings)", + property); + return false; + } + + nsAutoJSString jsStr; + if (!jsStr.init(cx, element)) { + return false; + } + + results->AppendElement(NS_ConvertUTF16toUTF8(jsStr)); + } + + return true; +} + +nsresult TelemetryEvent::RegisterEvents(const nsACString& aCategory, + JS::Handle<JS::Value> aEventData, + bool aBuiltin, JSContext* cx) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Events can only be registered in the parent process"); + + if (!IsValidIdentifierString(aCategory, 30, true, true)) { + JS_ReportErrorASCII( + cx, "Category parameter should match the identifier pattern."); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Category); + return NS_ERROR_INVALID_ARG; + } + + if (!aEventData.isObject()) { + JS_ReportErrorASCII(cx, "Event data parameter should be an object"); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other); + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted<JSObject*> obj(cx, &aEventData.toObject()); + JS::Rooted<JS::IdVector> eventPropertyIds(cx, JS::IdVector(cx)); + if (!JS_Enumerate(cx, obj, &eventPropertyIds)) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other); + return NS_ERROR_FAILURE; + } + + // Collect the event data into local storage first. + // Only after successfully validating all contained events will we register + // them into global storage. + nsTArray<DynamicEventInfo> newEventInfos; + nsTArray<bool> newEventExpired; + + for (size_t i = 0, n = eventPropertyIds.length(); i < n; i++) { + nsAutoJSString eventName; + if (!eventName.init(cx, eventPropertyIds[i])) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other); + return NS_ERROR_FAILURE; + } + + if (!IsValidIdentifierString(NS_ConvertUTF16toUTF8(eventName), + kMaxMethodNameByteLength, false, true)) { + JS_ReportErrorASCII(cx, + "Event names should match the identifier pattern."); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Name); + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted<JS::Value> value(cx); + if (!JS_GetPropertyById(cx, obj, eventPropertyIds[i], &value) || + !value.isObject()) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other); + return NS_ERROR_FAILURE; + } + JS::Rooted<JSObject*> eventObj(cx, &value.toObject()); + + // Extract the event registration data. + nsTArray<nsCString> methods; + nsTArray<nsCString> objects; + nsTArray<nsCString> extra_keys; + bool expired = false; + bool recordOnRelease = false; + + // The methods & objects properties are required. + if (!GetArrayPropertyValues(cx, eventObj, "methods", &methods)) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other); + return NS_ERROR_FAILURE; + } + + if (!GetArrayPropertyValues(cx, eventObj, "objects", &objects)) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other); + return NS_ERROR_FAILURE; + } + + // extra_keys is optional. + bool hasProperty = false; + if (JS_HasProperty(cx, eventObj, "extra_keys", &hasProperty) && + hasProperty) { + if (!GetArrayPropertyValues(cx, eventObj, "extra_keys", &extra_keys)) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other); + return NS_ERROR_FAILURE; + } + } + + // expired is optional. + if (JS_HasProperty(cx, eventObj, "expired", &hasProperty) && hasProperty) { + JS::Rooted<JS::Value> temp(cx); + if (!JS_GetProperty(cx, eventObj, "expired", &temp) || + !temp.isBoolean()) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other); + return NS_ERROR_FAILURE; + } + + expired = temp.toBoolean(); + } + + // record_on_release is optional. + if (JS_HasProperty(cx, eventObj, "record_on_release", &hasProperty) && + hasProperty) { + JS::Rooted<JS::Value> temp(cx); + if (!JS_GetProperty(cx, eventObj, "record_on_release", &temp) || + !temp.isBoolean()) { + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other); + return NS_ERROR_FAILURE; + } + + recordOnRelease = temp.toBoolean(); + } + + // Validate methods. + for (auto& method : methods) { + if (!IsValidIdentifierString(method, kMaxMethodNameByteLength, false, + true)) { + JS_ReportErrorASCII( + cx, "Method names should match the identifier pattern."); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Method); + return NS_ERROR_INVALID_ARG; + } + } + + // Validate objects. + for (auto& object : objects) { + if (!IsValidIdentifierString(object, kMaxObjectNameByteLength, false, + true)) { + JS_ReportErrorASCII( + cx, "Object names should match the identifier pattern."); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Object); + return NS_ERROR_INVALID_ARG; + } + } + + // Validate extra keys. + if (extra_keys.Length() > kMaxExtraKeyCount) { + JS_ReportErrorASCII(cx, "No more than 10 extra keys can be registered."); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::ExtraKeys); + return NS_ERROR_INVALID_ARG; + } + for (auto& key : extra_keys) { + if (!IsValidIdentifierString(key, kMaxExtraKeyNameByteLength, false, + true)) { + JS_ReportErrorASCII( + cx, "Extra key names should match the identifier pattern."); + mozilla::Telemetry::AccumulateCategorical( + LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::ExtraKeys); + return NS_ERROR_INVALID_ARG; + } + } + + // Append event infos to be registered. + for (auto& method : methods) { + for (auto& object : objects) { + // We defer the actual registration here in case any other event + // description is invalid. In that case we don't need to roll back any + // partial registration. + DynamicEventInfo info{aCategory, method, object, + extra_keys, recordOnRelease, aBuiltin}; + newEventInfos.AppendElement(info); + newEventExpired.AppendElement(expired); + } + } + } + + { + StaticMutexAutoLock locker(gTelemetryEventsMutex); + RegisterEvents(locker, aCategory, newEventInfos, newEventExpired, aBuiltin); + } + + return NS_OK; +} + +nsresult TelemetryEvent::CreateSnapshots(uint32_t aDataset, bool aClear, + uint32_t aEventLimit, JSContext* cx, + uint8_t optional_argc, + JS::MutableHandle<JS::Value> aResult) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + // Creating a JS snapshot of the events is a two-step process: + // (1) Lock the storage and copy the events into function-local storage. + // (2) Serialize the events into JS. + // We can't hold a lock for (2) because we will run into deadlocks otherwise + // from JS recording Telemetry. + + // (1) Extract the events from storage with a lock held. + nsTArray<std::pair<const char*, EventRecordArray>> processEvents; + nsTArray<std::pair<uint32_t, EventRecordArray>> leftovers; + { + StaticMutexAutoLock locker(gTelemetryEventsMutex); + + if (!gInitDone) { + return NS_ERROR_FAILURE; + } + + // The snapshotting function is the same for both static and dynamic builtin + // events. We can use the same function and store the events in the same + // output storage. + auto snapshotter = [aDataset, &locker, &processEvents, &leftovers, aClear, + optional_argc, + aEventLimit](EventRecordsMapType& aProcessStorage) { + for (const auto& entry : aProcessStorage) { + const EventRecordArray* eventStorage = entry.GetWeak(); + EventRecordArray events; + EventRecordArray leftoverEvents; + + const uint32_t len = eventStorage->Length(); + for (uint32_t i = 0; i < len; ++i) { + const EventRecord& record = (*eventStorage)[i]; + if (IsInDataset(GetDataset(locker, record.GetEventKey()), aDataset)) { + // If we have a limit, adhere to it. If we have a limit and are + // going to clear, save the leftovers for later. + if (optional_argc < 2 || events.Length() < aEventLimit) { + events.AppendElement(record); + } else if (aClear) { + leftoverEvents.AppendElement(record); + } + } + } + + if (events.Length()) { + const char* processName = + GetNameForProcessID(ProcessID(entry.GetKey())); + processEvents.EmplaceBack(processName, std::move(events)); + if (leftoverEvents.Length()) { + leftovers.EmplaceBack(entry.GetKey(), std::move(leftoverEvents)); + } + } + } + }; + + // Take a snapshot of the plain and dynamic builtin events. + snapshotter(gEventRecords); + if (aClear) { + gEventRecords.Clear(); + for (auto& pair : leftovers) { + gEventRecords.InsertOrUpdate( + pair.first, MakeUnique<EventRecordArray>(std::move(pair.second))); + } + leftovers.Clear(); + } + } + + // (2) Serialize the events to a JS object. + JS::Rooted<JSObject*> rootObj(cx, JS_NewPlainObject(cx)); + if (!rootObj) { + return NS_ERROR_FAILURE; + } + + const uint32_t processLength = processEvents.Length(); + for (uint32_t i = 0; i < processLength; ++i) { + JS::Rooted<JSObject*> eventsArray(cx); + if (NS_FAILED(SerializeEventsArray(processEvents[i].second, cx, + &eventsArray, aDataset))) { + return NS_ERROR_FAILURE; + } + + if (!JS_DefineProperty(cx, rootObj, processEvents[i].first, eventsArray, + JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + + aResult.setObject(*rootObj); + return NS_OK; +} + +/** + * Resets all the stored events. This is intended to be only used in tests. + */ +void TelemetryEvent::ClearEvents() { + StaticMutexAutoLock lock(gTelemetryEventsMutex); + + if (!gInitDone) { + return; + } + + gEventRecords.Clear(); +} + +void TelemetryEvent::SetEventRecordingEnabled(const nsACString& category, + bool enabled) { + StaticMutexAutoLock locker(gTelemetryEventsMutex); + + if (!gCategoryNames.Contains(category)) { + LogToBrowserConsole( + nsIScriptError::warningFlag, + NS_ConvertUTF8toUTF16( + nsLiteralCString( + "Unknown category for SetEventRecordingEnabled: ") + + category)); + return; + } + + if (enabled) { + gEnabledCategories.Insert(category); + } else { + gEnabledCategories.Remove(category); + } +} + +size_t TelemetryEvent::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + StaticMutexAutoLock locker(gTelemetryEventsMutex); + size_t n = 0; + + auto getSizeOfRecords = [aMallocSizeOf](auto& storageMap) { + size_t partial = storageMap.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& eventRecords : storageMap.Values()) { + partial += eventRecords->ShallowSizeOfIncludingThis(aMallocSizeOf); + + const uint32_t len = eventRecords->Length(); + for (uint32_t i = 0; i < len; ++i) { + partial += (*eventRecords)[i].SizeOfExcludingThis(aMallocSizeOf); + } + } + return partial; + }; + + n += getSizeOfRecords(gEventRecords); + + n += gEventNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = gEventNameIDMap.ConstIter(); !iter.Done(); iter.Next()) { + n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + n += gCategoryNames.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += gEnabledCategories.ShallowSizeOfExcludingThis(aMallocSizeOf); + + if (gDynamicEventInfo) { + n += gDynamicEventInfo->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (auto& info : *gDynamicEventInfo) { + n += info.SizeOfExcludingThis(aMallocSizeOf); + } + } + + return n; +} diff --git a/toolkit/components/telemetry/core/TelemetryEvent.h b/toolkit/components/telemetry/core/TelemetryEvent.h new file mode 100644 index 0000000000..8734df1174 --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryEvent.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryEvent_h__ +#define TelemetryEvent_h__ + +#include <stdint.h> +#include "js/TypeDecls.h" +#include "mozilla/Maybe.h" +#include "mozilla/TelemetryEventEnums.h" +#include "mozilla/TelemetryProcessEnums.h" +#include "nsTArray.h" +#include "nsStringFwd.h" + +namespace mozilla { +namespace Telemetry { +struct ChildEventData; +struct EventExtraEntry; +} // namespace Telemetry +} // namespace mozilla + +using mozilla::Telemetry::EventExtraEntry; + +// This module is internal to Telemetry. It encapsulates Telemetry's +// event recording and storage logic. It should only be used by +// Telemetry.cpp. These functions should not be used anywhere else. +// For the public interface to Telemetry functionality, see Telemetry.h. + +namespace TelemetryEvent { + +void InitializeGlobalState(bool canRecordBase, bool canRecordExtended); +void DeInitializeGlobalState(); + +void SetCanRecordBase(bool b); +void SetCanRecordExtended(bool b); + +// C++ API Endpoint. +void RecordEventNative( + mozilla::Telemetry::EventID aId, const mozilla::Maybe<nsCString>& aValue, + const mozilla::Maybe<CopyableTArray<EventExtraEntry>>& aExtra); + +// JS API Endpoints. +nsresult RecordEvent(const nsACString& aCategory, const nsACString& aMethod, + const nsACString& aObject, JS::Handle<JS::Value> aValue, + JS::Handle<JS::Value> aExtra, JSContext* aCx, + uint8_t optional_argc); + +void SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled); +nsresult RegisterEvents(const nsACString& aCategory, + JS::Handle<JS::Value> aEventData, bool aBuiltin, + JSContext* cx); + +nsresult CreateSnapshots(uint32_t aDataset, bool aClear, uint32_t aEventLimit, + JSContext* aCx, uint8_t optional_argc, + JS::MutableHandle<JS::Value> aResult); + +// Record events from child processes. +nsresult RecordChildEvents( + mozilla::Telemetry::ProcessID aProcessType, + const nsTArray<mozilla::Telemetry::ChildEventData>& aEvents); + +// Only to be used for testing. +void ClearEvents(); + +size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + +} // namespace TelemetryEvent + +#endif // TelemetryEvent_h__ diff --git a/toolkit/components/telemetry/core/TelemetryHistogram.cpp b/toolkit/components/telemetry/core/TelemetryHistogram.cpp new file mode 100644 index 0000000000..dea6c94079 --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryHistogram.cpp @@ -0,0 +1,3681 @@ +/* -*- 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 "TelemetryHistogram.h" + +#include <limits> +#include "base/histogram.h" +#include "geckoview/streaming/GeckoViewStreamingTelemetry.h" +#include "ipc/TelemetryIPCAccumulator.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject +#include "js/GCAPI.h" +#include "js/Object.h" // JS::GetClass, JS::GetMaybePtrFromReservedSlot, JS::SetReservedSlot +#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineFunction, JS_DefineProperty, JS_DefineUCProperty, JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/Atomics.h" +#include "mozilla/JSONWriter.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/Unused.h" +#include "nsClassHashtable.h" +#include "nsString.h" +#include "nsHashKeys.h" +#include "nsITelemetry.h" +#include "nsPrintfCString.h" +#include "TelemetryHistogramNameMap.h" +#include "TelemetryScalar.h" + +using base::BooleanHistogram; +using base::CountHistogram; +using base::FlagHistogram; +using base::LinearHistogram; +using mozilla::MakeTuple; +using mozilla::MakeUnique; +using mozilla::StaticMutex; +using mozilla::StaticMutexAutoLock; +using mozilla::UniquePtr; +using mozilla::Telemetry::HistogramAccumulation; +using mozilla::Telemetry::HistogramCount; +using mozilla::Telemetry::HistogramID; +using mozilla::Telemetry::HistogramIDByNameLookup; +using mozilla::Telemetry::KeyedHistogramAccumulation; +using mozilla::Telemetry::ProcessID; +using mozilla::Telemetry::Common::CanRecordDataset; +using mozilla::Telemetry::Common::CanRecordProduct; +using mozilla::Telemetry::Common::GetCurrentProduct; +using mozilla::Telemetry::Common::GetIDForProcessName; +using mozilla::Telemetry::Common::GetNameForProcessID; +using mozilla::Telemetry::Common::IsExpiredVersion; +using mozilla::Telemetry::Common::IsInDataset; +using mozilla::Telemetry::Common::LogToBrowserConsole; +using mozilla::Telemetry::Common::RecordedProcessType; +using mozilla::Telemetry::Common::StringHashSet; +using mozilla::Telemetry::Common::SupportedProduct; +using mozilla::Telemetry::Common::ToJSString; + +namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator; + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// Naming: there are two kinds of functions in this file: +// +// * Functions named internal_*: these can only be reached via an +// interface function (TelemetryHistogram::*). They mostly expect +// the interface function to have acquired +// |gTelemetryHistogramMutex|, so they do not have to be +// thread-safe. However, those internal_* functions that are +// reachable from internal_WrapAndReturnHistogram and +// internal_WrapAndReturnKeyedHistogram can sometimes be called +// without |gTelemetryHistogramMutex|, and so might be racey. +// +// * Functions named TelemetryHistogram::*. This is the external interface. +// Entries and exits to these functions are serialised using +// |gTelemetryHistogramMutex|, except for GetKeyedHistogramSnapshots and +// CreateHistogramSnapshots. +// +// Avoiding races and deadlocks: +// +// All functions in the external interface (TelemetryHistogram::*) are +// serialised using the mutex |gTelemetryHistogramMutex|. This means +// that the external interface is thread-safe, and many of the +// internal_* functions can ignore thread safety. But it also brings +// a danger of deadlock if any function in the external interface can +// get back to that interface. That is, we will deadlock on any call +// chain like this +// +// TelemetryHistogram::* -> .. any functions .. -> TelemetryHistogram::* +// +// To reduce the danger of that happening, observe the following rules: +// +// * No function in TelemetryHistogram::* may directly call, nor take the +// address of, any other function in TelemetryHistogram::*. +// +// * No internal function internal_* may call, nor take the address +// of, any function in TelemetryHistogram::*. +// +// internal_WrapAndReturnHistogram and +// internal_WrapAndReturnKeyedHistogram are not protected by +// |gTelemetryHistogramMutex| because they make calls to the JS +// engine, but that can in turn call back to Telemetry and hence back +// to a TelemetryHistogram:: function, in order to report GC and other +// statistics. This would lead to deadlock due to attempted double +// acquisition of |gTelemetryHistogramMutex|, if the internal_* functions +// were required to be protected by |gTelemetryHistogramMutex|. To +// break that cycle, we relax that requirement. Unfortunately this +// means that this file is not guaranteed race-free. + +// This is a StaticMutex rather than a plain Mutex (1) so that +// it gets initialised in a thread-safe manner the first time +// it is used, and (2) because it is never de-initialised, and +// a normal Mutex would show up as a leak in BloatView. StaticMutex +// also has the "OffTheBooks" property, so it won't show as a leak +// in BloatView. +static StaticMutex gTelemetryHistogramMutex MOZ_UNANNOTATED; + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE TYPES + +namespace { + +// Hardcoded probes +// +// The order of elements here is important to minimize the memory footprint of a +// HistogramInfo instance. +// +// Any adjustements need to be reflected in gen_histogram_data.py +struct HistogramInfo { + uint32_t min; + uint32_t max; + uint32_t bucketCount; + uint32_t name_offset; + uint32_t expiration_offset; + uint32_t label_count; + uint32_t key_count; + uint32_t store_count; + uint16_t label_index; + uint16_t key_index; + uint16_t store_index; + RecordedProcessType record_in_processes; + bool keyed; + uint8_t histogramType; + uint8_t dataset; + SupportedProduct products; + + const char* name() const; + const char* expiration() const; + nsresult label_id(const char* label, uint32_t* labelId) const; + bool allows_key(const nsACString& key) const; + bool is_single_store() const; +}; + +// Structs used to keep information about the histograms for which a +// snapshot should be created. +struct HistogramSnapshotData { + CopyableTArray<base::Histogram::Sample> mBucketRanges; + CopyableTArray<base::Histogram::Count> mBucketCounts; + int64_t mSampleSum; // Same type as base::Histogram::SampleSet::sum_ +}; + +struct HistogramSnapshotInfo { + HistogramSnapshotData data; + HistogramID histogramID; +}; + +typedef mozilla::Vector<HistogramSnapshotInfo> HistogramSnapshotsArray; +typedef mozilla::Vector<HistogramSnapshotsArray> HistogramProcessSnapshotsArray; + +// The following is used to handle snapshot information for keyed histograms. +typedef nsTHashMap<nsCStringHashKey, HistogramSnapshotData> + KeyedHistogramSnapshotData; + +struct KeyedHistogramSnapshotInfo { + KeyedHistogramSnapshotData data; + HistogramID histogramId; +}; + +typedef mozilla::Vector<KeyedHistogramSnapshotInfo> + KeyedHistogramSnapshotsArray; +typedef mozilla::Vector<KeyedHistogramSnapshotsArray> + KeyedHistogramProcessSnapshotsArray; + +/** + * A Histogram storage engine. + * + * Takes care of recording data into multiple stores if necessary. + */ +class Histogram { + public: + /* + * Create a new histogram instance from the given info. + * + * If the histogram is already expired, this does not allocate. + */ + Histogram(HistogramID histogramId, const HistogramInfo& info, bool expired); + ~Histogram(); + + /** + * Add a sample to this histogram in all registered stores. + */ + void Add(uint32_t sample); + + /** + * Clear the named store for this histogram. + */ + void Clear(const nsACString& store); + + /** + * Get the histogram instance from the named store. + */ + bool GetHistogram(const nsACString& store, base::Histogram** h); + + bool IsExpired() const { return mIsExpired; } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + private: + // String -> Histogram* + typedef nsClassHashtable<nsCStringHashKey, base::Histogram> HistogramStore; + HistogramStore mStorage; + + // A valid pointer if this histogram belongs to only the main store + base::Histogram* mSingleStore; + + // We don't track stores for expired histograms. + // We just store a single flag and all other operations become a no-op. + bool mIsExpired; +}; + +class KeyedHistogram { + public: + KeyedHistogram(HistogramID id, const HistogramInfo& info, bool expired); + ~KeyedHistogram(); + nsresult GetHistogram(const nsCString& aStore, const nsCString& key, + base::Histogram** histogram); + base::Histogram* GetHistogram(const nsCString& aStore, const nsCString& key); + uint32_t GetHistogramType() const { return mHistogramInfo.histogramType; } + nsresult GetKeys(const StaticMutexAutoLock& aLock, const nsCString& store, + nsTArray<nsCString>& aKeys); + // Note: unlike other methods, GetJSSnapshot is thread safe. + nsresult GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj, + const nsACString& aStore, bool clearSubsession); + nsresult GetSnapshot(const StaticMutexAutoLock& aLock, + const nsACString& aStore, + KeyedHistogramSnapshotData& aSnapshot, + bool aClearSubsession); + + nsresult Add(const nsCString& key, uint32_t aSample, ProcessID aProcessType); + void Clear(const nsACString& aStore); + + HistogramID GetHistogramID() const { return mId; } + + bool IsEmpty(const nsACString& aStore) const; + + bool IsExpired() const { return mIsExpired; } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + private: + typedef nsClassHashtable<nsCStringHashKey, base::Histogram> + KeyedHistogramMapType; + typedef nsClassHashtable<nsCStringHashKey, KeyedHistogramMapType> + StoreMapType; + + StoreMapType mStorage; + // A valid map if this histogram belongs to only the main store + KeyedHistogramMapType* mSingleStore; + + const HistogramID mId; + const HistogramInfo& mHistogramInfo; + bool mIsExpired; +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE STATE, SHARED BY ALL THREADS + +namespace { + +// Set to true once this global state has been initialized +bool gInitDone = false; + +// Whether we are collecting the base, opt-out, Histogram data. +bool gCanRecordBase = false; +// Whether we are collecting the extended, opt-in, Histogram data. +bool gCanRecordExtended = false; + +// The storage for actual Histogram instances. +// We use separate ones for plain and keyed histograms. +Histogram** gHistogramStorage; +// Keyed histograms internally map string keys to individual Histogram +// instances. +KeyedHistogram** gKeyedHistogramStorage; + +// To simplify logic below we use a single histogram instance for all expired +// histograms. +Histogram* gExpiredHistogram = nullptr; + +// The single placeholder for expired keyed histograms. +KeyedHistogram* gExpiredKeyedHistogram = nullptr; + +// This tracks whether recording is enabled for specific histograms. +// To utilize C++ initialization rules, we invert the meaning to "disabled". +bool gHistogramRecordingDisabled[HistogramCount] = {}; + +// This is for gHistogramInfos, gHistogramStringTable +#include "TelemetryHistogramData.inc" + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE CONSTANTS + +namespace { + +// List of histogram IDs which should have recording disabled initially. +const HistogramID kRecordingInitiallyDisabledIDs[] = { + mozilla::Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, + + // The array must not be empty. Leave these item here. + mozilla::Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD, + mozilla::Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD}; + +const char* TEST_HISTOGRAM_PREFIX = "TELEMETRY_TEST_"; + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// The core storage access functions. +// They wrap access to the histogram storage and lookup caches. + +namespace { + +size_t internal_KeyedHistogramStorageIndex(HistogramID aHistogramId, + ProcessID aProcessId) { + return aHistogramId * size_t(ProcessID::Count) + size_t(aProcessId); +} + +size_t internal_HistogramStorageIndex(const StaticMutexAutoLock& aLock, + HistogramID aHistogramId, + ProcessID aProcessId) { + static_assert(HistogramCount < std::numeric_limits<size_t>::max() / + size_t(ProcessID::Count), + "Too many histograms and processes to store in a 1D array."); + + return aHistogramId * size_t(ProcessID::Count) + size_t(aProcessId); +} + +Histogram* internal_GetHistogramFromStorage(const StaticMutexAutoLock& aLock, + HistogramID aHistogramId, + ProcessID aProcessId) { + size_t index = + internal_HistogramStorageIndex(aLock, aHistogramId, aProcessId); + return gHistogramStorage[index]; +} + +void internal_SetHistogramInStorage(const StaticMutexAutoLock& aLock, + HistogramID aHistogramId, + ProcessID aProcessId, + Histogram* aHistogram) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Histograms are stored only in the parent process."); + + size_t index = + internal_HistogramStorageIndex(aLock, aHistogramId, aProcessId); + MOZ_ASSERT(!gHistogramStorage[index], + "Mustn't overwrite storage without clearing it first."); + gHistogramStorage[index] = aHistogram; +} + +KeyedHistogram* internal_GetKeyedHistogramFromStorage(HistogramID aHistogramId, + ProcessID aProcessId) { + size_t index = internal_KeyedHistogramStorageIndex(aHistogramId, aProcessId); + return gKeyedHistogramStorage[index]; +} + +void internal_SetKeyedHistogramInStorage(HistogramID aHistogramId, + ProcessID aProcessId, + KeyedHistogram* aKeyedHistogram) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Keyed Histograms are stored only in the parent process."); + + size_t index = internal_KeyedHistogramStorageIndex(aHistogramId, aProcessId); + MOZ_ASSERT(!gKeyedHistogramStorage[index], + "Mustn't overwrite storage without clearing it first"); + gKeyedHistogramStorage[index] = aKeyedHistogram; +} + +// Factory function for base::Histogram instances. +base::Histogram* internal_CreateBaseHistogramInstance(const HistogramInfo& info, + int bucketsOffset); + +// Factory function for histogram instances. +Histogram* internal_CreateHistogramInstance(HistogramID histogramId); + +bool internal_IsHistogramEnumId(HistogramID aID) { + static_assert(((HistogramID)-1 > 0), "ID should be unsigned."); + return aID < HistogramCount; +} + +// Look up a plain histogram by id. +Histogram* internal_GetHistogramById(const StaticMutexAutoLock& aLock, + HistogramID histogramId, + ProcessID processId, + bool instantiate = true) { + MOZ_ASSERT(internal_IsHistogramEnumId(histogramId)); + MOZ_ASSERT(!gHistogramInfos[histogramId].keyed); + MOZ_ASSERT(processId < ProcessID::Count); + + Histogram* h = + internal_GetHistogramFromStorage(aLock, histogramId, processId); + if (h || !instantiate) { + return h; + } + + h = internal_CreateHistogramInstance(histogramId); + MOZ_ASSERT(h); + internal_SetHistogramInStorage(aLock, histogramId, processId, h); + + return h; +} + +// Look up a keyed histogram by id. +KeyedHistogram* internal_GetKeyedHistogramById(HistogramID histogramId, + ProcessID processId, + bool instantiate = true) { + MOZ_ASSERT(internal_IsHistogramEnumId(histogramId)); + MOZ_ASSERT(gHistogramInfos[histogramId].keyed); + MOZ_ASSERT(processId < ProcessID::Count); + + KeyedHistogram* kh = + internal_GetKeyedHistogramFromStorage(histogramId, processId); + if (kh || !instantiate) { + return kh; + } + + const HistogramInfo& info = gHistogramInfos[histogramId]; + const bool isExpired = IsExpiredVersion(info.expiration()); + + // If the keyed histogram is expired, set its storage to the expired + // keyed histogram. + if (isExpired) { + if (!gExpiredKeyedHistogram) { + // If we don't have an expired keyed histogram, create one. + gExpiredKeyedHistogram = + new KeyedHistogram(histogramId, info, true /* expired */); + MOZ_ASSERT(gExpiredKeyedHistogram); + } + kh = gExpiredKeyedHistogram; + } else { + kh = new KeyedHistogram(histogramId, info, false /* expired */); + } + + internal_SetKeyedHistogramInStorage(histogramId, processId, kh); + + return kh; +} + +// Look up a histogram id from a histogram name. +nsresult internal_GetHistogramIdByName(const StaticMutexAutoLock& aLock, + const nsACString& name, + HistogramID* id) { + const uint32_t idx = HistogramIDByNameLookup(name); + MOZ_ASSERT(idx < HistogramCount, + "Intermediate lookup should always give a valid index."); + + // The lookup hashes the input and uses it as an index into the value array. + // Hash collisions can still happen for unknown values, + // therefore we check that the name matches. + if (name.Equals(gHistogramInfos[idx].name())) { + *id = HistogramID(idx); + return NS_OK; + } + + return NS_ERROR_ILLEGAL_VALUE; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: Misc small helpers + +namespace { + +bool internal_CanRecordBase() { return gCanRecordBase; } + +bool internal_CanRecordExtended() { return gCanRecordExtended; } + +bool internal_AttemptedGPUProcess() { + // Check if it was tried to launch a process. + bool attemptedGPUProcess = false; + if (auto gpm = mozilla::gfx::GPUProcessManager::Get()) { + attemptedGPUProcess = gpm->AttemptedGPUProcess(); + } + return attemptedGPUProcess; +} + +// Note: this is completely unrelated to mozilla::IsEmpty. +bool internal_IsEmpty(const StaticMutexAutoLock& aLock, + const base::Histogram* h) { + return h->is_empty(); +} + +void internal_SetHistogramRecordingEnabled(const StaticMutexAutoLock& aLock, + HistogramID id, bool aEnabled) { + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + gHistogramRecordingDisabled[id] = !aEnabled; +} + +bool internal_IsRecordingEnabled(HistogramID id) { + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + return !gHistogramRecordingDisabled[id]; +} + +const char* HistogramInfo::name() const { + return &gHistogramStringTable[this->name_offset]; +} + +const char* HistogramInfo::expiration() const { + return &gHistogramStringTable[this->expiration_offset]; +} + +nsresult HistogramInfo::label_id(const char* label, uint32_t* labelId) const { + MOZ_ASSERT(label); + MOZ_ASSERT(this->histogramType == nsITelemetry::HISTOGRAM_CATEGORICAL); + if (this->histogramType != nsITelemetry::HISTOGRAM_CATEGORICAL) { + return NS_ERROR_FAILURE; + } + + for (uint32_t i = 0; i < this->label_count; ++i) { + // gHistogramLabelTable contains the indices of the label strings in the + // gHistogramStringTable. + // They are stored in-order and consecutively, from the offset label_index + // to (label_index + label_count). + uint32_t string_offset = gHistogramLabelTable[this->label_index + i]; + const char* const str = &gHistogramStringTable[string_offset]; + if (::strcmp(label, str) == 0) { + *labelId = i; + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +bool HistogramInfo::allows_key(const nsACString& key) const { + MOZ_ASSERT(this->keyed); + + // If we didn't specify a list of allowed keys, just return true. + if (this->key_count == 0) { + return true; + } + + // Otherwise, check if |key| is in the list of allowed keys. + for (uint32_t i = 0; i < this->key_count; ++i) { + // gHistogramKeyTable contains the indices of the key strings in the + // gHistogramStringTable. They are stored in-order and consecutively, + // from the offset key_index to (key_index + key_count). + uint32_t string_offset = gHistogramKeyTable[this->key_index + i]; + const char* const str = &gHistogramStringTable[string_offset]; + if (key.EqualsASCII(str)) { + return true; + } + } + + // |key| was not found. + return false; +} + +bool HistogramInfo::is_single_store() const { + return store_count == 1 && store_index == UINT16_MAX; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: Histogram Get, Add, Clone, Clear functions + +namespace { + +nsresult internal_CheckHistogramArguments(const HistogramInfo& info) { + if (info.histogramType != nsITelemetry::HISTOGRAM_BOOLEAN && + info.histogramType != nsITelemetry::HISTOGRAM_FLAG && + info.histogramType != nsITelemetry::HISTOGRAM_COUNT) { + // Sanity checks for histogram parameters. + if (info.min >= info.max) { + return NS_ERROR_ILLEGAL_VALUE; + } + + if (info.bucketCount <= 2) { + return NS_ERROR_ILLEGAL_VALUE; + } + + if (info.min < 1) { + return NS_ERROR_ILLEGAL_VALUE; + } + } + + return NS_OK; +} + +Histogram* internal_CreateHistogramInstance(HistogramID histogramId) { + const HistogramInfo& info = gHistogramInfos[histogramId]; + + if (NS_FAILED(internal_CheckHistogramArguments(info))) { + MOZ_ASSERT(false, "Failed histogram argument checks."); + return nullptr; + } + + const bool isExpired = IsExpiredVersion(info.expiration()); + + if (isExpired) { + if (!gExpiredHistogram) { + gExpiredHistogram = new Histogram(histogramId, info, /* expired */ true); + } + + return gExpiredHistogram; + } + + Histogram* wrapper = new Histogram(histogramId, info, /* expired */ false); + + return wrapper; +} + +base::Histogram* internal_CreateBaseHistogramInstance( + const HistogramInfo& passedInfo, int bucketsOffset) { + if (NS_FAILED(internal_CheckHistogramArguments(passedInfo))) { + MOZ_ASSERT(false, "Failed histogram argument checks."); + return nullptr; + } + + // We don't actually store data for expired histograms at all. + MOZ_ASSERT(!IsExpiredVersion(passedInfo.expiration())); + + HistogramInfo info = passedInfo; + const int* buckets = &gHistogramBucketLowerBounds[bucketsOffset]; + + base::Histogram::Flags flags = base::Histogram::kNoFlags; + base::Histogram* h = nullptr; + switch (info.histogramType) { + case nsITelemetry::HISTOGRAM_EXPONENTIAL: + h = base::Histogram::FactoryGet(info.min, info.max, info.bucketCount, + flags, buckets); + break; + case nsITelemetry::HISTOGRAM_LINEAR: + case nsITelemetry::HISTOGRAM_CATEGORICAL: + h = LinearHistogram::FactoryGet(info.min, info.max, info.bucketCount, + flags, buckets); + break; + case nsITelemetry::HISTOGRAM_BOOLEAN: + h = BooleanHistogram::FactoryGet(flags, buckets); + break; + case nsITelemetry::HISTOGRAM_FLAG: + h = FlagHistogram::FactoryGet(flags, buckets); + break; + case nsITelemetry::HISTOGRAM_COUNT: + h = CountHistogram::FactoryGet(flags, buckets); + break; + default: + MOZ_ASSERT(false, "Invalid histogram type"); + return nullptr; + } + + return h; +} + +nsresult internal_HistogramAdd(const StaticMutexAutoLock& aLock, + Histogram& histogram, const HistogramID id, + uint32_t value, ProcessID aProcessType) { + // Check if we are allowed to record the data. + bool canRecordDataset = + CanRecordDataset(gHistogramInfos[id].dataset, internal_CanRecordBase(), + internal_CanRecordExtended()); + // If `histogram` is a non-parent-process histogram, then recording-enabled + // has been checked in its owner process. + if (!canRecordDataset || + (aProcessType == ProcessID::Parent && !internal_IsRecordingEnabled(id))) { + return NS_OK; + } + + // Don't record if the current platform is not enabled + if (!CanRecordProduct(gHistogramInfos[id].products)) { + return NS_OK; + } + + if (&histogram != gExpiredHistogram && + GetCurrentProduct() == SupportedProduct::GeckoviewStreaming) { + const HistogramInfo& info = gHistogramInfos[id]; + GeckoViewStreamingTelemetry::HistogramAccumulate( + nsDependentCString(info.name()), + info.histogramType == nsITelemetry::HISTOGRAM_CATEGORICAL, value); + return NS_OK; + } + + // The internal representation of a base::Histogram's buckets uses `int`. + // Clamp large values of `value` to be INT_MAX so they continue to be treated + // as large values (instead of negative ones). + if (value > INT_MAX) { + TelemetryScalar::Add( + mozilla::Telemetry::ScalarID::TELEMETRY_ACCUMULATE_CLAMPED_VALUES, + NS_ConvertASCIItoUTF16(gHistogramInfos[id].name()), 1); + value = INT_MAX; + } + + histogram.Add(value); + + return NS_OK; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: Histogram reflection helpers + +namespace { + +/** + * Copy histograms and samples to Mozilla-friendly structures. + * Please note that this version does not make use of JS contexts. + * + * @param {StaticMutexAutoLock} the proof we hold the mutex. + * @param {Histogram} the histogram to reflect. + * @return {nsresult} NS_ERROR_FAILURE if we fail to allocate memory for the + * snapshot. + */ +nsresult internal_GetHistogramAndSamples(const StaticMutexAutoLock& aLock, + const base::Histogram* h, + HistogramSnapshotData& aSnapshot) { + MOZ_ASSERT(h); + + // Convert the ranges of the buckets to a nsTArray. + const size_t bucketCount = h->bucket_count(); + for (size_t i = 0; i < bucketCount; i++) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier, or change the return type to void. + aSnapshot.mBucketRanges.AppendElement(h->ranges(i)); + } + + // Get a snapshot of the samples. + base::Histogram::SampleSet ss = h->SnapshotSample(); + + // Get the number of samples in each bucket. + for (size_t i = 0; i < bucketCount; i++) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier, or change the return type to void. + aSnapshot.mBucketCounts.AppendElement(ss.counts(i)); + } + + // Finally, save the |sum|. We don't need to reflect declared_min, + // declared_max and histogram_type as they are in gHistogramInfo. + aSnapshot.mSampleSum = ss.sum(); + return NS_OK; +} + +/** + * Reflect a histogram snapshot into a JavaScript object. + * The returned histogram object will have the following properties: + * + * bucket_count - Number of buckets of this histogram + * histogram_type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR, + * HISTOGRAM_BOOLEAN, HISTOGRAM_FLAG, HISTOGRAM_COUNT, or HISTOGRAM_CATEGORICAL + * sum - sum of the bucket contents + * range - A 2-item array of minimum and maximum bucket size + * values - Map from bucket start to the bucket's count + */ +nsresult internal_ReflectHistogramAndSamples( + JSContext* cx, JS::Handle<JSObject*> obj, + const HistogramInfo& aHistogramInfo, + const HistogramSnapshotData& aSnapshot) { + if (!(JS_DefineProperty(cx, obj, "bucket_count", aHistogramInfo.bucketCount, + JSPROP_ENUMERATE) && + JS_DefineProperty(cx, obj, "histogram_type", + aHistogramInfo.histogramType, JSPROP_ENUMERATE) && + JS_DefineProperty(cx, obj, "sum", double(aSnapshot.mSampleSum), + JSPROP_ENUMERATE))) { + return NS_ERROR_FAILURE; + } + + // Don't rely on the bucket counts from "aHistogramInfo": it may + // differ from the length of aSnapshot.mBucketCounts due to expired + // histograms. + const size_t count = aSnapshot.mBucketCounts.Length(); + MOZ_ASSERT(count == aSnapshot.mBucketRanges.Length(), + "The number of buckets and the number of counts must match."); + + // Create the "range" property and add it to the final object. + JS::Rooted<JSObject*> rarray(cx, JS::NewArrayObject(cx, 2)); + if (rarray == nullptr || + !JS_DefineProperty(cx, obj, "range", rarray, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + // Add [min, max] into the range array + if (!JS_DefineElement(cx, rarray, 0, aHistogramInfo.min, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + if (!JS_DefineElement(cx, rarray, 1, aHistogramInfo.max, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + + JS::Rooted<JSObject*> values(cx, JS_NewPlainObject(cx)); + if (values == nullptr || + !JS_DefineProperty(cx, obj, "values", values, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + + bool first = true; + size_t last = 0; + + for (size_t i = 0; i < count; i++) { + auto value = aSnapshot.mBucketCounts[i]; + if (value == 0) { + continue; + } + + if (i > 0 && first) { + auto range = aSnapshot.mBucketRanges[i - 1]; + if (!JS_DefineProperty(cx, values, nsPrintfCString("%d", range).get(), 0, + JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + + first = false; + last = i + 1; + + auto range = aSnapshot.mBucketRanges[i]; + if (!JS_DefineProperty(cx, values, nsPrintfCString("%d", range).get(), + value, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + + if (last > 0 && last < count) { + auto range = aSnapshot.mBucketRanges[last]; + if (!JS_DefineProperty(cx, values, nsPrintfCString("%d", range).get(), 0, + JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +bool internal_ShouldReflectHistogram(const StaticMutexAutoLock& aLock, + base::Histogram* h, HistogramID id) { + // Only flag histograms are serialized when they are empty. + // This has historical reasons, changing this will require downstream changes. + // The cheaper path here is to just deprecate flag histograms in favor + // of scalars. + uint32_t type = gHistogramInfos[id].histogramType; + if (internal_IsEmpty(aLock, h) && type != nsITelemetry::HISTOGRAM_FLAG) { + return false; + } + + // Don't reflect the histogram if it's not allowed in this product. + if (!CanRecordProduct(gHistogramInfos[id].products)) { + return false; + } + + return true; +} + +/** + * Helper function to get a snapshot of the histograms. + * + * @param {aLock} the lock proof. + * @param {aStore} the name of the store to snapshot. + * @param {aDataset} the dataset for which the snapshot is being requested. + * @param {aClearSubsession} whether or not to clear the data after + * taking the snapshot. + * @param {aIncludeGPU} whether or not to include data for the GPU. + * @param {aOutSnapshot} the container in which the snapshot data will be + * stored. + * @return {nsresult} NS_OK if the snapshot was successfully taken or + * NS_ERROR_OUT_OF_MEMORY if it failed to allocate memory. + */ +nsresult internal_GetHistogramsSnapshot( + const StaticMutexAutoLock& aLock, const nsACString& aStore, + unsigned int aDataset, bool aClearSubsession, bool aIncludeGPU, + bool aFilterTest, HistogramProcessSnapshotsArray& aOutSnapshot) { + if (!aOutSnapshot.resize(static_cast<uint32_t>(ProcessID::Count))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); + ++process) { + HistogramSnapshotsArray& hArray = aOutSnapshot[process]; + + for (size_t i = 0; i < HistogramCount; ++i) { + const HistogramInfo& info = gHistogramInfos[i]; + if (info.keyed) { + continue; + } + + HistogramID id = HistogramID(i); + + if (!CanRecordInProcess(info.record_in_processes, ProcessID(process)) || + ((ProcessID(process) == ProcessID::Gpu) && !aIncludeGPU)) { + continue; + } + + if (!IsInDataset(info.dataset, aDataset)) { + continue; + } + + bool shouldInstantiate = + info.histogramType == nsITelemetry::HISTOGRAM_FLAG; + Histogram* w = internal_GetHistogramById(aLock, id, ProcessID(process), + shouldInstantiate); + if (!w || w->IsExpired()) { + continue; + } + + base::Histogram* h = nullptr; + if (!w->GetHistogram(aStore, &h)) { + continue; + } + + if (!internal_ShouldReflectHistogram(aLock, h, id)) { + continue; + } + + const char* name = info.name(); + if (aFilterTest && strncmp(TEST_HISTOGRAM_PREFIX, name, + strlen(TEST_HISTOGRAM_PREFIX)) == 0) { + if (aClearSubsession) { + h->Clear(); + } + continue; + } + + HistogramSnapshotData snapshotData; + if (NS_FAILED(internal_GetHistogramAndSamples(aLock, h, snapshotData))) { + continue; + } + + if (!hArray.emplaceBack(HistogramSnapshotInfo{snapshotData, id})) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (aClearSubsession) { + h->Clear(); + } + } + } + return NS_OK; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: class Histogram + +namespace { + +Histogram::Histogram(HistogramID histogramId, const HistogramInfo& info, + bool expired) + : mStorage(), mSingleStore(nullptr), mIsExpired(expired) { + if (IsExpired()) { + return; + } + + const int bucketsOffset = gHistogramBucketLowerBoundIndex[histogramId]; + + if (info.is_single_store()) { + mSingleStore = internal_CreateBaseHistogramInstance(info, bucketsOffset); + } else { + for (uint32_t i = 0; i < info.store_count; i++) { + auto store = nsDependentCString( + &gHistogramStringTable[gHistogramStoresTable[info.store_index + i]]); + mStorage.InsertOrUpdate(store, UniquePtr<base::Histogram>( + internal_CreateBaseHistogramInstance( + info, bucketsOffset))); + } + } +} + +Histogram::~Histogram() { delete mSingleStore; } + +void Histogram::Add(uint32_t sample) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Only add to histograms in the parent process"); + if (!XRE_IsParentProcess()) { + return; + } + + if (IsExpired()) { + return; + } + + if (mSingleStore != nullptr) { + mSingleStore->Add(sample); + } else { + for (auto iter = mStorage.Iter(); !iter.Done(); iter.Next()) { + auto& h = iter.Data(); + h->Add(sample); + } + } +} + +void Histogram::Clear(const nsACString& store) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Only clear histograms in the parent process"); + if (!XRE_IsParentProcess()) { + return; + } + + if (mSingleStore != nullptr) { + if (store.EqualsASCII("main")) { + mSingleStore->Clear(); + } + } else { + base::Histogram* h = nullptr; + bool found = GetHistogram(store, &h); + if (!found) { + return; + } + MOZ_ASSERT(h, "Should have found a valid histogram in the named store"); + + h->Clear(); + } +} + +bool Histogram::GetHistogram(const nsACString& store, base::Histogram** h) { + MOZ_ASSERT(!IsExpired()); + if (IsExpired()) { + return false; + } + + if (mSingleStore != nullptr) { + if (store.EqualsASCII("main")) { + *h = mSingleStore; + return true; + } + + return false; + } + + return mStorage.Get(store, h); +} + +size_t Histogram::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + size_t n = 0; + n += aMallocSizeOf(this); + /* + * In theory mStorage.SizeOfExcludingThis should included the data part of the + * map, but the numbers seemed low, so we are only taking the shallow size and + * do the iteration here. + */ + n += mStorage.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& h : mStorage.Values()) { + n += h->SizeOfIncludingThis(aMallocSizeOf); + } + if (mSingleStore != nullptr) { + // base::Histogram doesn't have SizeOfExcludingThis, so we are overcounting + // the pointer here. + n += mSingleStore->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: class KeyedHistogram and internal_ReflectKeyedHistogram + +namespace { + +nsresult internal_ReflectKeyedHistogram( + const KeyedHistogramSnapshotData& aSnapshot, const HistogramInfo& info, + JSContext* aCx, JS::Handle<JSObject*> aObj) { + for (const auto& entry : aSnapshot) { + const HistogramSnapshotData& keyData = entry.GetData(); + + JS::Rooted<JSObject*> histogramSnapshot(aCx, JS_NewPlainObject(aCx)); + if (!histogramSnapshot) { + return NS_ERROR_FAILURE; + } + + if (NS_FAILED(internal_ReflectHistogramAndSamples(aCx, histogramSnapshot, + info, keyData))) { + return NS_ERROR_FAILURE; + } + + const NS_ConvertUTF8toUTF16 key(entry.GetKey()); + if (!JS_DefineUCProperty(aCx, aObj, key.Data(), key.Length(), + histogramSnapshot, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +KeyedHistogram::KeyedHistogram(HistogramID id, const HistogramInfo& info, + bool expired) + : mStorage(), + mSingleStore(nullptr), + mId(id), + mHistogramInfo(info), + mIsExpired(expired) { + if (IsExpired()) { + return; + } + + if (info.is_single_store()) { + mSingleStore = new KeyedHistogramMapType; + } else { + for (uint32_t i = 0; i < info.store_count; i++) { + auto store = nsDependentCString( + &gHistogramStringTable[gHistogramStoresTable[info.store_index + i]]); + mStorage.InsertOrUpdate(store, MakeUnique<KeyedHistogramMapType>()); + } + } +} + +KeyedHistogram::~KeyedHistogram() { delete mSingleStore; } + +nsresult KeyedHistogram::GetHistogram(const nsCString& aStore, + const nsCString& key, + base::Histogram** histogram) { + if (IsExpired()) { + MOZ_ASSERT(false, + "KeyedHistogram::GetHistogram called on an expired histogram."); + return NS_ERROR_FAILURE; + } + + KeyedHistogramMapType* histogramMap; + bool found; + + if (mSingleStore != nullptr) { + histogramMap = mSingleStore; + } else { + found = mStorage.Get(aStore, &histogramMap); + if (!found) { + return NS_ERROR_FAILURE; + } + } + + found = histogramMap->Get(key, histogram); + if (found) { + return NS_OK; + } + + int bucketsOffset = gHistogramBucketLowerBoundIndex[mId]; + auto h = UniquePtr<base::Histogram>{ + internal_CreateBaseHistogramInstance(mHistogramInfo, bucketsOffset)}; + if (!h) { + return NS_ERROR_FAILURE; + } + + h->ClearFlags(base::Histogram::kUmaTargetedHistogramFlag); + *histogram = h.get(); + + bool inserted = + histogramMap->InsertOrUpdate(key, std::move(h), mozilla::fallible); + if (MOZ_UNLIKELY(!inserted)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +base::Histogram* KeyedHistogram::GetHistogram(const nsCString& aStore, + const nsCString& key) { + base::Histogram* h = nullptr; + if (NS_FAILED(GetHistogram(aStore, key, &h))) { + return nullptr; + } + return h; +} + +nsresult KeyedHistogram::Add(const nsCString& key, uint32_t sample, + ProcessID aProcessType) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Only add to keyed histograms in the parent process"); + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + bool canRecordDataset = + CanRecordDataset(mHistogramInfo.dataset, internal_CanRecordBase(), + internal_CanRecordExtended()); + // If `histogram` is a non-parent-process histogram, then recording-enabled + // has been checked in its owner process. + if (!canRecordDataset || (aProcessType == ProcessID::Parent && + !internal_IsRecordingEnabled(mId))) { + return NS_OK; + } + + // Don't record if expired. + if (IsExpired()) { + return NS_OK; + } + + // Don't record if the current platform is not enabled + if (!CanRecordProduct(gHistogramInfos[mId].products)) { + return NS_OK; + } + + // The internal representation of a base::Histogram's buckets uses `int`. + // Clamp large values of `sample` to be INT_MAX so they continue to be treated + // as large values (instead of negative ones). + if (sample > INT_MAX) { + TelemetryScalar::Add( + mozilla::Telemetry::ScalarID::TELEMETRY_ACCUMULATE_CLAMPED_VALUES, + NS_ConvertASCIItoUTF16(mHistogramInfo.name()), 1); + sample = INT_MAX; + } + + base::Histogram* histogram; + if (mSingleStore != nullptr) { + histogram = GetHistogram("main"_ns, key); + if (!histogram) { + MOZ_ASSERT(false, "Missing histogram in single store."); + return NS_ERROR_FAILURE; + } + + histogram->Add(sample); + } else { + for (uint32_t i = 0; i < mHistogramInfo.store_count; i++) { + auto store = nsDependentCString( + &gHistogramStringTable + [gHistogramStoresTable[mHistogramInfo.store_index + i]]); + base::Histogram* histogram = GetHistogram(store, key); + MOZ_ASSERT(histogram); + if (histogram) { + histogram->Add(sample); + } else { + return NS_ERROR_FAILURE; + } + } + } + + return NS_OK; +} + +void KeyedHistogram::Clear(const nsACString& aStore) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Only clear keyed histograms in the parent process"); + if (!XRE_IsParentProcess()) { + return; + } + + if (IsExpired()) { + return; + } + + if (mSingleStore) { + if (aStore.EqualsASCII("main")) { + mSingleStore->Clear(); + } + return; + } + + KeyedHistogramMapType* histogramMap; + bool found = mStorage.Get(aStore, &histogramMap); + if (!found) { + return; + } + + histogramMap->Clear(); +} + +bool KeyedHistogram::IsEmpty(const nsACString& aStore) const { + if (mSingleStore != nullptr) { + if (aStore.EqualsASCII("main")) { + return mSingleStore->IsEmpty(); + } + + return true; + } + + KeyedHistogramMapType* histogramMap; + bool found = mStorage.Get(aStore, &histogramMap); + if (!found) { + return true; + } + return histogramMap->IsEmpty(); +} + +size_t KeyedHistogram::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + size_t n = 0; + n += aMallocSizeOf(this); + /* + * In theory mStorage.SizeOfExcludingThis should included the data part of the + * map, but the numbers seemed low, so we are only taking the shallow size and + * do the iteration here. + */ + n += mStorage.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& h : mStorage.Values()) { + n += h->SizeOfIncludingThis(aMallocSizeOf); + } + if (mSingleStore != nullptr) { + // base::Histogram doesn't have SizeOfExcludingThis, so we are overcounting + // the pointer here. + n += mSingleStore->SizeOfExcludingThis(aMallocSizeOf); + } + return n; +} + +nsresult KeyedHistogram::GetKeys(const StaticMutexAutoLock& aLock, + const nsCString& store, + nsTArray<nsCString>& aKeys) { + KeyedHistogramMapType* histogramMap; + if (mSingleStore != nullptr) { + histogramMap = mSingleStore; + } else { + bool found = mStorage.Get(store, &histogramMap); + if (!found) { + return NS_ERROR_FAILURE; + } + } + + if (!aKeys.SetCapacity(histogramMap->Count(), mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (const auto& key : histogramMap->Keys()) { + if (!aKeys.AppendElement(key, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return NS_OK; +} + +nsresult KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj, + const nsACString& aStore, + bool clearSubsession) { + // Get a snapshot of the data. + KeyedHistogramSnapshotData dataSnapshot; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + MOZ_ASSERT(internal_IsHistogramEnumId(mId)); + + // Take a snapshot of the data here, protected by the lock, and then, + // outside of the lock protection, mirror it to a JS structure. + nsresult rv = GetSnapshot(locker, aStore, dataSnapshot, clearSubsession); + if (NS_FAILED(rv)) { + return rv; + } + } + + // Now that we have a copy of the data, mirror it to JS. + return internal_ReflectKeyedHistogram(dataSnapshot, gHistogramInfos[mId], cx, + obj); +} + +/** + * Return a histogram snapshot for the named store. + * + * If getting the snapshot succeeds, NS_OK is returned and `aSnapshot` contains + * the snapshot data. If the histogram is not available in the named store, + * NS_ERROR_NO_CONTENT is returned. For other errors, NS_ERROR_FAILURE is + * returned. + */ +nsresult KeyedHistogram::GetSnapshot(const StaticMutexAutoLock& aLock, + const nsACString& aStore, + KeyedHistogramSnapshotData& aSnapshot, + bool aClearSubsession) { + KeyedHistogramMapType* histogramMap; + if (mSingleStore != nullptr) { + if (!aStore.EqualsASCII("main")) { + return NS_ERROR_NO_CONTENT; + } + + histogramMap = mSingleStore; + } else { + bool found = mStorage.Get(aStore, &histogramMap); + if (!found) { + // Nothing in the main store is fine, it's just handled as empty + return NS_ERROR_NO_CONTENT; + } + } + + // Snapshot every key. + for (const auto& entry : *histogramMap) { + base::Histogram* keyData = entry.GetWeak(); + if (!keyData) { + return NS_ERROR_FAILURE; + } + + HistogramSnapshotData keySnapshot; + if (NS_FAILED( + internal_GetHistogramAndSamples(aLock, keyData, keySnapshot))) { + return NS_ERROR_FAILURE; + } + + // Append to the final snapshot. + aSnapshot.InsertOrUpdate(entry.GetKey(), std::move(keySnapshot)); + } + + if (aClearSubsession) { + Clear(aStore); + } + + return NS_OK; +} + +/** + * Helper function to get a snapshot of the keyed histograms. + * + * @param {aLock} the lock proof. + * @param {aDataset} the dataset for which the snapshot is being requested. + * @param {aClearSubsession} whether or not to clear the data after + * taking the snapshot. + * @param {aIncludeGPU} whether or not to include data for the GPU. + * @param {aOutSnapshot} the container in which the snapshot data will be + * stored. + * @return {nsresult} NS_OK if the snapshot was successfully taken or + * NS_ERROR_OUT_OF_MEMORY if it failed to allocate memory. + */ +nsresult internal_GetKeyedHistogramsSnapshot( + const StaticMutexAutoLock& aLock, const nsACString& aStore, + unsigned int aDataset, bool aClearSubsession, bool aIncludeGPU, + bool aFilterTest, KeyedHistogramProcessSnapshotsArray& aOutSnapshot) { + if (!aOutSnapshot.resize(static_cast<uint32_t>(ProcessID::Count))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); + ++process) { + KeyedHistogramSnapshotsArray& hArray = aOutSnapshot[process]; + + for (size_t i = 0; i < HistogramCount; ++i) { + HistogramID id = HistogramID(i); + const HistogramInfo& info = gHistogramInfos[id]; + if (!info.keyed) { + continue; + } + + if (!CanRecordInProcess(info.record_in_processes, ProcessID(process)) || + ((ProcessID(process) == ProcessID::Gpu) && !aIncludeGPU)) { + continue; + } + + if (!IsInDataset(info.dataset, aDataset)) { + continue; + } + + KeyedHistogram* keyed = + internal_GetKeyedHistogramById(id, ProcessID(process), + /* instantiate = */ false); + if (!keyed || keyed->IsEmpty(aStore) || keyed->IsExpired()) { + continue; + } + + const char* name = info.name(); + if (aFilterTest && strncmp(TEST_HISTOGRAM_PREFIX, name, + strlen(TEST_HISTOGRAM_PREFIX)) == 0) { + if (aClearSubsession) { + keyed->Clear(aStore); + } + continue; + } + + // Take a snapshot of the keyed histogram data! + KeyedHistogramSnapshotData snapshot; + if (!NS_SUCCEEDED( + keyed->GetSnapshot(aLock, aStore, snapshot, aClearSubsession))) { + return NS_ERROR_FAILURE; + } + + if (!hArray.emplaceBack( + KeyedHistogramSnapshotInfo{std::move(snapshot), id})) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + return NS_OK; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: thread-unsafe helpers for the external interface + +namespace { + +bool internal_RemoteAccumulate(const StaticMutexAutoLock& aLock, + HistogramID aId, uint32_t aSample) { + if (XRE_IsParentProcess()) { + return false; + } + + if (!internal_IsRecordingEnabled(aId)) { + return true; + } + + TelemetryIPCAccumulator::AccumulateChildHistogram(aId, aSample); + return true; +} + +bool internal_RemoteAccumulate(const StaticMutexAutoLock& aLock, + HistogramID aId, const nsCString& aKey, + uint32_t aSample) { + if (XRE_IsParentProcess()) { + return false; + } + + if (!internal_IsRecordingEnabled(aId)) { + return true; + } + + TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(aId, aKey, aSample); + return true; +} + +void internal_Accumulate(const StaticMutexAutoLock& aLock, HistogramID aId, + uint32_t aSample) { + if (!internal_CanRecordBase() || + internal_RemoteAccumulate(aLock, aId, aSample)) { + return; + } + + Histogram* w = internal_GetHistogramById(aLock, aId, ProcessID::Parent); + MOZ_ASSERT(w); + internal_HistogramAdd(aLock, *w, aId, aSample, ProcessID::Parent); +} + +void internal_Accumulate(const StaticMutexAutoLock& aLock, HistogramID aId, + const nsCString& aKey, uint32_t aSample) { + if (!gInitDone || !internal_CanRecordBase() || + internal_RemoteAccumulate(aLock, aId, aKey, aSample)) { + return; + } + + KeyedHistogram* keyed = + internal_GetKeyedHistogramById(aId, ProcessID::Parent); + MOZ_ASSERT(keyed); + keyed->Add(aKey, aSample, ProcessID::Parent); +} + +void internal_AccumulateChild(const StaticMutexAutoLock& aLock, + ProcessID aProcessType, HistogramID aId, + uint32_t aSample) { + if (!internal_CanRecordBase()) { + return; + } + + Histogram* w = internal_GetHistogramById(aLock, aId, aProcessType); + if (w == nullptr) { + NS_WARNING("Failed GetHistogramById for CHILD"); + } else { + internal_HistogramAdd(aLock, *w, aId, aSample, aProcessType); + } +} + +void internal_AccumulateChildKeyed(const StaticMutexAutoLock& aLock, + ProcessID aProcessType, HistogramID aId, + const nsCString& aKey, uint32_t aSample) { + if (!gInitDone || !internal_CanRecordBase()) { + return; + } + + KeyedHistogram* keyed = internal_GetKeyedHistogramById(aId, aProcessType); + MOZ_ASSERT(keyed); + keyed->Add(aKey, aSample, aProcessType); +} + +void internal_ClearHistogram(const StaticMutexAutoLock& aLock, HistogramID id, + const nsACString& aStore) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!XRE_IsParentProcess()) { + return; + } + + // Handle keyed histograms. + if (gHistogramInfos[id].keyed) { + for (uint32_t process = 0; + process < static_cast<uint32_t>(ProcessID::Count); ++process) { + KeyedHistogram* kh = internal_GetKeyedHistogramById( + id, static_cast<ProcessID>(process), /* instantiate = */ false); + if (kh) { + kh->Clear(aStore); + } + } + } else { + // Reset the histograms instances for all processes. + for (uint32_t process = 0; + process < static_cast<uint32_t>(ProcessID::Count); ++process) { + Histogram* h = + internal_GetHistogramById(aLock, id, static_cast<ProcessID>(process), + /* instantiate = */ false); + if (h) { + h->Clear(aStore); + } + } + } +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: JSHistogram_* functions + +// NOTE: the functions in this section: +// +// internal_JSHistogram_Add +// internal_JSHistogram_Name +// internal_JSHistogram_Snapshot +// internal_JSHistogram_Clear +// internal_WrapAndReturnHistogram +// +// all run without protection from |gTelemetryHistogramMutex|. If they +// held |gTelemetryHistogramMutex|, there would be the possibility of +// deadlock because the JS_ calls that they make may call back into the +// TelemetryHistogram interface, hence trying to re-acquire the mutex. +// +// This means that these functions potentially race against threads, but +// that seems preferable to risking deadlock. + +namespace { + +static constexpr uint32_t HistogramObjectDataSlot = 0; +static constexpr uint32_t HistogramObjectSlotCount = + HistogramObjectDataSlot + 1; + +void internal_JSHistogram_finalize(JS::GCContext*, JSObject*); + +static const JSClassOps sJSHistogramClassOps = {nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* enumerate */ + nullptr, /* newEnumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + internal_JSHistogram_finalize}; + +static const JSClass sJSHistogramClass = { + "JSHistogram", /* name */ + JSCLASS_HAS_RESERVED_SLOTS(HistogramObjectSlotCount) | + JSCLASS_FOREGROUND_FINALIZE, /* flags */ + &sJSHistogramClassOps}; + +struct JSHistogramData { + HistogramID histogramId; +}; + +bool internal_JSHistogram_CoerceValue(JSContext* aCx, + JS::Handle<JS::Value> aElement, + HistogramID aId, uint32_t aHistogramType, + uint32_t& aValue) { + if (aElement.isString()) { + // Strings only allowed for categorical histograms + if (aHistogramType != nsITelemetry::HISTOGRAM_CATEGORICAL) { + LogToBrowserConsole( + nsIScriptError::errorFlag, + nsLiteralString( + u"String argument only allowed for categorical histogram")); + return false; + } + + // Label is given by the string argument + nsAutoJSString label; + if (!label.init(aCx, aElement)) { + LogToBrowserConsole(nsIScriptError::errorFlag, + u"Invalid string parameter"_ns); + return false; + } + + // Get the label id for accumulation + nsresult rv = gHistogramInfos[aId].label_id( + NS_ConvertUTF16toUTF8(label).get(), &aValue); + if (NS_FAILED(rv)) { + nsPrintfCString msg("'%s' is an invalid string label", + NS_ConvertUTF16toUTF8(label).get()); + LogToBrowserConsole(nsIScriptError::errorFlag, + NS_ConvertUTF8toUTF16(msg)); + return false; + } + } else if (!(aElement.isNumber() || aElement.isBoolean())) { + LogToBrowserConsole(nsIScriptError::errorFlag, u"Argument not a number"_ns); + return false; + } else if (aElement.isNumber() && aElement.toNumber() > UINT32_MAX) { + // Clamp large numerical arguments to aValue's acceptable values. + // JS::ToUint32 will take aElement modulo 2^32 before returning it, which + // may result in a smaller final value. + aValue = UINT32_MAX; +#ifdef DEBUG + LogToBrowserConsole(nsIScriptError::errorFlag, + u"Clamped large numeric value"_ns); +#endif + } else if (!JS::ToUint32(aCx, aElement, &aValue)) { + LogToBrowserConsole(nsIScriptError::errorFlag, + u"Failed to convert element to UInt32"_ns); + return false; + } + + // If we're here then all type checks have passed and aValue contains the + // coerced integer + return true; +} + +bool internal_JSHistogram_GetValueArray(JSContext* aCx, JS::CallArgs& args, + uint32_t aHistogramType, + HistogramID aId, bool isKeyed, + nsTArray<uint32_t>& aArray) { + // This function populates aArray with the values extracted from args. Handles + // keyed and non-keyed histograms, and single and array of values. Also + // performs sanity checks on the arguments. Returns true upon successful + // population, false otherwise. + + uint32_t firstArgIndex = 0; + if (isKeyed) { + firstArgIndex = 1; + } + + // Special case of no argument (or only key) and count histogram + if (args.length() == firstArgIndex) { + if (!(aHistogramType == nsITelemetry::HISTOGRAM_COUNT)) { + LogToBrowserConsole( + nsIScriptError::errorFlag, + nsLiteralString( + u"Need at least one argument for non count type histogram")); + return false; + } + + aArray.AppendElement(1); + return true; + } + + if (args[firstArgIndex].isObject() && !args[firstArgIndex].isString()) { + JS::Rooted<JSObject*> arrayObj(aCx, &args[firstArgIndex].toObject()); + + bool isArray = false; + JS::IsArrayObject(aCx, arrayObj, &isArray); + + if (!isArray) { + LogToBrowserConsole( + nsIScriptError::errorFlag, + nsLiteralString( + u"The argument to accumulate can't be a non-array object")); + return false; + } + + uint32_t arrayLength = 0; + if (!JS::GetArrayLength(aCx, arrayObj, &arrayLength)) { + LogToBrowserConsole(nsIScriptError::errorFlag, + u"Failed while trying to get array length"_ns); + return false; + } + + for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; arrayIdx++) { + JS::Rooted<JS::Value> element(aCx); + + if (!JS_GetElement(aCx, arrayObj, arrayIdx, &element)) { + nsPrintfCString msg("Failed while trying to get element at index %d", + arrayIdx); + LogToBrowserConsole(nsIScriptError::errorFlag, + NS_ConvertUTF8toUTF16(msg)); + return false; + } + + uint32_t value = 0; + if (!internal_JSHistogram_CoerceValue(aCx, element, aId, aHistogramType, + value)) { + nsPrintfCString msg("Element at index %d failed type checks", arrayIdx); + LogToBrowserConsole(nsIScriptError::errorFlag, + NS_ConvertUTF8toUTF16(msg)); + return false; + } + aArray.AppendElement(value); + } + + return true; + } + + uint32_t value = 0; + if (!internal_JSHistogram_CoerceValue(aCx, args[firstArgIndex], aId, + aHistogramType, value)) { + return false; + } + aArray.AppendElement(value); + return true; +} + +static JSHistogramData* GetJSHistogramData(JSObject* obj) { + MOZ_ASSERT(JS::GetClass(obj) == &sJSHistogramClass); + return JS::GetMaybePtrFromReservedSlot<JSHistogramData>( + obj, HistogramObjectDataSlot); +} + +bool internal_JSHistogram_Add(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject() || + JS::GetClass(&args.thisv().toObject()) != &sJSHistogramClass) { + JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class"); + return false; + } + + JSObject* obj = &args.thisv().toObject(); + JSHistogramData* data = GetJSHistogramData(obj); + MOZ_ASSERT(data); + HistogramID id = data->histogramId; + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + uint32_t type = gHistogramInfos[id].histogramType; + + // This function should always return |undefined| and never fail but + // rather report failures using the console. + args.rval().setUndefined(); + + nsTArray<uint32_t> values; + if (!internal_JSHistogram_GetValueArray(cx, args, type, id, false, values)) { + // Either GetValueArray or CoerceValue utility function will have printed a + // meaningful error message, so we simply return true + return true; + } + + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + for (uint32_t aValue : values) { + internal_Accumulate(locker, id, aValue); + } + } + return true; +} + +bool internal_JSHistogram_Name(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject() || + JS::GetClass(&args.thisv().toObject()) != &sJSHistogramClass) { + JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class"); + return false; + } + + JSObject* obj = &args.thisv().toObject(); + JSHistogramData* data = GetJSHistogramData(obj); + MOZ_ASSERT(data); + HistogramID id = data->histogramId; + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + const char* name = gHistogramInfos[id].name(); + + auto cname = NS_ConvertASCIItoUTF16(name); + args.rval().setString(ToJSString(cx, cname)); + + return true; +} + +/** + * Extract the store name from JavaScript function arguments. + * The first and only argument needs to be an object with a "store" property. + * If no arguments are given it defaults to "main". + */ +nsresult internal_JS_StoreFromObjectArgument(JSContext* cx, + const JS::CallArgs& args, + nsAutoString& aStoreName) { + if (args.length() == 0) { + aStoreName.AssignLiteral("main"); + } else if (args.length() == 1) { + if (!args[0].isObject()) { + JS_ReportErrorASCII(cx, "Expected object argument."); + return NS_ERROR_FAILURE; + } + + JS::Rooted<JS::Value> storeValue(cx); + JS::Rooted<JSObject*> argsObject(cx, &args[0].toObject()); + if (!JS_GetProperty(cx, argsObject, "store", &storeValue)) { + JS_ReportErrorASCII(cx, + "Expected object argument to have property 'store'."); + return NS_ERROR_FAILURE; + } + + nsAutoJSString store; + if (!storeValue.isString() || !store.init(cx, storeValue)) { + JS_ReportErrorASCII( + cx, "Expected object argument's 'store' property to be a string."); + return NS_ERROR_FAILURE; + } + + aStoreName.Assign(store); + } else { + JS_ReportErrorASCII(cx, "Expected at most one argument."); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool internal_JSHistogram_Snapshot(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!XRE_IsParentProcess()) { + JS_ReportErrorASCII( + cx, "Histograms can only be snapshotted in the parent process"); + return false; + } + + if (!args.thisv().isObject() || + JS::GetClass(&args.thisv().toObject()) != &sJSHistogramClass) { + JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class"); + return false; + } + + JSObject* obj = &args.thisv().toObject(); + JSHistogramData* data = GetJSHistogramData(obj); + MOZ_ASSERT(data); + HistogramID id = data->histogramId; + + nsAutoString storeName; + nsresult rv = internal_JS_StoreFromObjectArgument(cx, args, storeName); + if (NS_FAILED(rv)) { + return false; + } + + HistogramSnapshotData dataSnapshot; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + + // This is not good standard behavior given that we have histogram instances + // covering multiple processes. + // However, changing this requires some broader changes to callers. + Histogram* w = internal_GetHistogramById(locker, id, ProcessID::Parent); + base::Histogram* h = nullptr; + if (!w->GetHistogram(NS_ConvertUTF16toUTF8(storeName), &h)) { + // When it's not in the named store, let's skip the snapshot completely, + // but don't fail + args.rval().setUndefined(); + return true; + } + // Take a snapshot of the data here, protected by the lock, and then, + // outside of the lock protection, mirror it to a JS structure + if (NS_FAILED(internal_GetHistogramAndSamples(locker, h, dataSnapshot))) { + return false; + } + } + + JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx)); + if (!snapshot) { + return false; + } + + if (NS_FAILED(internal_ReflectHistogramAndSamples( + cx, snapshot, gHistogramInfos[id], dataSnapshot))) { + return false; + } + + args.rval().setObject(*snapshot); + return true; +} + +bool internal_JSHistogram_Clear(JSContext* cx, unsigned argc, JS::Value* vp) { + if (!XRE_IsParentProcess()) { + JS_ReportErrorASCII(cx, + "Histograms can only be cleared in the parent process"); + return false; + } + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject() || + JS::GetClass(&args.thisv().toObject()) != &sJSHistogramClass) { + JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class"); + return false; + } + + JSObject* obj = &args.thisv().toObject(); + JSHistogramData* data = GetJSHistogramData(obj); + MOZ_ASSERT(data); + + nsAutoString storeName; + nsresult rv = internal_JS_StoreFromObjectArgument(cx, args, storeName); + if (NS_FAILED(rv)) { + return false; + } + + // This function should always return |undefined| and never fail but + // rather report failures using the console. + args.rval().setUndefined(); + + HistogramID id = data->histogramId; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + internal_ClearHistogram(locker, id, NS_ConvertUTF16toUTF8(storeName)); + } + + return true; +} + +// NOTE: Runs without protection from |gTelemetryHistogramMutex|. +// See comment at the top of this section. +nsresult internal_WrapAndReturnHistogram(HistogramID id, JSContext* cx, + JS::MutableHandle<JS::Value> ret) { + JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &sJSHistogramClass)); + if (!obj) { + return NS_ERROR_FAILURE; + } + + // The 3 functions that are wrapped up here are eventually called + // by the same thread that runs this function. + if (!(JS_DefineFunction(cx, obj, "add", internal_JSHistogram_Add, 1, 0) && + JS_DefineFunction(cx, obj, "name", internal_JSHistogram_Name, 1, 0) && + JS_DefineFunction(cx, obj, "snapshot", internal_JSHistogram_Snapshot, 1, + 0) && + JS_DefineFunction(cx, obj, "clear", internal_JSHistogram_Clear, 1, + 0))) { + return NS_ERROR_FAILURE; + } + + JSHistogramData* data = new JSHistogramData{id}; + JS::SetReservedSlot(obj, HistogramObjectDataSlot, JS::PrivateValue(data)); + ret.setObject(*obj); + + return NS_OK; +} + +void internal_JSHistogram_finalize(JS::GCContext* gcx, JSObject* obj) { + if (!obj || JS::GetClass(obj) != &sJSHistogramClass) { + MOZ_ASSERT_UNREACHABLE("Should have the right JS class."); + return; + } + + JSHistogramData* data = GetJSHistogramData(obj); + MOZ_ASSERT(data); + delete data; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: JSKeyedHistogram_* functions + +// NOTE: the functions in this section: +// +// internal_JSKeyedHistogram_Add +// internal_JSKeyedHistogram_Name +// internal_JSKeyedHistogram_Keys +// internal_JSKeyedHistogram_Snapshot +// internal_JSKeyedHistogram_Clear +// internal_WrapAndReturnKeyedHistogram +// +// Same comments as above, at the JSHistogram_* section, regarding +// deadlock avoidance, apply. + +namespace { + +void internal_JSKeyedHistogram_finalize(JS::GCContext*, JSObject*); + +static const JSClassOps sJSKeyedHistogramClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* enumerate */ + nullptr, /* newEnumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + internal_JSKeyedHistogram_finalize}; + +static const JSClass sJSKeyedHistogramClass = { + "JSKeyedHistogram", /* name */ + JSCLASS_HAS_RESERVED_SLOTS(HistogramObjectSlotCount) | + JSCLASS_FOREGROUND_FINALIZE, /* flags */ + &sJSKeyedHistogramClassOps}; + +static JSHistogramData* GetJSKeyedHistogramData(JSObject* obj) { + MOZ_ASSERT(JS::GetClass(obj) == &sJSKeyedHistogramClass); + return JS::GetMaybePtrFromReservedSlot<JSHistogramData>( + obj, HistogramObjectDataSlot); +} + +bool internal_JSKeyedHistogram_Snapshot(JSContext* cx, unsigned argc, + JS::Value* vp) { + if (!XRE_IsParentProcess()) { + JS_ReportErrorASCII( + cx, "Keyed histograms can only be snapshotted in the parent process"); + return false; + } + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject() || + JS::GetClass(&args.thisv().toObject()) != &sJSKeyedHistogramClass) { + JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class"); + return false; + } + + JSObject* obj = &args.thisv().toObject(); + JSHistogramData* data = GetJSKeyedHistogramData(obj); + MOZ_ASSERT(data); + HistogramID id = data->histogramId; + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + + // This function should always return |undefined| and never fail but + // rather report failures using the console. + args.rval().setUndefined(); + + // This is not good standard behavior given that we have histogram instances + // covering multiple processes. + // However, changing this requires some broader changes to callers. + KeyedHistogram* keyed = internal_GetKeyedHistogramById( + id, ProcessID::Parent, /* instantiate = */ true); + if (!keyed) { + JS_ReportErrorASCII(cx, "Failed to look up keyed histogram"); + return false; + } + + nsAutoString storeName; + nsresult rv; + rv = internal_JS_StoreFromObjectArgument(cx, args, storeName); + if (NS_FAILED(rv)) { + return false; + } + + JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx)); + if (!snapshot) { + JS_ReportErrorASCII(cx, "Failed to create object"); + return false; + } + + rv = keyed->GetJSSnapshot(cx, snapshot, NS_ConvertUTF16toUTF8(storeName), + false); + + // If the store is not available, we return nothing and don't fail + if (rv == NS_ERROR_NO_CONTENT) { + args.rval().setUndefined(); + return true; + } + + if (!NS_SUCCEEDED(rv)) { + JS_ReportErrorASCII(cx, "Failed to reflect keyed histograms"); + return false; + } + + args.rval().setObject(*snapshot); + return true; +} + +bool internal_JSKeyedHistogram_Add(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject() || + JS::GetClass(&args.thisv().toObject()) != &sJSKeyedHistogramClass) { + JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class"); + return false; + } + + JSObject* obj = &args.thisv().toObject(); + JSHistogramData* data = GetJSKeyedHistogramData(obj); + MOZ_ASSERT(data); + HistogramID id = data->histogramId; + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + + // This function should always return |undefined| and never fail but + // rather report failures using the console. + args.rval().setUndefined(); + if (args.length() < 1) { + LogToBrowserConsole(nsIScriptError::errorFlag, u"Expected one argument"_ns); + return true; + } + + nsAutoJSString key; + if (!args[0].isString() || !key.init(cx, args[0])) { + LogToBrowserConsole(nsIScriptError::errorFlag, u"Not a string"_ns); + return true; + } + + // Check if we're allowed to record in the provided key, for this histogram. + if (!gHistogramInfos[id].allows_key(NS_ConvertUTF16toUTF8(key))) { + nsPrintfCString msg("%s - key '%s' not allowed for this keyed histogram", + gHistogramInfos[id].name(), + NS_ConvertUTF16toUTF8(key).get()); + LogToBrowserConsole(nsIScriptError::errorFlag, NS_ConvertUTF8toUTF16(msg)); + TelemetryScalar::Add(mozilla::Telemetry::ScalarID:: + TELEMETRY_ACCUMULATE_UNKNOWN_HISTOGRAM_KEYS, + NS_ConvertASCIItoUTF16(gHistogramInfos[id].name()), 1); + return true; + } + + const uint32_t type = gHistogramInfos[id].histogramType; + + nsTArray<uint32_t> values; + if (!internal_JSHistogram_GetValueArray(cx, args, type, id, true, values)) { + // Either GetValueArray or CoerceValue utility function will have printed a + // meaningful error message so we simple return true + return true; + } + + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + for (uint32_t aValue : values) { + internal_Accumulate(locker, id, NS_ConvertUTF16toUTF8(key), aValue); + } + } + return true; +} + +bool internal_JSKeyedHistogram_Name(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject() || + JS::GetClass(&args.thisv().toObject()) != &sJSKeyedHistogramClass) { + JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class"); + return false; + } + + JSObject* obj = &args.thisv().toObject(); + JSHistogramData* data = GetJSKeyedHistogramData(obj); + MOZ_ASSERT(data); + HistogramID id = data->histogramId; + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + const char* name = gHistogramInfos[id].name(); + + auto cname = NS_ConvertASCIItoUTF16(name); + args.rval().setString(ToJSString(cx, cname)); + + return true; +} + +bool internal_JSKeyedHistogram_Keys(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject() || + JS::GetClass(&args.thisv().toObject()) != &sJSKeyedHistogramClass) { + JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class"); + return false; + } + + JSObject* obj = &args.thisv().toObject(); + JSHistogramData* data = GetJSKeyedHistogramData(obj); + MOZ_ASSERT(data); + HistogramID id = data->histogramId; + + nsAutoString storeName; + nsresult rv = internal_JS_StoreFromObjectArgument(cx, args, storeName); + if (NS_FAILED(rv)) { + return false; + } + + nsTArray<nsCString> keys; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + + // This is not good standard behavior given that we have histogram instances + // covering multiple processes. + // However, changing this requires some broader changes to callers. + KeyedHistogram* keyed = + internal_GetKeyedHistogramById(id, ProcessID::Parent); + + MOZ_ASSERT(keyed); + if (!keyed) { + return false; + } + + if (NS_FAILED( + keyed->GetKeys(locker, NS_ConvertUTF16toUTF8(storeName), keys))) { + return false; + } + } + + // Convert keys from nsTArray<nsCString> to JS array. + JS::RootedVector<JS::Value> autoKeys(cx); + if (!autoKeys.reserve(keys.Length())) { + return false; + } + + for (const auto& key : keys) { + JS::Rooted<JS::Value> jsKey(cx); + jsKey.setString(ToJSString(cx, key)); + if (!autoKeys.append(jsKey)) { + return false; + } + } + + JS::Rooted<JSObject*> jsKeys(cx, JS::NewArrayObject(cx, autoKeys)); + if (!jsKeys) { + return false; + } + + args.rval().setObject(*jsKeys); + return true; +} + +bool internal_JSKeyedHistogram_Clear(JSContext* cx, unsigned argc, + JS::Value* vp) { + if (!XRE_IsParentProcess()) { + JS_ReportErrorASCII( + cx, "Keyed histograms can only be cleared in the parent process"); + return false; + } + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject() || + JS::GetClass(&args.thisv().toObject()) != &sJSKeyedHistogramClass) { + JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class"); + return false; + } + + JSObject* obj = &args.thisv().toObject(); + JSHistogramData* data = GetJSKeyedHistogramData(obj); + MOZ_ASSERT(data); + HistogramID id = data->histogramId; + + // This function should always return |undefined| and never fail but + // rather report failures using the console. + args.rval().setUndefined(); + + nsAutoString storeName; + nsresult rv = internal_JS_StoreFromObjectArgument(cx, args, storeName); + if (NS_FAILED(rv)) { + return false; + } + + KeyedHistogram* keyed = nullptr; + { + MOZ_ASSERT(internal_IsHistogramEnumId(id)); + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + + // This is not good standard behavior given that we have histogram instances + // covering multiple processes. + // However, changing this requires some broader changes to callers. + keyed = internal_GetKeyedHistogramById(id, ProcessID::Parent, + /* instantiate = */ false); + + if (!keyed) { + return true; + } + + keyed->Clear(NS_ConvertUTF16toUTF8(storeName)); + } + + return true; +} + +// NOTE: Runs without protection from |gTelemetryHistogramMutex|. +// See comment at the top of this section. +nsresult internal_WrapAndReturnKeyedHistogram( + HistogramID id, JSContext* cx, JS::MutableHandle<JS::Value> ret) { + JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &sJSKeyedHistogramClass)); + if (!obj) return NS_ERROR_FAILURE; + // The 6 functions that are wrapped up here are eventually called + // by the same thread that runs this function. + if (!(JS_DefineFunction(cx, obj, "add", internal_JSKeyedHistogram_Add, 2, + 0) && + JS_DefineFunction(cx, obj, "name", internal_JSKeyedHistogram_Name, 1, + 0) && + JS_DefineFunction(cx, obj, "snapshot", + internal_JSKeyedHistogram_Snapshot, 1, 0) && + JS_DefineFunction(cx, obj, "keys", internal_JSKeyedHistogram_Keys, 1, + 0) && + JS_DefineFunction(cx, obj, "clear", internal_JSKeyedHistogram_Clear, 1, + 0))) { + return NS_ERROR_FAILURE; + } + + JSHistogramData* data = new JSHistogramData{id}; + JS::SetReservedSlot(obj, HistogramObjectDataSlot, JS::PrivateValue(data)); + ret.setObject(*obj); + + return NS_OK; +} + +void internal_JSKeyedHistogram_finalize(JS::GCContext* gcx, JSObject* obj) { + if (!obj || JS::GetClass(obj) != &sJSKeyedHistogramClass) { + MOZ_ASSERT_UNREACHABLE("Should have the right JS class."); + return; + } + + JSHistogramData* data = GetJSKeyedHistogramData(obj); + MOZ_ASSERT(data); + delete data; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryHistogram:: + +// All of these functions are actually in namespace TelemetryHistogram::, +// but the ::TelemetryHistogram prefix is given explicitly. This is +// because it is critical to see which calls from these functions are +// to another function in this interface. Mis-identifying "inwards +// calls" from "calls to another function in this interface" will lead +// to deadlocking and/or races. See comments at the top of the file +// for further (important!) details. + +void TelemetryHistogram::InitializeGlobalState(bool canRecordBase, + bool canRecordExtended) { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + MOZ_ASSERT(!gInitDone, + "TelemetryHistogram::InitializeGlobalState " + "may only be called once"); + + gCanRecordBase = canRecordBase; + gCanRecordExtended = canRecordExtended; + + if (XRE_IsParentProcess()) { + gHistogramStorage = + new Histogram* [HistogramCount * size_t(ProcessID::Count)] {}; + gKeyedHistogramStorage = + new KeyedHistogram* [HistogramCount * size_t(ProcessID::Count)] {}; + } + + // Some Telemetry histograms depend on the value of C++ constants and hardcode + // their values in Histograms.json. + // We add static asserts here for those values to match so that future changes + // don't go unnoticed. + // clang-format off + static_assert((uint32_t(JS::GCReason::NUM_TELEMETRY_REASONS) + 1) == + gHistogramInfos[mozilla::Telemetry::GC_MINOR_REASON].bucketCount && + (uint32_t(JS::GCReason::NUM_TELEMETRY_REASONS) + 1) == + gHistogramInfos[mozilla::Telemetry::GC_MINOR_REASON_LONG].bucketCount && + (uint32_t(JS::GCReason::NUM_TELEMETRY_REASONS) + 1) == + gHistogramInfos[mozilla::Telemetry::GC_REASON_2].bucketCount, + "NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json." + " If this was an intentional change, update the n_values for the " + "following in Histograms.json: GC_MINOR_REASON, GC_MINOR_REASON_LONG, " + "GC_REASON_2"); + + // clang-format on + + gInitDone = true; +} + +void TelemetryHistogram::DeInitializeGlobalState() { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + gCanRecordBase = false; + gCanRecordExtended = false; + gInitDone = false; + + // FactoryGet `new`s Histograms for us, but requires us to manually delete. + if (XRE_IsParentProcess()) { + for (size_t i = 0; i < HistogramCount * size_t(ProcessID::Count); ++i) { + if (gKeyedHistogramStorage[i] != gExpiredKeyedHistogram) { + delete gKeyedHistogramStorage[i]; + } + if (gHistogramStorage[i] != gExpiredHistogram) { + delete gHistogramStorage[i]; + } + } + delete[] gHistogramStorage; + delete[] gKeyedHistogramStorage; + } + delete gExpiredHistogram; + gExpiredHistogram = nullptr; + delete gExpiredKeyedHistogram; + gExpiredKeyedHistogram = nullptr; +} + +#ifdef DEBUG +bool TelemetryHistogram::GlobalStateHasBeenInitialized() { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + return gInitDone; +} +#endif + +bool TelemetryHistogram::CanRecordBase() { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + return internal_CanRecordBase(); +} + +void TelemetryHistogram::SetCanRecordBase(bool b) { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + gCanRecordBase = b; +} + +bool TelemetryHistogram::CanRecordExtended() { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + return internal_CanRecordExtended(); +} + +void TelemetryHistogram::SetCanRecordExtended(bool b) { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + gCanRecordExtended = b; +} + +void TelemetryHistogram::InitHistogramRecordingEnabled() { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + auto processType = XRE_GetProcessType(); + for (size_t i = 0; i < HistogramCount; ++i) { + const HistogramInfo& h = gHistogramInfos[i]; + mozilla::Telemetry::HistogramID id = mozilla::Telemetry::HistogramID(i); + bool canRecordInProcess = + CanRecordInProcess(h.record_in_processes, processType); + internal_SetHistogramRecordingEnabled(locker, id, canRecordInProcess); + } + + for (auto recordingInitiallyDisabledID : kRecordingInitiallyDisabledIDs) { + internal_SetHistogramRecordingEnabled(locker, recordingInitiallyDisabledID, + false); + } +} + +void TelemetryHistogram::SetHistogramRecordingEnabled(HistogramID aID, + bool aEnabled) { + if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) { + MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids."); + return; + } + + const HistogramInfo& h = gHistogramInfos[aID]; + if (!CanRecordInProcess(h.record_in_processes, XRE_GetProcessType())) { + // Don't permit record_in_process-disabled recording to be re-enabled. + return; + } + + if (!CanRecordProduct(h.products)) { + // Don't permit products-disabled recording to be re-enabled. + return; + } + + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + internal_SetHistogramRecordingEnabled(locker, aID, aEnabled); +} + +nsresult TelemetryHistogram::SetHistogramRecordingEnabled( + const nsACString& name, bool aEnabled) { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + HistogramID id; + if (NS_FAILED(internal_GetHistogramIdByName(locker, name, &id))) { + return NS_ERROR_FAILURE; + } + + const HistogramInfo& hi = gHistogramInfos[id]; + if (CanRecordInProcess(hi.record_in_processes, XRE_GetProcessType())) { + internal_SetHistogramRecordingEnabled(locker, id, aEnabled); + } + return NS_OK; +} + +void TelemetryHistogram::Accumulate(HistogramID aID, uint32_t aSample) { + if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) { + MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids."); + return; + } + + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + internal_Accumulate(locker, aID, aSample); +} + +void TelemetryHistogram::Accumulate(HistogramID aID, + const nsTArray<uint32_t>& aSamples) { + if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) { + MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids."); + return; + } + + MOZ_ASSERT(!gHistogramInfos[aID].keyed, + "Cannot accumulate into a keyed histogram. No key given."); + + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + for (uint32_t sample : aSamples) { + internal_Accumulate(locker, aID, sample); + } +} + +void TelemetryHistogram::Accumulate(HistogramID aID, const nsCString& aKey, + uint32_t aSample) { + if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) { + MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids."); + return; + } + + // Check if we're allowed to record in the provided key, for this histogram. + if (!gHistogramInfos[aID].allows_key(aKey)) { + nsPrintfCString msg("%s - key '%s' not allowed for this keyed histogram", + gHistogramInfos[aID].name(), aKey.get()); + LogToBrowserConsole(nsIScriptError::errorFlag, NS_ConvertUTF8toUTF16(msg)); + TelemetryScalar::Add(mozilla::Telemetry::ScalarID:: + TELEMETRY_ACCUMULATE_UNKNOWN_HISTOGRAM_KEYS, + NS_ConvertASCIItoUTF16(gHistogramInfos[aID].name()), + 1); + return; + } + + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + internal_Accumulate(locker, aID, aKey, aSample); +} + +void TelemetryHistogram::Accumulate(HistogramID aID, const nsCString& aKey, + const nsTArray<uint32_t>& aSamples) { + if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) { + MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids"); + return; + } + + // Check that this histogram is keyed + MOZ_ASSERT(gHistogramInfos[aID].keyed, + "Cannot accumulate into a non-keyed histogram using a key."); + + // Check if we're allowed to record in the provided key, for this histogram. + if (!gHistogramInfos[aID].allows_key(aKey)) { + nsPrintfCString msg("%s - key '%s' not allowed for this keyed histogram", + gHistogramInfos[aID].name(), aKey.get()); + LogToBrowserConsole(nsIScriptError::errorFlag, NS_ConvertUTF8toUTF16(msg)); + TelemetryScalar::Add(mozilla::Telemetry::ScalarID:: + TELEMETRY_ACCUMULATE_UNKNOWN_HISTOGRAM_KEYS, + NS_ConvertASCIItoUTF16(gHistogramInfos[aID].name()), + 1); + return; + } + + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + for (uint32_t sample : aSamples) { + internal_Accumulate(locker, aID, aKey, sample); + } +} + +nsresult TelemetryHistogram::Accumulate(const char* name, uint32_t sample) { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + if (!internal_CanRecordBase()) { + return NS_ERROR_NOT_AVAILABLE; + } + HistogramID id; + nsresult rv = + internal_GetHistogramIdByName(locker, nsDependentCString(name), &id); + if (NS_FAILED(rv)) { + return rv; + } + internal_Accumulate(locker, id, sample); + return NS_OK; +} + +nsresult TelemetryHistogram::Accumulate(const char* name, const nsCString& key, + uint32_t sample) { + bool keyNotAllowed = false; + + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + if (!internal_CanRecordBase()) { + return NS_ERROR_NOT_AVAILABLE; + } + HistogramID id; + nsresult rv = + internal_GetHistogramIdByName(locker, nsDependentCString(name), &id); + if (NS_SUCCEEDED(rv)) { + // Check if we're allowed to record in the provided key, for this + // histogram. + if (gHistogramInfos[id].allows_key(key)) { + internal_Accumulate(locker, id, key, sample); + return NS_OK; + } + // We're holding |gTelemetryHistogramMutex|, so we can't print a message + // here. + keyNotAllowed = true; + } + } + + if (keyNotAllowed) { + LogToBrowserConsole(nsIScriptError::errorFlag, + u"Key not allowed for this keyed histogram"_ns); + TelemetryScalar::Add(mozilla::Telemetry::ScalarID:: + TELEMETRY_ACCUMULATE_UNKNOWN_HISTOGRAM_KEYS, + NS_ConvertASCIItoUTF16(name), 1); + } + return NS_ERROR_FAILURE; +} + +void TelemetryHistogram::AccumulateCategorical(HistogramID aId, + const nsCString& label) { + if (NS_WARN_IF(!internal_IsHistogramEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids."); + return; + } + + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + if (!internal_CanRecordBase()) { + return; + } + uint32_t labelId = 0; + if (NS_FAILED(gHistogramInfos[aId].label_id(label.get(), &labelId))) { + return; + } + internal_Accumulate(locker, aId, labelId); +} + +void TelemetryHistogram::AccumulateCategorical( + HistogramID aId, const nsTArray<nsCString>& aLabels) { + if (NS_WARN_IF(!internal_IsHistogramEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids."); + return; + } + + if (!internal_CanRecordBase()) { + return; + } + + // We use two loops, one for getting label_ids and another one for actually + // accumulating the values. This ensures that in the case of an invalid label + // in the array, no values are accumulated. In any call to this API, either + // all or (in case of error) none of the values will be accumulated. + + nsTArray<uint32_t> intSamples(aLabels.Length()); + for (const nsCString& label : aLabels) { + uint32_t labelId = 0; + if (NS_FAILED(gHistogramInfos[aId].label_id(label.get(), &labelId))) { + return; + } + intSamples.AppendElement(labelId); + } + + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + + for (uint32_t sample : intSamples) { + internal_Accumulate(locker, aId, sample); + } +} + +void TelemetryHistogram::AccumulateChild( + ProcessID aProcessType, + const nsTArray<HistogramAccumulation>& aAccumulations) { + MOZ_ASSERT(XRE_IsParentProcess()); + + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + if (!internal_CanRecordBase()) { + return; + } + for (uint32_t i = 0; i < aAccumulations.Length(); ++i) { + if (NS_WARN_IF(!internal_IsHistogramEnumId(aAccumulations[i].mId))) { + MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids."); + continue; + } + internal_AccumulateChild(locker, aProcessType, aAccumulations[i].mId, + aAccumulations[i].mSample); + } +} + +void TelemetryHistogram::AccumulateChildKeyed( + ProcessID aProcessType, + const nsTArray<KeyedHistogramAccumulation>& aAccumulations) { + MOZ_ASSERT(XRE_IsParentProcess()); + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + if (!internal_CanRecordBase()) { + return; + } + for (uint32_t i = 0; i < aAccumulations.Length(); ++i) { + if (NS_WARN_IF(!internal_IsHistogramEnumId(aAccumulations[i].mId))) { + MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids."); + continue; + } + internal_AccumulateChildKeyed(locker, aProcessType, aAccumulations[i].mId, + aAccumulations[i].mKey, + aAccumulations[i].mSample); + } +} + +nsresult TelemetryHistogram::GetAllStores(StringHashSet& set) { + for (uint32_t storeIdx : gHistogramStoresTable) { + const char* name = &gHistogramStringTable[storeIdx]; + nsAutoCString store; + store.AssignASCII(name); + if (!set.Insert(store, mozilla::fallible)) { + return NS_ERROR_FAILURE; + } + } + return NS_OK; +} + +nsresult TelemetryHistogram::GetCategoricalHistogramLabels( + JSContext* aCx, JS::MutableHandle<JS::Value> aResult) { + JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx)); + if (!root_obj) { + return NS_ERROR_FAILURE; + } + aResult.setObject(*root_obj); + + for (const HistogramInfo& info : gHistogramInfos) { + if (info.histogramType != nsITelemetry::HISTOGRAM_CATEGORICAL) { + continue; + } + + const char* name = info.name(); + JS::Rooted<JSObject*> labels(aCx, + JS::NewArrayObject(aCx, info.label_count)); + if (!labels) { + return NS_ERROR_FAILURE; + } + + if (!JS_DefineProperty(aCx, root_obj, name, labels, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + + for (uint32_t i = 0; i < info.label_count; ++i) { + uint32_t string_offset = gHistogramLabelTable[info.label_index + i]; + const char* const label = &gHistogramStringTable[string_offset]; + auto clabel = NS_ConvertASCIItoUTF16(label); + JS::Rooted<JS::Value> value(aCx); + value.setString(ToJSString(aCx, clabel)); + if (!JS_DefineElement(aCx, labels, i, value, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + } + + return NS_OK; +} + +nsresult TelemetryHistogram::GetHistogramById( + const nsACString& name, JSContext* cx, JS::MutableHandle<JS::Value> ret) { + HistogramID id; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + nsresult rv = internal_GetHistogramIdByName(locker, name, &id); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + if (gHistogramInfos[id].keyed) { + return NS_ERROR_FAILURE; + } + } + // Runs without protection from |gTelemetryHistogramMutex| + return internal_WrapAndReturnHistogram(id, cx, ret); +} + +nsresult TelemetryHistogram::GetKeyedHistogramById( + const nsACString& name, JSContext* cx, JS::MutableHandle<JS::Value> ret) { + HistogramID id; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + nsresult rv = internal_GetHistogramIdByName(locker, name, &id); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + if (!gHistogramInfos[id].keyed) { + return NS_ERROR_FAILURE; + } + } + // Runs without protection from |gTelemetryHistogramMutex| + return internal_WrapAndReturnKeyedHistogram(id, cx, ret); +} + +const char* TelemetryHistogram::GetHistogramName(HistogramID id) { + if (NS_WARN_IF(!internal_IsHistogramEnumId(id))) { + MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids."); + return nullptr; + } + + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + const HistogramInfo& h = gHistogramInfos[id]; + return h.name(); +} + +nsresult TelemetryHistogram::CreateHistogramSnapshots( + JSContext* aCx, JS::MutableHandle<JS::Value> aResult, + const nsACString& aStore, unsigned int aDataset, bool aClearSubsession, + bool aFilterTest) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + // Runs without protection from |gTelemetryHistogramMutex| + JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx)); + if (!root_obj) { + return NS_ERROR_FAILURE; + } + aResult.setObject(*root_obj); + + // Include the GPU process in histogram snapshots only if we actually tried + // to launch a process for it. + bool includeGPUProcess = internal_AttemptedGPUProcess(); + + HistogramProcessSnapshotsArray processHistArray; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + nsresult rv = internal_GetHistogramsSnapshot( + locker, aStore, aDataset, aClearSubsession, includeGPUProcess, + aFilterTest, processHistArray); + if (NS_FAILED(rv)) { + return rv; + } + } + + // Make the JS calls on the stashed histograms for every process + for (uint32_t process = 0; process < processHistArray.length(); ++process) { + JS::Rooted<JSObject*> processObject(aCx, JS_NewPlainObject(aCx)); + if (!processObject) { + return NS_ERROR_FAILURE; + } + if (!JS_DefineProperty(aCx, root_obj, + GetNameForProcessID(ProcessID(process)), + processObject, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + + for (const HistogramSnapshotInfo& hData : processHistArray[process]) { + HistogramID id = hData.histogramID; + + JS::Rooted<JSObject*> hobj(aCx, JS_NewPlainObject(aCx)); + if (!hobj) { + return NS_ERROR_FAILURE; + } + + if (NS_FAILED(internal_ReflectHistogramAndSamples( + aCx, hobj, gHistogramInfos[id], hData.data))) { + return NS_ERROR_FAILURE; + } + + if (!JS_DefineProperty(aCx, processObject, gHistogramInfos[id].name(), + hobj, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + } + return NS_OK; +} + +nsresult TelemetryHistogram::GetKeyedHistogramSnapshots( + JSContext* aCx, JS::MutableHandle<JS::Value> aResult, + const nsACString& aStore, unsigned int aDataset, bool aClearSubsession, + bool aFilterTest) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + // Runs without protection from |gTelemetryHistogramMutex| + JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx)); + if (!obj) { + return NS_ERROR_FAILURE; + } + aResult.setObject(*obj); + + // Include the GPU process in histogram snapshots only if we actually tried + // to launch a process for it. + bool includeGPUProcess = internal_AttemptedGPUProcess(); + + // Get a snapshot of all the data while holding the mutex. + KeyedHistogramProcessSnapshotsArray processHistArray; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + nsresult rv = internal_GetKeyedHistogramsSnapshot( + locker, aStore, aDataset, aClearSubsession, includeGPUProcess, + aFilterTest, processHistArray); + if (NS_FAILED(rv)) { + return rv; + } + } + + // Mirror the snapshot data to JS, now that we released the mutex. + for (uint32_t process = 0; process < processHistArray.length(); ++process) { + JS::Rooted<JSObject*> processObject(aCx, JS_NewPlainObject(aCx)); + if (!processObject) { + return NS_ERROR_FAILURE; + } + if (!JS_DefineProperty(aCx, obj, GetNameForProcessID(ProcessID(process)), + processObject, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + + for (const KeyedHistogramSnapshotInfo& hData : processHistArray[process]) { + const HistogramInfo& info = gHistogramInfos[hData.histogramId]; + + JS::Rooted<JSObject*> snapshot(aCx, JS_NewPlainObject(aCx)); + if (!snapshot) { + return NS_ERROR_FAILURE; + } + + if (!NS_SUCCEEDED(internal_ReflectKeyedHistogram(hData.data, info, aCx, + snapshot))) { + return NS_ERROR_FAILURE; + } + + if (!JS_DefineProperty(aCx, processObject, info.name(), snapshot, + JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + } + return NS_OK; +} + +size_t TelemetryHistogram::GetHistogramSizesOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + + size_t n = 0; + + // If we allocated the array, let's count the number of pointers in there and + // each entry's size. + if (gKeyedHistogramStorage) { + n += HistogramCount * size_t(ProcessID::Count) * sizeof(KeyedHistogram*); + for (size_t i = 0; i < HistogramCount * size_t(ProcessID::Count); ++i) { + if (gKeyedHistogramStorage[i] && + gKeyedHistogramStorage[i] != gExpiredKeyedHistogram) { + n += gKeyedHistogramStorage[i]->SizeOfIncludingThis(aMallocSizeOf); + } + } + } + + // If we allocated the array, let's count the number of pointers in there. + if (gHistogramStorage) { + n += HistogramCount * size_t(ProcessID::Count) * sizeof(Histogram*); + for (size_t i = 0; i < HistogramCount * size_t(ProcessID::Count); ++i) { + if (gHistogramStorage[i] && gHistogramStorage[i] != gExpiredHistogram) { + n += gHistogramStorage[i]->SizeOfIncludingThis(aMallocSizeOf); + } + } + } + + // We only allocate the expired (keyed) histogram once. + if (gExpiredKeyedHistogram) { + n += gExpiredKeyedHistogram->SizeOfIncludingThis(aMallocSizeOf); + } + + if (gExpiredHistogram) { + n += gExpiredHistogram->SizeOfIncludingThis(aMallocSizeOf); + } + + return n; +} + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: GeckoView specific helpers + +namespace base { +class PersistedSampleSet : public base::Histogram::SampleSet { + public: + explicit PersistedSampleSet(const nsTArray<base::Histogram::Count>& aCounts, + int64_t aSampleSum); +}; + +PersistedSampleSet::PersistedSampleSet( + const nsTArray<base::Histogram::Count>& aCounts, int64_t aSampleSum) { + // Initialize the data in the base class. See Histogram::SampleSet + // for the fields documentation. + const size_t numCounts = aCounts.Length(); + counts_.SetLength(numCounts); + + for (size_t i = 0; i < numCounts; i++) { + counts_[i] = aCounts[i]; + redundant_count_ += aCounts[i]; + } + sum_ = aSampleSum; +}; +} // namespace base + +namespace { +/** + * Helper function to write histogram properties to JSON. + * Please note that this needs to be called between + * StartObjectProperty/EndObject calls that mark the histogram's + * JSON creation. + */ +void internal_ReflectHistogramToJSON(const HistogramSnapshotData& aSnapshot, + mozilla::JSONWriter& aWriter) { + aWriter.IntProperty("sum", aSnapshot.mSampleSum); + + // Fill the "counts" property. + aWriter.StartArrayProperty("counts"); + for (size_t i = 0; i < aSnapshot.mBucketCounts.Length(); i++) { + aWriter.IntElement(aSnapshot.mBucketCounts[i]); + } + aWriter.EndArray(); +} + +bool internal_CanRecordHistogram(const HistogramID id, ProcessID aProcessType) { + // Check if we are allowed to record the data. + if (!CanRecordDataset(gHistogramInfos[id].dataset, internal_CanRecordBase(), + internal_CanRecordExtended())) { + return false; + } + + // Check if we're allowed to record in the given process. + if (aProcessType == ProcessID::Parent && !internal_IsRecordingEnabled(id)) { + return false; + } + + if (aProcessType != ProcessID::Parent && + !CanRecordInProcess(gHistogramInfos[id].record_in_processes, + aProcessType)) { + return false; + } + + // Don't record if the current platform is not enabled + if (!CanRecordProduct(gHistogramInfos[id].products)) { + return false; + } + + return true; +} + +nsresult internal_ParseHistogramData( + JSContext* aCx, JS::Handle<JS::PropertyKey> aEntryId, + JS::Handle<JSObject*> aContainerObj, nsACString& aOutName, + nsTArray<base::Histogram::Count>& aOutCountArray, int64_t& aOutSum) { + // Get the histogram name. + nsAutoJSString histogramName; + if (!histogramName.init(aCx, aEntryId)) { + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + + CopyUTF16toUTF8(histogramName, aOutName); + + // Get the data for this histogram. + JS::Rooted<JS::Value> histogramData(aCx); + if (!JS_GetPropertyById(aCx, aContainerObj, aEntryId, &histogramData)) { + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + + if (!histogramData.isObject()) { + // base::Histogram data need to be an object. If that's not the case, skip + // it and try to load the rest of the data. + return NS_ERROR_FAILURE; + } + + // Get the "sum" property. + JS::Rooted<JS::Value> sumValue(aCx); + JS::Rooted<JSObject*> histogramObj(aCx, &histogramData.toObject()); + if (!JS_GetProperty(aCx, histogramObj, "sum", &sumValue)) { + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + + if (!JS::ToInt64(aCx, sumValue, &aOutSum)) { + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + + // Get the "counts" array. + JS::Rooted<JS::Value> countsArray(aCx); + bool countsIsArray = false; + if (!JS_GetProperty(aCx, histogramObj, "counts", &countsArray) || + !JS::IsArrayObject(aCx, countsArray, &countsIsArray)) { + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + + if (!countsIsArray) { + // The "counts" property needs to be an array. If this is not the case, + // skip this histogram. + return NS_ERROR_FAILURE; + } + + // Get the length of the array. + uint32_t countsLen = 0; + JS::Rooted<JSObject*> countsArrayObj(aCx, &countsArray.toObject()); + if (!JS::GetArrayLength(aCx, countsArrayObj, &countsLen)) { + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + + // Parse the "counts" in the array. + for (uint32_t arrayIdx = 0; arrayIdx < countsLen; arrayIdx++) { + JS::Rooted<JS::Value> elementValue(aCx); + int countAsInt = 0; + if (!JS_GetElement(aCx, countsArrayObj, arrayIdx, &elementValue) || + !JS::ToInt32(aCx, elementValue, &countAsInt)) { + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + aOutCountArray.AppendElement(countAsInt); + } + + return NS_OK; +} + +} // Anonymous namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PUBLIC: GeckoView serialization/deserialization functions. + +nsresult TelemetryHistogram::SerializeHistograms(mozilla::JSONWriter& aWriter) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Only save histograms in the parent process"); + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + // Include the GPU process in histogram snapshots only if we actually tried + // to launch a process for it. + bool includeGPUProcess = internal_AttemptedGPUProcess(); + + // Take a snapshot of the histograms. + HistogramProcessSnapshotsArray processHistArray; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + // We always request the "opt-in"/"prerelease" dataset: we internally + // record the right subset, so this will only return "prerelease" if + // it was recorded. + if (NS_FAILED(internal_GetHistogramsSnapshot( + locker, "main"_ns, nsITelemetry::DATASET_PRERELEASE_CHANNELS, + false /* aClearSubsession */, includeGPUProcess, + false /* aFilterTest */, processHistArray))) { + return NS_ERROR_FAILURE; + } + } + + // Make the JSON calls on the stashed histograms for every process + for (uint32_t process = 0; process < processHistArray.length(); ++process) { + aWriter.StartObjectProperty( + mozilla::MakeStringSpan(GetNameForProcessID(ProcessID(process)))); + + for (const HistogramSnapshotInfo& hData : processHistArray[process]) { + HistogramID id = hData.histogramID; + + aWriter.StartObjectProperty( + mozilla::MakeStringSpan(gHistogramInfos[id].name())); + internal_ReflectHistogramToJSON(hData.data, aWriter); + aWriter.EndObject(); + } + aWriter.EndObject(); + } + + return NS_OK; +} + +nsresult TelemetryHistogram::SerializeKeyedHistograms( + mozilla::JSONWriter& aWriter) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Only save keyed histograms in the parent process"); + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + // Include the GPU process in histogram snapshots only if we actually tried + // to launch a process for it. + bool includeGPUProcess = internal_AttemptedGPUProcess(); + + // Take a snapshot of the keyed histograms. + KeyedHistogramProcessSnapshotsArray processHistArray; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + // We always request the "opt-in"/"prerelease" dataset: we internally + // record the right subset, so this will only return "prerelease" if + // it was recorded. + if (NS_FAILED(internal_GetKeyedHistogramsSnapshot( + locker, "main"_ns, nsITelemetry::DATASET_PRERELEASE_CHANNELS, + false /* aClearSubsession */, includeGPUProcess, + false /* aFilterTest */, processHistArray))) { + return NS_ERROR_FAILURE; + } + } + + // Serialize the keyed histograms for every process. + for (uint32_t process = 0; process < processHistArray.length(); ++process) { + aWriter.StartObjectProperty( + mozilla::MakeStringSpan(GetNameForProcessID(ProcessID(process)))); + + const KeyedHistogramSnapshotsArray& hArray = processHistArray[process]; + for (size_t i = 0; i < hArray.length(); ++i) { + const KeyedHistogramSnapshotInfo& hData = hArray[i]; + HistogramID id = hData.histogramId; + const HistogramInfo& info = gHistogramInfos[id]; + + aWriter.StartObjectProperty(mozilla::MakeStringSpan(info.name())); + + // Each key is a new object with a "sum" and a "counts" property. + for (const auto& entry : hData.data) { + const HistogramSnapshotData& keyData = entry.GetData(); + aWriter.StartObjectProperty(PromiseFlatCString(entry.GetKey())); + internal_ReflectHistogramToJSON(keyData, aWriter); + aWriter.EndObject(); + } + + aWriter.EndObject(); + } + aWriter.EndObject(); + } + + return NS_OK; +} + +nsresult TelemetryHistogram::DeserializeHistograms( + JSContext* aCx, JS::Handle<JS::Value> aData) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Only load histograms in the parent process"); + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + // Telemetry is disabled. This should never happen, but let's leave this check + // for consistency with other histogram updates routines. + if (!internal_CanRecordBase()) { + return NS_OK; + } + + typedef mozilla::Tuple<nsCString, nsTArray<base::Histogram::Count>, int64_t> + PersistedHistogramTuple; + typedef mozilla::Vector<PersistedHistogramTuple> PersistedHistogramArray; + typedef mozilla::Vector<PersistedHistogramArray> PersistedHistogramStorage; + + // Before updating the histograms, we need to get the data out of the JS + // wrappers. We can't hold the histogram mutex while handling JS stuff. + // Build a <histogram name, value> map. + JS::Rooted<JSObject*> histogramDataObj(aCx, &aData.toObject()); + JS::Rooted<JS::IdVector> processes(aCx, JS::IdVector(aCx)); + if (!JS_Enumerate(aCx, histogramDataObj, &processes)) { + // We can't even enumerate the processes in the loaded data, so + // there is nothing we could recover from the persistence file. Bail out. + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + + // Make sure we have enough storage for all the processes. + PersistedHistogramStorage histogramsToUpdate; + if (!histogramsToUpdate.resize(static_cast<uint32_t>(ProcessID::Count))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // The following block of code attempts to extract as much data as possible + // from the serialized JSON, even in case of light data corruptions: if, for + // example, the data for a single process is corrupted or is in an unexpected + // form, we press on and attempt to load the data for the other processes. + JS::Rooted<JS::PropertyKey> process(aCx); + for (auto& processVal : processes) { + // This is required as JS API calls require an Handle<jsid> and not a + // plain jsid. + process = processVal; + // Get the process name. + nsAutoJSString processNameJS; + if (!processNameJS.init(aCx, process)) { + JS_ClearPendingException(aCx); + continue; + } + + // Make sure it's valid. Note that this is safe to call outside + // of a locked section. + NS_ConvertUTF16toUTF8 processName(processNameJS); + ProcessID processID = GetIDForProcessName(processName.get()); + if (processID == ProcessID::Count) { + NS_WARNING( + nsPrintfCString("Failed to get process ID for %s", processName.get()) + .get()); + continue; + } + + // And its probes. + JS::Rooted<JS::Value> processData(aCx); + if (!JS_GetPropertyById(aCx, histogramDataObj, process, &processData)) { + JS_ClearPendingException(aCx); + continue; + } + + if (!processData.isObject()) { + // |processData| should be an object containing histograms. If this is + // not the case, silently skip and try to load the data for the other + // processes. + continue; + } + + // Iterate through each histogram. + JS::Rooted<JSObject*> processDataObj(aCx, &processData.toObject()); + JS::Rooted<JS::IdVector> histograms(aCx, JS::IdVector(aCx)); + if (!JS_Enumerate(aCx, processDataObj, &histograms)) { + JS_ClearPendingException(aCx); + continue; + } + + // Get a reference to the deserialized data for this process. + PersistedHistogramArray& deserializedProcessData = + histogramsToUpdate[static_cast<uint32_t>(processID)]; + + JS::Rooted<JS::PropertyKey> histogram(aCx); + for (auto& histogramVal : histograms) { + histogram = histogramVal; + + int64_t sum = 0; + nsTArray<base::Histogram::Count> deserializedCounts; + nsCString histogramName; + if (NS_FAILED(internal_ParseHistogramData(aCx, histogram, processDataObj, + histogramName, + deserializedCounts, sum))) { + continue; + } + + // Finally append the deserialized data to the storage. + if (!deserializedProcessData.emplaceBack(MakeTuple( + std::move(histogramName), std::move(deserializedCounts), sum))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + // Update the histogram storage. + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + + for (uint32_t process = 0; process < histogramsToUpdate.length(); + ++process) { + PersistedHistogramArray& processArray = histogramsToUpdate[process]; + + for (auto& histogramData : processArray) { + // Attempt to get the corresponding ID for the deserialized histogram + // name. + HistogramID id; + if (NS_FAILED(internal_GetHistogramIdByName( + locker, mozilla::Get<0>(histogramData), &id))) { + continue; + } + + ProcessID procID = static_cast<ProcessID>(process); + if (!internal_CanRecordHistogram(id, procID)) { + // We're not allowed to record this, so don't try to restore it. + continue; + } + + // Get the Histogram instance: this will instantiate it if it doesn't + // exist. + Histogram* w = internal_GetHistogramById(locker, id, procID); + MOZ_ASSERT(w); + + if (!w || w->IsExpired()) { + continue; + } + + base::Histogram* h = nullptr; + constexpr auto store = "main"_ns; + if (!w->GetHistogram(store, &h)) { + continue; + } + MOZ_ASSERT(h); + + if (!h) { + // Don't restore expired histograms. + continue; + } + + // Make sure that histogram counts have matching sizes. If not, + // |AddSampleSet| will fail and crash. + size_t numCounts = mozilla::Get<1>(histogramData).Length(); + if (h->bucket_count() != numCounts) { + MOZ_ASSERT(false, + "The number of restored buckets does not match with the " + "on in the definition"); + continue; + } + + // Update the data for the histogram. + h->AddSampleSet( + base::PersistedSampleSet(std::move(mozilla::Get<1>(histogramData)), + mozilla::Get<2>(histogramData))); + } + } + } + + return NS_OK; +} + +nsresult TelemetryHistogram::DeserializeKeyedHistograms( + JSContext* aCx, JS::Handle<JS::Value> aData) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Only load keyed histograms in the parent process"); + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + // Telemetry is disabled. This should never happen, but let's leave this check + // for consistency with other histogram updates routines. + if (!internal_CanRecordBase()) { + return NS_OK; + } + + typedef mozilla::Tuple<nsCString, nsCString, nsTArray<base::Histogram::Count>, + int64_t> + PersistedKeyedHistogramTuple; + typedef mozilla::Vector<PersistedKeyedHistogramTuple> + PersistedKeyedHistogramArray; + typedef mozilla::Vector<PersistedKeyedHistogramArray> + PersistedKeyedHistogramStorage; + + // Before updating the histograms, we need to get the data out of the JS + // wrappers. We can't hold the histogram mutex while handling JS stuff. + // Build a <histogram name, value> map. + JS::Rooted<JSObject*> histogramDataObj(aCx, &aData.toObject()); + JS::Rooted<JS::IdVector> processes(aCx, JS::IdVector(aCx)); + if (!JS_Enumerate(aCx, histogramDataObj, &processes)) { + // We can't even enumerate the processes in the loaded data, so + // there is nothing we could recover from the persistence file. Bail out. + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + + // Make sure we have enough storage for all the processes. + PersistedKeyedHistogramStorage histogramsToUpdate; + if (!histogramsToUpdate.resize(static_cast<uint32_t>(ProcessID::Count))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // The following block of code attempts to extract as much data as possible + // from the serialized JSON, even in case of light data corruptions: if, for + // example, the data for a single process is corrupted or is in an unexpected + // form, we press on and attempt to load the data for the other processes. + JS::Rooted<JS::PropertyKey> process(aCx); + for (auto& processVal : processes) { + // This is required as JS API calls require an Handle<jsid> and not a + // plain jsid. + process = processVal; + // Get the process name. + nsAutoJSString processNameJS; + if (!processNameJS.init(aCx, process)) { + JS_ClearPendingException(aCx); + continue; + } + + // Make sure it's valid. Note that this is safe to call outside + // of a locked section. + NS_ConvertUTF16toUTF8 processName(processNameJS); + ProcessID processID = GetIDForProcessName(processName.get()); + if (processID == ProcessID::Count) { + NS_WARNING( + nsPrintfCString("Failed to get process ID for %s", processName.get()) + .get()); + continue; + } + + // And its probes. + JS::Rooted<JS::Value> processData(aCx); + if (!JS_GetPropertyById(aCx, histogramDataObj, process, &processData)) { + JS_ClearPendingException(aCx); + continue; + } + + if (!processData.isObject()) { + // |processData| should be an object containing histograms. If this is + // not the case, silently skip and try to load the data for the other + // processes. + continue; + } + + // Iterate through each keyed histogram. + JS::Rooted<JSObject*> processDataObj(aCx, &processData.toObject()); + JS::Rooted<JS::IdVector> histograms(aCx, JS::IdVector(aCx)); + if (!JS_Enumerate(aCx, processDataObj, &histograms)) { + JS_ClearPendingException(aCx); + continue; + } + + // Get a reference to the deserialized data for this process. + PersistedKeyedHistogramArray& deserializedProcessData = + histogramsToUpdate[static_cast<uint32_t>(processID)]; + + JS::Rooted<JS::PropertyKey> histogram(aCx); + for (auto& histogramVal : histograms) { + histogram = histogramVal; + // Get the histogram name. + nsAutoJSString histogramName; + if (!histogramName.init(aCx, histogram)) { + JS_ClearPendingException(aCx); + continue; + } + + // Get the data for this histogram. + JS::Rooted<JS::Value> histogramData(aCx); + if (!JS_GetPropertyById(aCx, processDataObj, histogram, &histogramData)) { + JS_ClearPendingException(aCx); + continue; + } + + // Iterate through each key in the histogram. + JS::Rooted<JSObject*> keysDataObj(aCx, &histogramData.toObject()); + JS::Rooted<JS::IdVector> keys(aCx, JS::IdVector(aCx)); + if (!JS_Enumerate(aCx, keysDataObj, &keys)) { + JS_ClearPendingException(aCx); + continue; + } + + JS::Rooted<JS::PropertyKey> key(aCx); + for (auto& keyVal : keys) { + key = keyVal; + + int64_t sum = 0; + nsTArray<base::Histogram::Count> deserializedCounts; + nsCString keyName; + if (NS_FAILED(internal_ParseHistogramData( + aCx, key, keysDataObj, keyName, deserializedCounts, sum))) { + continue; + } + + // Finally append the deserialized data to the storage. + if (!deserializedProcessData.emplaceBack(MakeTuple( + nsCString(NS_ConvertUTF16toUTF8(histogramName)), + std::move(keyName), std::move(deserializedCounts), sum))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + } + + // Update the keyed histogram storage. + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + + for (uint32_t process = 0; process < histogramsToUpdate.length(); + ++process) { + PersistedKeyedHistogramArray& processArray = histogramsToUpdate[process]; + + for (auto& histogramData : processArray) { + // Attempt to get the corresponding ID for the deserialized histogram + // name. + HistogramID id; + if (NS_FAILED(internal_GetHistogramIdByName( + locker, mozilla::Get<0>(histogramData), &id))) { + continue; + } + + ProcessID procID = static_cast<ProcessID>(process); + if (!internal_CanRecordHistogram(id, procID)) { + // We're not allowed to record this, so don't try to restore it. + continue; + } + + KeyedHistogram* keyed = internal_GetKeyedHistogramById(id, procID); + MOZ_ASSERT(keyed); + + if (!keyed || keyed->IsExpired()) { + // Don't restore if we don't have a destination storage or the + // histogram is expired. + continue; + } + + // Get data for the key we're looking for. + base::Histogram* h = nullptr; + if (NS_FAILED(keyed->GetHistogram( + "main"_ns, mozilla::Get<1>(histogramData), &h))) { + continue; + } + MOZ_ASSERT(h); + + if (!h) { + // Don't restore if we don't have a destination storage. + continue; + } + + // Make sure that histogram counts have matching sizes. If not, + // |AddSampleSet| will fail and crash. + size_t numCounts = mozilla::Get<2>(histogramData).Length(); + if (h->bucket_count() != numCounts) { + MOZ_ASSERT(false, + "The number of restored buckets does not match with the " + "on in the definition"); + continue; + } + + // Update the data for the histogram. + h->AddSampleSet( + base::PersistedSampleSet(std::move(mozilla::Get<2>(histogramData)), + mozilla::Get<3>(histogramData))); + } + } + } + + return NS_OK; +} diff --git a/toolkit/components/telemetry/core/TelemetryHistogram.h b/toolkit/components/telemetry/core/TelemetryHistogram.h new file mode 100644 index 0000000000..9f415f3637 --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryHistogram.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryHistogram_h__ +#define TelemetryHistogram_h__ + +#include "mozilla/TelemetryComms.h" +#include "mozilla/TelemetryHistogramEnums.h" +#include "mozilla/TelemetryProcessEnums.h" +#include "nsXULAppAPI.h" +#include "TelemetryCommon.h" + +namespace mozilla { +// This is only used for the GeckoView persistence. +class JSONWriter; +} // namespace mozilla + +// This module is internal to Telemetry. It encapsulates Telemetry's +// histogram accumulation and storage logic. It should only be used by +// Telemetry.cpp. These functions should not be used anywhere else. +// For the public interface to Telemetry functionality, see Telemetry.h. + +namespace TelemetryHistogram { + +void InitializeGlobalState(bool canRecordBase, bool canRecordExtended); +void DeInitializeGlobalState(); +#ifdef DEBUG +bool GlobalStateHasBeenInitialized(); +#endif + +bool CanRecordBase(); +void SetCanRecordBase(bool b); +bool CanRecordExtended(); +void SetCanRecordExtended(bool b); + +void InitHistogramRecordingEnabled(); +void SetHistogramRecordingEnabled(mozilla::Telemetry::HistogramID aID, + bool aEnabled); + +nsresult SetHistogramRecordingEnabled(const nsACString& id, bool aEnabled); + +void Accumulate(mozilla::Telemetry::HistogramID aHistogram, uint32_t aSample); +void Accumulate(mozilla::Telemetry::HistogramID aHistogram, + const nsTArray<uint32_t>& aSamples); +void Accumulate(mozilla::Telemetry::HistogramID aID, const nsCString& aKey, + uint32_t aSample); +void Accumulate(mozilla::Telemetry::HistogramID aID, const nsCString& aKey, + const nsTArray<uint32_t>& aSamples); +/* + * Accumulate a sample into the named histogram. + * + * Returns NS_OK on success. + * Returns NS_ERROR_NOT_AVAILABLE if recording Telemetry is disabled. + * Returns NS_ERROR_FAILURE on other errors. + */ +nsresult Accumulate(const char* name, uint32_t sample); + +/* + * Accumulate a sample into the named keyed histogram by key. + * + * Returns NS_OK on success. + * Returns NS_ERROR_NOT_AVAILABLE if recording Telemetry is disabled. + * Returns NS_ERROR_FAILURE on other errors. + */ +nsresult Accumulate(const char* name, const nsCString& key, uint32_t sample); + +void AccumulateCategorical(mozilla::Telemetry::HistogramID aId, + const nsCString& aLabel); +void AccumulateCategorical(mozilla::Telemetry::HistogramID aId, + const nsTArray<nsCString>& aLabels); + +void AccumulateChild( + mozilla::Telemetry::ProcessID aProcessType, + const nsTArray<mozilla::Telemetry::HistogramAccumulation>& aAccumulations); +void AccumulateChildKeyed( + mozilla::Telemetry::ProcessID aProcessType, + const nsTArray<mozilla::Telemetry::KeyedHistogramAccumulation>& + aAccumulations); + +/** + * Append the list of registered stores to the given set. + */ +nsresult GetAllStores(mozilla::Telemetry::Common::StringHashSet& set); + +nsresult GetCategoricalHistogramLabels(JSContext* aCx, + JS::MutableHandle<JS::Value> aResult); + +nsresult GetHistogramById(const nsACString& name, JSContext* cx, + JS::MutableHandle<JS::Value> ret); + +nsresult GetKeyedHistogramById(const nsACString& name, JSContext* cx, + JS::MutableHandle<JS::Value> ret); + +const char* GetHistogramName(mozilla::Telemetry::HistogramID id); + +nsresult CreateHistogramSnapshots(JSContext* aCx, + JS::MutableHandle<JS::Value> aResult, + const nsACString& aStore, + unsigned int aDataset, bool aClearSubsession, + bool aFilterTest = false); + +nsresult GetKeyedHistogramSnapshots(JSContext* aCx, + JS::MutableHandle<JS::Value> aResult, + const nsACString& aStore, + unsigned int aDataset, + bool aClearSubsession, + bool aFilterTest = false); + +size_t GetHistogramSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + +// These functions are only meant to be used for GeckoView persistence. +// They are responsible for updating in-memory probes with the data persisted +// on the disk and vice-versa. +nsresult SerializeHistograms(mozilla::JSONWriter& aWriter); +nsresult SerializeKeyedHistograms(mozilla::JSONWriter& aWriter); +nsresult DeserializeHistograms(JSContext* aCx, JS::Handle<JS::Value> aData); +nsresult DeserializeKeyedHistograms(JSContext* aCx, + JS::Handle<JS::Value> aData); + +} // namespace TelemetryHistogram + +#endif // TelemetryHistogram_h__ diff --git a/toolkit/components/telemetry/core/TelemetryOrigin.cpp b/toolkit/components/telemetry/core/TelemetryOrigin.cpp new file mode 100644 index 0000000000..1f0b1f16d2 --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryOrigin.cpp @@ -0,0 +1,618 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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 "TelemetryOrigin.h" + +#include "nsIObserverService.h" +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "TelemetryCommon.h" +#include "TelemetryOriginEnums.h" + +#include "js/Array.h" // JS::NewArrayObject +#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty +#include "mozilla/Atomics.h" +#include "mozilla/Base64.h" +#include "mozilla/dom/PrioEncoder.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/Tuple.h" +#include "mozilla/UniquePtr.h" + +#include <cmath> +#include <type_traits> + +using mozilla::Get; +using mozilla::MakeTuple; +using mozilla::MakeUnique; +using mozilla::MallocSizeOf; +using mozilla::StaticMutex; +using mozilla::StaticMutexAutoLock; +using mozilla::Tuple; +using mozilla::UniquePtr; +using mozilla::dom::PrioEncoder; +using mozilla::Telemetry::OriginMetricID; +using mozilla::Telemetry::Common::ToJSString; + +/*********************************************************************** + * + * Firefox Origin Telemetry + * Docs: + * https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/origin.html + * + * Origin Telemetry stores pairs of information (metric, origin) which boils + * down to "$metric happened on $origin". + * + * Prio can only encode up-to-2046-length bit vectors. The process of + * transforming these pairs of information into bit vectors is called "App + * Encoding". The bit vectors are then "Prio Encoded" into binary goop. The + * binary goop is then "Base64 Encoded" into strings. + * + */ + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE TYPES + +namespace { + +class OriginMetricIDHashKey : public PLDHashEntryHdr { + public: + typedef const OriginMetricID& KeyType; + typedef const OriginMetricID* KeyTypePointer; + + explicit OriginMetricIDHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + OriginMetricIDHashKey(OriginMetricIDHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mValue(std::move(aOther.mValue)) {} + ~OriginMetricIDHashKey() = default; + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return static_cast<std::underlying_type<OriginMetricID>::type>(*aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const OriginMetricID mValue; +}; + +} // anonymous namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE STATE, SHARED BY ALL THREADS +// +// Access for all of this state (except gInitDone) must be guarded by +// gTelemetryOriginMutex. + +namespace { + +// This is a StaticMutex rather than a plain Mutex (1) so that +// it gets initialised in a thread-safe manner the first time +// it is used, and (2) because it is never de-initialised, and +// a normal Mutex would show up as a leak in BloatView. StaticMutex +// also has the "OffTheBooks" property, so it won't show as a leak +// in BloatView. +// Another reason to use a StaticMutex instead of a plain Mutex is +// that, due to the nature of Telemetry, we cannot rely on having a +// mutex initialized in InitializeGlobalState. Unfortunately, we +// cannot make sure that no other function is called before this point. +static StaticMutex gTelemetryOriginMutex MOZ_UNANNOTATED; + +typedef nsTArray<Tuple<const char*, const char*>> OriginHashesList; +UniquePtr<OriginHashesList> gOriginHashesList; + +typedef nsTHashMap<nsCStringHashKey, size_t> OriginToIndexMap; +UniquePtr<OriginToIndexMap> gOriginToIndexMap; + +typedef nsTHashMap<nsCStringHashKey, size_t> HashToIndexMap; +UniquePtr<HashToIndexMap> gHashToIndexMap; + +typedef nsTHashMap<nsCStringHashKey, uint32_t> OriginBag; +typedef nsTHashMap<OriginMetricIDHashKey, OriginBag> IdToOriginBag; + +UniquePtr<IdToOriginBag> gMetricToOriginBag; + +mozilla::Atomic<bool, mozilla::Relaxed> gInitDone(false); + +// Useful for app-encoded data +typedef nsTArray<std::pair<OriginMetricID, nsTArray<nsTArray<bool>>>> + IdBoolsPairArray; + +// Prio has a maximum supported number of bools it can encode at a time. +// This means a single metric may result in several encoded payloads if the +// number of origins exceeds the number of bools. +// Each encoded payload corresponds to an element in the `prioData` array in the +// "prio" ping. +// This number is the number of encoded payloads needed per metric, and is +// equivalent to "how many bitvectors do we need to encode this whole list of +// origins?" +static uint32_t gPrioDatasPerMetric; + +// The number of "meta-origins": in-band metadata about origin telemetry. +// Currently 1: the "unknown origin recorded" meta-origin. +static uint32_t kNumMetaOrigins = 1; + +constexpr auto kUnknownOrigin = "__UNKNOWN__"_ns; + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: thread-safe helpers + +namespace { + +const char* GetNameForMetricID(OriginMetricID aId) { + MOZ_ASSERT(aId < OriginMetricID::Count); + return mozilla::Telemetry::MetricIDToString[static_cast<uint32_t>(aId)]; +} + +// Calculates the number of `prioData` elements we'd need if we were asked for +// an encoded snapshot right now. +uint32_t PrioDataCount(const StaticMutexAutoLock& lock) { + uint32_t count = 0; + for (const auto& origins : gMetricToOriginBag->Values()) { + uint32_t maxOriginCount = 0; + for (const auto& data : origins.Values()) { + maxOriginCount = std::max(maxOriginCount, data); + } + count += gPrioDatasPerMetric * maxOriginCount; + } + return count; +} + +// Takes the storage and turns it into bool arrays for Prio to encode, turning +// { metric1: [origin1, origin2, ...], ...} +// into +// [(metric1, [[shard1], [shard2], ...]), ...] +// Note: if an origin is present multiple times for a given metric, we must +// generate multiple (id, boolvectors) pairs so that they are all reported. +// Meaning +// { metric1: [origin1, origin2, origin2] } +// must turn into (with a pretend gNumBooleans of 1) +// [(metric1, [[1], [1]]), (metric1, [[0], [1]])] +nsresult AppEncodeTo(const StaticMutexAutoLock& lock, + IdBoolsPairArray& aResult) { + for (const auto& bagEntry : *gMetricToOriginBag) { + OriginMetricID id = bagEntry.GetKey(); + const OriginBag& bag = bagEntry.GetData(); + + uint32_t generation = 1; + uint32_t maxGeneration = 1; + do { + // Fill in the result bool vectors with `false`s. + nsTArray<nsTArray<bool>> metricData(gPrioDatasPerMetric); + metricData.SetLength(gPrioDatasPerMetric); + for (size_t i = 0; i < metricData.Length() - 1; ++i) { + metricData[i].SetLength(PrioEncoder::gNumBooleans); + for (auto& metricDatum : metricData[i]) { + metricDatum = false; + } + } + auto& lastArray = metricData[metricData.Length() - 1]; + lastArray.SetLength((gOriginHashesList->Length() + kNumMetaOrigins) % + PrioEncoder::gNumBooleans); + for (auto& metricDatum : lastArray) { + metricDatum = false; + } + + for (const auto& originEntry : bag) { + uint32_t originCount = originEntry.GetData(); + if (originCount >= generation) { + maxGeneration = std::max(maxGeneration, originCount); + + const nsACString& origin = originEntry.GetKey(); + size_t index; + if (!gOriginToIndexMap->Get(origin, &index)) { + return NS_ERROR_FAILURE; + } + MOZ_ASSERT(index < (gOriginHashesList->Length() + kNumMetaOrigins)); + size_t shardIndex = index / PrioEncoder::gNumBooleans; + MOZ_ASSERT(shardIndex < metricData.Length()); + MOZ_ASSERT(index % PrioEncoder::gNumBooleans < + metricData[shardIndex].Length()); + metricData[shardIndex][index % PrioEncoder::gNumBooleans] = true; + } + } + aResult.EmplaceBack(id, std::move(metricData)); + } while (generation++ < maxGeneration); + } + return NS_OK; +} + +} // anonymous namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryOrigin:: + +void TelemetryOrigin::InitializeGlobalState() { + if (!XRE_IsParentProcess()) { + return; + } + + StaticMutexAutoLock locker(gTelemetryOriginMutex); + + MOZ_ASSERT(!gInitDone, + "TelemetryOrigin::InitializeGlobalState " + "may only be called once"); + + // The contents and order of the arrays that follow matter. + // Both ensure a consistent app-encoding. + static const char sOriginStrings[] = { +#define ORIGIN(origin, hash) origin "\0" +#include "TelemetryOriginData.inc" +#undef ORIGIN + }; + static const char sHashStrings[] = { +#define ORIGIN(origin, hash) hash "\0" +#include "TelemetryOriginData.inc" +#undef ORIGIN + }; + + struct OriginHashLengths { + uint8_t originLength; + uint8_t hashLength; + }; + static const OriginHashLengths sOriginHashLengths[] = { +#define ORIGIN(origin, hash) {sizeof(origin), sizeof(hash)}, +#include "TelemetryOriginData.inc" +#undef ORIGIN + }; + + static const size_t kNumOrigins = MOZ_ARRAY_LENGTH(sOriginHashLengths); + + gOriginHashesList = MakeUnique<OriginHashesList>(kNumOrigins); + + gPrioDatasPerMetric = + ceil(static_cast<double>(kNumOrigins + kNumMetaOrigins) / + PrioEncoder::gNumBooleans); + + gOriginToIndexMap = + MakeUnique<OriginToIndexMap>(kNumOrigins + kNumMetaOrigins); + gHashToIndexMap = MakeUnique<HashToIndexMap>(kNumOrigins); + size_t originOffset = 0; + size_t hashOffset = 0; + for (size_t i = 0; i < kNumOrigins; ++i) { + const char* origin = &sOriginStrings[originOffset]; + const char* hash = &sHashStrings[hashOffset]; + MOZ_ASSERT(!kUnknownOrigin.Equals(origin), + "Unknown origin literal is reserved in Origin Telemetry"); + + gOriginHashesList->AppendElement(MakeTuple(origin, hash)); + + const size_t originLength = sOriginHashLengths[i].originLength; + const size_t hashLength = sOriginHashLengths[i].hashLength; + + originOffset += originLength; + hashOffset += hashLength; + + // -1 to leave off the null terminators. + gOriginToIndexMap->InsertOrUpdate( + nsDependentCString(origin, originLength - 1), i); + gHashToIndexMap->InsertOrUpdate(nsDependentCString(hash, hashLength - 1), + i); + } + + // Add the meta-origin for tracking recordings to untracked origins. + gOriginToIndexMap->InsertOrUpdate(kUnknownOrigin, + gOriginHashesList->Length()); + + gMetricToOriginBag = MakeUnique<IdToOriginBag>(); + + // This map shouldn't change at runtime, so make debug builds complain + // if it tries. + gOriginToIndexMap->MarkImmutable(); + gHashToIndexMap->MarkImmutable(); + + gInitDone = true; +} + +void TelemetryOrigin::DeInitializeGlobalState() { + if (!XRE_IsParentProcess()) { + return; + } + + StaticMutexAutoLock locker(gTelemetryOriginMutex); + MOZ_ASSERT(gInitDone); + + if (!gInitDone) { + return; + } + + gOriginHashesList = nullptr; + + gOriginToIndexMap = nullptr; + + gHashToIndexMap = nullptr; + + gMetricToOriginBag = nullptr; + + gInitDone = false; +} + +nsresult TelemetryOrigin::RecordOrigin(OriginMetricID aId, + const nsACString& aOrigin) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + uint32_t prioDataCount; + { + StaticMutexAutoLock locker(gTelemetryOriginMutex); + + // Common Telemetry error-handling practices for recording functions: + // only illegal calls return errors whereas merely incorrect ones are mutely + // ignored. + if (!gInitDone) { + return NS_OK; + } + + size_t index; + nsCString origin(aOrigin); + if (gHashToIndexMap->Get(aOrigin, &index)) { + MOZ_ASSERT(aOrigin.Equals(Get<1>((*gOriginHashesList)[index]))); + origin = Get<0>((*gOriginHashesList)[index]); + } + + if (!gOriginToIndexMap->Contains(origin)) { + // Only record one unknown origin per metric per snapshot. + // (otherwise we may get swamped and blow our data budget.) + if (gMetricToOriginBag->Contains(aId) && + gMetricToOriginBag->LookupOrInsert(aId).Contains(kUnknownOrigin)) { + return NS_OK; + } + origin = kUnknownOrigin; + } + + auto& originBag = gMetricToOriginBag->LookupOrInsert(aId); + originBag.LookupOrInsert(origin)++; + + prioDataCount = PrioDataCount(locker); + } + + static uint32_t sPrioPingLimit = + mozilla::Preferences::GetUint("toolkit.telemetry.prioping.dataLimit", 10); + if (prioDataCount >= sPrioPingLimit) { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + // Ensure we don't notify while holding the lock in case of synchronous + // dispatch. May deadlock ourselves if we then trigger a snapshot. + os->NotifyObservers(nullptr, "origin-telemetry-storage-limit-reached", + nullptr); + } + } + + return NS_OK; +} + +nsresult TelemetryOrigin::GetOriginSnapshot( + bool aClear, JSContext* aCx, JS::MutableHandle<JS::Value> aResult) { + if (NS_WARN_IF(!XRE_IsParentProcess())) { + return NS_ERROR_FAILURE; + } + + if (!gInitDone) { + return NS_OK; + } + + // Step 1: Grab the lock, copy into stack-local storage, optionally clear. + IdToOriginBag copy; + { + StaticMutexAutoLock locker(gTelemetryOriginMutex); + + if (aClear) { + // I'd really prefer to clear after we're sure the snapshot didn't go + // awry, but we can't hold a lock preventing recording while using JS + // APIs. And replaying any interleaving recording sounds like too much + // squeeze for not enough juice. + + gMetricToOriginBag->SwapElements(copy); + } else { + for (const auto& entry : *gMetricToOriginBag) { + copy.InsertOrUpdate(entry.GetKey(), entry.GetData().Clone()); + } + } + } + + // Step 2: Without the lock, generate JS datastructure for snapshotting + JS::Rooted<JSObject*> rootObj(aCx, JS_NewPlainObject(aCx)); + if (NS_WARN_IF(!rootObj)) { + return NS_ERROR_FAILURE; + } + aResult.setObject(*rootObj); + for (const auto& entry : copy) { + JS::Rooted<JSObject*> originsObj(aCx, JS_NewPlainObject(aCx)); + if (NS_WARN_IF(!originsObj)) { + return NS_ERROR_FAILURE; + } + if (!JS_DefineProperty(aCx, rootObj, GetNameForMetricID(entry.GetKey()), + originsObj, JSPROP_ENUMERATE)) { + NS_WARNING("Failed to define property in origin snapshot."); + return NS_ERROR_FAILURE; + } + + for (const auto& originEntry : entry.GetData()) { + if (!JS_DefineProperty(aCx, originsObj, + nsPromiseFlatCString(originEntry.GetKey()).get(), + originEntry.GetData(), JSPROP_ENUMERATE)) { + NS_WARNING("Failed to define origin and count in snapshot."); + return NS_ERROR_FAILURE; + } + } + } + + return NS_OK; +} + +nsresult TelemetryOrigin::GetEncodedOriginSnapshot( + bool aClear, JSContext* aCx, JS::MutableHandle<JS::Value> aSnapshot) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + if (!gInitDone) { + return NS_OK; + } + + // Step 1: Take the lock and app-encode. Optionally clear. + nsresult rv; + IdBoolsPairArray appEncodedMetricData; + { + StaticMutexAutoLock lock(gTelemetryOriginMutex); + + rv = AppEncodeTo(lock, appEncodedMetricData); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aClear) { + // I'd really prefer to clear after we're sure the snapshot didn't go + // awry, but we can't hold a lock preventing recording while using JS + // APIs. And replaying any interleaving recording sounds like too much + // squeeze for not enough juice. + + gMetricToOriginBag->Clear(); + } + } + + // Step 2: Don't need the lock to prio-encode and base64-encode + nsTArray<std::pair<nsCString, std::pair<nsCString, nsCString>>> prioData; + for (auto& metricData : appEncodedMetricData) { + auto& boolVectors = metricData.second; + for (uint32_t i = 0; i < boolVectors.Length(); ++i) { + // "encoding" is of the form `metricName-X` where X is the shard index. + nsCString encodingName = + nsPrintfCString("%s-%u", GetNameForMetricID(metricData.first), i); + nsCString aResult; + nsCString bResult; + rv = PrioEncoder::EncodeNative(encodingName, boolVectors[i], aResult, + bResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + nsCString aBase64; + rv = mozilla::Base64Encode(aResult, aBase64); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + nsCString bBase64; + rv = mozilla::Base64Encode(bResult, bBase64); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + prioData.EmplaceBack(std::move(encodingName), + std::pair(std::move(aBase64), std::move(bBase64))); + } + } + + // Step 3: Still don't need the lock to translate to JS + // The resulting data structure is: + // [{ + // encoding: <encoding name>, + // prio: { + // a: <base64 string>, + // b: <base64 string>, + // }, + // }, ...] + + JS::Rooted<JSObject*> prioDataArray( + aCx, JS::NewArrayObject(aCx, prioData.Length())); + if (NS_WARN_IF(!prioDataArray)) { + return NS_ERROR_FAILURE; + } + uint32_t i = 0; + for (auto& prioDatum : prioData) { + JS::Rooted<JSObject*> prioDatumObj(aCx, JS_NewPlainObject(aCx)); + if (NS_WARN_IF(!prioDatumObj)) { + return NS_ERROR_FAILURE; + } + JSString* encoding = ToJSString(aCx, prioDatum.first); + JS::Rooted<JSString*> rootedEncoding(aCx, encoding); + if (NS_WARN_IF(!JS_DefineProperty(aCx, prioDatumObj, "encoding", + rootedEncoding, JSPROP_ENUMERATE))) { + return NS_ERROR_FAILURE; + } + + JS::Rooted<JSObject*> prioObj(aCx, JS_NewPlainObject(aCx)); + if (NS_WARN_IF(!prioObj)) { + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(!JS_DefineProperty(aCx, prioDatumObj, "prio", prioObj, + JSPROP_ENUMERATE))) { + return NS_ERROR_FAILURE; + } + + JS::Rooted<JSString*> aRootStr(aCx, + ToJSString(aCx, prioDatum.second.first)); + if (NS_WARN_IF(!JS_DefineProperty(aCx, prioObj, "a", aRootStr, + JSPROP_ENUMERATE))) { + return NS_ERROR_FAILURE; + } + JS::Rooted<JSString*> bRootStr(aCx, + ToJSString(aCx, prioDatum.second.second)); + if (NS_WARN_IF(!JS_DefineProperty(aCx, prioObj, "b", bRootStr, + JSPROP_ENUMERATE))) { + return NS_ERROR_FAILURE; + } + + if (NS_WARN_IF(!JS_DefineElement(aCx, prioDataArray, i++, prioDatumObj, + JSPROP_ENUMERATE))) { + return NS_ERROR_FAILURE; + } + } + + aSnapshot.setObject(*prioDataArray); + + return NS_OK; +} + +/** + * Resets all the stored events. This is intended to be only used in tests. + */ +void TelemetryOrigin::ClearOrigins() { + StaticMutexAutoLock lock(gTelemetryOriginMutex); + + if (!gInitDone) { + return; + } + + gMetricToOriginBag->Clear(); +} + +size_t TelemetryOrigin::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { + StaticMutexAutoLock locker(gTelemetryOriginMutex); + size_t n = 0; + + if (!gInitDone) { + return 0; + } + + n += gMetricToOriginBag->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (const auto& origins : gMetricToOriginBag->Values()) { + // The string hashkey and count should both be contained by the hashtable. + n += origins.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + // The string hashkey and ID should both be contained within the hashtable. + n += gOriginToIndexMap->ShallowSizeOfIncludingThis(aMallocSizeOf); + + return n; +} + +size_t TelemetryOrigin::SizeOfPrioDatasPerMetric() { + return gPrioDatasPerMetric; +} diff --git a/toolkit/components/telemetry/core/TelemetryOrigin.h b/toolkit/components/telemetry/core/TelemetryOrigin.h new file mode 100644 index 0000000000..ac8edaf76c --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryOrigin.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryOrigin_h__ +#define TelemetryOrigin_h__ + +#include "TelemetryOriginEnums.h" +#include "mozilla/MemoryReporting.h" +#include "js/TypeDecls.h" +#include "nsError.h" +#include "nsStringFwd.h" + +// This module is internal to Telemetry. It encapsulates Telemetry's +// origin recording and storage logic. It should only be used by +// Telemetry.cpp. These functions should not be used anywhere else. +// For the public interface to Telemetry functionality, see Telemetry.h. + +namespace TelemetryOrigin { + +void InitializeGlobalState(); +void DeInitializeGlobalState(); + +// C++ Recording Endpoint +nsresult RecordOrigin(mozilla::Telemetry::OriginMetricID aId, + const nsACString& aOrigin); + +// JS API Endpoints. +nsresult GetOriginSnapshot(bool aClear, JSContext* aCx, + JS::MutableHandle<JS::Value> aResult); + +nsresult GetEncodedOriginSnapshot(bool aClear, JSContext* aCx, + JS::MutableHandle<JS::Value> aSnapshot); + +// Only to be used for testing. +void ClearOrigins(); + +size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + +// Only to be used for testing. +size_t SizeOfPrioDatasPerMetric(); + +} // namespace TelemetryOrigin + +#endif // TelemetryOrigin_h__ diff --git a/toolkit/components/telemetry/core/TelemetryOriginData.inc b/toolkit/components/telemetry/core/TelemetryOriginData.inc new file mode 100644 index 0000000000..7587e2664e --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryOriginData.inc @@ -0,0 +1,2488 @@ +// dummy origin, this is used to be a counter of page loads. +ORIGIN("PAGELOAD", "PAGELOAD") + +ORIGIN("advertstream.com", "lzPiT1FuoHNMKQ1Hw8AaTi68TokOB24ciFBqmCk62ek=") +ORIGIN("kitaramedia.com", "r+U9PL3uMrjCKe8/T8goY9MHPA+6JckC3R+/1R9TQKA=") +ORIGIN("questionmarket.com", "3KCO/qN+KmApmfH3RaXAmdR65Z/TRfrr6pds7aDKn1c=") +ORIGIN("3lift.com", "33Xrix7c41Jc9q3InjMWHq+yKVoa/u2IB511kr4X+Ro=") +ORIGIN("affine.tv", "wKqJRMTORbe8f4TO5LTGaTlt4yZVKlyomX2Exvfcv6A=") +ORIGIN("longtailvideo.com", "B0tCrpMgOedsm2p2cLJcHrITf/amX5cxB+90PrEpJ8c=") +ORIGIN("undertonenetworks.com", "uaBhUaQhVEMpY9MctAg2QNOszcD6VUq2Gkf44GOp3tA=") +ORIGIN("rhythmnewmedia.com", "PmyV0h3/zQb41TFz6eMHhmHA7KoTtMCSiFWjhQhu22A=") +ORIGIN("admob.com", "LDMime9kpePYvq6Pq12mURD/D8Z2KMKzs09NjrzyPLw=") +ORIGIN("traffichouse.com", "DhgYTwLBhShT2/9n10Uf9eVPSsSK0Zg06CHADRkjDVM=") +ORIGIN("factortg.com", "zS+1e249KvImm0daAdpW1C1HpRvWpbqPPEEI6AWX10Q=") +ORIGIN("quantcount.com", "8Bca3PU4reqBFEDTN15xsGcRQatcIW6hGEJPsH8daRk=") +ORIGIN("appnexus.com", "ZlSg6m2m27lIX92VkVZFHXs1aayLjp8tBoyfwH/11bI=") +ORIGIN("v12data.com", "ZpbW94B+SOgv5mRkNXIbY2MnG5TaK80sScRRIWeMsp4=") +ORIGIN("bid-tag.com", "P4ulhbe1IeRuMjlcZluAXHFYtSz0PaWk76ZJ/pzGjFQ=") +ORIGIN("clickinc.com", "nJ+L3BudR50yb6HuaNUrJoK17P0bvWB+sqZT5KroZdQ=") +ORIGIN("afdads.com", "+vp5Yzs7IM1XOC1VdP1xraGeHfyq7JCoBsMS/gV8CTk=") +ORIGIN("btbuckets.com", "x5JtU/50sZIVMIKmMorAtTdoT6yE1HDfiFUwnKQhhPU=") +ORIGIN("cognitivematch.com", "ziKo8oeJCeyyVU+xVoXjf2jX27VvvlKgadQqeT8WPE4=") +ORIGIN("avalanchers.com", "eGOLSecVm5ksVhQjdJpvUrkQr5Ed0sSkvprXJ7AbRD0=") +ORIGIN("dapper.net", "6CxVmpKEdWaYeBU4qvfbyYLqCRMDwbNcRh10TMsq19U=") +ORIGIN("networldmedia.net", "KRlqDNv+uaCXsDmahrWHQcMadOP6/tMJrT4wzxd7NrY=") +ORIGIN("convertro.com", "PLFRTpSjCz8R1H+0ng6T/qlAA+IrlT1L+sGUfNWlriE=") +ORIGIN("ibsys.com", "y8DIdXX1kcixnIf1T7hXF2d/inOBu4ZGajTJ0K7S0Dw=") +ORIGIN("iaded.com", "5uVkUjmYmPw1UgCxRVliOru7zUT+pe6yTtjkgf55faA=") +ORIGIN("aim.com", "r9KkX7rpREZfKvp+XEqfa42rFIBW8TpkIaXE8C9LI4o=") +ORIGIN("uniqlick.com", "VVLYCKI6MjaEDANBZ5Kbe8C+3LDtijCZ+hEyvFLErOA=") +ORIGIN("sensic.net", "r6wMZ6WDj/xIqRT7yHUGHPnCPOhNG7+YbGCl2Vn/1vQ=") +ORIGIN("valuedopinions.co.uk", "576FreIj3ZYNxiI2UgBZtu332vqG836ytq9hn0060H0=") +ORIGIN("google.com.om", "9b0yq8T61pWJfi+7cmceD8FYfu7QT73RY6Rrorz20xk=") +ORIGIN("adpepper.us", "mGdGac9aZMkM6HGAskyj0feWsNnPC0XqYM+bKawUvjU=") +ORIGIN("intentmedia.com", "aryeFrblEh0unR89MI9Pofexmjw991lN87NKA4qmN4o=") +ORIGIN("zincx.com", "mywmqQRodivUxwsamYCOQCngDiObIwLqMe5JJaKmya8=") +ORIGIN("63squares.com", "3Ac0sF3gOLvJDCRA4/eKxO3usxdXfxi4+K00rdq419E=") +ORIGIN("polarmobile.com", "DzKM4PViM8GcD+JoOP8md/uos5+sHD2/TDpLDCtuHt0=") +ORIGIN("a2dfp.net", "dWzIJxdxMHhHi6nHPRPJgsLdaG95OxpZ7BqVbPOCQ9I=") +ORIGIN("tapit.com", "nZE8+1LCJ5ntd4jnwyuTY7yuINpQBqANH0AWcZtn04Y=") +ORIGIN("cubics.com", "FQeoRzACI6A9sMXqjgpylqHbBdSHVT0QPEPNhU0iE3E=") +ORIGIN("google.co.tz", "OsMBp3zD6/0p0Q/4cyIHTsYEeos9eiWuoewgQ+46C4I=") +ORIGIN("adservice.google.ca", "/sRtEM3sGLNc2uK8+f3ZLTNGC3eJbfW2fq6SFk8LuzQ=") +ORIGIN("buysellads.com", "Fdj9N80yBFsl2a3uWCSMt04TzljANzoEkwWEVJaFmfQ=") +ORIGIN("google.co.th", "PjNhWOpPN9BNVy7Om6huml6EIlOnxT3cbgndLutTWjE=") +ORIGIN("csi-tracking.com", "4ngLaqk4s9nfIlOWCOq7YV76SH2fhernmqCE0rPLT2I=") +ORIGIN("seevolution.com", "0eZ/0CvRNPtGcP10am2XeNOczE7q/YMPaK/r6tRkIMM=") +ORIGIN("adroitinteractive.com", "EEiybYKuHMJ8OHmau6WrImdSKl9o+ZmSGhB+aYBjEas=") +ORIGIN("google.ws", "nqZM17wkSqlm3jP/vkrVFmYkAQLRyTZ+fz8jGOLGXUE=") +ORIGIN("vkontakte.ru", "yYjB8fQKsd2LqcluIbH6qQanpm/yJhypZ2EEhL19xD4=") +ORIGIN("pantherssl.com", "kKbWJnZcLARAXXr+Rw8qfjtvv8qRpYCGXwQ27UfedrU=") +ORIGIN("creative-serving.com", "8gCMYvAF/tSzGEiRQR9sheK/FZtwLrKU8RdOWy/XYig=") +ORIGIN("attributionmodel.com", "KEiccRGg1zPPuN5lX7pAegNhTQTJjnX+sBDk6DdssF0=") +ORIGIN("ero-advertising.com", "C1S/RJBN7+QxjfdM6+2EK2B1iT3iFtIs3UJ/93hbr7I=") +ORIGIN("integralads.com", "jfzyERbKS9ONoU+jeNV9ponS1N8GvUVgpknOiJ4Sw4k=") +ORIGIN("netmng.com", "ejWPm9YwLa2j+3gnQeyFNiLAx+7jMUV75q+M7bo9R6M=") +ORIGIN("rutarget.ru", "6A99vGepWBIGZMkvDL5elkaLY2t1p4irEKf3uyFQZgU=") +ORIGIN("enecto.com", "3EG0vjyyZw87xjR2+sP3PHNBpjU6eg+dI33dBksC2II=") +ORIGIN("mookie1.com", "IOW6Ekz4YgwwrNbHMPoDBtwX13/KI3i6G4/Mofw4E9U=") +ORIGIN("flite.com", "3H1hKHEsZkL7bXrmM4sOaga/6mtazZq+lj4v0g1Z6iA=") +ORIGIN("buzzlogic.com", "uldFf7XvwRvyhbrXAcZwa0DXce/rscSXpmrO2N82iCQ=") +ORIGIN("c3tag.com", "2C9W9JqKPfEJxtxzZs69VFP2q2T6QowSnybqnC/vL5I=") +ORIGIN("campaign-archive1.com", "VIiFN/Y2Hjkvg7eVEdXQjv3/HHu93VJSWDDtWts5sbk=") +ORIGIN("brandcrumb.com", "6UcuY9uzDrOUcj7MjpPsITvcYfyeKiCAsnBM7EU37uk=") +ORIGIN("oberon-media.com", "ZBsO0hpsU7SjPAjbx2fBm4JTnMqHcNRwaRbmqqmgG4g=") +ORIGIN("futureads.com", "FB36V57yJbJRA0XX6tJ1C2Mnr+GI/wvLBIqxuwl7rRI=") +ORIGIN("ratevoice.com", "m1QdeUYXqSYlDLNB0K35pSQUGHBeyVLRl6wLrB0E0Io=") +ORIGIN("jobthread.com", "lt2kIRh5mKuDx8EmkaJQwTi98KQsb9sQ62aqMJb0X84=") +ORIGIN("contextuads.com", "+psLidb/2EmAl76vOjG6hHI/WDwIKpOqE76JXJ4GJJc=") +ORIGIN("exoclick.com", "oqDgdw6g3nqAAXbmtqYFMgBdmzsrvE5VKnCxR5WPkb8=") +ORIGIN("pjatr.com", "KZMDNouN9anyyVSviOzQHpLb8uE6JcMO80hYIiYkNfU=") +ORIGIN("sparkstudios.com", "UzZaGWHSDW9ypbyp5Nki1nxCU6igL02ixm07I3CoaAs=") +ORIGIN("coxdigitalsolutions.com", "HjiDaCtdOqaNcFQZBU2EHiKv6JKSI7W8iMNU1AhmSNE=") +ORIGIN("chango.com", "AB/CeisT06/NbY71sI5TpA01wcY2tRZI4WXSgd9C6vY=") +ORIGIN("sagemetrics.com", "afKUC52VQeoCCqsqLxviIuzPDg68tyG1xQEs37z5MSI=") +ORIGIN("glam.com", "V890DZ6azY2uH46yDmW5pkOfzIqEQ7MD1rbmLz/ZNPE=") +ORIGIN("cnzz.com", "gqvJZLE7HvtBduKYJDVL0aIQjsclVvFHF+3WMPy4IcM=") +ORIGIN("xtendmedia.com", "o1/BlBZfg77yvucPscObiNFiPvyWEX3IZMrsOm0572o=") +ORIGIN("clicktale.com", "ZMHO54G+D7dJky0hTtFzSWTCIdSoqYKHcrNqZiNrn5k=") +ORIGIN("bouncex.com", "jW59n05tCRUMldpoJD2HTElcx4Ass7aSwdtua6amVDU=") +ORIGIN("sitemeter.com", "v9yPP1Aetre4daHcGiu0moYGLGLWod5l3FK7w/0yGaY=") +ORIGIN("rightmedia.com", "IdNcB9TeoTwAKPo1rM/z5YCMBv588CQ3poGh1j57KDM=") +ORIGIN("excitad.com", "+i4qSDCs7C6dnWCH7AQB+RjZa974EmULJVyWQ4yGqZg=") +ORIGIN("keymetric.net", "krK/aZhO4oGdmIG6eHQXddsgoftGO6HMWGpF9w6UKMg=") +ORIGIN("4info.com", "ReB3bdUueaplUpDtdy0EPG+EQtTu2vakjMDhhM0TfWc=") +ORIGIN("yieldmo.com", "hlHpyexEgydpFvFtL6drZa5//r+1Fr5bGIgVA1SbVGo=") +ORIGIN("omnicomgroup.com", "VGCgjSICH4Zn28kGfiGfFQRLAjNJm9iclR3z8D/4tMA=") +ORIGIN("clickfuse.com", "TvqeQ0JokyvASz8f8oXJvIQi7HDMZcAx0h/kGphEqCE=") +ORIGIN("tensquare.com", "NkRkcabAtWIwQbJZFIpV2C0PedHrOCe7hF6C5jLNM6c=") +ORIGIN("syncapse.com", "CaW9comwyn7az8C6Ea56pYuBMeqI+b9LFsrCrDQJjD8=") +ORIGIN("monster.com", "tgCqaFrYaLaxkVSpBEPY/aoVrnFjDb99X8zzHdy/MOE=") +ORIGIN("adf.ly", "2vijsLchTBjilW7zkxIt0J/dFJuy+H4ClGNkVNcdIAk=") +ORIGIN("observerapp.com", "r6qaWoGn1fMVvXSUrgPmejC5SAlazITfy0dH4kWWQpc=") +ORIGIN("precisionclick.com", "vAD+oMzJ41OR5731JmpBL56EzQmhJrZLmKe2R6MCPao=") +ORIGIN("google.es", "vtJn5KltjScFTJ99FPIgf9eTZHWF6KgULdhVMm4xMK8=") +ORIGIN("themig.com", "B+fIakNg0qxnR1T8MNQOg2+POyRDoEYHajXco/59fnw=") +ORIGIN("maps.google.com", "C1mkefBpsxsfN65HxyBO5h3ccSqu9NVHHwXupY++CJU=") +ORIGIN("tellapt.com", "OR8bdbSWvenf92OCJ7UAHEUhm7tXMBAHJxLTDkXp7JM=") +ORIGIN("fluct.jp", "UQk5cT1zgIey7FamHqkO/bSsw8+hujTNbJumjN2HlYc=") +ORIGIN("google.ee", "BIjZakI3ChrLy6S7OTUampyXO+WUJFp4dYEfvXDQTtc=") +ORIGIN("branica.com", "FToe9BNJgjRJtRbW90WedqLog4lTu2nBzArWyL3QlJI=") +ORIGIN("mediashakers.com", "COlBtDziXmW11S8sXXP/4lc5nHpd9KRGpDPvw8dWASQ=") +ORIGIN("targetingmarketplace.com", "65SSNzFz4JI9NXzRlTHSvHqYrHH0iNISX9OeM2SrWOQ=") +ORIGIN("vemba.com", "BpIyozDkb8KupVJVWm4KijPYelKqlRAIdJ0f8Fb9+SM=") +ORIGIN("whiteops.com", "gZ5i5o3SPjOPSa81ICS4is2PksHCemz1Wmey4bOcKNQ=") +ORIGIN("akamai.com", "6T1MRyXgMODdmg2HLpWNQ9AOGS5W+0GKYlFpPx7lmDM=") +ORIGIN("isocket.com", "mE9TvU6T+Erh+89tD93xCNeResD6eDUgahW2LJWl5pI=") +ORIGIN("cetrk.com", "/Gkfd+5K41Wug5HPrDkXaHulsW0F7TwgpXx40IUOcA0=") +ORIGIN("clustrmaps.com", "N1vrG+vOj/28ik/2cXMnYaSSLRs8LHBE+ZeUOUQC9Xg=") +ORIGIN("yume.com", "SmoI3OtvYtvm2JTJGN/Bu4cQSuL9fC5/AFUs4VvC0O4=") +ORIGIN("anormal-media.de", "uqMvjGmAIyWQ4VNDv0BruRgUyMJC+/E3Nd9ZxQrLfB0=") +ORIGIN("gocampaignlive.com", "I1iOtQMGOjWh88D8YIYbdUEQtxmlT1e/gipFqt1iSIw=") +ORIGIN("tlvmedia.com", "FyIc54NMi40Y1qPduUURK0oVR95jZsB9fH6CTOleqHA=") +ORIGIN("clearsightinteractive.com", "p8yKdWMurgxes7oEVd9rsqJYJM+8qKNRqcrGzROnjVM=") +ORIGIN("eyewonder.com", "GLt5nHEaCPNA+UKNZJl0QlcGytkQBxguWjw51VgD1Nw=") +ORIGIN("zaful.com", "6SIh7TMO5PxtODq2nWR3FljrJu1x4PAQGxbVcGVPjAo=") +ORIGIN("health.google.com", "YMXGsyp800IvpySasfnGKuxPJ9Z1ZDLoc2VBayNakcs=") +ORIGIN("zaparena.com", "0G4gryDrvXxJ7BhebTFpOLvqoLhYyW5Zp7TzHqNaIlM=") +ORIGIN("adtiger.de", "2ZOmahXP4x/soUCHBVapGRE5TfMSib0XyKv81PVU/yQ=") +ORIGIN("homesessive.com", "xZu4N7c8jTy+2+m5P6jzxWIlntRk3QHMg748CE2gS7Y=") +ORIGIN("adsummos.com", "ucbwCCsY6D/DWb2CQm8O22+jP1uqc8SMkXJc3qFkXpU=") +ORIGIN("apnewsregistry.com", "HTR3YiuYgtJ+vjUyH/6gLSy+3DfH0dXqHlNGupq0Wuw=") +ORIGIN("collective.com", "+NMdHgQwS+wfHHeo8gZQmTphUPJuj2vAxwilL8Uc/6o=") +ORIGIN("adnxs.com", "7nX5mLXDcidEaboQwV1VJyYzNCBTtkA81iuECMXY5Fo=") +ORIGIN("google.co.cr", "RKmmbs4vJGvqaXg6S3aF3fjvtOfh0I8c8NuqmtM4r5k=") +ORIGIN("mashlogic.com", "bT6rtBUhcgNQzE28jxPiKLxQYD5nJ7KcSxY+n2LMqag=") +ORIGIN("optim.al", "NwOUOqcy0tsYdaCPGumud/1R17u7vgGdMroneAyOLkw=") +ORIGIN("visitorville.com", "MQ6A5Y8R6v4tH4rbMUp740c+fll/CqW7XHesKHNAKHo=") +ORIGIN("adaptiveads.com", "kZxDtr9Iu9860NIni3vwiTSWCrx9O+Q1l6JvNzaGQmk=") +ORIGIN("resolutionmedia.com", "8TSL9+24iYsHqJWYpakAMZnduMlZcYSDbItU8vTi4TA=") +ORIGIN("adgrx.com", "qJKRlvyzSSIjYGsF+C/gWZ9mg2qPT6kp5EHx1X6JrSw=") +ORIGIN("netflame.cc", "+MOYz0h/b0UMcFybuDwhyfIuZUYRCPnM5L38/UpS+zE=") +ORIGIN("amung.us", "hFwKFPFDX2/9FJX1uDlbHm4Fe1D3yZmbWY1xmli4C3c=") +ORIGIN("adclickmedia.com", "nbOsjSMTpJGDwIe/4/ECERHCj9YOfV6JSNqcoXy/sUs=") +ORIGIN("merchenta.com", "g+NZrL8wMAL9aqoxz+fMREJmbKAHSmuTflB8ePdQmlE=") +ORIGIN("techsolutions.com.tw", "ADdun7GzDX48lmc5NIiLgmbM6+Kj9lyGfgXgc1vvc4k=") +ORIGIN("spacechimpmedia.com", "6HaL2hYUffSktL9oV+3ivRmRNMqtX4LOkoPxlg7jnrU=") +ORIGIN("adleave.com", "FIwRZheNEmwHiPX9o2W3z9sicsmOWoa9Dvm5TmnKTUA=") +ORIGIN("adloox.com", "YwePt91Q8kvuUGlIl3OftlE/PdZnQ8KWF2ioAKeVRp0=") +ORIGIN("wsod.com", "Wbds8Pry37Rzid6n/Ae9sQSA3NJ/2QYmhJeYA5IuQTk=") +ORIGIN("exitjunction.com", "cdHutr9A+uhif+V3a4zU7LodKQ7iJqyFP1zAZChCcjs=") +ORIGIN("burstmedia.com", "xCXgwrmHIA24wa2uImPfFo03XtjeI6BSm77UM7LbQtA=") +ORIGIN("qksz.com", "nv1dgCC+C7XXzOSfrUh0L/F7vO3DqAImxAY8qcWr4iI=") +ORIGIN("medianet.com", "mFJR5mBMKGXEFe7VT4gpyBGMlSfGemSkj5ZPSknxFi0=") +ORIGIN("meetic-partners.com", "ThgIEpaFJSbHTTc6BiY+Ql5R/wy8pQo4prKs8FCS4IE=") +ORIGIN("adservice.google.com", "ORbzeOvsHcz/DEuwyZXenmorqa79AoIdfjEXXKIxKIY=") +ORIGIN("aweber.com", "yS05ryjdIMp62z//tNjawBngAYVXAzukDN+dgqUdGsI=") +ORIGIN("plusone.google.com", "wuwBibFAMty+38JOtk6FSMny3KVthMu4qneskVIlHsg=") +ORIGIN("appenda.com", "aL1Yt4LK9rAL7jYQi30tSQjJBKYs6C1zZXuAV5J/c8U=") +ORIGIN("dotomi.com", "QI/FeeR3ClhaV5p9a7t+N1FwXNY4XU19AKSvO4CsSnA=") +ORIGIN("google.be", "ninu4S6jmuJLUzNcVcaQNegSM3CcHnyEVWrJP/2l+Ts=") +ORIGIN("mythingsmedia.com", "KUuS6yDDFrmUb76MzPb4aXO8vlFkyFrxkmyBnpAmTIA=") +ORIGIN("convertglobal.com", "2lDkaFtMu4slWg82xvdmqrZ7Kde8vB4VR/NhgDQ7NPk=") +ORIGIN("thesearchagency.com", "6u/xtg4AUhAUjfs2Y55TLePN8ML0GtSgEgxqrO/QWKs=") +ORIGIN("inadco.com", "DWrLv7cj04P7JgFPALFLDeJEYQNRrh9fXOcCAhSwkEw=") +ORIGIN("rekko.com", "B3enJR58B0tg0RnROi02A2zPTt++gETMUHQrunZ4LP8=") +ORIGIN("info.yahoo.com", "Bo0Ips4yD7Mo9paKI9ZmjM1QNZfZXJu1IiCdJZjr3vo=") +ORIGIN("cptgt.com", "gayCCzQq9VTbfda3XNTVKvsTtZ70aMxJjRjmz2WIkcA=") +ORIGIN("durasite.net", "jhwXAQ9zduqoyBWZUQ97VzQz7kdqdosQOSINMJ3OSlA=") +ORIGIN("acceleratorusa.com", "s818ajKTF+HJkSUa+A+9NiiNUFC1nh2yoX5b1y9VuKA=") +ORIGIN("instantservice.com", "iR6sIYVjMYoMl0LrX1craxzR5kwXAeXCG5qnrqm5w5s=") +ORIGIN("google.com.lb", "QRqWnfgnKv7q1BHLbASqAmV+GYAbO1a2IkR5GIy8yV4=") +ORIGIN("sociomantic.com", "yiruXvD5/RdaeA4L4qiVye5KK+dS/sJdCsArvnc2ek0=") +ORIGIN("oridian.com", "gucHgioh6QQn0YvWjcsAqjz2wcPGqJOwlE6HqY4eTqs=") +ORIGIN("delivr.com", "gMMld57FUKXhJFE7aOBDJuXu1FeE7+Ol3710ErsSgQY=") +ORIGIN("video.google.com", "W5Hu3W8FRu+TXUGv3+JGZixp+VYu+Mc88jxi1zlSioM=") +ORIGIN("targetix.net", "II0h7dl15K4NjrYoYtwIystgZoCtiYnDKLanFv9NK/o=") +ORIGIN("edit.yahoo.com", "SFsNdsCUL2ddjCOFIsqZgO0AEemrig4oLZYQ4b60qQ4=") +ORIGIN("abmr.net", "vyzHmI2ZXlyORjLWyWe5Nu5A18ps8WAHua/k9orgxSk=") +ORIGIN("getglue.com", "XrKgzCMU+SIsxn559X4/oXzm29RtdXKUjaJmF3oA87k=") +ORIGIN("w3roi.com", "slv3XYfhEYMozv2NjRwqQttYj/704l8ZOWEy1ea/ZfY=") +ORIGIN("google.co.ck", "wtAUUILgVGy7bNn4bBuRS+Sl2aQ8TkfaQAbDtuco89s=") +ORIGIN("adforgeinc.com", "LC90sUPTTfQsEZeYknzPpaGZbFm+W/fM7VZub3nOmaA=") +ORIGIN("jink.de", "NkYIIACtTFZyM7SYh+KixyVRi9yUaw/XS3eaX92OwwU=") +ORIGIN("newstogram.com", "wuvbG2khEdIyTMTlY8o0CeMJwTVRGHPknszbyIPViAE=") +ORIGIN("cedexis.net", "JUrkmXu81hrfXEPf/+Xo6iQuZYCGrUy5ai1WDVbwqhE=") +ORIGIN("yceml.net", "HkTU8NGjJrTniwO73t2KkVXP/tU/Xn8zAPxEmohPx14=") +ORIGIN("dianomi.com", "8XmsGH9qbQlCnsBAA9qnuQ/SYugR/0eOoUWc5UTNzc0=") +ORIGIN("dynadmic.com", "jZ3Vbcb0eV8vIFo6vhCJpMEG8spAvAEQGECEhGK+HOE=") +ORIGIN("traffichaus.com", "rnw1MC2i4AkbvQ2/cy5Kk6jR0rUX01ADVmmRyDpKWik=") +ORIGIN("gtopstats.com", "9ktW+Ikq9Z8KQ5EH83jX7GRim9/JS03IYHiBM1Y77Bo=") +ORIGIN("dwin1.com", "IibMkEU+23MPkOzcsystn0F63nIaDyv8PFOlLwz8Lqs=") +ORIGIN("oggifinogi.com", "mBAJHuVCnpQSrcwwVK5iqtHVO0wkEEEnPbWHl3XotsA=") +ORIGIN("adviva.co.uk", "WGv/0dK/pV5I6eaUqtwHv3P+uhiKuLtzQPphA1J3qJs=") +ORIGIN("silverpop.com", "+OotVnE7ElacHZxRR05KRcWvrCJ8q4t7jqph1qUK0T8=") +ORIGIN("google.vg", "ftG7d3FYjsBSdhGyNBFrziNEVr6uAyXbuWesfDnHNgQ=") +ORIGIN("yieldlab.de", "mrIdzNV/L8m7QVYzwBqClrkNrr+lXPfWizMoL4i7tcU=") +ORIGIN("exponential.com", "V8s4EPGzy33Nkcx1LOr2qg+XMDNrp/bTEmPFzYX/iDY=") +ORIGIN("tweetdeck.com", "dTioGUQ9RAZ5oJTeK2YoezH28GBqslprT398ADJsteQ=") +ORIGIN("interpolls.com", "vAFbOgHR7xQDDz1p+O38bc11ecge/fEwA1Ew1B+U/Zk=") +ORIGIN("eproof.com", "DfB6Llyile5CZNjpWqog84WzrhnWpQs9aGfnh1knJDk=") +ORIGIN("powerlinks.com", "1f2+BYGoCr8D7Gk3ZDhxuGpAxqZFc+3gWTNE7P2QQGM=") +ORIGIN("tidaltv.com", "1H1Au8eOuQS6Rmxse51Uyn9WIosmoTkCIoj07vpwXAg=") +ORIGIN("investor.google.com", "kUUYwhw+1LLCGgW2YkZcaSNrkNp07rF549uTxVWZTeA=") +ORIGIN("propellerads.com", "jItVlgf2s4ZvcRg6g+wXvRPvmrgi+P+C9K0pVy84fzM=") +ORIGIN("exosrv.com", "z74xyytEZKRMU+v2qgfOBP2lvJVAfeI9dTDZwjKB62k=") +ORIGIN("networkedblogs.com", "exGHPpgFgmutZ6vZK3GMn7yI4mjXMfqHzvBG2OHDSm0=") +ORIGIN("mojiva.com", "Sr9+fmbj35lZpWx5vxhB7mls5UhuBk6GjvcAS1Q9rpo=") +ORIGIN("orkut.com", "fJBdx7C+mncegbGgoX03EMXrKSbFgBlPoho2LajAnW4=") +ORIGIN("wordstream.com", "Z74VG2rzUNqnYEKru/0KdVwxAyZy5wkajsdhdXgQ748=") +ORIGIN("adimg.net", "s/tzyGdSeKO0uAfeLVS3gMi/ssyXMBUtCG9fRB7y8Pk=") +ORIGIN("kantarmedia.com", "oPcp2qlgSxdI07s0UtjKTvpNUTcxih3hI4wv5wjlqi0=") +ORIGIN("ib-ibi.com", "NyQ50hZGemE9pNd0mXHTItjHe6/MaqaDoJv27wFYRE0=") +ORIGIN("specificmedia.com", "ZDTdQnS2i5Gc0Vs9epWe3h0/nS+ues0Vb92vQDA1aS4=") +ORIGIN("eyeota.net", "Qx+Le3QORoglsURbdYVKBKwsQJatAZUXmKmS6wMp/OA=") +ORIGIN("compete.com", "Uy2ILN0FIX96fyZVQy2yoSxAITZQBEs508g9ZpDt5w8=") +ORIGIN("company-target.com", "KjV2twYHLQMs+YfZzTPDpZG2L00z+Sq9/54PhwHJBek=") +ORIGIN("buzzfed.com", "SkWhkKsjZmgxk8TXa/saoVg5Y+M+jiRXwrhZ6tLSX+o=") +ORIGIN("directresponsegroup.com", "cmQaWu2FqCnmDOOlQ0FxACJ+tCoxHQ1VoZCzT1wgyBM=") +ORIGIN("matomy.com", "QJnmIkFNiWDJ0rUeYHWyNtzeSEQHIb5vTkvQR6HgZZY=") +ORIGIN("wahoha.com", "0S97413t9NSABQJyiAMQuEIujWeJtrVMVazqPTttTsc=") +ORIGIN("syndigonetworks.com", "AFlvi8VWr4x9QjHPMWvRENn8uYbynhERb1Omi895JZI=") +ORIGIN("nxtck.com", "t9ee8RI0Zxx43hY7ANMSBKuhxv6itI3efl6K8J3KUl4=") +ORIGIN("cardlytics.com", "KrVgrlR76dF1CT3WUiPrMNdahbbd7/LHl2JtGr/wAjw=") +ORIGIN("addgloo.com", "7lPH/t6gzZWmDJhjvE3IfkS2LxdzOLcZswNq9rtkh24=") +ORIGIN("travoramedia.com", "6ITWPFThdCQ03ZyrVCg7Ssx2jJWlsw55fLppDROxk3I=") +ORIGIN("qnsr.com", "6sgOt0L6+8i5QUa37sMGv5PwIgs/jya7ecw3ekYny9E=") +ORIGIN("baynote.net", "E88yOcJMaDDrW5NDvsPi23zvKxXiWEDkrbIvqrdBCWM=") +ORIGIN("inflectionpointmedia.com", "+hsR2GJaVU8MvB8zEwopimEvhsk5OH2CX4mNxYIcYxU=") +ORIGIN("assoc-amazon.com", "aWX8MjAVx8cfGADC4OL59TOxMNUEQfOXMkFR3quDBu0=") +ORIGIN("msn.com", "AAykEU507YSZKkhPNYjPKqbsFiCmHFVNteU3PLal2Ac=") +ORIGIN("videos.google.com", "nxXfqLB7LP1zVYA9J4I2xxSr4tQ3jZJTBKUSn/k5V4w=") +ORIGIN("responsys.com", "/6MhwRDCWiqA7+YsO0EdFEZj90o+oZKXRYGafYoWNjE=") +ORIGIN("google.no", "8xD4clQg8QKeANlArT7b7VeGj9ZeN47PoOjMwoTwGPc=") +ORIGIN("aggregateknowledge.com", "YoU8LOeWsCyDIuymFhN4SfPyWqNZMLooB4e0avXX39c=") +ORIGIN("vibrantmedia.com", "xE/aFEPfW6nY7HWw5s61xuMc89heSgqK6KexaLsI9zU=") +ORIGIN("moolah-media.com", "GDvnfDahH1vKo03GrYeyTlmZpW4JteHCx7wq1yHXYzU=") +ORIGIN("bidsystem.com", "Zg2f7+Jpj1kL7mkBWUtEoDnM/OoyjiBLfTR/JnpUsz0=") +ORIGIN("2mdn.net", "Xm63aZSDF1ukZYkajq/x3lUgU5C1xt4RyVhOt7cnvDo=") +ORIGIN("netmining.com", "b5iS89MEV6o6OMSmkhkj3L9dbBzljosVl/hWXWnESoc=") +ORIGIN("fmpub.net", "DUS9whCJEJOacADOoeklwJF2ROAcnjem9IikEyxENbs=") +ORIGIN("behavioralengine.com", "EeJ9yRv28ofJwAyEs6Z3U/eraZkKMGiHDkPiCgivLv0=") +ORIGIN("certifica.com", "Ivfe7hBpzf1CjTPMqc1N2Rt5qex8JdugReZ+2bLVIKA=") +ORIGIN("monoloop.com", "WgXQelAlaDRilUq8Ktz5BviYmxtPoiSQ/JWB8dJ7Uc4=") +ORIGIN("google.dz", "EAGO3NhlV4jHzTEVauNpdFp/+HsGuRlyughsh/vZr50=") +ORIGIN("free-pagerank.com", "hBe8ExTpOAQsH5KG2sW7WgVdIWMJRnSIGUiV/v/h7dM=") +ORIGIN("cj.com", "qAxk1Az4tihti7K4v79PUklG6u4/rG1kNZQ0KBMnwuY=") +ORIGIN("buzzparadise.com", "L7qE98Fc3M50Tg6pVA2COkGf5QHIYgd3cleALOuNwcs=") +ORIGIN("googleartproject.com", "yw3kZEgeAJBX9gqaQ1fac+cRWJApJ9pBAuhAARyoevc=") +ORIGIN("google.dm", "cjlWsTc4OZ84VGdTfTUhpT0+0KmtInDgI82DsABBhnw=") +ORIGIN("wiredminds.de", "PO/EhzizxyioCXnZBs/TWAWhHfkiECldcEG+2vbz4TA=") +ORIGIN("acuityplatform.com", "avZn0aQTb0iugZL7/gE6RtUhZn013qCcANRW2PPq6/k=") +ORIGIN("google.dj", "o+6bIEWA35eIfxii6xWr5nc4BI8T8xvMRQR743o75rI=") +ORIGIN("prismapp.io", "7s0BLFOeKLJx0CpCJXujTjzTHOHORwIuEQbX8s937aY=") +ORIGIN("google.de", "el+7VCp9VgG2y446iq39UVIh2HdbH2A5X7Kn4RF34FE=") +ORIGIN("halogennetwork.com", "DkHBwJFE7sVymwJXKi2H+2irLAzqq44TGCL/Js5RYkw=") +ORIGIN("eyeconomy.com", "z903DSOw0OaUHh/ui85Rktxqgv27lUdquSV8BSgaZOk=") +ORIGIN("yieldoptimizer.com", "e8GWz2rRGg2M9eqyo5L69djEraOg0nCaxzMYEi8CYgo=") +ORIGIN("cmmeglobal.com", "R902VTDO+CRI08Q5JtGDvk/7Ap/4HLwVV/BZMR45N6s=") +ORIGIN("eyeconomy.co.uk", "iN2kM+YSYmT5kLq3lubVlyUjueLpp8r0LKbH3zuZdn8=") +ORIGIN("adbrite.com", "Dv79wiOaEEoCyci4y9DlIKtqdKP//LS0pjgWYPz5uAE=") +ORIGIN("spinner.com", "y5uXjYshCekA8SeyRtaFY1NJ+KbmQPiYh0cHa+m7PMY=") +ORIGIN("mailchimp.com", "MevRnR2fKlEzfoAUdYC7Bxq55gNheFNpPfu55s4UnE8=") +ORIGIN("beaconads.com", "5n06TfOg55vixWVdtp+J9VSevdh/idDPwk3IoC1AOMg=") +ORIGIN("adspirit.net", "66dCq7cjFR+91+ufbJ74Vcmr8KdhSquAA9P32QxxVqk=") +ORIGIN("relevad.com", "XDJVLdrzlAKLE5rfA1UGJeWJKnJo8/Tu15XUreUQmYg=") +ORIGIN("goldspotmedia.com", "ZiroH7RovDl3+pG+qtlOJ6n6pVIqBsl9wKd2Tbv3Arc=") +ORIGIN("cyberplex.com", "N69Pbmc4mAXqaqyIszUqYAshGBgBHoRxeDmiU2SzzN4=") +ORIGIN("moikrug.ru", "og7wP8NQyhdf4sDARDrMKj5uVHXxbdofbjxVoKYQcHE=") +ORIGIN("communicatorcorp.com", "+TD3trr49nslzAi+I1g0MSRYkVJDBJvG13qTCQxWMPs=") +ORIGIN("vk.com", "eabQm5mUr2rpvbyT5RIdK1uOTPvowVajgQ1P7YsZWiw=") +ORIGIN("adinterax.com", "xVL30k9nbAb/vpCK58PcU9uFUKEvvP2lv8tesu3Tcmw=") +ORIGIN("inuvo.com", "+3lUokO7m3bIwK27shMFHRSkYtO+bVniQBaBVkGcI1k=") +ORIGIN("horyzon-media.com", "gciBQnT+9UwUuU0ulv4s8QPmRgpSsU9GcGtb66nXlCQ=") +ORIGIN("unrulymedia.com", "nhVjDUXKrsSB0kYHdlqhbCYLXa9NU7yHjgc5EqErJTQ=") +ORIGIN("optinmonster.com", "kKv7PNjOfP5x8LsPgejqbc8VOUY0/foEf++cxx3Oub0=") +ORIGIN("webads.co.uk", "xwQQRiuCyl+vnN9JSsVL+/PmLlvT4Yw7x1udTC44j1Q=") +ORIGIN("pswec.com", "7ai7FE6WnxFcMN7FBOSChq2L8UcvRO1OHeEXpXJhwXc=") +ORIGIN("pontiflex.com", "hpJheNxFkI7SEMpl3J3KO5m5jAVJTf7fOhC7uRurxPc=") +ORIGIN("pntra.com", "T3nk2SfwvX7V+rdlpmZ7/nRP/AAR/KydLdMZ7tfJ4rI=") +ORIGIN("struq.com", "yvPLa2Z8nHyS6XoNHDWtUqkAER/M1gY1i4DjPDWH1p0=") +ORIGIN("begun.ru", "ZnjupIH4YpDpokN0YElocIRDE9g741ydO4kXGPhitOI=") +ORIGIN("unbounce.com", "xbu1EDLTG3nusOA85Y94fpKvPU8HBjLN7ZvqoCeeVf8=") +ORIGIN("insightexpressai.com", "Z95DPWMqBITUVD1WeAFDcUjWdLRrUqXudOtJ2pKKAcw=") +ORIGIN("admanager-xertive.com", "x95d5OHmhJdNzg1mgFfSwzmkgLcLSbO9HfGqkAiHOdo=") +ORIGIN("facebook.fr", "UsBY6V/I0OUbud1LcvE2SqRxFXR1qENdqnHo4clTNhU=") +ORIGIN("google.nu", "Hds0/Ouo5rzSVcBhyjsGrDtUXO7UOfyuzfkoJhYWpy0=") +ORIGIN("valuead.com", "stAQXq+jfAZzfKFwUl5buXFLG8/A6mqPKsGHzaeHN+4=") +ORIGIN("rundsp.com", "+GmB4+M4A7eAYiJYhKmaqBOwREu6GUfNHMJY0qyxi9Y=") +ORIGIN("pictela.com", "Ki4//aXH7ZpjYC8Sw8fn8Ylu2mWgqlDgkiHVHIyFrCQ=") +ORIGIN("google.co.ke", "3tutpCCz7658D7vFuXCN0+2usW/BVBj7CyF83PdbJBk=") +ORIGIN("intergi.com", "ltPbxjny1bWPIR+DQicULsv2itmzBGj2dnK16qNKfGs=") +ORIGIN("adgainersolutions.com", "Mm93fXFyrBh7wNtPYPV+kPCVrg9eppbByTKau95sA50=") +ORIGIN("atoomic.com", "lDAUC8EjBglx/2Wnz4PFW4FbM1BumUj8VVf7l1yclPM=") +ORIGIN("netaffiliation.com", "zdZBW5aRNh+Y32U0DArUeUopXCOjKnwfFX+Tq5PYsTY=") +ORIGIN("google.co.kr", "QPYvbD3Gl58ABVG11qbzBIuL5HvKe26KBOg1dbZL6j0=") +ORIGIN("tradetracker.net", "W8T4XUVer+7ItOZKRfZR3n5wE4P8jdaRO3n6egZfWsQ=") +ORIGIN("pop6.com", "EG6wPtvfCk9sAg7Sm1K+8mihKPwFuJ01FFzReqmR/4A=") +ORIGIN("blogads.com", "ZTZghw5egl++jCUHL3u6zE/kUti26YyLAo9DtdIEeEg=") +ORIGIN("extend.tv", "cVjW2sDgOVZ5aX8Pj0E2JZpetO6ewTaKqBvhaRHsyEI=") +ORIGIN("multiplestreammktg.com", "0mkF1IPDx98sNSitZxc0mnSFxlFfKush1tknM09pcmk=") +ORIGIN("adglare.net", "4bNqF9W9vvGSx9pbm9MCdxFfNoMWDkBNZhIEE0SOHL4=") +ORIGIN("investingchannel.com", "JlMfJ3cBmwIxFLDxJ5wzD/oKqOGqCO6fQEvIKLdF9sQ=") +ORIGIN("adsafemedia.com", "nvKTY1o7j9ul1XcE4WHV5X/tIkDEv1LozrOIZR2fziQ=") +ORIGIN("dialogmgr.com", "f9Crde46FORcW096gWHgUK38PqcMMhwRKOHXfsEsitM=") +ORIGIN("flattr.com", "2iy4wCeDgx98ElRMy0z4+jviMfVQbRd+ozNlDuasV3I=") +ORIGIN("blogcounter.de", "CFVHm6gYiqt1pZ/t5lKBHrLh3ZbLfv3hEq4tjZUdTW0=") +ORIGIN("reinvigorate.net", "wobQXpJBUZZIUVZ46+mnQpN2IF/cL0lt2oIym2UrO0g=") +ORIGIN("rim.com", "qRd7xU3/Anp4mFlQ60ov7q4UJDw6LC3mkZ32KWBBeHQ=") +ORIGIN("mcafee.com", "CuD3bBd0aQwXcGbYFMAD0hYuKDs6wlQM4N3C2kIScWg=") +ORIGIN("provers.pro", "V9TfRjTyEdRmxTH9MY1bO2tF6peO6GkWqAq5aWDL2/g=") +ORIGIN("enginenetwork.com", "byKK4qD0uHBh4qBOTQK2MIY4fOsg2tML1A5BT2BpMQo=") +ORIGIN("blvdstatus.com", "1xo1IDqEoVsV3mx2fBsG2wcY0u9syb4VH/3ok/Pe4/A=") +ORIGIN("viewablemedia.net", "koXY7NosiOoUAx5Z/+tn5/Fp/8M90/9ZzaBTnUVG+cs=") +ORIGIN("admailtiser.com", "s3TK5N5rafzoU3ZI7pYvF/VyyjxPvmQRXiFdgKA2Iqs=") +ORIGIN("microsoftstore.com", "AVah/TypPj6DtouZcrgFSW4rvq3tj9x2BCA6TNh++b4=") +ORIGIN("ohana-media.com", "LS1Z2FSrt7tZlCpcutN4r9IYc4XoMerz3x1ClMuEK0w=") +ORIGIN("moatads.com", "+Iry112ma4fPUyBnC9m5VRHJDm7/CLl+iZPxazw1hpQ=") +ORIGIN("perimeterx.net", "+sWqJO2JjmRTblobsTYvLKE0e8CYOoiLx83vnzHnlyo=") +ORIGIN("optify.net", "D7rlergC8Eq+7C/J4Or2JbYSVaHTUdIjncUIyh1+coM=") +ORIGIN("adyoulike.com", "KbKDmYoOFvjcVOAClaC403siqt7N5faogZzi9UJcfgE=") +ORIGIN("dsmmadvantage.com", "Sjw1F37vTEevqCoDntjYojavYr8Q3Ka/uN2qFpdcbSE=") +ORIGIN("accounts.google.com", "R5E6a1Ye1oBysmYUL4oSh4XF508Epehc5ERLpA1LXoY=") +ORIGIN("4dsply.com", "SVAL/V7uBm7x9aBvuZ7nnho6a+bYeI4Krr3JcyibgaY=") +ORIGIN("adv-adserver.com", "1yy7SGQ0VQEdrVeYBvKq5DH7OSakzAPYfoFPl7czDzU=") +ORIGIN("buzzster.com", "WyNdsSbzsCgAkegzDubqtwsEXMs7Ww0S6ukaftaQwic=") +ORIGIN("xgraph.net", "Wsb7ERuF7SYHos2li9GUKcKJFW9JqVAr6q0K1JKJLA8=") +ORIGIN("destinationurl.com", "uAosIftniKbWBC7pBqxuMSd+Q6dimVCwsZVOJ4f009s=") +ORIGIN("buy.at", "4x0LqPhjvnxYz+UJ0EJcl9gVAK7QCfso1Wf3ybuE1Rs=") +ORIGIN("engago.com", "kt4mrpbteg8SsGoxtPnDo5YE26UKOZcEx4iOmdZzAQo=") +ORIGIN("atgsvcs.com", "9WzcUrv3jGBodbxGmBmMox+gRcmkgrm3B+2zw0e6FBg=") +ORIGIN("proclivitymedia.com", "IlzxSST84DmUrSRzFfylznj7Zy+eQDQ5ZrkfX2zaypI=") +ORIGIN("brainient.com", "0lt5A7Arcy0SDog3V5njA34wm/9LSP5rgVBPCQQTt3M=") +ORIGIN("augme.com", "v4Zp+LUJ9n086cejquiujxpaBlXBWZzr0lTWwnrCaEM=") +ORIGIN("foxnetworks.com", "IsyCxhA8Z09c5WUVFmj+RZyLEO6fbg91ls6g3ovbB1w=") +ORIGIN("adnium.com", "uMm+kgNfG5714SAuQSXb75KjmgHbNfOXYvHaePhDs9w=") +ORIGIN("adcentriconline.com", "LRSXo7qFBuUbdfvFl3JMJSxjCuRe0DPMXLCGRKpClwo=") +ORIGIN("netelixir.com", "i/9qDtIY0LamfIEaqFMlFurdRIxDMphsf0DX5+m29PM=") +ORIGIN("gleam.io", "vRTtGfK8nMsnLE55cGrevtSIXHlCw7NIF6ZTebtzDQc=") +ORIGIN("onm.de", "BOzSdx8RrZlfqbQ5GewdnPD13kqracvKJRi780xCCWU=") +ORIGIN("web-stat.com", "u7g7cP/v1DsAK9gu0KIoh3clDyKTRiZQV13CyQNaAH0=") +ORIGIN("emediate.dk", "218QtJHWdJTXg5S2dJr14DbLCBpwLypOoIcDDc+Vkts=") +ORIGIN("skype.com", "aSGYlwD3gGX9Scr7j6zHN4J0EFLytHWwTE/OKxI4KN4=") +ORIGIN("perfectmarket.com", "oDwujqJLNDAFlm2ljeDJlCq+i5NZvRI3Ivt7LJezSJ8=") +ORIGIN("sophus3.co.uk", "3W1GzUfGlXJ1UqTJK60ebEOCpOggGOmanq2SQDDxAKQ=") +ORIGIN("gstatic.com", "KRWIs5dvl2QBQEMKrOW3v6bUoxS/D18rdMhSyVYrV5I=") +ORIGIN("yieldmanager.net", "v3LILto7TnCshaNBmZe8nEUNZbvqMyvg1SOLax4pJao=") +ORIGIN("innity.com", "s9Y6xPilUh4YNNX00mvpt1uY1I533Lj6Bhyb8VPi8NU=") +ORIGIN("yhmg.com", "o9lC6a5JrzCYUydLL17OUqh/Own8I1LHIzjLMjC2LCs=") +ORIGIN("afy11.net", "XApDUUARayrOd61+kq0X3kcHouV+iVULEvyyEwym6Rk=") +ORIGIN("medley.com", "bszpZRxlyxRErBqIVuet2fOZm2WRMEEhO1Ci24uEgu0=") +ORIGIN("applovin.com", "tmol56cuQy/BaV2sWpJ3KSYnU82mTorX2IYSZrcJDOg=") +ORIGIN("encrypted.google.com", "vRyMkoWxQgeRIeTey/H2OLV21i0W+AlkNvX7mfWW+ZQ=") +ORIGIN("sesamestats.com", "bzLMMh/MQKm/RwEmhJci1Qg6qiGkHbja7KtoaztPT6A=") +ORIGIN("adinsight.com", "7U1PAwhZ3Ks6OBKtynT9nZ8d/P6T6i42Gt70nsixhpI=") +ORIGIN("yieldlab.net", "9Xs/zaEYwC0BhPETkE3IiBP++l2T+AHezBdXxiGNG/A=") +ORIGIN("platform-one.co.jp", "H0nUTRgQ1aeCE4i3EyfYRU9bxc2pcq3thOsQjMs/U4k=") +ORIGIN("jivox.com", "ihv8/C+ro46hACQRxlhogzecn8A5ZnxD10SfdQZ6P28=") +ORIGIN("collective-media.net", "PdDk+nVjBg37Wt4T8b0r4Luk+ub4OwZ5QCuOfub/KG8=") +ORIGIN("safecount.net", "0XTKAlW6TBmGCrvXK8rcD6QPjNAn4h3iRFsbDKuzXN4=") +ORIGIN("adsupply.com", "zH3NKvQQeHJL5MtbEsXFfxdlJi1oLyQGVJUCOGHyXDA=") +ORIGIN("pagefair.net", "5AeCEU+Co96Llf7+xmmedcpfe/pRqsCSkn7LdGjjmNc=") +ORIGIN("openxenterprise.com", "c13tdcJCGXkZm7YV/VJPr3pLLhI7M/C6jmS46swPRTU=") +ORIGIN("btrll.com", "cQBzwWnqZCbRgTyHaTNWz9yDRfwxEThzMdWfU7dtoVQ=") +ORIGIN("zune.net", "BZQgFDVowgfnP1MmrjVDmBT4JHvUdvi58vV/PUscQhA=") +ORIGIN("yimg.com", "xMPFEjIzL1JQiE5FrDWs0uSrp4z/DXTrLpTynGMwvIo=") +ORIGIN("dp-dhl.com", "afhcrI/WpNE9+mHQ/v+OV0VpxHoXHNkF5VUE2mj71xs=") +ORIGIN("answers.yahoo.com", "QXruU2Eh/6Gb0y03/P+aAepO6eYjyn7IE+DMdqg49+w=") +ORIGIN("amazon.fr", "ruBUwyHltWjWNyuFfbVCSd+aI/XbE3gxsJMnnOhhe9E=") +ORIGIN("infonline.de", "doqVyFcV9wLR3uXQYIg1GdOGw/rInZCC/VTcz8Ekseo=") +ORIGIN("liveramp.com", "rX9jerNrKvfR6LJhTeysutH20pMC4IQHO9EnJ4NY4gY=") +ORIGIN("amadesa.com", "ru/+YLMxRaBvakngaPUpMfsuPdT9V6gLFx8pwDHGZuE=") +ORIGIN("fetchback.com", "tK3wa+YIqZZKsygE+rpSuwvZj/KyLEIk4skGdjpL0PQ=") +ORIGIN("adrolays.com", "IdQCFJw2xpAfJGrjXD0xa7+/z9e3TH6r2D+aNS8pvBo=") +ORIGIN("channelintelligence.com", "sesNUlwEicObBzYFtfkfEkeB490Ia5kY3+213Xfcicg=") +ORIGIN("stumbleupon.com", "vvfVTaMmyANMlqFVU6/PMoLGMI9wrMNIfkFRW8tWLPU=") +ORIGIN("google.com.my", "+wxyAihDwyyPXlhDKr88YAf2VONDIRMj/Gs0k96EB4Y=") +ORIGIN("leadlander.com", "aLWv58CW4hBeQln3tXU6k2A8K3JhNjRpWMBL5nscTrQ=") +ORIGIN("atlassolutions.com", "7AOy0TLqLK84D8jY+GFp0FmwmGp4mvIHO6nNQMIBu8M=") +ORIGIN("storygize.com", "1XduF+J6z8wYEnNayqUi8Euv+LH2QWPOQqsHgKBrU5I=") +ORIGIN("peer39.com", "BTOWmT2DdU661Ws0g9/hmrVX3hYSumQ0B0rGqvYhJXE=") +ORIGIN("trueffect.com", "kHIL3WX3PIRcWLAf5oBrSXbc+VqdQIXEC2NoevMk0OA=") +ORIGIN("smartadserver.com", "J0zhHOxsrVKOwU0TSx2bUoDUeVpJf/5AMOHfjJOqv0Y=") +ORIGIN("ekolay.net", "Vm6b6JTgFyDadxQ7CHwPDHnFZkgA9Tms/a6u7q/MzWA=") +ORIGIN("google.com.mm", "iZ9B45w1iY7mDNHxKrocb2BLqh8AV3TXhxpF9AFoSCQ=") +ORIGIN("binlayer.com", "0hqyi/mIHvvOF9rMNHRFG4Oi0yBCrIDA8s8OQcgGobc=") +ORIGIN("iesnare.com", "yVO0dAyNJbnsTkD/IxQ/YXo71eTFrs6T/lfR2ZZFpew=") +ORIGIN("advg.jp", "jVuFYPhyzIjysw2lS0XpRRPXI8UMQ9XY1zYiuIh5H2E=") +ORIGIN("halogenmediagroup.com", "kQ0GmMyOzQPhmyW8P5VPTFfdAtUG8H7VhL1fvpb9y5g=") +ORIGIN("disqusads.com", "iDAzUpzo0KLzpcu32hp50Bh+3NVWg3N44eH65Pp8IAY=") +ORIGIN("fulltango.com", "9mrImGBJG6G6BZK3syy2E/oV9YcR2MNr0en476qK9Dg=") +ORIGIN("efrontier.com", "mXduzowBX7q6BqpB6FdEvhdpAkASNXoqt+zaCxV9H14=") +ORIGIN("searchforce.com", "PXRA20FtmExbUEEQTgYvvE88hOLbo/DvsqG5IRBCOmI=") +ORIGIN("clixpy.com", "hvFmUWHoufSc+aaJlAzPlo5qaZ7WsjNIFL+Kc1cIJBo=") +ORIGIN("augur.io", "h5vSG1HfB9LqVUpbB0wFg2uKsi/rOmvxtcqgAcEZGXo=") +ORIGIN("marketgid.com", "fzB8JaoZsp5lrs5aTDUafz8plBIlRvFE1FNAG3osKbU=") +ORIGIN("madvertise.com", "K1MX/rF3itrruxJJ33XtXHSX+yCzdXt5mEjMIBYgZMg=") +ORIGIN("drive.google.com", "EqDwJoP7wN+qFew9xtSbEwE2b9eiDT9P8uY803o2jJw=") +ORIGIN("google.co.ve", "2oB/es8AoKyn5ASwPrn07QuW/oEr2zmzJ7TYvPzX8OY=") +ORIGIN("mandatory.com", "PF+EiLz43zbXtvJhkMzQoZiRYuphJE7nlDiZA6KunWU=") +ORIGIN("google.co.vi", "X36Z9nLIVdnGv383CNv7vyCoqMLhR4WY1r0tYd+CI7A=") +ORIGIN("licdn.com", "ZUA9jakhHmDNeBVI1jEVY6nKnfbb2dx1p/Zieta+jYg=") +ORIGIN("activemeter.com", "WCyGFOlG6qPoL5Tvi5RFCCiO3zqYPm8vBrN5Uzj3etE=") +ORIGIN("adcloud.com", "QTZSl0v4uHS9zNU61nne9IWMoB9Ca+WOiBIWOuQpxXI=") +ORIGIN("gomez.com", "S7W7K6Nynq4VPae2/7D7M6bTEUaiEUTuPPlaKA+TzMw=") +ORIGIN("addthis.com", "pJ7Mq7EiTnbpoX6J4WPsKT0qyaONExhOTgfJQkJuYTA=") +ORIGIN("adfusion.com", "jmVCMZhdFGcqD3ixU+G4SHkxazwTZN8twjkP9f7Z/9M=") +ORIGIN("51network.com", "8sxM6Yw0sT7KM5CW7WPFMMWY8tknqANTOCzmavf2m9I=") +ORIGIN("spylog.com", "LwvQJMO+yr67nlNqHM29omxi5TYS48wkAFq5hhTm0XI=") +ORIGIN("smallbusiness.yahoo.com", "6V/NUvhEa0ZVJuFSTV4qPzUXnKcowJJsX4/UFjNPbeU=") +ORIGIN("connectedads.net", "Far8npXw9cntNLXE9GI5ZFO86DHYUPnn2cV/UXr+h/8=") +ORIGIN("typepad.com", "KhAuvxEqMELaNaQIly+DosVlvlkA6oKiyc1vJS29AFk=") +ORIGIN("adventori.com", "VJQz0TQjp6AgQlst3U3Tq7zpeoNrjB+vdEZe2JLhJao=") +ORIGIN("actonsoftware.com", "RqTHP4FvOTT4KwyJ2KMqeEq9k6KapaoU7K/3DBV/O1E=") +ORIGIN("mediabrix.com", "jHEDIc7OVfVr2b5KrS3uzT8IpIQpU3Aa83ScImaQOZE=") +ORIGIN("clickintext.net", "K/pf0PqwusuqULQXFx+xJ/hgixh/Vi1xCEWzS/6om3M=") +ORIGIN("adsvelocity.com", "mNOaA7HnxV+efMBkHkLcSRlfq0Th8B3dNWZMDFOm8MI=") +ORIGIN("internetbrands.com", "Dja1WVyFShBFMEk54QdLLHDvjHaG7DhrVQzkwpxL37E=") +ORIGIN("accuenmedia.com", "918mhOe6udg8mvzVekt9afZo6mz3jD+wgBmW5oPv5vk=") +ORIGIN("rollick.io", "RPacef0jy6Z0+HcNJIijFPFolEAlLzZM4cTDJgX9S6g=") +ORIGIN("iprospect.com", "1m6rxh/Xlv5GKLttg12sSfCn/UHZl6KuKMO1hyuBCCQ=") +ORIGIN("gemius.com", "aA0v5H9OmpjQ0ZfRYZKHl0I0yVxOBFo0J+1C0w7nHDI=") +ORIGIN("travel.yahoo.com", "uHiHGUBvTCptS8A1S7nZEQZrs/ogNm4G168MoQrml2c=") +ORIGIN("betgenius.com", "X9PO7XKh4Nks+dlgfqerx9UVDm30BHIPoYQh8GBU+p0=") +ORIGIN("lotlinx.com", "Oy8wzpn2JWM20o45mkatkSa6/IUxR3bcJfVZN+9/H8s=") +ORIGIN("effectivemeasure.net", "u2WpE/EhOzny6W4cncKEMoJVxKwTZYlpcvBpc+TZ9ow=") +ORIGIN("adecn.com", "7tQwoGFwkeuw4q2HE5DtapdJzftPaecm2DIvn2WKPCg=") +ORIGIN("3dstats.com", "rpK7RbtyTxvoF1SGUAVTSCyB+Jo8B8+4tt0Mth/N99k=") +ORIGIN("bing.com", "oMydI77SYFyiFJSEY0NLuMq1p+p+idtplVxDpOwUXLA=") +ORIGIN("steelhouse.com", "nNhmd9DY8w+aZ/lpcomjpd1aJjY5jIJq7eweZMdeTtI=") +ORIGIN("webmecanik.com", "A/mHgdfVaun45MOFrNtyF0+DIT61lGIjXP+ZcBGO+S8=") +ORIGIN("llnwd.net", "V9tZPRP8KuZsSQ3xt+jUV6MdNX3dR94NEI2Rl2nxDvE=") +ORIGIN("radarurl.com", "eEqsqHPgSHWCG64gX/r4oyeucFp9zz7YFdE/mSPEgg4=") +ORIGIN("webtrends.com", "Cf0hmPujx20NKX/BUoFSSBBi2FGBWwoR4nzpB8VQtI4=") +ORIGIN("auditude.com", "Ldpc7BwfJ3IqXw7oS35TbeQHDyeOctynFVDV0YEHGZg=") +ORIGIN("nspmotion.com", "c1O1C1TG3AsNL9vKd+VLG/iGH65nfHfdj0VLZD4BSug=") +ORIGIN("adotmob.com", "NreKOaYNa7ApLRNLKcXqy4dAR5OdHPgGxvb/f2d08f4=") +ORIGIN("zumobi.com", "W7D+CCrjbmoGMKxG8HzLNjGWbLw9wQXd4T3Jfxe9vos=") +ORIGIN("lucidmedia.com", "cTk+2sQJDlCedyiFyRYyCY5yQz5NyG6Q9FFkmNuC+4M=") +ORIGIN("conversionruler.com", "zsnHMFGYvGI3/wkFGFfY98Lzc7ZAU82D8wKyvsA/ML0=") +ORIGIN("webtrackingservices.com", "j9XdHI7XRu2GlrafLZ2yuY83XiEB/AUJNBTST30hejM=") +ORIGIN("bazaarvoice.com", "K6Cr8SE57mbGFO2Y+ysdsReGPv+u4x/qrTEtH3quvuw=") +ORIGIN("anormal-tracker.de", "DyXUYAeY8tMDqmC1CKzsRcm9lYtyL3+bLo7RcBcCZvY=") +ORIGIN("insightgrit.com", "jK3ykpEGpYvNV+/KyLZM6r3Q5RBU9sLEMH/rd7F0jvo=") +ORIGIN("bitcoinplus.com", "nGdtUMCQodWo4rQxpMNTh9m3eW5wYBA5cop7jzrV0Jg=") +ORIGIN("pronunciator.com", "fl79pDqRdq03IpmfIu+PJpS1U4Vn7jWOYMoLB5/0Vj8=") +ORIGIN("google.ml", "W4N7D0aeX03dJrPHq9p4LrvxW3HZG2cn5ymIwYWNDAM=") +ORIGIN("pm14.com", "APC3BqXXq8xBU/SQRpAAiGIfyyD4VrSdm/Fvh1Jz+jM=") +ORIGIN("adplan-ds.com", "ZouJaXGKEjghdQEb/+8iJ2fxe7BpVz+KrN4uJ9Qx6ZI=") +ORIGIN("dt07.net", "SV+3y1aZA/ff7K8Q+tD8V+GbYl3EJD7n6XS+yYC/yfE=") +ORIGIN("google.kz", "/e4ku4fk5Jqza+nc+QrjxDUz5j3wEbPuKw1EaxQkM9U=") +ORIGIN("navegg.com", "GPwJLWULL7Flmo0Hve3lEEqBYI/IYm5RntWdoBYnEI4=") +ORIGIN("intercom.io", "dLS8gNpGei3Mv3WHCaFWKNLKxT/ag3IHV5RRB5q3HCk=") +ORIGIN("paid-to-promote.net", "kOCQLSZzrGHqzj2o02pK8rTuOGh0EjeY2CwTke/tNlQ=") +ORIGIN("cobalt.com", "3TJHs3Ez5sobhBRQ6k2oTCZTTBl1hxQ0KtY4xU7f720=") +ORIGIN("google.ki", "vfYWYKvnlaEyB2o+w0rk+GbDLyYyE6BV1o01YGmqsCQ=") +ORIGIN("theblogfrog.com", "AtZILLNbvgrQ0osM1hgLTYsnNK4+vtUaUv6IscowzAw=") +ORIGIN("adxpose.com", "xy5Wnbvgi/X9LW1lGDkUlzqxxZN3/bXNUM4wbMsT5GQ=") +ORIGIN("vizury.com", "Xl9nK/mymX+2diAeUXAr5qGeiLsw6GSIxBPDCzsmN/o=") +ORIGIN("samurai-factory.jp", "klCSl5zGHkBQdlo3aWYyN1SztpDXPie5lo5L7PEbZzk=") +ORIGIN("yandex.ru/set/s/rsya-tag-users/data", "emw7UmKVbwPiPHjqSskuOG75U6hd2/ObNjtHzDWo1jU=") +ORIGIN("digitize.ie", "2zZouShoPV5ffYUboDehpj0odIzgJHDXXVTjXSb/hqs=") +ORIGIN("activeconversion.com", "fI92e+EYIjax6P/ddnXqvexjTploDX3RqwXFaZF33ac=") +ORIGIN("xplusone.com", "7vhz+dMUtziue59+c5qP5riTVRPRuiq0pZ1qsUtPL2k=") +ORIGIN("sptag1.com", "fDhxXyLRj8D1AnveNbV8X3vSDN9c9H8EYbBkPkkVnDE=") +ORIGIN("nuffnang.com", "RYzJsJXbSGk7ypKV26Jf8F8rNN4YxhTKGFIggh5EUfI=") +ORIGIN("adzly.com", "ehp0mFkLZmI5LTcJgvwx9wgNIhYKemUXiB/Y2C/ViEE=") +ORIGIN("domodomain.com", "0AK//U5zoK5XT9uYvgi34r8yePj37QqWFeUke4O/C/A=") +ORIGIN("cedexis.com", "ixiy07RvivO7wye8LQrW4RBEpWRiaXhy75U29P4Tx0M=") +ORIGIN("consiliummedia.com", "5sGXJQRcF19qF8jgjlNDFt/uV0mgONVask1aO1m+I/U=") +ORIGIN("supersonicads.com", "DsfuuDOQ32vSgMmEJqcblp0kZ7+9we6/d6Pe7YVrRuI=") +ORIGIN("goldbach.com", "DzYb5p3yI6lYkqeSAE3Ooyr33DxDmjXjDiuNe+Ta3Ic=") +ORIGIN("tmogul.com", "Mbla0LRlGaMavYjD+RZXzIhfQS4hmEiLMUh5K8XABGk=") +ORIGIN("blogherads.com", "WxyjK1Q1vgxiEKlAAapla1ryXUMMAX2aKgidf5PmjMw=") +ORIGIN("huffingtonpost.com", "l+pTBZ0bbldkglrtSv+CEYoXHBB3vqr+1kEnOvkpbJk=") +ORIGIN("googletagmanager.com", "htTEl//KYksZTKdJ+KNDK+Jp5eGQk1QWfu66o1+oEyI=") +ORIGIN("emediate.eu", "0ODQ8QFAI3x/YmCYO3zfmAvkCfRQTTt4DrrywrCW90w=") +ORIGIN("adinsight.eu", "ti8O8FRd5hJ+0tjPlL2RiejfxsH4tRz/NpY1EiQzrqQ=") +ORIGIN("ucoz.br", "Vz6OMjOuXbYEH1TRaPMQ0avft0/iM1mtUD7Bu9M4Eic=") +ORIGIN("webtraffic.no", "y5GojvnaOVlQYtTWDbC5faHwnxfMu1yqj/BvFUnfnC4=") +ORIGIN("chemistry.com", "fc9XfHbE1F0QDmoUwF19St7cv7u3u7HEYtsvO1Y3BMs=") +ORIGIN("avsads.com", "1rXwqgoASkHJ4WldS6iH871me7eINomahJyP/IYB1W0=") +ORIGIN("salesforce.com", "/mqEVQJxEhYbN6akygr6Ht5ozpWMwVKVPd0VnMB14+M=") +ORIGIN("adlantic.nl", "YiWZErVw8ucMrTwCoCRH80adN4Grtt+enMdzYvVdZcM=") +ORIGIN("2leep.com", "374XwW5oUoTNREtXgoUo1XZ6MOlz6iTTA6xHsAfGzOE=") +ORIGIN("messenger.com", "G9hyol24e/338S2UlqIvxBegSLg2Z4GWAXROoINT+j4=") +ORIGIN("affinesystems.com", "6E7DmkZzDdp8WWTRdGTGH8udd3iMTpFJuRT1yuPUluU=") +ORIGIN("adbutler.com", "w4uhdjO1mD31IglbLIIaPT4hOBp0OLU1kkNWblrsD8M=") +ORIGIN("flytxt.com", "0PAoVdmrGNUDiCmncBSzNCGHzAZ6Cq/SpBraDeliZq0=") +ORIGIN("etargetnet.com", "WSZSEaigHH2yjszSb6yOQ2IIUoHBiQdHhkQjnGBuGDY=") +ORIGIN("connexity.net", "ocvRYa3FOLyhBs4fqNYnYdTF18fS5ca906VBmVzRBK0=") +ORIGIN("ml314.com", "mryRX1QjRaQUFM7H09/pT/vsnqrdngBZquMIjAqJBKI=") +ORIGIN("pulpix.com", "t5DTechWhM2KFmUw9YB4nO6QnW5Wu1gcSHJDaziUXCU=") +ORIGIN("google.co.jp", "aDCaocCSpMzdhohHIpeTianvwftx1c6ZFE6a2lx3R8o=") +ORIGIN("liadm.com", "jQQ/xvUeNzHUK/v7ygtlnnU8cXwJcNd38rTDPb0BAak=") +ORIGIN("piximedia.com", "sX9G4Ha88ZpN/zs3ErL7+48b3AixnUTLVOdmHcyThvo=") +ORIGIN("epicadvertising.com", "ibht+XW0ZlwKL/YhK2hc6Rn49xSbQszlknaXcnyTZgI=") +ORIGIN("clovenetwork.com", "NX2B6f7+skyQBlXl07n4s/WQJdi1J6bIgnjgTeFv4TI=") +ORIGIN("metrixlab.com", "cMVk95F3+YcfLe/jAbIx2Fg8vneiO8lgKAfjsZG11X0=") +ORIGIN("gogrid.com", "tMSG7CD2VOyr/fpXR0y6occpuQOO+j8b+dz7Cuchkl4=") +ORIGIN("sendpulse.com", "RGjG+8ecPvEb+7ugNRMnS8iFU6MQwKSUTD0amF+kf/8=") +ORIGIN("bridgetrack.com", "p8I/6ybAPOhv/uNomd7RreHKgZ2k4uJibXSfIbDkJkg=") +ORIGIN("industrybrains.com", "NK+W1LTjLJ6x4gah7aoeTgLBWxmte9lFG5TaaiXWpXQ=") +ORIGIN("rnmd.net", "d9AzbtAjxrs7/tt1vT6H0Hl9y50o09SKVld2OAEIPSA=") +ORIGIN("semantiqo.com", "CDbjLVtn4Zvgr/6j6Vl3ZB5BAPqZRrWZDroHrS87oiA=") +ORIGIN("mundomedia.com", "PvGX03JCq2cMHEa7zMoQzzYqqvEvk5vV7HZzCtYnf0E=") +ORIGIN("adometry.com", "QXZUOahlsV347h/Cdw6GFOs/I1kZEj2Ypd2j3GUJ96Q=") +ORIGIN("improvedigital.com", "pGlavDB71LKjq/IVa/qCiSwfK7+qMco6StWmR5MghgQ=") +ORIGIN("adreadypixels.com", "Lz3aXGGGlwSfDZNwt0F3J3plQXvQuUsn0Fc5IcU0exY=") +ORIGIN("adoftheyear.com", "RgFHHu52TUk38Ac+QfApVoqROs9pioMWEZgvfLXieXU=") +ORIGIN("libertystmedia.com", "OTuSLaS+BQVy0iat9sVIFODIyIC1cGAZh4b+mQTIW8M=") +ORIGIN("todacell.com", "LkGXxyM48XMw1Bz659Q42lo6s1U3ZkN++VSErVRPsFM=") +ORIGIN("google.com.bd", "FJf1hnUiOLUE2U9Pwn+TmVRWrRtie7VXFOdPL9kQ2tE=") +ORIGIN("lijit.com", "liNCnulffOLAkMdQZ9+DwWvlPE42M7WRlo0tRCmRBms=") +ORIGIN("adbot.tw", "GyAZpuXlitPI/Ui+mO1UZjBv+TAMyt4e6gIQTzo/ItA=") +ORIGIN("tubemogul.com", "aNGm4P7SYIkpfj530e0Lkz1/W1AUEqawT5pJrGljb0Q=") +ORIGIN("bounceexchange.com", "cxT55v4jmBBHspjXWhTsCjb0YHamNj7H2IbBEt8X++0=") +ORIGIN("google.com.bo", "3sSwKJ5tYkovp44Nl2HXY1g72ucXhiVbZWrdPtSQq9M=") +ORIGIN("radiatemedia.com", "zjb4s3HYD53s/1DsMuT5oFNqtWIneSx5HdMBlewDnOs=") +ORIGIN("scribefire.com", "eaynEw4jO2+1pm/vzsuDcdpmb6znZDbK90FnULRSa20=") +ORIGIN("dailyme.com", "WUTJT6nisdZWKspOVotoEnY5vzDFqKk2UirHg5Eu6zw=") +ORIGIN("survata.com", "DIrAwMLx+fDoBLfJjRvtm8kd7oXkc8pA1qgal1a+URQ=") +ORIGIN("ad6media.fr", "Con2Uft5y7Nff1Pyb0MsBmyFZlav7TiLHm60OkElImg=") +ORIGIN("conversantmedia.com", "G0hfghWgz2h1MjRNUBw6iVCP1BkWtlfTAkwEYJ+vw9Q=") +ORIGIN("deepintent.com", "i32KOzhwTYxHkxmvAUX7m2XLzldQ36s/NUSbuU29lL0=") +ORIGIN("v12group.com", "drr4ieWmeQYD13fwYQP+zIC2XKexTHjtq2anvVzveOE=") +ORIGIN("fyre.co", "d5n79A2vVIkX/5xwRkUu2TDQTXnV/r+POffir+xc/QA=") +ORIGIN("blutrumpet.com", "C48UkNLmz81TyGK9bpgAb0pNAdApRO1BP6qb9T+4jFg=") +ORIGIN("amazon.it", "iOLn61YiC254tNUc0kTGKzRTQykDIfDPcESX41dw/5I=") +ORIGIN("quantcast.com", "tqJniNfw/V2Qc0r6zrcVU72V2cYB/nu7MqFk+NMVUCM=") +ORIGIN("hearst.com", "EWwQZoAI6PIVqLJMJ+maAjqi0G9Ctdo0O1v4/BCWa8w=") +ORIGIN("footprintlive.com", "d5i5SH9CEaqCUs5hEk6xNcyyWTzD6zDe46UJHYCZ8Gw=") +ORIGIN("vizisense.net", "C/bXnkdHjb9cvs9WNC5FDA+2gysRR41cxRK/sMDI3gA=") +ORIGIN("wrethicap.info", "QDlWaCT4s8iMjgidRmyN/h4uu+mFWbTA+YR1aBNiWWA=") +ORIGIN("zemanta.com", "8VarLa8DQjkWCa+w0Ujslc8Cy5UKWdRQO9k3CzscYbc=") +ORIGIN("optimumresponse.com", "n88o6ZhgM9w9jPZU5SZrvY4D8qQRoxpD+Ww0AuJG214=") +ORIGIN("tumblr.com", "GLl/AZLOor/CIUqAEGdbT2GR01uPwiDEULC+DCYCDEU=") +ORIGIN("semasio.com", "ttyd1vqHYqIAFog9h0miMnjsjpwV9MJs7XVepWnTJoE=") +ORIGIN("mdadx.com", "NVFX6OX1ST67fcYvnkAjXSteP036HGMFQJH0XGIc4vU=") +ORIGIN("iprom.net", "MgY5pTtQyJ2o8FHGA+qmTn9+NF/ArtWWf1X2NwumuTw=") +ORIGIN("boo-box.com", "yBudFZR41Wl7LklsYDRcXNW4pwTlU0JNjxafKUog//Y=") +ORIGIN("sixapart.com", "eQj5qV0mQJ4vQobZIjoc1bD61+a/YENAtUhLfD28QXk=") +ORIGIN("adworx.be", "WGqoo0Jd2/PD47kU9jI24VC34/OXZk5V6I6kVKo0aRA=") +ORIGIN("news.yahoo.com", "zKgDsS4g1aznXovUDs+eirHklypN/8cJWT3kn9+jRts=") +ORIGIN("neustar.biz", "sLSEFq8pL3rrfX5S17+T3jgeZk5zqL1Rbw+vtOqjWZk=") +ORIGIN("nurago.de", "f+ZbVChRL7h7oCmc7LMFjyX8FVRkvrYUL7JdRLqIcXs=") +ORIGIN("saymedia.com", "IyRnEDkzvdpNpZhUnwo+vfaVFSpnXEI0siM3lDmwMqU=") +ORIGIN("yieldify.com", "htpGkU5otclT7l5BK4oiRLPpXY/UbbmXEPOzvj6V7CE=") +ORIGIN("didit.com", "EwlPtGRYq83JGukcOQuMPMm/3tBqzmn7DwS5qhY0SMM=") +ORIGIN("shareaholic.com", "a4kKpV3Ht0ca6IKyM4uE8BK/njoLX8Xit8c3dPMEQ6s=") +ORIGIN("crispmedia.com", "kN6hEY6AEt8iTmuMYPWh6XQwzwq2r8EFBfZKqKkf7WE=") +ORIGIN("games.yahoo.com", "IWPd3cU1Zuw+KfvaaDtEdIFADUBUP12gekKGqF5EH3I=") +ORIGIN("statcounter.com", "B+7oZVCK0bBuETEc8rMsvOl+yDDmpjiRso87bn91IGY=") +ORIGIN("pulsemgr.com", "DQzDFdrMUYKuJZINKKEzx8DjJwCy2HQxW54jIVRXnVs=") +ORIGIN("mxpnl.com", "59YXD9qxauWHpbyZItdTgmwKVndKB9LYLO6Y5/8toHw=") +ORIGIN("nanigans.com", "Hexm5igS7LLytN4IwyQ2hE5fofljE+1A3h3Vs6RAIQU=") +ORIGIN("mashero.com", "5Xwz1OaivsSqTAZuXSMn/JhmQM50tMlAWHOS/qvQgpc=") +ORIGIN("pagefair.com", "RQBqHCxreS0un5KEHVMBQ7qfW3FBYqo+pb5DZJcnx8Q=") +ORIGIN("clickbooth.com", "DBW/ovv9rAJ6cwBzUFHjOTCuhIbifhk2O50G0uf55e0=") +ORIGIN("inskinmedia.com", "5UyB5mtBvxPI9qBfT8lEtA1UZAZOBlTLj2ZOsn0cySg=") +ORIGIN("webtrekk.net", "miAUqSOn+xzopTH66lTExAhiGcczrmnSc0y6L5ZiCuc=") +ORIGIN("appssavvy.com", "62bO0+PVr5GdHgxT4Y2Mbka2YnV0kxvKQrq3IFP3y9g=") +ORIGIN("online-metrix.net", "ttjM9NgnZKkSuDdRM7kgItoxcyfCHgQxmnNoGbepNuc=") +ORIGIN("aolanswers.com", "Ro//odqJUdd/nZ19maVt3QRHxbzg0Al26ylQ7PP8eLU=") +ORIGIN("amazon-adsystem.com", "tT128d9e1yVpGo5xhdhCOcSOeOfbq57FHkSzQzV52jc=") +ORIGIN("technorati.com", "1PNVCjM+3w32NG286R7wtBB2dzDqof5El0m0c8tqYLQ=") +ORIGIN("bidtellect.com", "klJ73numjGb8neMkR3R0JynCuF7BUjGZPszxKTbFkes=") +ORIGIN("admized.com", "L1lKxO4MVP7K9TZ5pPm5uM0kNGH8AfE4qpRYX7RBH74=") +ORIGIN("blogrollr.com", "SUJzNTTG5q5oLt6fW9VTkK90D85kMm3gLo0WuXGaZUY=") +ORIGIN("adverline.com", "wPxFTS6dyblLB9Y9Sq0Ouy8WtxBp73lt7g8irb9wpCg=") +ORIGIN("myads.com", "QfBY4+LoOSNxvUncaALYJ7hYjWDK9sIDX8ZPa8rPdUM=") +ORIGIN("mobsmith.com", "BNTrv8gVBVzFdbfKkBUnP9tyMixxQ+hnHbbh/NqRoXI=") +ORIGIN("triggit.com", "EsYoAZ88ow3iGV98odSF6L78BC59RFREw2ovpLi/Kjo=") +ORIGIN("roxr.net", "FUuBKnmXS/qF/9SaGZlSAXAmZYXCAuAOv0GqPvlvQIk=") +ORIGIN("protected.media", "QAQVzBN/orU55Vb0OaOCPzIYs+HyPsE1LY3tr6Xdsx8=") +ORIGIN("friendfeed.com", "YZJ5B8ip13exLu0uJdcXYxgKJWjMIT0Tuqn9LIQHJ3M=") +ORIGIN("sdfje.com", "gHP0jLLBEimhJ8dtAS7IOw5aoPiIjyIbzDVUk5mv8zY=") +ORIGIN("iperceptions.com", "Fo+KxyOLqOfLYym0kTa8cHKjc718JEPo1zyokbMqUA0=") +ORIGIN("9c9media.ca", "+KCCx4sGnahonL5SfUAcbtdg3iEhfwzwhLqAVPRDfVI=") +ORIGIN("websitealive9.com", "WIu7JinjyCT9yqWoh0HoSfHlmWCIllh1VBDkKiSc1ng=") +ORIGIN("zestad.com", "R6FCagxZz6HVUgQwqlXt1L0WvrGgih7LC7cVi5bVe8A=") +ORIGIN("semasio.net", "chj56FhqSIC7gUS0OTZTpnplWWJtGQyClMXYIhFkwFw=") +ORIGIN("activengage.com", "o4T8gCkPzRrz1FX9oiHoE1+NHd5xq8qXWY53qUpStNI=") +ORIGIN("googletagservices.com", "2ImL5DSoDWPg3cjouHn8exn+jYt2MvwPegDEGRtBynw=") +ORIGIN("papayamobile.com", "VqN1NraNtLjP7RHiKUTDzeQicdwarH9wHLQ1PX36chM=") +ORIGIN("kcdwa.com", "WReRQk/heSPxbyBHP9mbgTa/xiZreWnP7EOCDuevuvA=") +ORIGIN("simply.com", "oYsAqU/08Awcw0vRgicisV4e3kEpoUaQxD79rpPe9ao=") +ORIGIN("opera.com", "VjSMqUcyiPpR95r1AAQNoVWNYOc8h0KDJVSe2Zvf/3c=") +ORIGIN("mydas.mobi", "c4V7tNERF4OgfrSt8ZEJqWHxKclXTJaFxPUnrwEFrLQ=") +ORIGIN("mycounter.com.ua", "uzlvdTFQnUFxzdSQ802rHfy8NT59YClYys20iQSXtBg=") +ORIGIN("mobilemeteor.com", "r+ITMaY0m8235GzrI9gvRb9Y9i6C5+1214ynBwFt5CY=") +ORIGIN("chrome.google.com", "Xbq8tZLhAuUbGkEvCGg6iizZyoJYWqr5wpFdp/fC1mE=") +ORIGIN("showmeinn.com", "fNpRdarLrozVrYbD5VoFy1w7b8j38YaYQZzTmAlOMT8=") +ORIGIN("geoads.com", "X2sbtx24wIamHbywdYkvfwH/l0aVk3Nhe3PymW2gOH8=") +ORIGIN("opinmind.com", "51iVHWCxLYLIhOzJ1Ihec58gkR6d7hwtvoybCUjNsh8=") +ORIGIN("vindicogroup.com", "kTrinToZ78g1l1Eu7lbiU06XhvamyoYiBVo8Sd0hdfg=") +ORIGIN("selectablemedia.com", "wLNh/8U+dw0qQWo3aOto/Myp9fdk3tWh8hti4P6+FKw=") +ORIGIN("appmetrx.com", "mZTyXVdF7LPUV/evI78il/lNDu1NM/hQD05RJj7EU88=") +ORIGIN("reklamz.com", "/eAIbYUO9nkoaS7faBbm2JSqND9tmGC3km+SUc65bX0=") +ORIGIN("adbureau.net", "cBKKhYyALrSnihAAOB3zGUNzPAHn4TJL++WyRlLTTVY=") +ORIGIN("google.je", "JatsoZB53rUR/9UAFz8xBKG6NK7EMBIu4J0/FrJREdo=") +ORIGIN("mopub.com", "ApdsE5MKLZCe/D6g1Vi2XrW5OBL6YP7086MlrVkjGH8=") +ORIGIN("checkm8.com", "298kF+8AJBTI2YCI5XWVVuDloZlMcdBaicdxMmZV+Bg=") +ORIGIN("clixtell.com", "joDvd5B0X7mHAhBrxgynShMMvDeU2zW47R6iaqd2cMg=") +ORIGIN("sptag3.com", "keMzBzhQ9SbPCTGsBH9J+MzRUvUfBdqbWDmG1XbCi9A=") +ORIGIN("mdotlabs.com", "lYGYbpUNIiU+Fet34ehDePpmcnflNfm12nHTI49K8lM=") +ORIGIN("help.yahoo.com", "r37O2A4HLeQda8NJqtcywLwRSugi0HjHiMwRcpA2W6s=") +ORIGIN("adpepper.com", "uIhRnLGTDgdSyi6R/oMpqDYsWGz2DQecdYwG3QiSFgw=") +ORIGIN("adjug.com", "jrCEecZ7wq0InUqdLeMCDBAgtaDZcBbTbttjNDzylts=") +ORIGIN("adtechjp.com", "RBiZC24E9+v5Ipwo7ic7iMZ2J3joeJT3P5PNRBbsFo8=") +ORIGIN("conversiondashboard.com", "OOU4+JUThV7b/hIKAowYyF+hv8tElVAMD3UAoDDT+hQ=") +ORIGIN("adbuyer.com", "AVW5//O8yvDn76JW3nlikfHDboyoyQLx6EUBPvjeDZY=") +ORIGIN("adcirrus.com", "PnrDXW30VUX/MfinzqJxMJ2wL7hLNWTDMrA95gPjwro=") +ORIGIN("datranmedia.com", "sK7ie612ekm5DMw9um54IMfzAHm7Jq+0qh6gSLYTaiw=") +ORIGIN("ds-iq.com", "lAEidJkHhIojlLnGgJHO1iCUkZRIn9V/oCcKEK/RG5g=") +ORIGIN("picasaweb.google.com", "ZfzTURTHW/Bb94ekMqRUVL7fympJ8JghAI5XtiH6hYc=") +ORIGIN("successfultogether.co.uk", "QEs/StErvXBntaIwqJfC0dQNvWU9qJitwD0bUjjYYoI=") +ORIGIN("google.tl", "SYu/vNHOupVtKAf75A2OyUqZQ38oStoAk/rcaku0Hzw=") +ORIGIN("google.tm", "6Sjo9tUBF7LxlhaEe+TJWnJeHmbG5dAN19uI73A6i1U=") +ORIGIN("adpdealerservices.com", "vYxPzJHxLPWuVlOBY5iyvvOlTouuVROZFYRpAaP2fIg=") +ORIGIN("google.to", "1nH7ErY6HMzUJlm2bY8gBMvLpLc9WEeNnoCbyXS1tRo=") +ORIGIN("google.tk", "sybdVxB1dIAu/4qfTQxFDfdFGCC2L4u+pdYBTKgcrYw=") +ORIGIN("google.td", "syfp7MFAs1fHHNIozj5KHGnrfQNMDTwliAXX85iwZjI=") +ORIGIN("google.com.br", "6kfVZPGsPTN0xZTVGHn3Lt6R7N4q65OxhCyWPDJ1VK0=") +ORIGIN("clickaider.com", "kL9BgXIcJFCq3w+ndcuQ3eRg3yqvqH2zQQOzOwkeH1M=") +ORIGIN("adnologies.com", "CDh0cp58WWwrnU62eRL9SCYHZfB4dgUyETiERWCIQcs=") +ORIGIN("islay.tech", "U4FCxs4fHPGR6k9rMmfkZ+eBlMaQ5YH3rTHgbH9Eelg=") +ORIGIN("trends.google.com", "jV6YKBczZgSViR9rad76EFDeifMQZTw3k18mBjpOrUU=") +ORIGIN("cpxadroit.com", "TN2q1q0zW54VKnLEe1kgExJIIldHESIOXvbsQntJK4c=") +ORIGIN("google.tt", "d/WkVwe6I/4aHWf1sMKxnVRgKMU7DM/M7OxuxS1MQ9o=") +ORIGIN("eleavers.com", "4UfP9a6HP0ZkeCuWSD0o34bavirKWGQ/ZVXUcMl/mFg=") +ORIGIN("mixpo.com", "SmC0UOVBEj8vP3JbaMni/O8iO6v2bhSscPXgCoRi6h0=") +ORIGIN("ybrantdigital.com", "91CvRjHVBO33qCPmJ7HZP0gGZaouB9+a0q9qIB75bE0=") +ORIGIN("convergetrack.com", "DXCSO9vK2VjV/Gid2eKrrBd+JL16O4H+sc6f4uo/KYw=") +ORIGIN("adsonar.com", "jkkRP78rZLO88Q2qHg9kKeO7NeWj5Z3sH+8MdldckSo=") +ORIGIN("pepperjam.com", "RCn2jIY/bio9Lfn0kmS/eDImuQj0ExOWD9PvGsC2KAY=") +ORIGIN("adelixir.com", "tuhy2eIt9dDToVjuGywlYB3RNFFJ2vRVCQTCfLZXsPg=") +ORIGIN("bmmetrix.com", "05E939UEjPc8TXPXZBGjQWCUjeWUxC143mwUMAOPI+s=") +ORIGIN("google.com.bz", "sNGtnPBxrq+GQkTS3nRfzgu7fEQHS2DSeMKLFt9dzSY=") +ORIGIN("google.co.ma", "ShjAECiCzz750nf/GUc4dq8rKBJkCuV9i8oytHO2Qhc=") +ORIGIN("performancing.com", "CETLq75b/Z1eSZ5cAPvrKuIy4VdWe/3T/4YiQo8rWZc=") +ORIGIN("legalredirect.yahoo.com", "0rnpcvELKPfhygkqucWC7awSvO6L1RyLZDvYAKaZbtA=") +ORIGIN("play.google.com", "j3TbCCaFHBTOUtUkEk+5tCI6jmveHzovWeoIUY26ceY=") +ORIGIN("retargeter.com", "gwmltgCFeaUs8eHrDkBrDcsyXbqN8dF1xbxqPtcxTQM=") +ORIGIN("facebook.de", "UyI5vMnt92gQIwcHmL7l7F5Ka8fAu2jh6OkJnkX9/5Q=") +ORIGIN("crsspxl.com", "/3e8PFKLOpN/AJtTs/8chkm5vJ0NysM3rGCrYd00iz8=") +ORIGIN("5min.com", "zY2tj0G+tL0D+K0O9oYjMcEcDJX5DabI2dB54U3Jb28=") +ORIGIN("hotjobs.yahoo.com", "5zWRNgLTj6gg2PasX7vklMP2xSZ8HYPef+AQr8XwfR0=") +ORIGIN("histats.com", "Ec09xsPXrAwk5ge17DLayMyaZvmv5w31MWXdGR3Apdc=") +ORIGIN("google.co.mz", "dzDVQtwyBvkXDFTXG26ZH1XQ48360QIXnvMQHSchFX4=") +ORIGIN("digitalriver.com", "oqHggIDtFPLtcHOGFjL7QmQHaGDVBz91lhCPQH5fw6c=") +ORIGIN("sphere.com", "XBRq4KRFUGNUKROrH7MMloKBsA1ASRJ+If0wnwfgq8c=") +ORIGIN("gfk.com", "a4Pd/FO+ApJ4hDcTFrz8+Wt822YNVFGZuSVTXj+Qi7Q=") +ORIGIN("codesearch.google.com", "BWaynFWySl3kyxjJMly3IEreP5/tFRBLKST7j+gr8mE=") +ORIGIN("ad-maven.com", "NQcQ/bsnyuB4A+g4iBNv1jwVQT/+8dlShbzx3pSeEv8=") +ORIGIN("adpredictive.com", "vpVhxUwzYt2WX/Q7xXCfc+l74LIVlmM6XtZboDxgv5w=") +ORIGIN("uptrends.com", "NK8B32W6lV3g6vK2mFbLRfXSCCzPz+Kp4O3eqmdt6js=") +ORIGIN("operasoftware.com", "vvknWHMmxR8dXcuPul5t5XiCyPYmvkfS3l0DqvXI1aM=") +ORIGIN("facebook.net", "+P10RWqWUlKqc7QgFK1QtZR70eC/KOv0zDgGFhoO8AQ=") +ORIGIN("adultadworld.com", "3SvcSRWgZBrrL9CW8Aue11YMV7M4BFX7yS+Qxs0z72Q=") +ORIGIN("cambio.com", "eIonAX8ACNj0numGfpmVbg3lxD2KAz/BHHwLPGtjl/U=") +ORIGIN("hitsniffer.com", "dpxIvtQm1+aenv6dkNcs7Zp3clf/RMzeaDRZnHBLYGc=") +ORIGIN("ecsanalytics.com", "xTEedVDnFy5NsydMT63HJanqb/X8WOe0lkWktJiJrL0=") +ORIGIN("adzcentral.com", "yh2xJZv9Q0GkdPsXi5ofQZ3c4WoAbmwxbD/7PnRwpR0=") +ORIGIN("google.com.bh", "zxCVLzVSfSjiMHin972TNjrLG4eXaIOA/GVzeOFXfaQ=") +ORIGIN("contextweb.com", "mACymJ5S9fgbKmG0eFydHOAoXN+pevZUqVP8XRlGG2U=") +ORIGIN("google.com.bn", "7HZo4ud2VB+QOqp7kcPGarqT0t8XtmwWpfL6xjKjvOw=") +ORIGIN("inspectlet.com", "3qiiBbrR01Ezhj0uWq8po5X6oQU2lC3/7bHUH/pof6o=") +ORIGIN("belstat.be", "Nz70OfEoAl9pI18Vmv7R7wApjuev4o3ePpphg5gv610=") +ORIGIN("trafficmp.com", "A/hAVx+2aUMa9V+85J8fw0499VSG68yw7VHr4/3qjns=") +ORIGIN("lyris.com", "63ow+MVunQ46Z4HmAPFKTGYNAJuGKPJW4+y4TGOIauc=") +ORIGIN("avocet.io", "Kj5WtrxLxQMLsUQn2442MBZw6DRxnbrubRrQjIOYtnc=") +ORIGIN("xaxis.com", "UJiTht0/3FcJsFxoVJdrUxKcs228KnIq8DglTXBnY3U=") +ORIGIN("vresp.com", "WEQYxN1Ks63xWNpLq3qPcxBC1JuJuwdZi8XiLtQqB8U=") +ORIGIN("lzjl.com", "Gy8OsQkqGISxYgl7xJSRljaIVFO5RtdjPOpRs31SLyM=") +ORIGIN("idgtechnetwork.com", "tapYb80WRoUV8WBKKWlYNSTMtslJHklzpPi+2rkig0I=") +ORIGIN("buzzcity.com", "5Y71NsBoY23x9pChQX5LYhjM99plPt5dTqr5x10VIs4=") +ORIGIN("globe7.com", "b9MxXfl8hgWaRnRtl0A6x2/ZehErKsvca8wKrum6mmY=") +ORIGIN("admaximizer.com", "yQdBdTh+2TY88vM3uFhOCRuJnHgT7hmrNBdcygyjKfI=") +ORIGIN("webtraffic.se", "x15jWGw9jQnkyHdVY2PeGjYN2sx1/IFlZJA2rJsogWk=") +ORIGIN("loomia.com", "yng8ClckuBys/tgoyQEc7NzCQJDKARIf0snPK6rppls=") +ORIGIN("searchforce.net", "1y4+rcwtzBLOWF5faiZQLe/qdIM0xssCY6lbdMtfkak=") +ORIGIN("everestjs.net", "BL8mx96zTfnc646A6K6dZ+PuLbITPmi+Yonh+y51mFc=") +ORIGIN("webtrendslive.com", "SO/L154nrRfwQIKfflRw8uNS60sDnBpqYNtnf/3ZtJA=") +ORIGIN("fanplayr.com", "XR2GTJz6hQyuov6O8ibdbf2XE+bcWzW0Jy0ART3ioso=") +ORIGIN("extreme-dm.com", "Tz6gauTEZnsR/LsqvTH7oV0RPQ6jmnEBGxnOu4mS1Ig=") +ORIGIN("edgesuite.net", "PHbUopy4jkciQfBTV03WGEe4uaRIbJ6B1VklU4AYing=") +ORIGIN("weborama.fr", "UWSUJDlJfAKZNFUaJ8zT5XxlKonYZEYZM/FXwhszdIo=") +ORIGIN("resonatenetworks.com", "7mR5fWMBMnFmgYoMOdWkwagv/2b/vjM6i2Ry/I9KFRY=") +ORIGIN("rhythmone.com", "vUKY/qmCgyCFeDvKhZZz98TI4FIwga+q0CmSaIjdaRc=") +ORIGIN("localyokelmedia.com", "fa3VrSV1DeA2NIL7qyg9k9HPNyhHb2grie3ItXqn+zU=") +ORIGIN("convergedirect.com", "rFFl61m+cyUBs5zcB0AeypWwJj4zkbH0lQIVGiOIMEc=") +ORIGIN("postrank.com", "diuWkFWmauSQshcUpHOfJGVz+dvfbyr1SbyOVWFDdXA=") +ORIGIN("bkrtx.com", "EchQwgMoeVAIsXKC92PGhrIWw8zLGNgDClLKKvO5YQk=") +ORIGIN("nextstat.com", "AWh9sMGdV0BoQx2yNotCfHhvVS9WWc9w4InClA8fNIA=") +ORIGIN("socialchorus.com", "Ie/7IWfwNZs+TC11sdD3JouyaSaUhIE0tpl48VpEGFg=") +ORIGIN("clickhype.com", "6xGz0UVzhJ+fXMT6vvTrs/I9PaAhgIiVI3J/U2Rupos=") +ORIGIN("psonstrentie.info", "cGAM+JRHsInNdT1F0Zpbr57ZJIa7+ymq/X4V104gO68=") +ORIGIN("oneiota.co.uk", "dfpAIeVW1wEaeeLGpeOu5MG+g2xa5XifFQMeZjIeLP0=") +ORIGIN("sputnik.ru", "/gMvsTnrUcIrJcVQ3i3R0W7U6sBIkIcBZMXfnAEI+OI=") +ORIGIN("etracker.com", "8qUQYkcXPawly7pN3QS/ub5wHzRhT+TH+AZ6xqGQZyE=") +ORIGIN("alenty.com", "LZHUMUXlUAli372+imcTv3sAQbSlz6//Z8W7Pqk+5vM=") +ORIGIN("shopzilla.com", "fXcJcKZerEh+0/edq4cHpAt1a2Sj/7dWVaUDxCRa4l0=") +ORIGIN("admatrix.jp", "0F/8/dbSmdRjpCJB9svNyoCO1s6/k3wCKL/4V3wErfs=") +ORIGIN("ffn.com", "mvfKH3PSvEr5livAMr4yERj3FmI9O3DF0RzWH6jr7FM=") +ORIGIN("hotwords.com", "M0kBNa3qL2Fzp4HK6zMgelojPhAeRW4pr26IfRx0uos=") +ORIGIN("adobe.com", "FlCbaZR01PhErY63f0goCj04k4OLGgE9zAeTinsLYPo=") +ORIGIN("google-melange.com", "OuaG4QrFEKPPW3o3tBxhGO7wgqnkOjLTNDLOfWiymfY=") +ORIGIN("addvantagemedia.com", "+XorU2mTHixNiDCk59NF+l05vVAB11pUV7b2rRQunyk=") +ORIGIN("marinsm.com", "OCzQpKu/qeb/9hZFeuIp3ht9sPctqcGKHwi+aPAbB5s=") +ORIGIN("qjex.net", "/D1XToBeK8A5aIxeONMps1CppJpdYqXIHmfmntqW7aI=") +ORIGIN("tonefuse.com", "BJoaCN2K9e3VHfztUD/QVi6cuhjKxl87txMqiDdcQEM=") +ORIGIN("quismatch.com", "NJuZ9pOxS8IxQf7laBtVnmq+JqrTrN5qgHZNi0fTaCk=") +ORIGIN("do-not-tracker.org", "sQ8o1NENpHrlnitJQoKgHBYP2f0rYhLbD7IKR3bHTJg=") +ORIGIN("reklamport.com", "DTaTz/YB/akSuliuI0lljnQNWs5Q4Jb1AZyWbAASbTU=") +ORIGIN("vimeocdn.com", "Bk8jL5uM4wPXwywJkXLsNrFDOUbfDOIDrZiNNr87RHE=") +ORIGIN("vizisense.com", "DnX9MSgetgYlXavkU9pi4wKf4aP7h3BeQwr6sIop8mY=") +ORIGIN("gamesforwindows.com", "XwEqDJxPIdcePX0oHBMYHNn+bOdiANlg+HUmmx1G77o=") +ORIGIN("yellowhammermg.com", "QQbIonUO7R+209TURFtSolDsN6ExCdVxsDcnjsNzN4w=") +ORIGIN("netseer.com", "lD/hzZ+yYQCzoEbcwzhC3C/UNwzLW6+xtT57ckPUmYE=") +ORIGIN("crwdcntrl.net", "c8dUNdbMfrQtbwiNBUhhCO2Nk9Q8RVx2ib3IC93MGUU=") +ORIGIN("inviziads.com", "EKfVWxx0iM9NNCh3AXmrHqqO2fBO55NkJ4rNl4wFE1Q=") +ORIGIN("mail.google.com", "EVqNwgvUlPp21Cg1gTu9o/67iQoOeoQWLjY4DwSpML4=") +ORIGIN("silver-path.com", "aiU6P4feg6jggb8og47s2UsTR92fKC8akU2OtJIqnls=") +ORIGIN("usocial.pro", "UEuRz89KapQjCap07Hj8V1cyW8KPR1NmU63IukffD04=") +ORIGIN("feedproxy.google.com", "soyMPUqdz1DP0kl6fTYcqJ0MUNszZ1RgHJ6ka7X1Tr4=") +ORIGIN("blogcatalog.com", "V9kEIey8v/0XyJ9hwmHSqcJWMHpn4d7htg6ybbl4HsM=") +ORIGIN("zetaemailsolutions.com", "dzR7bu2HkyRnRgPfzr16L8m1BUJjXrKvYOYMMqed3bk=") +ORIGIN("brightroll.com", "YsqaMb9rZU+tyl/EbYmlut7kLZemJmCCsMC9NrEJDW4=") +ORIGIN("iprom.si", "LtQnXJ9kKL+zyK8ZRvJKJgd9pIGbRFuqeyy5wLE4lR4=") +ORIGIN("blaze.com", "yips+kva6VqnMXKgdR7/r++cQmgr6GAJaCMTxx+vK6o=") +ORIGIN("adside.com", "iZrrygOv2b5qaULjuzSQ5kmY00JTs7aPLf3ejmomgXc=") +ORIGIN("sophus3.com", "pd/5CqTD2Q0/+jq0dGQb/amR+eC2jajzgExTYkaOdGo=") +ORIGIN("aboutecho.com", "FVHRLoqdLo2y4StPhazkXYVaYUriaVTjw0NoMRIhw2U=") +ORIGIN("nugg.ad", "eFYLiPXueqloEBQEmDk+esAbr5Sb/CCzD7liNES3FUg=") +ORIGIN("gfkdaphne.com", "IY36+E9JReWD4ZCd0UkBf+jF9w0sKHVUyTCcsMZK72o=") +ORIGIN("office.com", "u8ucZsw2sDq966c98tMjhs8QkZUhrv4PGnYDn3dPwYc=") +ORIGIN("ooyala.com", "jyFfTg+Nf0o66V3L67XdwZGJ/W1ZyWPy4LC05pArm/g=") +ORIGIN("drawbrid.ge", "uhaiiprZAToXZPRBilSISOTlwjjfFXogPrmn2hF9fE8=") +ORIGIN("eyereturnmarketing.com", "cYl5I/nQ+m2S4DxLMugkD8+sgF8t6llrynYWbyxVIKk=") +ORIGIN("publicidees.com", "0+DkU+xYxynLOD2ccScpe7Ahd4s1yLs7QBLtdJq83hM=") +ORIGIN("vimeo.com", "Lrd6afAXxiv07BkHlLf5am3Bn6HRPnE75kDjM3HiYv8=") +ORIGIN("cbproads.com", "kwjC63twLD0e9zHNg7lLFg/uzi3j+hH9oDv3YyBY384=") +ORIGIN("adglare.com", "xv9t5ZlWdr7mIrl48AO14N2nNzDb9VZt/3LQf5PPUWk=") +ORIGIN("leadformix.com", "bax5UUC4FHZ+YO4yLKBiTMLPkC/f4Y/Oez8CLYtH6RE=") +ORIGIN("tellapart.com", "W7G2XWe65Yyd3oio/bLxS7XfQw8Nu421O1atYzu2dBY=") +ORIGIN("mail.yahoo.com", "1rLzD1viMjF7CLEIc59EWufKqDeoYiGL0Pfkr20L4rU=") +ORIGIN("amazon.de", "wEAf0lX5nZLBCM2/nHrScJUFJznvqICFtfp76bqAjXs=") +ORIGIN("clicksor.net", "dIP9Qc5zu1cYmzdTfPgWTvifeTOOqO1tXBQKJjC40+s=") +ORIGIN("effectivemeasure.com", "8UU71AwYL1Q8lF+XFkbMyDzMwFM6xPp/wOkLnO4wTQY=") +ORIGIN("clickability.com", "Pfw/wsSl79rWe9WwJbKxKXmGYIPhF0utlJrrgGPPEV0=") +ORIGIN("p-td.com", "6Ne0rjsv45sNv+LxGsX07nya+4T7MVOdOscHqdMewYk=") +ORIGIN("roia.biz", "hvQfW9uyCefnJdVFo1oJknhl3GuMm3S0Dp7VrTlDGXQ=") +ORIGIN("google.com.cu", "gbnixk4hNDbtRUhytxv3WaGFoJ0VF8i31fuE9a+ChmI=") +ORIGIN("staticstuff.net", "wLWeVLBJCDUYQSi5rbYl8MI32OW5kmWFNRfGI97bIYg=") +ORIGIN("google.com.cy", "Z3jeUlpBpWP/jcVQXKvjDpNPmtF6dLDycgc51ndeGpI=") +ORIGIN("intelligencefocus.com", "spmalYlfF4nprunctTwRiT+US5YqexulnMxjbzFLlhQ=") +ORIGIN("decideinteractive.com", "vBB05juGLFuOBy93fk9s3lazmiMJ9lcEFDzxfEofv04=") +ORIGIN("google.com.co", "S3GWHUYtC8Y6dALdA+P/wxh4UkUseHpzNAZmpMJY838=") +ORIGIN("ixs1.net", "N49ymCbtISe+a5Vsdt0ow88jzdOmFqQx8CQUZhYZD4g=") +ORIGIN("leadforce1.com", "l0SspEq8ho3jJAzLlbO2PDTk11THouHgCLglktl+zgg=") +ORIGIN("truste.com", "MFUVNxulamuMOYe838yN37dzuuSU0fjAg7ohvhSXIVM=") +ORIGIN("adition.com", "fSoWNG7T3lI/VEH/0EU57ZB23ssv94Ioso/INXX7im4=") +ORIGIN("globaltakeoff.com", "NRB0Dmx4syRviERwsgnrAH4l7mMAFxoj1A7J5q4X/d8=") +ORIGIN("bunchball.com", "Pj0Z62t4sAM2F4c3KcMz4BS0Dj4j0G/+u8ZDjtsgBFY=") +ORIGIN("actisens.com", "VBbN6c8FMyixSaUTb/Bs1kT8ZK8vM45Yf3n1m6yU4Sk=") +ORIGIN("ypolicyblog.com", "57/thf5QHzgo9NkP0ho4dkUwDGMx4ZsxCldPFvhglNA=") +ORIGIN("affinity.com", "7QciEuTqHy6enlGKo6qzr9GzQuQhfL/oMemk/v1tyEI=") +ORIGIN("103092804.com", "5jDWdRlk9lHC1EjLjXq8cU/kBb9borIyccsOMAX6ing=") +ORIGIN("nedstatbasic.net", "npHeEKB4+gsnGDB2VUuERmJV1u3xSEy8BDpnmNYLFZw=") +ORIGIN("z5x.com", "7SQip9j8IxSLiLj4iDbH3jyaL9JPNg+NgIBP0G5SDa4=") +ORIGIN("sublime.xyz", "/+44zHotyS0zm5TdLubaZUL00qcq2364H8dRVxFg91A=") +ORIGIN("gismads.jp", "n1TcDkfYJSfN1Eup//o98resGYCeRMsOmQdMVnW5+84=") +ORIGIN("channeladvisor.com", "+WEd2sGf8fUer9CXE2LFhpeWUEax0NExEXFLlvp9cus=") +ORIGIN("plus.google.com", "dCUT61Ynh8StdDL7hMjrqtyRLLXjuljGr18zQCl4bOg=") +ORIGIN("aimatch.com", "sN1pFYc56pIswm/GeOi/W4jcyU5mu6R88XKERqaUfgQ=") +ORIGIN("jumptap.com", "La49XRZT9svfrN5KYnO6Lg2/BTlkPwYisVsxj4YQXEw=") +ORIGIN("hubspot.com", "vfFsqOH73q75yzwzKFRiEEz502OYvp4qLssbjIGh1/E=") +ORIGIN("getgamesmart.com", "m3i06IY1MZucJMEnQg7zkftCoTgJJUIKTTgl9n2nw3A=") +ORIGIN("adnetwork.vn", "knfS9opi1rk7ftQmTHoSeTm1WvIM9qvaUeO69FJYC4g=") +ORIGIN("inq.com", "KQtmEPSnnaZUbrVbbcVl6BtU8Vfygpi2ga/Ugt3YkI4=") +ORIGIN("feedburner.com", "E/+J+AF889i5QV1aC0+K2FLlMDHA/X7abW/BPggPHaA=") +ORIGIN("automattic.com", "3Mb1qQDfHtA6xWj14acAk6/EEd2/SR7wPq+izh14Hco=") +ORIGIN("adeurope.com", "z+mMgB7Zk0DBevXr5fO7lVms7N5/nUP7Hxsu3HeA/OQ=") +ORIGIN("genius.com", "25+RnbpWO6HrEEWUktAmddFv1MzQjATjq9gR8HOmix0=") +ORIGIN("upsellit.com", "p7R2p7I9x5EuhgNQJJNmM0e8Lpjh4JuUHzCT7HXJH1o=") +ORIGIN("google.ci", "8RlUYPxUENZZTm0sTCIJmV+ltoOEStBZCn5GR7Y0/Vs=") +ORIGIN("kontera.com", "Yq7kqnlKnbwt6qqsiNg5jqfb08preP53hraDKWEbbgI=") +ORIGIN("nr7.us", "ks8rs8ADHg1Dvwz0yAGdlc73ZttUFO/bDLZyH2xtrjs=") +ORIGIN("smaato.com", "4cOlKyV3/1dxo8agsrJlUuo0Ytouk8X4U5HTFiLW7lM=") +ORIGIN("tremormedia.com", "sjgsqsBtQ4nVvhliZ0uRSHZe1EGPbxi/44hwIAflqMg=") +ORIGIN("adlegend.com", "0fqv3+xIncxSkv2aN+L/607lzP9Lhq8V1QLj9a0qF2Y=") +ORIGIN("pocketcents.com", "rX0C6QuqxkfJzbBrgMLfMb2+12LIY5KeFUB9V1Ot1Y8=") +ORIGIN("webclicktracker.com", "5VVuv0OKtUVLmWOY9clA4BgcQ7fd1hkuYhvthxX/UJ8=") +ORIGIN("appier.com", "8I7PyxxPoH4m7yQtMcHGq6ltS0m217mk30NCdqes9oI=") +ORIGIN("double-check.com", "960rmB7psH3FkaufvjfvAhgRnKRfDKKgB86m503xbGQ=") +ORIGIN("stat-track.com", "muhb6wAtUneh+r7cnW2s8GpzwfHl7BED2IWA6QQx7LI=") +ORIGIN("sabavision.com", "Fh6A2K1b6XluE3o6sjJc9B1cJNqDnXuRWRZ8Dpj2nyg=") +ORIGIN("sabre.com", "P1kJVps8e6DmOcVHltseqvuE7Ahb7wfJ6Gi2+1jl3qU=") +ORIGIN("searchmarketing.com", "FeXRBc9S08/s3LIZ/KKq+MoC3+AubExgl1xzk7j2F24=") +ORIGIN("ucoz.ru", "sxFwFMsGASa4vaBE07mSKS2uhEySbDzPaIDWBF4n95k=") +ORIGIN("adsbyisocket.com", "QqMQ5kyir/hVU/BE4/uSkUBlZXw2bX6gKub92fuI2dk=") +ORIGIN("steelhousemedia.com", "Qe9ElOgZne2VBtBTrjCyfGW7jOzbwbE6IAJV9Y6gcOs=") +ORIGIN("adverticum.net", "5s+ae9gmMF7ztxFE8DKMBYP3PoNg4xOHCR8AVylMrdE=") +ORIGIN("c3metrics.com", "rBQfTIt689pu2anCTNotawqoL4YAdaFX1DkCmCBKYKY=") +ORIGIN("qualaroo.com", "gUulZ1vu/vFVAWpuA2I3hOT4/2SN9oQEkJOeRb8Cd8Y=") +ORIGIN("lowermybills.com", "24wNKxq9y7uDmXqWNi84qQJXVbiZnBgAkWLu8MlBdKo=") +ORIGIN("openstat.ru", "dN0fQ/Er9DXtu2j9WYLL5iEsQPLG18FZcnWkCZfTN6I=") +ORIGIN("sproutinc.com", "bQgehomchXASb/VR4mcFgx834U9ETZ3HSeKpGK+FTlM=") +ORIGIN("zune.com", "ez2LMRE5J/Lvgwy8JG7KgDzKuZLEiwotOQ7XVYFWSFk=") +ORIGIN("moviefone.com", "3n2H0YVCxjJ+b/ErKc74mjL1jxNyZgklAi4CORZT3pQ=") +ORIGIN("nabbr.com", "N3WE/M4EOORm5B0CTjIwW0/iGfjQ2GEfAP5HiF6wLuc=") +ORIGIN("adworx.nl", "UpElD664AaoORabGTZqggrD6WjoGTQgvgzxFt22Ofkc=") +ORIGIN("pinimg.com", "KCy4/gS/4jgdrcCIqXcU8GJsFXsjSVwEUBsX3cDCiog=") +ORIGIN("tradetracker.com", "29kKXg5PaWFK+NsNVDkEJus8p/BDdR3IuXB1Sj9feOg=") +ORIGIN("juicyads.com", "PtO6NodUOA97J1bU2TwwomZ8X3D0P7d/n9di5a5wPA8=") +ORIGIN("amigos.com", "76odtdlW+oTxhtN5V/UPvNAoeEW9FUDMYeWgntmmWTo=") +ORIGIN("google.co.zw", "/ggwpITSE8Y37+Qj0N48DrdI5XFnG+6qvKCcIPwvgFs=") +ORIGIN("intensedebate.com", "z4R0je8lWHZ3++m/1mopmr8cCF049PghhfMDOSai1ck=") +ORIGIN("webtraxs.com", "AvT8nsGZ1lCGxaRvs8wnoQqSM60IENz6u2lBzgGjo7M=") +ORIGIN("com.com", "c6KKDWpLwNcMXBsAa9/V4K7JkodTz7cCNEsw6Db8CUA=") +ORIGIN("wiredminds.com", "0sGzWwxDUUplV+S72n/wAPq2uVFkvjk/RzFeFRF54Og=") +ORIGIN("netapplications.com", "eyPSfI+fQm7piiYhHQ2Mxw3N1ug0tko0b60aicP0mqo=") +ORIGIN("google.co.za", "wUDP0/Y+TIB3Zr66HZkuF6DDudKL441t6B7vNdibIMc=") +ORIGIN("google.co.zm", "/HoIxMUq44R4bknVHzQ9WxWQXi2sjJch55y6qlBeHLY=") +ORIGIN("addynamo.com", "YCvtDJHG4f6kfTizDwHaVsYq1a93Oqmoe11OzAcsFdo=") +ORIGIN("phonalytics.com", "4rz2mY/zrdDHKQh3/UwHCSa9vzplmoul3LiBdNjD7EI=") +ORIGIN("tailsweep.com", "peZ+47Qru7eBiN0OqpZ6HffZRsb7qWRCunN63q+ni2U=") +ORIGIN("google.it", "U2qNN9DGbXcbTqBk/6KoRqG4PlyBohs1JMKEJ/oakkU=") +ORIGIN("google.is", "gBvknSp9eKN/v8WqmFgeiICGqUQy+HRuNa4A5I9Bcpk=") +ORIGIN("google.iq", "+0y8kLJ5eY7cxZcrE4wq6nigj0BrumUSFptBFoFtIOM=") +ORIGIN("ydworld.com", "iAlX5sCM/AsPeKF//yFhH2WbSq9bp3nGm/FWRSq/HIk=") +ORIGIN("theboombox.com", "5hsvqyy1+oNrZ9tUlw6oATjDJWvtbeIARueyI6SDwuM=") +ORIGIN("demandmedia.com", "F8iRhSEEJ7aC0xrTiUu7YylQl4FcReFLov9rgj/tMlg=") +ORIGIN("monetizemore.com", "g5lPAFPBanYsk0yVUMNsSVQHuPMiStXf1NFiYbfBeOE=") +ORIGIN("adxvalue.com", "aQT2wZQTZpRpHtitSXIfXVzOc/ieHGjPAWIxkcnXFWk=") +ORIGIN("google.ie", "PvBSwtAZHLbYSwwZCcYakq9i8BmJwyXor/ZGpZZPzYo=") +ORIGIN("instinctiveads.com", "6YMpF37RjCxldZXeZRRahxsvw5r1CSC1bpSwOYSkz4E=") +ORIGIN("velti.com", "0efpZvDV39K6IBsG/SVz7cej//zQx3/wS2/7QFOW2MQ=") +ORIGIN("researchnow.com", "TY1QEneMQukHkyzkKwQc+l7s114hOP1XqMzwRivp1RI=") +ORIGIN("google.im", "580B/6jd5Cglnig9kX/kryURbUpTPFzfFNRL3XB65aw=") +ORIGIN("socital.com", "qjdv7UoPpnMp13E/KcgQ9dWwnTYmKEgUAMy0bAZSF48=") +ORIGIN("suggestions.yahoo.com", "sUtFAUwZwm/8k0p59gqPHLLFB3KMhhVKnXqoVXfJzhA=") +ORIGIN("adnetwork.net", "lj+8DCwDS7UE9vNzHoqsQMbatyGJBkYkbbvSDtvt1P0=") +ORIGIN("eyeblaster.com", "8WdyxvvUvALmzEYrJpsgB+EvzJGlFoqsf2uxVDMcLD8=") +ORIGIN("xg4ken.com", "W4pCIdSPaCGIhNCuLN0Zq3lDKBLQ9Lrl28XNdXjvB8c=") +ORIGIN("percentmobile.com", "Q84jmAJLkDQq2xRLsvP0/QLbkts1dbTt+OWDts9a2q0=") +ORIGIN("33across.com", "FZgvrkh1i/+6HQpPo8zqGofoNXWrtVJJDnW+/wwJtOs=") +ORIGIN("rocketfuel.com", "jAtAu1hVKvQSPT9e2xX7f977M8A0w9JGilkbX6ipl9I=") +ORIGIN("coremetrics.com", "xIiREq8oi4GDVbAsIgFNJGkPsNaY1glpp2hcG0bXQjI=") +ORIGIN("mobials.com", "tDxvo8/UcF2KlNFOty8yeibBQrbc4kbW0PjPRwGKoj0=") +ORIGIN("awio.com", "wJetY9lUx1vvHOHeuZZd79efwS+notBd9yrucMfOBc8=") +ORIGIN("thenumagroup.com", "AVmY1yjDeFqj+30fGuyIYUZmdWqNeKIFzBnXjhLpQLI=") +ORIGIN("keewurd.com", "C40AcvyGrDtbYLN8rLRDTO5MbInH+zglTq//1C3ebs8=") +ORIGIN("adstours.com", "mHbZ1D3Uiaihjb9YMkhXkw7DE3GoEvDx0LoQP9/OZeY=") +ORIGIN("qoof.com", "pEDQapu3XbOxR9XVpPoPFUrI8x+la79O6XzQmp3A71Q=") +ORIGIN("smrtlnks.com", "f+93xuCJd0s/3aJHhWmgncEhxgPqlrOBNeXR14GzGwY=") +ORIGIN("owneriq.net", "2mdyMRhajhkU8MEKxkaJ+EaM1cWQKmEP0eif4DZPIho=") +ORIGIN("webtrekk.com", "TdE5eGpDmxlOcye5v80DxXDVNjT/WJ0A9I9l922diFA=") +ORIGIN("yieldivision.com", "fmitR4w0SxpNLAeuqtLL1a4Kjp/oSIqxoTDbPgeNx4A=") +ORIGIN("jaroop.com", "KPzOF0B6s3xqN2Z8I1IA2VyZKHWnT+oNjnO79JUP9Z4=") +ORIGIN("sitecompass.com", "c/uxDUITNfQUeJHDdSrBUccb6LkngROypU1EoAZ/7OQ=") +ORIGIN("addynamo.net", "i0eWqs1MKpVP3L9hLrZROu6SPoorCg8ZmA4qSajssK4=") +ORIGIN("am.ua", "AX/SI6SF349nsFSTCjxCd0izdKWO2DSqACPOak466A8=") +ORIGIN("maxmind.com", "nZEEvaXcQqXVknmE88xqI+Yl2VI1RNQJv3Cp8WUfjoM=") +ORIGIN("adsrevenue.net", "GqrX7nlxQn7OEqjmEDBVrypCwjyWXj3yDIbvQDY1/TE=") +ORIGIN("acuity.com", "nOb7beXII3P/2pDnBsKAAYcwvLGsO8OTxhqDiuWizbM=") +ORIGIN("adready.com", "1JSMn+0P/BayAovarC+Ti+iBsNDtsmhzPsfT0sqRHhc=") +ORIGIN("mobfox.com", "JnLE/J0LV0YyGeQKC4DoFwxtRrYilBm6EIkXakWLowo=") +ORIGIN("optnmstr.com", "mixnLRFrXb9/j4IYUpF6VmFURJS/KYCJRt376PiORSU=") +ORIGIN("veeseo.com", "z+xZWvGNxMV/ATSYr9T0W8l1ctiYp5r0MrT5mVSG4FQ=") +ORIGIN("google.co.ls", "eKmbkkxrPGi9K7U+ZX51nxj5LQF12aM0gxQuvCMY84Y=") +ORIGIN("mywebgrocer.com", "yJW7xT48YNTutq6b2VZoJ9fxp17Y0nC+nBaRHlTMlbY=") +ORIGIN("andbeyond.media", "zKMRvR3bZbYfeMOozv3iwI3kKgFh1qX6GaxZFThsSWs=") +ORIGIN("amazon.es", "D98NwOvz5uUufPccYWxC4T1TGdCALCLT5QkhIHkstwQ=") +ORIGIN("bluemetrix.com", "BiMas+YlzPfwZvOl0c9vwo0gulvQDfpq+8XXUC3waSc=") +ORIGIN("officelive.com", "55VZH4JJ8GJrbhD8PKLMKIvGR+f09k82DJZZwy9X9Jg=") +ORIGIN("banner-rotation.com", "R7Sg/bMr+7cnw4+glviPsWSFIhBykGTKJS1c8BKC3mE=") +ORIGIN("tribalfusion.com", "pjIyqddRcHdfD+9mPWvwsOSFRsDzpvEKzON7w7xWa6w=") +ORIGIN("quadrantone.com", "O8w1rWBMV7Xwh9RksBNPoPhemP2bKxuVPkrcbrmWZq4=") +ORIGIN("pch.com", "x3eO8nRYA5dWIjEnwhP5RNcm0uWrgpNl+EdOeo8PxaM=") +ORIGIN("adlantis.jp", "CEwGmCG4AyXFu6yI2Gp7EdYTlh7fdMvZuo+DfoFT708=") +ORIGIN("adyard.de", "1JiCSZQq3r1o6oabYWXuGGXTTMahxHW74EdJk2NDOio=") +ORIGIN("admagnet.com", "vU9/CGC+4RwUtjeChfiyU0YalMJsU120D5cvCiAia8E=") +ORIGIN("krux.com", "lkk/Hs4FNlenTotUn0f9ruKbTh0bz5zB41q9fJ+JuVI=") +ORIGIN("owneriq.com", "otZi6l139sg0LsCgBgW60L1IJP8G5meG5sQIP5YwGfI=") +ORIGIN("newtention.net", "N8dpoC5YSuTqKH7DT3lWoX8JFA1Ad8m9YjXpo/XaQgo=") +ORIGIN("fbcdn.net", "86fFUL2qrG1elBfJWnuYu3+Lk3FxGZSjRRjnMQqiUu8=") +ORIGIN("skribit.com", "I0igNFi9QFB0smx93FA7eub18bV/MkC9JMvuBTDb8sU=") +ORIGIN("specificmedia.co.uk", "lItXPpboiYMiGrYTehpJvwZcbr7y33bHlnymB+nntzc=") +ORIGIN("livefyre.com", "ySVutV1Nn+gn8Y3YIjUEHBtf+iS8bKzsd/4jAWQUdjM=") +ORIGIN("piwik.org", "5NNw0g60XfWQgCWA64Ubjse6eXrQJIZ0AoZJetYwgeI=") +ORIGIN("scribol.com", "RGvLXspeGH/KddJ87QMVIluTWMMBm3v1EP78VuBVyEw=") +ORIGIN("visiblemeasures.com", "MpWiwtyDxXsPwRrPkKuPdxWq8EKo57B10aTS4SWa0CI=") +ORIGIN("marktest.com", "imUuVTwemnFG1GTrrHrkFOv82Rjy0BE5pinTSdRRURQ=") +ORIGIN("onetruefan.com", "2CsQMBOQrcDthef0okcjbBP79mi0Gq3zDJtHi4Tdh90=") +ORIGIN("verticalhealth.net", "kuJUrZobOA99Krqt9UhQ78ZW6CKcffzIFpitMIwjs8A=") +ORIGIN("ivdopia.com", "wywT70v4SapDgoGc6fUctMKGf8tkGHUWEKsuTEwffSY=") +ORIGIN("stormiq.com", "BxbNDSzQkqVl9PyaMXdHgDY7RaF++vY3vW07TA1cztE=") +ORIGIN("wave.google.com", "4Y4GpMvNlIZIBarPZ9VtxKBAueBLCI+HWVGza2tkV10=") +ORIGIN("advisormedia.cz", "CDh8MVo1zXBBdjth85/zNcN2SsZeatBxyfqdqT9/AUs=") +ORIGIN("batanganetwork.com", "qp4NHmx9dw+MJUu+tcGWBZEMpWFMRiSbNqfcFYKH6ho=") +ORIGIN("winamp.com", "SYJvfcgrNZPLVvi0x0vugmbwVO0gqJLIRp7yf8a5SEg=") +ORIGIN("getsatisfaction.com", "FSlj1x6AJACPA1pxIoVbO5tMQ4a/dSMP4sWcW1D/VB8=") +ORIGIN("istrack.com", "QRMTEIyHFLjFul4ODfuK1fGCTwwvxNZId0bKeh81QLE=") +ORIGIN("brighttag.com", "urZb0cLf5pbUCXg1XhZylMlvg2uXP+GPQRArX9CYGbY=") +ORIGIN("twitter.jp", "2v9NbReRqIwbkOE9UsV/HERW8ZxrY0MwcbUvtQ8n7c0=") +ORIGIN("afterdownload.com", "+IdFGnA1bGVoI0na+F0YzRCbqEClkJIWlAnZQq5l45E=") +ORIGIN("rsz.sk", "ZLtFnCx/zJepMh1hkzpd5ga0Z1JNoSJOgCVs6exWSgc=") +ORIGIN("calendar.yahoo.com", "0vYyFXrGpg/smZlVrOrimBG6HnJP6k/cQZbhpTmNsj0=") +ORIGIN("ltassrv.com", "ILbJ3P+o+EZ2optE9n0+wDkKVYUmHXaN/jNJKtNn7WU=") +ORIGIN("realestate.yahoo.com", "gzo1NwI+VZZg/fkbcBie6bMPMuP9iE3hMtvoM1lkErA=") +ORIGIN("heyzap.com", "N6H0ammEK8G+3Kddn/1d9dDOH3zSyQoJAV6zMc0U0pE=") +ORIGIN("translate.google.com", "2ykQOuwa4AMt+DYKc3CM+rngwxuCnHPEqx+F+pSFKrM=") +ORIGIN("youtube.com", "LvOZqM9U3cK9V1r05/4lr38ecDvgztKSGdyzL4bvE8c=") +ORIGIN("targ.ad", "0EqYvNu6YwkUDKmjaTFucMum04oJAhrcBIn4tW4bAPQ=") +ORIGIN("apps.yahoo.com", "BviAZINJG/oh+zy1nIBxCzWO+1aWJKu6pf7KMjgKMLE=") +ORIGIN("icrossing.com", "tdxmgtSf2aALNbqlM3M0Ewda1Zfu8mNa7W5YgCsFqQs=") +ORIGIN("dc-storm.com", "B6+pB7Q6SU4nDBoFAVu2DBX5/SxBXmLAPXddVt5NwEg=") +ORIGIN("tmnetads.com", "FKPWUWo9692lAMYcGAqYKNP7jfyvHJFvUZRfEgqW2kU=") +ORIGIN("tynt.com", "5/z7F96vqBrvqBnCnptQjrJGvaEHJCLj4TD32K76Iz8=") +ORIGIN("sports.yahoo.com", "rTLgjxadYV44w+liZqrSdCJRtYrxGRnoEWjF3pEgsKs=") +ORIGIN("tumri.com", "WLhr/ZNDswg3uc8Y/aUuXPdwol2KpVRcdNYE4gbLMzU=") +ORIGIN("bidvertiser.com", "u9S5dwNx3jFYgJz7PBNqvUhQhrrwbmw4RvUHDHIA2d0=") +ORIGIN("betssonpalantir.com", "6/uYP15SiPCXO+rVv85pG1OsPGwEZe0WDImaQ3mtGZU=") +ORIGIN("socialinterface.com", "kjsu2xCXea+7E38D/7qjCUtg80VL5e8mAWAHP6yLN3k=") +ORIGIN("adfrontiers.com", "FnB9mI4KSs3vQcB5eav+vWwFRs5KV7wXqkmW3AWVqok=") +ORIGIN("autocentre.ua", "5EbXtoNqrqBh5HJCcrZ6r5Vba5Zbxv1EUCk5aeB9FN8=") +ORIGIN("adotube.com", "TWYwR0mf1lpDrhn2abI8HsIg1PdUMcfgpsad9Yow0OI=") +ORIGIN("yt1187.net", "E8gRuIMxYEzrTiJ6xZ/i6ylESlMA9XzkImqMmSa3Qi0=") +ORIGIN("pixlee.com", "XAY80sQSgBSgkb1hoXGDv7UfBYfRW7Yo8j4RnjA76KQ=") +ORIGIN("bvmedia.ca", "0PNf2vqDlRXjhW++No8q0AqN8FHat6Zbx1TVw5lB2Pg=") +ORIGIN("mediaplex.com", "02wN+pw3B3dlh7pw22uUuFPceDQ7WwoAQPAUbwXyyXE=") +ORIGIN("webiqonline.com", "sVIqa2FqMvDxJFccwQyUQXAbvTL4JLM1gEjET8gWb04=") +ORIGIN("complexmedianetwork.com", "UOZIHG4Y6OtcMPgVKytky5aydIQkW9y2t2ukSkuBkdY=") +ORIGIN("esm1.net", "WpsYp2gkeAoeHRgtlnNxe7cbteSrYtX6cv02zPHy7UQ=") +ORIGIN("lqcdn.com", "dr0qbKP1X4iNTa+7W1uDw+rP4mW7qwcekm/kJvpJf3k=") +ORIGIN("theboot.com", "fc5cngUUBDBsxuXT+YWQbOpdo9kIrAfKdi/81g888Pc=") +ORIGIN("typekit.com", "srgLh3TQTSNcLcGTpQvT51iybygpShrTeut3D36hFZM=") +ORIGIN("adblade.com", "uVm6qGD4wvPVxLRKN2LSx/2x+VqODYTpRWkHBMzlwfI=") +ORIGIN("lypn.com", "Hmc/SWPalBc7Et/5JtU+kIsuZl1IPaCHGNFM0gaeuH8=") +ORIGIN("hit-parade.com", "qaREBOakRdNtXF7ckaRnYSyfUjw5gVb7hgKmdkVbilA=") +ORIGIN("lptracker.io", "zesFc4ajX9O4qLQxSITwKgOZcL/nZrTUzyDLZ/EKenI=") +ORIGIN("datonics.com", "112FmpjR9+hTH4FHFH1mzjWZj2BL8vPx7UlwVuMu6Eo=") +ORIGIN("teads.tv", "UIDqIe6gwzqXsUxA3qWD/1C/LbVp/cvZI1L4L4bkEUQ=") +ORIGIN("grapeshot.co.uk", "OzwfbmF7qS2tJd4YX1DSLrvVwnBV6iyA0Y4w2CtrJFo=") +ORIGIN("nrelate.com", "y/muysFZKKKWWUL0binVGYR6azZ5WJ7qSzniDTQUGUE=") +ORIGIN("infra-ad.com", "Q2R9TTtKb/h9xUeQ9+AdymVgld7sXfL95stOESKjlrM=") +ORIGIN("developers.google.com", "+UDLhMuL1h3C/GXUlim2bopVQNpVlTklpJATRbAnbhQ=") +ORIGIN("genesismedia.com", "etQ+E2hQPtUwj0i/dmxIlqqHipJHQudPYHv7GIQCp9g=") +ORIGIN("adyield.com", "ganOVedK9uhPcjDITZKDiElpdJrQcZYoNgLwmB/n8IU=") +ORIGIN("mythings.com", "I99tEOtT0u1peCXT7LalhPYNj1644CLrvpVgozcLuP4=") +ORIGIN("bittads.com", "xdwxNPtiRsb1+Qe2jgZh9M36LvkCKGJzALmU/v8PIj0=") +ORIGIN("google.hn", "Dy/jjJXGs8b+ORf91GvlXItWT2n6MVgAgiXw940/J8g=") +ORIGIN("shoporielder.pro", "5Rde3/5aqgsjR35gDTKnrgF3HntiokioKm0SADcLoG0=") +ORIGIN("list-manage.com", "B9REVtHoWG7N1VaMhnWVGn0SGt+JNFcJfbCYEQxDdLI=") +ORIGIN("epicmobileads.com", "JANJ/nOy7PgWfFzLt3prSZaiKrY9/UYQEvEPstcNOEY=") +ORIGIN("mm7.net", "E3QODwOWnBhIM0RbfCEzjkG23Rv/izRBUzN9EosVTxo=") +ORIGIN("amazingcounters.com", "ZMsVmhgYge3gYoJnA065Tq8q/t8fezpRhSvgsyVSxZE=") +ORIGIN("adextent.com", "b4eY4qs0DguhACYg5wVSvS6BiuTCf0xKcrk1o73zyDQ=") +ORIGIN("distiltag.com", "ulda9DZudAuoclaAcB4gNNm7KfYRuhPj2EnvF0DAw0Q=") +ORIGIN("google.hr", "BaJjjtBMjjRuhVYYMG6xq6fTiRMB1LuWFnLHj0+TaLg=") +ORIGIN("google.ht", "bHo9kzkzIYFQkiv+59zwDcA8IRPymm91V3TvtluTqc4=") +ORIGIN("google.hu", "4kTSC0vRHAFGzf15jq4CJ+bmKR5r9JtVY7Kww7vukK0=") +ORIGIN("websitealive5.com", "5fy4vAsSnpIckqqZyPZXHFsix+ec2a821/QcBCG7BqI=") +ORIGIN("moat.com", "7mbfW33kpLnnNe+DDRRnv4m+Xj6A5+d69Ile0LL1+hc=") +ORIGIN("bufferapp.com", "QRIEpR8vsyFZcagYbHPfJsbDthHfvBaAIFoxBpX2ccs=") +ORIGIN("bigmir.net", "wgUkPrzStnl5xaiujN+UlWtGra36z8CkW/UJd9b+9Io=") +ORIGIN("adap.tv", "MRLV3etU+HaP8l7UE01TklDKh8B8ZVVHuRs6irHmN2o=") +ORIGIN("buysight.com", "H6OceeSCljDkVHy6CUOYQJfqvyesA+bdEedWLBTzI+k=") +ORIGIN("shorttailmedia.com", "xTrdgMvfveEEUo7/3afimH/IqkkajB6mKiolsdBykKo=") +ORIGIN("xbox.com", "6vyeJig5j8x2i711keRiw6nye81ajp+QJVUFCNyalW8=") +ORIGIN("overture.com", "jdgYtaYuFkOYs5oGUBWzrHJ/pXuF3wrpbrh0LsG/V0o=") +ORIGIN("widgetserver.com", "RTYSQib4wS+K4w8ABGPBpBALRQfm9L3aHxUCV8J3fFU=") +ORIGIN("groovinads.com", "JQtBIItBVTugA8RoPuKKlEP5OJ+Sh0F9zfDdPvC6CR0=") +ORIGIN("adx1.com", "GJ6VCQIK5VALgVJOr5uEPCiz+z9BOZSbozm9x/baU3c=") +ORIGIN("burstbeacon.com", "pgYZpPX7EnAgNgNGJ4kTcQF0EWz/fHEgtQqJbVvRHIA=") +ORIGIN("mediametrie-estat.com", "V+sNc+pC3f19LL8mN1KKlkVCH/YJUwUzCL9PUcRdpUY=") +ORIGIN("storygize.net", "ftVQvd+KftlaUth8xl/hjTqllEp+CQ9O3qCpvj9jj4s=") +ORIGIN("popads.net", "/fkgEt5vedNSd14DJbtGxJg1JieOlnulAqDzP5V2bXc=") +ORIGIN("getclicky.com", "eV4l3pf4xW8yHUL2tij1pz/wdKuEcNp+qwPqesUaUlM=") +ORIGIN("federatedmedia.net", "aH8PfwCWdLyf9rlMIbcRKOU/oyHYs8ba4jFQvcP6018=") +ORIGIN("yandex.st", "hFt7YrxUOaykrcRcS3TekcbprTizjD4wq0jnH5U6kvo=") +ORIGIN("linkshare.com", "moSMU3yBQcWqQL+S0bJ112nrSDPW3eFcOooyHs8DoGg=") +ORIGIN("music.google.com", "rvOjI6Ji36k+lOJ/YUC7mZEuAWd+aS7ZYkQBSSMFSS8=") +ORIGIN("markit.com", "pLlnPagDzZ1PfK83dhF07KM6eHDMTxBmlUNyxAJJCPw=") +ORIGIN("certona.com", "3YbOk/LouPoBgnvoSgO6uym7uJd/B2sRq53zGq7Z+tw=") +ORIGIN("facilitatedigital.com", "KpgFYYRxi/FuH0/cKg6V/Zlc2GTFdSoAUSmmkfXSbrc=") +ORIGIN("ewaydirect.com", "6RyqcJUeRhls4Ozk3C6hdjO4RyAyOfQn3GeCz80r2do=") +ORIGIN("thecounter.com", "MZ9ixaYzmSFsfLZyyfNMTbYqyy5l8IRGqnu8ZinillA=") +ORIGIN("sabrehospitality.com", "Pi3jrJ+YHetrwN2T8SCSpudS/tEseuUcLiAKNhLI8Qg=") +ORIGIN("evisionsmarketing.com", "79S6ftm8UdgigApAHlKmd8hISSsmCFVwiTEOt9En37g=") +ORIGIN("suite66.com", "Omr8womR5HZuPptH3c6R1QgSy8DzG08evsinBig7grc=") +ORIGIN("xa.net", "F6RTdH9u4GGI/bgloB8TZ9rU/pJaoRiRnw5mixuRG6A=") +ORIGIN("popunder.ru", "o77kAiKL2SaKxWTTI5MoU0cZbf8dwl+j9WWz0nZcD94=") +ORIGIN("websitealive1.com", "sGXv5NYqQILO8O01kfaSYKAuB6YBjNObPtDo7jDx42w=") +ORIGIN("ucoz.net", "93Rb2uYd0aujdBSZiRQc41eB3wUxYl8q0LBTzwBDuBQ=") +ORIGIN("mediaforge.com", "zI0JGQYn/g67QdsaZI0bgqSlFLO9AHDWq2Uyk75l97A=") +ORIGIN("support.google.com", "639r5sthwF+y5H2OmZQlVNaxk+LAZmDjSQCmMf3bpFU=") +ORIGIN("adcloud.net", "sWETIIclIQpafU+HzjzSByMniEl8siVKxP46udDglPE=") +ORIGIN("adobedtm.com", "cu09xEOu6TPET/wqV1gaJ10DWhzyHAfLnNilWVdO7vY=") +ORIGIN("technoratimedia.com", "EvPJLc1scD/g22ExyC8xZqVRl1dDKpga74ApSx9OpnM=") +ORIGIN("clicmanager.fr", "Kw/7HtyR4anW/seQsw2PzjHHoEL53FJinDwjc3/bR4k=") +ORIGIN("redaril.com", "lt2muU5Yk8FjpfiD563qpkd8jDCV68tfn+RDnlIGnEo=") +ORIGIN("lfov.net", "cmWghawvYlkDklu2b+k85mEqU7c/XCHZrWQN38btbr0=") +ORIGIN("adaptly.com", "k/b3JTXeDn17ayvqow/mGo5yvPATRtFkPTS7OorQPLc=") +ORIGIN("idg.com", "S42GOZr4VHeRApEQgQ4vpo82pYynNb3+f7jUm3fGuao=") +ORIGIN("viral-loops.com", "muKTm6IMX2nkAgso7epGhlHGFtZNdsZ6mqOFn5y9mN4=") +ORIGIN("rensovetors.info", "4l8JksdA9j3WvaCwA7s0izjKDieqvwTy9egHjKdDvxQ=") +ORIGIN("impressiondesk.com", "RI+fxZdxVuo6nvSN00jDmGNrPkSJ91P7+dX5OJX4Y98=") +ORIGIN("linkconnector.com", "CytQHsuX6uW8qbCNz7j//V+ozpB2PhiqBwRC/gxV1Wo=") +ORIGIN("engineseeker.com", "KlQ1q0Fd9401eHnwvHlQDB6fM5bSiPYhN+Ghp/V5Wac=") +ORIGIN("monetate.net", "QD7UJLpIjqejHo4Z8PbABYIv5KyHaTQf2LYzc+E1Uu0=") +ORIGIN("luckyorange.com", "nzwRTu95vNO2eAHPikhWjXEgmgItIUIIvx0iEBFFAOg=") +ORIGIN("adperium.com", "NYqcavywSEeEpXoHTPTaBoYgBYxNOuPIGy23EX0UQE8=") +ORIGIN("belstat.de", "U85QGR768/RpjbgP23zpx3z2eHtMBR3mYkCOaKBmNj8=") +ORIGIN("aprecision.net", "vwBqk5A8GVNM3ZqdIDdAYeuhiOvau1Piz4aDEppnlpc=") +ORIGIN("proxilinks.com", "lMEeSNZ1ZkRWIHd5dqVmn4AXnvOzgg2PdP3H/O9cSQA=") +ORIGIN("google.com.ly", "ixvCgu9B0REEoEM2z7txf8F37bpq8GPdMvgXgUgQFz0=") +ORIGIN("freeonlineusers.com", "Lt4scl+lzdsRGvhMUxtZ90GjNoY6H4+RJfeZU58/oE4=") +ORIGIN("rapleaf.com", "YXrn84yx5ne29xPEnhJbPDr4qlM13ukGq5+6WlVjwTI=") +ORIGIN("layer-ads.net", "QeLBcs0MrSBx1YS9sQ7rAm/u5lvdisOhUixqMK3bemQ=") +ORIGIN("revenuemax.de", "eFmeTnSdLgQ8uzcs52FiJSdbdOAVL+oYvpk+2MSLYvY=") +ORIGIN("smartlook.com", "o2T+rQMW059B1CmofhGBOkAb4UqAkWNlrH5pcufN6UI=") +ORIGIN("retailautomata.com", "Pd3njCGoqhB8zOuKJNliJWRkHgymcd4OXiS+Yphvn8c=") +ORIGIN("adhigh.net", "qSO1yHzUYEhBq6DIjDxpi4vmLzcFwrXbSkEI35ZG6hM=") +ORIGIN("doubleverify.com", "W74F2qsLcFTSfrlMHfI9LfxGcby28ojsJTyS0MfSBbM=") +ORIGIN("instinctive.io", "+3BMm0M53oUykDFFjq86MoLGJM0JXBelwrwJKXlQTw8=") +ORIGIN("avatars.yahoo.com", "scZnkdTVGzYoW2oyp7Sh8feDBn4HgAJCTYj4SC/7W7g=") +ORIGIN("rmbn.net", "N8MALaG6slMpsuwQkdL+nLO2XNkzCIjVhhSRPxILyOY=") +ORIGIN("rimmkaufman.com", "eA2BHbGwt1OqFrPlHJlaNZtEzxmq9NqlXShX0K9bSyg=") +ORIGIN("cpalead.com", "hgbjJCddD6gjVn7EskJr3638USW0wPvTiAceR2WHJ2I=") +ORIGIN("statistik-gallup.net", "RFqyMMnhCQ9YsLFYOL6Uibs4jZ1ugr5jDmZex61gr48=") +ORIGIN("plista.com", "lsTii4yqdmUO3tCitWZWM/TuFLQTzCHcn5976GTzqjA=") +ORIGIN("rkdms.com", "Ot7LqVfkyvO6X6zYd0ovJi+YYRUgLzLxURNxSn19+Rk=") +ORIGIN("getpolymorph.com", "uIrHZ8cMpKmNuEKLWuwxw2UqFSB/P+K833uG0rQ91Zw=") +ORIGIN("guj.de", "VOJZUJtHndXxauMsGVU/SRFtIqvRYlCveQR6N05tbIM=") +ORIGIN("google.com.sg", "0y7cTz5fk89VB0GSi97XnubOIgPt+bvtElSf1b8Ugq4=") +ORIGIN("google.com.sb", "ofYwW4O6dyFptaNlIVJEpetu9wIh1D6eHDODk6/HA64=") +ORIGIN("google.com.sa", "5zpMZmET2yKNY+Sk7p/gTgwAsx+HqS0tiSuC32ttLww=") +ORIGIN("triplelift.com", "fvTKUs1XCsmuuqzjSjNiFPfSlT9CsqoxWTcBev54rgA=") +ORIGIN("nielsen.com", "n2CeFmwF6QsgAqDbaAD1fQxvRBltagp68jNbOuHHEWY=") +ORIGIN("google.com.sl", "ZDEB/DtHg6kofLlhRF9AuyPY8tII8tGS9WwvRcprjJA=") +ORIGIN("talk.google.com", "b5KfS4pVDceMUMJSj37hEOSmXgLhxIwR+AmPA6RYtYE=") +ORIGIN("google.com.sv", "/hzOpQR36lDRjFqpmKc5YlcE62ss7VV9lmPCIFVZHiU=") +ORIGIN("jemmgroup.com", "LyYB5lA7hw3RYSGmnqkiR7oBQmirvVxI8txJO6LGeRA=") +ORIGIN("adfunkyserver.com", "8WPqOuNIPCQ9BF5xrw4+2S8jmOZw5pOcHdhP/A99iik=") +ORIGIN("finance.google.com", "5V9GIwZAheN8qG8Bt3yWYjlNbD+aaCV84EROekwumMY=") +ORIGIN("engagebdr.com", "wWzsEqm/Wgto4hSlH5EWC7U/LIvZOQD1QYGtKrxbGHg=") +ORIGIN("imrworldwide.com", "bAUNKY+TxAWdHV9w2u6DmjoZR8jSdEFDvhq07c5GdK4=") +ORIGIN("motigo.com", "IR1R5Ufm9lpZzTcAceL9sjIuBsUL3y65hnK/oE4MP0A=") +ORIGIN("hotelchamp.com", "vAVyJgsFyPNQsrLvj5l1VOpKi0Lt4OM4ukgwUVdWUPw=") +ORIGIN("getintent.com", "vQH1p9iwJgxuXLYr/dCARV5CV0iXDYC6v2a5ECB+Acw=") +ORIGIN("etrigue.com", "1tgiK0tnFjSLDdHDTuYcnVPtG8QSkfcnEh5d12HxPU4=") +ORIGIN("ipredictive.com", "Ldc3H1bvXeD97IBtmwEXa49RYSeSc2xdqChcUtWuusE=") +ORIGIN("fastclick.com", "cprcmOh6mfPl1+SpomeZ6uWsKP4C6eiKqZm56+3/wp8=") +ORIGIN("gannett.com", "FoWMjIi4KnWz4wsE4EVEaf9eqBM6FwzYFpQXAvj8/aY=") +ORIGIN("luminate.com", "1WSp/mFP9jnN/8D5MX5yt99E7GnG3hR2bpIXcbJgvbQ=") +ORIGIN("keyade.com", "F8/ccXm0Ac/ggV6f3/jV4RxAclBQsakRxGQ0YiGJE4w=") +ORIGIN("srtk.net", "EAVwtxTsVJXhDT9e7+Ks6pD5s6Z6pFReYIbe98+yUEE=") +ORIGIN("belstat.com", "HITWA4OXFMlSDt1HWwzb9Xh7EpciaCz2Kg53GoNuhwU=") +ORIGIN("localytics.com", "mP0oTjyQ2NDaDXjY2MFqCJpNx6DA5SrXwDDH8GelW/o=") +ORIGIN("js-kit.com", "9eRG2SRmZTWJHVsEqFWx9QUcgcH1R1slOrdYqk+y8i0=") +ORIGIN("ipcounter.de", "bSjbacEgw2Z5gnVIIHoOcFKE4FGqIUK6F4R13HtGhM4=") +ORIGIN("aol.com", "VpPrtVw+1hvXI7rOU7K/i+ovPOsjq6CyghUal+4E3dc=") +ORIGIN("tumri.net", "xPY0IR/3iuymK3Cw7oGD465viz2ZuqS7yZ6aaRQPce0=") +ORIGIN("contaxe.com", "juVQPkEM9J/6JEe1KrSUBKlXixX2BlDKCjCpYILDZSM=") +ORIGIN("marimedia.net", "y0ZIr+pLi786Yrz8tICKUeX7qCDQgFSUbfbJBTR0iYQ=") +ORIGIN("adswizz.com", "or8h6Gw8g+gT3e3KPQu1hEtgsMmKAb39WNUVLK4NtBs=") +ORIGIN("noisecreep.com", "A16Ikz2q8874FI2HZvOUqJeel2kEkNsHeKdJveF8lgI=") +ORIGIN("600z.com", "mKj5fw+rktatT6SQODZjOoUD1/uZvOOn81FCsR3fhj8=") +ORIGIN("applifier.com", "PeWNW2NvmkL+VfaTyLUfNWa1pY1JojAwzH7X5TXqoqA=") +ORIGIN("contentabc.com", "+hwvGbypUdpuRyjKkPeqq0C9868o5m9JpQm4UUU4TaE=") +ORIGIN("adsfac.us", "5lUcDe6XA8ZT7a7xhcfQZN5hb/XSpflr1h4ifcHwwsg=") +ORIGIN("scandinavianadnetworks.com", "X/cxusJ4St++KvMOCljt4ygypFxZnb2G3DOjFlDMW4E=") +ORIGIN("books.google.com", "9fleVvXABvxVKjhXpqqQuzY/xAGZ3HdMIEJXxfknhmY=") +ORIGIN("admarketplace.net", "BB3+ucHXA4SA2tvbM7C43A3CCYTiUL7C4G/rgZk3gic=") +ORIGIN("viglink.com", "O/X7R+xeDrOu0Vg5ZpFW33YTb/bCMCKddIOu2y8kl/A=") +ORIGIN("tiqiq.com", "6LKPaKJ/ff/cIIZ0469COVmWmkDeU8C4jxsjwibVpH4=") +ORIGIN("adspeed.net", "TVloZEa9TPnFXwjX5njRIGYytHnJUP+WGlv0wHSv/go=") +ORIGIN("adsrvr.org", "1SdIroP4iTri+eWN1aTcVINc1aa1bwza07xa4r4lKXA=") +ORIGIN("xcvgdf.party", "vUouwesxMaDcFi0y2s3kLwXDVmamAA1upoYqY9WMEuo=") +ORIGIN("adserver.yahoo.com", "6b+JsMVq5gMP1kvPLCIZEy2N5yNdmfdQtWi/JpDriGs=") +ORIGIN("synacor.com", "KeKiBduor85rvnPiqkhf5s3NkVmSPbxQzIzavljKNWs=") +ORIGIN("websitealive2.com", "afAci3ruzA7FdXqinGCllTPuPQg5o67IH5efwbOooeQ=") +ORIGIN("sessioncam.com", "YXQwPWRAHpYwm8yKIK4bI5XLOUO+e1MI6dczGPd4hik=") +ORIGIN("google.com.ai", "HLNqJnsQ9WoyKrfdzCS3G58XcGqry0qj7iRSU+RA3SE=") +ORIGIN("google.com.af", "gqCv5xCojj2GELft0wIpCYBQu9FnLqDrsAR0EWNxKTg=") +ORIGIN("google.com.ag", "WLv5omNpSbHsNrQX/a+Lt+Clk4az5sxM9U/ypkF0xC4=") +ORIGIN("proximic.com", "R+DH78rBJ6AYN746EPfU3dvoI+Z1Lwa/NMnNGVvXxbg=") +ORIGIN("act-on.com", "NSdALIRE4WKYrhuKcS0mC07s7f/W1YTEQWNaEZz6fj4=") +ORIGIN("google.com.au", "wj++R3urDO6J5UF0dOqhRiriBsbxcN+yLcZgp85FVLQ=") +ORIGIN("reklamstore.com", "MwA3OqJVe/8HB4CoZdUCwo6tLdnlH9TFPcI68Pofozs=") +ORIGIN("google.com.ar", "2ylMfrX1xah7lWeAoO4Xx+YBzXdSgmUGiEgsJFVxqHg=") +ORIGIN("clearlink.com", "UM4V5KJ1t99QaNxglgbynlMsFaqCTaOO31yH4TzmF9w=") +ORIGIN("clickwinks.com", "cRxbmtCdAXUxmlg9sM2amI6S0IzoEOJ5uDApQDqMdto=") +ORIGIN("tradedoubler.com", "4Q89ZfqA1XJB5eNLzHk6IsQlPqNHc/uG/R+qCn9sPfk=") +ORIGIN("adotsolution.com", "fQEF51g72Y/qIHN96sDO1doj+yTAh20xK5aGKMOaMJ0=") +ORIGIN("shortcuts.com", "JN3REEdW94Qs5q9WK8AglE0QanOLzUg3+q29qiAhDJI=") +ORIGIN("adingo.jp", "vcBM3JA2SF5WEcbCodBvFoOnf1oCwb8VdakTApnEgG0=") +ORIGIN("trafficscore.com", "3MiGABVx7fl8rkAiYVuWD2VBafl4030yuW1E6MLlKt8=") +ORIGIN("onad.eu", "VWw+tdYT073mEYgV67BaSvhEqheSbJdI1QVtNgimNPQ=") +ORIGIN("aerifymedia.com", "SwWXYgM1tNya3zi0brL/SJN3Eva0tgynB23TSbACyr4=") +ORIGIN("adscience.nl", "Rt4Gqw3gW3UhTEFzkanXJMsM+iY/3jBFUxXVCnihYCM=") +ORIGIN("secure-adserver.com", "fmwQUC/tP0PhpNhJQ4HINYI5XcJQvyfxGpsuzx1yQYI=") +ORIGIN("aolcdn.com", "cOBb1eCzy1D5jIuJ3mMj1Wc+txQnaVMnj5307KeROzI=") +ORIGIN("visbrands.com", "OuZvmBHc8vLu0digEOGz8ci2TfsQewyagVeaud0PkyA=") +ORIGIN("vgwort.de", "LEkG+eOrEZM8B2qeH/nHjD7pEUNqbLJsALOBSKMSxZ8=") +ORIGIN("blogher.com", "rHJBkZ5si3s/Jw7vKqIuwrXp4LsIrFqzzniRI7nNJis=") +ORIGIN("mercent.com", "Q6AaAeLdcLtJbF6WFLheiYYBZQKVq5jiBHRuC2JdwSw=") +ORIGIN("yandex.ru/cycounter", "m7Xg30ieKBLCyA/TmJzAqDa8lOBZR5CqWswTuzVDUFA=") +ORIGIN("adsnative.com", "YpKgfQ0hB5Pca+9CwLWrlGOn3pCZJHTgvVz3cExNJKo=") +ORIGIN("brightcove.com", "olHyu01w9/aX07WuYU1qCor8TxYC//avFaV96b5SOP4=") +ORIGIN("richrelevance.com", "sy3oBW3g6fkwdd4vmGVfeRSqASIdQ9e+6mJnJE6pJ1Q=") +ORIGIN("yandex.ru", "pCZT2iEKVLaHTzfw1KEtpeibtDbyxqAfgyRucc21ROU=") +ORIGIN("dmtry.com", "ygk1S4+c+eQCzznpTmZrp/cyA7o32B23jVE2meGIMSI=") +ORIGIN("ybx.io", "jX/PoyrR7rYqLNNuj5wdQOcDgiXtUZv0+jIe0OIPPlA=") +ORIGIN("ibpxl.com", "kLvX/lZeEw77QXrCB6z26wZbBo78f1XF5cz2ei7wZkE=") +ORIGIN("yesads.com", "r2L+dzB179bpqNOfoDpV4lZnJ/9QXDVzwYOuHd1G4kw=") +ORIGIN("everestads.net", "PtpGWlz98somavzJEfUcaHyTJpM7Lw84N31xOAceKf4=") +ORIGIN("vserv.mobi", "ixhNYW4Opo+b7/3r2vHsm1VmDYNHLzGeXmwbsVlgaVc=") +ORIGIN("baronsoffers.com", "Zb6J0QC/MSZQ4OqSAfmErFTg87kfHbmUqqntt69d3F8=") +ORIGIN("oracle.com", "rzXeKt+U81NGy+4wIbrNTKXFi3ugf62yEa2YKvtUlO0=") +ORIGIN("zypmedia.com", "obNUX5cIQyTu6ahk0yriQFmjIa6mPfHl0QaXCE2Ltio=") +ORIGIN("cmadsasia.com", "tf61+ecR8iOkxc3UCD0+c1NOdqgP9xPxrBvCTvXOE6M=") +ORIGIN("agkn.com", "KdByr4N8/MdNcPJzOwLN5nc1eKfMvTEkez95JzsdohI=") +ORIGIN("eulerian.com", "YFIK6sbINhUrKWz55EhyvfdP7Y7C8y9OeIWKzZ5ycKg=") +ORIGIN("adaramedia.com", "QygYtOQzJZOIZaLkU2+CiwvPYn6qNEX8THz+X5eLLhM=") +ORIGIN("healthpricer.com", "sUXitjYwfMMBpd1wEEXhfmzVn+rihQ4N3oT6S1qSQdA=") +ORIGIN("engadget.com", "cw83oeY3rKJjcdnKqZlamAGLlq7Trm5VrVr+2oSkXbo=") +ORIGIN("bloom-hq.com", "nAoKwmSE9bz03DGzNI5F+6QN1RMeGsjn5uhaG7r9mE8=") +ORIGIN("autos.yahoo.com", "IQ6RptYgUAhmqlvpc8Z1rHsgX3fWnkpGJ2NLkK0aFlI=") +ORIGIN("mybuys.com", "l48Ii/o6b1AWTaeesPeqla+8lOqLa5dW6SqQqpoV2fA=") +ORIGIN("everything.yahoo.com", "BVVykvQDuYIjBfpGvzLjD4keh5yBPLH+lDMk0ELgG0U=") +ORIGIN("nprove.com", "fwj8VG8tebk+DLLuBCAr+4J3oc2QgpBHpyV7xPnY+Sg=") +ORIGIN("admoda.com", "aoi9vZTRk0hmx9krdN/5vM4NcTyrV716cOuWT2SmgHo=") +ORIGIN("microsoftalumni.com", "QcUoOaSE/yop5EFnLmpzvMggUQxAubtYb3nU7P6tFFM=") +ORIGIN("congoo.com", "+N3qJ5CD/ur0O3guxumfX9JbluXO0Or5jZzqcMsGbUY=") +ORIGIN("adsbwm.com", "J4uVlJedJbSPZdwzjLuARm75ijTUX/TLLDY2o9jgIeY=") +ORIGIN("uservoice.com", "fZC56+ANvKTW6lW4YsZvBtuAIqEJouux1/9j1+YdSxk=") +ORIGIN("gw-ec.com", "fBNHG/eL6yPly1IZ5FNyeuvhREfHTR/drFjtFVaY6kU=") +ORIGIN("buzz.yahoo.com", "BIZPfvuKP/Xv6yTLpvs3gAOpob6FYf78GrnqGnO3xP0=") +ORIGIN("ubermedia.com", "alKytBgn2TxamPHToLSeeVJe/Z9eJriz+3SgLXkLMnw=") +ORIGIN("goldbachgroup.com", "YPgqx82u5+p4FqdU0V+NuOyZwhHFf17iN9nadeMpolo=") +ORIGIN("valueclick.com", "Qgd0wwfMgtxg5wbd1yJI/tLjsXi5ciD7GtrkyK54wIM=") +ORIGIN("waterfrontmedia.com", "1w9EIOvvo30BltzkE8bMt7kJrfPbxbJIadNV8ggYmpk=") +ORIGIN("operamediaworks.com", "2ijoyxGV+uJqrymQ+WragchadMo3aX1B5iRb8CMxJAA=") +ORIGIN("google.jo", "8nDeN6i9YVGcnWZZ2FmdazsxreM0fHaH5lHx8M7dHGw=") +ORIGIN("microsoft.com", "D5O8juISn1bvLlrkTgBrP1j+ORAyXgxOVCLgPM24Mes=") +ORIGIN("toolbar.google.com", "4a4xIyqheVr/dpWBga9xbWSHn5QkRrRvDMgtYxM9zmQ=") +ORIGIN("up-value.de", "0QYe3hmMTtJstkK8hYzo4wGf7I3Aft6/BQBjkCfgHdg=") +ORIGIN("tap.me", "zXTFlqy3BKMtLoLbgSaRo63dsmH4IC/HFCrr7Cp6Yz0=") +ORIGIN("adxvalue.de", "Om2y+VYmYPSWG9L8rdgVYyn8KnhhFhK/0odKj9WZkcY=") +ORIGIN("trumba.com", "ZFmoRqH16nCPMGMQaJejir0KwvcryPrAeBdJ4PwcuVk=") +ORIGIN("xmladed.com", "Zuo8N9dPaNBz5Q/Eh4u1u11ucDaG5vYFWG69ya+EzpI=") +ORIGIN("apmebf.com", "lPQDfHCdCFSYZ7GpU270vw1QxsfWRMLcBpu8uhn2u+I=") +ORIGIN("omg.yahoo.com", "g3B8kA/T0ZK9ge9LnFa3W8daI1DtrKmTGiv9T7Tp/Ho=") +ORIGIN("buzzfeed.com", "FAwJbzbbpBW1t6u3TvXw7eJ/Akzwot2pcpF17lLUTx8=") +ORIGIN("affbuzzads.com", "PUWu9ytGWhs7jQ+4aAqJ3EzzjD02HwBymkRuiP5MCQg=") +ORIGIN("taboola.com", "CWmk83tOcbqI08iNZ5h73n5aF/Sh8imA6D9JeoFHnqc=") +ORIGIN("admagnet.net", "0afcIw919TlAwpyj3FpfI48ApyXWxJnXUdBE0NYvS94=") +ORIGIN("geniee.co.jp", "TEpKU96+OP2XbvR3a81eI8GXnOIuz1oxCbmvHEv3Qk0=") +ORIGIN("pointroll.com", "ayRHArscm6+1+iL20Qyw0WIfVoAiXNWofHwg/qc4ULQ=") +ORIGIN("deltaprojects.se", "ljGRTE7TX6jB5mZEDtfjUcO20ysU/oU9i9pRo+vniN0=") +ORIGIN("chartbeat.net", "1EB04+Rp2FK4oe6UI9zLfyQoZDP64eY9GlvPqfssfaU=") +ORIGIN("microsoftalumni.org", "9VlyWuE8sptiQH93/OGR8+Fmu0KDWJwtKpcgRbm8qVM=") +ORIGIN("tracksimple.com", "uiwv8mTkAW++SR1pmgPlfUh0z3pbSmC/O6IKAvNiUhQ=") +ORIGIN("backbeatmedia.com", "ehZSahwKBU2TrmA/gvrkKomSE1WeqKxH3F2Y9TwfC0w=") +ORIGIN("ayads.co", "hMroLyhXyOdSjuT371gca+H4AmjKgY9XneSxUQoWwu4=") +ORIGIN("doubleclick.net", "uXNT1PzjAVau8b402OMAIGDejKbiXfQX5iXvPASfO/s=") +ORIGIN("adscale.de", "QwnUBW1yJNoFkRHBOh5ZWsKlIVnM3nfriOUs5mx4/oU=") +ORIGIN("etracker.de", "qgtbwdFAwa8R4cmoa0jCji61vlH9rFWePa1EzZugYa0=") +ORIGIN("stylelist.com", "FqbOc/Al0L4TmyJIvvH+EEt2bjbvRJTdchmg+YdZ8Jo=") +ORIGIN("google.com.na", "TM4wcN6lJQaEaqMXeNJGY51ZrFAJAXAVd72ZjPESnnM=") +ORIGIN("resultlinks.com", "I0mKEgQ4rTSDFmO2C6LWTExSbDeMLiZZevzqL2Iuexk=") +ORIGIN("lockerdome.com", "bkQMLPiXrqn/FHwAtq8Ki9k5viP+ykWDdOiXb8wA968=") +ORIGIN("liveintent.com", "eNZmdXD8iqrdLwepzhNtd17vzFzJXMboL39Ztp7XYrA=") +ORIGIN("voicefive.com", "dNi1ZOfeWFm2G9nKB3pgFZzn1+c9Gjg64HEFeZNSykk=") +ORIGIN("revinet.com", "XZGE37npYLGp/x2GtNlo68fGTgt54EZc9gKLpAhbcoE=") +ORIGIN("clicksor.com", "tL052g9CBFu4weeERJzpc43poifMFN7Fb+D5kFBwLSs=") +ORIGIN("i-stats.com", "hPIRK1EDJ4G4bMRvQ+o/IrhPPULot7i/Gumc8+vq2ps=") +ORIGIN("marketo.net", "2WSth6AnvC8Jqq9ZgjNU6mmKfwijv363Jtlv7TFaolc=") +ORIGIN("usemax.de", "69uhn8coMdxPkV6KxtbhU5l3LEeGjPPoiwZkCYexJQ0=") +ORIGIN("ru4.com", "bpWQoUiC1aBmwV51ZoZLM7XCw9FxDaaRHopKP48fmRg=") +ORIGIN("m6d.com", "8iCZ4/JoGeSYYCmEyJOMjWNaQHIzkrjtUM7hXq163/M=") +ORIGIN("spot200.com", "iEpA0zbrcgH7or9Nz5Yjsyk2NQLrxIC41513MDmgHlo=") +ORIGIN("gopjn.com", "MkJgZMBi3vpX9KtJUR/g9w1sSrI6V7NnAGHGsNxFslM=") +ORIGIN("nextperformance.com", "bqlq81KyI4RumM6JkU0VLI2N5LUHDoleYEg4K1UoM6k=") +ORIGIN("hurra.com", "NTKrKIv81eCuygXJwSmEPbACcQfmd0PPBDI/KokWzcA=") +ORIGIN("pntrs.com", "L5Qt7WrqUpAFUCflldH1XJ+fVn1he60o3/NyWUwOmrk=") +ORIGIN("contextin.com", "N7bxxUMaVG/neSxFQeTPq2O9Z/k4Wpb4InEC/orZi9Y=") +ORIGIN("notepad.yahoo.com", "1kpI0RuYWWuRY0Q7LTUzU43Za0TSd9/CGnQeRUqeP4U=") +ORIGIN("gb-world.net", "qTqp8vesQIs0uuWkJ6fxQRRtRMKxHfQjDhy7SWF0B8I=") +ORIGIN("google.si", "IEqFlBcWCndVZ5FsT4EhTDWw26zmAPd+tArgNvkJ1do=") +ORIGIN("opentracker.net", "G75vxthdefFtl3t4nXAIirpvOY20316LAmqE81flVTA=") +ORIGIN("google.sh", "AGIBSyPMxA/WINa8hSFE2G6yo719GBTxI2ubkU4Z65Y=") +ORIGIN("aim4media.com", "/+xA3D+1uh/3xJAoX+3ss+Vfihk9Mw/oQyrSV3mDIVg=") +ORIGIN("kikin.com", "jjQzTVb3UfMx5MP7x7hG7gRQba+AiAu/4jukFpD8aNo=") +ORIGIN("dsnrgroup.com", "dErd95Vex2x7zyK/IWg79AZVmkwsZSu8+v898tdNTMw=") +ORIGIN("dataium.com", "3TQSSAEIE/QWjiUc2aTzA8KzXi71LkEwRvKE+ZBlNOU=") +ORIGIN("imrworldwide.net", "yLh2FHJpMJaeMQXZXrfxh8XamjzTpfDQw0KiNc1ARN0=") +ORIGIN("yoggrt.com", "s0WDzz7YyBVhwwpNwDCE/xK9HN63+htUhq1qEQcao2k=") +ORIGIN("streamray.com", "RZ8I3NraxF12bUiV8uPEV3F84jpWgF0gkbAZTQDITIg=") +ORIGIN("bttrack.com", "MXgbFANmi/dgruw5ETsxpP1pviPkuCnbx9CE5oDjpuU=") +ORIGIN("albacross.com", "m88uBlbwdI766uJEDXSVcSMKeKhuESuOr54UIQWWp24=") +ORIGIN("clearsaleing.com", "Po2h8458JGJu6rHSsbjZsV3+pzkZbW9xbpZPbyJA7bM=") +ORIGIN("ebay.com", "FwXBfevpOhVrZOt0bKl61/o4+6lQNlL8r4r5VMCVMSE=") +ORIGIN("google.sn", "FFrDoTk659V/Gmo8DGu+xSBtpYruejl9tue+4f7GAh8=") +ORIGIN("bnmla.com", "tXMEBHGi5d0rZS0Kv6ede8CMBAxutcjYYH8gB+BmDx0=") +ORIGIN("affasi.com", "xnRXdzqWjPu/ctB2HU2gAva5NdqEjVOli8Rp1tSlQ2g=") +ORIGIN("google.vu", "1+qfA9IUF+XSb6++j0oVZlhbqGVB4squHa/dz340+MQ=") +ORIGIN("trackalyzer.com", "T9LC8UHSzoKs8igkstVv5x0JIzlF2xOvRVzC7NE/DNw=") +ORIGIN("google.sc", "J4nmbKbRb/gRmtWPVS4zaUHRjZ/RiUxaaJy+7iXF7Js=") +ORIGIN("valueclick.net", "Lv0AwEsJUt1e+zqiJQJ1e3CZqMvaKp1sp2SZFq12LiQ=") +ORIGIN("msndirect.com", "jJ9r3LzOQ+ZrqUU4W++g5aBM3KYeyAPNUoT2Y1LKZRw=") +ORIGIN("boudja.com", "y/3GAp/MRrIucrYpKVP2+V1aTFhO8ItlD/MbDtGq/YE=") +ORIGIN("admedia.com", "eaxrT3b1kvcTElYzc7yYtPdN97bW73BhfM/CMlx3Dxg=") +ORIGIN("longboardmedia.com", "peFCJ/SYamGXeTQpRmMzbVa6M1Fx/DXJiMDpAkIIrG0=") +ORIGIN("google.dk", "9EYG9ykW2FFg4Llod9SOLiP7z2zl3LhUgD6fttJaNhY=") +ORIGIN("fizzbuzzmedia.net", "awHz8dMekp0dYxinuWez+Ewwsbe8KCSvNbxA9cuoY7Q=") +ORIGIN("layer-ad.org", "iS4LydySWdVKmKPJSRhc/ST55i/Q1D0DFzTB16O1DW0=") +ORIGIN("cbox.ws", "61ygRDbHnQYr/fQI70V++SJuFuAanPwgZPd2aslQZo4=") +ORIGIN("audienceinsights.net", "c+Dw9cS1zE+X/uE+vn9CDPg/DQdJHFQri+OQ0BQ9QNE=") +ORIGIN("contentwidgets.net", "HAknI9fjEAKm6AQjRjLRcr5r1K/0IUGQwfFSF4e1Ih4=") +ORIGIN("google.tn", "gDwFWIvusifAIsEXQ3J331VIwmUJzWGb+3loKX4VG2U=") +ORIGIN("checkout.google.com", "+bTzy0fQhaHzF9lNq3G4v12VA4h0MG7yulCXi4eMNFo=") +ORIGIN("linezing.com", "MO0rPgeBEVkpu6BK7fwjnnPdC7iY2NyfIJd5voG81w0=") +ORIGIN("go-mpulse.net", "xcr0C2skvWzTyI3WFjTvjlie22zQ/+j8ncT/W96VMpE=") +ORIGIN("google.rw", "HY8iRcXNiDuStNJfuVHkMvUT1k+77SAX435vFvYGyhg=") +ORIGIN("quisma.com", "ZBA3/bP7Ur8/lFBR/t5awczOJ5BGEN7FhDcbdF4OkdQ=") +ORIGIN("seewhy.com", "LuOQlloZdjVHNc/JuyUSQpjrW4j1zjTHvmTn5L9zmhA=") +ORIGIN("google.co.nz", "P271UfEoHANkzIFS8/lMGBQUc8yW4yn9Sqd3UF7z7Vc=") +ORIGIN("adwitserver.com", "X5jl2yQu5vwNiCAa49XRMx1cQZt5d+jUrkvNqtusdTQ=") +ORIGIN("xiti.com", "idMS6Whn748VlENvC3kZAymuJNH/PNiepUBUYAYybWk=") +ORIGIN("proximic.net", "srLW2Eyjq1huea4wW8KEGgDXjbAMTMk13Uojtus0Pik=") +ORIGIN("upcoming.yahoo.com", "9TNmDbbszDoenc/LwWSxgtAW1a2hBh3ALKVQcoa3l8A=") +ORIGIN("google.tg", "0+mAvK/M+N/VsdYixaHsttDWxLp9hFLHami4CkgLNJY=") +ORIGIN("wishabi.com", "NF8FH9ztMQzWAyb/eOQl68bGjUfD8hk7+zoJYTrjCww=") +ORIGIN("conversive.nl", "4Q4PPuynMdHeozElLmTEsFH4Z3yrukNCH8Kdokc3/84=") +ORIGIN("ientry.com", "4O+6JS503kZemPKMBy1rbdxagqnvmbU3QX8b1OAlEVM=") +ORIGIN("zapunited.com", "VE6p4SOJtvhVR7FhQS2bDUlxmmtOTqBIkCh2yC9eCcY=") +ORIGIN("stylemepretty.com", "wRhC3jghSpLhlJKi6MlbFaAH9UBgamtfggfaFQNqRSQ=") +ORIGIN("graphenedigitalanalytics.in", "AcqId4/F9k1FTPl7Lr1JhD5CbYbtT4idkKLMRexfQfk=") +ORIGIN("amazon.ca", "NL7R6K6xhzLJTgrW44t2EMZj0gatZRcmi+0JgeKblTA=") +ORIGIN("ambientdigital.com.vn", "+mmcQW0PVduNqFKZagtzgScMqi5NTsFvFwXT+XtqwkM=") +ORIGIN("sodoit.com", "BHphQU/B7hVy1qjP6YrXk+KFZo/8BBSJizQYQV2Lr1k=") +ORIGIN("adtotal.pl", "SvG6guIMmnpay6wn2tFa2vtT9ySaaA8l1Gww4aEey/M=") +ORIGIN("proclivitysystems.com", "dtFJZwIN5uANZENlz8JgS0QxyR8jBrd4NEfTe4ldEaY=") +ORIGIN("bizo.com", "AspWJJ5EfsASPKHXxvsln2Zg93GPoU2j/GpSteMGpY4=") +ORIGIN("narrative.io", "8GfUtvRoqEMYAGP0R//J8+wq/20B9/xa7BHd1t/zdI4=") +ORIGIN("wowanalytics.co.uk", "4RDC0dZ+zOe3TH8EyxoNu0orjgp/Bm6wHyAs/iJIvgI=") +ORIGIN("dashboardad.net", "R2FM70uN28hDA0TRFHpENcCRs0MP6FMC3m5+Tkv0+X8=") +ORIGIN("adversal.com", "eL3UX06vBSgjZeueDtV4D1MX6a0rDgWYdhXq2Qyupq0=") +ORIGIN("makers.com", "RBwr/5MRgo1+oD+Oh9TOupRQcKwg0+oaET22fxs8I8Q=") +ORIGIN("facebook.com", "NeAyZg7bkhwMDOWb+iidxahMcbmVhLNZ101rA9AN5m8=") +ORIGIN("brightedge.com", "e6nDthl1YIJe5BHnGN4cifPZw3PjOnJE09kY2YCJhug=") +ORIGIN("itisatracker.com", "coqG/WdwSAEI41XJBN7yU0IqFPCp7UnLAv2FBp+2b0Q=") +ORIGIN("cams.com", "77ZZsofss08/vROdukNv+B8WCimQN1/xjQAzDCs9KgI=") +ORIGIN("bucksense.com", "nezQc+rw3XAGZQNi0Ic6ajhXBMZ6R5B0sKZCiA1P8ZE=") +ORIGIN("adhood.com", "bLDRESPjRIWAAof6Y6mZRW2pJEdR0niKZxJDnYjHj8g=") +ORIGIN("viewbix.com", "qaYP0aIWZ6r7XOZfiNzAqyK6XXsFkD8XdEe5qf/mp+U=") +ORIGIN("ymail.com", "f8mD6lUvfI0VP8MI1iHrT1LoSqY+zM86c1aYoRoqSo0=") +ORIGIN("adaction.se", "4LoHXWU/UN9/osQ5XYAyF4JjGZE/sLb2+ksakP1Sf2Q=") +ORIGIN("oo4.com", "yYa7aZFgsC8RgeMu6rO2/MvrF787kvQKaV4UXE2m8N8=") +ORIGIN("google.com.pa", "ORFI9lZ6yaBOdN3+NaTEsir6ReMfRL/ILAAe9MilvLQ=") +ORIGIN("google.com.pg", "aK5AGBiIbwPo3buGHHq4pBEoQ3/u4sweLFrdgEyXCgs=") +ORIGIN("google.com.pe", "CCLnONoACptZdOr+R3CF6t9jcqDblUzBGkFclioqP+0=") +ORIGIN("google.com.pk", "v4UnBOb3wxodpEIkutS7dvb7bvvkJBoRI3Z35Ns7tHQ=") +ORIGIN("sites.google.com", "ABYLUrTeQ2zalyYvWk2K5f8ZTBXsBm6n9znJa4I3PMY=") +ORIGIN("google.com.ph", "oDLQhXLSR4EO1HyTrXrMH6wgU2gKeO3AePE6ZVilFU8=") +ORIGIN("metricsdirect.com", "IQB01jja+rbxpaRV3I5jnSSSICwGGRz5dcasAg4L2zE=") +ORIGIN("google.com.pr", "4aVNUPXOsZZMdahD+ALmNbTtaUsVhE1T2fnJx4Z3PQw=") +ORIGIN("affili.net", "UlI3sh5+MtqgOSVezGjCQqcqix4qePTOsB1j8JGwn+8=") +ORIGIN("adtoll.com", "RU/IUnRZICyFnElLiDqW8W80kRXIx6zlL2k8SfRSKJ8=") +ORIGIN("google.com.py", "CliUZUGvF0Mf9GfeFL+YEPZlKpEEb0invpBwD5aLUbk=") +ORIGIN("ignitad.com", "mtlOcHyjRc+69p6QgRq+auU1mw17BnGFmFepgJmkIVw=") +ORIGIN("gostats.com", "LVmEcBR8dJmqPC/GD4jHL6Z2LbI3TJWRtMm0j0ldfmk=") +ORIGIN("persianstat.com", "79JNMyI059WWglwA26d7TA5jAqrKx8/h5R0ouWldYwc=") +ORIGIN("outbrain.com", "+vM0aIwc1Q/u1KS0hPMpcb35i4qXCLapgFhrj0cCLs0=") +ORIGIN("ucoz.com", "P9xVFUiE8nPLnoqpCJ1ofFm+grMnC58q9kKEVvQ9HAM=") +ORIGIN("meebo.com", "t8/Ek9Ak7ZvgBar3a5RZOW1gCS8EAlBkJLxqIOmTVJ8=") +ORIGIN("burstly.com", "R7qUl4SkcZDX6YUOZIOf68/U1IG/jp9ZpaLhPQdUKKc=") +ORIGIN("crimtan.com", "K2MQpx6gZbOebQYnzOepr+1MMQ+njWHEqwIozT7ROvg=") +ORIGIN("comclick.com", "N+/mJoV1QMivMcUU42ad1BHQpnTybjY7U4h0LkoeafU=") +ORIGIN("liveinternet.ru", "FrvRXv8fg2soJz37uN/i6Z/1IbOld60KNrdJnNt2o8U=") +ORIGIN("advombat.ru", "7GZzyazYzkBCOgfZgjMGL1vq0MAaTC1CPrflWgQwHRc=") +ORIGIN("editions.com", "61QBYUiXxnNr8pZwkA3Gnkdb9oBe7u3zw/dDR4IwP8A=") +ORIGIN("ebuzzing.com", "KYvPd7HDRZ53AAflAaejYJPlHLO7fQ5Kjkz3IlFh1V0=") +ORIGIN("adzerk.com", "bcyGKZYeqV0YHkLr9yj9AMLvR2V8o8LljLbBjyxEFe8=") +ORIGIN("adxyield.com", "2daTXt0j897apgPeS+hVk5qgJuUVpKCj/8guK7M0NmQ=") +ORIGIN("videologygroup.com", "PsrodQzSbfw1HqKYvnL9wNcC80kNqlQudaj3yPlV+No=") +ORIGIN("googlesyndication.com", "Jbk4yGpJrjwETi3FAfhujm8mfe0akiIF9H50O2+SW2Q=") +ORIGIN("onlinewebstats.com", "iuEv6XDyJrqqKpKcgbtDPiXqcWhtj6xKs+UabHCuVe8=") +ORIGIN("formalyzer.com", "To5Xdml2Ym6aOIlT5sZ92OtkhXVDZNzvJgk2ZKDymiE=") +ORIGIN("digitalwindow.com", "wZLo2pDEqcvIu6Ci5O+Zn+ua1hjziuFul+aNYJpX31E=") +ORIGIN("linkz.net", "LUKffYtyPqJ2E/SVcvB945G2BbDakeCC39F6m1t2VTs=") +ORIGIN("px-eu.dynamicyield.com", "oVUIxA2jK0YtCcXU8kkUv+5ZTzWTlNuLmQgW47iNllA=") +ORIGIN("woopra.com", "tbiUsvexk7twV35xsoAkJ+dgPwJSLsfMbK8t+PjdFVw=") +ORIGIN("exelator.com", "lYhicFys4dYZauta5SlCvmpy9myqcXyFweDRgmAgzfc=") +ORIGIN("yabuka.com", "b4SA4RUrCu/Mhcj6+flLyMX8cb1Vfzoqm+wAg4c9FSg=") +ORIGIN("www.yahoo.com", "0cdbrLs9UADi21bvCuuY51ZOMVf26Fa5puKcytic38k=") +ORIGIN("4mads.com", "vBoOy3mHZIEdVx0E9iYKo7CzOjHgksTvc5R7OaosdjM=") +ORIGIN("shine.yahoo.com", "I8cNu4QF7Ec1Slwd8n9WqQem1n+8DPkHPFDdrse+sXk=") +ORIGIN("adxpansion.com", "ZTCnlYHjGXPIrKyTeZAhluqt6QyrmP1zBHDG0q2KW6I=") +ORIGIN("vindicosuite.com", "n605Qx6S2PN/Erno0XJxZT2kXRDDqzWyt7yiUJDyp6c=") +ORIGIN("spotxchange.com", "PWe2f4nohZCOvdhJOVRSIyi/9DFZJuH7srY5xM/IsuM=") +ORIGIN("akqa.com", "wQd7SYO/cOvnZM+zOTMFOh+uoZGgMgp7cfEbPbkjS/o=") +ORIGIN("aoltechguru.com", "y7vZ5YVX5v7E+HJEAH7tl6+DkklEsy1CqcBrTjn/IYY=") +ORIGIN("leadbolt.com", "xqUYGzyZtSODss1FwMAvsBxw0x8IrB7QLtFS+F3lPSU=") +ORIGIN("eyeviewdigital.com", "cKVphxihaBpEAUTJzJF5So73Eq7coXlMO9ud+Um0XTo=") +ORIGIN("agencytradingdesk.net", "78WIxAPOjIzrlIVxZpBxDOMpKP+qkZfDw2jlscfaVZk=") +ORIGIN("pawnation.com", "0Vhtca5kM5V9Ry4QdLpCAmO5KuCKXUO7jzLPmVG6Or8=") +ORIGIN("widgets.yahoo.com", "UCNojlsdBycxP3fSeIAa5xLQ8at9gCjgbxy52NzZcpQ=") +ORIGIN("casalemedia.com", "IEY3iayqxVJU8OtikaeCXeAYoLX1OJ5BsoULAnqGA8g=") +ORIGIN("peerfly.com", "sPyPhgQ+u10FEGQD9s13fhGzP3qvUUUkZxkCc06DeZo=") +ORIGIN("groupm.com", "ALfktirfcZG8t+IHoMa6QS1AvRNSjyzpbQAKiOqw3zQ=") +ORIGIN("google.nl", "kRaXgkx+slRP+Pf4wgOAWOPzVeUJyv1GlffGrUDeDJE=") +ORIGIN("pulse.yahoo.com", "ilZdJHwI/3/QlQ2KHze/LaKerkoN1lEm2HoNt8q0tAA=") +ORIGIN("openx.com", "xv6V9x43xDspP8sTXQNlX2xzm54ttdBUrZt6Y4wJuv4=") +ORIGIN("smowtion.com", "PlbexQJ6+j9VMmppHitvRh9dO96gqeiPjYfhGTTfbSw=") +ORIGIN("google.ne", "V2QG2e3j6D5yDm5GqdTzwh0JoFCC3ql9wudnlxU/Alw=") +ORIGIN("media.net", "g5zNFXMV8UAp+npY3chZ+LNxjnPioBUrrmYFpb8DlH0=") +ORIGIN("userplane.com", "OrVLdF54VcDTxuoWx8iXyqPzfJw+k8a+2RU9WwFkE5M=") +ORIGIN("ftjcfx.com", "Nj/4+bPOtR+lZTesgNe7uLNGLHWnZZvjR/BGNuqUOOc=") +ORIGIN("scanscout.com", "QMlmpaSGyMxT5tZqTvnQJ0Falnwr56BgU5a1ao5zKsc=") +ORIGIN("audience2media.com", "9G7M7GgTOHdcEPFAftGE3xhm6X8uMcTkiQaJSUNMiGo=") +ORIGIN("google.nr", "sDgk7t88uiwkv8+kl6GIZK43D3FwQsONGSbuEI1ozSY=") +ORIGIN("batanga.com", "hfDXHzfUbbIWNoLm1TC3x/atSiy+ocmH6gNd1H6gpd4=") +ORIGIN("2o7.net", "9z3OfrFf/VjxUDdTtlxd4cb4XSjPMTjdZA+a6u1r3Vw=") +ORIGIN("perfiliate.com", "Ty7IUf+U/nPZ6sR7WcSJgIJ/GLgXg3V8eAEn8V0X9BE=") +ORIGIN("entireweb.com", "ftH2yKaUUCiP6761X5PKXIK/Q+bkWB+bj6HMHcnACoU=") +ORIGIN("openx.org", "cATl5c0LSEoGf0SbKKuDeBDEhC3/TnqEHqbL6Vk+Q4I=") +ORIGIN("mexad.com", "xMCVoa6Jd9Ji9qR2ljLLuakr59RRvNWnv8Gy7GckLn8=") +ORIGIN("trafmag.com", "ttgwdKvZwPmsmSnlvbM0RY8DLWJF/Jec1RNeulQjcdM=") +ORIGIN("adfox.yandex.ru", "7vvhGofXQpX68eIbx9B7RCX4SPmwn0KmPsUTw35P9C4=") +ORIGIN("adocean.pl", "HtiJTQVO+KamTUt8ZWy1XpAZ/mDiFcx6LKvWhpXijO4=") +ORIGIN("ignitionone.net", "wzuWjS7sTZnBtGoAuJgn57ofR4boiHmtLzd1sBzFa5g=") +ORIGIN("justuno.com", "2dHm3T2UczuFiQBlLc7aI2sqh+DJcGdFdCeId/1kh2s=") +ORIGIN("demandbase.com", "AYNaN4gxNZlKQGgWl8ZRr754/JPu+Gu+P5t76am7E0I=") +ORIGIN("adreadytractions.com", "7Fq9bxW87utO0UHVviHnAfvx90+96zvGB6bMliuSI20=") +ORIGIN("clickfrog.ru", "Yp4LmbX91PAXkOdatXTK2tQ0IAFKCHOYXrGwQrK0IAE=") +ORIGIN("kissinsights.com", "H3t0k5PCLcKzw73X1PwdH1mqrI8xVkrx6qrDOXEXGrk=") +ORIGIN("conduit.com", "VH/THuOqMk5IeP2IZZ3vcu8MuDVn0oXwRXSuQItgkps=") +ORIGIN("tracemyip.org", "czX6UKa0/sSKKslXhntSTx0RGfMkQ+SXSNevtERz74w=") +ORIGIN("crashlytics.com", "7559mwZrt1MRUQmX9X1/ZPqaiXa6UWUZSsjN5nhuCb8=") +ORIGIN("storeland.ru", "YK63ZSM8bYyRq0LVGF5MMgKzEwNBcxo8zq644PnqPSM=") +ORIGIN("advertising.yahoo.com", "IYZ88DG/iss/2nzsN7R0RYXACvPCBjxn0KwaF8mlXhI=") +ORIGIN("inadcoads.com", "w2lhwH00E5WXo4yiFOS5SRcL7i1IOzCZg3JFeVrLnPc=") +ORIGIN("fullstory.com", "Axh1PCCCPDSZCC9IYuNjUJRtwhFBb/vQNlylFyTAEQ4=") +ORIGIN("skimresources.com", "7/uIGTt09r/tRlg3/K0XNb3UdyvBZ2nTzi6Q6OD80j8=") +ORIGIN("gsicommerce.com", "Tfy3V5qKimoR4VwLn8AehmsgPa42Mrx4i5Elkz2SJE4=") +ORIGIN("360yield.com", "meAQwlKCJR368GV3NOcDfiABpFNNC2AWzCS3zOGLTf8=") +ORIGIN("pinpoll.com", "+WH4BFPKmzBNAtV0IOVCLMb/T/bBY9nbA1io7xJmAbc=") +ORIGIN("quicknoodles.com", "A3p9ONjoEWoEqHnWJKT1F73aguUT9//GOqYXy0zO3U0=") +ORIGIN("appcast.io", "HmbM8V6GWRNsPeM6kftlf5KaVHWSkkANosMwVC0pZcY=") +ORIGIN("popcde.com", "UWt1l9jrvcqZeugg/75+dgwTvLZrML2lfKFit+Gi5i0=") +ORIGIN("xtify.com", "rcWHYtIClsqteZiyvoYNTXO+DV3NUcynD6vMXU2MVIo=") +ORIGIN("bigdoor.com", "cGmGubExcI9RDkaF5rr+ASDIsBR4mbTXxeUqTX6JkHM=") +ORIGIN("acxiomapac.com", "T47YG7ZxgBTjXYEdlI0Ra6hZzgzFqREjTi+lSyESELo=") +ORIGIN("dl-rms.com", "RF5mvUJT71liRbX3cO2YF9wm39mnD4MldbM6jKQy5rA=") +ORIGIN("nexage.com", "ituALroEfLlNnvt2InRsxFRVBY+MKmkuaTUQkAAQcNE=") +ORIGIN("ggpht.com", "p4OSwrRYVwAm1/4Medcd8B5+oryhM+1e7wQVDg3UjoY=") +ORIGIN("teracent.net", "d1NRwA2WuXkFYACVNA4jqpTMjrrnGvwPTak4p8EbiR8=") +ORIGIN("cxense.com", "ZOHNlLaui+/22uq79KlEwnlBOkSyov9AayYoj1Airbc=") +ORIGIN("decdna.net", "ZiLv4hUdkDCsXPgRSbiu7O1j494zssDuCiywupvk/Xg=") +ORIGIN("adgentdigital.com", "SpDjiJjfxowZ+8DlhN2qDzGEsDGnfzA9D31aGPk5F/Y=") +ORIGIN("popularmedia.com", "vBMWVXURF+YQxz+7UxfJVsfU57vUmfw8pGIqey8RQ24=") +ORIGIN("conduit-services.com", "vKydEg9OqboklKxp2HgeiPUL4EIqm1hJoQDbFwET4/M=") +ORIGIN("appengine.google.com", "tkHDTcesxXVRfA6fhplL3gfbJTlT5W3kA8jmBptXt1A=") +ORIGIN("tyroo.com", "HIyJZKz/pxyt7Ji3VXTgoLlz0Xvy/h+3EP+xZBZnAh4=") +ORIGIN("groceryshopping.net", "mn9tiKQ3blDTZUGehOqZtVjk/4ie17vIJa8TI6fCcNI=") +ORIGIN("simpli.fi", "W/P2mAz76bisjTcaKvCNlMdmmGJGCnkvgjP6TYijt2Q=") +ORIGIN("google.co.ao", "rcdMp2UkiGgCzs1nmlgOnLW8ea8ChsdCeZNyq8rxR88=") +ORIGIN("tisoomi.com", "FRArVYWAc9PhDi/W7fRPrlJC2UQc8hCN1/LXL6i0dsQ=") +ORIGIN("avmws.com", "0FRcGYEhuimiq7Cx8p2kBPTcEX8H/svnbsFoXVJdr/E=") +ORIGIN("tinymce.com", "8IqiBSZkPrFxfX6b9si08URJivscg481PlK2x7sdq6c=") +ORIGIN("rmxads.com", "KEOjKJnSkuaU+1x5UP9SQg/nMoUzhYIbnjV8Yj9Kqkg=") +ORIGIN("adsafeprotected.com", "If5mXVjpwUSZFC4Dnk0/QEBX4bVAsophWLUrjFHYjgk=") +ORIGIN("adatus.com", "ukqWBm01VIXL75F0MK0JgHj934AJKHV1dXObH6iozmM=") +ORIGIN("adcolony.com", "zcQFANtv2riPlFTWeqdKpZJRIbtOOZ2JTPqMypZoVV4=") +ORIGIN("mediamath.com", "fJUFPSkVoshAqFxGHVKS0174sUoWJzB/syU1CE9Y29o=") +ORIGIN("madhouse.cn", "QxPp9FBKk9MawRvYblqDD4QONZ++920oEj7YGlv8gY4=") +ORIGIN("dedicatednetworks.com", "5SWr64Xn+K/JNeczjCfFbYdRJuP+prQgHiTVStAlDSM=") +ORIGIN("getsitecontrol.com", "Ic76Vl1Ox+wPUaJQYEP28szLj4vd7oPa9xoSZprGc1s=") +ORIGIN("dt00.net", "2CtICrGLWt5Cs1bjSMn8rpaWfRb3oD91Qc85eH7daA0=") +ORIGIN("markmonitor.com", "QAeQpGPy17396Myck1UG1GQym8Acb87LPzfucx1LG9o=") +ORIGIN("revsci.net", "3BWdA95qp9f1l3So/PZs5DfT9QKbCP22KKAtIy+zhG0=") +ORIGIN("belstat.fr", "ukGada/IvOjIbWfQLmynZpweKKdrvNp2lBnRVXAtIvs=") +ORIGIN("instagram.com", "rkQFJ+B17LzNvF2pGNkLMzawz4ECE4o5EyvztNKCcnk=") +ORIGIN("googleadservices.com", "3P+l5YQcA80Guqe8hwrVTcC3KAa8jfMZ8gTTjYt4r0U=") +ORIGIN("harrenmedia.com", "Lsqnr5B+qiT4UKeKx4+y/cBxj91ylj8JcpEy1pbgDUY=") +ORIGIN("addlvr.com", "fIy93Ud3/bKTSPYxJwM8tmlvmYsvijk1IZop+BU5YgY=") +ORIGIN("s-msn.com", "imIZD4Em24jWxA9UneBx/g2LmdX0ecgnGQuZtnKXIug=") +ORIGIN("politads.com", "OQU8bSsQ+lOse1ftSk6B2uuOFXcZj8ZrRKfoAUSFjXU=") +ORIGIN("underdogmedia.com", "ijXDMM0rIO0xLNjyefyQVPgvCSJXQC3AzeGKmSG9uhc=") +ORIGIN("voice2page.com", "s3rnKg3mi/Wt9cMsp4LOW3zjDQH61ygv4a6Uipdn3ts=") +ORIGIN("777seo.com", "eldqMjZZgtfVqOeE6PX4CRuIIWVC7gAmKjjmHttO8ng=") +ORIGIN("xaded.com", "EDi2Ob4fnXXwsDvIiDjbgEWQLpDygeGiwynR/0NDYwI=") +ORIGIN("sageanalyst.net", "nfWZtyLDddOq4/zjeTel/YT1H64F5BGehZPn6Rpc0kU=") +ORIGIN("poprule.com", "Wz/RomQnOe0TipPZq6rTJw76SX50Hkf0pQJMUgihRjY=") +ORIGIN("sedotracker.de", "xGyI0MuOo0vBII1AQXpWwLSvWV00uHtLKRvAz2w9Uy0=") +ORIGIN("eyenewton.ru", "iR1lyJEPm3fHBaeuHWmz2rp7Nzlyr7n/QGoMBFZckPg=") +ORIGIN("mmismm.com", "dpjF8/t6AJ2guj3EAWK8/nodwGmI0QsruutmlgEhTTQ=") +ORIGIN("realmedia.com", "WzAhBlKC62qdpLYcNJAxUBvyWv12ABJcYVc098LqWfk=") +ORIGIN("linkedin.com", "6yXS0BsWjrjik69K3yJdhnmFTRswoxMOf6nyPTE5WUY=") +ORIGIN("extensionfactory.com", "37edxbg+AemA1e6QaI/V3XzNzHevccMDmF3+4PLFKZM=") +ORIGIN("admotion.com", "RyXRTdjlY9ojpaIMN8/KD0ERzXZcQVbtsw/5d4e4Oac=") +ORIGIN("tweetmeme.com", "/f+mrFB9Bk1ApNhbqHwq3fUXbmr51iOcotbfWSTAPls=") +ORIGIN("local.yahoo.com", "2PXZc5QZj7xIIPAmONlv+NtO32FFO8vKeGUX0oXLne0=") +ORIGIN("eulerian.net", "QvypiataekoLsPGhqXsLISMpJOC8RiAyj38jx3D5l5k=") +ORIGIN("umbel.com", "b0RAkBf+1pMdpQ+YWX4khKT9XI35x0sxYnLjq3DRJcU=") +ORIGIN("admeta.com", "s3u6RdtinOExQDCaPiUqxpn0MR9gurHdDp4HOJb2gYw=") +ORIGIN("complex.com", "pK2SUkq/ag7hoSdp+3TqqvwdangQoxz+xGmLL3ZYflA=") +ORIGIN("luckyorange.net", "H7caEDN3jCZpX2i8+weECMIgq5bmi3xmD5FxHZM/rTA=") +ORIGIN("google.com.qa", "i0Ih/3nRXVPFwY0M0KguiDgYgyAv+Pf+X0OwETlJLiQ=") +ORIGIN("intermundomedia.com", "crjUZIUbTGXtxa2dZWLqH26fYAC5+f0u0hCCfm2fLrQ=") +ORIGIN("matomymarket.com", "ItPQmeoiLpkEHpEiY5zt4PeMx3+xe7peD6qp4rTlsS8=") +ORIGIN("smileymedia.com", "FcOYiv+ZER+ZpRUYx/2LffBLT07aeV8qSTPRaCG1HbU=") +ORIGIN("adtruth.com", "aZRc+j4CfmEYOhG75YRPp2FLKKRKJStZcXO+FlbFweA=") +ORIGIN("adsmarket.com", "X07gMyQTDL9T5iL/EI9cGrlGqKGBIBWXi+R7xV0pOnU=") +ORIGIN("panoramio.com", "AL6GWrD5o7RdsdV2Bh1NlCbhdPn8qkMHRA5QQe5osNs=") +ORIGIN("hlserve.com", "F/hXmd5pK70vGuvMoEocBpLsQ2Cr0VHtVuDnhpZVFKs=") +ORIGIN("clickequations.net", "CUN9GpCYRuoCkrCiyCHa1je2cyrTBw/z9VD4o8CtTM8=") +ORIGIN("mdotm.com", "cN6MwsLxj5JG0fpT8zLoED9YQAGy/8fEbYbrMo7Jw4w=") +ORIGIN("scoreloop.com", "eZXkKFvgbF1JzWTvh8ZevIavN5z/zb+4BSMoXGjuIZ0=") +ORIGIN("causes.com", "qHdUKXgCRzGmiSdUCeBZt32GJfioRHURD+FBu/YPy5k=") +ORIGIN("revcontent.com", "W3JdvHglaPmv6OwJnYjH1sqHt/PxZtn2TakEQ/exh3g=") +ORIGIN("vdopia.com", "6U/SnyBk7+1zxD/c4q8+LPCD37Lxvg+OYC7UY75mhoY=") +ORIGIN("mongoosemetrics.com", "5hC4XCZe3E8jj/M3Ed6ASCqGk8vkm+VfSmvQZtlzWKM=") +ORIGIN("adsfac.sg", "f8fm6lzg8UT1teEHoZMgomXgGzFFKRYWTe/9PLK42to=") +ORIGIN("evolvemediametrics.com", "iuBKNh0Xs74ukYztt5EZeP4IZ4/wxuYoNfNgYaG7oCk=") +ORIGIN("webgozar.com", "JWqrquZ+f0GxZIQ0hBPrBcyEHx14XAlArW+dvfb+oW8=") +ORIGIN("adbrn.com", "WngE0QhzxQUHCuH+zsNsSRjiYj34m9bZeqXK6BCEeC4=") +ORIGIN("e-kolay.net", "raPEZSsoZ2AeTWM9gPgnWbei0MleSYXMw2EWSa/BGHM=") +ORIGIN("gravatar.com", "KXWTNxfibEiN7rJGrabCTjrZmZUCoiNs0mw4Ofjipa4=") +ORIGIN("acquisio.com", "Qjcwx4iaaVPGX0+NcKKjctemrQRAbwUfrEwA+stDI1o=") +ORIGIN("predictad.com", "0I3CgWsiIyI93tLL6wYxT6k71LW3jxwbZBUvxQiSv58=") +ORIGIN("wp.pl", "h3Qd/uSKV7aL4JAkGvt4pzkNDeAeicO8RPsceJ3xJXM=") +ORIGIN("cc-dt.com", "WQm7SYEn7sipfJGJrt1/+J11Gd6JjJWv67AfUnmxqSw=") +ORIGIN("adkeeper.com", "VSTOz95HX5XXOSfmPS8LvjTrnohRrI2kCLC5SZHJfNU=") +ORIGIN("nuffnang.com.my", "T/+nVrMIEhb8xcNm4y6I33ovr9R0qLECBnPoorcmg0Q=") +ORIGIN("w3counter.com", "bX8lxAzCmAWTMqgncueKHcQp1n/eMk5fhg9kl3YgKaQ=") +ORIGIN("orangesoda.com", "Z9xZPMzt9salBCLkgH6XyacNSZ2yV5nsWvwsgbV360g=") +ORIGIN("audiencescience.com", "ZL8xWNeRjP+vOOSWv57Zcx8y27giPnY0QJMp5Hsi0XU=") +ORIGIN("sharethis.com", "tYyTYgHsnXvnTO9BXWPXjlgNKuM+tEPLaNoWaeNzhMo=") +ORIGIN("fimserve.com", "ypQoagCJoEK5gooafj0rXIFnTtKWc/pTnG3zmlipwqk=") +ORIGIN("google.com.gh", "NQgzQz54D2er7eJ0j2I/wJYnHtthxU94HRWcvmiilOk=") +ORIGIN("google.com.gi", "cB6tv+IJt2k4Hbd1JwgAyzw7T7hgSRMUJQEcdNMSAHY=") +ORIGIN("businessol.com", "P442IHfbcnhPJ4eDXCtwO7uqodGW8IKnZMLhx/TvR+Q=") +ORIGIN("invitemedia.com", "Yl2FDsWvndsTS2wPkGtcndPVLOu9j1Za76sEoF3nIgk=") +ORIGIN("adality.de", "o1i+s9ym/w6wh4CxNC671QBf0RwsMIPgRhisSwL0h1M=") +ORIGIN("flickr.com", "9YeOmEW6nlEpFRegpUOZ8bEh4P+fCZzJZ3q4R2bvr+4=") +ORIGIN("adsperity.com", "aUFQw2Uw/fY+IEeZKCcY4x1codNeAPf7zijxfwKmFew=") +ORIGIN("directtrack.com", "5Sp2WflMcleqkpkKAyR4Vp2KAXjCCpPBjTJANCOqISg=") +ORIGIN("google.com.gt", "XH3m3MFUIT5JiS5uywFRBQdpf8ywOBdJCZnHswykaoA=") +ORIGIN("optmd.com", "GafAj6TcTcfTWeVuDyUIon5oBqPLKe9+/3jJuUtUKTw=") +ORIGIN("adsfac.eu", "zm+ZjXeRYcWhvkB1c52CdSyHia3Zakx2t4e5OfNh20c=") +ORIGIN("oewabox.at", "tQR+jez42pX8on1UiDJVRBLz4wURmQQkExWAj9uynfk=") +ORIGIN("mixpanel.com", "8lcK8TCZyq8VHyQ6Go8EL7MAp13lLr3FqcLVbbLNoxk=") +ORIGIN("customerconversio.com", "RyxbbiZ7fRmXHDE12ddwITsio3Hl0BrmpX20fKVf+6U=") +ORIGIN("bannerbank.ru", "RCxq1a15ggvbye6EAJGErCZB5iJkKAqjHdqwxBwSET8=") +ORIGIN("cdnma.com", "D2diPk5F0sPT36HpSTo7/iXqb69oZjpvXVvPAZA1Vy0=") +ORIGIN("ignitionone.com", "RdkWDRVFqvbz4xmF8jLQp+qkm2jmhSwZxxNAUvontCU=") +ORIGIN("dlqm.net", "Uoo/sjc/qA1b3UnhZPhs8iK3h5M83ewfPnGFQQr/ywM=") +ORIGIN("zanox.com", "leeFZsNdnblFWsUutZ9LE49kmMZ0nM2Q4AhsAwiEPB0=") +ORIGIN("mirando.de", "qeuThB9BdP9g2/Ki2GA72ZOW03Gs0Vz0cUi6wgI6YUM=") +ORIGIN("godatafeed.com", "d7SKc9V7dafDQQaMrkK4zkTO5Rsgir0icVKPnNcgrTk=") +ORIGIN("sponsorads.de", "Pwef80uIEOegBMQBGD6kLDICV6LGFc2Ql65GiqNfSA8=") +ORIGIN("lakana.com", "yJ/HzEco8mueGN11C3bn2mk+2FxLoYyGqZ/fr/wi2bM=") +ORIGIN("websitealive.com", "p5kMnVTKcxHmxr6xaUUtuk02WGmZevB2JGc+JozAEmQ=") +ORIGIN("blacklabelads.com", "azo1Ed81Lzjpn9FlC3X2/6vnQYxE+cZGtxk+EJ9MvGo=") +ORIGIN("cpmstar.com", "UuJEHjAcH64fTHMBtuIvIJnH9uBHhQoSfPcEghfiyZk=") +ORIGIN("relestar.com", "QmSHGIwWkcAzk3XielsLaL725/t91bdIn9FU0n8W5ew=") +ORIGIN("eyereturn.com", "tFDOM8+/eREe7cxaqKjq1MIDHRQ6B5I1jHSGL26ztTk=") +ORIGIN("searchignite.com", "5wkJCdRs/gzxjcAi/tz6j2E1XzrJogUn7EJBAUfG3bU=") +ORIGIN("criteo.net", "D7unWJiTohnOIEF5lOZqh9RABdcYSwXKAV5dDkD+YCY=") +ORIGIN("skupenet.com", "xAYsImGOJVlo28LFiFP1bKcXxfrYZ+/mfX/ctfj9G2Q=") +ORIGIN("megaindex.ru", "E/ezYJlbwJSoIyVTv16EPBNV0Zxsh0UveWDFKgcgfUY=") +ORIGIN("sas.com", "7KuXE6nQisq5P/Rs9/tW72ZNOb1PL8xxY8E3BfalbDc=") +ORIGIN("microad.jp", "aYKXropR9a8noZ/msW6HQnEkBTQmJUYv3yY2nsdYtqM=") +ORIGIN("ctasnet.com", "0sFpvoLFdg+VLBeiXgulnHZ4HgEAnW0FsmSRVUQ2K+k=") +ORIGIN("google.az", "8e9bs1hRKFS/HoVW4AWE/8SAQhMN+6WT/qxqCnSaKzU=") +ORIGIN("adperfect.com", "fqmR935JgS5jMmWIqLQgBktvkZXJx1k0dtG5+ZjY+94=") +ORIGIN("marchex.com", "NBNKhHNcd4OgulsquyUgsP1AFIhBVKN+mo4bYNUvgyI=") +ORIGIN("admission.net", "ax8pkM1V8ny/PitTuzp4P/E0uNI2f/qCUfyrJSf+eSo=") +ORIGIN("y-track.com", "g4X+09RVAgCmX41vL6c58SxyAqglkNeo3EAV2kxlmeA=") +ORIGIN("worldwidetelescope.org", "7BwfWSVCnzIcA+XC/VmRK+FNWrLQwtPKCoAyzc2NAHY=") +ORIGIN("foxonestop.com", "YZlYTAIPo9O/FRg7vsGx5IXm6+JKugjeMiRepmB4uwI=") +ORIGIN("googleusercontent.com", "FKFwhGtIF5nkJDljk/cC6hvnswwBMNyQDKAlkIh7Yqk=") +ORIGIN("theepicmediagroup.com", "i2hksGl6nwYlEBzGjvzVfzO8PoLfqphZCqY3FT6WqE4=") +ORIGIN("resonateinsights.com", "nTop+2DVEMk1m4wvcD5Zq8JDktcWN8vVKnnSvF1l8jU=") +ORIGIN("amazon.com", "DyfB7zRqDBriXdPkTNEjOc73l83FSEo2wiODXqaz31Q=") +ORIGIN("usitechnologies.com", "fRhso3ragsg/5UTEoeB9XqySbyixUEo1NjKjJ8vjmjI=") +ORIGIN("martinimedianetwork.com", "74T0ChjFz9mJDVbVj5TqGnfSLX2i6YbPZYowLkUpsSU=") +ORIGIN("clickotmedia.com", "HUR7WBOjGrl27FOHHRuU3p+QOx6yTdq3xdLerUzayX4=") +ORIGIN("pinterest.com", "Y4xrG2vLYYV+ZYU9ure/UB9ehQ28HgF/SXnLY1X4EvE=") +ORIGIN("komli.net", "H9sNLgUnw+X7Uk+vCd0wRhnOUlT69Cnse/DeXt4Utjw=") +ORIGIN("traffiliate.com", "mCSJ0CPnSsuGcqIhcur/o+SV9sAu4Ut6oLV1n1Yb9Ds=") +ORIGIN("arkwrightshomebrew.com", "aI8ui56mW4IG6HQz4tpkH4Gu4EKYVGVHbpFQIyYRReA=") +ORIGIN("onevision.com.tw", "jCDvBRzh31TPx9bJVIZm+iq++MYEgY96NCUFHgKAS6Y=") +ORIGIN("jirbo.com", "PIsWES6BR7tWKB8zPhinVsk0YvK7hVygToGDOcgIjmQ=") +ORIGIN("tremorhub.com", "ONG7Rscv0VqoBbv3ZNSJwnD97JjvGbCmPbCReQBtT7U=") +ORIGIN("accelerator-media.com", "y7MOkcdXYUDEoyFZZXFYcvRJiqPdduqs0qUc9EDSvKY=") +ORIGIN("rovion.com", "GclQAd/kg7cuZf2DZuH1PdM6/K2wNLS/2pZWuOhKJD4=") +ORIGIN("clearstream.tv", "YEhi40ZxP6Lsh10i0kMddvMNy/IUPsHx1O/V3Um5PV8=") +ORIGIN("tqlkg.com", "0qH8CXE4XrwGmhY9eIFrcqMaenAN0R3djBeZAK45PRU=") +ORIGIN("experian.com", "3mvcWUSZUTTd04pQbdCb5Wez6pa7K0kPgnRPI23QaMc=") +ORIGIN("marketo.com", "CsRxmMxwMnqWI58v1dRb05VYTVQVqJnK/+Ud1hDAYD0=") +ORIGIN("marketingsolutions.yahoo.com", "1MU6vtBUp/eQgo44JiBn9NGIPqmHYgKMCCIsvEbtjoE=") +ORIGIN("cya2.net", "6v26DRilXV+9eS2I+gDm6OgaqsbH8MEUuKY/3ycH2rc=") +ORIGIN("adwords.google.com", "2iFSHddOnnMrskkYzy5JALgRZQyy7r0y8IWdSk+GAEU=") +ORIGIN("optimizely.com", "T1/GWTBgFRkO+KZhAyMK585r+s4Nk4/VUNmyd+JHB9c=") +ORIGIN("fuel451.com", "/6Qag2mwFb/26kgwrbzYtpzbgIEATRlwKu12nf4fXAA=") +ORIGIN("touchcommerce.com", "YzgJvATJ3+AmtHbqO8yptQTZVgXHWgIN2q11hLOrVr0=") +ORIGIN("digg.com", "MOiGhBLzJ2II85kGtVWP42VmM6Hcwm6YSo1wZCO6MDQ=") +ORIGIN("switchconcepts.com", "kMCY8vCBDLGsr/7/PNIb7ZH9PD7q81iAA6GZ8BJe8TY=") +ORIGIN("addfreestats.com", "TJELSIW9FSeND0bf6XyrjM0V3Shj2qk5ahxLHzrymV8=") +ORIGIN("sevenads.net", "ARMngFn9xM3L6Lw8fh3uSIEo4ym0fQ2tBjfGBskjd5Q=") +ORIGIN("z5x.net", "FyyO7HDastA3iQSL5TvOGti0F/RZec274CLxv/GdTdQ=") +ORIGIN("boostbox.com.br", "Mz9EutrY8OQ6c+e/862uWemoiN2GgiuNdyedTteQlb0=") +ORIGIN("quantserve.com", "mKjDx0szPJWpBfWPOoltvbiej5qg7Bi6b1x2eaTDBcs=") +ORIGIN("shinobi.jp", "CkmLbxY/d2UGssX/hEiOWYE9HsJXvPAkaevUnANs6Nc=") +ORIGIN("dynamicoxygen.com", "GNlfgH9VCNXejDRYBHbzhnqwdaVQyqHKzO4Lg5ky5mw=") +ORIGIN("contactatonce.com", "q1OUEd5LMZpY3IXykmFgIDaHMQBXDWq7U0ncsU0UPuM=") +ORIGIN("google.mg", "FbFum2k/tR7YrNt/l5AHPXzh3Fv58q2oRsC6TqIghzI=") +ORIGIN("google.me", "UHyPtcO8Eov4/N4drByh+ZWNsm84cAEI3/DbzQ6rsYQ=") +ORIGIN("google.md", "B0TEWh9tM9CXduQUrAIfms08C9wCu1bEJxivUQslJWM=") +ORIGIN("google.mk", "2NZUkEAvhZ7HggIWZvjkfyDahrX+31//EnwzTgT0pmc=") +ORIGIN("metanetwork.com", "nTGfDf1jorZ7+Z/knkvDrMqJno4hmEMfELhrHKgxtpo=") +ORIGIN("yahoofs.com", "WRUwV4rsdBAmZZfdjZw+mnNh66UJGaOu88dwgbfAOF4=") +ORIGIN("kokteyl.com", "CDD4GCgY53cKo0iiZN7SQ885MABBNRqLMqoGz9Y/E78=") +ORIGIN("domdex.net", "PwG06EKyumOa+K3H4KqT3xPm3EHkin7Kb8Gwud4ofBA=") +ORIGIN("joystiq.com", "CmAl1N22afQTvHF6+kVcOIbErvhNwk7/v/P0VHX2fF4=") +ORIGIN("google.ms", "MxVr3CIK8Zt9cZE6IdtGcc2Np90oaDECj19fgc/sgCY=") +ORIGIN("netshelter.com", "6Tvt4x3DEXXJj+5fivfXhmJE2RxXX5zEX9TSZ2XWTpc=") +ORIGIN("google.mw", "qp7BFSsESR55x4c4yLrivgP7N5hX5/xPYJ4sv6+YQRE=") +ORIGIN("google.mv", "wbOz7Zz4DpvPo2HcykwEcga6p+uviOSGv5EM591vB2c=") +ORIGIN("google.mu", "1lxopNVrcvCjueLNhl26o84abJaYH8U8OZcGVMnuD9s=") +ORIGIN("iasds01.com", "baa0LygbfFrLo2JnbieP02z19w0KRzZ1I03pBcIGpxo=") +ORIGIN("adserverpub.com", "2MA48s4xSlY9Sg2bFugOnlbbi1olKIod1C+9M9RizsU=") +ORIGIN("ewebse.com", "4xFWDoI9u+7p7Y1BBFekdkOBZagnywdeEPBFKEqW8Qc=") +ORIGIN("olark.com", "EL9inOG9VCEVY6H5pBd8sf0pjbrCzRFjlmX8zHCa+LE=") +ORIGIN("traveladvertising.com", "dUrIr4Xthw1OSXT1nimCtiFNNojeJrH13GYLK1JM4lY=") +ORIGIN("mapquest.com", "0g/Aoe+Iuqq0ZFjSbhZVXAfMD4mAWzW61u1NBMlKrBw=") +ORIGIN("groups.google.com", "xen3JDG+xaPAlJKPWzeZWIJbGmFwFnzOY3rBBEJ3oKo=") +ORIGIN("emxdigital.com", "Te6/rJP3qE7KSJKlbgQSMd0YSWN8kWXVo9TgZhXFnXc=") +ORIGIN("chango.ca", "AgXXQ8WGtQygCTh10PtIrAuUgs1h//hqCP9YYrzBbq4=") +ORIGIN("ensighten.com", "0sycK5ZBgof+Mcs6gARfqC6uXbnRXVsjYazSmpKAbUw=") +ORIGIN("hotmart.com", "C5GebeMcJF24rXh8dNLIAiNkQJILsurdkTdndQcZFlk=") +ORIGIN("barilliance.com", "5YMp4HVN3ZQA8WRw4ZFwltiTOpJ7813GUGn41CYtXfQ=") +ORIGIN("nr-data.net", "iikUgReFeRO7gfdDzHUKYYvAtYx5I5Xms0+SiGhJdj0=") +ORIGIN("medialets.com", "gFk35BPQDX3Dc8lTVB3eg56lTXk9tUf0QinEqmtPC54=") +ORIGIN("brandsideplatform.com", "2Aor4FtQWmwsGC5NqqnM6vn94lB246uMfCaPwx61E/8=") +ORIGIN("userapi.com", "AHEjjwFRRIk6C9UiLD0olZCZmaRsmOwbmerJETcUT7U=") +ORIGIN("lfstmedia.com", "Vj+zgZz/8dKWSwXd91R1TVhhu+jbUbYFqrl846AJ/Uc=") +ORIGIN("kinopoisk.ru", "bKE7PsD9CMzOOtSESlnkhvYitTzHW142YuB9JDRa3wU=") +ORIGIN("clickdistrict.com", "6hYyaDaVZFPmJUcUZ9h7BZ2Fhm1gNVtgFXpZcwgiO7w=") +ORIGIN("adadvisor.net", "Me9kKp1u1GPFFOm9ooYMnoiGPCOeQWplu7dnDuTvzq4=") +ORIGIN("adspirit.com", "dR5Nh9/etZmkGfRMDABX/vjsW38cXBVvcNP0wwsqOsw=") +ORIGIN("kaltura.com", "VNW+5ymDw4UqsI0UkgvOoaBoSLMCQg03bjV0vUQik6c=") +ORIGIN("btstatic.com", "m4AQ0RNYtEJK4kQNtr/x1u55ykpvJwv+LExPsEwq2xg=") +ORIGIN("leadback.com", "xGAPZ6PE14YZwXuJroRrYPbH47PRS93Kq/HLv8vxN9s=") +ORIGIN("ppcprotect.com", "CIx29J/hM1O5TiH2GHggAgwIqI0fpQ9obCvEqND2sso=") +ORIGIN("vdna-assets.com", "n3nn3YXjHZGHmKTBmmkvYP4tMCIDhz3tX0l4rcHM3V0=") +ORIGIN("adfox.ru", "sh4CXPgS1+lUfXsHKYBizQh/Rfo/V4EVO2rfNsp3H9c=") +ORIGIN("interclick.com", "LvCfDU33F/Lr5hw+SZ0v1W9FM/QdDBi+Ym6TBQbVvT0=") +ORIGIN("reddit.com", "Y7jPzch+J1UGqhw9qv8G0WdiYRz5jIGZQyB+rnv6r0w=") +ORIGIN("ieaddons.com", "ZqaFzcCyLTtyhshT98D84mB+0w3pU8pY8nxrvg/WP00=") +ORIGIN("google.mn", "MiuIHgZIcVmxCZ3Mpah7cKTZDThLVXUXPV6RrxeSPGE=") +ORIGIN("echosearch.com", "C4YZQUG4xXTFcsP+HjDqUneOf3NLw+/fzXQSbtpFtC0=") +ORIGIN("bitmedia.io", "frOM3axfVJ7n/IdkG2VlKXN3LV3SMgDtk10vfN6NdMA=") +ORIGIN("udmserve.net", "E0/RXPWMjRdnlbHD4FfP4hfINOwcSy37CU2DTHZ3iNI=") +ORIGIN("stackadapt.com", "rBYUhD1+sNOL4cnar0kLq0PjrSHVgXSgP8U4gjttuek=") +ORIGIN("millennialmedia.com", "3IEO/EJFjA7cjb3uOwINASrXo1j+6Er50RNSZeK839U=") +ORIGIN("picasa.google.com", "gysJeYOwYsgFwERqMUj2m9kQIZj998qVqm740/99a9s=") +ORIGIN("google.ch", "x7Uf+U1ql8KFcHhwy1IZottMgpme5TNMdhYehUgmTLc=") +ORIGIN("admarvel.com", "ocK/DTgNfQ2c67xIeOki9Oqfo5JmsD+cycIn2qJtdNk=") +ORIGIN("mkt51.net", "OSLwZ5GsVZNEpWHKK1VwIfOfL5w5RjvtAXwUC6OQ0Cg=") +ORIGIN("google.cl", "546HM8M0Wp1WjzK4HqVOCDMd0Ql9BcgryBjGuMyHqxs=") +ORIGIN("thinkrealtime.com", "/2C5dz1Ss7WwpT8D6RkJuQ1CupFBHsaK/wyZ/1kWhiY=") +ORIGIN("google.ca", "F8hpVR2ojJX4qREKApIXU0gToLsWZJHzKgs6dSCMlcw=") +ORIGIN("adpersia.com", "GGmQT1YZY3VcXaNMBzFiOQ9lNq70XL2S2li9sMmxbfU=") +ORIGIN("google.cd", "3DIt8oOAFgd7MeS7hAparpnjis1nbo4jylxdGKZ1rtw=") +ORIGIN("google.cg", "EzdLGoCasg+aGVva0m9orb+X7s0nEMOYw7PUnN+si7s=") +ORIGIN("google.cf", "p8idACgpwTndX540CjuX58RQyBGQEVFK9FkJAznnSZE=") +ORIGIN("google.cz", "LN2NBW9VxUZ+KvllelMKdiR3yNpEgRg/JdFfnzhG7ZI=") +ORIGIN("pheedo.com", "kuO9ZH5oslIPbMOhWB+7LUrDDPLS1+89g5IzKP0auSs=") +ORIGIN("akncdn.com", "Q0/7DeaWa+cJcoRcl8oulj8n0no/JbaUXMckHfbnwec=") +ORIGIN("trafficrevenue.net", "UGWp5MzHP7anX7B9TSlrJi7k7HbhZxA0d+F9mqUu/Zc=") +ORIGIN("adreactor.com", "FXU94/aW9yB/jv0r64hUJ1jaRlh2n0NoxAr2NYXdpLs=") +ORIGIN("analytics.yahoo.com", "fPfQTfHRDndHGAw1cPj1HhL1DtRPC7uj3gBJpYG/7ms=") +ORIGIN("google.cv", "B6aki1/PFv5CWyJmaP1YE4ORA7MEluuPt9JPhlaGTBk=") +ORIGIN("axf8.net", "SJzyJvCU7yNf2PipzWsW7di5pg+SzMpamyfG4Dfp5hM=") +ORIGIN("beanstockmedia.com", "wYXXDkxW7N+KSq6YFZvIGK8QWqnJtxrMQIhI9mFMJZA=") +ORIGIN("gtop.ro", "5j0TdwyQ7AvMsIVYiUnV68mNz1d+hgCg/AlRFbMy7Xs=") +ORIGIN("google.com.do", "9gZmzicakB+lvDixIWedcqVCQKdmhorPxKSj0RAH4J4=") +ORIGIN("xad.com", "+9ICFdzoZOHHK4G0JQjBgF9jaHiDj6kemb11hk8ajwg=") +ORIGIN("pixel.sg", "79DOndo08/+xKci7pjuo+ck9ttX+3i0IiD4a8rBapFg=") +ORIGIN("hotlog.ru", "uCtkNOBad6G6tJS2LBAYOFpFyODRzbTQi46Ovz4vsKo=") +ORIGIN("adsfac.net", "eHNovZ3wJujh82BdEh0ljkS2UKrsnE2JN2LicMGSc90=") +ORIGIN("sketchup.google.com", "4adbukGj0RzHQ8+3nfZU6rjcviAxJEwY22ceJPrfugM=") +ORIGIN("conviva.com", "ZiHxoPvSfjdmbk9N8nAjkYzGCplKadfV9YQLWCHI9Us=") +ORIGIN("mediaocean.com", "RsXNjY3HEcH56rrScEwstoaS2yb43WLIWLxeuDZPyok=") +ORIGIN("shinystat.com", "xLK++t8SqIHwkwcYgIdiABZSICAZI5uMXNmo7Df9JWM=") +ORIGIN("tweetup.com", "ZvWLG1eYq55FJfWPqWw+9Uhh4O9GYy4RJ4d6k3wyFCw=") +ORIGIN("mindset-media.com", "vBziBg6GwM+y/R2x900xAjf3DA66156YSaho1Sv+wwM=") +ORIGIN("veremedia.com", "JYgr9acFRsbbi7EmlL3UmWaf0d9QSZswZB5Vly/i7WE=") +ORIGIN("infogroup.com", "pu64Hnf//pi0QZogP1vCbRoruoAYfMBAy04rcTb0ddQ=") +ORIGIN("shoutcast.com", "gVMxBTqTp0lLCD2L0PT9Qu0hG250oROigk6Bm2/U7qU=") +ORIGIN("twelvefold.com", "lEvdIp3tpx/IZ8YDG3wt0MnPZz18TmCn18UYztYOz5E=") +ORIGIN("peerset.com", "FGuIyFWPjHIMPEnFP28uvbp1rlTn0sWNlDqaTfU8Wlk=") +ORIGIN("wishabi.net", "wA6Xlk8hVcT5a4+AoELj3oLC2wCQCLA2uBM5wEj91Gc=") +ORIGIN("switchadhub.com", "oF2qaDMtkBw7k+dTwnyU3t+QCUrcX00FGZ8pyGYyU4w=") +ORIGIN("estara.com", "AbRwgc9pHMaK4lezSL+TSlHdeVx8M3ZEF37GKcVmr8s=") +ORIGIN("dyntrk.com", "ZqbUfema6OjafVQv7zZhYghHUjaCF7lJW3OAkTAGshA=") +ORIGIN("ad-score.com", "+tV3Oakjw63hvEkwU00IeybEZzqKAqj4GD+VrEvvNJc=") +ORIGIN("adsummos.net", "sNuYs6deC3Oa49kf/6YgIEQzUCVuYBs++K8vhCCs5EQ=") +ORIGIN("sptag2.com", "Qqf7sP0ytdD9vk6WbVCkDv8OjPkPC3++HJkrarY/TuQ=") +ORIGIN("daphnecm.com", "S2eyId/mnNWYaT2IdyTm2RRvJJwUuCENKtiFoYh82cg=") +ORIGIN("websitealive6.com", "Aur+NtRD3FHz44KD7b54Ls8+SBHmHiD7aYk4UB3StvI=") +ORIGIN("dailyfinance.com", "C4P11jI/fL/+vw/28ZUszXJ0xHLjD8fYCs5l4U8+SWM=") +ORIGIN("i.ua", "3SjuIK17KVP94ebIKRENSKeCcLT4iEqlpx+owW8H0pQ=") +ORIGIN("adternal.com", "KalD1p1jS8XelXVybEaOX1w0/LQrlz/3pvLKF0bPo3c=") +ORIGIN("www.google.com", "vJqPK2//1YVx4Yi7EQVF+Ps69RzfGmNpbVBamHCoW+U=") +ORIGIN("dmpxs.com", "4NnbW4vzjQfTW37xV3t+hPeGhEPfuHIt84frlK9qBgo=") +ORIGIN("permuto.com", "jy0X6AdIuXABbdInZw8m64twq1BWSgU9PGBjMHMMqnA=") +ORIGIN("lytiks.com", "E+SvgO6FVVieXJXZa/jRfMlmZuRkmH2JWqLLZOdzFjo=") +ORIGIN("yandex.ru/clck/click", "rbUJTeMVNsu+cCVgGv3wwaV/iTBYB+mmyLk0CIkeGOo=") +ORIGIN("lifestreetmedia.com", "uNuXMGjMi6ucDE4EghXD2Gt6YONHizxtc/MjtgJZWuQ=") +ORIGIN("iacadvertising.com", "7kiJsLKAeRLzEK71yW7c0TDHOy1JPpU4OVJQh48Wa2M=") +ORIGIN("google.com.vn", "wieBI+zdpgrN0PXNzs3jMH/mFj9S0fu7Bwm9B1lwYHA=") +ORIGIN("yoc.com", "E6KZV0OjEKfjDGOSdyxUeCMqyiz0WU9oVIjZmAhgbOs=") +ORIGIN("google.com.vc", "8SLhnFwJliJl86DDpQ80yTOd03d1Ps3zHTfN5dfhBRc=") +ORIGIN("heronpartners.com.au", "EKmGlXteGJVSI10btVZ9Hix7gHXF/pdDAVohNwYv92M=") +ORIGIN("dedicatedmedia.com", "ZCr3eVd+Z3lI3FWX+8QmbGtMXepgURI5tJmDMrxewSM=") +ORIGIN("eqads.com", "vZIQ66NeasMvaMaEUe6d9k6KagRch+eobsqSN5w+hoA=") +ORIGIN("projectwonderful.com", "k5o3pKV2Z9jVm3Hl7tzsfdX+KmuqpiUtBavllKxNc1Y=") +ORIGIN("evolvemediacorp.com", "zpEQTjQEdDCsstKBdEKca8jA+iM+zxiPMYrrnW528I4=") +ORIGIN("webgozar.ir", "4j5dOare0AX2aVLo0zXwNedPfluJNp0TuHL7JVDcefU=") +ORIGIN("liveperson.com", "0hOIheaBe2IrHt8tB7UJhzSw9t80WWXZpVwanQwyTr4=") +ORIGIN("websitealive3.com", "BGQ9yzLZp96Iqm0t7iXrhD26kBK/IQ8AUfkXnqn02p0=") +ORIGIN("dwstat.cn", "206q8jUyaCsYPX7yA+JXWvJyDmcbq9ahhmszX8CzGa4=") +ORIGIN("adrevolution.com", "JeK60//TuczAoxBsmCZl6oLrcXX6BfVtkFUjBke3n2Q=") +ORIGIN("omniture.com", "CsqQBRoN2C/bLi3Zj9zEkHYt7Jcpkook10fHi+w7jNM=") +ORIGIN("veruta.com", "9I6ej0XsQEePvelgWRn9cmIbXkDc6Cv5CxulNJc6lIM=") +ORIGIN("gravity.com", "gPqWmGtoz1Gwa99U6NLkZofxBaAHWL+mlxp+FzsxXpk=") +ORIGIN("netshelter.net", "sH3PAKpgrk4KK0VQlg6mFeX5zmbiffCjrdeS5P/CG44=") +ORIGIN("fathomdelivers.com", "8HXJjz/RNbP90JMQdOnLr85qO5vx4Z8jWO+fhPiB5jo=") +ORIGIN("mc.yandex.ru", "PNZAwztdc7k+X5y9lgqCT4Zp6dGz3tHpfbWiKim1r/o=") +ORIGIN("compuware.com", "/tiTZMjwmHxuxf+CE0OgYRsSAuMW16SQeH1chrgy5Dk=") +ORIGIN("w55c.net", "QKoc1xcuERNaZhDo/NBYskByq818T0hwy/26kNaV1ic=") +ORIGIN("anonymous-media.com", "Sxtqpe1QzdGniJHfrmkEgUKZRZuIdHR6PmJGdQb/Ibc=") +ORIGIN("bizmey.com", "W8NT3a/x4lp7082fw2jWltEMKZodztG2OEzoxTC2/Kg=") +ORIGIN("cpxinteractive.com", "8MEN1yx2q/3nUOUjsTfPgPpBP2K1WyzGTCNxLEfV9EI=") +ORIGIN("mobiletheory.com", "yYFUxkUUfPnVfuTda5/CbAzKErv80Xv8Uqx/ybAViEE=") +ORIGIN("picadmedia.com", "f55vWm6KWAVReZ1JIQGtp37Dt4/Ar2NxrLnF6deBxFY=") +ORIGIN("springmetrics.com", "JqU2UDBBpezNhdWQ6rRmYmSzZGLR2DDHpTN8NfdCElo=") +ORIGIN("wunderloop.net", "KpZ0PwuU4cZyyjAOXU/i8HGzZZ76Q+3xn35ASQFimpw=") +ORIGIN("msg.yahoo.com", "nRFNUlJcJvSHd5daJPPjjK2XPZF//nEn3Hy3H7hDekc=") +ORIGIN("csdata3.com", "S4eiVPvOScLfuKNuZFZ7qrLQ4ZU0Q2JW+ymQ2azdRU8=") +ORIGIN("buysafe.com", "iEpTfJIzDt9ogMN2UPHkqZQewqXEXQn8sdUnu0bA1Xk=") +ORIGIN("tns-counter.ru", "TVzPxqJx14XrxqoruMtxmEMJZjHlPX9LBUnmuOub8UI=") +ORIGIN("dinclinx.com", "Tv+yNARYT/2y9gjarhxpiFlSFtdec/P9bmQxffsevQ4=") +ORIGIN("amgdgt.com", "2ot5D3UINdSQ4JEET3fAcqNgPhCeJSPbbY3Y+lBImAo=") +ORIGIN("glammedia.com", "9O0BlrL5QDgOY39bOlssCKGAWxbgNZqvWN/VS6XKetU=") +ORIGIN("quakemarketing.com", "3/jVCVkwCibsOE5C9B1KulFGU3GhA+CqykJKgyuujSQ=") +ORIGIN("advertise.com", "f5ik6QEVMkhZEV5/T+lsjtoeNvYS2QrCiH1YVHTKI8w=") +ORIGIN("ppctracking.net", "ZLEts2V3492WdYabOx8f0X5UF47WTLAe6S4PYvWmXRA=") +ORIGIN("ad4game.com", "8VrSZSkCjQnnFowBg7okluuLI7FGQlqtEyCtBJFgbic=") +ORIGIN("websitealive8.com", "abwTyMuDEzql4/vegcAAH4kDK00eYwC+7T7Ek3Oc6zw=") +ORIGIN("popadscdn.net", "8eFA2PKudEf8XSE02KHliQNyq+vQSDin/pD1KhflC4A=") +ORIGIN("crm-metrix.com", "tFXiZ2T0b0kXkT8vdxMHxlEr7mbiBz5YgCzzhXkim8k=") +ORIGIN("trackersimulator.org", "w5+97KYvqCfTZ31OARaAqvvPOCo1krYQuIfjEsyUWYg=") +ORIGIN("spongegroup.com", "+c+DpR8GCDQxD8mL/W0doMRDucRD9QBI88VY32ftQ/4=") +ORIGIN("mybloglog.com", "/4NeVNQ1M3654f6WhT6Rp9Rg/UC3CdwkOdjwgVB4K1k=") +ORIGIN("google.li", "bSg66dRJ0Y7Xi68p0BK4KyUmgC0fUVOaMaCcDVpfv14=") +ORIGIN("google.lk", "0M7cqIquw23mEG/hPKJtQJZJTd2n3LIz1hKNL5Po1Kg=") +ORIGIN("lexosmedia.com", "nDPPUa6tm2v6SmNppuCY5R5TqTtqEpFCja24pwwvPFU=") +ORIGIN("adlibrium.com", "HDqJ8S3hhwl6HJB/A2s5FxSQ1B+1o55XbQHtXC5jxJY=") +ORIGIN("google.lv", "IGPTfCdZRCF1/Qp//4SnzWMBLbFETQAniEqGMd+QlDs=") +ORIGIN("wpp.com", "dSetmBZ8rLLLCD1uFeyqdc6OIR6VBGHMI74WBlYf2eA=") +ORIGIN("adknife.com", "QnSNJPpPLFqs7vYXGRo3AJkhJUwkCHMKS+OhNLreZBc=") +ORIGIN("crosspixel.net", "zW4y8mD4u3XpHrnhBp8mwQ/9Bh7qPLt7BXUajfhhv2M=") +ORIGIN("snapengage.com", "dOHq6VLSI+Z6mBCMepKXxPAE6HQWFkTpf8kcSdGNmTY=") +ORIGIN("xplosion.de", "8+0JhpNNG8DXKa/lGsFDA1ux/MMgmFj/CCks0sEnqO8=") +ORIGIN("adkernel.com", "aAIrahvZwEzvs6RN5f2GIQ2vG0HC7hhqt7FJ9QIrMEg=") +ORIGIN("websitealive4.com", "jt5oZTrUUh5TDJkEMk74ZNErld5gQccPO1B55RSsQ7Y=") +ORIGIN("carbonads.com", "mefqduGOSJItMB7R+Dj8XuiX4UVwb+ixQllUXWecE2c=") +ORIGIN("otracking.com", "UhBmG37WdPq6vXsfqdOBbSuiLyobqXKQKlbH9cgQGW8=") +ORIGIN("autoblog.com", "rPgd1xerke9Lzy49Zwrib9/jRmQAldnUUaskRhHv+5M=") +ORIGIN("allstarmediagroup.com", "IgYlMENDsOWmSyI8gN54Cp4TRkJugq/5XcaDrekaVBM=") +ORIGIN("bouncex.net", "qkmWxivaN0fcvwbEzkr8vZrZOOwAdAC3KP78Ex86Enc=") +ORIGIN("lookery.com", "gY3To6JJa9KEkh3V36QnfIL6wnQ6KIOguizkpx89xYw=") +ORIGIN("convert.com", "PISd8WcWNhXNCiuq+8m9OZ3TgWzltV3plCgbW3WQ2yk=") +ORIGIN("adocean-global.com", "6CCVz27fKMJrRaDxKU2Scx+3RbKCjtC5LxPkZaXnzXU=") +ORIGIN("att.com", "d0nCZX38Hk2lrLEsa57DjG5t8uQuxA+UPKHBAKbDf+Q=") +ORIGIN("designbloxlive.com", "qZSx2/N4uZJk1ydmpFGnxc+dxQiuXub0DNgpbkv1g3M=") +ORIGIN("vistrac.com", "LAfv+rV2isKtDJ+WmD00GpuY6nAtOeXNAOVgDZ2EZ24=") +ORIGIN("intellitxt.com", "NVnY4Cd3jXQngn0PWWi2EAnS8bNPJsvszTGir0rulz8=") +ORIGIN("crazyegg.com", "4+/K5s18gWYF1Ic2GgPEZrH1dAinX0jndtw1uiS/56k=") +ORIGIN("mediatrust.com", "rfl4Ymc3xN7C6Dh5tB8qoCSPKDOOME6PjssAIIZlBhg=") +ORIGIN("adviva.net", "HarhZIbpE4x14AJZmZta3OsAuxQRfVWCjcq2uC5dYME=") +ORIGIN("campaigngrid.com", "zbZfKo8ZFCuicjSLlzX4Xxg3c5N9Q10JNFQl9hwBBgk=") +ORIGIN("rocketmail.com", "02vfjTiqvuyLePEfyprfrmxD5X6dYrRZ9kQkfX9N1wE=") +ORIGIN("superfish.com", "s3C5LrOHkv5LcVHXaNVWpVn9rMZnqvDGcOWqQopsYu4=") +ORIGIN("adomik.com", "sYMWu5JDeAIqnMPnJqGOcN5rsbh93KPoHWItQ2pl7Bk=") +ORIGIN("netconversions.com", "ejMElOuFS1cQ9JntjBwr2Ru/y40H8VXcr6gJdswNG0M=") +ORIGIN("adonion.com", "xhbtiZVLFexGZbgGFjMjLhswc8SkNPN9RA33ToKW+Wc=") +ORIGIN("my.yahoo.com", "FYnJcJFlhsFlqyyD1eZLo9cyE6zY3g3P8Y7ppAv63zY=") +ORIGIN("adriver.ru", "in2sNGu/AEhp02fc7hy0lAUUK8mBIJ6AUxubpDOEdlw=") +ORIGIN("adknowledge.com", "o0zse4Z7fM+b+ViG85zFEBuSdYdQpoKQkDrAnL0DajI=") +ORIGIN("rhythmxchange.com", "8T0gblAQunnZ//eXTHBS6NZAfkuHGqzklgmh5KW1Dt0=") +ORIGIN("thebrighttag.com", "YJXz/sKCs62da1tHuMpXfDtvXrBHx/rzPeTgyt9BKpg=") +ORIGIN("oxamedia.com", "z4PxwXxQ3l/pLTO0y1I5a0ATT1sdM/JLNB8h37aj+6U=") +ORIGIN("movies.yahoo.com", "Y10FiN5kWDV5LMSZWAZs6TpI4D7GcXbD2cBncMrcGpY=") +ORIGIN("matchbin.com", "M8xBMW7EtS7cW0qJIWkXgKbHJAj4L3tpQirYpX9NFzQ=") +ORIGIN("opt.ne.jp", "y9HMvDonlBduiZAre9DZ/ByK9+0W413xHnsc9mvqu04=") +ORIGIN("browser-update.org", "D13YWLbPoZomvza7sraf++qFkIjdv7MtLMs1sAz4Nr8=") +ORIGIN("websitealive7.com", "er0qGppCIDgii70Ssj03XNwQ7Hjb0oi+nG6voZJO/GU=") +ORIGIN("apture.com", "NSTDqUtQrEtgnuEDnpRKEJi46ThS/xgpUdy22UJePhQ=") +ORIGIN("infectiousmedia.com", "mnIoFQV/aoK0KnbqZ7N41YZI4f0R1Lbpte0mZftw9JA=") +ORIGIN("undertonevideo.com", "u/PuW+sHCPtLjGkV0Vroel+Zi2BmbTYobZF4SM+vG+s=") +ORIGIN("google.cat", "k4DmM08D2qiWXQPhGPrpyrXirM33cddjtW2xvV/yyHg=") +ORIGIN("huntmads.com", "9H+w/cVuKsdQop2z/YlEM9ec/ozs9iychDUEX94CQSQ=") +ORIGIN("google.bj", "iPeNPS7WuZQ/5sTeF2+NpMQdq0qrjHhHciAVSHNKfo4=") +ORIGIN("google.bi", "3rtF23RFNUZaXyuAizDKRBDiKbWM6D0eUgrxcR5chIs=") +ORIGIN("google.ba", "DUSYSZeg4k0u06L3xuP2ye0q4pIcGq1TgfDVa0U1AZs=") +ORIGIN("servedbyopenx.com", "iznY7JdbbgWEvdjloOtxKGmqwzl8ELKjycl0Fn3UJJY=") +ORIGIN("google.bg", "BnCLogJbzLdBYDbKPxEEm2etYQl6zq89pKhZ520alAk=") +ORIGIN("clickmanage.com", "AU6E/BZQlPvADha2b7N9NLc8xQyvA7eyJ/xAcHm0qqo=") +ORIGIN("google.by", "y9B0FoAFG/T5HmhtZ5hw3D9X2tbqOkogY0iPtJa9/as=") +ORIGIN("surphace.com", "KHIZQy89ZTBaqpkTZSITwGnWjn+pZnnhlRwMjAFm84g=") +ORIGIN("google.bs", "XljeXn3Rm7rYRvO+F+Nn05Jojg7+bkML09mfPY5bqEw=") +ORIGIN("adspirit.de", "0lG0/truCjj5wk6gR247O+9gTIG3tSORfYcvydMC7Xs=") +ORIGIN("google.bt", "4GaOzpg+S+HVv3nzULvz1PjvWhfPanbpRY+OKffaydI=") +ORIGIN("gaug.es", "zu5jsFSLhxBLCEpRDYaNYN+JicD5S5lG1FePnwwvpPw=") +ORIGIN("tapgage.com", "wjCNKyL58d264ItYPo8WrHpkepliC/4HHjcBe40zIns=") +ORIGIN("oneupweb.com", "58Yq06aCdVYVbGZ9rFQUZHlBBWYkof2vQgutxfblkFY=") +ORIGIN("affectv.co.uk", "93xddV9hnF2qfqLT1UEsaU0d96T2xa65Aw1Xh4xoYWI=") +ORIGIN("paypopup.com", "Fp5K6TthfsQVDJixT0ZJ96tHIFr52NazB9wfK0J5UJ8=") +ORIGIN("carts.guru", "aJvJm6hhvHjsx5vd7paUq+/6rxk/HAY8SlcX6ho8TKk=") +ORIGIN("gamned.com", "ivaB91dO+U26QN0Frjg7MkFmgZDgLKiY25imJ1RC0rs=") +ORIGIN("yieldbuild.com", "EFYzwzRBVKFwoe6UGvlJZLeedBaummRiR0qbUGCdqis=") +ORIGIN("sptag.com", "sZ9cgdH8TtKZoPMCzqmhetNceaLRH/n4cLax8ju4bO0=") +ORIGIN("reachlocal.com", "Jn8z4v6wrZaK3zEspvG+8xRvtqB25OAJ8y8bvfHMcD0=") +ORIGIN("eviltracker.net", "K1cV9PtNwyBpd6plW9sROObHwRcU9s2xrhJEmLrnK88=") +ORIGIN("orbengine.com", "PayIC4V/B2uTywlOv3dypCqiqvdaXf/x+6oFkj7LZwI=") +ORIGIN("trackingsoft.com", "EiHP/Gac4SOOvyQhIKGYhjPFfNYKNNZeBRinn8+27Rw=") +ORIGIN("nostringsattached.com", "QyWLuBbowDWoPhp6SerVulx5gmxBFjUKD0EndtYmaXA=") +ORIGIN("zenfs.com", "8rUvUWwO/3lL+yV6WOlvPt6t3N5k4XqTUg2ngskf4/o=") +ORIGIN("openx.net", "N2yxTBUwCIRdJzRyvTu1E9k6xDFv4Mivlvi/R/VBxmI=") +ORIGIN("demdex.com", "7g6HOQuRswnx4IJ4j6ZqjHynFhLYODwRKQN4RlTPusU=") +ORIGIN("crosspixelmedia.com", "qCHolpbrnqGYKTCcO+rC4WPPxqU1ypw7g6m4j5A8E/M=") +ORIGIN("nowspots.com", "fqofyOV55PGcHGOUcYhE/73b6EQ107dpw5RnSIY0WDQ=") +ORIGIN("scorecardresearch.com", "PBR77QQCAh1DGHwG3Oe3UfPlynlzqpKY0QhJ05b6Ryk=") +ORIGIN("compasslabs.com", "6ax8UJMTPQgLvILYac8xsCUV8r/B9aTxq2E5myr3NHI=") +ORIGIN("various.com", "tVT8ZfkfShu03gdLEMKSBVXJ/8sOswZJ3bXm3OeTs28=") +ORIGIN("medicxmedia.com", "C9h3TE/GPOsEAKwiv6qUPcj5q5BHgGQkFvOc3Njg2/g=") +ORIGIN("optimost.com", "nRMxFiGdLD/ogOBWZlqBaVDHD56enah1UxWYTKcRzKY=") +ORIGIN("adlooxtracking.com", "BBq2eH+GoIFgUj0KKEi0ZR26lvlV/bIiwyfB8qiXMBc=") +ORIGIN("adbutler.de", "PjzD1i+4C882oJ67vFls7pGn26tx+cIqPd2KVxwXe+Q=") +ORIGIN("windowsphone.com", "ZX3pXTD6OTW1hsqEZbWF1Hg39emDx3U3ehELJPjj0fs=") +ORIGIN("clickdensity.com", "zZ3w4VKWJiz68G2HVbvvff/CaEaCdiuGgufTUbkaIV4=") +ORIGIN("gumgum.com", "RCVqKGRSE/eZBNqF7W+5uvHFdzRmG0j5IDDoOU/nXcE=") +ORIGIN("adonnetwork.net", "IoE/tNI9x/474hl90K0Uu/dOf4k+hpSZYsNFK1w/PLw=") +ORIGIN("ivwbox.de", "0Inl+SjZyOCQC2rOgTuAuMlqbDPnYUueTZ6Nfo+il3g=") +ORIGIN("csm-secure.com", "GiYPYviUBALrdgx5MKC139ZzXfpGnAPXpgATx0Lc2uE=") +ORIGIN("atwola.com", "FljpjwtfAW//yhmIzHzxbj08wiU8UL7xui+aHb+1A3w=") +ORIGIN("accelia.net", "M+nOwkJf98I7XdGhVMk5QE3crfheAbIEvEujzu5aDsQ=") +ORIGIN("demdex.net", "RiD7v7ztR5Sits5FKjWoww0gH0cijPsX3LVnc+tST8U=") +ORIGIN("hipcricket.com", "O2+3XKxBXhO6ZH0wydEcdBHCqIp0tjnfljoNrEaXDWU=") +ORIGIN("touchclarity.com", "/ngzbBBrrHgm02Kjup26lEBR9zf1qLtyCiPz0uIDVxI=") +ORIGIN("trafficfacts.com", "VH7DF3Ey4kK2avLlZz2QPOyN0pHcgy7WeyIMB2EZg1s=") +ORIGIN("aloodo.com", "bz+zvsA6+UzbWz6a1ZFPh36c1gTNPr0GbEtpAgK3JgM=") +ORIGIN("yieldads.com", "C+hM4lGQA6qtZiPlld0Rf7iI1WpUZ0yJmnK5NFKP/KM=") +ORIGIN("did-it.com", "yWJDUgjczrtKx9vvOwS7UVxdPCr0o0QTnUDgDUGqRRw=") +ORIGIN("e-planning.net", "mwy5sQ8Vmo1DG/Ezap/zAgr13EYBy2yL4kQ6QjFT93s=") +ORIGIN("creafi.com", "2WXK7KcdkNcPYdImSYTyN4CMb4TsfRcAfdUVzCx5bgs=") +ORIGIN("manifest.ru", "vLViDR637oqxqGivTf7msK2QO94WG+nLdypu4kJ0/SE=") +ORIGIN("out-there-media.com", "j7OTv6FCeL6fRKiiQ+W9JyZFae9hJcHNmGSK/ld+5yM=") +ORIGIN("tinder.com", "PUnNFz/j+8i2kObF/IAFpVkVFLCCcjcYSPV3sxpNBVk=") +ORIGIN("ligatus.com", "3kHilnp/OaIYgoFtAdTe8XWn+dnFmwFxp0MkX2d48s8=") +ORIGIN("an.yandex.ru", "BSGPiITwntLN6Imf+ya24Kz1XRJFZaY1x2ugKbzBOKM=") +ORIGIN("sublimemedia.net", "j7CnXCrtbgF9a42k4+AIyJ6A3X27L0e3jg+YhDX8fGU=") +ORIGIN("newsinc.com", "kVDev/mub6TeeqCQF/XggdcBuE1otUpbA0MvFcdmcoc=") +ORIGIN("listrak.com", "WKsBd0PdXhhkstYrFMKmVwf7DWpxsfLqRuloNTN3po4=") +ORIGIN("outlook.com", "lHK41tv1djlJ4bMJ5dsVV+3VF4ts3uTDPUMDC43aTH4=") +ORIGIN("google.com.mx", "mp+7jW8fCPkmMBXnWThyDmWDjKuEKCMUhb+l/FK8hlI=") +ORIGIN("giantrealm.com", "2zRwu8vXxNs6wqrUsQLQq6PySn8xIOh+WrTybZgzbyE=") +ORIGIN("comscore.com", "QzNSOPPrx5QDdPR/TOXRxk4o/0xSl6ITJRVbLOTzP5w=") +ORIGIN("reson8.com", "4MsqsZZHxEiREFxa6yHNe5p/4QUXPmTkDay5zBIdH+Q=") +ORIGIN("teracent.com", "VKZrSciAO5SQJqPMc4w/IFbt6V0c8dXdIS8PTVdY4rg=") +ORIGIN("webhosting.yahoo.com", "e2KqMowGTAU1FrEdhsTsYVCAN0jTBs8TeAx3OQ9LAwo=") +ORIGIN("vtrenz.net", "/m1WTKhyv1O0spA5d5leSUejOVmvUUtQf8lJLtm8Enc=") +ORIGIN("sitescout.com", "RmtZAHK1IxnT1r7GF/FVJCYNIgvKd6zfSMZAeGlQZnc=") +ORIGIN("ismatlab.com", "T2nfD3TX691ohE35O2qgIcWHUKyS4SEvpg63T2F0XJ4=") +ORIGIN("caraytech.com.ar", "HZHSKqnq3PoINXnMhChHecRHLYCe5oAo1n7WBFUjp8I=") +ORIGIN("moceanmobile.com", "A5woKPh+7ntb9nnO9Y4vxVmn/bAgzxFGjpudN/xXFHU=") +ORIGIN("atinternet.com", "PsZouLzxqu1S0uyP/7CeckPZPPOs3WCrzo7+0j9IfSA=") +ORIGIN("healthvault.com", "BxwKg9uXYVFk1umhzMh2D35cYLD4OIhig4T67hU6zKo=") +ORIGIN("sensis.com.au", "pIUJU87sLv5xmrPhP6q8VeMhdPG51kbF6kctO246UQk=") +ORIGIN("script.google.com", "xWsfr1aQ8TluuIuPrxRy4UV2o5eqlSPZ+NfKWeHcgkc=") +ORIGIN("ze-fir.com", "YML1TMqiIlIQpTddycueE3SkQ08D3Wu9kTNWjC8iXUM=") +ORIGIN("moolahmedia.com", "5VC2ZEN/joyP0n0n0BfKs+jc2GN5m2MvPw5ko26g7KU=") +ORIGIN("feedburner.google.com", "vDUDMGkwkwTEqC5WB3rzaup0AK4tqNqECcOP1k0OK5A=") +ORIGIN("adshuffle.com", "3Tr6TsxUbX8dHZ4WKWbX4CXl9k+vIlrYomVTYjYxfmI=") +ORIGIN("gigcount.com", "cJ7qbI5IP0yLvn4ge+0imSCuEgGKRbqMbsTCjpYb9uc=") +ORIGIN("adverticum.com", "jWuQC0k7bIOLxgM8mYycraGWYwg3HPnTrYJ8c+ptDAQ=") +ORIGIN("madisonlogic.com", "Ri7LE+1T+iS26LbqmYrwpOg9A1j+XZHF7PsvvVhSPTQ=") +ORIGIN("mobvision.com", "3saxMfd9HefZFbc5V91gMfrcrJTFhPIdLKnWj3ymeas=") +ORIGIN("liverail.com", "//Qy2j7Jwzc/5Pz3R3QVAaepFCyFSqo8mXqBTF8rPj4=") +ORIGIN("komli.com", "R2IrNUXWiZKyKtk5nzLAEO1ilbfRH7+UtOwoc6J+WMo=") +ORIGIN("google.co.il", "Ftw0Pb6fsV4pTcMwM8UIw50EXhDw+G767tviD9Qd7Sc=") +ORIGIN("google.com.ec", "rCfhO8ux4aFP3Ih6Ib+2QgxYBjPYu6QXHyepBkCfKQQ=") +ORIGIN("socialtwist.com", "23djzeMdy0kNOwCE0vwEl86TK+N32kmBMSFbzuC1ddo=") +ORIGIN("google.com.eg", "TAv5+M9tgeuQ9RkL2gfRtiAH0TAm+Oe19j9eh82uObo=") +ORIGIN("etineria.com", "LQL+nEqkrSTiKg1wPmKSSOQcZys9+3TdfX4SYu3EyJ8=") +ORIGIN("adengage.com", "50ECv2gmZ3tzApGp0WNfR3/Nc3SM5seemUaghJ4q5VA=") +ORIGIN("google.com.et", "4s+N3uXFwoEoiaYAX4XxKQcFlex3JVQ+ZJ1jH1R9fZQ=") +ORIGIN("adtech.com", "C1SqueVBZcjpwemYFwQSilNBxICk7GtzxqG93CXXk0k=") +ORIGIN("rlcdn.com", "Bm/FeLM535Kb9STQLEnI5bHkzFe8Ag+3kgVI8CUSdc4=") +ORIGIN("poool.fr", "9IrmY943dDkULFKwNxYpxdAI7H8IhTf6wSTwcSzT4hY=") +ORIGIN("yandex.ru/portal/set/any", "AaKqS0qYo00XhtTSqlzDX9GE6m14J9LRzczXvs+NAww=") +ORIGIN("recaptcha.net", "11gzf+26hSNqW+LV+Izbsnbpvxfyaer53CppNLpGUmI=") +ORIGIN("game-advertising-online.com", "9Kzsd6CSJatgH0uJXrQ3xkIdLBJ2g/TaxhIVMwYbkKA=") +ORIGIN("levexis.com", "Lsdv+LIa+QImFWTFM5KDT93UquTwL9KFLLisRK7Y1YQ=") +ORIGIN("address.yahoo.com", "xHTMwNke92FedHAVmeZR6YUFVi+fnVKWdSLD59g+kqA=") +ORIGIN("groups.yahoo.com", "I7LTRGZfOq2f9V6hjm/XFnx4EuuhebwJy3N+lDBeXgk=") +ORIGIN("ucoz.ae", "XRqCnvaWLkVMGowra7rvJhviNh1pFkwUf5/fJGNuVVI=") +ORIGIN("brilig.com", "8XJqA+nt6g1W7ywZDMPJylHeVeV8abJ5FqZHzBbPvCM=") +ORIGIN("epsilon.com", "Eql9xUB9l904/ZAypi5c9OcmbK9W/JgH6fPVUBmPuwE=") +ORIGIN("displaymarketplace.com", "qtV1mvMVjiihPUGLMUmrFhUl0qQJnnEAu1tKqCxbXPI=") +ORIGIN("collarity.com", "SGS7eIbXhvBZRqPlO1xjTrCC1JaqNmpWYRuNDKKyIls=") +ORIGIN("ohanaqb.com", "PyNCWhYnrpfW5nZMJ4+o74snB+2RNTZ0vOzD80vTG/s=") +ORIGIN("magnetic.com", "AsYccxfK3rNSIUM+uFewJIXGgAHju8LevaWDuXb3vNg=") +ORIGIN("bizographics.com", "8xr49DGl6c9oF7GWIoQRPAyF0io0eHtb8Xzn/FeOSCo=") +ORIGIN("match.com", "gDmwCjAn+8lCyYVVzgWgfDNs4AI7je+dNLaZgHEcqnU=") +ORIGIN("advertising.com", "Ia9UpUKFDWP3A2vcIBlUsQjg8Ae1V19DYitUzo2b8To=") +ORIGIN("newtention.de", "aJQ8IWTWXVJOLAB0XZm+IlN4bZicJ2yRE+EKG+30kSM=") +ORIGIN("adthink.com", "/fG65RvzRmLKDjELVR0NRqhbhLl4feZLk5936lNvcBY=") +ORIGIN("vidible.tv", "x5/2kt46fhJHrovZhZGUNzc00UCAVhloziwkCG+Bihw=") +ORIGIN("msads.net", "NUljKE7BPQdY4dNy6eJqzpu7usRwwHw7M8UO2kIeB8I=") +ORIGIN("adroll.com", "cEuM2qqp4xTrKLo06MfuifUAbOSgd3hBLgXp4ItsANU=") +ORIGIN("talkgadget.google.com", "3VYNNfzenpCbW5n4iTs8mkS8Ke75y1IQnWlfo9oqyC0=") +ORIGIN("visualdna-stats.com", "wepRyC9srnVxtDYv1rsS3xTVCJi907qEXKTvrrbhogg=") +ORIGIN("newrelic.com", "ShwHUYFNKihKSa5FtzNPEgPaTDism8EL7JT+O95AcqU=") +ORIGIN("thismoment.com", "7mAAG/1hHg05J5MXN0atecBTBPx8fuNVxjEFbcLoFno=") +ORIGIN("acuityads.com", "Hj5v8n3qLepEg+e+GS07ab7DONpP7lnbkxFWZPA9Y28=") +ORIGIN("getiton.com", "79Np0/teEMvVrlP/w1PfHKci/e2ZVnurC+214v20EkY=") +ORIGIN("adify.com", "WL5kzFRmmcWr944DgtVGHJA2Gviyl9MQh04tQYY3+zU=") +ORIGIN("google.cm", "sxuQYg9orI/ijnwYUu9HXzYWab+n2VeTlZtZD44tfOk=") +ORIGIN("twyn.com", "ilOVshUHCcfeCvwpmZHq0jXG7VQKo/FOdSBwzPum3lE=") +ORIGIN("itsoneiota.com", "z8n7rZFaPOMPa54SOcSnE4XEaUs5/Pps5POZHYo+/C0=") +ORIGIN("games.com", "5PI4PppyuED2TB8nRYOuctrh+Z8j9vGae+U6YYg58Dw=") +ORIGIN("admarketplace.com", "DO/Ok/qhxwymKC0vTTyPlZxxPApCAaL7y0EsCjn791Q=") +ORIGIN("videoegg.com", "Etz/ESoKy8lqFzjItOKrbQ1aCZXH2Qr5h+cK0EHHero=") +ORIGIN("google.com.kh", "d6fuSU6sKTMSwfenlYW4sSY/zoA3AYvu+xo08datVO0=") +ORIGIN("loopfuse.net", "txF3ukzddgM9tjA+HaMB+AOxcobw1MANfqe+Mj3Zdws=") +ORIGIN("google.cn", "IoLfwbF8wQzSIDD2Zvvqpm5L3rAtpMarmlSbljDPTDA=") +ORIGIN("adconion.com", "RYdqLhqxCSkWIFS5wx9TGDxSRlCnB1rwQIr5bCSU71M=") +ORIGIN("onaudience.com", "6JoaqA3+7A7JvhiZ9JDxIc0xe7wywGEy9xrGFITIs0I=") +ORIGIN("bluestreak.com", "QDQ1gUgBjj4CtRG/Ign5byffdtoDbvPq5uxNgaz6jN0=") +ORIGIN("tattomedia.com", "yOLmNRXdqyBJKjkxgOKStPdgpqV02oGqocY63mtGvpU=") +ORIGIN("fizzbuzzmedia.com", "k+z0EIrEi3FGFSmALOAKm7eK67qYu2YclyySRqWZ3Ag=") +ORIGIN("hitsprocessor.com", "icxSh5FSj9QKqvu0WuIUg8gZicjSiQxr03Whgh6JT/I=") +ORIGIN("google.com.kw", "A4bhr3ZyINQ96DBODYXICTG+ogW83xHBckN/NF9bKXw=") +ORIGIN("pages05.net", "gV9gb5wuLJ+JheRYBY1Uz6CZhadM0cUoC+G4QAU72zw=") +ORIGIN("atemda.com", "mwX7kDFYGZbyLM+zSCUfhdyyZDuunc+yi09hFAfcVoI=") +ORIGIN("statistics.ro", "Ii1SErCCvrTERUdCb275X1gbvBtZ0Nh0qddzeGFV3rk=") +ORIGIN("mecglobal.com", "BiREpY6huJtVtopoHJcUKexZtp4WDlFAytnVM1c/LRA=") +ORIGIN("statisfy.net", "c1KOXVvvVmmwRTfJ7hPRsmaV4OdTGBGWTXU1PZ/XLRA=") +ORIGIN("dgit.com", "+XddheJHUzZWgKL4oiHO5DLhg3400GL3HrYilzUUtT0=") +ORIGIN("vertster.com", "n2Ow6goFxmaUId50MyIc/XSC2HQr0yc5ak9NPwLH/Nw=") +ORIGIN("adnetik.com", "8ACXMYJn3b9Vuaj4iFnqsQYSZz7x2H/5c1k1Fv539ZU=") +ORIGIN("centraliprom.com", "AQ+6dWH27tNgjv7SLbAjLBkue5b/Ec5EFuRSSY3W1rc=") +ORIGIN("payhit.com", "nO+kiPhoiBGOmbQy+U5pn94s1ymMaWVcvikO3u45Q7U=") +ORIGIN("traveladnetwork.com", "Pgwj806yX6wAFDv/ffWBJiAzIZntvAPRsNYjoNTXMsk=") +ORIGIN("crowdscience.com", "nLVwgh9ntCblDoVeF6G9o0dgHF0eTc43PBDPbbVTEE8=") +ORIGIN("frogsex.com", "Ypc9kvpw9bGz481+mxUONafK2oYFwhBoLiLi1PkUFw4=") +ORIGIN("wibiya.com", "0D3UjZcxYxtkJvrMK8k+FQiHQKhTKeDR+0x4bbwFGcM=") +ORIGIN("lkqd.com", "i83wIU1cph74qQIXOYAxQosTHNOSSap6LcK6ES/Whws=") +ORIGIN("search.yahoo.com", "bJsgJLiLaShyCkb5RvEefZWctQIlGRBxwYDdTwlJnQ4=") +ORIGIN("gssprt.jp", "kTB+xbjBP/VNtE5m9PITLCrJdkCj0oyFEeo9GoszxP4=") +ORIGIN("cmadseu.com", "079cwiPM5k8zx0BYWD9pKqI3uHr+fcRxiAOnFK5fboo=") +ORIGIN("advertisespace.com", "XEakfck3Ikxj4cKk0vV1u5qgp2qX6IZdQFywC62BEC4=") +ORIGIN("advertserve.com", "t12SwusR2YeOfm70kaEdUbCFihrRHuNYDJHqDiu37C4=") +ORIGIN("sonobi.com", "4qJpNzDeoaFE0agTIebmZiwaGp7RwGSvRajfqD203jA=") +ORIGIN("emjcd.com", "usLPmYl5H+pctVqm2PDWERKH1AX1RG9G8Ika/oQzGWc=") +ORIGIN("fiksu.com", "DBYvXSEwIZyj/m5KrTXX+KkCqcKjQNpr2wm5gCGeTE4=") +ORIGIN("faithadnet.com", "uy4yJkVgMFanjlsOgntwLVDF4XNMGQTxzJXLYsNaznc=") +ORIGIN("staticflickr.com", "wVwCdqpEdkQbYr/NdKn/ItkhALOs9l5IxgrvAiwpM7I=") +ORIGIN("gemius.pl", "Zyt8wXbYXgNo8ghX3AUBSKKhandOIgncb2h+i+vdjao=") +ORIGIN("google.st", "eaeyk/iXQiGdyGJqRMV96RHIYfbUOIeKoQLVWTeVpLE=") +ORIGIN("tapad.com", "EONnrXrv9FmjgRMuxLh/TV+AusP9X2DNkpYsIkZ4/50=") +ORIGIN("everydayhealth.com", "Pkqz0gOFXntdamFdX3KmlQDbLAPMWR4r6W2hB8qKW3Y=") +ORIGIN("google.sk", "ktU1/HLl/iw6kdQWwx4tUmP1dCOxQErtO3AnEiutvYo=") +ORIGIN("google.sm", "x8s9pOWlN11/O9zTJ+EJJ/pW1L6UbzSmKzP5tplm5fE=") +ORIGIN("adgear.com", "YXmzjpfsJJjSQWHAA+QpBtSsx4AuuE2DdhRpVCnlp/k=") +ORIGIN("google.so", "MMgBIgrI+yiMgNv6/u4wKg7ogDZzSOWnvIEmkuf5LnU=") +ORIGIN("fwmrm.net", "LmEPTjrQTmUHPpxrbsM9pxSRH6B2sUrOxpAesA+KWOw=") +ORIGIN("imiclk.com", "qE8fH5cXSeVVWiVlryAU1OSf0rxfCyz3fsI39HkAkME=") +ORIGIN("sedotracker.com", "3TOBZcWLPifQXy43xBdxJ0E+DmJrkJw5qEDAtDnS7EU=") +ORIGIN("google.se", "joAlyfsGu04vp25qGR+T/s7kdEuy1oMplsRPTSM60VU=") +ORIGIN("amobee.com", "G6wEzDDpGznMhiBzonYs0aX/vBsGJeYdVc31qh1OGwY=") +ORIGIN("belboon.com", "FBG5008rOv4UaE/bzYMsEOvj290vAl87bnSBQ2zrnrY=") +ORIGIN("glanceguide.com", "8vMvKS7Rk/tEE/O7/WLhAaR4wybAA351dvWq9uEBiGw=") +ORIGIN("intermarkets.net", "ULcqxNpg6wkrGwvPgP1A6Nlns1vcCTzVeaUY5CRSgiE=") +ORIGIN("twimg.com", "5IdosM5ZVh5bwUGlIGHdRVJOdbZsrX1Z3ZLkMHYlvcU=") +ORIGIN("mypressplus.com", "B+qUWdIFy5xbHGBPVC3g4uiofcNlFLjZ1TJU91GV6JA=") +ORIGIN("krxd.net", "05CDzuimWISXdtXGGSrebKNYar8f0hUVB60MeBuKEL0=") +ORIGIN("cbsinteractive.com", "UXCyd2a5TrO8M/jp+IYmon8JXovziCOaamm7tx+bs/4=") +ORIGIN("trovus.co.uk", "lqvfEFpPfYrjk1Msy6afXPcUtIa5xql32VLnXTWwkP0=") +ORIGIN("reduxmedia.com", "IrT6IERLCT16dCTwVmQpk6SzN0GMwW51kngbp8GA+Ao=") +ORIGIN("adsty.com", "KvP+te9pIfRvIS17QIUBNNIC9C5uEgg7QeUVMY6DDVo=") +ORIGIN("pressflex.com", "doS1ZTe+oqthzFqW4+CWuFMs+8kGNbMgTGWsrzim8gQ=") +ORIGIN("gestionpub.com", "eK8hbSIvyiAzPeh0Lga5MeMTioiAGxgqGn9/gg4CTNI=") +ORIGIN("ad2onegroup.com", "R0ScrWEWI9rDk8NEYbq5sVm3rWIdHV32YKDBbVHwc2k=") +ORIGIN("atdmt.com", "tIkn5plScqduaYTo5dNjrBZE9M7vWvrEsBmOCgWmeic=") +ORIGIN("gmads.net", "01RDIP/5izjdDFQzUWRV9BhcM/cnC3n92h1xE/LCPAk=") +ORIGIN("px.dynamicyield.com", "mC3pO1Od8RmEa4kLnUJqmCwN1+KankSTYfn9kLw6Uv4=") +ORIGIN("po.st", "k3z8Rl/3KLaRntfpSN7uc059dxArC7/56NXtldavOkc=") +ORIGIN("liveperson.net", "g7avo7QJMUzI+Vo1xytOBccz8bodzYR14HYtInXvz5E=") +ORIGIN("adtrgt.com", "pKfVkY6n6YadT0jNE9reCEbqPLsMd0pUnxInXDUAfQM=") +ORIGIN("statsit.com", "Rg4KTGaZu3Z2p74dlum9X4ZtB4xLHH5yjR8wvotpT0k=") +ORIGIN("iovation.com", "cQK6eMjNjaxEQqpJwKlLlcUv9cQTL/rAXuEGa6+hxWo=") +ORIGIN("ninua.com", "RpwQseBCCcwpfO4Euw+WtTYnXyuSS5A7CSUSIcUQxyc=") +ORIGIN("sensisdata.com.au", "x9j/s7IOLPKmweVPyzztqsPWqpi44HEU1MHl5t5tLB0=") +ORIGIN("adzmath.com", "xdMRxmpzZJ6vhq4Yhwtk+8rdPm7vReLeoAYSW6NvibM=") +ORIGIN("attracto.com", "57eoOX2eQ/xC3vIBFgLJfg3P4p11/syP1LlMlKYwA3I=") +ORIGIN("aidata.me", "ikrvtTrpo7tEiwYjALQAdOhhEuuI4trIq2jLIjMku6k=") +ORIGIN("appflood.com", "gaqnKNf3MCUQ0jdsL7re540zf/D3pGhRektjQUkOSck=") +ORIGIN("harrenmedianetwork.com", "F2x9yWZmSN/gxNzW7U7p2qhu5LjkXA4TGMdpHLEToi0=") +ORIGIN("kruxdigital.com", "yodKDZ1zKf9OC1ddmHWvJgSmIW72RLvro8SiUzjTqxQ=") +ORIGIN("net-results.com", "7HSPdnO/QNPponaPfYol74Cqstmtz84gt6VFfA9gA8Q=") +ORIGIN("spectate.com", "oqxcgX9wisE2tTomwEsX19rOMaXUGbeU2j4L7lMCVJM=") +ORIGIN("fathomseo.com", "EkTxu1LSTeCsPJ4V1oGadWYEHJkpiPVWPc2t1qdttGY=") +ORIGIN("pixanalytics.com", "zgA2tvxmmKiOqXCYGRvR5m7hJqYgrMAxAV5JJNTTERI=") +ORIGIN("extensions.ru", "SqlTNfD6+9nM84d4W26bQvpf2IMjvYL+y08zsgaHJt0=") +ORIGIN("adbull.com", "NVU5e0JzQvrKSB23q2rySnXcf3HraW3lFJyO06T0HR8=") +ORIGIN("kitd.com", "zcJMoSK3aOgNycC0StWVwFJQEHNGlR0rRaNdrPYiB7Q=") +ORIGIN("google.co.bw", "IRpOhDT2uSnA0nV1qg64Tj3IfV7NFS8Aq+IbXSj7AfM=") +ORIGIN("consumable.com", "UDNa9ARyxyujS2BARyJIeZ9677FfODCTOUNbzcHSxgc=") +ORIGIN("thinglink.com", "fi5KHehsxMpiwj6PTO3uij/vu5IrlHVLEg4oZwJf0/U=") +ORIGIN("google.am", "f2MPQCTpZ+bcEELx+H0kLMKjRljmDhHZj7LYDj4uZuo=") +ORIGIN("google.al", "b7O6vD9FiAZDP7gZuruGbCynLBvI7bhNXytQbwh2YEo=") +ORIGIN("ads.yahoo.com", "XJ8D/vXoe2S/sPBKJWZS2WNFPZQaN6CPZeMi+bvL6Ig=") +ORIGIN("rfihub.com", "W+YOvtn/0w7ooGYwyq1ISVSSScb+E/miyZH5+3++mwY=") +ORIGIN("google.ae", "T2H1sBM1XTnbIjxw+C782Fim3+zHolUS1YykIn2UCQg=") +ORIGIN("google.ad", "iPjSMgKdog4qgTHhG+JobDQ7l4w5ltJdGN57Gkj9DUg=") +ORIGIN("nextag.com", "IDjn+yqsnFYOu95zXnvLggfTnLOP/XOA9h7MlBd3ZVA=") +ORIGIN("yandex.com", "szCORmCiVckiW4GphVM1uUKacSjyajeOSNz2akfxSiA=") +ORIGIN("listrakbi.com", "6Pdg+DeZxeiiEDpwY9IHHu3YtJcE6T9Hy9g+kdsPdyA=") +ORIGIN("clearsearchmedia.com", "jbZ3Djf23B0H6YcqwK3Za2gwbPhLNkDG5+6D5PT1qeU=") +ORIGIN("pricegrabber.com", "Xx5ioATAulUPzjg7zeF561yBLL9pt89vermDv2AHDkY=") +ORIGIN("google.at", "SavxAochojFlJYJD3WmHR6VX4OQiOZdhXMPR8ft1Fes=") +ORIGIN("google.as", "P8i+KyaMg+o7M5wMBKlpyVnAACVw3eYw6/h7NKu6LJs=") +ORIGIN("dsnrmg.com", "U9BMaOdtEj4GN/8Pei7O8uAgAZzhzWk1eLQbdCgmLoI=") +ORIGIN("unanimis.co.uk", "R+lgQnlS4jpBIJn8OHl7HwDYCDZdGglrwzMqCmK+fjc=") +ORIGIN("web-visor.com", "1NR3rkUTXWA/d4yZvt9s9OHhVP3r4fMJVDKh+nlfGhs=") +ORIGIN("ic-live.com", "FDa2VtMLG67ulZIRei59Ef8z518iR2ogb4Syx/rbDcg=") +ORIGIN("lduhtrp.net", "V6wSW1XuoXDCRhvdgy82/UIjvhXDyaja7u6+FR4XbBo=") +ORIGIN("adtelligence.de", "QaXAgbrM5GmyGgS/hO1BHXqTzfO8jdxCI81U7ONbVkE=") +ORIGIN("freewheel.tv", "JIdnfkKeiKC+WCdWPvWc/5V9s0d0Y2BMlW/iBaojQ04=") +ORIGIN("merkleinc.com", "4n0T05ixutV8TMfO0kmDaIyJmh6V41vi48MBUQYBXAM=") +ORIGIN("intentmedia.net", "a4kNcHudTEZ+f528ZlGOUnuqBR8P9jGawr5uC8eM50I=") +ORIGIN("web.com", "0h2kS3ffABPcpTGzSgZFxrHSvis8YT27McTjVmx6Fr8=") +ORIGIN("chitika.net", "Bo3GEY7GJqWxnJ2R1vX15XN+KW+h8UEZCo10BKL16H4=") +ORIGIN("nexac.com", "p+qsRXoGxfevmLQwPfFOp8t4FJ31XNHsOa3lP0oHxSE=") +ORIGIN("ucoz.du", "lQSeHjvK17SkFPOlmGhXZyvjDzKWzkgO9Ekuzy0km5M=") +ORIGIN("pulse360.com", "8lP1JMw2ny1LMFkMBYN0c0K68ZFT1i7E8tlcNAnR7q8=") +ORIGIN("ioam.de", "5dTCczF3tZSnB0y5TcD2uYBb5W/WMNiUm2tr3G3SCnw=") +ORIGIN("marktest.pt", "YXZByR2+aktfIdzE9aplvREo/Pahr/QS9yJYsLXgGac=") +ORIGIN("clickdimensions.com", "4QxcbtBZYg3J18lKpqQoOL8+ruOlnVARkIUkdSq2ie8=") +ORIGIN("meteorsolutions.com", "54MSRRekMSU0zB+TqgBFQukPU95pcO9S3HDG9jC/Toc=") +ORIGIN("dep-x.com", "MFO1THhvciGmsBLfQf41cPd2CjA+OZBxs5T+RCI8B7E=") +ORIGIN("google.com.tw", "h5Vuqig4CJDn2tj8hKSpyPMmCDk1z10rFmjK7QkLXl0=") +ORIGIN("google.com.tr", "uu3z+4v0XAc+XTwk2AR17BbedZDaOvT9u7bmOW5h7U8=") +ORIGIN("clixmetrix.com", "Pbv+gdmerySr8ezzm33rWoXWFnH8jCzr4TkUDv3dBBA=") +ORIGIN("avantlink.com", "YKYnRmLccGp1XwC7edwOLNB7pVeTV2b0E0lziJyWf24=") +ORIGIN("maxymiser.com", "zaQcAycl1GNUSAkm/E4KZMhbSTmcENml7ZBh2PEFzz8=") +ORIGIN("nurago.com", "g5Y5JrH9SyvixlHvGodfFsdrdTWV3pjjAEO4Idc5Z0c=") +ORIGIN("matomymedia.com", "Uc13vKxootBQuG1CZexP49wJtyJQT79txMbTvNbki/U=") +ORIGIN("google.com.tj", "pt56tTq+U2MBHZeLQNa1XSb5JJB2bAoYw+wXMWIleKU=") +ORIGIN("earnify.com", "qT8flbVyHTCafoIbQ9L4tPjn4ivLQjeNls4ZPKpGqvw=") +ORIGIN("mediavoice.com", "gpihV72NIKgT10xDQgrsKAg6wQOIiMDYN7m5a0JSuXg=") +ORIGIN("bannerconnect.net", "9+kGb/Uq17wTfDkpql/011TxzTEX3Qzqy8WaUQVa2H4=") +ORIGIN("cmcore.com", "KNbbZtySXyuxLcZXfaV8JkXvI5aUVW+aJP28DO9G0FI=") +ORIGIN("google.kg", "1alFigTnbme6rrIoOYzcNVV1aEhfOgfeGfoYA6Ho1vM=") +ORIGIN("virgul.com", "tZeDvwYyyubXNsuxqfIpoxNRaQSWnXc8Ni1AHnc54mw=") +ORIGIN("prosperent.com", "cIlXR8IrJtCy0KMWHC5vhio0wDVzN/TVXld4EkzAnZI=") +ORIGIN("urtbk.com", "NNe3N70/GL6pysWXa+DSJb7U3c+10nrmhnkRWZBDzvA=") +ORIGIN("forbes.com", "EDcy8uGdIPI1pYvReV9cAQbtT1ePunPfa/oDOod/LuY=") +ORIGIN("google.com.hk", "GPZzfCAPetVklZNAyWxpgOIrbse8YUmk0zYXK9kBE/g=") +ORIGIN("chitika.com", "KEyR697+9dET8kCWo77/c7TSKYj+8gYa2gXtpFBhur0=") +ORIGIN("shorte.st", "qz/FOuVvz3kHWuHh0Ekmx+oZlSUN2DHlamYcz155OHY=") +ORIGIN("postini.com", "8KmSacDChYECwu1cbh8vh0dyiUYbINrHZdMn6PLjZko=") +ORIGIN("sweeterge.info", "7CFOh2RP5BT6THtUOzizhXxQIIEVsOMENhJezfltxFI=") +ORIGIN("hitbox.com", "zdewwLzy+7GpKIrGkLoAYtbfmIoxmF43adhq2kxxNlE=") +ORIGIN("accordantmedia.com", "zshebYbA05f29uHQCsZj0PKpTGxhhgdiHO5Gp6ITVao=") +ORIGIN("dsnextgen.com", "/Rv6jowRGRgywAb899zS+CClr/ACKjsXAloSJNHYyC0=") +ORIGIN("coremotives.com", "oZXM27I044/s9MZexIFdKutzYEP1rR58drf65JVqSeg=") +ORIGIN("epicmarketplace.com", "T/A3arcD+2zMxWLR//RvB25jnyUlagm+Wo6bnb9NvYI=") +ORIGIN("dada.pro", "wcEln8vAdV8wHW3utT5P7kXYYBfFrFsyvR9ymtq+Utw=") +ORIGIN("reztrack.com", "DHERmuORF1j6WtZ5ZRE4yyBfRI5KnNeofcELAlyKaC8=") +ORIGIN("yldmgrimg.net", "YnkdbBDibMP17p7+MuQr4iVyVtpAuMUzlfmNc1zg6jI=") +ORIGIN("ampxchange.com", "br65l0gdoCWuJfRqk7DtdNGNKiZx6Txgf37i3CNPMZs=") +ORIGIN("korrelate.com", "TuwtFAby2dX+QFknAG2Tgp46poptD+QF6Ru1p2/s9Ew=") +ORIGIN("tuaw.com", "fHZ/tz7/reLDhj9+OvYQO14aLS+RgWAxfpSo3F3pisI=") +ORIGIN("hands.com.br", "T8TqJ6fcczUWeJifc3+wQyJlpZbmEiyuvEX8KS3e9pM=") +ORIGIN("googleapis.com", "LI3uwhA/1EY58D7mkiAc67Dd8FIwY3QTsI/gpkA9LAE=") +ORIGIN("visitstreamer.com", "BWGUJSViGhx4S2d3APvkwdit0XPc26iHsBtdXh2kuWI=") +ORIGIN("dtmpub.com", "b7/qWZL0LL4zm2AEhqmHyzv/QxbG3SkSuBAa2mt+BHo=") +ORIGIN("fxj.com.au", "EN0HG/6EXl2HlNJLhSkp0gfsFK4TE9tiyOL1pKHQkp4=") +ORIGIN("wallet.google.com", "jJTEKHaR54lnane4AlbP/8PY87833GM/W2HRAYzKFyA=") +ORIGIN("affilinet-inside.de", "HkHcCa9rQKrjVyfMFjNe5zaZRABkawIaxXGFQouNC9I=") +ORIGIN("google.rs", "eKqzCr3ckBugkmstYVOBOD+xqZD1ONKmEnQZ434vATg=") +ORIGIN("dataxu.com", "0P9y+oYQbUE84BfYap6Y03wM++K8Iz8yfhLN4Oq5FC0=") +ORIGIN("brand.net", "NFrp/OkM6pJZTy6vJ6RDk7GjYpuKyrwwYXibzl/fSi4=") +ORIGIN("google.ru", "LwUlbCSh+qlylIA6MqE6XaL0tWtjeZNIkqujsss+sEY=") +ORIGIN("google.ro", "45B83S1zgSNgohGuV0MmRzzihtHdktQg5aZWV2Ezu5U=") +ORIGIN("fastclick.net", "O1ru8zidahl21NNvayIdpa7uMVIzEp49ueYOyPxgEeU=") +ORIGIN("adspeed.com", "Vd4odYMzLLP5UagKdxnHmFdZpTvI/Xsb+Tpg3CQbTjk=") +ORIGIN("snap.com", "lk40y9Vjo5TdqwA+tlJFYINeTXVeJygFEDA81zfhb5U=") +ORIGIN("indieclick.com", "D8BRNJknyHMQUL298KffmF+9uFbCAIAjrzUSFS+wxvA=") +ORIGIN("appsflyer.com", "Qns0O5n3d6fX6JaxnV0qH1VUXezcMYpPNFFQcOD+F70=") +ORIGIN("hi-media.com", "UaSbBcft7W9EPr5IdFsaO8NPHwmBQnXLr0R8P7HQFP8=") +ORIGIN("messages.yahoo.com", "1DRHkQvUvJqiJO8Cw61lQ105gJZsusfFg078tbTqB84=") +ORIGIN("admicro.vn", "7FnpGp8F0Ov/5mPsMFfXVcFaI3QjyJpn/tLDiVkejjY=") +ORIGIN("greystripe.com", "4VlgS2JDqRupF1LQ3g8xofDX5H0whoI/GSgip9WVd+4=") +ORIGIN("bannertgt.com", "TbfC4yAec+L/IX+VtDtAXnlYbhyEVSVwxrD0MQh7ApU=") +ORIGIN("webmessenger.yahoo.com", "ynBf7ZI6tm1ti/4PZTWaS4cpgb5bzBNk4pqsaTda8yM=") +ORIGIN("bluecava.com", "6l4/gQ6p+pW0qHkIYPuzX+cUxCHcbcQSmNDi75uFzoo=") +ORIGIN("snoobi.com", "1UxdmXnMlzbqQUuJTNcR1bFYAp9OsoQVDjPbuVsJML8=") +ORIGIN("teadma.com", "YzmrBlAW8VAcu8oqMuAZIgXNKWspMss4l5Vc+0FV/FE=") +ORIGIN("burstnet.com", "hbK4Ocb8t1oQ/mwBx0uX5QrxScy+NmgMAtjLu1vQBWI=") +ORIGIN("etarget.eu", "L5SENaN1J5oApWxgMYTcf8BtvmJimoXyxJKEQBT1qzA=") +ORIGIN("tns-cs.net", "/O18XSdu8MjP6xHboYnf/HeodYCbkbiWmIfyYIsrEVU=") +ORIGIN("adrdgt.com", "EHm6wNRlGcVSHn9ChWB5jLhM39rBIjajEj81mayOIlc=") +ORIGIN("pardot.com", "ic/jLBhs3O0FuiOdIB/A0hrJ9sl5tPHc7Cl+ZHabfuA=") +ORIGIN("clicktale.net", "TJTRDSlQjHtxKSYdwJJuMrT0eqSror7oK1W5LTeneMI=") +ORIGIN("omnitagjs.com", "Kvk1IILHJDRw9Tl11A8MgwnQQOjv9jQNbTgfHvSRK0s=") +ORIGIN("google.bf", "Lkp9HjaqJWePAQ0Nd8Oveghy0teB1uZpy3BkJooSi+w=") +ORIGIN("pippio.com", "MwP0BCYfHCsWOMNftLJqF4ugmbdwKlFSUhZj2vJ5jqQ=") +ORIGIN("estat.com", "sd+EzT2FTPrRTKNUYjt5bXtD8atyUNMXrhrA8wOG80E=") +ORIGIN("digitaltarget.ru", "j+lUTfTq3I4WoyJamG4LhFxB54gvR504LcHRzDMqNfo=") +ORIGIN("directadvert.ru", "t4/nJnbgcbZSY2ojkHHkiP1pvKDft1HmwLnO/ZRyxMM=") +ORIGIN("singlefeed.com", "axpiDxixgcOJXBo/Qmee/j7VFHKpmU3OcHcOvitGmUo=") +ORIGIN("addthisedge.com", "IhrpoRBKTqL/3w6En7WrTFK5MWxajfF32mr/vQGTWCE=") +ORIGIN("fb.com", "H+Hpj9uHj4KHUYGuI3oAe+brlavVMzhC0SPEEOXXbBQ=") +ORIGIN("newtentionassets.net", "YVy5lqo+BbIrdFAIPy7HQ1VgpGpp3USKQXAgXfJxkhg=") +ORIGIN("enquisite.com", "cBTP8JbmrUFU8BhlRfY6e22HUmjsUV2kUv6e1O7LbSA=") +ORIGIN("yoc-performance.com", "pqjK4dVdLNWgGSsWnK67lWjQWK8ofIF0C5DrScfTEq4=") +ORIGIN("netbina.com", "naVcNktvZ9vQyJR+ZNKcuAsYFwq8I/9I4OB3UEN2Z/M=") +ORIGIN("chartboost.com", "lbhR34CbrYsG3PBEfisXxc0+d2rCcRJWjuSplOvJnyU=") +ORIGIN("emediate.biz", "mz/rEqdeQIMiqiWSXZsOHV47eO9QAN50AS630VgoFIU=") +ORIGIN("primevisibility.com", "WKzxRjCI2LsAmfKR0ZuywlrMtu7BUWAYGrPXNvqNTEE=") +ORIGIN("breaktime.com.tw", "wfHZynwBe/+ulRo1Dk37f7MkmMl7rS1Mad000Br6dnE=") +ORIGIN("adworx.at", "i+OjoJQtBhILpakCHOxs7orchTHWq3N1Zrvbh0N9UNg=") +ORIGIN("httpool.com", "rypHdCgNFqXvjc8S8zPigIe6XVeMx+szVnGbqEtBZ8M=") +ORIGIN("adiant.com", "2RNFuWIF7lb0iv9d01UYQWfBBlvQc2XEGcc10luMFGA=") +ORIGIN("cadreon.com", "0x/VyVIgJMA+g1QOBputUcz561rzMkh/K5neC3nWhJA=") +ORIGIN("pixazza.com", "RFrds7rdEWyLV9yz9OrY2JKBWj0gS/4mT4SupSG9hzw=") +ORIGIN("res-x.com", "4rJ/vsktgwLvWXJfy2ptLrCaE9fENHBRef3gfltKYrI=") +ORIGIN("mediawhiz.com", "9Wf7dGvSH42bSycmfKpBkUG7v/jsHUjjMYDPSyPrV9Q=") +ORIGIN("sparklit.com", "5w/mm5Gu4msN6oNa5bVs65vOmeuE6sLxCQGbB2bRKow=") +ORIGIN("collserve.com", "nejp41+1NWOTxZsQ2ckav/PdsQX02/aoMv05dwwSkt4=") +ORIGIN("criteo.com", "y8dBpRDXkbOtt2X6wIS6KLuywoF9wF7l+xSNlMpn8vc=") +ORIGIN("martiniadnetwork.com", "VisBYvaxtDVb2e4WojUr13NVrGO8x4rFvs61nnyoMMQ=") +ORIGIN("weborama.com", "D2ckaZUTT9nq8cK1I8kbNLeHX4tLUbdSTV3oLAVvEdI=") +ORIGIN("adtegrity.com", "1luEwM6IzPStIOTAWy2eM1tLBxRITK4xoJwUPEkvUmM=") +ORIGIN("ubertags.com", "Ul7rfGzAwYPa4b/zuxu3vMcVFc1UYeTzuo3qEc/1T74=") +ORIGIN("rmbn.ru", "zgwLE4uaOrlX9tqFebVgjJEfOTcRk/2u42sV0LbYCiI=") +ORIGIN("login.yahoo.com", "AAcwvd3YNublF6sjEQa4uk+8mtPR1dZSZ8Ft7ajxgC8=") +ORIGIN("undertone.com", "aghoy/szgP2hMSrlyA4kKFaqPolvHF8mCcuuJu3LFDc=") +ORIGIN("monetate.com", "i4E+zGa0fzEOHgrAYSZCKD4QW5YeHdiabJ/dvso5soQ=") +ORIGIN("adsco.re", "FQFNYIyaA29JnsBZL3JSE3se2PUh7fE/58BTqV8cAfs=") +ORIGIN("unicast.com", "oeQrDhaRxITpkDznxfZwvzK2oLmEIvy/JMUvbC5svSw=") +ORIGIN("pipes.yahoo.com", "QOas0bxWGJzLXdHfYmklVUm4TK6BHaQMOjWnl+rFylo=") +ORIGIN("4u.pl", "B8ejtJS3200XjNlYelpzoYPA4y+ql1fbwBk2WF4A6bQ=") +ORIGIN("visistat.com", "Iwlk6HOxB2ja73OL3jnAzEB5IPYsGs4oiZdniDBWFPA=") +ORIGIN("list.ru", "aGHkoyvwSHJ3qz3U0qefyc0qyxR3yeUUr/IQZzoMvo0=") +ORIGIN("apis.google.com", "E7joNep+naSYgbzXDiaW7EFtSIoNn27aEAz838SYkYE=") +ORIGIN("admost.com", "AgxtUuvryxPpngJE4/xxRCOktrh3ohna2mYAT6M/m4I=") +ORIGIN("infernotions.com", "frEsyLPOJdRIejfsfIS7skvRiWa+vi9In+dFdbbWI1s=") +ORIGIN("switchconcepts.co.uk", "JOrpwxdAUxT0eiGHU5sZQMYrN7L8kKEB9mYhluO1eh8=") +ORIGIN("adprs.net", "5h7cuLgWsI7bzDQ8YQq1W0a+fupKzwfWUoYFPkF6hc4=") +ORIGIN("mobilestorm.com", "c74p3b2Exa+JpCwKah684CMP0biR1YmTVsOeltVfqKM=") +ORIGIN("zanox-affiliate.de", "nWyLPjsamiA9oJWglDZW95thbtp0KAUN1ccRqjN6r9o=") +ORIGIN("yumenetworks.com", "2qdNbBfVg/FIZ40B+wu5uuamr0HdHC5pZnbrkK34hy0=") +ORIGIN("salesforceliveagent.com", "5M3TdZ4YoK5WETCtuHywf7pTZrkSvYgLQdjt/N9rzI0=") +ORIGIN("adserverplus.com", "lQTR3oqHWPPw0wm0NF9go69zYbRPCpXtO2l1C0AVHfM=") +ORIGIN("agreensdistra.info", "6jIXgOF8I/+UHMCEO9ZGtwUlCBalSFIpdAiVp1FoXco=") +ORIGIN("merchantadvantage.com", "Eh9izYGkqpVILTb88CLX2jJNX0rZrbvnrzbM0UgunEk=") +ORIGIN("financialcontent.com", "c2dtuO5j24CIuKnvcTi9KPBa+K2vlW6gyl62IJpGNK0=") +ORIGIN("code.google.com", "7KGLEgiPrCIWgpFJEUAzHAsYskLyLyBTRSLpWQPLf5o=") +ORIGIN("adjuggler.net", "6fwGaL7u8i4KeGO2sqT4xoee6BJwWsrhYI3GBVhBkFM=") +ORIGIN("rambler.ru", "OQumm40Vu20ipYToWWzzi2DEkfLRHgSBmUK2kg6Zrvg=") +ORIGIN("mxptint.net", "w37YAI4a/P5OMOWWCdJQ/TQHs595HUUS8TAPDZ4eF08=") +ORIGIN("opolen.com.br", "o8GHAv8D69Orqt0CC1Z5a1ZSRD1CRYoENLVI1XLnjl8=") +ORIGIN("segment.io", "hUWQ4a+xVg/U4r8tPDSCEXvCP3m/eHQ6gVLdXp2w35A=") +ORIGIN("sapient.com", "J/dBlZXUV8vUWa1NECcKG+kME/pTFrAU2j1eOwiak2M=") +ORIGIN("freedom.com", "hk4MYBJMul023W/yfDM4d4DguyJBuigVQs2GxeuQbMM=") +ORIGIN("adnetinteractive.com", "mSscpBJYZ6EH34di5CN1jTyo2zmBskWNWSxAEh+KFNA=") +ORIGIN("sa-as.com", "zGpCteLGWJ4dE+9qT4yXM66Woh7fAkZyTbFiR9WjRIU=") +ORIGIN("google.com.uy", "Riz72KTW5FeAnAcuhVSwqq7ysug0HIrDzHz41jg92QY=") +ORIGIN("spongecell.com", "Vyi3Pkp6B/yx83khK9qgW6pbA1lg544VHKK9b/kBtG0=") +ORIGIN("google.com.ua", "0M1UUWOkI7/7ZDGsdSTbS3Eeqb3YsHfkUWxcSjZgH70=") +ORIGIN("jinkads.com", "gVdR/PY69jpX3pm1fvNZta57wWD8KZK4hIqfQnvs9p4=") +ORIGIN("navdmp.com", "9eB0RvB5qL+IK8Op4bMF8wR60G2/ooP+620AMeeTixw=") +ORIGIN("vcmedia.vn", "Y6cAyU+rmTXKwH7R2Zl+psO94Y0SClV41HBDFVeBFJ8=") +ORIGIN("friends2follow.com", "hrQNbevAY6ApYAOkeLDxEt+v7pYxCGvAKCvsdvueC4c=") +ORIGIN("thesearchagency.net", "NOPH/e+lqYmqYihwyGq9l58+DZ+clrBSPj86WNhwezU=") +ORIGIN("zendesk.com", "Nw861yuwxHKmVmLMIrZIiESc6/oZUHSIZOyLsAb7Af4=") +ORIGIN("google.co.in", "hdD+adBPmkdn188h4mBJespGBFbJQtijiw5pXruB0yI=") +ORIGIN("adbrain.com", "vE2Q/1IKuVBLkBb1xT9YL/S9Yx1Pl7fa2/473Yosyy0=") +ORIGIN("liftdna.com", "yguMgt8Z+XCwmWSVB+2VGzI/tjeMIxono0d0YkOY/Qo=") +ORIGIN("expo-max.com", "qDD6YbhqqlhW+v84wQXHx80T9BppvDe8wcE40HOfImo=") +ORIGIN("ppjol.net", "wLRH6n8HEjmiewQ8T4ZY+aMl2sL5NCTb0Xe917VuVuQ=") +ORIGIN("adparlor.com", "Acz7VYYYqOGtllj9wDOpfQiRKiyhPtedvGOoIjlvGjw=") +ORIGIN("scanalert.com", "gdkL4lsOTI/+RhcOqmzpW4rpU0DBH5r4VSQR+KxhqME=") +ORIGIN("vizu.com", "uZEoVo/K/ybrNmArvTomMu6DxlfY0C58WayxpN2LTa8=") +ORIGIN("datvantage.com", "u8z4bFzxh1Dckxg1n7z1xYdAOLU4amNV97bWIWN6Mj8=") +ORIGIN("hp.com", "P6RNZI4qik6ho88ACpeGiBXxmDQCQcECpzd3nq8dIWY=") +ORIGIN("clickyab.com", "0ahzdK/xuf7oFGPNqxwOvzrdq5gjXngKu9rq6cF7Uaw=") +ORIGIN("thummit.com", "6A+nrofDJfMEYE0srUneOxBzRj330m5sZShwwpXZcy8=") +ORIGIN("mediaiprom.com", "hSias/zwXnXznXOhv4bih3H+NgP7SUzBkfU2mbxRpZk=") +ORIGIN("zedo.com", "mcVr9L66mBqLkM+EmeYfVbsyDKZ8/Rcgz8mpnYt6eJk=") +ORIGIN("lotame.com", "mn693pD1UgKPjF7jVH9NSNzf7wXJtA+JJlYvHolc348=") +ORIGIN("cpvfeed.com", "aPwKH0W8dLN2CZKFSQiGYWbB34artt6JScwMFgPLO6Q=") +ORIGIN("disqus.com", "LvpoRjBBWnNZTuI0YEuLDrz0wGk6UQUidJWr4a2XCZs=") +ORIGIN("adultfriendfinder.com", "8E5MfrKJIYsfJnsR7gr78MeB7PEa2vg9A5Rt2XIU+EI=") +ORIGIN("brandscreen.com", "DJAfGmVim9Kkn83CyWS3NzxyT4HKs16U0Sc/HEAje6Y=") +ORIGIN("radiusmarketing.com", "UCkxHPzZFtvjyNuJDvQMFrQOdIxSuVPRlTsDQJ4OAVw=") +ORIGIN("inmobi.com", "fMeUk5Jf6kXlVyEGLL8FKaIveDsftlHnFWsDteNkJVg=") +ORIGIN("fout.jp", "/ewU91cdx2O/vLGLxio3NbC1dEd/veJ1Y6s9pi1F+wU=") +ORIGIN("docs.google.com", "yHZU/TNkHWXVxr80G+N96vv0pjiU8CEsZlfwN2CkRM0=") +ORIGIN("flashtalking.com", "3Euk9omCSD1WhWa9lxABIwGuCudAUoCSvZMTwGPE48k=") +ORIGIN("earth.google.com", "HssiCQpV/mQ39XJW9+AgZmGU7oZn1fRw3KxAaF3SHNw=") +ORIGIN("omtrdc.net", "ISEqrSWx1NIhqnSgg/PyzNCJCS02SIZmiiEZvqaljrg=") +ORIGIN("adjuggler.com", "CF0TozjbaP7mdmQOrZzCaAU1JaTtIGm/KfAFrhP41CQ=") +ORIGIN("audienceiq.com", "/CCvQJp3XixfYKB9crV+PppPmrvnmvH/5PB1PhWmj0w=") +ORIGIN("seevast.com", "Dw3UnaJdSrpGh3LGjWmU6YKnaMy2nxrsg2KPfja6bOo=") +ORIGIN("mypagerank.net", "u9ypLhZiQA1lmQ6LSV0PHvtoMkyqjUWp2KzCVyJ4b2s=") +ORIGIN("amazon.co.uk", "38+UAVUFazxjo+3kQ6jeoRLmykDTimzck9uwmrAHVQU=") +ORIGIN("alexa.com", "ie+T3T0L4P5I9JrXgIOVV98Ymo2MbQNJzzBEews4XXU=") +ORIGIN("oversee.net", "dPIRqYrwW6XLLdZ0MYqFbSBr2iiDVPNMiHbJiq0lIWk=") +ORIGIN("chartbeat.com", "j2NMBBHry3P4brbZWdwcEIMvsHI7uB/Y75vyh5jHD6g=") +ORIGIN("opinionbar.com", "wFY4EibU9YPhb+AGNS9ReZ2Dajm7ExX0it4T6A6ml+Q=") +ORIGIN("pntrac.com", "qDQ2aF2sIJuGq0iOFwxPVw4WNw8KdydCHtijSzNzKlI=") +ORIGIN("punchtab.com", "/uZUwhm+IqA5oLQ/BpRg2f9zsmTkU0abON1QOMJyGQY=") +ORIGIN("heias.com", "BQ0wXEMhvJypZX2q09v2oOHDsNIZD58QyZEgYht7UhQ=") +ORIGIN("trackset.com", "Yntnx6JmDFVlK2iYXf4IacsE2QxmdMJGQPfzAnf5WF4=") +ORIGIN("gwallet.com", "tuKRKO+d3lIAd0rObvqUwZFVJ06oM2cKE7f6Fw5W454=") +ORIGIN("zango.com", "Kl2a6IWkg6b/X+A2/3xX6m9a460PW0pC5XrBe+UzyiM=") +ORIGIN("i-behavior.com", "NEIexF9T2lCx67266812o2+N59mHnED8VOTfQjGZQtw=") +ORIGIN("shareasale.com", "Ft9u1frJDLzLqiYrUo//od0kL5Y9Ox8PFvI1AaFFTJU=") +ORIGIN("domdex.com", "ucaehEL2lqi7E+CMw6ZMgN4bkzi0Sop5AyFkxnHUB+E=") +ORIGIN("finance.yahoo.com", "SFZo32AGJSYHrjym0b2bo5p0pf7mIv1uvDD5vx2OmwQ=") +ORIGIN("adabra.com", "PFRqc2VINIAmlQA03hfeyFcdrZbfvz0pF3JrJAijBnc=") +ORIGIN("media-servers.net", "hm48WYurM06k8uF7+J0xln/+gyftOXxuUZ4qbRrL4Tg=") +ORIGIN("anadcoads.com", "Is+frX+UQ4nVFrgRV6+dkzMQZMgr1rBqZ42OV/vhBy4=") +ORIGIN("gmodules.com", "TLRYpSvs8Hr7A8t7zBr8yYNOkuO3zY8Zf9w9hKRdPWI=") +ORIGIN("thewheelof.com", "izGpWBCERF4PJpF9pUJf7nfjRbQNKFbEW/F6nGqJ+a8=") +ORIGIN("maxbounty.com", "WoNUZ7NVzPGd/Swtu+8srzOSEUZf4pLnWF9n0QXcv5g=") +ORIGIN("othersonline.com", "UPv3GkXoyRKPKCFoFmPbc2V462ecEz9YlS320PY2uCc=") +ORIGIN("swoop.com", "lleDvPk6+c0m9Py8qCDgnfReeHNY0GTW1TbH4A90+Ms=") +ORIGIN("stumble-upon.com", "v90yIijsw+5b04v3rf82E7BilxKO1t9+2UkNIlzw/c8=") +ORIGIN("wanmo.com", "Y0rPbGbZk+r3XRTML7Lt+mhYJ+VIL6aWaZ4tGWD6s0g=") +ORIGIN("vendemore.com", "gSWGOLXT96mOiwND4CmBxCwCcQYY42wLqKYb/p9uIxc=") +ORIGIN("aivalabs.com", "8iBD7+eqxr+7dgCQDhUjuNSZGBgatCP3X6jGt6CC7wA=") +ORIGIN("genesismediaus.com", "eHo9/enY49PzTjBGUC2Dq+qNWKd5OW+I1h7z79OWc2g=") +ORIGIN("adnext.fr", "djLaKej5iQkhwraBVp7G/svHT2gWGV/BBU4hl3R+DOs=") +ORIGIN("smtad.net", "QLG/MiiiVc/pFvmCkWiLS4IifUIhFsAqULluNWbb3lc=") +ORIGIN("fuelx.com", "ogS0fiRyQejFY37AahtlIHtTaQ88he7tQItxeeLdDJs=") +ORIGIN("ads-twitter.com", "yAiTblWPg7GDjndxNVeOaoeCq3/3Sbp5Y5PEQnVEsvY=") +ORIGIN("meebocdn.net", "B1ERy8s67kFEvcRjJ1OU4hUvZl2JPoRYp9O942ZFHT8=") +ORIGIN("addynamix.com", "EBQ93OPWXMOJgCjpx0TyICbLymcTodPLFCm774lEaPk=") +ORIGIN("lockerz.com", "U1nPf3fsWtncJ56UAJiIgsymARQ3HdCr9FIlP+F4bLI=") +ORIGIN("raasnet.com", "bRVst6UArdcuPt6QJWPkqhEK4AZGnHLoBADI+XCGB7k=") +ORIGIN("aquantive.com", "3DUOicBt+/Oyw/NOJCwITrFe5CcD0F7K6ihArfom+SM=") +ORIGIN("google.com.jm", "V027h/I6+W78/QEbScoMobFCQGzr9yKB8uW2j8pCw8E=") +ORIGIN("connextra.com", "PNhwHtLjRJWQeVS40s67aW4fb1/TXzAcRM0LvCH4pzk=") +ORIGIN("rmmonline.com", "30ZDT59BPMMsGiAdeKrHBQKTKwbXIJv/wd+pj2eqt6A=") +ORIGIN("revtrax.com", "NnKlSwlp+400jKPCwHq5onoA7YxvqQ5bYApqQGNvWY8=") +ORIGIN("hotjar.com", "6HqhIt6tbtcjP2GKEFn9tFFcGo0fKXIX/TZU+5mbjxQ=") +ORIGIN("rubiconproject.com", "AlN4GrZXB/iKh/6sduQ+N3awneU+8u5nnnuT8xqm2u0=") +ORIGIN("dmtracker.com", "rbppVK81tQuDT6A1rUsoMHUEBnPaks73T4VANkR/Uas=") +ORIGIN("smartclip.com", "XGPutSAvcfoaATq51uXiJjlHp0LyB9DT67ookbbozPc=") +ORIGIN("affiliatetracking.com", "UFXU+rRbQy9limFLbsfqYX/Gu10eBGH76/IIoh4C9aA=") +ORIGIN("postrelease.com", "zzXH/ypflH6ANBagnNQIIboqISoZ02coMnps9bcLJ1Y=") +ORIGIN("yp.com", "D+gPAn59DPDdjg6iH2MTdjgpggOvuCh9jthyR7Pu/gM=") +ORIGIN("biz.yahoo.com", "NCnD2u+oUONKd29pjUObOK4BW+R9KjFPVvJNc5HuUvk=") +ORIGIN("kissmyads.com", "qQCCIk1GtkWEMVrACn0X5zYRgantEvQOpv0Njf+1Oyc=") +ORIGIN("skimlinks.com", "uon1qoiH5tWSnP2+iYiQ/MhFCKpZPshOFs76uzXzqOg=") +ORIGIN("iegallery.com", "xYskBMn5CwXKhPaDNXXJpXDDCanRuUHhLKywwW+1BXs=") +ORIGIN("adform.net", "rqYZeo23HHblETDnYvi9csSLZU8TBd6FHaoPGaDXzLE=") +ORIGIN("adtech.de", "isqUS2myN5Dnb5H4iFOeBWTwiCFs3gdcjx4LGZOW/s4=") +ORIGIN("atrinsic.com", "Bp2I1WbK+O01amKvRgPBRIVusieXVMNLCGguZNA2xcI=") +ORIGIN("turn.com", "P7WRBC923LIAl2eVGdBZD2Mf/b9oX3k1aRY1Vo+mxhk=") +ORIGIN("hs-analytics.net", "rsyEvcbWEmO2llyoXRGxZli2qbFgypFlWpmcWuysG+Y=") +ORIGIN("exelate.com", "TOArPndULz3A3K0sprPTjU66iPR5J82ljiolro7GFmQ=") +ORIGIN("lkqd.net", "L+tGIJhE9TESmKsozQQB/4vLk0eAs+bZ+vGBVKOxwlw=") +ORIGIN("unica.com", "x/nUJGu5R1zWCh4nR+CecdJyWpOBGWEZffDdl064CJo=") +ORIGIN("xertivemedia.com", "kiwG20RdbjCQ0gQCjJaxln9hrp/i1lDilRN1/ckmggQ=") +ORIGIN("bluekai.com", "NWsUUr3jFAk5zeCTHDQT3zt0koAf0GHltt7M3mc/nTs=") +ORIGIN("mochila.com", "MCa4mgoalyzNSV7lqcXJi0XO737UA6NTDref1TpgrCU=") +ORIGIN("globaltakeoff.net", "CvVk4eMQdmGByBYPwshs4CRYMUqH33vAZeL7xJL1ZXo=") +ORIGIN("adhaven.com", "xnR7P3zHkIkfLEnZt1K8mToL/2hjFNLDDXUYYL36NSo=") +ORIGIN("aemedia.com", "AlPlfiwolUYHIm/LZKRM/C1QuudoFMWhCHa1tMNcBNU=") +ORIGIN("reedge.com", "xcOXDjSlAFX9aEJbY4H22L0huLilTDyKs+QviCNsn6s=") +ORIGIN("iclive.com", "gaM6xcuJvpu9s4a/AsW2d8LuK7bbzpo8xiATKwAzplo=") +ORIGIN("nuggad.net", "Ze7GmPxncx4T84VqIaH82TIre2IBCnwf+bK8H21RJnI=") +ORIGIN("shopping.google.com", "el46f2WxSySYQRcWiQ6no5PLhRR0tRvQEMhrcDoLkPU=") +ORIGIN("hotwords.es", "wAhODNd3j5QXen65KM+R7kSiFSiO+Y+z7WY59bYfLDQ=") +ORIGIN("cloudfront.net", "7uMtzzi8OPcJEoqOzpKq+yE3M11c+IG+8mILMUBkVsM=") +ORIGIN("monitus.net", "FL6ypQkaE5LoaFVoUF46MZIX9a0y1/sRZQuG4fi1a3A=") +ORIGIN("infolinks.com", "iWM+ItOKMoR7EIsoNeV95ZYLWuFBMEgPaNF+pt67Whc=") +ORIGIN("guoshipartners.com", "nWNcr0EjUnew46zyxB83IfbF+akxDUvz/dfQgMyPFoQ=") +ORIGIN("flurry.com", "LwR1dZWGcqLzwbBG4TsJQj8xlbUPlxWLJsPHHwtfhAw=") +ORIGIN("conduit-banners.com", "eksLAIj1W7/B34GSh5l4Jm2zW9hIGFc6KZOwhcGXQso=") +ORIGIN("kenshoo.com", "B8atPfzSq0T2xYikxvfFZDc7oUs7WysyEIShNUrjhx0=") +ORIGIN("choicestream.com", "MiCEdrisWBUAOxEbYfZuCZMMGpeCXgeYZQjKt/mfpJg=") +ORIGIN("admixer.co.kr", "tJ5OeTFiSfasHiIvJHxf/ivuOTt9oRPp5d8QUUkS9zs=") +ORIGIN("maxpointinteractive.com", "Ihu70DLsM8+QT85EKdLDwl3xgmKE8zMtkqTCnwNir5s=") +ORIGIN("nextaction.net", "8vxWg5V76+P7kYrEd1+CPtgzz5+9MZtAAS0BHNzSIQA=") +ORIGIN("emediate.com", "MFLjLKf+yJyrQQrGlVRGCi874LT9i2CJs8YaZz2Qxt8=") +ORIGIN("pjtra.com", "vwQyU3HhNiIB6uv0oXsmBF2Ex+Qxu1bIodqfO1EATBE=") +ORIGIN("jasperlabs.com", "8yjT8igTP4uDRRCZjmRpvqF3bGti71N9XN9dCx9Sjkw=") +ORIGIN("gsimedia.net", "MHOtniRinVniREppVc91+tHJ+rmd2VMtrYnZQHwbvlE=") +ORIGIN("adserver.com", "372lhGD3hlCBcXaIgtPcdjixJdrqQU5FS0rvbeaprmA=") +ORIGIN("yieldmanager.com", "/pcETRcGxDc7g9Qj46+PdYKKZYbWCOB5RwFK6FTSEoM=") +ORIGIN("hilltopads.net", "Fs2uh9HZGmHmy51DTwtRtOzRF3XdXuXnqdWMJLKJ2S8=") +ORIGIN("addthiscdn.com", "Q8zmlPd25mJDkE0ORkxRuT8ao0h/tstZ2aMcCF48OTw=") +ORIGIN("extremetracking.com", "GhVAq/u1bp7b0WoL1QFhYKTFoJCEpyj+SS5ljFf62+I=") +ORIGIN("kissmetrics.com", "motCCpzEa4NaD4y9WR58gzsUCYQvoxIMBUCNMpB3iQk=") +ORIGIN("ethicalads.net", "XurLv3MBC5CNDKxt1Dq8IMg6L+58mujorFd6rsNpfd8=") +ORIGIN("oewa.at", "f0dkIxhexhIZqfArinVz1xrLVDwlRw+OLocQHBZQecg=") +ORIGIN("beencounter.com", "UqXAURFe2r96R+CDgoBehnVSdK2KqG9TuSKDJwkC9fM=") +ORIGIN("limelight.com", "6UqJ5crDkVOnj3HtRJBuoNyNbKoKEUvSSoJfFonAHtw=") +ORIGIN("quintelligence.com", "jYxWE+RKyRaV+Hjv6ZSXglS5IlYUpIlsxBhTx8tajjw=") +ORIGIN("peer39.net", "IkbfBF6Tk/ag8jZxrLjwXV3iYydxi64+CNQkkXNZeuE=") +ORIGIN("gorillanation.com", "wHwryHTPn1SELd0TCsz/ZAielV3KDGVuFS/doSLoC6Q=") +ORIGIN("yandex.ru/clck/counter", "ScDl2FLpjUo9TyVTuN/BssG01oUN4LUivDwpc6+HyqA=") +ORIGIN("visualwebsiteoptimizer.com", "qaNeytsE/TIsadmGqyg/ys6dHjDXhJ+WnJHqkxZjKQ8=") +ORIGIN("adgibbon.com", "CAGVXh/TZYHO4P/96TVV6yEkoS+fnXni+WSWUljHi8A=") +ORIGIN("digbro.com", "Ty2uWauQ3KSITWsYDulAlS6E4jmKzvKEqupcPQ4nFZc=") +ORIGIN("react2media.com", "XK+60uWgTwJzy/c/wGf/jS7EMxrqFqdM2eu56ms75TA=") +ORIGIN("terra.com.br", "3w6Yul7AhCEvdfZtfEOwkoFjVz+WUHDSCf6aa0+T10k=") +ORIGIN("fairfax.com.au", "D9SojaSQJFQns6k0VA4JWvy3vOzURuWuueq40DF1aeg=") +ORIGIN("prometheusintelligencetechnology.com", "qWtOdQUfAjIkHeZl+jJmYnuHpSQ61oKSLi0a+ye32mw=") +ORIGIN("hilltopads.com", "o9mjL1EVpqpjDGhV+M7S8xXZVKQkjWtKPbJNRT41xis=") +ORIGIN("mediacom.com", "bnoxalLXWt0sb9Mb97hZITfD1xdYjNgOYaehJ+x3SZY=") +ORIGIN("attracta.com", "D+VPBDDutNd3j8Wn8AX+s8ZRgjGJmxtYQn/xs/a52g0=") +ORIGIN("capitaldata.fr", "KtPjgAjOM4mZSHMbgyJ/kZolrK+y+zLV19oSYKiXRB8=") +ORIGIN("adchemy.com", "PujhbXfMhTorG4qjwpUMfC3lIZ69M7jIutp6WCn4YfQ=") +ORIGIN("everesttech.net", "rY734YHBfxZlfPOkEZs2rEcwuystf+JTmYqg2h6b/70=") +ORIGIN("pro-market.net", "w2jMbuFCPYigdrIIPyCPO2C97wBpnBhMWQp2Mz7I4u0=") +ORIGIN("feedperfect.com", "eZghqjaGJTf0rxOx3vy//0wYWbSXVgpXjYsBtiEtPIE=") +ORIGIN("google.ge", "4PanC1O4CCrbOIC6A5mkjQPffMzK83OAowADsk+d3/Q=") +ORIGIN("google.gg", "HolrgkFOtuUfOt/f1sAO11DpyZRK8A2UvlFkPh3KHtY=") +ORIGIN("google.ga", "2zFIGZwGbLpJDMH2A4RetgZqCMGLHqglkCbQ+e7ppY4=") +ORIGIN("google.gm", "bOpkdAk8SQq/EccXIDakWehpHq0yVEiIU6Eh1ySk6Gw=") +ORIGIN("google.gl", "GgGDpfimsA4tbLT88Eh+J4facPoQKHDIGX5vB3wd64c=") +ORIGIN("belstat.nl", "toL/JqXxiBc0Vw+0wCH+5DvcoyXzrlboZWCW2+/IAps=") +ORIGIN("google.gp", "68bmHTESsDta36JHibzyO5c6Ucwby3w7c03XQtynbec=") +ORIGIN("google.gr", "76bKDP1iv99ELo5ckwvc3AgM0Xca6Ms6ztWOVhnmr8A=") +ORIGIN("vserv.com", "i+Pt0oIqDUTvbaNm77b1pBKo0DesxrbBG3nHKwpU2Rs=") +ORIGIN("google.gy", "cLMXcM2KNkbsM5as6/1Uf+bkpsr/jqDhrWliZWUobOI=") +ORIGIN("adfonic.com", "vXVVok5Y4AuVy8b28/hJSYrf6xN8WT1taAz2d60vMFU=") +ORIGIN("wingify.com", "xQUJbWTqiDfKnLqXujJwAbzrYAD1PFBYWio019c34cM=") +ORIGIN("inner-active.com", "JbwMJchyrK5GUYqNchmAYa4KDiWT+dv5f3OeBZJYgL8=") +ORIGIN("feedjit.com", "fn4mGtqS1P2FPxKCFsC9SwMHtCStssd42kx3dxC1V60=") +ORIGIN("google.com.fj", "ngws0drvFLHWoEax8F113wcUGmijftDZyjmdcyltSgc=") +ORIGIN("adlucent.com", "9AEZYdU6Hp2Sa18xVkBHdt/oavIFqLQVV+RRLBQDB+E=") +ORIGIN("dataxu.net", "6t6FpThCn1iUsHB4YU5Nk78JJHxXqgJBBqP/7jPVVZ4=") +ORIGIN("mobclix.com", "NzY3Au4YypMVH1mSCKJ4x5jYS/x6LktUbSlmct4iM7Q=") +ORIGIN("traffiq.com", "wyw0KnFgwJscEaUB0dmvu0cIing6x4uKCdzf87Fy5q8=") +ORIGIN("sitestat.com", "rG3cWO+2RoKzL7rnNgPDEpQvqswkaxsDN5B0ckzUK/M=") +ORIGIN("websitealive0.com", "kiMGG2sZwloDZqNhAx1Pvxgsn8ycth+gq4gO/hUZBls=") +ORIGIN("admobile.com", "b0fAcImObRTileeEtC+CdbRa+rk7xCxIEQ3m99b9V3E=") +ORIGIN("adtegrity.net", "ZRkceLPAXE+2PEWG+gkDfRJdsjH8NOkT97HAZpxJlcw=") +ORIGIN("media6degrees.com", "83mRm8sevX9pdgT+3pdh5a9yRPtAy/vse1HXtBAj1cQ=") +ORIGIN("wysistat.com", "aXCjYw5+GfQJ9ARd/13zmlUhNxTtiM50IWqC4A34UZU=") +ORIGIN("adformdsp.net", "ZVO3iDUcnVmmi4ipnjxgLashnepY30JbADBu1rQbdy8=") +ORIGIN("eztargetmedia.com", "gyJuTxIf4DFOHi/WO27aShtj6qqoRpA0jo2kW/CJQqE=") +ORIGIN("awaps.yandex.ru", "Qv0yfUPxopb/eWXI6qDRvIBuAjafWaSPyF67oOz3J+k=") +ORIGIN("ucoz.fr", "4PtMWZppV4PVP8QkKj0iAACfWjMnRHjBNKEiuRA14kI=") +ORIGIN("sexinyourcity.com", "2G4ptC7cP8iM59FciAgYDLFsqJp925SAwIW9cbXgAp0=") +ORIGIN("clearspring.com", "OVAaSC9Y4xtyZcrKbYSmVHZSqHh7zkYw3GUjjx69HmM=") +ORIGIN("blogger.com", "1PiXFiCCxLm0TmVgtif2UcOszZJAchPOYmYkCJVMzVg=") +ORIGIN("retirement-living.com", "viLxMTWAQ3Dq25MB5rFjSeY9p5AgO2lyqp3ALiWoAkQ=") +ORIGIN("nudatasecurity.com", "TghqZbhkf2WJ6vOpSVktVgPiKl/DM/4a0Lq0/Phmwjg=") +ORIGIN("adbroker.de", "6nccMCRpQWvvhejiRs2u+YP5ZRBapR0hcKX5VGTQUMw=") +ORIGIN("acxiom.com", "3UWUsxt1OjPPOa7AMuvqWzFBl/KPP3qbsr6PCvHTvSg=") +ORIGIN("parsely.com", "K25kUIOucmn0ED4d9FoklUHlQ2DJnQHMvSquuaZltHQ=") +ORIGIN("clickguard.com", "xYodb6xnZdaheyvk7r6D5K1e5OXm2FMchdm3tDER/b4=") +ORIGIN("dsply.com", "sOK6oILc5ttdTM2vZGmomjADd3ulQezWi014ddXujj8=") +ORIGIN("xgraph.com", "2NcB6dBwXPzk42hFHi6LVzTeNMsm9wS/kgQheI1p/GU=") +ORIGIN("rfihub.net", "PN4qA/uVCw60ARW6ZuHO7KHznIq1hb3Gm3DJwRrJsCU=") +ORIGIN("yadro.ru", "FJVKFF2JkwoKV28kb9N4ZbjoygI40mOXmBTBZcs8Wu8=") +ORIGIN("decktrade.com", "A4JuevO4vmBTBwtX9FH2F90ngOh8Ey8V2V5ewH09Ovk=") +ORIGIN("ringier.cz", "a/95Usja37PhowVgoA1zUELRgAO0opFfHaRz8uFzqR4=") +ORIGIN("lynchpin.com", "TdUFTjt0kUERKU/fLO0l2dCCyzFoh4qEaXP2o8kl3lY=") +ORIGIN("adacado.com", "mbLeJmN5iAiHkmO/K/bXA6TqheqzNaXgzrAyH52/ROs=") +ORIGIN("stratigent.com", "RJmNisy7UVBe2wpp7ldsL2LxPJGI7cqglRAwF0Z0BkA=") +ORIGIN("hitslink.com", "tpW7H8hxe4gIOvoaGtFxM2r3wePPBWTMy4ezYixod+Y=") +ORIGIN("google.com.mt", "nggYcw27M5xSv0/y3EyUZ+11IDhGHFJdSwrcL9h5r9I=") +ORIGIN("baynote.com", "c0j9r87+hFXFkdK7EvulRkQm8+KE1cHmMAVZxLugDK0=") +ORIGIN("cpmadvisors.com", "k1MJxVrtuFYk+/qOmAHOqvhpudfj/23QFO70QnPL1CQ=") +ORIGIN("movielush.com", "EcCjvIYeVhgcuVrE0sqoX/P8codTJnjJw14eGvY1bbE=") +ORIGIN("adiquity.com", "Td7G160eLsO7m3jXPnpXCX+m8IVytJMzOzcgh93nYqc=") +ORIGIN("adventive.com", "DNgJGPSb7vY4K6PpqmsVf9VENB33IDTCEbr2C+cY0dg=") +ORIGIN("adrevolver.com", "1du4mUa8yW+rQVwO0g9dLokvCUBXHHG0nsCB//wAoBk=") +ORIGIN("strikead.com", "eam5LMLlToQFqLFycWcQRjZ9mJf8L5qtTX8+V0daEK4=") +ORIGIN("yandex.by", "ruybcX2pvGMKxCRWpIe6ftHs+yysVAmTL0RiRCGOz9c=") +ORIGIN("p.brsrvr.com", "RQfs+0UyhHC6WSS+MVC13SllP/SQTQ3zBngjf/qi6IA=") +ORIGIN("phpmyvisites.us", "wKW6z2fCppAkCyPtY3Gi9sBhd1DY/7rYjIbzv+960GI=") +ORIGIN("google.com.ng", "RF1g81myQx+FjY23KHB9MEb6wvKVkPO1voajWia2W2U=") +ORIGIN("google.com.nf", "ny4tSP402SSnapITMrHa59InAzf7/FkEcvgkCD4GnMw=") +ORIGIN("google.com.ni", "XfF1RSerP19ZtKhF2xcEGix4MMKd++EvlHjiWwrneGo=") +ORIGIN("kanoodle.com", "TFbDy+H6ih9B+NMHLgDV6/vMZFGrW9wuA/mdqxZOExw=") +ORIGIN("brealtime.com", "QupUtOi6EP/PV4hr5I02DDaj3ioU1bQEypVJQMd1CsU=") +ORIGIN("google.com.np", "eigqmhnniiknmHhqzM2jGMal05xwRAHn2pB7yMmYwEA=") +ORIGIN("googlemail.com", "N0t/oELPOzghf/0pHRZgaD6546SxMAG9cbH1fK1Bd3g=") +ORIGIN("woopra-ns.com", "QX0gAY/1an2NQ7O8bOTYGrP7lJ7V2Hc6zUlddPsUTNA=") +ORIGIN("twyn-group.com", "TRYp4MquMlHhktCDwx6gEkDWBsbIN5gqVM2W0r3IsB4=") +ORIGIN("mystighty.info", "jS7yDRMNabPfqVgpiOqEkMz3CUXtiiEDL7X34Mu0tGE=") +ORIGIN("doublepimp.com", "E6n3KiepzqEo3SqDPkU/vg7VcMV9C0cEJG2eJXkotso=") +ORIGIN("keywordmax.com", "gF1pxUvvqLOX8y2Aao2LiYPe3Ts+JZsTkVhp6pRHfzs=") +ORIGIN("serving-sys.com", "qvtpezST/UNchB+835JP7g3Pan7XKOfpr6hdQqJkaPY=") +ORIGIN("adzerk.net", "SUuTDIC/x1IiGKSrsTlPq7PGB/T7gltvYqWljO7MeRk=") +ORIGIN("adelphic.com", "WqJ7xsZBGc20WyvLO3VPv+r9hxD30Q6cUgrSjmnHELU=") +ORIGIN("godaddy.com", "I22hOROgTcpCJy2EX6aTV/fRYA89CYnMk1Gs3QGDoSI=") +ORIGIN("bgclck.me", "iICaf0oGYG3m65s4p6fwFgR+JzXBKMGpbyWvd+NvG8k=") +ORIGIN("geniegroupltd.co.uk", "lqt4aaM+T+Dp/3e65cd4Y3FUF5OQA1sS8tZzmQxbeZI=") +ORIGIN("amazon.co.jp", "2OH5RfRgt2hBaUyq9fJGfdxMb8JWHm6oO+hLx2CI7+0=") +ORIGIN("infostars.ru", "Op25M/vdRO3aAQhn3i8KlERlQ7X2MdhaLDg7CbRPFNc=") +ORIGIN("cpvtgt.com", "WaPOvmbGct+ix6hWzNwlclDrWjYMVj6CQ5FllD/FP+U=") +ORIGIN("tremorvideo.com", "vaA0YF8auwQJVWMIrAYltELy8vyAlljwsNvU22YMfuA=") +ORIGIN("adonnetwork.com", "//PeGn3o6STCxDZ1VpLofuodxwZt0U1opGcneUkWDV0=") +ORIGIN("games2win.com", "3TJK3/hpOAmA0VzZhbdWOR2bnRfyHTR7qxqkq3Ebb6g=") +ORIGIN("google-analytics.com", "nfLpMnbE3tXvD7L7pWmHcFU/H5UETdtt+b4eZ+WMGB4=") +ORIGIN("encoremetrics.com", "QKVFQg0HDUGdDnRsSwl+Kxsi3trQ9a/QcJryQyN/71M=") +ORIGIN("doublepositive.com", "QjPL34/b8nt4z9FtyM0dTjm/6BuYRNdnaF+061VTRN8=") +ORIGIN("google.ps", "wpmj1RNNAcuqPzyAvsprdbBCrMsMDrJCUyO1142aSic=") +ORIGIN("google.pt", "8zGrlHHXCN4xvciFBXLiBvdXUXA8kCeT/6c2iiNqPew=") +ORIGIN("clipsyndicate.com", "pTETTD+kClerTHzyXr1IBzFEbuJKEf8H4U8sslePHH0=") +ORIGIN("bubblestat.com", "sqRMzifjUEji4lSVMIMYN96XpZq7x2Le0zihwARxCF4=") +ORIGIN("topsy.com", "tOscJ/xJB7VJ6kCvUGLfE8FlorFLnO7lJt3rWEPyELM=") +ORIGIN("cart.ro", "p4nTR5fggWlhorq4drMRF31X9y2NGAVZAvtsWqWzUy8=") +ORIGIN("ytsa.net", "zJUooWTFXXjhoSvGqExgP7YT8LjgadP725w4DN0Qh4A=") +ORIGIN("addecisive.com", "P51J0uAGOyBG706xipSKS8qpy30joqWqeFTTje3Nn/8=") +ORIGIN("google.pl", "5MFXUszUVt/uzdvHpagaEXc76HxPOBcPAL4Ik64qaCU=") +ORIGIN("google.pn", "M6eTcroXtxPb6d6H7W8Mj9Ev88oMDut29TwwETZ0PRw=") +ORIGIN("google.la", "Y3HXhz0oZa01kk1eseFf7MWGBDNXNN8z1aplFSr5m90=") +ORIGIN("turnto.com", "MR66YU+XTq943u3cRgFxJIbdq4G5VUdxt3Nx7I/uX6I=") +ORIGIN("247realmedia.com", "6iVKLG6hd9xUHgryviC/fd0szgriWpnLwnqkkJGz1EY=") +ORIGIN("euroclick.com", "lDnm+o4Bb+BVt7XegQmGE+90RNAIvpvqXzhhs8mnV+Q=") +ORIGIN("pubmatic.com", "PDKyNpwWJr7s1Z0D8H224ErzU15UpXfZS/oO+uMxi28=") +ORIGIN("admeld.com", "vPhSb+wA8xC37SkfoN9MGgoz8Bd8khq7h6xIx/bj8ps=") +ORIGIN("adultmoda.com", "KGCkYwtjonN3DMN5iYWslOhp05ICtRyAg9kCLKtQ9mc=") +ORIGIN("inboundwriter.com", "jztADB4HnJQ4UWR0ZE4CFypIndiAIRAyfxmJxeaq/RY=") +ORIGIN("ipromote.com", "Ekr7FW1KHfaozPAroB30iIndZyIfJ6c9I7rY0sqDIuI=") +ORIGIN("adrtx.net", "JcYV540d05WrBFxs1neAVstmr7Nq5j1DQeg+AFe0cvY=") +ORIGIN("adsfac.info", "YQJCLVRlhsvsLeoV2OHCtwi2U5IwCbG2Nb2Mq1adGJs=") +ORIGIN("knol.google.com", "7isgP9uFg+KQjFEfuK5xMISONKoled2qbvxlQtUm05g=") +ORIGIN("yahooapis.com", "CGEPsrBnFILwJL11Tjz15N9HaMCFKkrAtdI8/jAzkuw=") +ORIGIN("insightexpress.com", "5spU+PV8LmCEgTXtG1zmU24eK8wYdCLQcOT3DMublCo=") +ORIGIN("csdata1.com", "Ao3/NViw6taQuas9rb7V57d6J2MJwl4Hf1Kxe6nvPK0=") +ORIGIN("adconnexa.com", "e60mDfWpU/yKJK1UTUA9BtZGVb4Rw/sMeUqCtGY+G8E=") +ORIGIN("stargamesaffiliate.com", "/W+oIX0ant4x6huUbMP90viG4XzISPew443RitgzG7w=") +ORIGIN("legolas-media.com", "BEWlCJm6oOVmmAMr29kl2WeLDuYTxsq/77K4D9ja4lA=") +ORIGIN("telstra.com.au", "dngmGPDreuh49ST8eryQQ09yNBb5BjxyMxUle1xmR5Y=") +ORIGIN("gmail.com", "YUhHHolh1gWizFkn/7n2xXLRKt/yx+HqlL2VHgHmsiE=") +ORIGIN("awltovhc.com", "0fZETTFfdH+RiH1qKZvS38o8slVx5OqM2E8nNBfWewE=") +ORIGIN("clicktracks.com", "S/SIW287Xh+s+8yy31egknvgqfo8mw6XmP2pf3M1jqM=") +ORIGIN("bidswitch.net", "Vt+6m7s33bbqtjHKVqyzHaTr5GaTLbhyzMvKTbuxvJE=") +ORIGIN("noktamedya.com", "uaYlYoz+kfW9qqzUWCXZ7NZ8l8+FayiEqos6hrc8g68=") +ORIGIN("google.lt", "yqRhCh6pH1sCMeeF2BO2NPUh8afj33YneSMyc9Xf5+U=") +ORIGIN("verticalacuity.com", "DoH5AfiNR09T0lBw+xl9SynApuThLFoEGUDlCqZ3UHc=") +ORIGIN("google.lu", "ZliPg3sbl3Bx/254hCa6FAFUIbpxp5/hQ6J0jOZ78L4=") +ORIGIN("youknowbest.com", "nTwKg7gVdoLP5FFkdISyNUmU+GtEdVDDKDOLoFkwhSM=") +ORIGIN("src.kitcode.net", "6vS1p9+ZNIUNbEFZuxthlrVSLuaAZXlvdcNPYe0IHKU=") +ORIGIN("voice.google.com", "BKDrnMGSsgXVEjkwgL7oLs4oG0Vdx1x6Lp6FGOO/8lk=") +ORIGIN("gigya.com", "ausWPqhK0ulWOdTDdAi9dDQcIq6euXpoPIBJLGnIfjE=") +ORIGIN("tacoda.net", "9/lQNfL4ALCNUiG2xzehFPuNTPXvRS1esAODiqdWKVw=") +ORIGIN("developermedia.com", "GhofUC9cRWzUQ3lIquBESD9LwEgtK32kh6fXeq7Dt3s=") +ORIGIN("cpmatic.com", "WFo3DXfKT3NbzxmSBaxASgzHblgsWDHQbDM+kFaICQM=") +ORIGIN("onestat.com", "8n8vYYnRjnHT+T441HztQDqQqaL3TWQR556SAAUqFhs=") +ORIGIN("plugin.management", "2kBf5YpTrvWm/i6g6zSW2YSRrvmw3b6vwQeYvZ74Go0=") +ORIGIN("adohana.com", "1V27BFRB/z5RqjIjLELuiwgsc67rRlrGRWKNKXFaKFM=") +ORIGIN("mindshare.nl", "VBUoneI2ZT2PRbwP3MwpSq0T6HGEsTv2JvvxFP43UOI=") +ORIGIN("polldaddy.com", "epTHk29UCBP0SErBUiLAee4EMjr4JMc4SAYYLC3yllo=") +ORIGIN("roiservice.com", "eO5Qfhu4sPKG/9ASWvg4jb2c09NyfgX5C/+/QORornc=") +ORIGIN("365media.com", "R9UexJHQ5GWrMd2Z9umNuGR3BLNe8ZGArtsl+XK3X2M=") +ORIGIN("bluelithium.com", "ErxC0eNUlSd9mIj+Hn8QwMpHQVceMJKX03YnLlPe9+8=") +ORIGIN("adsymptotic.com", "GjFf/iPOdNumdpBLmbrsytpop8J1u5YmFx/qivYKcNs=") +ORIGIN("qksz.net", "35LtjBK8Hh2WIJUCPzODnoV+nuRPhnw0JBbt+WCmu50=") +ORIGIN("grvcdn.com", "mN7gWLHi9morQlmMZuUjawP1UwRlYL23PeCDM7x+T4g=") +ORIGIN("emxdgt.com", "h36/y0E9WmM08TzEAkMy/NbYr0Gn3JE2SJ4gOSwMOg8=") +ORIGIN("gosquared.com", "wXZEF9KH5DBYnsMTe00kbfQKqGL21Brbt8TdktNS9A0=") +ORIGIN("google.fm", "CvepxASOdY52KZG811o2T5L5YkKrQgTbvvLG7pZfCWs=") +ORIGIN("adfunky.com", "Qbpdxqfd6AhnVNWNkmU9oltwVAWBsqzQya2SnrD+ye4=") +ORIGIN("imedia.cz", "r7YzQp2tLXLcT8ioXEKq3I1FVHTOaO+LcM3mI0m/FTE=") +ORIGIN("specificclick.net", "kFWtJ9sSLSOEi3qESEmHm5NC+naOI9wyLlZMyZweUN4=") +ORIGIN("google.fi", "d4tDu7TVVDudsfK/Id7X2Y8FmgCMQGM+VF32HsUdsvw=") +ORIGIN("google.fr", "yAb5Zg0XLui9Y4dTge+JjDsGyQMHDEbo3kRp/4k87TE=") +ORIGIN("tnsglobal.com", "CBIO18Gt5/JQpgFNCXY2Dii/57X9mKmgyclXA6M79IE=") +ORIGIN("ibm.com", "spyx2WTEo/xoN7iRozH3eswO7KFv5kfxufMnazkEaSY=") +ORIGIN("tweetboard.com", "J79++DC3BgMOGPgect/KQYCW+Rni6u6YC98xqN+ooq4=") +ORIGIN("adversalservers.com", "PNq+Ls/CJ36SSS0et1HZ/WiDuJNFPblZYfJDhupNFZA=") +ORIGIN("adtechus.com", "OcOZ6GFT3UfCzFDnTlaLS5Y4z2i4riOK4+d2rasbUkY=") +ORIGIN("twittercounter.com", "20C2bgBha9BObsHfS+EIpaxu60u72KNcTIjzhIa+HHM=") +ORIGIN("airpush.com", "b8w8a6jJBf4ypgUmX65kVbjRpjAOXaFqI8ASwRCfRwc=") +ORIGIN("visualdna.com", "Qs/9pcsgAB61UI+h+gzjCpK0y3PUV5fq6mo/4XHeFOs=") +ORIGIN("gunggo.com", "ipc638vyxNFkfRNPoON6UGPC1X73IP8yu/PqlIjj/+g=") +ORIGIN("usabilitysciences.com", "fyIPWRJ5c1ZxN5Hay432FIeUJEJ0OWsC/3/Uh9IBLOM=") +ORIGIN("abaxinteractive.com", "qS3wJInMwDy8ewhNv+HAE1VSirhJR/RAdMunQyC6FNk=") +ORIGIN("magnify360.com", "pzbNxzR5ITEiEUGWVXC4T8kyR5aDY6XlBdhrnmyyr8I=") +ORIGIN("logdy.com", "8maMecTf4YcZJKN1KRdkyFGHstMSMopPSerO7pkNyeA=") +ORIGIN("maxusglobal.com", "dWNpNiqM8LwVhXoAQJcsyhcLj/995QNCrS4THLgf+CM=") +ORIGIN("csdata2.com", "1cGmxe2oo6505LUcNSC44Yy2+u9bgHPYYXg+Ea/7V9g=") +ORIGIN("iac.com", "SZSFBfRirUPuyD1RSoYgVaKYUuGckRJ/6p/+laWEVIQ=") +ORIGIN("google.co.uz", "PInb5/Jq9gv/RNrpCP26m77RqVEbO9kO9cI4YRDUCqA=") +ORIGIN("yandex.com.tr", "sawWnWXWNPE1GOvKk3cpJdPbD9kMXOn3GYB6ka5CgQw=") +ORIGIN("wtp101.com", "b0Dla0qz22tA9rMxagngWG9qoidJ5vCpSk2kmFTzBUk=") +ORIGIN("svlu.net", "pIAKroYlruDvhh0UbvhNHLnpcxd451SdSeJZRYZjh1U=") +ORIGIN("google.co.uk", "YhvuR5TjpAp1kBKS4lSAyguLNj9J63lDTeBzBCbZiuI=") +ORIGIN("sensisdigitalmedia.com.au", "/ObEs/Rgjk+3ICojq3zvp3OUakvz8UoNWuspdvzhMWk=") +ORIGIN("alerts.yahoo.com", "bqtAdDAxQZy6rjQavUyy2lb7az1CAuiS6aheA8G5jok=") +ORIGIN("yandex.ua", "dSK456tW1Xwtp/7dg0IamAjlcbzCcoGzZE1sRPNJ5HE=") +ORIGIN("kitchendaily.com", "8nw4zpVgyAjlzLMekv+ipZCoeR5WEbeNQdtj93BSfzI=") +ORIGIN("cmads.com.tw", "Wrihb3gYEPhDr08mpjPIigoPJObOUqDnQ2edgS7kfQU=") +ORIGIN("google.co.ug", "Z2HPupC8Y6jE9LCXSZx7RoyHLclkEJ8Wrb1jD4zn4SY=") +ORIGIN("veoxa.com", "YXXBSP6+f6hjGRPYE6xmlXAtLug4kvYE9tiopUXkkbI=") +ORIGIN("live.com", "7M0/r7UjOdP5TKBFvbeDwZpP/nkB0IVaAsuYZSr9mr4=") +ORIGIN("mail.ru", "h90DtgXXu0dp66WCJrvqvz2CgP5wuzKkNW7tGGMD3Bk=") +ORIGIN("peerius.com", "tIuO+7hZ8FSFStZSWpcf8fG3OBofLUoghUIOsqHgSm0=") +ORIGIN("turntonetworks.com", "k6rf0op+YNTTh8xIvB20NTCyhQUaEF3kSV+J2SnPigw=") +ORIGIN("tonemedia.com", "SOtHIzhQwR0nabTyxr8j64leAy340HgBJufhSayIB3Q=") +ORIGIN("rtbidder.net", "elX9K92R1Dy+GOxXhxH/NvujK3XfhXVAfXUOas4TLBU=") +ORIGIN("google.co.id", "zM56p790PS9sxbBCwEyZzCVfbjFQWERCfOu90c8Lbvg=") +ORIGIN("twitter.com", "7+XSRurpWgtx+tvSD5S4Uq5xvaSXSHEDAV5Gm3pWbkI=") +ORIGIN("visualrevenue.com", "gCeFfwQFEPxOu0dW+l890WUYwk13VtJG6BdjxnxDFLc=") +ORIGIN("rlcdn.net", "YeaCqU+nhEHYDxQavoKRozFv/Og0qNSXMjL1+wJPjWw=") +ORIGIN("eloqua.com", "Cy1S7yfqXzxYxdiqsdYn3/G8YG6XO/QN10C6I99T8s0=") +ORIGIN("news.google.com", "YuKZZymtdAVFJWRl5UOHwhh3wGxV8xFdOu1thH8J1A8=") +ORIGIN("p-advg.com", "KbmdPvSSZbUAiAhmCEyFINZb7h8cKALit/i5micympU=") +ORIGIN("sharethrough.com", "vGNht7RnDTCxFqTZ/Hov8tJi4T/0KKKBH8ZiKwN+kh0=") +ORIGIN("brandaffinity.net", "np7fAq9SRqRIucYXt2IfdL+JWHEJQTNQnvwmrPF3zsQ=") +ORIGIN("vendio.com", "7WFklFArow8+3jkPsqCxHd74I9TM7alWjs+daLXBGV0=") +ORIGIN("techcrunch.com", "8pmXPGLVeYyM/SjC233pn66TsVoLD+TQC2b3iX7ErLI=") +ORIGIN("yuilibrary.com", "dSMvd7ZTR2FY+a13ONdNFr5V5OY0P2NsfS4pulWjtzQ=") +ORIGIN("adcde.com", "VaWn7NZSVlwUu9O/9/sEKPs8Ov0RLPKRFCxSpbTcPDI=") +ORIGIN("pictela.net", "L1NXkj2U5GHf9VQGPFn44doWl2XbhUAnUkwA9/omDds=") +ORIGIN("adrolays.de", "YAZKQHakPzwzlxfD7g2S4WD35knvRbMP0Ps/yOArfRo=") +ORIGIN("radiumone.com", "PKJPFns8LP1/wckQhuZlzAfh9QeoAEDqf9igBYojV7U=") +ORIGIN("developer.yahoo.com", "Nrz1eqSXEPL1rbldIlHGmgFjoAX86HPSeCO0mmxRmrE=") +ORIGIN("clickable.net", "m4Scz7zc91YiLAaOa8Zx9u4Cm07o3F4CjOYBMeVVRRI=") +ORIGIN("burstdirectads.com", "Q5pxsv2z/uH+D5vE9kyYfiuFP44lvJ/+1EOVs803zzA=") +ORIGIN("qsstats.com", "PnEX1rWjkvb5VO1UyANwfDto/goiR53AlPvkASyPuak=") +ORIGIN("summitmedia.co.uk", "biE216vi8UcYSIKF0Cvq+oAmyRTxXLRbcQuDskwwXfE=") +ORIGIN("adsmart.com", "vRbCtY83XzQsjWHtA5mVmd/SCRXqXGDxA5vZqsGu03Y=") +ORIGIN("quinstreet.com", "sG7xOk8q7TwvvpLMPOgvqs9Hk/PrHTR0VFLj86bhrA0=") +ORIGIN("valueclickmedia.com", "dCEGshDVxQUAtgVmYkbG51eVmN9ix96tS+XkUvlg0qw=") +ORIGIN("1rx.io", "BNcxlA5DX+h3oeORIwJXFeg2REcCjJk71842xtzdh7k=") +ORIGIN("webmetro.com", "/nHY1XFXcT0bWmXzwHmfr0FHj97k86KcnxV9tqGotwY=") +ORIGIN("nuconomy.com", "+7ENDBVruI9RTBg4CPOPh/ygYQ27z69bgx2ae28gb3g=") +ORIGIN("images.google.com", "g49WVkX52Q4wwr7M3GjOkuL3anN9nKKp1rAMfYag9e0=") +ORIGIN("thetradedesk.com", "03dyXX8gmppDpuU4stWmxBW0IP7+eQhcywgri7j+uUc=") +ORIGIN("hooklogic.com", "iFQrpjZWR5FpTNH/kWYRW5j2b2xKkfjDT2xK9fWe2bs=") +ORIGIN("audienceadnetwork.com", "AxNWTOsyAkC4fSyDPd1CN395M5CdZhmIk9cZ+rChMzE=") +ORIGIN("mmapiws.com", "CYKUtmNj09mSAWSAKHfuttxbTQ2ynpEqucX7qkLIidY=") +ORIGIN("b0e8.com", "Co/6TjaoY6VB0iMbU4wySMmOMmavCgrzB/TTfuY4gNo=") +ORIGIN("nakanohito.jp", "+R01KsDizmcZDta+Q2QkDAoGp5nFwl1oQ/SR5+7CFVc=") +ORIGIN("de17a.com", "Cmu23OqLpLjQml+U82RtH88RZnFv0u+nMIXdZReeGhc=") +ORIGIN("renegadeinternet.com", "+8Cu48EizDYBPViZMmAtr+1/l4duO8CkHkBml/cluUY=") +ORIGIN("verticalresponse.com", "F0ySG5B/4LWEQIfVSDnNdy3H53RusgYF8ljiK7+FAow=") +ORIGIN("networldmedia.com", "EeIpLPYezBF/e5pjKAl/MHW9nnBQjM3ZbCgXTmhD838=") +ORIGIN("adform.com", "luc0spL2jZckFpFuyyH3A5ZAsm77xIaDBPO0Ei4uAQU=") +ORIGIN("yellowtracker.com", "nMFY5n/+qjZB9o4vHUbTI1M1x3BbbKMiWPa8dXH1k9w=") +ORIGIN("zopim.com", "Eu+zmA1UJmag0axvo1duSSWMfx7/+WpRk/k+lQryHVs=") +ORIGIN("doclix.com", "iA3vMVkmXYdOIJwrvQ432VIUxoH2GP5EvZKD+MRkZL4=") +ORIGIN("datasift.com", "AbUpWsNuc7JAG87JtMnLkOy2TwbERoewA2OmOVt4AHA=") +ORIGIN("mb01.com", "X9/9IgjduKZsZNh+qcaHIfFcczni2UHbBzrCrq3ZiLI=") +ORIGIN("hittail.com", "bzUrFVcPFDIKxXpMgwwDqYPpRPmqniS/SQO45aJtiX0=") +ORIGIN("haloscan.com", "AGF75p17F4Y+Lh3uTcS4M7Wip8dbilRIUvTrvj6DHeg=") +ORIGIN("inbox.google.com", "SETskRfVJfgkrEOzF3bg4OLen9zaSTwhyaUAiKDvD7w=") +ORIGIN("azetklik.sk", "Z532Goxf9qifO32Ngh6cl1Xu9DWkLX4MsX9CLtMvMVY=") +ORIGIN("rightaction.com", "gYCy0gvIUKlc4M8tmD/DqJ9QiIYabe/WYf/V3/BZark=") +ORIGIN("mathtag.com", "2M2U/R8LuShy13GU0Z/8mjX8AnYMz0dnv2I+pxQ49Zc=") +ORIGIN("mouseflow.com", "5HPwzePIU+NiJI+b90n1o+rxSWzmE90jQaGZbyYkSB0=") +ORIGIN("patch.com", "hza+WcYpIu98rNjfSmQH9v59EofdLOb7UErhMnSB8SI=") +ORIGIN("venatusmedia.com", "XZOM5Lv+ZlQT4B6rxS8inCvkqamFmhdbO3UMK89nDmU=") +ORIGIN("runads.com", "c51XZFKuxR9sAFNDGCxaFCG8HHEKVrpSUn60WmPxOug=") +ORIGIN("connexity.com", "W6TP0LUmeeDvQ7d5CUNJQBbBgzQca3U3lP93u4IOTJY=") +ORIGIN("linksynergy.com", "tDvbLbQ+0UMrKE3Rs+LPAdqgIYRRdqr/MW2il8VQr0o=") +ORIGIN("alexametrics.com", "Hwn39xeqNN0OkHgzsBTNuunG3OpodVTGICWpmJ7lcwM=") diff --git a/toolkit/components/telemetry/core/TelemetryScalar.cpp b/toolkit/components/telemetry/core/TelemetryScalar.cpp new file mode 100644 index 0000000000..148d49a14c --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryScalar.cpp @@ -0,0 +1,4192 @@ +/* -*- 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 "TelemetryScalar.h" + +#include "geckoview/streaming/GeckoViewStreamingTelemetry.h" +#include "ipc/TelemetryIPCAccumulator.h" +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject +#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefineUCProperty, JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_HasProperty +#include "mozilla/dom/ContentParent.h" +#include "mozilla/JSONWriter.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TelemetryComms.h" +#include "mozilla/Unused.h" +#include "nsBaseHashtable.h" +#include "nsClassHashtable.h" +#include "nsContentUtils.h" +#include "nsHashKeys.h" +#include "nsITelemetry.h" +#include "nsIVariant.h" +#include "nsIXPConnect.h" +#include "nsJSUtils.h" +#include "nsPrintfCString.h" +#include "nsVariant.h" +#include "TelemetryScalarData.h" + +using mozilla::MakeUnique; +using mozilla::Nothing; +using mozilla::Preferences; +using mozilla::Some; +using mozilla::StaticAutoPtr; +using mozilla::StaticMutex; +using mozilla::StaticMutexAutoLock; +using mozilla::UniquePtr; +using mozilla::Telemetry::DynamicScalarDefinition; +using mozilla::Telemetry::KeyedScalarAction; +using mozilla::Telemetry::ProcessID; +using mozilla::Telemetry::ScalarAction; +using mozilla::Telemetry::ScalarActionType; +using mozilla::Telemetry::ScalarID; +using mozilla::Telemetry::ScalarVariant; +using mozilla::Telemetry::Common::AutoHashtable; +using mozilla::Telemetry::Common::CanRecordDataset; +using mozilla::Telemetry::Common::CanRecordProduct; +using mozilla::Telemetry::Common::GetCurrentProduct; +using mozilla::Telemetry::Common::GetIDForProcessName; +using mozilla::Telemetry::Common::GetNameForProcessID; +using mozilla::Telemetry::Common::IsExpiredVersion; +using mozilla::Telemetry::Common::IsInDataset; +using mozilla::Telemetry::Common::IsValidIdentifierString; +using mozilla::Telemetry::Common::LogToBrowserConsole; +using mozilla::Telemetry::Common::RecordedProcessType; +using mozilla::Telemetry::Common::StringHashSet; +using mozilla::Telemetry::Common::SupportedProduct; + +namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator; + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// Naming: there are two kinds of functions in this file: +// +// * Functions named internal_*: these can only be reached via an +// interface function (TelemetryScalar::*). If they access shared +// state, they require the interface function to have acquired +// |gTelemetryScalarMutex| to ensure thread safety. +// +// * Functions named TelemetryScalar::*. This is the external interface. +// Entries and exits to these functions are serialised using +// |gTelemetryScalarsMutex|. +// +// Avoiding races and deadlocks: +// +// All functions in the external interface (TelemetryScalar::*) are +// serialised using the mutex |gTelemetryScalarsMutex|. This means +// that the external interface is thread-safe. But it also brings +// a danger of deadlock if any function in the external interface can +// get back to that interface. That is, we will deadlock on any call +// chain like this +// +// TelemetryScalar::* -> .. any functions .. -> TelemetryScalar::* +// +// To reduce the danger of that happening, observe the following rules: +// +// * No function in TelemetryScalar::* may directly call, nor take the +// address of, any other function in TelemetryScalar::*. +// +// * No internal function internal_* may call, nor take the address +// of, any function in TelemetryScalar::*. + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE TYPES + +namespace { + +const uint32_t kMaximumNumberOfKeys = 100; +const uint32_t kMaxEventSummaryKeys = 500; +const uint32_t kMaximumKeyStringLength = 72; +const uint32_t kMaximumStringValueLength = 50; +// The category and scalar name maximum lengths are used by the dynamic +// scalar registration function and must match the constants used by +// the 'parse_scalars.py' script for static scalars. +const uint32_t kMaximumCategoryNameLength = 40; +const uint32_t kMaximumScalarNameLength = 40; +const uint32_t kScalarCount = + static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount); + +// To stop growing unbounded in memory while waiting for scalar deserialization +// to finish, we immediately apply pending operations if the array reaches +// a certain high water mark of elements. +const size_t kScalarActionsArrayHighWaterMark = 10000; + +const char* TEST_SCALAR_PREFIX = "telemetry.test."; + +// The max offset supported by gScalarStoresTable for static scalars' stores. +// Also the sentinel value (with store_count == 0) for just the sole "main" +// store. +const uint32_t kMaxStaticStoreOffset = UINT16_MAX; + +enum class ScalarResult : uint8_t { + // Nothing went wrong. + Ok, + // General Scalar Errors + NotInitialized, + CannotUnpackVariant, + CannotRecordInProcess, + CannotRecordDataset, + KeyedTypeMismatch, + UnknownScalar, + OperationNotSupported, + InvalidType, + InvalidValue, + // Keyed Scalar Errors + KeyIsEmpty, + KeyTooLong, + TooManyKeys, + KeyNotAllowed, + // String Scalar Errors + StringTooLong, + // Unsigned Scalar Errors + UnsignedNegativeValue, + UnsignedTruncatedValue, +}; + +// A common identifier for both built-in and dynamic scalars. +struct ScalarKey { + uint32_t id; + bool dynamic; +}; + +// Dynamic scalar store names. +StaticAutoPtr<nsTArray<RefPtr<nsAtom>>> gDynamicStoreNames; + +/** + * Scalar information for dynamic definitions. + */ +struct DynamicScalarInfo : BaseScalarInfo { + nsCString mDynamicName; + bool mDynamicExpiration; + uint32_t store_count; + uint32_t store_offset; + + DynamicScalarInfo(uint32_t aKind, bool aRecordOnRelease, bool aExpired, + const nsACString& aName, bool aKeyed, bool aBuiltin, + const nsTArray<nsCString>& aStores) + : BaseScalarInfo(aKind, + aRecordOnRelease + ? nsITelemetry::DATASET_ALL_CHANNELS + : nsITelemetry::DATASET_PRERELEASE_CHANNELS, + RecordedProcessType::All, aKeyed, 0, 0, + GetCurrentProduct(), aBuiltin), + mDynamicName(aName), + mDynamicExpiration(aExpired) { + store_count = aStores.Length(); + if (store_count == 0) { + store_count = 1; + store_offset = kMaxStaticStoreOffset; + } else { + store_offset = kMaxStaticStoreOffset + 1 + gDynamicStoreNames->Length(); + for (const auto& storeName : aStores) { + gDynamicStoreNames->AppendElement(NS_Atomize(storeName)); + } + MOZ_ASSERT( + gDynamicStoreNames->Length() < UINT32_MAX - kMaxStaticStoreOffset - 1, + "Too many dynamic scalar store names. Overflow."); + } + }; + + // The following functions will read the stored text + // instead of looking it up in the statically generated + // tables. + const char* name() const override; + const char* expiration() const override; + + uint32_t storeCount() const override; + uint32_t storeOffset() const override; +}; + +const char* DynamicScalarInfo::name() const { return mDynamicName.get(); } + +const char* DynamicScalarInfo::expiration() const { + // Dynamic scalars can either be expired or not (boolean flag). + // Return an appropriate version string to leverage the scalar expiration + // logic. + return mDynamicExpiration ? "1.0" : "never"; +} + +uint32_t DynamicScalarInfo::storeOffset() const { return store_offset; } +uint32_t DynamicScalarInfo::storeCount() const { return store_count; } + +typedef nsBaseHashtableET<nsDepCharHashKey, ScalarKey> CharPtrEntryType; +typedef AutoHashtable<CharPtrEntryType> ScalarMapType; + +// Dynamic scalar definitions. +StaticAutoPtr<nsTArray<DynamicScalarInfo>> gDynamicScalarInfo; + +const BaseScalarInfo& internal_GetScalarInfo(const StaticMutexAutoLock& lock, + const ScalarKey& aId) { + if (!aId.dynamic) { + return gScalars[aId.id]; + } + + return (*gDynamicScalarInfo)[aId.id]; +} + +bool IsValidEnumId(mozilla::Telemetry::ScalarID aID) { + return aID < mozilla::Telemetry::ScalarID::ScalarCount; +} + +bool internal_IsValidId(const StaticMutexAutoLock& lock, const ScalarKey& aId) { + // Please note that this function needs to be called with the scalar + // mutex being acquired: other functions might be messing with + // |gDynamicScalarInfo|. + return aId.dynamic + ? (aId.id < gDynamicScalarInfo->Length()) + : IsValidEnumId(static_cast<mozilla::Telemetry::ScalarID>(aId.id)); +} + +/** + * Convert a nsIVariant to a mozilla::Variant, which is used for + * accumulating child process scalars. + */ +ScalarResult GetVariantFromIVariant(nsIVariant* aInput, uint32_t aScalarKind, + mozilla::Maybe<ScalarVariant>& aOutput) { + switch (aScalarKind) { + case nsITelemetry::SCALAR_TYPE_COUNT: { + uint32_t val = 0; + nsresult rv = aInput->GetAsUint32(&val); + if (NS_FAILED(rv)) { + return ScalarResult::CannotUnpackVariant; + } + aOutput = mozilla::Some(mozilla::AsVariant(val)); + break; + } + case nsITelemetry::SCALAR_TYPE_STRING: { + nsString val; + nsresult rv = aInput->GetAsAString(val); + if (NS_FAILED(rv)) { + return ScalarResult::CannotUnpackVariant; + } + aOutput = mozilla::Some(mozilla::AsVariant(val)); + break; + } + case nsITelemetry::SCALAR_TYPE_BOOLEAN: { + bool val = false; + nsresult rv = aInput->GetAsBool(&val); + if (NS_FAILED(rv)) { + return ScalarResult::CannotUnpackVariant; + } + aOutput = mozilla::Some(mozilla::AsVariant(val)); + break; + } + default: + MOZ_ASSERT(false, "Unknown scalar kind."); + return ScalarResult::UnknownScalar; + } + return ScalarResult::Ok; +} + +/** + * Write a nsIVariant with a JSONWriter, used for GeckoView persistence. + */ +nsresult WriteVariantToJSONWriter( + uint32_t aScalarType, nsIVariant* aInputValue, + const mozilla::Span<const char>& aPropertyName, + mozilla::JSONWriter& aWriter) { + MOZ_ASSERT(aInputValue); + + switch (aScalarType) { + case nsITelemetry::SCALAR_TYPE_COUNT: { + uint32_t val = 0; + nsresult rv = aInputValue->GetAsUint32(&val); + NS_ENSURE_SUCCESS(rv, rv); + aWriter.IntProperty(aPropertyName, val); + break; + } + case nsITelemetry::SCALAR_TYPE_STRING: { + nsCString val; + nsresult rv = aInputValue->GetAsACString(val); + NS_ENSURE_SUCCESS(rv, rv); + aWriter.StringProperty(aPropertyName, val); + break; + } + case nsITelemetry::SCALAR_TYPE_BOOLEAN: { + bool val = false; + nsresult rv = aInputValue->GetAsBool(&val); + NS_ENSURE_SUCCESS(rv, rv); + aWriter.BoolProperty(aPropertyName, val); + break; + } + default: + MOZ_ASSERT(false, "Unknown scalar kind."); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +// Implements the methods for ScalarInfo. +const char* ScalarInfo::name() const { + return &gScalarsStringTable[this->name_offset]; +} + +const char* ScalarInfo::expiration() const { + return &gScalarsStringTable[this->expiration_offset]; +} + +/** + * The base scalar object, that serves as a common ancestor for storage + * purposes. + */ +class ScalarBase { + public: + explicit ScalarBase(const BaseScalarInfo& aInfo) + : mStoreCount(aInfo.storeCount()), + mStoreOffset(aInfo.storeOffset()), + mStoreHasValue(mStoreCount), + mName(aInfo.name()) { + mStoreHasValue.SetLength(mStoreCount); + for (auto& val : mStoreHasValue) { + val = false; + } + }; + virtual ~ScalarBase() = default; + + // Set, Add and SetMaximum functions as described in the Telemetry IDL. + virtual ScalarResult SetValue(nsIVariant* aValue) = 0; + virtual ScalarResult AddValue(nsIVariant* aValue) { + return ScalarResult::OperationNotSupported; + } + virtual ScalarResult SetMaximum(nsIVariant* aValue) { + return ScalarResult::OperationNotSupported; + } + + // Convenience methods used by the C++ API. + virtual void SetValue(uint32_t aValue) { + mozilla::Unused << HandleUnsupported(); + } + virtual ScalarResult SetValue(const nsAString& aValue) { + return HandleUnsupported(); + } + virtual void SetValue(bool aValue) { mozilla::Unused << HandleUnsupported(); } + virtual void AddValue(uint32_t aValue) { + mozilla::Unused << HandleUnsupported(); + } + virtual void SetMaximum(uint32_t aValue) { + mozilla::Unused << HandleUnsupported(); + } + + // GetValue is used to get the value of the scalar when persisting it to JS. + virtual nsresult GetValue(const nsACString& aStoreName, bool aClearStore, + nsCOMPtr<nsIVariant>& aResult) = 0; + + // To measure the memory stats. + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + virtual size_t SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const = 0; + + protected: + bool HasValueInStore(size_t aStoreIndex) const; + void ClearValueInStore(size_t aStoreIndex); + void SetValueInStores(); + nsresult StoreIndex(const nsACString& aStoreName, size_t* aStoreIndex) const; + + private: + ScalarResult HandleUnsupported() const; + + const uint32_t mStoreCount; + const uint32_t mStoreOffset; + nsTArray<bool> mStoreHasValue; + + protected: + const nsCString mName; +}; + +ScalarResult ScalarBase::HandleUnsupported() const { + MOZ_ASSERT(false, "This operation is not support for this scalar type."); + return ScalarResult::OperationNotSupported; +} + +bool ScalarBase::HasValueInStore(size_t aStoreIndex) const { + MOZ_ASSERT(aStoreIndex < mStoreHasValue.Length(), + "Invalid scalar store index."); + return mStoreHasValue[aStoreIndex]; +} + +void ScalarBase::ClearValueInStore(size_t aStoreIndex) { + MOZ_ASSERT(aStoreIndex < mStoreHasValue.Length(), + "Invalid scalar store index to clear."); + mStoreHasValue[aStoreIndex] = false; +} + +void ScalarBase::SetValueInStores() { + for (auto& val : mStoreHasValue) { + val = true; + } +} + +nsresult ScalarBase::StoreIndex(const nsACString& aStoreName, + size_t* aStoreIndex) const { + if (mStoreCount == 1 && mStoreOffset == kMaxStaticStoreOffset) { + // This Scalar is only in the "main" store. + if (aStoreName.EqualsLiteral("main")) { + *aStoreIndex = 0; + return NS_OK; + } + return NS_ERROR_NO_CONTENT; + } + + // Multiple stores. Linear scan to find one that matches aStoreName. + // Dynamic Scalars start at kMaxStaticStoreOffset + 1 + if (mStoreOffset > kMaxStaticStoreOffset) { + auto dynamicOffset = mStoreOffset - kMaxStaticStoreOffset - 1; + for (uint32_t i = 0; i < mStoreCount; ++i) { + auto scalarStore = (*gDynamicStoreNames)[dynamicOffset + i]; + if (nsAtomCString(scalarStore).Equals(aStoreName)) { + *aStoreIndex = i; + return NS_OK; + } + } + return NS_ERROR_NO_CONTENT; + } + + // Static Scalars are similar. + for (uint32_t i = 0; i < mStoreCount; ++i) { + uint32_t stringIndex = gScalarStoresTable[mStoreOffset + i]; + if (aStoreName.EqualsASCII(&gScalarsStringTable[stringIndex])) { + *aStoreIndex = i; + return NS_OK; + } + } + return NS_ERROR_NO_CONTENT; +} + +size_t ScalarBase::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + return mStoreHasValue.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +/** + * The implementation for the unsigned int scalar type. + */ +class ScalarUnsigned : public ScalarBase { + public: + using ScalarBase::SetValue; + + explicit ScalarUnsigned(const BaseScalarInfo& aInfo) + : ScalarBase(aInfo), mStorage(aInfo.storeCount()) { + mStorage.SetLength(aInfo.storeCount()); + for (auto& val : mStorage) { + val = 0; + } + }; + + ~ScalarUnsigned() override = default; + + ScalarResult SetValue(nsIVariant* aValue) final; + void SetValue(uint32_t aValue) final; + ScalarResult AddValue(nsIVariant* aValue) final; + void AddValue(uint32_t aValue) final; + ScalarResult SetMaximum(nsIVariant* aValue) final; + void SetMaximum(uint32_t aValue) final; + nsresult GetValue(const nsACString& aStoreName, bool aClearStore, + nsCOMPtr<nsIVariant>& aResult) final; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final; + + private: + nsTArray<uint32_t> mStorage; + + ScalarResult CheckInput(nsIVariant* aValue); + + // Prevent copying. + ScalarUnsigned(const ScalarUnsigned& aOther) = delete; + void operator=(const ScalarUnsigned& aOther) = delete; +}; + +ScalarResult ScalarUnsigned::SetValue(nsIVariant* aValue) { + ScalarResult sr = CheckInput(aValue); + if (sr == ScalarResult::UnsignedNegativeValue) { + return sr; + } + + uint32_t value = 0; + if (NS_FAILED(aValue->GetAsUint32(&value))) { + return ScalarResult::InvalidValue; + } + + SetValue(value); + return sr; +} + +void ScalarUnsigned::SetValue(uint32_t aValue) { + if (GetCurrentProduct() == SupportedProduct::GeckoviewStreaming) { + GeckoViewStreamingTelemetry::UintScalarSet(mName, aValue); + return; + } + for (auto& val : mStorage) { + val = aValue; + } + SetValueInStores(); +} + +ScalarResult ScalarUnsigned::AddValue(nsIVariant* aValue) { + ScalarResult sr = CheckInput(aValue); + if (sr == ScalarResult::UnsignedNegativeValue) { + return sr; + } + + uint32_t newAddend = 0; + nsresult rv = aValue->GetAsUint32(&newAddend); + if (NS_FAILED(rv)) { + return ScalarResult::InvalidValue; + } + + AddValue(newAddend); + return sr; +} + +void ScalarUnsigned::AddValue(uint32_t aValue) { + MOZ_ASSERT(GetCurrentProduct() != SupportedProduct::GeckoviewStreaming); + for (auto& val : mStorage) { + val += aValue; + } + SetValueInStores(); +} + +ScalarResult ScalarUnsigned::SetMaximum(nsIVariant* aValue) { + MOZ_ASSERT(GetCurrentProduct() != SupportedProduct::GeckoviewStreaming); + ScalarResult sr = CheckInput(aValue); + if (sr == ScalarResult::UnsignedNegativeValue) { + return sr; + } + + uint32_t newValue = 0; + nsresult rv = aValue->GetAsUint32(&newValue); + if (NS_FAILED(rv)) { + return ScalarResult::InvalidValue; + } + + SetMaximum(newValue); + return sr; +} + +void ScalarUnsigned::SetMaximum(uint32_t aValue) { + for (auto& val : mStorage) { + if (aValue > val) { + val = aValue; + } + } + SetValueInStores(); +} + +nsresult ScalarUnsigned::GetValue(const nsACString& aStoreName, + bool aClearStore, + nsCOMPtr<nsIVariant>& aResult) { + size_t storeIndex = 0; + nsresult rv = StoreIndex(aStoreName, &storeIndex); + if (NS_FAILED(rv)) { + return rv; + } + if (!HasValueInStore(storeIndex)) { + return NS_ERROR_NO_CONTENT; + } + nsCOMPtr<nsIWritableVariant> outVar(new nsVariant()); + rv = outVar->SetAsUint32(mStorage[storeIndex]); + if (NS_FAILED(rv)) { + return rv; + } + aResult = std::move(outVar); + if (aClearStore) { + mStorage[storeIndex] = 0; + ClearValueInStore(storeIndex); + } + return NS_OK; +} + +size_t ScalarUnsigned::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + n += ScalarBase::SizeOfExcludingThis(aMallocSizeOf); + n += mStorage.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; +} + +ScalarResult ScalarUnsigned::CheckInput(nsIVariant* aValue) { + // If this is a floating point value/double, we will probably get truncated. + uint16_t type = aValue->GetDataType(); + if (type == nsIDataType::VTYPE_FLOAT || type == nsIDataType::VTYPE_DOUBLE) { + return ScalarResult::UnsignedTruncatedValue; + } + + int32_t signedTest; + // If we're able to cast the number to an int, check its sign. + // Warn the user if he's trying to set the unsigned scalar to a negative + // number. + if (NS_SUCCEEDED(aValue->GetAsInt32(&signedTest)) && signedTest < 0) { + return ScalarResult::UnsignedNegativeValue; + } + return ScalarResult::Ok; +} + +/** + * The implementation for the string scalar type. + */ +class ScalarString : public ScalarBase { + public: + using ScalarBase::SetValue; + + explicit ScalarString(const BaseScalarInfo& aInfo) + : ScalarBase(aInfo), mStorage(aInfo.storeCount()) { + mStorage.SetLength(aInfo.storeCount()); + }; + + ~ScalarString() override = default; + + ScalarResult SetValue(nsIVariant* aValue) final; + ScalarResult SetValue(const nsAString& aValue) final; + nsresult GetValue(const nsACString& aStoreName, bool aClearStore, + nsCOMPtr<nsIVariant>& aResult) final; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final; + + private: + nsTArray<nsString> mStorage; + + // Prevent copying. + ScalarString(const ScalarString& aOther) = delete; + void operator=(const ScalarString& aOther) = delete; +}; + +ScalarResult ScalarString::SetValue(nsIVariant* aValue) { + // Check that we got the correct data type. + uint16_t type = aValue->GetDataType(); + if (type != nsIDataType::VTYPE_CHAR && type != nsIDataType::VTYPE_WCHAR && + type != nsIDataType::VTYPE_CHAR_STR && + type != nsIDataType::VTYPE_WCHAR_STR && + type != nsIDataType::VTYPE_STRING_SIZE_IS && + type != nsIDataType::VTYPE_WSTRING_SIZE_IS && + type != nsIDataType::VTYPE_UTF8STRING && + type != nsIDataType::VTYPE_CSTRING && + type != nsIDataType::VTYPE_ASTRING) { + return ScalarResult::InvalidType; + } + + nsAutoString convertedString; + nsresult rv = aValue->GetAsAString(convertedString); + if (NS_FAILED(rv)) { + return ScalarResult::InvalidValue; + } + return SetValue(convertedString); +}; + +ScalarResult ScalarString::SetValue(const nsAString& aValue) { + auto str = Substring(aValue, 0, kMaximumStringValueLength); + if (GetCurrentProduct() == SupportedProduct::GeckoviewStreaming) { + GeckoViewStreamingTelemetry::StringScalarSet(mName, + NS_ConvertUTF16toUTF8(str)); + return aValue.Length() > kMaximumStringValueLength + ? ScalarResult::StringTooLong + : ScalarResult::Ok; + } + for (auto& val : mStorage) { + val.Assign(str); + } + SetValueInStores(); + if (aValue.Length() > kMaximumStringValueLength) { + return ScalarResult::StringTooLong; + } + return ScalarResult::Ok; +} + +nsresult ScalarString::GetValue(const nsACString& aStoreName, bool aClearStore, + nsCOMPtr<nsIVariant>& aResult) { + nsCOMPtr<nsIWritableVariant> outVar(new nsVariant()); + size_t storeIndex = 0; + nsresult rv = StoreIndex(aStoreName, &storeIndex); + if (NS_FAILED(rv)) { + return rv; + } + if (!HasValueInStore(storeIndex)) { + return NS_ERROR_NO_CONTENT; + } + rv = outVar->SetAsAString(mStorage[storeIndex]); + if (NS_FAILED(rv)) { + return rv; + } + if (aClearStore) { + ClearValueInStore(storeIndex); + } + aResult = std::move(outVar); + return NS_OK; +} + +size_t ScalarString::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + n += ScalarBase::SizeOfExcludingThis(aMallocSizeOf); + n += mStorage.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto& val : mStorage) { + n += val.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + return n; +} + +/** + * The implementation for the boolean scalar type. + */ +class ScalarBoolean : public ScalarBase { + public: + using ScalarBase::SetValue; + + explicit ScalarBoolean(const BaseScalarInfo& aInfo) + : ScalarBase(aInfo), mStorage(aInfo.storeCount()) { + mStorage.SetLength(aInfo.storeCount()); + for (auto& val : mStorage) { + val = false; + } + }; + + ~ScalarBoolean() override = default; + + ScalarResult SetValue(nsIVariant* aValue) final; + void SetValue(bool aValue) final; + nsresult GetValue(const nsACString& aStoreName, bool aClearStore, + nsCOMPtr<nsIVariant>& aResult) final; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final; + + private: + nsTArray<bool> mStorage; + + // Prevent copying. + ScalarBoolean(const ScalarBoolean& aOther) = delete; + void operator=(const ScalarBoolean& aOther) = delete; +}; + +ScalarResult ScalarBoolean::SetValue(nsIVariant* aValue) { + // Check that we got the correct data type. + uint16_t type = aValue->GetDataType(); + if (type != nsIDataType::VTYPE_BOOL && type != nsIDataType::VTYPE_INT8 && + type != nsIDataType::VTYPE_INT16 && type != nsIDataType::VTYPE_INT32 && + type != nsIDataType::VTYPE_INT64 && type != nsIDataType::VTYPE_UINT8 && + type != nsIDataType::VTYPE_UINT16 && type != nsIDataType::VTYPE_UINT32 && + type != nsIDataType::VTYPE_UINT64) { + return ScalarResult::InvalidType; + } + + bool value = false; + if (NS_FAILED(aValue->GetAsBool(&value))) { + return ScalarResult::InvalidValue; + } + SetValue(value); + return ScalarResult::Ok; +}; + +void ScalarBoolean::SetValue(bool aValue) { + if (GetCurrentProduct() == SupportedProduct::GeckoviewStreaming) { + GeckoViewStreamingTelemetry::BoolScalarSet(mName, aValue); + return; + } + for (auto& val : mStorage) { + val = aValue; + } + SetValueInStores(); +} + +nsresult ScalarBoolean::GetValue(const nsACString& aStoreName, bool aClearStore, + nsCOMPtr<nsIVariant>& aResult) { + nsCOMPtr<nsIWritableVariant> outVar(new nsVariant()); + size_t storeIndex = 0; + nsresult rv = StoreIndex(aStoreName, &storeIndex); + if (NS_FAILED(rv)) { + return rv; + } + if (!HasValueInStore(storeIndex)) { + return NS_ERROR_NO_CONTENT; + } + if (aClearStore) { + ClearValueInStore(storeIndex); + } + rv = outVar->SetAsBool(mStorage[storeIndex]); + if (NS_FAILED(rv)) { + return rv; + } + aResult = std::move(outVar); + return NS_OK; +} + +size_t ScalarBoolean::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + n += ScalarBase::SizeOfExcludingThis(aMallocSizeOf); + n += mStorage.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; +} + +/** + * Allocate a scalar class given the scalar info. + * + * @param aInfo The informations for the scalar coming from the definition file. + * @return nullptr if the scalar type is unknown, otherwise a valid pointer to + * the scalar type. + */ +ScalarBase* internal_ScalarAllocate(const BaseScalarInfo& aInfo) { + ScalarBase* scalar = nullptr; + switch (aInfo.kind) { + case nsITelemetry::SCALAR_TYPE_COUNT: + scalar = new ScalarUnsigned(aInfo); + break; + case nsITelemetry::SCALAR_TYPE_STRING: + scalar = new ScalarString(aInfo); + break; + case nsITelemetry::SCALAR_TYPE_BOOLEAN: + scalar = new ScalarBoolean(aInfo); + break; + default: + MOZ_ASSERT(false, "Invalid scalar type"); + } + return scalar; +} + +/** + * The implementation for the keyed scalar type. + */ +class KeyedScalar { + public: + typedef std::pair<nsCString, nsCOMPtr<nsIVariant>> KeyValuePair; + + // We store the name instead of a reference to the BaseScalarInfo because + // the BaseScalarInfo can move if it's from a dynamic scalar. + explicit KeyedScalar(const BaseScalarInfo& info) + : mScalarName(info.name()), + mScalarKeyCount(info.key_count), + mScalarKeyOffset(info.key_offset), + mMaximumNumberOfKeys(kMaximumNumberOfKeys){}; + ~KeyedScalar() = default; + + // Set, Add and SetMaximum functions as described in the Telemetry IDL. + // These methods implicitly instantiate a Scalar[*] for each key. + ScalarResult SetValue(const StaticMutexAutoLock& locker, + const nsAString& aKey, nsIVariant* aValue); + ScalarResult AddValue(const StaticMutexAutoLock& locker, + const nsAString& aKey, nsIVariant* aValue); + ScalarResult SetMaximum(const StaticMutexAutoLock& locker, + const nsAString& aKey, nsIVariant* aValue); + + // Convenience methods used by the C++ API. + void SetValue(const StaticMutexAutoLock& locker, const nsAString& aKey, + uint32_t aValue); + void SetValue(const StaticMutexAutoLock& locker, const nsAString& aKey, + bool aValue); + void AddValue(const StaticMutexAutoLock& locker, const nsAString& aKey, + uint32_t aValue); + void SetMaximum(const StaticMutexAutoLock& locker, const nsAString& aKey, + uint32_t aValue); + + // GetValue is used to get the key-value pairs stored in the keyed scalar + // when persisting it to JS. + nsresult GetValue(const nsACString& aStoreName, bool aClearStorage, + nsTArray<KeyValuePair>& aValues); + + // To measure the memory stats. + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + // To permit more keys than normal. + void SetMaximumNumberOfKeys(uint32_t aMaximumNumberOfKeys) { + mMaximumNumberOfKeys = aMaximumNumberOfKeys; + }; + + private: + typedef nsClassHashtable<nsCStringHashKey, ScalarBase> ScalarKeysMapType; + + const nsCString mScalarName; + ScalarKeysMapType mScalarKeys; + uint32_t mScalarKeyCount; + uint32_t mScalarKeyOffset; + uint32_t mMaximumNumberOfKeys; + + ScalarResult GetScalarForKey(const StaticMutexAutoLock& locker, + const nsAString& aKey, ScalarBase** aRet); + + bool AllowsKey(const nsAString& aKey) const; +}; + +ScalarResult KeyedScalar::SetValue(const StaticMutexAutoLock& locker, + const nsAString& aKey, nsIVariant* aValue) { + ScalarBase* scalar = nullptr; + ScalarResult sr = GetScalarForKey(locker, aKey, &scalar); + if (sr != ScalarResult::Ok) { + return sr; + } + + return scalar->SetValue(aValue); +} + +ScalarResult KeyedScalar::AddValue(const StaticMutexAutoLock& locker, + const nsAString& aKey, nsIVariant* aValue) { + ScalarBase* scalar = nullptr; + ScalarResult sr = GetScalarForKey(locker, aKey, &scalar); + if (sr != ScalarResult::Ok) { + return sr; + } + + return scalar->AddValue(aValue); +} + +ScalarResult KeyedScalar::SetMaximum(const StaticMutexAutoLock& locker, + const nsAString& aKey, + nsIVariant* aValue) { + ScalarBase* scalar = nullptr; + ScalarResult sr = GetScalarForKey(locker, aKey, &scalar); + if (sr != ScalarResult::Ok) { + return sr; + } + + return scalar->SetMaximum(aValue); +} + +void KeyedScalar::SetValue(const StaticMutexAutoLock& locker, + const nsAString& aKey, uint32_t aValue) { + ScalarBase* scalar = nullptr; + ScalarResult sr = GetScalarForKey(locker, aKey, &scalar); + + if (sr != ScalarResult::Ok) { + // Bug 1451813 - We now report which scalars exceed the key limit in + // telemetry.keyed_scalars_exceed_limit. + if (sr == ScalarResult::KeyTooLong) { + MOZ_ASSERT(false, "Key too long to be recorded in the scalar."); + } + return; + } + + return scalar->SetValue(aValue); +} + +void KeyedScalar::SetValue(const StaticMutexAutoLock& locker, + const nsAString& aKey, bool aValue) { + ScalarBase* scalar = nullptr; + ScalarResult sr = GetScalarForKey(locker, aKey, &scalar); + + if (sr != ScalarResult::Ok) { + // Bug 1451813 - We now report which scalars exceed the key limit in + // telemetry.keyed_scalars_exceed_limit. + if (sr == ScalarResult::KeyTooLong) { + MOZ_ASSERT(false, "Key too long to be recorded in the scalar."); + } + return; + } + + return scalar->SetValue(aValue); +} + +void KeyedScalar::AddValue(const StaticMutexAutoLock& locker, + const nsAString& aKey, uint32_t aValue) { + ScalarBase* scalar = nullptr; + ScalarResult sr = GetScalarForKey(locker, aKey, &scalar); + + if (sr != ScalarResult::Ok) { + // Bug 1451813 - We now report which scalars exceed the key limit in + // telemetry.keyed_scalars_exceed_limit. + if (sr == ScalarResult::KeyTooLong) { + MOZ_ASSERT(false, "Key too long to be recorded in the scalar."); + } + return; + } + + return scalar->AddValue(aValue); +} + +void KeyedScalar::SetMaximum(const StaticMutexAutoLock& locker, + const nsAString& aKey, uint32_t aValue) { + ScalarBase* scalar = nullptr; + ScalarResult sr = GetScalarForKey(locker, aKey, &scalar); + + if (sr != ScalarResult::Ok) { + // Bug 1451813 - We now report which scalars exceed the key limit in + // telemetry.keyed_scalars_exceed_limit. + if (sr == ScalarResult::KeyTooLong) { + MOZ_ASSERT(false, "Key too long to be recorded in the scalar."); + } + + return; + } + + return scalar->SetMaximum(aValue); +} + +/** + * Get a key-value array with the values for the Keyed Scalar. + * @param aValue The array that will hold the key-value pairs. + * @return {nsresult} NS_OK or an error value as reported by the + * the specific scalar objects implementations (e.g. + * ScalarUnsigned). + */ +nsresult KeyedScalar::GetValue(const nsACString& aStoreName, bool aClearStorage, + nsTArray<KeyValuePair>& aValues) { + for (const auto& entry : mScalarKeys) { + ScalarBase* scalar = entry.GetWeak(); + + // Get the scalar value. + nsCOMPtr<nsIVariant> scalarValue; + nsresult rv = scalar->GetValue(aStoreName, aClearStorage, scalarValue); + if (rv == NS_ERROR_NO_CONTENT) { + // No value for this store. + continue; + } + if (NS_FAILED(rv)) { + return rv; + } + + // Append it to value list. + aValues.AppendElement( + std::make_pair(nsCString(entry.GetKey()), scalarValue)); + } + + return NS_OK; +} + +// Forward declaration +nsresult internal_GetKeyedScalarByEnum(const StaticMutexAutoLock& lock, + const ScalarKey& aId, + ProcessID aProcessStorage, + KeyedScalar** aRet); + +// Forward declaration +nsresult internal_GetEnumByScalarName(const StaticMutexAutoLock& lock, + const nsACString& aName, ScalarKey* aId); + +/** + * Get the scalar for the referenced key. + * If there's no such key, instantiate a new Scalar object with the + * same type of the Keyed scalar and create the key. + */ +ScalarResult KeyedScalar::GetScalarForKey(const StaticMutexAutoLock& locker, + const nsAString& aKey, + ScalarBase** aRet) { + if (aKey.IsEmpty()) { + return ScalarResult::KeyIsEmpty; + } + + if (!AllowsKey(aKey)) { + KeyedScalar* scalarUnknown = nullptr; + ScalarKey scalarUnknownUniqueId{ + static_cast<uint32_t>( + mozilla::Telemetry::ScalarID::TELEMETRY_KEYED_SCALARS_UNKNOWN_KEYS), + false}; + ProcessID process = ProcessID::Parent; + nsresult rv = internal_GetKeyedScalarByEnum(locker, scalarUnknownUniqueId, + process, &scalarUnknown); + if (NS_FAILED(rv)) { + return ScalarResult::TooManyKeys; + } + scalarUnknown->AddValue(locker, NS_ConvertUTF8toUTF16(mScalarName), 1); + + return ScalarResult::KeyNotAllowed; + } + + if (aKey.Length() > kMaximumKeyStringLength) { + return ScalarResult::KeyTooLong; + } + + NS_ConvertUTF16toUTF8 utf8Key(aKey); + + ScalarBase* scalar = nullptr; + if (mScalarKeys.Get(utf8Key, &scalar)) { + *aRet = scalar; + return ScalarResult::Ok; + } + + ScalarKey uniqueId; + nsresult rv = internal_GetEnumByScalarName(locker, mScalarName, &uniqueId); + if (NS_FAILED(rv)) { + return (rv == NS_ERROR_FAILURE) ? ScalarResult::NotInitialized + : ScalarResult::UnknownScalar; + } + + const BaseScalarInfo& info = internal_GetScalarInfo(locker, uniqueId); + if (mScalarKeys.Count() >= mMaximumNumberOfKeys) { + if (aKey.EqualsLiteral("telemetry.keyed_scalars_exceed_limit")) { + return ScalarResult::TooManyKeys; + } + + KeyedScalar* scalarExceed = nullptr; + + ScalarKey uniqueId{ + static_cast<uint32_t>( + mozilla::Telemetry::ScalarID::TELEMETRY_KEYED_SCALARS_EXCEED_LIMIT), + false}; + + ProcessID process = ProcessID::Parent; + nsresult rv = + internal_GetKeyedScalarByEnum(locker, uniqueId, process, &scalarExceed); + + if (NS_FAILED(rv)) { + return ScalarResult::TooManyKeys; + } + + scalarExceed->AddValue(locker, NS_ConvertUTF8toUTF16(info.name()), 1); + + return ScalarResult::TooManyKeys; + } + + scalar = internal_ScalarAllocate(info); + if (!scalar) { + return ScalarResult::InvalidType; + } + + mScalarKeys.InsertOrUpdate(utf8Key, UniquePtr<ScalarBase>(scalar)); + + *aRet = scalar; + return ScalarResult::Ok; +} + +size_t KeyedScalar::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + size_t n = aMallocSizeOf(this); + for (const auto& scalar : mScalarKeys.Values()) { + n += scalar->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +bool KeyedScalar::AllowsKey(const nsAString& aKey) const { + // If we didn't specify a list of allowed keys, just return true. + if (mScalarKeyCount == 0) { + return true; + } + + for (uint32_t i = 0; i < mScalarKeyCount; ++i) { + uint32_t stringIndex = gScalarKeysTable[mScalarKeyOffset + i]; + if (aKey.EqualsASCII(&gScalarsStringTable[stringIndex])) { + return true; + } + } + + return false; +} + +typedef nsUint32HashKey ScalarIDHashKey; +typedef nsUint32HashKey ProcessIDHashKey; +typedef nsClassHashtable<ScalarIDHashKey, ScalarBase> ScalarStorageMapType; +typedef nsClassHashtable<ScalarIDHashKey, KeyedScalar> + KeyedScalarStorageMapType; +typedef nsClassHashtable<ProcessIDHashKey, ScalarStorageMapType> + ProcessesScalarsMapType; +typedef nsClassHashtable<ProcessIDHashKey, KeyedScalarStorageMapType> + ProcessesKeyedScalarsMapType; + +typedef mozilla::Tuple<const char*, nsCOMPtr<nsIVariant>, uint32_t> + ScalarDataTuple; +typedef nsTArray<ScalarDataTuple> ScalarTupleArray; +typedef nsTHashMap<ProcessIDHashKey, ScalarTupleArray> ScalarSnapshotTable; + +typedef mozilla::Tuple<const char*, nsTArray<KeyedScalar::KeyValuePair>, + uint32_t> + KeyedScalarDataTuple; +typedef nsTArray<KeyedScalarDataTuple> KeyedScalarTupleArray; +typedef nsTHashMap<ProcessIDHashKey, KeyedScalarTupleArray> + KeyedScalarSnapshotTable; + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE STATE, SHARED BY ALL THREADS + +namespace { + +// Set to true once this global state has been initialized. +bool gInitDone = false; + +bool gCanRecordBase; +bool gCanRecordExtended; + +// The Name -> ID cache map. +ScalarMapType gScalarNameIDMap(kScalarCount); + +// The (Process Id -> (Scalar ID -> Scalar Object)) map. This is a +// nsClassHashtable, it owns the scalar instances and takes care of deallocating +// them when they are removed from the map. +ProcessesScalarsMapType gScalarStorageMap; +// As above, for the keyed scalars. +ProcessesKeyedScalarsMapType gKeyedScalarStorageMap; +// Provide separate storage for "dynamic builtin" plain and keyed scalars, +// needed to support "build faster" in local developer builds. +ProcessesScalarsMapType gDynamicBuiltinScalarStorageMap; +ProcessesKeyedScalarsMapType gDynamicBuiltinKeyedScalarStorageMap; + +// Whether or not the deserialization of persisted scalars is still in progress. +// This is never the case on Desktop or Fennec. +// Only GeckoView restores persisted scalars. +bool gIsDeserializing = false; +// This batches scalar accumulations that should be applied once loading +// finished. +StaticAutoPtr<nsTArray<ScalarAction>> gScalarsActions; +StaticAutoPtr<nsTArray<KeyedScalarAction>> gKeyedScalarsActions; + +bool internal_IsScalarDeserializing(const StaticMutexAutoLock& lock) { + return gIsDeserializing; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: Function that may call JS code. + +// NOTE: the functions in this section all run without protection from +// |gTelemetryScalarsMutex|. If they held the mutex, there would be the +// possibility of deadlock because the JS_ calls that they make may call +// back into the TelemetryScalar interface, hence trying to re-acquire the +// mutex. +// +// This means that these functions potentially race against threads, but +// that seems preferable to risking deadlock. + +namespace { + +/** + * Converts the error code to a human readable error message and prints it to + * the browser console. + * + * @param aScalarName The name of the scalar that raised the error. + * @param aSr The error code. + */ +void internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr) { + nsAutoString errorMessage; + AppendUTF8toUTF16(aScalarName, errorMessage); + + switch (aSr) { + case ScalarResult::NotInitialized: + errorMessage.AppendLiteral(u" - Telemetry was not yet initialized."); + break; + case ScalarResult::CannotUnpackVariant: + errorMessage.AppendLiteral( + u" - Cannot convert the provided JS value to nsIVariant."); + break; + case ScalarResult::CannotRecordInProcess: + errorMessage.AppendLiteral( + u" - Cannot record the scalar in the current process."); + break; + case ScalarResult::KeyedTypeMismatch: + errorMessage.AppendLiteral( + u" - Attempting to manage a keyed scalar as a scalar (or " + u"vice-versa)."); + break; + case ScalarResult::UnknownScalar: + errorMessage.AppendLiteral(u" - Unknown scalar."); + break; + case ScalarResult::OperationNotSupported: + errorMessage.AppendLiteral( + u" - The requested operation is not supported on this scalar."); + break; + case ScalarResult::InvalidType: + errorMessage.AppendLiteral( + u" - Attempted to set the scalar to an invalid data type."); + break; + case ScalarResult::InvalidValue: + errorMessage.AppendLiteral( + u" - Attempted to set the scalar to an incompatible value."); + break; + case ScalarResult::StringTooLong: + AppendUTF8toUTF16( + nsPrintfCString(" - Truncating scalar value to %d characters.", + kMaximumStringValueLength), + errorMessage); + break; + case ScalarResult::KeyIsEmpty: + errorMessage.AppendLiteral(u" - The key must not be empty."); + break; + case ScalarResult::KeyTooLong: + AppendUTF8toUTF16( + nsPrintfCString(" - The key length must be limited to %d characters.", + kMaximumKeyStringLength), + errorMessage); + break; + case ScalarResult::KeyNotAllowed: + AppendUTF8toUTF16( + nsPrintfCString(" - The key is not allowed for this scalar."), + errorMessage); + break; + case ScalarResult::TooManyKeys: + AppendUTF8toUTF16( + nsPrintfCString(" - Keyed scalars cannot have more than %d keys.", + kMaximumNumberOfKeys), + errorMessage); + break; + case ScalarResult::UnsignedNegativeValue: + errorMessage.AppendLiteral( + u" - Trying to set an unsigned scalar to a negative number."); + break; + case ScalarResult::UnsignedTruncatedValue: + errorMessage.AppendLiteral(u" - Truncating float/double number."); + break; + default: + // Nothing. + return; + } + + LogToBrowserConsole(nsIScriptError::warningFlag, errorMessage); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: helpers for the external interface + +namespace { + +bool internal_CanRecordBase(const StaticMutexAutoLock& lock) { + return gCanRecordBase; +} + +bool internal_CanRecordExtended(const StaticMutexAutoLock& lock) { + return gCanRecordExtended; +} + +/** + * Check if the given scalar is a keyed scalar. + * + * @param lock Instance of a lock locking gTelemetryHistogramMutex + * @param aId The scalar identifier. + * @return true if aId refers to a keyed scalar, false otherwise. + */ +bool internal_IsKeyedScalar(const StaticMutexAutoLock& lock, + const ScalarKey& aId) { + return internal_GetScalarInfo(lock, aId).keyed; +} + +/** + * Check if we're allowed to record the given scalar in the current + * process. + * + * @param lock Instance of a lock locking gTelemetryHistogramMutex + * @param aId The scalar identifier. + * @return true if the scalar is allowed to be recorded in the current process, + * false otherwise. + */ +bool internal_CanRecordProcess(const StaticMutexAutoLock& lock, + const ScalarKey& aId) { + const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId); + return CanRecordInProcess(info.record_in_processes, XRE_GetProcessType()); +} + +bool internal_CanRecordProduct(const StaticMutexAutoLock& lock, + const ScalarKey& aId) { + const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId); + return CanRecordProduct(info.products); +} + +bool internal_CanRecordForScalarID(const StaticMutexAutoLock& lock, + const ScalarKey& aId) { + // Get the scalar info from the id. + const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId); + + // Can we record at all? + bool canRecordBase = internal_CanRecordBase(lock); + if (!canRecordBase) { + return false; + } + + bool canRecordDataset = CanRecordDataset(info.dataset, canRecordBase, + internal_CanRecordExtended(lock)); + if (!canRecordDataset) { + return false; + } + + return true; +} + +/** + * Check if we are allowed to record the provided scalar. + * + * @param lock Instance of a lock locking gTelemetryHistogramMutex + * @param aId The scalar identifier. + * @param aKeyed Are we attempting to write a keyed scalar? + * @param aForce Whether to allow recording even if the probe is not allowed on + * the current process. + * This must only be true for GeckoView persistence and recorded + * actions. + * @return ScalarResult::Ok if we can record, an error code otherwise. + */ +ScalarResult internal_CanRecordScalar(const StaticMutexAutoLock& lock, + const ScalarKey& aId, bool aKeyed, + bool aForce = false) { + // Make sure that we have a keyed scalar if we are trying to change one. + if (internal_IsKeyedScalar(lock, aId) != aKeyed) { + return ScalarResult::KeyedTypeMismatch; + } + + // Are we allowed to record this scalar based on the current Telemetry + // settings? + if (!internal_CanRecordForScalarID(lock, aId)) { + return ScalarResult::CannotRecordDataset; + } + + // Can we record in this process? + if (!aForce && !internal_CanRecordProcess(lock, aId)) { + return ScalarResult::CannotRecordInProcess; + } + + // Can we record on this product? + if (!internal_CanRecordProduct(lock, aId)) { + return ScalarResult::CannotRecordDataset; + } + + return ScalarResult::Ok; +} + +/** + * Get the scalar enum id from the scalar name. + * + * @param lock Instance of a lock locking gTelemetryHistogramMutex + * @param aName The scalar name. + * @param aId The output variable to contain the enum. + * @return + * NS_ERROR_FAILURE if this was called before init is completed. + * NS_ERROR_INVALID_ARG if the name can't be found in the scalar definitions. + * NS_OK if the scalar was found and aId contains a valid enum id. + */ +nsresult internal_GetEnumByScalarName(const StaticMutexAutoLock& lock, + const nsACString& aName, ScalarKey* aId) { + if (!gInitDone) { + return NS_ERROR_FAILURE; + } + + CharPtrEntryType* entry = + gScalarNameIDMap.GetEntry(PromiseFlatCString(aName).get()); + if (!entry) { + return NS_ERROR_INVALID_ARG; + } + *aId = entry->GetData(); + return NS_OK; +} + +/** + * Get a scalar object by its enum id. This implicitly allocates the scalar + * object in the storage if it wasn't previously allocated. + * + * @param lock Instance of a lock locking gTelemetryHistogramMutex + * @param aId The scalar identifier. + * @param aProcessStorage This drives the selection of the map to use to store + * the scalar data coming from child processes. This is only meaningful + * when this function is called in parent process. If that's the case, + * if this is not |GeckoProcessType_Default|, the process id is used to + * allocate and store the scalars. + * @param aRes The output variable that stores scalar object. + * @return + * NS_ERROR_INVALID_ARG if the scalar id is unknown. + * NS_ERROR_NOT_AVAILABLE if the scalar is expired. + * NS_OK if the scalar was found. If that's the case, aResult contains a + * valid pointer to a scalar type. + */ +nsresult internal_GetScalarByEnum(const StaticMutexAutoLock& lock, + const ScalarKey& aId, + ProcessID aProcessStorage, + ScalarBase** aRet) { + if (!internal_IsValidId(lock, aId)) { + MOZ_ASSERT(false, "Requested a scalar with an invalid id."); + return NS_ERROR_INVALID_ARG; + } + + const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId); + + // Dynamic scalars fixup: they are always stored in the "dynamic" process, + // unless they are part of the "builtin" Firefox probes. Please note that + // "dynamic builtin" probes are meant to support "artifact" and "build faster" + // builds. + if (aId.dynamic && !info.builtin) { + aProcessStorage = ProcessID::Dynamic; + } + + ScalarBase* scalar = nullptr; + // Initialize the scalar storage to the parent storage. This will get + // set to the child storage if needed. + uint32_t storageId = static_cast<uint32_t>(aProcessStorage); + + // Put dynamic-builtin scalars (used to support "build faster") in a + // separate storage. + ProcessesScalarsMapType& processStorage = + (aId.dynamic && info.builtin) ? gDynamicBuiltinScalarStorageMap + : gScalarStorageMap; + + // Get the process-specific storage or create one if it's not + // available. + ScalarStorageMapType* const scalarStorage = + processStorage.GetOrInsertNew(storageId); + + // Check if the scalar is already allocated in the parent or in the child + // storage. + if (scalarStorage->Get(aId.id, &scalar)) { + // Dynamic scalars can expire at any time during the session (e.g. an + // add-on was updated). Check if it expired. + if (aId.dynamic) { + const DynamicScalarInfo& dynInfo = + static_cast<const DynamicScalarInfo&>(info); + if (dynInfo.mDynamicExpiration) { + // The Dynamic scalar is expired. + return NS_ERROR_NOT_AVAILABLE; + } + } + // This was not a dynamic scalar or was not expired. + *aRet = scalar; + return NS_OK; + } + + // The scalar storage wasn't already allocated. Check if the scalar is expired + // and then allocate the storage, if needed. + if (IsExpiredVersion(info.expiration())) { + return NS_ERROR_NOT_AVAILABLE; + } + + scalar = internal_ScalarAllocate(info); + if (!scalar) { + return NS_ERROR_INVALID_ARG; + } + + scalarStorage->InsertOrUpdate(aId.id, UniquePtr<ScalarBase>(scalar)); + *aRet = scalar; + return NS_OK; +} + +void internal_ApplyPendingOperations(const StaticMutexAutoLock& lock); + +/** + * Record the given action on a scalar into the pending actions list. + * + * If the pending actions list overflows the high water mark length + * all operations are immediately applied, including the passed action. + * + * @param aScalarAction The action to record. + */ +void internal_RecordScalarAction(const StaticMutexAutoLock& lock, + const ScalarAction& aScalarAction) { + // Make sure to have the storage. + if (!gScalarsActions) { + gScalarsActions = new nsTArray<ScalarAction>(); + } + + // Store the action. + gScalarsActions->AppendElement(aScalarAction); + + // If this action overflows the pending actions array, we immediately apply + // pending operations and assume loading is over. If loading still happens + // afterwards, some scalar values might be overwritten and inconsistent, but + // we won't lose operations on otherwise untouched probes. + if (gScalarsActions->Length() > kScalarActionsArrayHighWaterMark) { + internal_ApplyPendingOperations(lock); + return; + } +} + +/** + * Record the given action on a scalar on the main process into the pending + * actions list. + * + * If the pending actions list overflows the high water mark length + * all operations are immediately applied, including the passed action. + * + * @param aId The scalar's ID this action applies to + * @param aDynamic Determines if the scalar is dynamic + * @param aAction The action to record + * @param aValue The additional data for the recorded action + */ +void internal_RecordScalarAction(const StaticMutexAutoLock& lock, uint32_t aId, + bool aDynamic, ScalarActionType aAction, + const ScalarVariant& aValue) { + internal_RecordScalarAction( + lock, + ScalarAction{aId, aDynamic, aAction, Some(aValue), ProcessID::Parent}); +} + +/** + * Record the given action on a keyed scalar into the pending actions list. + * + * If the pending actions list overflows the high water mark length + * all operations are immediately applied, including the passed action. + * + * @param aScalarAction The action to record. + */ +void internal_RecordKeyedScalarAction(const StaticMutexAutoLock& lock, + const KeyedScalarAction& aScalarAction) { + // Make sure to have the storage. + if (!gKeyedScalarsActions) { + gKeyedScalarsActions = new nsTArray<KeyedScalarAction>(); + } + + // Store the action. + gKeyedScalarsActions->AppendElement(aScalarAction); + + // If this action overflows the pending actions array, we immediately apply + // pending operations and assume loading is over. If loading still happens + // afterwards, some scalar values might be overwritten and inconsistent, but + // we won't lose operations on otherwise untouched probes. + if (gKeyedScalarsActions->Length() > kScalarActionsArrayHighWaterMark) { + internal_ApplyPendingOperations(lock); + return; + } +} + +/** + * Record the given action on a keyed scalar on the main process into the + * pending actions list. + * + * If the pending actions list overflows the high water mark length + * all operations are immediately applied, including the passed action. + * + * @param aId The scalar's ID this action applies to + * @param aDynamic Determines if the scalar is dynamic + * @param aKey The scalar's key + * @param aAction The action to record + * @param aValue The additional data for the recorded action + */ +void internal_RecordKeyedScalarAction(const StaticMutexAutoLock& lock, + uint32_t aId, bool aDynamic, + const nsAString& aKey, + ScalarActionType aAction, + const ScalarVariant& aValue) { + internal_RecordKeyedScalarAction( + lock, + KeyedScalarAction{aId, aDynamic, aAction, NS_ConvertUTF16toUTF8(aKey), + Some(aValue), ProcessID::Parent}); +} + +/** + * Update the scalar with the provided value. This is used by the JS API. + * + * @param lock Instance of a lock locking gTelemetryHistogramMutex + * @param aName The scalar name. + * @param aType The action type for updating the scalar. + * @param aValue The value to use for updating the scalar. + * @param aProcessOverride The process for which the scalar must be updated. + * This must only be used for GeckoView persistence. It must be + * set to the ProcessID::Parent for all the other cases. + * @param aForce Whether to force updating even if load is in progress. + * @return a ScalarResult error value. + */ +ScalarResult internal_UpdateScalar( + const StaticMutexAutoLock& lock, const nsACString& aName, + ScalarActionType aType, nsIVariant* aValue, + ProcessID aProcessOverride = ProcessID::Parent, bool aForce = false) { + ScalarKey uniqueId; + nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId); + if (NS_FAILED(rv)) { + return (rv == NS_ERROR_FAILURE) ? ScalarResult::NotInitialized + : ScalarResult::UnknownScalar; + } + + ScalarResult sr = internal_CanRecordScalar(lock, uniqueId, false, aForce); + if (sr != ScalarResult::Ok) { + if (sr == ScalarResult::CannotRecordDataset) { + return ScalarResult::Ok; + } + return sr; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + const BaseScalarInfo& info = internal_GetScalarInfo(lock, uniqueId); + // Convert the nsIVariant to a Variant. + mozilla::Maybe<ScalarVariant> variantValue; + sr = GetVariantFromIVariant(aValue, info.kind, variantValue); + if (sr != ScalarResult::Ok) { + MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant."); + return sr; + } + TelemetryIPCAccumulator::RecordChildScalarAction( + uniqueId.id, uniqueId.dynamic, aType, variantValue.ref()); + return ScalarResult::Ok; + } + + if (!aForce && internal_IsScalarDeserializing(lock)) { + const BaseScalarInfo& info = internal_GetScalarInfo(lock, uniqueId); + // Convert the nsIVariant to a Variant. + mozilla::Maybe<ScalarVariant> variantValue; + sr = GetVariantFromIVariant(aValue, info.kind, variantValue); + if (sr != ScalarResult::Ok) { + MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant."); + return sr; + } + internal_RecordScalarAction(lock, uniqueId.id, uniqueId.dynamic, aType, + variantValue.ref()); + return ScalarResult::Ok; + } + + // Finally get the scalar. + ScalarBase* scalar = nullptr; + rv = internal_GetScalarByEnum(lock, uniqueId, aProcessOverride, &scalar); + if (NS_FAILED(rv)) { + // Don't throw on expired scalars. + if (rv == NS_ERROR_NOT_AVAILABLE) { + return ScalarResult::Ok; + } + return ScalarResult::UnknownScalar; + } + + if (aType == ScalarActionType::eAdd) { + return scalar->AddValue(aValue); + } + if (aType == ScalarActionType::eSet) { + return scalar->SetValue(aValue); + } + + return scalar->SetMaximum(aValue); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: thread-unsafe helpers for the keyed scalars + +namespace { + +/** + * Get a keyed scalar object by its enum id. This implicitly allocates the keyed + * scalar object in the storage if it wasn't previously allocated. + * + * @param lock Instance of a lock locking gTelemetryHistogramMutex + * @param aId The scalar identifier. + * @param aProcessStorage This drives the selection of the map to use to store + * the scalar data coming from child processes. This is only meaningful + * when this function is called in parent process. If that's the case, + * if this is not |GeckoProcessType_Default|, the process id is used to + * allocate and store the scalars. + * @param aRet The output variable that stores scalar object. + * @return + * NS_ERROR_INVALID_ARG if the scalar id is unknown or a this is a keyed + * string scalar. + * NS_ERROR_NOT_AVAILABLE if the scalar is expired. + * NS_OK if the scalar was found. If that's the case, aResult contains a + * valid pointer to a scalar type. + */ +nsresult internal_GetKeyedScalarByEnum(const StaticMutexAutoLock& lock, + const ScalarKey& aId, + ProcessID aProcessStorage, + KeyedScalar** aRet) { + if (!internal_IsValidId(lock, aId)) { + MOZ_ASSERT(false, "Requested a keyed scalar with an invalid id."); + return NS_ERROR_INVALID_ARG; + } + + const BaseScalarInfo& info = internal_GetScalarInfo(lock, aId); + + // Dynamic scalars fixup: they are always stored in the "dynamic" process, + // unless they are part of the "builtin" Firefox probes. Please note that + // "dynamic builtin" probes are meant to support "artifact" and "build faster" + // builds. + if (aId.dynamic && !info.builtin) { + aProcessStorage = ProcessID::Dynamic; + } + + KeyedScalar* scalar = nullptr; + // Initialize the scalar storage to the parent storage. This will get + // set to the child storage if needed. + uint32_t storageId = static_cast<uint32_t>(aProcessStorage); + + // Put dynamic-builtin scalars (used to support "build faster") in a + // separate storage. + ProcessesKeyedScalarsMapType& processStorage = + (aId.dynamic && info.builtin) ? gDynamicBuiltinKeyedScalarStorageMap + : gKeyedScalarStorageMap; + + // Get the process-specific storage or create one if it's not + // available. + KeyedScalarStorageMapType* const scalarStorage = + processStorage.GetOrInsertNew(storageId); + + if (scalarStorage->Get(aId.id, &scalar)) { + *aRet = scalar; + return NS_OK; + } + + if (IsExpiredVersion(info.expiration())) { + return NS_ERROR_NOT_AVAILABLE; + } + + // We don't currently support keyed string scalars. Disable them. + if (info.kind == nsITelemetry::SCALAR_TYPE_STRING) { + MOZ_ASSERT(false, "Keyed string scalars are not currently supported."); + return NS_ERROR_INVALID_ARG; + } + + scalar = new KeyedScalar(info); + + scalarStorage->InsertOrUpdate(aId.id, UniquePtr<KeyedScalar>(scalar)); + *aRet = scalar; + return NS_OK; +} + +/** + * Update the keyed scalar with the provided value. This is used by the JS API. + * + * @param lock Instance of a lock locking gTelemetryHistogramMutex + * @param aName The scalar name. + * @param aKey The key name. + * @param aType The action type for updating the scalar. + * @param aValue The value to use for updating the scalar. + * @param aProcessOverride The process for which the scalar must be updated. + * This must only be used for GeckoView persistence. It must be + * set to the ProcessID::Parent for all the other cases. + * @return a ScalarResult error value. + */ +ScalarResult internal_UpdateKeyedScalar( + const StaticMutexAutoLock& lock, const nsACString& aName, + const nsAString& aKey, ScalarActionType aType, nsIVariant* aValue, + ProcessID aProcessOverride = ProcessID::Parent, bool aForce = false) { + ScalarKey uniqueId; + nsresult rv = internal_GetEnumByScalarName(lock, aName, &uniqueId); + if (NS_FAILED(rv)) { + return (rv == NS_ERROR_FAILURE) ? ScalarResult::NotInitialized + : ScalarResult::UnknownScalar; + } + + ScalarResult sr = internal_CanRecordScalar(lock, uniqueId, true, aForce); + if (sr != ScalarResult::Ok) { + if (sr == ScalarResult::CannotRecordDataset) { + return ScalarResult::Ok; + } + return sr; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + const BaseScalarInfo& info = internal_GetScalarInfo(lock, uniqueId); + // Convert the nsIVariant to a Variant. + mozilla::Maybe<ScalarVariant> variantValue; + sr = GetVariantFromIVariant(aValue, info.kind, variantValue); + if (sr != ScalarResult::Ok) { + MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant."); + return sr; + } + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + uniqueId.id, uniqueId.dynamic, aKey, aType, variantValue.ref()); + return ScalarResult::Ok; + } + + if (!aForce && internal_IsScalarDeserializing(lock)) { + const BaseScalarInfo& info = internal_GetScalarInfo(lock, uniqueId); + // Convert the nsIVariant to a Variant. + mozilla::Maybe<ScalarVariant> variantValue; + sr = GetVariantFromIVariant(aValue, info.kind, variantValue); + if (sr != ScalarResult::Ok) { + MOZ_ASSERT(false, "Unable to convert nsIVariant to mozilla::Variant."); + return sr; + } + internal_RecordKeyedScalarAction(lock, uniqueId.id, uniqueId.dynamic, aKey, + aType, variantValue.ref()); + return ScalarResult::Ok; + } + + // Finally get the scalar. + KeyedScalar* scalar = nullptr; + rv = internal_GetKeyedScalarByEnum(lock, uniqueId, aProcessOverride, &scalar); + if (NS_FAILED(rv)) { + // Don't throw on expired scalars. + if (rv == NS_ERROR_NOT_AVAILABLE) { + return ScalarResult::Ok; + } + return ScalarResult::UnknownScalar; + } + + if (aType == ScalarActionType::eAdd) { + return scalar->AddValue(lock, aKey, aValue); + } + if (aType == ScalarActionType::eSet) { + return scalar->SetValue(lock, aKey, aValue); + } + + return scalar->SetMaximum(lock, aKey, aValue); +} + +/** + * Helper function to convert an array of |DynamicScalarInfo| + * to |DynamicScalarDefinition| used by the IPC calls. + */ +void internal_DynamicScalarToIPC( + const StaticMutexAutoLock& lock, + const nsTArray<DynamicScalarInfo>& aDynamicScalarInfos, + nsTArray<DynamicScalarDefinition>& aIPCDefs) { + for (auto& info : aDynamicScalarInfos) { + DynamicScalarDefinition stubDefinition; + stubDefinition.type = info.kind; + stubDefinition.dataset = info.dataset; + stubDefinition.expired = info.mDynamicExpiration; + stubDefinition.keyed = info.keyed; + stubDefinition.name = info.mDynamicName; + stubDefinition.builtin = info.builtin; + aIPCDefs.AppendElement(stubDefinition); + } +} + +/** + * Broadcasts the dynamic scalar definitions to all the other + * content processes. + */ +void internal_BroadcastDefinitions( + const nsTArray<DynamicScalarDefinition>& scalarDefs) { + nsTArray<mozilla::dom::ContentParent*> parents; + mozilla::dom::ContentParent::GetAll(parents); + if (!parents.Length()) { + return; + } + + // Broadcast the definitions to the other content processes. + for (auto parent : parents) { + mozilla::Unused << parent->SendAddDynamicScalars(scalarDefs); + } +} + +void internal_RegisterScalars(const StaticMutexAutoLock& lock, + const nsTArray<DynamicScalarInfo>& scalarInfos) { + // Register the new scalars. + if (!gDynamicScalarInfo) { + gDynamicScalarInfo = new nsTArray<DynamicScalarInfo>(); + } + if (!gDynamicStoreNames) { + gDynamicStoreNames = new nsTArray<RefPtr<nsAtom>>(); + } + + for (auto& scalarInfo : scalarInfos) { + // Allow expiring scalars that were already registered. + CharPtrEntryType* existingKey = + gScalarNameIDMap.GetEntry(scalarInfo.name()); + if (existingKey) { + // Change the scalar to expired if needed. + if (scalarInfo.mDynamicExpiration && !scalarInfo.builtin) { + DynamicScalarInfo& scalarData = + (*gDynamicScalarInfo)[existingKey->GetData().id]; + scalarData.mDynamicExpiration = true; + } + continue; + } + + gDynamicScalarInfo->AppendElement(scalarInfo); + uint32_t scalarId = gDynamicScalarInfo->Length() - 1; + CharPtrEntryType* entry = gScalarNameIDMap.PutEntry(scalarInfo.name()); + entry->SetData(ScalarKey{scalarId, true}); + } +} + +/** + * Creates a snapshot of the desired scalar storage. + * @param {aLock} The proof of lock to access scalar data. + * @param {aScalarsToReflect} The table that will contain the snapshot. + * @param {aDataset} The dataset we're asking the snapshot for. + * @param {aProcessStorage} The scalar storage to take a snapshot of. + * @param {aIsBuiltinDynamic} Whether or not the storage is for dynamic builtin + * scalars. + * @return NS_OK or the error code describing the failure reason. + */ +nsresult internal_ScalarSnapshotter(const StaticMutexAutoLock& aLock, + ScalarSnapshotTable& aScalarsToReflect, + unsigned int aDataset, + ProcessesScalarsMapType& aProcessStorage, + bool aIsBuiltinDynamic, bool aClearScalars, + const nsACString& aStoreName) { + // Iterate the scalars in aProcessStorage. The storage may contain empty or + // yet to be initialized scalars from all the supported processes. + for (const auto& entry : aProcessStorage) { + ScalarStorageMapType* scalarStorage = entry.GetWeak(); + ScalarTupleArray& processScalars = + aScalarsToReflect.LookupOrInsert(entry.GetKey()); + + // Are we in the "Dynamic" process? + bool isDynamicProcess = + ProcessID::Dynamic == static_cast<ProcessID>(entry.GetKey()); + + // Iterate each available child storage. + for (const auto& childEntry : *scalarStorage) { + ScalarBase* scalar = childEntry.GetWeak(); + + // Get the informations for this scalar. + const BaseScalarInfo& info = internal_GetScalarInfo( + aLock, ScalarKey{childEntry.GetKey(), + aIsBuiltinDynamic ? true : isDynamicProcess}); + + // Serialize the scalar if it's in the desired dataset. + if (IsInDataset(info.dataset, aDataset)) { + // Get the scalar value. + nsCOMPtr<nsIVariant> scalarValue; + nsresult rv = scalar->GetValue(aStoreName, aClearScalars, scalarValue); + if (rv == NS_ERROR_NO_CONTENT) { + // No value for this store. Proceed. + continue; + } + if (NS_FAILED(rv)) { + return rv; + } + // Append it to our list. + processScalars.AppendElement( + mozilla::MakeTuple(info.name(), scalarValue, info.kind)); + } + } + if (processScalars.Length() == 0) { + aScalarsToReflect.Remove(entry.GetKey()); + } + } + return NS_OK; +} + +/** + * Creates a snapshot of the desired keyed scalar storage. + * @param {aLock} The proof of lock to access scalar data. + * @param {aScalarsToReflect} The table that will contain the snapshot. + * @param {aDataset} The dataset we're asking the snapshot for. + * @param {aProcessStorage} The scalar storage to take a snapshot of. + * @param {aIsBuiltinDynamic} Whether or not the storage is for dynamic builtin + * scalars. + * @return NS_OK or the error code describing the failure reason. + */ +nsresult internal_KeyedScalarSnapshotter( + const StaticMutexAutoLock& aLock, + KeyedScalarSnapshotTable& aScalarsToReflect, unsigned int aDataset, + ProcessesKeyedScalarsMapType& aProcessStorage, bool aIsBuiltinDynamic, + bool aClearScalars, const nsACString& aStoreName) { + // Iterate the scalars in aProcessStorage. The storage may contain empty or + // yet to be initialized scalars from all the supported processes. + for (const auto& entry : aProcessStorage) { + KeyedScalarStorageMapType* scalarStorage = entry.GetWeak(); + KeyedScalarTupleArray& processScalars = + aScalarsToReflect.LookupOrInsert(entry.GetKey()); + + // Are we in the "Dynamic" process? + bool isDynamicProcess = + ProcessID::Dynamic == static_cast<ProcessID>(entry.GetKey()); + + for (const auto& childEntry : *scalarStorage) { + KeyedScalar* scalar = childEntry.GetWeak(); + + // Get the informations for this scalar. + const BaseScalarInfo& info = internal_GetScalarInfo( + aLock, ScalarKey{childEntry.GetKey(), + aIsBuiltinDynamic ? true : isDynamicProcess}); + + // Serialize the scalar if it's in the desired dataset. + if (IsInDataset(info.dataset, aDataset)) { + // Get the keys for this scalar. + nsTArray<KeyedScalar::KeyValuePair> scalarKeyedData; + nsresult rv = + scalar->GetValue(aStoreName, aClearScalars, scalarKeyedData); + if (NS_FAILED(rv)) { + return rv; + } + if (scalarKeyedData.Length() == 0) { + // Don't bother with empty keyed scalars. + continue; + } + // Append it to our list. + processScalars.AppendElement(mozilla::MakeTuple( + info.name(), std::move(scalarKeyedData), info.kind)); + } + } + if (processScalars.Length() == 0) { + aScalarsToReflect.Remove(entry.GetKey()); + } + } + return NS_OK; +} + +/** + * Helper function to get a snapshot of the scalars. + * + * @param {aLock} The proof of lock to access scalar data. + * @param {aScalarsToReflect} The table that will contain the snapshot. + * @param {aDataset} The dataset we're asking the snapshot for. + * @param {aClearScalars} Whether or not to clear the scalar storage. + * @param {aStoreName} The name of the store to snapshot. + * @return NS_OK or the error code describing the failure reason. + */ +nsresult internal_GetScalarSnapshot(const StaticMutexAutoLock& aLock, + ScalarSnapshotTable& aScalarsToReflect, + unsigned int aDataset, bool aClearScalars, + const nsACString& aStoreName) { + // Take a snapshot of the scalars. + nsresult rv = + internal_ScalarSnapshotter(aLock, aScalarsToReflect, aDataset, + gScalarStorageMap, false, /*aIsBuiltinDynamic*/ + aClearScalars, aStoreName); + if (NS_FAILED(rv)) { + return rv; + } + + // And a snapshot of the dynamic builtin ones. + rv = internal_ScalarSnapshotter(aLock, aScalarsToReflect, aDataset, + gDynamicBuiltinScalarStorageMap, + true, /*aIsBuiltinDynamic*/ + aClearScalars, aStoreName); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +/** + * Helper function to get a snapshot of the keyed scalars. + * + * @param {aLock} The proof of lock to access scalar data. + * @param {aScalarsToReflect} The table that will contain the snapshot. + * @param {aDataset} The dataset we're asking the snapshot for. + * @param {aClearScalars} Whether or not to clear the scalar storage. + * @param {aStoreName} The name of the store to snapshot. + * @return NS_OK or the error code describing the failure reason. + */ +nsresult internal_GetKeyedScalarSnapshot( + const StaticMutexAutoLock& aLock, + KeyedScalarSnapshotTable& aScalarsToReflect, unsigned int aDataset, + bool aClearScalars, const nsACString& aStoreName) { + // Take a snapshot of the scalars. + nsresult rv = internal_KeyedScalarSnapshotter( + aLock, aScalarsToReflect, aDataset, gKeyedScalarStorageMap, + false, /*aIsBuiltinDynamic*/ + aClearScalars, aStoreName); + if (NS_FAILED(rv)) { + return rv; + } + + // And a snapshot of the dynamic builtin ones. + rv = internal_KeyedScalarSnapshotter(aLock, aScalarsToReflect, aDataset, + gDynamicBuiltinKeyedScalarStorageMap, + true, /*aIsBuiltinDynamic*/ + aClearScalars, aStoreName); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +} // namespace + +// helpers for recording/applying scalar operations +namespace { + +void internal_ApplyScalarActions( + const StaticMutexAutoLock& lock, + const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions, + const mozilla::Maybe<ProcessID>& aProcessType = Nothing()) { + if (!internal_CanRecordBase(lock)) { + return; + } + + for (auto& upd : aScalarActions) { + ScalarKey uniqueId{upd.mId, upd.mDynamic}; + if (NS_WARN_IF(!internal_IsValidId(lock, uniqueId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + continue; + } + + if (internal_IsKeyedScalar(lock, uniqueId)) { + continue; + } + + // Are we allowed to record this scalar? We don't need to check for + // allowed processes here, that's taken care of when recording + // in child processes. + if (!internal_CanRecordForScalarID(lock, uniqueId)) { + continue; + } + + // Either we got passed a process type or it was explicitely set on the + // recorded action. It should never happen that it is set to an invalid + // value (such as ProcessID::Count) + ProcessID processType = aProcessType.valueOr(upd.mProcessType); + MOZ_ASSERT(processType != ProcessID::Count); + + // Refresh the data in the parent process with the data coming from the + // child processes. + ScalarBase* scalar = nullptr; + nsresult rv = + internal_GetScalarByEnum(lock, uniqueId, processType, &scalar); + if (NS_FAILED(rv)) { + // Bug 1513496 - We no longer log a warning if the scalar is expired. + if (rv != NS_ERROR_NOT_AVAILABLE) { + NS_WARNING("NS_FAILED internal_GetScalarByEnum for CHILD"); + } + continue; + } + + if (upd.mData.isNothing()) { + MOZ_ASSERT(false, "There is no data in the ScalarActionType."); + continue; + } + + // Get the type of this scalar from the scalar ID. We already checked + // for its validity a few lines above. + const uint32_t scalarType = internal_GetScalarInfo(lock, uniqueId).kind; + + // Extract the data from the mozilla::Variant. + switch (upd.mActionType) { + case ScalarActionType::eSet: { + switch (scalarType) { + case nsITelemetry::SCALAR_TYPE_COUNT: + if (!upd.mData->is<uint32_t>()) { + NS_WARNING("Attempting to set a count scalar to a non-integer."); + continue; + } + scalar->SetValue(upd.mData->as<uint32_t>()); + break; + case nsITelemetry::SCALAR_TYPE_BOOLEAN: + if (!upd.mData->is<bool>()) { + NS_WARNING( + "Attempting to set a boolean scalar to a non-boolean."); + continue; + } + scalar->SetValue(upd.mData->as<bool>()); + break; + case nsITelemetry::SCALAR_TYPE_STRING: + if (!upd.mData->is<nsString>()) { + NS_WARNING("Attempting to set a string scalar to a non-string."); + continue; + } + scalar->SetValue(upd.mData->as<nsString>()); + break; + } + break; + } + case ScalarActionType::eAdd: { + if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) { + NS_WARNING("Attempting to add on a non count scalar."); + continue; + } + // We only support adding uint32_t. + if (!upd.mData->is<uint32_t>()) { + NS_WARNING("Attempting to add to a count scalar with a non-integer."); + continue; + } + scalar->AddValue(upd.mData->as<uint32_t>()); + break; + } + case ScalarActionType::eSetMaximum: { + if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) { + NS_WARNING("Attempting to setMaximum on a non count scalar."); + continue; + } + // We only support SetMaximum on uint32_t. + if (!upd.mData->is<uint32_t>()) { + NS_WARNING( + "Attempting to setMaximum a count scalar to a non-integer."); + continue; + } + scalar->SetMaximum(upd.mData->as<uint32_t>()); + break; + } + default: + NS_WARNING("Unsupported action coming from scalar child updates."); + } + } +} + +void internal_ApplyKeyedScalarActions( + const StaticMutexAutoLock& lock, + const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions, + const mozilla::Maybe<ProcessID>& aProcessType = Nothing()) { + if (!internal_CanRecordBase(lock)) { + return; + } + + for (auto& upd : aScalarActions) { + ScalarKey uniqueId{upd.mId, upd.mDynamic}; + if (NS_WARN_IF(!internal_IsValidId(lock, uniqueId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + continue; + } + + if (!internal_IsKeyedScalar(lock, uniqueId)) { + continue; + } + + // Are we allowed to record this scalar? We don't need to check for + // allowed processes here, that's taken care of when recording + // in child processes. + if (!internal_CanRecordForScalarID(lock, uniqueId)) { + continue; + } + + // Either we got passed a process type or it was explicitely set on the + // recorded action. It should never happen that it is set to an invalid + // value (such as ProcessID::Count) + ProcessID processType = aProcessType.valueOr(upd.mProcessType); + MOZ_ASSERT(processType != ProcessID::Count); + + // Refresh the data in the parent process with the data coming from the + // child processes. + KeyedScalar* scalar = nullptr; + nsresult rv = + internal_GetKeyedScalarByEnum(lock, uniqueId, processType, &scalar); + if (NS_FAILED(rv)) { + // Bug 1513496 - We no longer log a warning if the scalar is expired. + if (rv != NS_ERROR_NOT_AVAILABLE) { + NS_WARNING("NS_FAILED internal_GetKeyedScalarByEnum for CHILD"); + } + continue; + } + + if (upd.mData.isNothing()) { + MOZ_ASSERT(false, "There is no data in the KeyedScalarAction."); + continue; + } + + // Get the type of this scalar from the scalar ID. We already checked + // for its validity a few lines above. + const uint32_t scalarType = internal_GetScalarInfo(lock, uniqueId).kind; + + // Extract the data from the mozilla::Variant. + switch (upd.mActionType) { + case ScalarActionType::eSet: { + switch (scalarType) { + case nsITelemetry::SCALAR_TYPE_COUNT: + if (!upd.mData->is<uint32_t>()) { + NS_WARNING("Attempting to set a count scalar to a non-integer."); + continue; + } + scalar->SetValue(lock, NS_ConvertUTF8toUTF16(upd.mKey), + upd.mData->as<uint32_t>()); + break; + case nsITelemetry::SCALAR_TYPE_BOOLEAN: + if (!upd.mData->is<bool>()) { + NS_WARNING( + "Attempting to set a boolean scalar to a non-boolean."); + continue; + } + scalar->SetValue(lock, NS_ConvertUTF8toUTF16(upd.mKey), + upd.mData->as<bool>()); + break; + default: + NS_WARNING("Unsupported type coming from scalar child updates."); + } + break; + } + case ScalarActionType::eAdd: { + if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) { + NS_WARNING("Attempting to add on a non count scalar."); + continue; + } + // We only support adding on uint32_t. + if (!upd.mData->is<uint32_t>()) { + NS_WARNING("Attempting to add to a count scalar with a non-integer."); + continue; + } + scalar->AddValue(lock, NS_ConvertUTF8toUTF16(upd.mKey), + upd.mData->as<uint32_t>()); + break; + } + case ScalarActionType::eSetMaximum: { + if (scalarType != nsITelemetry::SCALAR_TYPE_COUNT) { + NS_WARNING("Attempting to setMaximum on a non count scalar."); + continue; + } + // We only support SetMaximum on uint32_t. + if (!upd.mData->is<uint32_t>()) { + NS_WARNING( + "Attempting to setMaximum a count scalar to a non-integer."); + continue; + } + scalar->SetMaximum(lock, NS_ConvertUTF8toUTF16(upd.mKey), + upd.mData->as<uint32_t>()); + break; + } + default: + NS_WARNING( + "Unsupported action coming from keyed scalar child updates."); + } + } +} + +void internal_ApplyPendingOperations(const StaticMutexAutoLock& lock) { + if (gScalarsActions && gScalarsActions->Length() > 0) { + internal_ApplyScalarActions(lock, *gScalarsActions); + gScalarsActions->Clear(); + } + + if (gKeyedScalarsActions && gKeyedScalarsActions->Length() > 0) { + internal_ApplyKeyedScalarActions(lock, *gKeyedScalarsActions); + gKeyedScalarsActions->Clear(); + } + + // After all pending operations are applied deserialization is done + gIsDeserializing = false; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryScalars:: + +// This is a StaticMutex rather than a plain Mutex (1) so that +// it gets initialised in a thread-safe manner the first time +// it is used, and (2) because it is never de-initialised, and +// a normal Mutex would show up as a leak in BloatView. StaticMutex +// also has the "OffTheBooks" property, so it won't show as a leak +// in BloatView. +// Another reason to use a StaticMutex instead of a plain Mutex is +// that, due to the nature of Telemetry, we cannot rely on having a +// mutex initialized in InitializeGlobalState. Unfortunately, we +// cannot make sure that no other function is called before this point. +static StaticMutex gTelemetryScalarsMutex MOZ_UNANNOTATED; + +void TelemetryScalar::InitializeGlobalState(bool aCanRecordBase, + bool aCanRecordExtended) { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + MOZ_ASSERT(!gInitDone, + "TelemetryScalar::InitializeGlobalState " + "may only be called once"); + + gCanRecordBase = aCanRecordBase; + gCanRecordExtended = aCanRecordExtended; + + // Populate the static scalar name->id cache. Note that the scalar names are + // statically allocated and come from the automatically generated + // TelemetryScalarData.h. + uint32_t scalarCount = + static_cast<uint32_t>(mozilla::Telemetry::ScalarID::ScalarCount); + for (uint32_t i = 0; i < scalarCount; i++) { + CharPtrEntryType* entry = gScalarNameIDMap.PutEntry(gScalars[i].name()); + entry->SetData(ScalarKey{i, false}); + } + + // To summarize dynamic events we need a dynamic scalar. + const nsTArray<DynamicScalarInfo> initialDynamicScalars({ + DynamicScalarInfo{ + nsITelemetry::SCALAR_TYPE_COUNT, + true /* recordOnRelease */, + false /* expired */, + nsAutoCString("telemetry.dynamic_event_counts"), + true /* keyed */, + false /* built-in */, + {} /* stores */, + }, + }); + internal_RegisterScalars(locker, initialDynamicScalars); + + gInitDone = true; +} + +void TelemetryScalar::DeInitializeGlobalState() { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + gCanRecordBase = false; + gCanRecordExtended = false; + gScalarNameIDMap.Clear(); + gScalarStorageMap.Clear(); + gKeyedScalarStorageMap.Clear(); + gDynamicBuiltinScalarStorageMap.Clear(); + gDynamicBuiltinKeyedScalarStorageMap.Clear(); + gDynamicScalarInfo = nullptr; + gDynamicStoreNames = nullptr; + gInitDone = false; +} + +void TelemetryScalar::DeserializationStarted() { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + gIsDeserializing = true; +} + +void TelemetryScalar::ApplyPendingOperations() { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + internal_ApplyPendingOperations(locker); +} + +void TelemetryScalar::SetCanRecordBase(bool b) { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + gCanRecordBase = b; +} + +void TelemetryScalar::SetCanRecordExtended(bool b) { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + gCanRecordExtended = b; +} + +/** + * Adds the value to the given scalar. + * + * @param aName The scalar name. + * @param aVal The numeric value to add to the scalar. + * @param aCx The JS context. + * @return NS_OK (always) so that the JS API call doesn't throw. In case of + * errors, a warning level message is printed in the browser console. + */ +nsresult TelemetryScalar::Add(const nsACString& aName, + JS::Handle<JS::Value> aVal, JSContext* aCx) { + // Unpack the aVal to nsIVariant. This uses the JS context. + nsCOMPtr<nsIVariant> unpackedVal; + nsresult rv = nsContentUtils::XPConnect()->JSToVariant( + aCx, aVal, getter_AddRefs(unpackedVal)); + if (NS_FAILED(rv)) { + internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant); + return NS_OK; + } + + ScalarResult sr; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + sr = internal_UpdateScalar(locker, aName, ScalarActionType::eAdd, + unpackedVal); + } + + // Warn the user about the error if we need to. + if (sr != ScalarResult::Ok) { + internal_LogScalarError(aName, sr); + } + + return NS_OK; +} + +/** + * Adds the value to the given scalar. + * + * @param aName The scalar name. + * @param aKey The key name. + * @param aVal The numeric value to add to the scalar. + * @param aCx The JS context. + * @return NS_OK (always) so that the JS API call doesn't throw. In case of + * errors, a warning level message is printed in the browser console. + */ +nsresult TelemetryScalar::Add(const nsACString& aName, const nsAString& aKey, + JS::Handle<JS::Value> aVal, JSContext* aCx) { + // Unpack the aVal to nsIVariant. This uses the JS context. + nsCOMPtr<nsIVariant> unpackedVal; + nsresult rv = nsContentUtils::XPConnect()->JSToVariant( + aCx, aVal, getter_AddRefs(unpackedVal)); + if (NS_FAILED(rv)) { + internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant); + return NS_OK; + } + + ScalarResult sr; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + sr = internal_UpdateKeyedScalar(locker, aName, aKey, ScalarActionType::eAdd, + unpackedVal); + } + + // Warn the user about the error if we need to. + if (sr != ScalarResult::Ok) { + internal_LogScalarError(aName, sr); + } + + return NS_OK; +} + +/** + * Adds the value to the given scalar. + * + * @param aId The scalar enum id. + * @param aVal The numeric value to add to the scalar. + */ +void TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue) { + if (NS_WARN_IF(!IsValidEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + return; + } + + ScalarKey uniqueId{static_cast<uint32_t>(aId), false}; + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) { + // We can't record this scalar. Bail out. + return; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + TelemetryIPCAccumulator::RecordChildScalarAction( + uniqueId.id, uniqueId.dynamic, ScalarActionType::eAdd, + ScalarVariant(aValue)); + return; + } + + if (internal_IsScalarDeserializing(locker)) { + internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic, + ScalarActionType::eAdd, ScalarVariant(aValue)); + return; + } + + ScalarBase* scalar = nullptr; + nsresult rv = + internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar); + if (NS_FAILED(rv)) { + return; + } + + scalar->AddValue(aValue); +} + +/** + * Adds the value to the given keyed scalar. + * + * @param aId The scalar enum id. + * @param aKey The key name. + * @param aVal The numeric value to add to the scalar. + */ +void TelemetryScalar::Add(mozilla::Telemetry::ScalarID aId, + const nsAString& aKey, uint32_t aValue) { + if (NS_WARN_IF(!IsValidEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + return; + } + + ScalarKey uniqueId{static_cast<uint32_t>(aId), false}; + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) { + // We can't record this scalar. Bail out. + return; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + uniqueId.id, uniqueId.dynamic, aKey, ScalarActionType::eAdd, + ScalarVariant(aValue)); + return; + } + + if (internal_IsScalarDeserializing(locker)) { + internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic, + aKey, ScalarActionType::eAdd, + ScalarVariant(aValue)); + return; + } + + KeyedScalar* scalar = nullptr; + nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId, + ProcessID::Parent, &scalar); + if (NS_FAILED(rv)) { + return; + } + + scalar->AddValue(locker, aKey, aValue); +} + +/** + * Sets the scalar to the given value. + * + * @param aName The scalar name. + * @param aVal The value to set the scalar to. + * @param aCx The JS context. + * @return NS_OK (always) so that the JS API call doesn't throw. In case of + * errors, a warning level message is printed in the browser console. + */ +nsresult TelemetryScalar::Set(const nsACString& aName, + JS::Handle<JS::Value> aVal, JSContext* aCx) { + // Unpack the aVal to nsIVariant. This uses the JS context. + nsCOMPtr<nsIVariant> unpackedVal; + nsresult rv = nsContentUtils::XPConnect()->JSToVariant( + aCx, aVal, getter_AddRefs(unpackedVal)); + if (NS_FAILED(rv)) { + internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant); + return NS_OK; + } + + ScalarResult sr; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + sr = internal_UpdateScalar(locker, aName, ScalarActionType::eSet, + unpackedVal); + } + + // Warn the user about the error if we need to. + if (sr != ScalarResult::Ok) { + internal_LogScalarError(aName, sr); + } + + return NS_OK; +} + +/** + * Sets the keyed scalar to the given value. + * + * @param aName The scalar name. + * @param aKey The key name. + * @param aVal The value to set the scalar to. + * @param aCx The JS context. + * @return NS_OK (always) so that the JS API call doesn't throw. In case of + * errors, a warning level message is printed in the browser console. + */ +nsresult TelemetryScalar::Set(const nsACString& aName, const nsAString& aKey, + JS::Handle<JS::Value> aVal, JSContext* aCx) { + // Unpack the aVal to nsIVariant. This uses the JS context. + nsCOMPtr<nsIVariant> unpackedVal; + nsresult rv = nsContentUtils::XPConnect()->JSToVariant( + aCx, aVal, getter_AddRefs(unpackedVal)); + if (NS_FAILED(rv)) { + internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant); + return NS_OK; + } + + ScalarResult sr; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + sr = internal_UpdateKeyedScalar(locker, aName, aKey, ScalarActionType::eSet, + unpackedVal); + } + + // Warn the user about the error if we need to. + if (sr != ScalarResult::Ok) { + internal_LogScalarError(aName, sr); + } + + return NS_OK; +} + +/** + * Sets the scalar to the given numeric value. + * + * @param aId The scalar enum id. + * @param aValue The numeric, unsigned value to set the scalar to. + */ +void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, uint32_t aValue) { + if (NS_WARN_IF(!IsValidEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + return; + } + + ScalarKey uniqueId{static_cast<uint32_t>(aId), false}; + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) { + // We can't record this scalar. Bail out. + return; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + TelemetryIPCAccumulator::RecordChildScalarAction( + uniqueId.id, uniqueId.dynamic, ScalarActionType::eSet, + ScalarVariant(aValue)); + return; + } + + if (internal_IsScalarDeserializing(locker)) { + internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic, + ScalarActionType::eSet, ScalarVariant(aValue)); + return; + } + + ScalarBase* scalar = nullptr; + nsresult rv = + internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar); + if (NS_FAILED(rv)) { + return; + } + + scalar->SetValue(aValue); +} + +/** + * Sets the scalar to the given string value. + * + * @param aId The scalar enum id. + * @param aValue The string value to set the scalar to. + */ +void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, + const nsAString& aValue) { + if (NS_WARN_IF(!IsValidEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + return; + } + + ScalarKey uniqueId{static_cast<uint32_t>(aId), false}; + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) { + // We can't record this scalar. Bail out. + return; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + TelemetryIPCAccumulator::RecordChildScalarAction( + uniqueId.id, uniqueId.dynamic, ScalarActionType::eSet, + ScalarVariant(nsString(aValue))); + return; + } + + if (internal_IsScalarDeserializing(locker)) { + internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic, + ScalarActionType::eSet, + ScalarVariant(nsString(aValue))); + return; + } + + ScalarBase* scalar = nullptr; + nsresult rv = + internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar); + if (NS_FAILED(rv)) { + return; + } + + scalar->SetValue(aValue); +} + +/** + * Sets the scalar to the given boolean value. + * + * @param aId The scalar enum id. + * @param aValue The boolean value to set the scalar to. + */ +void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, bool aValue) { + if (NS_WARN_IF(!IsValidEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + return; + } + + ScalarKey uniqueId{static_cast<uint32_t>(aId), false}; + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) { + // We can't record this scalar. Bail out. + return; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + TelemetryIPCAccumulator::RecordChildScalarAction( + uniqueId.id, uniqueId.dynamic, ScalarActionType::eSet, + ScalarVariant(aValue)); + return; + } + + if (internal_IsScalarDeserializing(locker)) { + internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic, + ScalarActionType::eSet, ScalarVariant(aValue)); + return; + } + + ScalarBase* scalar = nullptr; + nsresult rv = + internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar); + if (NS_FAILED(rv)) { + return; + } + + scalar->SetValue(aValue); +} + +/** + * Sets the keyed scalar to the given numeric value. + * + * @param aId The scalar enum id. + * @param aKey The scalar key. + * @param aValue The numeric, unsigned value to set the scalar to. + */ +void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, + const nsAString& aKey, uint32_t aValue) { + if (NS_WARN_IF(!IsValidEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + return; + } + + ScalarKey uniqueId{static_cast<uint32_t>(aId), false}; + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) { + // We can't record this scalar. Bail out. + return; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + uniqueId.id, uniqueId.dynamic, aKey, ScalarActionType::eSet, + ScalarVariant(aValue)); + return; + } + + if (internal_IsScalarDeserializing(locker)) { + internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic, + aKey, ScalarActionType::eSet, + ScalarVariant(aValue)); + return; + } + + KeyedScalar* scalar = nullptr; + nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId, + ProcessID::Parent, &scalar); + if (NS_FAILED(rv)) { + return; + } + + scalar->SetValue(locker, aKey, aValue); +} + +/** + * Sets the scalar to the given boolean value. + * + * @param aId The scalar enum id. + * @param aKey The scalar key. + * @param aValue The boolean value to set the scalar to. + */ +void TelemetryScalar::Set(mozilla::Telemetry::ScalarID aId, + const nsAString& aKey, bool aValue) { + if (NS_WARN_IF(!IsValidEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + return; + } + + ScalarKey uniqueId{static_cast<uint32_t>(aId), false}; + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) { + // We can't record this scalar. Bail out. + return; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + uniqueId.id, uniqueId.dynamic, aKey, ScalarActionType::eSet, + ScalarVariant(aValue)); + return; + } + + if (internal_IsScalarDeserializing(locker)) { + internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic, + aKey, ScalarActionType::eSet, + ScalarVariant(aValue)); + return; + } + + KeyedScalar* scalar = nullptr; + nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId, + ProcessID::Parent, &scalar); + if (NS_FAILED(rv)) { + return; + } + + scalar->SetValue(locker, aKey, aValue); +} + +/** + * Sets the scalar to the maximum of the current and the passed value. + * + * @param aName The scalar name. + * @param aVal The numeric value to set the scalar to. + * @param aCx The JS context. + * @return NS_OK (always) so that the JS API call doesn't throw. In case of + * errors, a warning level message is printed in the browser console. + */ +nsresult TelemetryScalar::SetMaximum(const nsACString& aName, + JS::Handle<JS::Value> aVal, + JSContext* aCx) { + // Unpack the aVal to nsIVariant. This uses the JS context. + nsCOMPtr<nsIVariant> unpackedVal; + nsresult rv = nsContentUtils::XPConnect()->JSToVariant( + aCx, aVal, getter_AddRefs(unpackedVal)); + if (NS_FAILED(rv)) { + internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant); + return NS_OK; + } + + ScalarResult sr; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + sr = internal_UpdateScalar(locker, aName, ScalarActionType::eSetMaximum, + unpackedVal); + } + + // Warn the user about the error if we need to. + if (sr != ScalarResult::Ok) { + internal_LogScalarError(aName, sr); + } + + return NS_OK; +} + +/** + * Sets the scalar to the maximum of the current and the passed value. + * + * @param aName The scalar name. + * @param aKey The key name. + * @param aVal The numeric value to set the scalar to. + * @param aCx The JS context. + * @return NS_OK (always) so that the JS API call doesn't throw. In case of + * errors, a warning level message is printed in the browser console. + */ +nsresult TelemetryScalar::SetMaximum(const nsACString& aName, + const nsAString& aKey, + JS::Handle<JS::Value> aVal, + JSContext* aCx) { + // Unpack the aVal to nsIVariant. This uses the JS context. + nsCOMPtr<nsIVariant> unpackedVal; + nsresult rv = nsContentUtils::XPConnect()->JSToVariant( + aCx, aVal, getter_AddRefs(unpackedVal)); + if (NS_FAILED(rv)) { + internal_LogScalarError(aName, ScalarResult::CannotUnpackVariant); + return NS_OK; + } + + ScalarResult sr; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + sr = internal_UpdateKeyedScalar(locker, aName, aKey, + ScalarActionType::eSetMaximum, unpackedVal); + } + + // Warn the user about the error if we need to. + if (sr != ScalarResult::Ok) { + internal_LogScalarError(aName, sr); + } + + return NS_OK; +} + +/** + * Sets the scalar to the maximum of the current and the passed value. + * + * @param aId The scalar enum id. + * @param aValue The numeric value to set the scalar to. + */ +void TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, + uint32_t aValue) { + if (NS_WARN_IF(!IsValidEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + return; + } + + ScalarKey uniqueId{static_cast<uint32_t>(aId), false}; + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + if (internal_CanRecordScalar(locker, uniqueId, false) != ScalarResult::Ok) { + // We can't record this scalar. Bail out. + return; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + TelemetryIPCAccumulator::RecordChildScalarAction( + uniqueId.id, uniqueId.dynamic, ScalarActionType::eSetMaximum, + ScalarVariant(aValue)); + return; + } + + if (internal_IsScalarDeserializing(locker)) { + internal_RecordScalarAction(locker, uniqueId.id, uniqueId.dynamic, + ScalarActionType::eSetMaximum, + ScalarVariant(aValue)); + return; + } + + ScalarBase* scalar = nullptr; + nsresult rv = + internal_GetScalarByEnum(locker, uniqueId, ProcessID::Parent, &scalar); + if (NS_FAILED(rv)) { + return; + } + + scalar->SetMaximum(aValue); +} + +/** + * Sets the keyed scalar to the maximum of the current and the passed value. + * + * @param aId The scalar enum id. + * @param aKey The key name. + * @param aValue The numeric value to set the scalar to. + */ +void TelemetryScalar::SetMaximum(mozilla::Telemetry::ScalarID aId, + const nsAString& aKey, uint32_t aValue) { + if (NS_WARN_IF(!IsValidEnumId(aId))) { + MOZ_ASSERT_UNREACHABLE("Scalar usage requires valid ids."); + return; + } + + ScalarKey uniqueId{static_cast<uint32_t>(aId), false}; + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + if (internal_CanRecordScalar(locker, uniqueId, true) != ScalarResult::Ok) { + // We can't record this scalar. Bail out. + return; + } + + // Accumulate in the child process if needed. + if (!XRE_IsParentProcess()) { + TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + uniqueId.id, uniqueId.dynamic, aKey, ScalarActionType::eSetMaximum, + ScalarVariant(aValue)); + return; + } + + if (internal_IsScalarDeserializing(locker)) { + internal_RecordKeyedScalarAction(locker, uniqueId.id, uniqueId.dynamic, + aKey, ScalarActionType::eSetMaximum, + ScalarVariant(aValue)); + return; + } + + KeyedScalar* scalar = nullptr; + nsresult rv = internal_GetKeyedScalarByEnum(locker, uniqueId, + ProcessID::Parent, &scalar); + if (NS_FAILED(rv)) { + return; + } + + scalar->SetMaximum(locker, aKey, aValue); +} + +nsresult TelemetryScalar::CreateSnapshots(unsigned int aDataset, + bool aClearScalars, JSContext* aCx, + uint8_t optional_argc, + JS::MutableHandle<JS::Value> aResult, + bool aFilterTest, + const nsACString& aStoreName) { + MOZ_ASSERT( + XRE_IsParentProcess(), + "Snapshotting scalars should only happen in the parent processes."); + // If no arguments were passed in, apply the default value. + if (!optional_argc) { + aClearScalars = false; + } + + JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx)); + if (!root_obj) { + return NS_ERROR_FAILURE; + } + aResult.setObject(*root_obj); + + // Return `{}` in child processes. + if (!XRE_IsParentProcess()) { + return NS_OK; + } + + // Only lock the mutex while accessing our data, without locking any JS + // related code. + ScalarSnapshotTable scalarsToReflect; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + nsresult rv = internal_GetScalarSnapshot(locker, scalarsToReflect, aDataset, + aClearScalars, aStoreName); + if (NS_FAILED(rv)) { + return rv; + } + } + + // Reflect it to JS. + for (const auto& entry : scalarsToReflect) { + const ScalarTupleArray& processScalars = entry.GetData(); + const char* processName = GetNameForProcessID(ProcessID(entry.GetKey())); + + // Create the object that will hold the scalars for this process and add it + // to the returned root object. + JS::Rooted<JSObject*> processObj(aCx, JS_NewPlainObject(aCx)); + if (!processObj || !JS_DefineProperty(aCx, root_obj, processName, + processObj, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + + for (ScalarTupleArray::size_type i = 0; i < processScalars.Length(); i++) { + const ScalarDataTuple& scalar = processScalars[i]; + + const char* scalarName = mozilla::Get<0>(scalar); + if (aFilterTest && strncmp(TEST_SCALAR_PREFIX, scalarName, + strlen(TEST_SCALAR_PREFIX)) == 0) { + continue; + } + + // Convert it to a JS Val. + JS::Rooted<JS::Value> scalarJsValue(aCx); + nsresult rv = nsContentUtils::XPConnect()->VariantToJS( + aCx, processObj, mozilla::Get<1>(scalar), &scalarJsValue); + if (NS_FAILED(rv)) { + return rv; + } + + // Add it to the scalar object. + if (!JS_DefineProperty(aCx, processObj, scalarName, scalarJsValue, + JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + } + + return NS_OK; +} + +nsresult TelemetryScalar::CreateKeyedSnapshots( + unsigned int aDataset, bool aClearScalars, JSContext* aCx, + uint8_t optional_argc, JS::MutableHandle<JS::Value> aResult, + bool aFilterTest, const nsACString& aStoreName) { + MOZ_ASSERT( + XRE_IsParentProcess(), + "Snapshotting scalars should only happen in the parent processes."); + // If no arguments were passed in, apply the default value. + if (!optional_argc) { + aClearScalars = false; + } + + JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx)); + if (!root_obj) { + return NS_ERROR_FAILURE; + } + aResult.setObject(*root_obj); + + // Return `{}` in child processes. + if (!XRE_IsParentProcess()) { + return NS_OK; + } + + // Only lock the mutex while accessing our data, without locking any JS + // related code. + KeyedScalarSnapshotTable scalarsToReflect; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + nsresult rv = internal_GetKeyedScalarSnapshot( + locker, scalarsToReflect, aDataset, aClearScalars, aStoreName); + if (NS_FAILED(rv)) { + return rv; + } + } + + // Reflect it to JS. + for (const auto& entry : scalarsToReflect) { + const KeyedScalarTupleArray& processScalars = entry.GetData(); + const char* processName = GetNameForProcessID(ProcessID(entry.GetKey())); + + // Create the object that will hold the scalars for this process and add it + // to the returned root object. + JS::Rooted<JSObject*> processObj(aCx, JS_NewPlainObject(aCx)); + if (!processObj || !JS_DefineProperty(aCx, root_obj, processName, + processObj, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + + for (KeyedScalarTupleArray::size_type i = 0; i < processScalars.Length(); + i++) { + const KeyedScalarDataTuple& keyedScalarData = processScalars[i]; + + const char* scalarName = mozilla::Get<0>(keyedScalarData); + if (aFilterTest && strncmp(TEST_SCALAR_PREFIX, scalarName, + strlen(TEST_SCALAR_PREFIX)) == 0) { + continue; + } + + // Go through each keyed scalar and create a keyed scalar object. + // This object will hold the values for all the keyed scalar keys. + JS::Rooted<JSObject*> keyedScalarObj(aCx, JS_NewPlainObject(aCx)); + + // Define a property for each scalar key, then add it to the keyed scalar + // object. + const nsTArray<KeyedScalar::KeyValuePair>& keyProps = + mozilla::Get<1>(keyedScalarData); + for (uint32_t i = 0; i < keyProps.Length(); i++) { + const KeyedScalar::KeyValuePair& keyData = keyProps[i]; + + // Convert the value for the key to a JSValue. + JS::Rooted<JS::Value> keyJsValue(aCx); + nsresult rv = nsContentUtils::XPConnect()->VariantToJS( + aCx, keyedScalarObj, keyData.second, &keyJsValue); + if (NS_FAILED(rv)) { + return rv; + } + + // Add the key to the scalar representation. + const NS_ConvertUTF8toUTF16 key(keyData.first); + if (!JS_DefineUCProperty(aCx, keyedScalarObj, key.Data(), key.Length(), + keyJsValue, JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + + // Add the scalar to the root object. + if (!JS_DefineProperty(aCx, processObj, scalarName, keyedScalarObj, + JSPROP_ENUMERATE)) { + return NS_ERROR_FAILURE; + } + } + } + + return NS_OK; +} + +nsresult TelemetryScalar::RegisterScalars(const nsACString& aCategoryName, + JS::Handle<JS::Value> aScalarData, + bool aBuiltin, JSContext* cx) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Dynamic scalars should only be created in the parent process."); + + if (!IsValidIdentifierString(aCategoryName, kMaximumCategoryNameLength, true, + false)) { + JS_ReportErrorASCII(cx, "Invalid category name %s.", + PromiseFlatCString(aCategoryName).get()); + return NS_ERROR_INVALID_ARG; + } + + if (!aScalarData.isObject()) { + JS_ReportErrorASCII(cx, "Scalar data parameter should be an object"); + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted<JSObject*> obj(cx, &aScalarData.toObject()); + JS::Rooted<JS::IdVector> scalarPropertyIds(cx, JS::IdVector(cx)); + if (!JS_Enumerate(cx, obj, &scalarPropertyIds)) { + return NS_ERROR_FAILURE; + } + + // Collect the scalar data into local storage first. + // Only after successfully validating all contained scalars will we register + // them into global storage. + nsTArray<DynamicScalarInfo> newScalarInfos; + + for (size_t i = 0, n = scalarPropertyIds.length(); i < n; i++) { + nsAutoJSString scalarName; + if (!scalarName.init(cx, scalarPropertyIds[i])) { + return NS_ERROR_FAILURE; + } + + if (!IsValidIdentifierString(NS_ConvertUTF16toUTF8(scalarName), + kMaximumScalarNameLength, false, true)) { + JS_ReportErrorASCII( + cx, "Invalid scalar name %s.", + PromiseFlatCString(NS_ConvertUTF16toUTF8(scalarName)).get()); + return NS_ERROR_INVALID_ARG; + } + + // Join the category and the probe names. + nsPrintfCString fullName("%s.%s", PromiseFlatCString(aCategoryName).get(), + NS_ConvertUTF16toUTF8(scalarName).get()); + + JS::Rooted<JS::Value> value(cx); + if (!JS_GetPropertyById(cx, obj, scalarPropertyIds[i], &value) || + !value.isObject()) { + return NS_ERROR_FAILURE; + } + JS::Rooted<JSObject*> scalarDef(cx, &value.toObject()); + + // Get the scalar's kind. + if (!JS_GetProperty(cx, scalarDef, "kind", &value) || !value.isInt32()) { + JS_ReportErrorASCII(cx, "Invalid or missing 'kind' for scalar %s.", + PromiseFlatCString(fullName).get()); + return NS_ERROR_FAILURE; + } + uint32_t kind = static_cast<uint32_t>(value.toInt32()); + + // Get the optional scalar's recording policy (default to false). + bool hasProperty = false; + bool recordOnRelease = false; + if (JS_HasProperty(cx, scalarDef, "record_on_release", &hasProperty) && + hasProperty) { + if (!JS_GetProperty(cx, scalarDef, "record_on_release", &value) || + !value.isBoolean()) { + JS_ReportErrorASCII(cx, "Invalid 'record_on_release' for scalar %s.", + PromiseFlatCString(fullName).get()); + return NS_ERROR_FAILURE; + } + recordOnRelease = static_cast<bool>(value.toBoolean()); + } + + // Get the optional scalar's keyed (default to false). + bool keyed = false; + if (JS_HasProperty(cx, scalarDef, "keyed", &hasProperty) && hasProperty) { + if (!JS_GetProperty(cx, scalarDef, "keyed", &value) || + !value.isBoolean()) { + JS_ReportErrorASCII(cx, "Invalid 'keyed' for scalar %s.", + PromiseFlatCString(fullName).get()); + return NS_ERROR_FAILURE; + } + keyed = static_cast<bool>(value.toBoolean()); + } + + // Get the optional scalar's expired state (default to false). + bool expired = false; + if (JS_HasProperty(cx, scalarDef, "expired", &hasProperty) && hasProperty) { + if (!JS_GetProperty(cx, scalarDef, "expired", &value) || + !value.isBoolean()) { + JS_ReportErrorASCII(cx, "Invalid 'expired' for scalar %s.", + PromiseFlatCString(fullName).get()); + return NS_ERROR_FAILURE; + } + expired = static_cast<bool>(value.toBoolean()); + } + + // Get the scalar's optional stores list (default to ["main"]). + nsTArray<nsCString> stores; + if (JS_HasProperty(cx, scalarDef, "stores", &hasProperty) && hasProperty) { + bool isArray = false; + if (!JS_GetProperty(cx, scalarDef, "stores", &value) || + !JS::IsArrayObject(cx, value, &isArray) || !isArray) { + JS_ReportErrorASCII(cx, "Invalid 'stores' for scalar %s.", + PromiseFlatCString(fullName).get()); + return NS_ERROR_FAILURE; + } + + JS::Rooted<JSObject*> arrayObj(cx, &value.toObject()); + uint32_t storesLength = 0; + if (!JS::GetArrayLength(cx, arrayObj, &storesLength)) { + JS_ReportErrorASCII(cx, + "Can't get 'stores' array length for scalar %s.", + PromiseFlatCString(fullName).get()); + return NS_ERROR_FAILURE; + } + + for (uint32_t i = 0; i < storesLength; ++i) { + JS::Rooted<JS::Value> elt(cx); + if (!JS_GetElement(cx, arrayObj, i, &elt)) { + JS_ReportErrorASCII( + cx, "Can't get element from scalar %s 'stores' array.", + PromiseFlatCString(fullName).get()); + return NS_ERROR_FAILURE; + } + if (!elt.isString()) { + JS_ReportErrorASCII(cx, + "Element in scalar %s 'stores' array isn't a " + "string.", + PromiseFlatCString(fullName).get()); + return NS_ERROR_FAILURE; + } + + nsAutoJSString jsStr; + if (!jsStr.init(cx, elt)) { + return NS_ERROR_FAILURE; + } + + stores.AppendElement(NS_ConvertUTF16toUTF8(jsStr)); + } + // In the event of the usual case (just "main"), save the storage. + if (stores.Length() == 1 && stores[0].EqualsLiteral("main")) { + stores.TruncateLength(0); + } + } + + // We defer the actual registration here in case any other event description + // is invalid. In that case we don't need to roll back any partial + // registration. + newScalarInfos.AppendElement( + DynamicScalarInfo{kind, recordOnRelease, expired, fullName, keyed, + aBuiltin, std::move(stores)}); + } + + // Register the dynamic definition on the parent process. + nsTArray<DynamicScalarDefinition> ipcDefinitions; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + ::internal_RegisterScalars(locker, newScalarInfos); + + // Convert the internal scalar representation to a stripped down IPC one. + ::internal_DynamicScalarToIPC(locker, newScalarInfos, ipcDefinitions); + } + + // Propagate the registration to all the content-processes. + // Do not hold the mutex while calling IPC. + ::internal_BroadcastDefinitions(ipcDefinitions); + + return NS_OK; +} + +/** + * Count in Scalars how many of which events were recorded. See bug 1440673 + * + * Event Telemetry unfortunately cannot use vanilla ScalarAdd because it needs + * to summarize events recorded in different processes to the + * telemetry.event_counts of the same process. Including "dynamic". + * + * @param aUniqueEventName - expected to be category#object#method + * @param aProcessType - the process of the event being summarized + * @param aDynamic - whether the event being summarized was dynamic + */ +void TelemetryScalar::SummarizeEvent(const nsCString& aUniqueEventName, + ProcessID aProcessType, bool aDynamic) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Only summarize events in the parent process"); + if (!XRE_IsParentProcess()) { + return; + } + + StaticMutexAutoLock lock(gTelemetryScalarsMutex); + + ScalarKey scalarKey{static_cast<uint32_t>(ScalarID::TELEMETRY_EVENT_COUNTS), + aDynamic}; + if (aDynamic) { + nsresult rv = internal_GetEnumByScalarName( + lock, nsAutoCString("telemetry.dynamic_event_counts"), &scalarKey); + if (NS_FAILED(rv)) { + NS_WARNING( + "NS_FAILED getting ScalarKey for telemetry.dynamic_event_counts"); + return; + } + } + + KeyedScalar* scalar = nullptr; + nsresult rv = + internal_GetKeyedScalarByEnum(lock, scalarKey, aProcessType, &scalar); + + if (NS_FAILED(rv)) { + NS_WARNING("NS_FAILED getting keyed scalar for event summary. Wut."); + return; + } + + // Set this each time as it may have been cleared and recreated between calls + scalar->SetMaximumNumberOfKeys(kMaxEventSummaryKeys); + + scalar->AddValue(lock, NS_ConvertASCIItoUTF16(aUniqueEventName), 1); +} + +/** + * Resets all the stored scalars. This is intended to be only used in tests. + */ +void TelemetryScalar::ClearScalars() { + MOZ_ASSERT(XRE_IsParentProcess(), + "Scalars should only be cleared in the parent process."); + if (!XRE_IsParentProcess()) { + return; + } + + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + gScalarStorageMap.Clear(); + gKeyedScalarStorageMap.Clear(); + gDynamicBuiltinScalarStorageMap.Clear(); + gDynamicBuiltinKeyedScalarStorageMap.Clear(); + gScalarsActions = nullptr; + gKeyedScalarsActions = nullptr; +} + +size_t TelemetryScalar::GetMapShallowSizesOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + return gScalarNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +size_t TelemetryScalar::GetScalarSizesOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + size_t n = 0; + + auto getSizeOf = [aMallocSizeOf](auto& storageMap) { + size_t partial = 0; + for (const auto& scalarStorage : storageMap.Values()) { + for (const auto& scalar : scalarStorage->Values()) { + partial += scalar->SizeOfIncludingThis(aMallocSizeOf); + } + } + return partial; + }; + + // Account for all the storage used for the different scalar types. + n += getSizeOf(gScalarStorageMap); + n += getSizeOf(gKeyedScalarStorageMap); + n += getSizeOf(gDynamicBuiltinScalarStorageMap); + n += getSizeOf(gDynamicBuiltinKeyedScalarStorageMap); + + return n; +} + +void TelemetryScalar::UpdateChildData( + ProcessID aProcessType, + const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions) { + MOZ_ASSERT(XRE_IsParentProcess(), + "The stored child processes scalar data must be updated from the " + "parent process."); + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + // If scalars are still being deserialized, we need to record the incoming + // operations as well. + if (internal_IsScalarDeserializing(locker)) { + for (const ScalarAction& action : aScalarActions) { + // We're only getting immutable access, so let's copy it + ScalarAction copy = action; + // Fix up the process type + copy.mProcessType = aProcessType; + internal_RecordScalarAction(locker, copy); + } + + return; + } + + internal_ApplyScalarActions(locker, aScalarActions, Some(aProcessType)); +} + +void TelemetryScalar::UpdateChildKeyedData( + ProcessID aProcessType, + const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions) { + MOZ_ASSERT(XRE_IsParentProcess(), + "The stored child processes keyed scalar data must be updated " + "from the parent process."); + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + + // If scalars are still being deserialized, we need to record the incoming + // operations as well. + if (internal_IsScalarDeserializing(locker)) { + for (const KeyedScalarAction& action : aScalarActions) { + // We're only getting immutable access, so let's copy it + KeyedScalarAction copy = action; + // Fix up the process type + copy.mProcessType = aProcessType; + internal_RecordKeyedScalarAction(locker, copy); + } + + return; + } + + internal_ApplyKeyedScalarActions(locker, aScalarActions, Some(aProcessType)); +} + +void TelemetryScalar::RecordDiscardedData( + ProcessID aProcessType, + const mozilla::Telemetry::DiscardedData& aDiscardedData) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Discarded Data must be updated from the parent process."); + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + if (!internal_CanRecordBase(locker)) { + return; + } + + if (GetCurrentProduct() == SupportedProduct::GeckoviewStreaming) { + return; + } + + ScalarBase* scalar = nullptr; + mozilla::DebugOnly<nsresult> rv; + + rv = internal_GetScalarByEnum( + locker, + ScalarKey{ + static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_ACCUMULATIONS), + false}, + aProcessType, &scalar); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + scalar->AddValue(aDiscardedData.mDiscardedHistogramAccumulations); + + rv = internal_GetScalarByEnum( + locker, + ScalarKey{static_cast<uint32_t>( + ScalarID::TELEMETRY_DISCARDED_KEYED_ACCUMULATIONS), + false}, + aProcessType, &scalar); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + scalar->AddValue(aDiscardedData.mDiscardedKeyedHistogramAccumulations); + + rv = internal_GetScalarByEnum( + locker, + ScalarKey{ + static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_SCALAR_ACTIONS), + false}, + aProcessType, &scalar); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + scalar->AddValue(aDiscardedData.mDiscardedScalarActions); + + rv = internal_GetScalarByEnum( + locker, + ScalarKey{static_cast<uint32_t>( + ScalarID::TELEMETRY_DISCARDED_KEYED_SCALAR_ACTIONS), + false}, + aProcessType, &scalar); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + scalar->AddValue(aDiscardedData.mDiscardedKeyedScalarActions); + + rv = internal_GetScalarByEnum( + locker, + ScalarKey{ + static_cast<uint32_t>(ScalarID::TELEMETRY_DISCARDED_CHILD_EVENTS), + false}, + aProcessType, &scalar); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + scalar->AddValue(aDiscardedData.mDiscardedChildEvents); +} + +/** + * Get the dynamic scalar definitions in an IPC-friendly + * structure. + */ +void TelemetryScalar::GetDynamicScalarDefinitions( + nsTArray<DynamicScalarDefinition>& aDefArray) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!gDynamicScalarInfo) { + // Don't have dynamic scalar definitions. Bail out! + return; + } + + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + internal_DynamicScalarToIPC(locker, *gDynamicScalarInfo, aDefArray); +} + +/** + * This adds the dynamic scalar definitions coming from + * the parent process to this child process. If a dynamic + * scalar definition is already defined, check if the new definition + * makes the scalar expired and eventually update the expiration + * state. + */ +void TelemetryScalar::AddDynamicScalarDefinitions( + const nsTArray<DynamicScalarDefinition>& aDefs) { + MOZ_ASSERT(!XRE_IsParentProcess()); + + nsTArray<DynamicScalarInfo> dynamicStubs; + + // Populate the definitions array before acquiring the lock. + for (auto& def : aDefs) { + bool recordOnRelease = def.dataset == nsITelemetry::DATASET_ALL_CHANNELS; + dynamicStubs.AppendElement(DynamicScalarInfo{def.type, + recordOnRelease, + def.expired, + def.name, + def.keyed, + def.builtin, + {} /* stores */}); + } + + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + internal_RegisterScalars(locker, dynamicStubs); + } +} + +nsresult TelemetryScalar::GetAllStores(StringHashSet& set) { + // Static stores + for (uint32_t storeIdx : gScalarStoresTable) { + const char* name = &gScalarsStringTable[storeIdx]; + nsAutoCString store; + store.AssignASCII(name); + if (!set.Insert(store, mozilla::fallible)) { + return NS_ERROR_FAILURE; + } + } + + // Dynamic stores + for (auto& ptr : *gDynamicStoreNames) { + nsAutoCString store; + ptr->ToUTF8String(store); + if (!set.Insert(store, mozilla::fallible)) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PUBLIC: GeckoView serialization/deserialization functions. + +/** + * Write the scalar data to the provided Json object, for + * GeckoView measurement persistence. The output format is the same one used + * for snapshotting the scalars. + * + * @param {aWriter} The JSON object to write to. + * @returns NS_OK or a failure value explaining why persistence failed. + */ +nsresult TelemetryScalar::SerializeScalars(mozilla::JSONWriter& aWriter) { + // Get a copy of the data, without clearing. + ScalarSnapshotTable scalarsToReflect; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // For persistence, we care about all the datasets. Worst case, they + // will be empty. + nsresult rv = internal_GetScalarSnapshot( + locker, scalarsToReflect, nsITelemetry::DATASET_PRERELEASE_CHANNELS, + false, /*aClearScalars*/ + "main"_ns); + if (NS_FAILED(rv)) { + return rv; + } + } + + // Persist the scalars to the JSON object. + for (const auto& entry : scalarsToReflect) { + const ScalarTupleArray& processScalars = entry.GetData(); + const char* processName = GetNameForProcessID(ProcessID(entry.GetKey())); + + aWriter.StartObjectProperty(mozilla::MakeStringSpan(processName)); + + for (const ScalarDataTuple& scalar : processScalars) { + nsresult rv = WriteVariantToJSONWriter( + mozilla::Get<2>(scalar) /*aScalarType*/, + mozilla::Get<1>(scalar) /*aInputValue*/, + mozilla::MakeStringSpan(mozilla::Get<0>(scalar)) /*aPropertyName*/, + aWriter /*aWriter*/); + if (NS_FAILED(rv)) { + // Skip this scalar if we failed to write it. We don't bail out just + // yet as we may salvage other scalars. We eventually need to call + // EndObject. + continue; + } + } + + aWriter.EndObject(); + } + + return NS_OK; +} + +/** + * Write the keyed scalar data to the provided Json object, for + * GeckoView measurement persistence. The output format is the same + * one used for snapshotting the keyed scalars. + * + * @param {aWriter} The JSON object to write to. + * @returns NS_OK or a failure value explaining why persistence failed. + */ +nsresult TelemetryScalar::SerializeKeyedScalars(mozilla::JSONWriter& aWriter) { + // Get a copy of the data, without clearing. + KeyedScalarSnapshotTable keyedScalarsToReflect; + { + StaticMutexAutoLock locker(gTelemetryScalarsMutex); + // For persistence, we care about all the datasets. Worst case, they + // will be empty. + nsresult rv = internal_GetKeyedScalarSnapshot( + locker, keyedScalarsToReflect, + nsITelemetry::DATASET_PRERELEASE_CHANNELS, false, /*aClearScalars*/ + "main"_ns); + if (NS_FAILED(rv)) { + return rv; + } + } + + // Persist the scalars to the JSON object. + for (const auto& entry : keyedScalarsToReflect) { + const KeyedScalarTupleArray& processScalars = entry.GetData(); + const char* processName = GetNameForProcessID(ProcessID(entry.GetKey())); + + aWriter.StartObjectProperty(mozilla::MakeStringSpan(processName)); + + for (const KeyedScalarDataTuple& keyedScalarData : processScalars) { + aWriter.StartObjectProperty( + mozilla::MakeStringSpan(mozilla::Get<0>(keyedScalarData))); + + // Define a property for each scalar key, then add it to the keyed scalar + // object. + const nsTArray<KeyedScalar::KeyValuePair>& keyProps = + mozilla::Get<1>(keyedScalarData); + for (const KeyedScalar::KeyValuePair& keyData : keyProps) { + nsresult rv = WriteVariantToJSONWriter( + mozilla::Get<2>(keyedScalarData) /*aScalarType*/, + keyData.second /*aInputValue*/, + PromiseFlatCString(keyData.first) /*aOutKey*/, aWriter /*aWriter*/); + if (NS_FAILED(rv)) { + // Skip this scalar if we failed to write it. We don't bail out just + // yet as we may salvage other scalars. We eventually need to call + // EndObject. + continue; + } + } + aWriter.EndObject(); + } + aWriter.EndObject(); + } + + return NS_OK; +} + +/** + * Load the persisted measurements from a Json object and inject them + * in the relevant process storage. + * + * @param {aData} The input Json object. + * @returns NS_OK if loading was performed, an error code explaining the + * failure reason otherwise. + */ +nsresult TelemetryScalar::DeserializePersistedScalars( + JSContext* aCx, JS::Handle<JS::Value> aData) { + MOZ_ASSERT(XRE_IsParentProcess(), "Only load scalars in the parent process"); + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + typedef std::pair<nsCString, nsCOMPtr<nsIVariant>> PersistedScalarPair; + typedef nsTArray<PersistedScalarPair> PersistedScalarArray; + typedef nsTHashMap<ProcessIDHashKey, PersistedScalarArray> + PeristedScalarStorage; + + PeristedScalarStorage scalarsToUpdate; + + // Before updating the scalars, we need to get the data out of the JS + // wrappers. We can't hold the scalars mutex while handling JS stuff. + // Build a <scalar name, value> map. + JS::Rooted<JSObject*> scalarDataObj(aCx, &aData.toObject()); + JS::Rooted<JS::IdVector> processes(aCx, JS::IdVector(aCx)); + if (!JS_Enumerate(aCx, scalarDataObj, &processes)) { + // We can't even enumerate the processes in the loaded data, so + // there is nothing we could recover from the persistence file. Bail out. + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + + // The following block of code attempts to extract as much data as possible + // from the serialized JSON, even in case of light data corruptions: if, for + // example, the data for a single process is corrupted or is in an unexpected + // form, we press on and attempt to load the data for the other processes. + JS::Rooted<JS::PropertyKey> process(aCx); + for (auto& processVal : processes) { + // This is required as JS API calls require an Handle<jsid> and not a + // plain jsid. + process = processVal; + // Get the process name. + nsAutoJSString processNameJS; + if (!processNameJS.init(aCx, process)) { + JS_ClearPendingException(aCx); + continue; + } + + // Make sure it's valid. Note that this is safe to call outside + // of a locked section. + NS_ConvertUTF16toUTF8 processName(processNameJS); + ProcessID processID = GetIDForProcessName(processName.get()); + if (processID == ProcessID::Count) { + NS_WARNING( + nsPrintfCString("Failed to get process ID for %s", processName.get()) + .get()); + continue; + } + + // And its probes. + JS::Rooted<JS::Value> processData(aCx); + if (!JS_GetPropertyById(aCx, scalarDataObj, process, &processData)) { + JS_ClearPendingException(aCx); + continue; + } + + if (!processData.isObject()) { + // |processData| should be an object containing scalars. If this is + // not the case, silently skip and try to load the data for the other + // processes. + continue; + } + + // Iterate through each scalar. + JS::Rooted<JSObject*> processDataObj(aCx, &processData.toObject()); + JS::Rooted<JS::IdVector> scalars(aCx, JS::IdVector(aCx)); + if (!JS_Enumerate(aCx, processDataObj, &scalars)) { + JS_ClearPendingException(aCx); + continue; + } + + JS::Rooted<JS::PropertyKey> scalar(aCx); + for (auto& scalarVal : scalars) { + scalar = scalarVal; + // Get the scalar name. + nsAutoJSString scalarName; + if (!scalarName.init(aCx, scalar)) { + JS_ClearPendingException(aCx); + continue; + } + + // Get the scalar value as a JS value. + JS::Rooted<JS::Value> scalarValue(aCx); + if (!JS_GetPropertyById(aCx, processDataObj, scalar, &scalarValue)) { + JS_ClearPendingException(aCx); + continue; + } + + if (scalarValue.isNullOrUndefined()) { + // We can't set scalars to null or undefined values, skip this + // and try to load other scalars. + continue; + } + + // Unpack the aVal to nsIVariant. + nsCOMPtr<nsIVariant> unpackedVal; + nsresult rv = nsContentUtils::XPConnect()->JSToVariant( + aCx, scalarValue, getter_AddRefs(unpackedVal)); + if (NS_FAILED(rv)) { + JS_ClearPendingException(aCx); + continue; + } + + // Add the scalar to the map. + PersistedScalarArray& processScalars = + scalarsToUpdate.LookupOrInsert(static_cast<uint32_t>(processID)); + processScalars.AppendElement(std::make_pair( + nsCString(NS_ConvertUTF16toUTF8(scalarName)), unpackedVal)); + } + } + + // Now that all the JS specific operations are finished, update the scalars. + { + StaticMutexAutoLock lock(gTelemetryScalarsMutex); + + for (const auto& entry : scalarsToUpdate) { + const PersistedScalarArray& processScalars = entry.GetData(); + for (PersistedScalarArray::size_type i = 0; i < processScalars.Length(); + i++) { + mozilla::Unused << internal_UpdateScalar( + lock, processScalars[i].first, ScalarActionType::eSet, + processScalars[i].second, ProcessID(entry.GetKey()), + true /* aForce */); + } + } + } + + return NS_OK; +} + +/** + * Load the persisted measurements from a Json object and injects them + * in the relevant process storage. + * + * @param {aData} The input Json object. + * @returns NS_OK if loading was performed, an error code explaining the + * failure reason otherwise. + */ +nsresult TelemetryScalar::DeserializePersistedKeyedScalars( + JSContext* aCx, JS::Handle<JS::Value> aData) { + MOZ_ASSERT(XRE_IsParentProcess(), "Only load scalars in the parent process"); + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + typedef mozilla::Tuple<nsCString, nsString, nsCOMPtr<nsIVariant>> + PersistedKeyedScalarTuple; + typedef nsTArray<PersistedKeyedScalarTuple> PersistedKeyedScalarArray; + typedef nsTHashMap<ProcessIDHashKey, PersistedKeyedScalarArray> + PeristedKeyedScalarStorage; + + PeristedKeyedScalarStorage scalarsToUpdate; + + // Before updating the keyed scalars, we need to get the data out of the JS + // wrappers. We can't hold the scalars mutex while handling JS stuff. + // Build a <scalar name, value> map. + JS::Rooted<JSObject*> scalarDataObj(aCx, &aData.toObject()); + JS::Rooted<JS::IdVector> processes(aCx, JS::IdVector(aCx)); + if (!JS_Enumerate(aCx, scalarDataObj, &processes)) { + // We can't even enumerate the processes in the loaded data, so + // there is nothing we could recover from the persistence file. Bail out. + JS_ClearPendingException(aCx); + return NS_ERROR_FAILURE; + } + + // The following block of code attempts to extract as much data as possible + // from the serialized JSON, even in case of light data corruptions: if, for + // example, the data for a single process is corrupted or is in an unexpected + // form, we press on and attempt to load the data for the other processes. + JS::Rooted<JS::PropertyKey> process(aCx); + for (auto& processVal : processes) { + process = processVal; + // Get the process name. + nsAutoJSString processNameJS; + if (!processNameJS.init(aCx, process)) { + JS_ClearPendingException(aCx); + continue; + } + + // Make sure it's valid. Note that this is safe to call outside + // of a locked section. + NS_ConvertUTF16toUTF8 processName(processNameJS); + ProcessID processID = GetIDForProcessName(processName.get()); + if (processID == ProcessID::Count) { + NS_WARNING( + nsPrintfCString("Failed to get process ID for %s", processName.get()) + .get()); + continue; + } + + // And its probes. + JS::Rooted<JS::Value> processData(aCx); + if (!JS_GetPropertyById(aCx, scalarDataObj, process, &processData)) { + JS_ClearPendingException(aCx); + continue; + } + + if (!processData.isObject()) { + // |processData| should be an object containing scalars. If this is + // not the case, silently skip and try to load the data for the other + // processes. + continue; + } + + // Iterate through each keyed scalar. + JS::Rooted<JSObject*> processDataObj(aCx, &processData.toObject()); + JS::Rooted<JS::IdVector> keyedScalars(aCx, JS::IdVector(aCx)); + if (!JS_Enumerate(aCx, processDataObj, &keyedScalars)) { + JS_ClearPendingException(aCx); + continue; + } + + JS::Rooted<JS::PropertyKey> keyedScalar(aCx); + for (auto& keyedScalarVal : keyedScalars) { + keyedScalar = keyedScalarVal; + // Get the scalar name. + nsAutoJSString scalarName; + if (!scalarName.init(aCx, keyedScalar)) { + JS_ClearPendingException(aCx); + continue; + } + + // Get the data for this keyed scalar. + JS::Rooted<JS::Value> keyedScalarData(aCx); + if (!JS_GetPropertyById(aCx, processDataObj, keyedScalar, + &keyedScalarData)) { + JS_ClearPendingException(aCx); + continue; + } + + if (!keyedScalarData.isObject()) { + // Keyed scalar data need to be an object. If that's not the case, skip + // it and try to load the rest of the data. + continue; + } + + // Get the keys in the keyed scalar. + JS::Rooted<JSObject*> keyedScalarDataObj(aCx, + &keyedScalarData.toObject()); + JS::Rooted<JS::IdVector> keys(aCx, JS::IdVector(aCx)); + if (!JS_Enumerate(aCx, keyedScalarDataObj, &keys)) { + JS_ClearPendingException(aCx); + continue; + } + + JS::Rooted<JS::PropertyKey> key(aCx); + for (auto keyVal : keys) { + key = keyVal; + // Get the process name. + nsAutoJSString keyName; + if (!keyName.init(aCx, key)) { + JS_ClearPendingException(aCx); + continue; + } + + // Get the scalar value as a JS value. + JS::Rooted<JS::Value> scalarValue(aCx); + if (!JS_GetPropertyById(aCx, keyedScalarDataObj, key, &scalarValue)) { + JS_ClearPendingException(aCx); + continue; + } + + if (scalarValue.isNullOrUndefined()) { + // We can't set scalars to null or undefined values, skip this + // and try to load other scalars. + continue; + } + + // Unpack the aVal to nsIVariant. + nsCOMPtr<nsIVariant> unpackedVal; + nsresult rv = nsContentUtils::XPConnect()->JSToVariant( + aCx, scalarValue, getter_AddRefs(unpackedVal)); + if (NS_FAILED(rv)) { + JS_ClearPendingException(aCx); + continue; + } + + // Add the scalar to the map. + PersistedKeyedScalarArray& processScalars = + scalarsToUpdate.LookupOrInsert(static_cast<uint32_t>(processID)); + processScalars.AppendElement( + mozilla::MakeTuple(nsCString(NS_ConvertUTF16toUTF8(scalarName)), + nsString(keyName), unpackedVal)); + } + } + } + + // Now that all the JS specific operations are finished, update the scalars. + { + StaticMutexAutoLock lock(gTelemetryScalarsMutex); + + for (const auto& entry : scalarsToUpdate) { + const PersistedKeyedScalarArray& processScalars = entry.GetData(); + for (PersistedKeyedScalarArray::size_type i = 0; + i < processScalars.Length(); i++) { + mozilla::Unused << internal_UpdateKeyedScalar( + lock, mozilla::Get<0>(processScalars[i]), + mozilla::Get<1>(processScalars[i]), ScalarActionType::eSet, + mozilla::Get<2>(processScalars[i]), ProcessID(entry.GetKey()), + true /* aForce */); + } + } + } + + return NS_OK; +} diff --git a/toolkit/components/telemetry/core/TelemetryScalar.h b/toolkit/components/telemetry/core/TelemetryScalar.h new file mode 100644 index 0000000000..c7e5352860 --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryScalar.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryScalar_h__ +#define TelemetryScalar_h__ + +#include <stdint.h> +#include "mozilla/TelemetryProcessEnums.h" +#include "mozilla/TelemetryScalarEnums.h" +#include "nsTArray.h" +#include "TelemetryCommon.h" + +// This module is internal to Telemetry. It encapsulates Telemetry's +// scalar accumulation and storage logic. It should only be used by +// Telemetry.cpp. These functions should not be used anywhere else. +// For the public interface to Telemetry functionality, see Telemetry.h. + +namespace mozilla { +// This is only used for the GeckoView persistence. +class JSONWriter; +namespace Telemetry { +struct ScalarAction; +struct KeyedScalarAction; +struct DiscardedData; +struct DynamicScalarDefinition; +} // namespace Telemetry +} // namespace mozilla + +namespace TelemetryScalar { + +void InitializeGlobalState(bool canRecordBase, bool canRecordExtended); +void DeInitializeGlobalState(); + +void SetCanRecordBase(bool b); +void SetCanRecordExtended(bool b); + +// JS API Endpoints. +nsresult Add(const nsACString& aName, JS::Handle<JS::Value> aVal, + JSContext* aCx); +nsresult Set(const nsACString& aName, JS::Handle<JS::Value> aVal, + JSContext* aCx); +nsresult SetMaximum(const nsACString& aName, JS::Handle<JS::Value> aVal, + JSContext* aCx); +nsresult CreateSnapshots(unsigned int aDataset, bool aClearScalars, + JSContext* aCx, uint8_t optional_argc, + JS::MutableHandle<JS::Value> aResult, bool aFilterTest, + const nsACString& aStoreName); + +// Keyed JS API Endpoints. +nsresult Add(const nsACString& aName, const nsAString& aKey, + JS::Handle<JS::Value> aVal, JSContext* aCx); +nsresult Set(const nsACString& aName, const nsAString& aKey, + JS::Handle<JS::Value> aVal, JSContext* aCx); +nsresult SetMaximum(const nsACString& aName, const nsAString& aKey, + JS::Handle<JS::Value> aVal, JSContext* aCx); +nsresult CreateKeyedSnapshots(unsigned int aDataset, bool aClearScalars, + JSContext* aCx, uint8_t optional_argc, + JS::MutableHandle<JS::Value> aResult, + bool aFilterTest, const nsACString& aStoreName); + +// C++ API Endpoints. +void Add(mozilla::Telemetry::ScalarID aId, uint32_t aValue); +void Set(mozilla::Telemetry::ScalarID aId, uint32_t aValue); +void Set(mozilla::Telemetry::ScalarID aId, const nsAString& aValue); +void Set(mozilla::Telemetry::ScalarID aId, bool aValue); +void SetMaximum(mozilla::Telemetry::ScalarID aId, uint32_t aValue); + +// Keyed C++ API Endpoints. +void Add(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + uint32_t aValue); +void Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + uint32_t aValue); +void Set(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, bool aValue); +void SetMaximum(mozilla::Telemetry::ScalarID aId, const nsAString& aKey, + uint32_t aValue); + +nsresult RegisterScalars(const nsACString& aCategoryName, + JS::Handle<JS::Value> aScalarData, bool aBuiltin, + JSContext* cx); + +// Event Summary +void SummarizeEvent(const nsCString& aUniqueEventName, + mozilla::Telemetry::ProcessID aProcessType, bool aDynamic); + +// Only to be used for testing. +void ClearScalars(); + +size_t GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf); +size_t GetScalarSizesOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + +void UpdateChildData( + mozilla::Telemetry::ProcessID aProcessType, + const nsTArray<mozilla::Telemetry::ScalarAction>& aScalarActions); + +void UpdateChildKeyedData( + mozilla::Telemetry::ProcessID aProcessType, + const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions); + +void RecordDiscardedData( + mozilla::Telemetry::ProcessID aProcessType, + const mozilla::Telemetry::DiscardedData& aDiscardedData); + +void GetDynamicScalarDefinitions( + nsTArray<mozilla::Telemetry::DynamicScalarDefinition>&); +void AddDynamicScalarDefinitions( + const nsTArray<mozilla::Telemetry::DynamicScalarDefinition>&); + +/** + * Append the list of registered stores to the given set. + * This includes dynamic stores. + */ +nsresult GetAllStores(mozilla::Telemetry::Common::StringHashSet& set); + +// They are responsible for updating in-memory probes with the data persisted +// on the disk and vice-versa. +nsresult SerializeScalars(mozilla::JSONWriter& aWriter); +nsresult SerializeKeyedScalars(mozilla::JSONWriter& aWriter); +nsresult DeserializePersistedScalars(JSContext* aCx, + JS::Handle<JS::Value> aData); +nsresult DeserializePersistedKeyedScalars(JSContext* aCx, + JS::Handle<JS::Value> aData); +// Mark deserialization as in progress. +// After this, all scalar operations are recorded into the pending operations +// list. +void DeserializationStarted(); +// Apply all operations from the pending operations list and mark +// deserialization finished afterwards. +void ApplyPendingOperations(); +} // namespace TelemetryScalar + +#endif // TelemetryScalar_h__ diff --git a/toolkit/components/telemetry/core/TelemetryUserInteraction.cpp b/toolkit/components/telemetry/core/TelemetryUserInteraction.cpp new file mode 100644 index 0000000000..ad6601c22c --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryUserInteraction.cpp @@ -0,0 +1,101 @@ +/* -*- 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 "nsString.h" +#include "MainThreadUtils.h" +#include "TelemetryUserInteraction.h" +#include "TelemetryUserInteractionData.h" +#include "TelemetryUserInteractionNameMap.h" +#include "UserInteractionInfo.h" + +using mozilla::Telemetry::UserInteractionIDByNameLookup; + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE TYPES +namespace { + +struct UserInteractionKey { + uint32_t id; +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE STATE, SHARED BY ALL THREADS + +namespace { +// Set to true once this global state has been initialized. +bool gInitDone = false; + +bool gCanRecordBase; +bool gCanRecordExtended; +} // namespace + +namespace { +// Implements the methods for UserInteractionInfo. +const char* UserInteractionInfo::name() const { + return &gUserInteractionsStringTable[this->name_offset]; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryUserInteraction:: + +void TelemetryUserInteraction::InitializeGlobalState(bool aCanRecordBase, + bool aCanRecordExtended) { + if (!XRE_IsParentProcess()) { + return; + } + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!gInitDone, + "TelemetryUserInteraction::InitializeGlobalState " + "may only be called once"); + + gCanRecordBase = aCanRecordBase; + gCanRecordExtended = aCanRecordExtended; + gInitDone = true; +} + +void TelemetryUserInteraction::DeInitializeGlobalState() { + if (!XRE_IsParentProcess()) { + return; + } + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gInitDone); + if (!gInitDone) { + return; + } + + gInitDone = false; +} + +bool TelemetryUserInteraction::CanRecord(const nsAString& aName) { + if (!gCanRecordBase) { + return false; + } + + nsCString name = NS_ConvertUTF16toUTF8(aName); + const uint32_t idx = UserInteractionIDByNameLookup(name); + + MOZ_DIAGNOSTIC_ASSERT( + idx < mozilla::Telemetry::UserInteractionID::UserInteractionCount, + "Intermediate lookup should always give a valid index."); + + if (name.Equals(gUserInteractions[idx].name())) { + return true; + } + + return false; +} diff --git a/toolkit/components/telemetry/core/TelemetryUserInteraction.h b/toolkit/components/telemetry/core/TelemetryUserInteraction.h new file mode 100644 index 0000000000..2b12a5f275 --- /dev/null +++ b/toolkit/components/telemetry/core/TelemetryUserInteraction.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryUserInteraction_h__ +#define TelemetryUserInteraction_h__ + +#include "nsStringFwd.h" + +namespace TelemetryUserInteraction { + +void InitializeGlobalState(bool canRecordBase, bool canRecordExtended); +void DeInitializeGlobalState(); + +bool CanRecord(const nsAString& aName); + +} // namespace TelemetryUserInteraction + +#endif // TelemetryUserInteraction_h__ diff --git a/toolkit/components/telemetry/core/UserInteractionInfo.h b/toolkit/components/telemetry/core/UserInteractionInfo.h new file mode 100644 index 0000000000..e20017b790 --- /dev/null +++ b/toolkit/components/telemetry/core/UserInteractionInfo.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryUserInteractionInfo_h__ +#define TelemetryUserInteractionInfo_h__ + +#include "TelemetryCommon.h" + +// This module is internal to Telemetry. It defines a structure that holds the +// UserInteraction info. It should only be used by +// TelemetryUserInteractionData.h automatically generated file and +// TelemetryUserInteraction.cpp. This should not be used anywhere else. For the +// public interface to Telemetry functionality, see Telemetry.h. + +namespace { + +struct UserInteractionInfo { + const uint32_t name_offset; + + explicit constexpr UserInteractionInfo(const uint32_t aNameOffset) + : name_offset(aNameOffset) {} + + const char* name() const; +}; + +} // namespace + +#endif // TelemetryUserInteractionInfo_h__ diff --git a/toolkit/components/telemetry/core/components.conf b/toolkit/components/telemetry/core/components.conf new file mode 100644 index 0000000000..82a624f4ec --- /dev/null +++ b/toolkit/components/telemetry/core/components.conf @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Headers = ['mozilla/Telemetry.h'] + +UnloadFunc = 'mozilla::Telemetry::ShutdownTelemetry' + +Classes = [ + { + 'js_name': 'telemetry', + 'cid': '{aea477f2-b3a2-469c-aa29-0a82d132b829}', + 'contract_ids': ['@mozilla.org/base/telemetry;1'], + 'interfaces': ['nsITelemetry'], + 'singleton': True, + 'type': 'nsITelemetry', + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS, + }, +] diff --git a/toolkit/components/telemetry/core/ipc/TelemetryComms.h b/toolkit/components/telemetry/core/ipc/TelemetryComms.h new file mode 100644 index 0000000000..75f59209b0 --- /dev/null +++ b/toolkit/components/telemetry/core/ipc/TelemetryComms.h @@ -0,0 +1,400 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Telemetry_Comms_h__ +#define Telemetry_Comms_h__ + +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryProcessEnums.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Variant.h" +#include "nsITelemetry.h" + +namespace mozilla { +namespace Telemetry { + +// Histogram accumulation types. +enum HistogramID : uint32_t; + +struct HistogramAccumulation { + mozilla::Telemetry::HistogramID mId; + uint32_t mSample; +}; + +struct KeyedHistogramAccumulation { + mozilla::Telemetry::HistogramID mId; + uint32_t mSample; + nsCString mKey; +}; + +// Scalar accumulation types. +enum class ScalarID : uint32_t; + +enum class ScalarActionType : uint32_t { eSet = 0, eAdd = 1, eSetMaximum = 2 }; + +typedef mozilla::Variant<uint32_t, bool, nsString> ScalarVariant; + +struct ScalarAction { + uint32_t mId; + bool mDynamic; + ScalarActionType mActionType; + // We need to wrap mData in a Maybe otherwise the IPC system + // is unable to instantiate a ScalarAction. + Maybe<ScalarVariant> mData; + // The process type this scalar should be recorded for. + // The IPC system will determine the process this action was coming from + // later. + mozilla::Telemetry::ProcessID mProcessType; +}; + +struct KeyedScalarAction { + uint32_t mId; + bool mDynamic; + ScalarActionType mActionType; + nsCString mKey; + // We need to wrap mData in a Maybe otherwise the IPC system + // is unable to instantiate a ScalarAction. + Maybe<ScalarVariant> mData; + // The process type this scalar should be recorded for. + // The IPC system will determine the process this action was coming from + // later. + mozilla::Telemetry::ProcessID mProcessType; +}; + +// Dynamic scalars support. +struct DynamicScalarDefinition { + uint32_t type; + uint32_t dataset; + bool expired; + bool keyed; + bool builtin; + nsCString name; + + bool operator==(const DynamicScalarDefinition& rhs) const { + return type == rhs.type && dataset == rhs.dataset && + expired == rhs.expired && keyed == rhs.keyed && + builtin == rhs.builtin && name.Equals(rhs.name); + } +}; + +struct ChildEventData { + mozilla::TimeStamp timestamp; + nsCString category; + nsCString method; + nsCString object; + mozilla::Maybe<nsCString> value; + CopyableTArray<EventExtraEntry> extra; +}; + +struct DiscardedData { + uint32_t mDiscardedHistogramAccumulations; + uint32_t mDiscardedKeyedHistogramAccumulations; + uint32_t mDiscardedScalarActions; + uint32_t mDiscardedKeyedScalarActions; + uint32_t mDiscardedChildEvents; +}; + +} // namespace Telemetry +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::Telemetry::HistogramAccumulation> { + typedef mozilla::Telemetry::HistogramAccumulation paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + aWriter->WriteUInt32(aParam.mId); + WriteParam(aWriter, aParam.mSample); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!aReader->ReadUInt32(reinterpret_cast<uint32_t*>(&(aResult->mId))) || + !ReadParam(aReader, &(aResult->mSample))) { + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::Telemetry::KeyedHistogramAccumulation> { + typedef mozilla::Telemetry::KeyedHistogramAccumulation paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + aWriter->WriteUInt32(aParam.mId); + WriteParam(aWriter, aParam.mSample); + WriteParam(aWriter, aParam.mKey); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!aReader->ReadUInt32(reinterpret_cast<uint32_t*>(&(aResult->mId))) || + !ReadParam(aReader, &(aResult->mSample)) || + !ReadParam(aReader, &(aResult->mKey))) { + return false; + } + + return true; + } +}; + +/** + * IPC scalar data message serialization and de-serialization. + */ +template <> +struct ParamTraits<mozilla::Telemetry::ScalarAction> { + typedef mozilla::Telemetry::ScalarAction paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + // Write the message type + aWriter->WriteUInt32(aParam.mId); + WriteParam(aWriter, aParam.mDynamic); + WriteParam(aWriter, static_cast<uint32_t>(aParam.mActionType)); + + if (aParam.mData.isNothing()) { + MOZ_CRASH("There is no data in the ScalarAction."); + return; + } + + if (aParam.mData->is<uint32_t>()) { + // That's a nsITelemetry::SCALAR_TYPE_COUNT. + WriteParam(aWriter, + static_cast<uint32_t>(nsITelemetry::SCALAR_TYPE_COUNT)); + WriteParam(aWriter, aParam.mData->as<uint32_t>()); + } else if (aParam.mData->is<nsString>()) { + // That's a nsITelemetry::SCALAR_TYPE_STRING. + WriteParam(aWriter, + static_cast<uint32_t>(nsITelemetry::SCALAR_TYPE_STRING)); + WriteParam(aWriter, aParam.mData->as<nsString>()); + } else if (aParam.mData->is<bool>()) { + // That's a nsITelemetry::SCALAR_TYPE_BOOLEAN. + WriteParam(aWriter, + static_cast<uint32_t>(nsITelemetry::SCALAR_TYPE_BOOLEAN)); + WriteParam(aWriter, aParam.mData->as<bool>()); + } else { + MOZ_CRASH("Unknown scalar type."); + } + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + // Read the scalar ID and the scalar type. + uint32_t scalarType = 0; + if (!aReader->ReadUInt32(reinterpret_cast<uint32_t*>(&(aResult->mId))) || + !ReadParam(aReader, reinterpret_cast<bool*>(&(aResult->mDynamic))) || + !ReadParam(aReader, + reinterpret_cast<uint32_t*>(&(aResult->mActionType))) || + !ReadParam(aReader, &scalarType)) { + return false; + } + + // De-serialize the data based on the scalar type. + switch (scalarType) { + case nsITelemetry::SCALAR_TYPE_COUNT: { + uint32_t data = 0; + // De-serialize the data. + if (!ReadParam(aReader, &data)) { + return false; + } + aResult->mData = mozilla::Some(mozilla::AsVariant(data)); + break; + } + case nsITelemetry::SCALAR_TYPE_STRING: { + nsString data; + // De-serialize the data. + if (!ReadParam(aReader, &data)) { + return false; + } + aResult->mData = mozilla::Some(mozilla::AsVariant(data)); + break; + } + case nsITelemetry::SCALAR_TYPE_BOOLEAN: { + bool data = false; + // De-serialize the data. + if (!ReadParam(aReader, &data)) { + return false; + } + aResult->mData = mozilla::Some(mozilla::AsVariant(data)); + break; + } + default: + MOZ_ASSERT(false, "Unknown scalar type."); + return false; + } + + return true; + } +}; + +/** + * IPC keyed scalar data message serialization and de-serialization. + */ +template <> +struct ParamTraits<mozilla::Telemetry::KeyedScalarAction> { + typedef mozilla::Telemetry::KeyedScalarAction paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + // Write the message type + aWriter->WriteUInt32(static_cast<uint32_t>(aParam.mId)); + WriteParam(aWriter, aParam.mDynamic); + WriteParam(aWriter, static_cast<uint32_t>(aParam.mActionType)); + WriteParam(aWriter, aParam.mKey); + + if (aParam.mData.isNothing()) { + MOZ_CRASH("There is no data in the KeyedScalarAction."); + return; + } + + if (aParam.mData->is<uint32_t>()) { + // That's a nsITelemetry::SCALAR_TYPE_COUNT. + WriteParam(aWriter, + static_cast<uint32_t>(nsITelemetry::SCALAR_TYPE_COUNT)); + WriteParam(aWriter, aParam.mData->as<uint32_t>()); + } else if (aParam.mData->is<nsString>()) { + // That's a nsITelemetry::SCALAR_TYPE_STRING. + // Keyed string scalars are not supported. + MOZ_ASSERT(false, + "Keyed String Scalar unable to be write from child process. " + "Not supported."); + } else if (aParam.mData->is<bool>()) { + // That's a nsITelemetry::SCALAR_TYPE_BOOLEAN. + WriteParam(aWriter, + static_cast<uint32_t>(nsITelemetry::SCALAR_TYPE_BOOLEAN)); + WriteParam(aWriter, aParam.mData->as<bool>()); + } else { + MOZ_CRASH("Unknown keyed scalar type."); + } + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + // Read the scalar ID and the scalar type. + uint32_t scalarType = 0; + if (!aReader->ReadUInt32(reinterpret_cast<uint32_t*>(&(aResult->mId))) || + !ReadParam(aReader, reinterpret_cast<bool*>(&(aResult->mDynamic))) || + !ReadParam(aReader, + reinterpret_cast<uint32_t*>(&(aResult->mActionType))) || + !ReadParam(aReader, &(aResult->mKey)) || + !ReadParam(aReader, &scalarType)) { + return false; + } + + // De-serialize the data based on the scalar type. + switch (scalarType) { + case nsITelemetry::SCALAR_TYPE_COUNT: { + uint32_t data = 0; + // De-serialize the data. + if (!ReadParam(aReader, &data)) { + return false; + } + aResult->mData = mozilla::Some(mozilla::AsVariant(data)); + break; + } + case nsITelemetry::SCALAR_TYPE_STRING: { + // Keyed string scalars are not supported. + MOZ_ASSERT(false, + "Keyed String Scalar unable to be read from child process. " + "Not supported."); + return false; + } + case nsITelemetry::SCALAR_TYPE_BOOLEAN: { + bool data = false; + // De-serialize the data. + if (!ReadParam(aReader, &data)) { + return false; + } + aResult->mData = mozilla::Some(mozilla::AsVariant(data)); + break; + } + default: + MOZ_ASSERT(false, "Unknown keyed scalar type."); + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::Telemetry::DynamicScalarDefinition> { + typedef mozilla::Telemetry::DynamicScalarDefinition paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + nsCString name; + WriteParam(aWriter, aParam.type); + WriteParam(aWriter, aParam.dataset); + WriteParam(aWriter, aParam.expired); + WriteParam(aWriter, aParam.keyed); + WriteParam(aWriter, aParam.builtin); + WriteParam(aWriter, aParam.name); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, reinterpret_cast<uint32_t*>(&(aResult->type))) || + !ReadParam(aReader, reinterpret_cast<uint32_t*>(&(aResult->dataset))) || + !ReadParam(aReader, reinterpret_cast<bool*>(&(aResult->expired))) || + !ReadParam(aReader, reinterpret_cast<bool*>(&(aResult->keyed))) || + !ReadParam(aReader, reinterpret_cast<bool*>(&(aResult->builtin))) || + !ReadParam(aReader, &(aResult->name))) { + return false; + } + return true; + } +}; + +template <> +struct ParamTraits<mozilla::Telemetry::ChildEventData> { + typedef mozilla::Telemetry::ChildEventData paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.timestamp); + WriteParam(aWriter, aParam.category); + WriteParam(aWriter, aParam.method); + WriteParam(aWriter, aParam.object); + WriteParam(aWriter, aParam.value); + WriteParam(aWriter, aParam.extra); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, &(aResult->timestamp)) || + !ReadParam(aReader, &(aResult->category)) || + !ReadParam(aReader, &(aResult->method)) || + !ReadParam(aReader, &(aResult->object)) || + !ReadParam(aReader, &(aResult->value)) || + !ReadParam(aReader, &(aResult->extra))) { + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::Telemetry::EventExtraEntry> { + typedef mozilla::Telemetry::EventExtraEntry paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.key); + WriteParam(aWriter, aParam.value); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, &(aResult->key)) || + !ReadParam(aReader, &(aResult->value))) { + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::Telemetry::DiscardedData> + : public PlainOldDataSerializer<mozilla::Telemetry::DiscardedData> {}; + +} // namespace IPC + +#endif // Telemetry_Comms_h__ diff --git a/toolkit/components/telemetry/core/ipc/TelemetryIPC.cpp b/toolkit/components/telemetry/core/ipc/TelemetryIPC.cpp new file mode 100644 index 0000000000..daeaeece65 --- /dev/null +++ b/toolkit/components/telemetry/core/ipc/TelemetryIPC.cpp @@ -0,0 +1,59 @@ +/* -*- 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 "TelemetryIPC.h" +#include "../TelemetryScalar.h" +#include "../TelemetryHistogram.h" +#include "../TelemetryEvent.h" + +namespace mozilla { + +void TelemetryIPC::AccumulateChildHistograms( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::HistogramAccumulation>& aAccumulations) { + TelemetryHistogram::AccumulateChild(aProcessType, aAccumulations); +} + +void TelemetryIPC::AccumulateChildKeyedHistograms( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::KeyedHistogramAccumulation>& aAccumulations) { + TelemetryHistogram::AccumulateChildKeyed(aProcessType, aAccumulations); +} + +void TelemetryIPC::UpdateChildScalars( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::ScalarAction>& aScalarActions) { + TelemetryScalar::UpdateChildData(aProcessType, aScalarActions); +} + +void TelemetryIPC::UpdateChildKeyedScalars( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::KeyedScalarAction>& aScalarActions) { + TelemetryScalar::UpdateChildKeyedData(aProcessType, aScalarActions); +} + +void TelemetryIPC::GetDynamicScalarDefinitions( + nsTArray<mozilla::Telemetry::DynamicScalarDefinition>& aDefs) { + TelemetryScalar::GetDynamicScalarDefinitions(aDefs); +} + +void TelemetryIPC::AddDynamicScalarDefinitions( + const nsTArray<mozilla::Telemetry::DynamicScalarDefinition>& aDefs) { + TelemetryScalar::AddDynamicScalarDefinitions(aDefs); +} + +void TelemetryIPC::RecordChildEvents( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::ChildEventData>& aEvents) { + TelemetryEvent::RecordChildEvents(aProcessType, aEvents); +} + +void TelemetryIPC::RecordDiscardedData( + Telemetry::ProcessID aProcessType, + const Telemetry::DiscardedData& aDiscardedData) { + TelemetryScalar::RecordDiscardedData(aProcessType, aDiscardedData); +} +} // namespace mozilla diff --git a/toolkit/components/telemetry/core/ipc/TelemetryIPC.h b/toolkit/components/telemetry/core/ipc/TelemetryIPC.h new file mode 100644 index 0000000000..bf45280059 --- /dev/null +++ b/toolkit/components/telemetry/core/ipc/TelemetryIPC.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryIPC_h__ +#define TelemetryIPC_h__ + +#include "mozilla/TelemetryProcessEnums.h" +#include "nsTArray.h" + +// This module provides the interface to accumulate Telemetry from child +// processes. Top-level actors for different child processes types +// (ContentParent, GPUChild) will call this for messages from their respective +// processes. + +namespace mozilla { + +namespace Telemetry { + +struct HistogramAccumulation; +struct KeyedHistogramAccumulation; +struct ScalarAction; +struct KeyedScalarAction; +struct DynamicScalarDefinition; +struct ChildEventData; +struct DiscardedData; + +} // namespace Telemetry + +namespace TelemetryIPC { + +/** + * Accumulate child process data into histograms for the given process type. + * + * @param aProcessType - the process type to accumulate the histograms for + * @param aAccumulations - accumulation actions to perform + */ +void AccumulateChildHistograms( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::HistogramAccumulation>& aAccumulations); + +/** + * Accumulate child process data into keyed histograms for the given process + * type. + * + * @param aProcessType - the process type to accumulate the keyed histograms for + * @param aAccumulations - accumulation actions to perform + */ +void AccumulateChildKeyedHistograms( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::KeyedHistogramAccumulation>& aAccumulations); + +/** + * Update scalars for the given process type with the data coming from child + * process. + * + * @param aProcessType - the process type to process the scalar actions for + * @param aScalarActions - actions to update the scalar data + */ +void UpdateChildScalars( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::ScalarAction>& aScalarActions); + +/** + * Update keyed scalars for the given process type with the data coming from + * child process. + * + * @param aProcessType - the process type to process the keyed scalar actions + * for + * @param aScalarActions - actions to update the keyed scalar data + */ +void UpdateChildKeyedScalars( + Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::KeyedScalarAction>& aScalarActions); + +/** + * Record events for the given process type with the data coming from child + * process. + * + * @param aProcessType - the process type to record the events for + * @param aEvents - events to record + */ +void RecordChildEvents(Telemetry::ProcessID aProcessType, + const nsTArray<Telemetry::ChildEventData>& aEvents); + +/** + * Record the counts of data the child process had to discard + * + * @param aProcessType - the process reporting the discarded data + * @param aDiscardedData - stats about the discarded data + */ +void RecordDiscardedData(Telemetry::ProcessID aProcessType, + const Telemetry::DiscardedData& aDiscardedData); + +/** + * Get the dynamic scalar definitions from the parent process. + * @param aDefs - The array that will contain the scalar definitions. + */ +void GetDynamicScalarDefinitions( + nsTArray<mozilla::Telemetry::DynamicScalarDefinition>& aDefs); + +/** + * Add the dynamic scalar definitions coming from the parent process + * to the current child process. + * @param aDefs - The array that contains the scalar definitions. + */ +void AddDynamicScalarDefinitions( + const nsTArray<mozilla::Telemetry::DynamicScalarDefinition>& aDefs); + +} // namespace TelemetryIPC +} // namespace mozilla + +#endif // TelemetryIPC_h__ diff --git a/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.cpp b/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.cpp new file mode 100644 index 0000000000..fa38aa9264 --- /dev/null +++ b/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.cpp @@ -0,0 +1,348 @@ +/* -*- 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 "TelemetryIPCAccumulator.h" + +#include "core/TelemetryScalar.h" +#include "mozilla/TelemetryProcessEnums.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/net/SocketProcessChild.h" +#include "mozilla/ipc/UtilityProcessChild.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPrefs_toolkit.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" + +using mozilla::StaticAutoPtr; +using mozilla::StaticMutex; +using mozilla::StaticMutexAutoLock; +using mozilla::TaskCategory; +using mozilla::Telemetry::ChildEventData; +using mozilla::Telemetry::DiscardedData; +using mozilla::Telemetry::HistogramAccumulation; +using mozilla::Telemetry::KeyedHistogramAccumulation; +using mozilla::Telemetry::KeyedScalarAction; +using mozilla::Telemetry::ScalarAction; +using mozilla::Telemetry::ScalarActionType; +using mozilla::Telemetry::ScalarVariant; + +namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator; + +// To stop growing unbounded in memory while waiting for +// StaticPrefs::toolkit_telemetry_ipcBatchTimeout() milliseconds to drain the +// probe accumulation arrays, we request an immediate flush if the arrays +// manage to reach certain high water mark of elements. +const size_t kHistogramAccumulationsArrayHighWaterMark = 5 * 1024; +const size_t kScalarActionsArrayHighWaterMark = 10000; +// With the current limits, events cost us about 1100 bytes each. +// This limits memory use to about 10MB. +const size_t kEventsArrayHighWaterMark = 10000; +// If we are starved we can overshoot the watermark. +// This is the multiplier over which we will discard data. +const size_t kWaterMarkDiscardFactor = 5; + +// Counts of how many pieces of data we have discarded. +DiscardedData gDiscardedData = {0}; + +// This timer is used for batching and sending child process accumulations to +// the parent. +nsITimer* gIPCTimer = nullptr; +mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArmed(false); +mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArming(false); + +// This batches child process accumulations that should be sent to the parent. +StaticAutoPtr<nsTArray<HistogramAccumulation>> gHistogramAccumulations; +StaticAutoPtr<nsTArray<KeyedHistogramAccumulation>> + gKeyedHistogramAccumulations; +StaticAutoPtr<nsTArray<ScalarAction>> gChildScalarsActions; +StaticAutoPtr<nsTArray<KeyedScalarAction>> gChildKeyedScalarsActions; +StaticAutoPtr<nsTArray<ChildEventData>> gChildEvents; + +// This is a StaticMutex rather than a plain Mutex so that (1) +// it gets initialised in a thread-safe manner the first time +// it is used, and (2) because it is never de-initialised, and +// a normal Mutex would show up as a leak in BloatView. StaticMutex +// also has the "OffTheBooks" property, so it won't show as a leak +// in BloatView. +static StaticMutex gTelemetryIPCAccumulatorMutex MOZ_UNANNOTATED; + +namespace { + +void DoArmIPCTimerMainThread(const StaticMutexAutoLock& lock) { + MOZ_ASSERT(NS_IsMainThread()); + gIPCTimerArming = false; + if (gIPCTimerArmed) { + return; + } + if (!gIPCTimer) { + gIPCTimer = NS_NewTimer().take(); + } + if (gIPCTimer) { + gIPCTimer->InitWithNamedFuncCallback( + TelemetryIPCAccumulator::IPCTimerFired, nullptr, + mozilla::StaticPrefs::toolkit_telemetry_ipcBatchTimeout(), + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, + "TelemetryIPCAccumulator::IPCTimerFired"); + gIPCTimerArmed = true; + } +} + +void ArmIPCTimer(const StaticMutexAutoLock& lock) { + if (gIPCTimerArmed || gIPCTimerArming) { + return; + } + gIPCTimerArming = true; + if (NS_IsMainThread()) { + DoArmIPCTimerMainThread(lock); + } else { + TelemetryIPCAccumulator::DispatchToMainThread(NS_NewRunnableFunction( + "TelemetryIPCAccumulator::ArmIPCTimer", []() -> void { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + DoArmIPCTimerMainThread(locker); + })); + } +} + +void DispatchIPCTimerFired() { + TelemetryIPCAccumulator::DispatchToMainThread(NS_NewRunnableFunction( + "TelemetryIPCAccumulator::IPCTimerFired", []() -> void { + TelemetryIPCAccumulator::IPCTimerFired(nullptr, nullptr); + })); +} + +} // anonymous namespace + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryIPCAccumulator:: + +void TelemetryIPCAccumulator::AccumulateChildHistogram( + mozilla::Telemetry::HistogramID aId, uint32_t aSample) { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + if (!gHistogramAccumulations) { + gHistogramAccumulations = new nsTArray<HistogramAccumulation>(); + } + if (gHistogramAccumulations->Length() >= + kWaterMarkDiscardFactor * kHistogramAccumulationsArrayHighWaterMark) { + gDiscardedData.mDiscardedHistogramAccumulations++; + return; + } + if (gHistogramAccumulations->Length() == + kHistogramAccumulationsArrayHighWaterMark) { + DispatchIPCTimerFired(); + } + gHistogramAccumulations->AppendElement(HistogramAccumulation{aId, aSample}); + ArmIPCTimer(locker); +} + +void TelemetryIPCAccumulator::AccumulateChildKeyedHistogram( + mozilla::Telemetry::HistogramID aId, const nsCString& aKey, + uint32_t aSample) { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + if (!gKeyedHistogramAccumulations) { + gKeyedHistogramAccumulations = new nsTArray<KeyedHistogramAccumulation>(); + } + if (gKeyedHistogramAccumulations->Length() >= + kWaterMarkDiscardFactor * kHistogramAccumulationsArrayHighWaterMark) { + gDiscardedData.mDiscardedKeyedHistogramAccumulations++; + return; + } + if (gKeyedHistogramAccumulations->Length() == + kHistogramAccumulationsArrayHighWaterMark) { + DispatchIPCTimerFired(); + } + gKeyedHistogramAccumulations->AppendElement( + KeyedHistogramAccumulation{aId, aSample, aKey}); + ArmIPCTimer(locker); +} + +void TelemetryIPCAccumulator::RecordChildScalarAction( + uint32_t aId, bool aDynamic, ScalarActionType aAction, + const ScalarVariant& aValue) { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + // Make sure to have the storage. + if (!gChildScalarsActions) { + gChildScalarsActions = new nsTArray<ScalarAction>(); + } + if (gChildScalarsActions->Length() >= + kWaterMarkDiscardFactor * kScalarActionsArrayHighWaterMark) { + gDiscardedData.mDiscardedScalarActions++; + return; + } + if (gChildScalarsActions->Length() == kScalarActionsArrayHighWaterMark) { + DispatchIPCTimerFired(); + } + // Store the action. The ProcessID will be determined by the receiver. + gChildScalarsActions->AppendElement(ScalarAction{ + aId, aDynamic, aAction, Some(aValue), Telemetry::ProcessID::Count}); + ArmIPCTimer(locker); +} + +void TelemetryIPCAccumulator::RecordChildKeyedScalarAction( + uint32_t aId, bool aDynamic, const nsAString& aKey, + ScalarActionType aAction, const ScalarVariant& aValue) { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + // Make sure to have the storage. + if (!gChildKeyedScalarsActions) { + gChildKeyedScalarsActions = new nsTArray<KeyedScalarAction>(); + } + if (gChildKeyedScalarsActions->Length() >= + kWaterMarkDiscardFactor * kScalarActionsArrayHighWaterMark) { + gDiscardedData.mDiscardedKeyedScalarActions++; + return; + } + if (gChildKeyedScalarsActions->Length() == kScalarActionsArrayHighWaterMark) { + DispatchIPCTimerFired(); + } + // Store the action. The ProcessID will be determined by the receiver. + gChildKeyedScalarsActions->AppendElement( + KeyedScalarAction{aId, aDynamic, aAction, NS_ConvertUTF16toUTF8(aKey), + Some(aValue), Telemetry::ProcessID::Count}); + ArmIPCTimer(locker); +} + +void TelemetryIPCAccumulator::RecordChildEvent( + const mozilla::TimeStamp& timestamp, const nsACString& category, + const nsACString& method, const nsACString& object, + const mozilla::Maybe<nsCString>& value, + const nsTArray<mozilla::Telemetry::EventExtraEntry>& extra) { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + + if (!gChildEvents) { + gChildEvents = new nsTArray<ChildEventData>(); + } + + if (gChildEvents->Length() >= + kWaterMarkDiscardFactor * kEventsArrayHighWaterMark) { + gDiscardedData.mDiscardedChildEvents++; + return; + } + + if (gChildEvents->Length() == kEventsArrayHighWaterMark) { + DispatchIPCTimerFired(); + } + + // Store the event. + gChildEvents->AppendElement( + ChildEventData{timestamp, nsCString(category), nsCString(method), + nsCString(object), value, extra.Clone()}); + ArmIPCTimer(locker); +} + +// This method takes the lock only to double-buffer the batched telemetry. +// It releases the lock before calling out to IPC code which can (and does) +// Accumulate (which would deadlock) +template <class TActor> +static void SendAccumulatedData(TActor* ipcActor) { + // Get the accumulated data and free the storage buffers. + nsTArray<HistogramAccumulation> histogramsToSend; + nsTArray<KeyedHistogramAccumulation> keyedHistogramsToSend; + nsTArray<ScalarAction> scalarsToSend; + nsTArray<KeyedScalarAction> keyedScalarsToSend; + nsTArray<ChildEventData> eventsToSend; + DiscardedData discardedData; + + { + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + if (gHistogramAccumulations) { + histogramsToSend = std::move(*gHistogramAccumulations); + } + if (gKeyedHistogramAccumulations) { + keyedHistogramsToSend = std::move(*gKeyedHistogramAccumulations); + } + if (gChildScalarsActions) { + scalarsToSend = std::move(*gChildScalarsActions); + } + if (gChildKeyedScalarsActions) { + keyedScalarsToSend = std::move(*gChildKeyedScalarsActions); + } + if (gChildEvents) { + eventsToSend = std::move(*gChildEvents); + } + discardedData = gDiscardedData; + gDiscardedData = {0}; + } + + // Send the accumulated data to the parent process. + MOZ_ASSERT(ipcActor); + if (histogramsToSend.Length()) { + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendAccumulateChildHistograms(histogramsToSend)); + } + if (keyedHistogramsToSend.Length()) { + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendAccumulateChildKeyedHistograms(keyedHistogramsToSend)); + } + if (scalarsToSend.Length()) { + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendUpdateChildScalars(scalarsToSend)); + } + if (keyedScalarsToSend.Length()) { + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendUpdateChildKeyedScalars(keyedScalarsToSend)); + } + if (eventsToSend.Length()) { + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendRecordChildEvents(eventsToSend)); + } + mozilla::Unused << NS_WARN_IF( + !ipcActor->SendRecordDiscardedData(discardedData)); +} + +// To ensure we don't loop IPCTimerFired->AccumulateChild->arm timer, we don't +// unset gIPCTimerArmed until the IPC completes +// +// This function must be called on the main thread, otherwise IPC will fail. +void TelemetryIPCAccumulator::IPCTimerFired(nsITimer* aTimer, void* aClosure) { + MOZ_ASSERT(NS_IsMainThread()); + + // Send accumulated data to the correct parent process. + switch (XRE_GetProcessType()) { + case GeckoProcessType_Content: + SendAccumulatedData(mozilla::dom::ContentChild::GetSingleton()); + break; + case GeckoProcessType_GPU: + SendAccumulatedData(mozilla::gfx::GPUParent::GetSingleton()); + break; + case GeckoProcessType_Socket: + SendAccumulatedData(mozilla::net::SocketProcessChild::GetSingleton()); + break; + case GeckoProcessType_Utility: + SendAccumulatedData( + mozilla::ipc::UtilityProcessChild::GetSingleton().get()); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported process type"); + break; + } + + gIPCTimerArmed = false; +} + +void TelemetryIPCAccumulator::DeInitializeGlobalState() { + MOZ_ASSERT(NS_IsMainThread()); + + StaticMutexAutoLock locker(gTelemetryIPCAccumulatorMutex); + if (gIPCTimer) { + NS_RELEASE(gIPCTimer); + } + + gHistogramAccumulations = nullptr; + gKeyedHistogramAccumulations = nullptr; + gChildScalarsActions = nullptr; + gChildKeyedScalarsActions = nullptr; + gChildEvents = nullptr; +} + +void TelemetryIPCAccumulator::DispatchToMainThread( + already_AddRefed<nsIRunnable>&& aEvent) { + SchedulerGroup::Dispatch(TaskCategory::Other, std::move(aEvent)); +} diff --git a/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.h b/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.h new file mode 100644 index 0000000000..e77ca95391 --- /dev/null +++ b/toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TelemetryIPCAccumulator_h__ +#define TelemetryIPCAccumulator_h__ + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Maybe.h" +#include "nsStringFwd.h" +#include "mozilla/Telemetry.h" // for EventExtraEntry +#include "mozilla/TelemetryComms.h" // for ScalarActionType, Scala... +#include "mozilla/TelemetryHistogramEnums.h" // for HistogramID + +class nsIRunnable; +class nsITimer; + +namespace mozilla { + +class TimeStamp; + +namespace TelemetryIPCAccumulator { + +// Histogram accumulation functions. +void AccumulateChildHistogram(mozilla::Telemetry::HistogramID aId, + uint32_t aSample); +void AccumulateChildKeyedHistogram(mozilla::Telemetry::HistogramID aId, + const nsCString& aKey, uint32_t aSample); + +// Scalar accumulation functions. +void RecordChildScalarAction(uint32_t aId, bool aDynamic, + mozilla::Telemetry::ScalarActionType aAction, + const mozilla::Telemetry::ScalarVariant& aValue); + +void RecordChildKeyedScalarAction( + uint32_t aId, bool aDynamic, const nsAString& aKey, + mozilla::Telemetry::ScalarActionType aAction, + const mozilla::Telemetry::ScalarVariant& aValue); + +void RecordChildEvent( + const mozilla::TimeStamp& timestamp, const nsACString& category, + const nsACString& method, const nsACString& object, + const mozilla::Maybe<nsCString>& value, + const nsTArray<mozilla::Telemetry::EventExtraEntry>& extra); + +void IPCTimerFired(nsITimer* aTimer, void* aClosure); + +void DeInitializeGlobalState(); + +void DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent); + +} // namespace TelemetryIPCAccumulator +} // namespace mozilla + +#endif // TelemetryIPCAccumulator_h__ diff --git a/toolkit/components/telemetry/core/nsITelemetry.idl b/toolkit/components/telemetry/core/nsITelemetry.idl new file mode 100644 index 0000000000..85868178db --- /dev/null +++ b/toolkit/components/telemetry/core/nsITelemetry.idl @@ -0,0 +1,702 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 "nsISupports.idl" +#include "nsIFile.idl" + +[scriptable,function, uuid(3d3b9075-5549-4244-9c08-b64fefa1dd60)] +interface nsIFetchTelemetryDataCallback : nsISupports +{ + void complete(); +}; + +[scriptable, uuid(273d2dd0-6c63-475a-b864-cb65160a1909)] +interface nsITelemetry : nsISupports +{ + /** + * Histogram types: + * HISTOGRAM_EXPONENTIAL - buckets increase exponentially + * HISTOGRAM_LINEAR - buckets increase linearly + * HISTOGRAM_BOOLEAN - For storing 0/1 values + * HISTOGRAM_FLAG - For storing a single value; its count is always == 1. + * HISTOGRAM_COUNT - For storing counter values without bucketing. + * HISTOGRAM_CATEGORICAL - For storing enumerated values by label. + */ + const unsigned long HISTOGRAM_EXPONENTIAL = 0; + const unsigned long HISTOGRAM_LINEAR = 1; + const unsigned long HISTOGRAM_BOOLEAN = 2; + const unsigned long HISTOGRAM_FLAG = 3; + const unsigned long HISTOGRAM_COUNT = 4; + const unsigned long HISTOGRAM_CATEGORICAL = 5; + + /** + * Scalar types: + * SCALAR_TYPE_COUNT - for storing a numeric value + * SCALAR_TYPE_STRING - for storing a string value + * SCALAR_TYPE_BOOLEAN - for storing a boolean value + */ + const unsigned long SCALAR_TYPE_COUNT = 0; + const unsigned long SCALAR_TYPE_STRING = 1; + const unsigned long SCALAR_TYPE_BOOLEAN = 2; + + /** + * Dataset types: + * DATASET_ALL_CHANNELS - the basic dataset that is on-by-default on all channels + * DATASET_PRERELEASE_CHANNELS - the extended dataset that is opt-in on release, + * opt-out on pre-release channels. + */ + const unsigned long DATASET_ALL_CHANNELS = 0; + const unsigned long DATASET_PRERELEASE_CHANNELS = 1; + + /** + * Serializes the histogram labels for categorical hitograms. + * The returned structure looks like: + * { "histogram1": [ "histogram1_label1", "histogram1_label2", ...], + * "histogram2": [ "histogram2_label1", "histogram2_label2", ...] + * ... + * } + * + * Note that this function should only be used in tests and about:telemetry. + */ + [implicit_jscontext] + jsval getCategoricalLabels(); + + /** + * Serializes the histograms from the given store to a JSON-style object. + * The returned structure looks like: + * { "process": { "name1": histogramData1, "name2": histogramData2 }, ... } + * + * Each histogram is represented in a packed format and has the following properties: + * bucket_count - Number of buckets of this histogram + * histogram_type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR, HISTOGRAM_BOOLEAN, + * HISTOGRAM_FLAG, HISTOGRAM_COUNT, or HISTOGRAM_CATEGORICAL + * sum - sum of the bucket contents + * range - A 2-item array of minimum and maximum bucket size + * values - Map from bucket to the bucket's count + * + * @param aStoreName The name of the store to snapshot. + * Defaults to "main". + * Custom stores are available when probes have them defined. + * See the `record_into_store` attribute on histograms. + * @see https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/histograms.html#record-into-store + * @param aClearStore Whether to clear out the histograms in the named store after snapshotting. + * Defaults to false. + * @param aFilterTest If true, `TELEMETRY_TEST_` histograms will be filtered out. + Filtered histograms are still cleared if `aClearStore` is true. + * Defaults to false. + */ + [implicit_jscontext] + jsval getSnapshotForHistograms([optional] in ACString aStoreName, [optional] in boolean aClearStore, [optional] in boolean aFilterTest); + + /** + * Serializes the keyed histograms from the given store to a JSON-style object. + * The returned structure looks like: + * { "process": { "name1": { "key_1": histogramData1, "key_2": histogramData2 }, ...}, ... } + * + * @param aStoreName The name of the store to snapshot. + * Defaults to "main". + * Custom stores are available when probes have them defined. + * See the `record_into_store` attribute on histograms. + * @see https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/histograms.html#record-into-store + * @param aClearStore Whether to clear out the keyed histograms in the named store after snapshotting. + * Defaults to false. + * @param aFilterTest If true, `TELEMETRY_TEST_` histograms will be filtered out. + Filtered histograms are still cleared if `aClearStore` is true. + * Defaults to false. + */ + [implicit_jscontext] + jsval getSnapshotForKeyedHistograms([optional] in ACString aStoreName, [optional] in boolean aClearStore, [optional] in boolean aFilterTest); + + /** + * Serializes the scalars from the given store to a JSON-style object. + * The returned structure looks like: + * { "process": { "category1.probe": 1,"category1.other_probe": false, ... }, ... }. + * + * @param aStoreName The name of the store to snapshot. + * Defaults to "main". + * Custom stores are available when probes have them defined. + * See the `record_into_store` attribute on scalars. + * @see https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/scalars.html#optional-fields + * @param aClearStore Whether to clear out the scalars in the named store after snapshotting. + * Defaults to false. + * @param aFilterTest If true, `telemetry.test` scalars will be filtered out. + Filtered scalars are still cleared if `aClearStore` is true. + * Defaults to false. + */ + [implicit_jscontext] + jsval getSnapshotForScalars([optional] in ACString aStoreName, [optional] in boolean aClearStore, [optional] in boolean aFilterTest); + + /** + * Serializes the keyed scalars from the given store to a JSON-style object. + * The returned structure looks like: + * { "process": { "category1.probe": { "key_1": 2, "key_2": 1, ... }, ... }, ... } + * + * @param aStoreName The name of the store to snapshot. + * Defaults to "main". + * Custom stores are available when probes have them defined. + * See the `record_into_store` attribute on scalars. + * @see https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/scalars.html#optional-fields + * @param aClearStore Whether to clear out the keyed scalars in the named store after snapshotting. + * Defaults to false. + * @param aFilterTest If true, `telemetry.test` scalars will be filtered out. + Filtered scalars are still cleared if `aClearStore` is true. + * Defaults to false. + */ + [implicit_jscontext] + jsval getSnapshotForKeyedScalars([optional] in ACString aStoreName, [optional] in boolean aClearStore, [optional] in boolean aFilterTest); + + /** + * The amount of time, in milliseconds, that the last session took + * to shutdown. Reads as 0 to indicate failure. + */ + readonly attribute uint32_t lastShutdownDuration; + + /** + * The number of failed profile lock attempts that have occurred prior to + * successfully locking the profile + */ + readonly attribute uint32_t failedProfileLockCount; + + /* + * An object containing information about slow SQL statements. + * + * { + * mainThread: { "sqlString1": [<hit count>, <total time>], "sqlString2": [...], ... }, + * otherThreads: { "sqlString3": [<hit count>, <total time>], "sqlString4": [...], ... } + * } + * + * where: + * mainThread: Slow statements that executed on the main thread + * otherThreads: Slow statements that executed on a non-main thread + * sqlString - String of the offending statement (see note) + * hit count - The number of times this statement required longer than the threshold time to execute + * total time - The sum of all execution times above the threshold time for this statement + * + * Note that dynamic SQL strings and SQL strings executed against addon DBs could contain private information. + * This property represents such SQL as aggregate database-level stats and the sqlString contains the database + * filename instead. + */ + [implicit_jscontext] + readonly attribute jsval slowSQL; + + /* + * See slowSQL above. + * + * An object containing full strings of every slow SQL statement if toolkit.telemetry.debugSlowSql = true + * The returned SQL strings may contain private information and should not be reported to Telemetry. + */ + [implicit_jscontext] + readonly attribute jsval debugSlowSQL; + + /** + * Flags for getUntrustedModuleLoadEvents. + */ + + /** + * This flag is set to retrieve all data including instances which have been + * retrieved before. If not set, only new instances since the last call + * will be returned. + * If this flag is set, KEEP_LOADEVENTS_NEW must not be set unless + * EXCLUDE_STACKINFO_FROM_LOADEVENTS is set. + * (See also MultiGetUntrustedModulesData::Serialize.) + */ + const unsigned long INCLUDE_OLD_LOADEVENTS = 1 << 0; + + /** + * This flag is set to keep the returned instances as if they were not + * retrieved, meaning those instances will be returned by a next method + * call without INCLUDE_OLD_LOADEVENTS. If not set, the returned instances + * can be re-retrieved only when INCLUDE_OLD_LOADEVENTS is specified. + * If this flag is set, INCLUDE_OLD_LOADEVENTS must not be set unless + * EXCLUDE_STACKINFO_FROM_LOADEVENTS is set. + * (See also MultiGetUntrustedModulesData::Serialize.) + */ + const unsigned long KEEP_LOADEVENTS_NEW = 1 << 1; + + /** + * This flag is set to include private fields. + * Do not specify this flag to retrieve data to be submitted. + */ + const unsigned long INCLUDE_PRIVATE_FIELDS_IN_LOADEVENTS = 1 << 2; + + /** + * This flag is set to exclude the "combinedStacks" field. + * Without this flag, the flags INCLUDE_OLD_LOADEVENTS and KEEP_LOADEVENTS_NEW + * cannot be set at the same time. + */ + const unsigned long EXCLUDE_STACKINFO_FROM_LOADEVENTS = 1 << 3; + + /* + * An array of untrusted module load events. Each element describes one or + * more modules that were loaded, contextual information at the time of the + * event (including stack trace), and flags describing the module's + * trustworthiness. + * + * @param aFlags Combination (bitwise OR) of the flags specified above. + * Defaults to 0. + */ + [implicit_jscontext] + Promise getUntrustedModuleLoadEvents([optional] in unsigned long aFlags); + + /* + * Asynchronously get an array of the modules loaded in the process. + * + * The data has the following structure: + * + * [ + * { + * "name": <string>, // Name of the module file (e.g. xul.dll) + * "version": <string>, // Version of the module + * "debugName": <string>, // ID of the debug information file + * "debugID": <string>, // Name of the debug information file + * "certSubject": <string>, // Name of the organization that signed the binary (Optional, only defined when present) + * }, + * ... + * ] + * + * @return A promise that resolves to an array of modules or rejects with + NS_ERROR_FAILURE on failure. + * @throws NS_ERROR_NOT_IMPLEMENTED if the Gecko profiler is not enabled. + */ + [implicit_jscontext] + Promise getLoadedModules(); + + /* + * An object with two fields: memoryMap and stacks. + * * memoryMap is a list of loaded libraries. + * * stacks is a list of stacks. Each stack is a list of pairs of the form + * [moduleIndex, offset]. The moduleIndex is an index into the memoryMap and + * offset is an offset in the library at memoryMap[moduleIndex]. + * This format is used to make it easier to send the stacks to the + * symbolication server. + */ + [implicit_jscontext] + readonly attribute jsval lateWrites; + + /** + * Create and return a histogram registered in TelemetryHistograms.h. + * + * @param id - unique identifier from TelemetryHistograms.h + * The returned object has the following functions: + * add(value) - Adds a sample of `value` to the histogram. + `value` may be a categorical histogram's label as a string, + a boolean histogram's value as a boolean, + or a number that fits inside a uint32_t. + * snapshot([optional] {store}) - Returns a snapshot of the histogram with the same data fields + as in getSnapshotForHistograms(). + Defaults to the "main" store. + * clear([optional] {store}) - Zeros out the histogram's buckets and sum. + Defaults to the "main" store. + Note: This is intended to be only used in tests. + */ + [implicit_jscontext] + jsval getHistogramById(in ACString id); + + /** + * Create and return a histogram registered in TelemetryHistograms.h. + * + * @param id - unique identifier from TelemetryHistograms.h + * The returned object has the following functions: + * add(string key, [optional] value) - Adds a sample of `value` to the histogram for that key. + If no histogram for that key exists yet, it is created. + `value` may be a categorical histogram's label as a string, + a boolean histogram's value as a boolean, + or a number that fits inside a uint32_t. + * snapshot([optional] {store}) - Returns the snapshots of all the registered keys in the form + {key1: snapshot1, key2: snapshot2, ...} in the specified store. + * Defaults to the "main" store. + * keys([optional] {store}) - Returns an array with the string keys of the currently registered + histograms in the given store. + Defaults to "main". + * clear([optional] {store}) - Clears the registered histograms from this. + * Defaults to the "main" store. + * Note: This is intended to be only used in tests. + */ + [implicit_jscontext] + jsval getKeyedHistogramById(in ACString id); + + /** + * A flag indicating if Telemetry can record base data (FHR data). This is true if the + * FHR data reporting service or self-support are enabled. + * + * In the unlikely event that adding a new base probe is needed, please check the data + * collection wiki at https://wiki.mozilla.org/Firefox/Data_Collection and talk to the + * Telemetry team. + */ + attribute boolean canRecordBase; + + /** + * A flag indicating if Telemetry is allowed to record extended data. Returns false if + * the user hasn't opted into "extended Telemetry" on the Release channel, when the + * user has explicitly opted out of Telemetry on Nightly/Aurora/Beta or if manually + * set to false during tests. + * + * Set this to false in tests to disable gathering of extended telemetry statistics. + */ + attribute boolean canRecordExtended; + + /** + * A flag indicating whether Telemetry is recording release data, which is a + * smallish subset of our usage data that we're prepared to handle from our + * largish release population. + * + * This is true most of the time. + * + * This will always return true in the case of a non-content child process. + * Only values returned on the parent process are valid. + * + * This does not indicate whether Telemetry will send any data. That is + * governed by user preference and other mechanisms. + * + * You may use this to determine if it's okay to record your data. + */ + readonly attribute boolean canRecordReleaseData; + + /** + * A flag indicating whether Telemetry is recording prerelease data, which is + * a largish amount of usage data that we're prepared to handle from our + * smallish pre-release population. + * + * This is true on pre-release branches of Firefox. + * + * This does not indicate whether Telemetry will send any data. That is + * governed by user preference and other mechanisms. + * + * You may use this to determine if it's okay to record your data. + */ + readonly attribute boolean canRecordPrereleaseData; + + /** + * A flag indicating whether Telemetry can submit official results (for base or extended + * data). This is true on official, non-debug builds with built in support for Mozilla + * Telemetry reporting. + * + * This will always return true in the case of a non-content child process. + * Only values returned on the parent process are valid. + */ + readonly attribute boolean isOfficialTelemetry; + + /** + * Enable/disable recording for this histogram at runtime. + * Recording is enabled by default, unless listed at kRecordingInitiallyDisabledIDs[]. + * Name must be a valid Histogram identifier, otherwise an assertion will be triggered. + * + * @param id - unique identifier from histograms.json + * @param enabled - whether or not to enable recording from now on. + */ + void setHistogramRecordingEnabled(in ACString id, in boolean enabled); + + /** + * Read data from the previous run. After the callback is called, the last + * shutdown time is available in lastShutdownDuration and any late + * writes in lateWrites. + */ + void asyncFetchTelemetryData(in nsIFetchTelemetryDataCallback aCallback); + + /** + * Get statistics of file IO reports, null, if not recorded. + * + * The statistics are returned as an object whose propoerties are the names + * of the files that have been accessed and whose corresponding values are + * arrays of size three, representing startup, normal, and shutdown stages. + * Each stage's entry is either null or an array with the layout + * [total_time, #creates, #reads, #writes, #fsyncs, #stats] + */ + [implicit_jscontext] + readonly attribute jsval fileIOReports; + + /** + * Return the number of milliseconds since process start using monotonic + * timestamps (unaffected by system clock changes). On Windows, this includes + * the period of time the device was suspended. On Linux and macOS, this does + * not include the period of time the device was suspneded. + */ + double msSinceProcessStart(); + + /** + * Return the number of milliseconds since process start using monotonic + * timestamps (unaffected by system clock changes), including the periods of + * time the device was suspended. + * @throws NS_ERROR_NOT_AVAILABLE if unavailable. + */ + double msSinceProcessStartIncludingSuspend(); + + /** + * Return the number of milliseconds since process start using monotonic + * timestamps (unaffected by system clock changes), excluding the periods of + * time the device was suspended. + * @throws NS_ERROR_NOT_AVAILABLE if unavailable. + */ + double msSinceProcessStartExcludingSuspend(); + + /** + * Time since the system wide epoch. This is not a monotonic timer but + * can be used across process boundaries. + */ + double msSystemNow(); + + /** + * Adds the value to the given scalar. + * + * @param aName The scalar name. + * @param aValue The numeric value to add to the scalar. Only unsigned integers supported. + */ + [implicit_jscontext] + void scalarAdd(in ACString aName, in jsval aValue); + + /** + * Sets the scalar to the given value. + * + * @param aName The scalar name. + * @param aValue The value to set the scalar to. If the type of aValue doesn't match the + * type of the scalar, the function will fail. For scalar string types, the this + * is truncated to 50 characters. + */ + [implicit_jscontext] + void scalarSet(in ACString aName, in jsval aValue); + + /** + * Sets the scalar to the maximum of the current and the passed value. + * + * @param aName The scalar name. + * @param aValue The numeric value to set the scalar to. Only unsigned integers supported. + */ + [implicit_jscontext] + void scalarSetMaximum(in ACString aName, in jsval aValue); + + /** + * Adds the value to the given keyed scalar. + * + * @param aName The scalar name. + * @param aKey The key name. + * @param aValue The numeric value to add to the scalar. Only unsigned integers supported. + */ + [implicit_jscontext] + void keyedScalarAdd(in ACString aName, in AString aKey, in jsval aValue); + + /** + * Sets the keyed scalar to the given value. + * + * @param aName The scalar name. + * @param aKey The key name. + * @param aValue The value to set the scalar to. If the type of aValue doesn't match the + * type of the scalar, the function will fail. + */ + [implicit_jscontext] + void keyedScalarSet(in ACString aName, in AString aKey, in jsval aValue); + + /** + * Sets the keyed scalar to the maximum of the current and the passed value. + * + * @param aName The scalar name. + * @param aKey The key name. + * @param aValue The numeric value to set the scalar to. Only unsigned integers supported. + */ + [implicit_jscontext] + void keyedScalarSetMaximum(in ACString aName, in AString aKey, in jsval aValue); + + /** + * Resets all the stored scalars. This is intended to be only used in tests. + */ + void clearScalars(); + + /** + * Immediately sends any Telemetry batched on this process to the parent + * process. This is intended only to be used on process shutdown. + */ + void flushBatchedChildTelemetry(); + + /** + * Record an event in Telemetry. + * + * @param aCategory The category name. + * @param aMethod The method name. + * @param aObject The object name. + * @param aValue An optional string value to record. + * @param aExtra An optional object of the form (string -> string). + * It should only contain registered extra keys. + * + * @throws NS_ERROR_INVALID_ARG When trying to record an unknown event. + */ + [implicit_jscontext, optional_argc] + void recordEvent(in ACString aCategory, in ACString aMethod, in ACString aObject, [optional] in jsval aValue, [optional] in jsval extra); + + /** + * Enable recording of events in a category. + * Events default to recording disabled. This allows to toggle recording for all events + * in the specified category. + * + * @param aCategory The category name. + * @param aEnabled Whether recording is enabled for events in that category. + */ + void setEventRecordingEnabled(in ACString aCategory, in boolean aEnabled); + + /** + * Serializes the recorded events to a JSON-appropriate array and optionally resets them. + * The returned structure looks like this: + * [ + * // [timestamp, category, method, object, stringValue, extraValues] + * [43245, "category1", "method1", "object1", "string value", null], + * [43258, "category1", "method2", "object1", null, {"key1": "string value"}], + * ... + * ] + * + * @param aDataset DATASET_ALL_CHANNELS or DATASET_PRERELEASE_CHANNELS. + * @param [aClear=false] Whether to clear out the flushed events after snapshotting. + * @param aEventLimit How many events per process to limit the snapshot to contain, all if unspecified. + * Even if aClear, the leftover event records are not cleared. + */ + [implicit_jscontext, optional_argc] + jsval snapshotEvents(in uint32_t aDataset, [optional] in boolean aClear, [optional] in uint32_t aEventLimit); + + /** + * Register new events to record them from addons. This allows registering multiple + * events for a category. They will be valid only for the current Firefox session. + * Note that events shipping in Firefox should be registered in Events.yaml. + * + * @param aCategory The unique category the events are registered in. + * @param aEventData An object that contains registration data for 1-N events of the form: + * { + * "categoryName": { + * "methods": ["test1"], + * "objects": ["object1"], + * "record_on_release": false, + * "extra_keys": ["key1", "key2"], // optional + * "expired": false // optional, defaults to false. + * }, + * ... + * } + * @param aEventData.<name>.methods List of methods for this event entry. + * @param aEventData.<name>.objects List of objects for this event entry. + * @param aEventData.<name>.extra_keys Optional, list of allowed extra keys for this event entry. + * @param aEventData.<name>.record_on_release Optional, whether to record this data on release. + * Defaults to false. + * @param aEventData.<name>.expired Optional, whether this event entry is expired. This allows + * recording it without error, but it will be discarded. Defaults to false. + */ + [implicit_jscontext] + void registerEvents(in ACString aCategory, in jsval aEventData); + + /** + * Parent process only. Register dynamic builtin events. The parameters + * have the same meaning as the usual |registerEvents| function. + * + * This function is only meant to be used to support the "artifact build"/ + * "build faster" developers by allowing to add new events without rebuilding + * the C++ components including the headers files. + */ + [implicit_jscontext] + void registerBuiltinEvents(in ACString aCategory, in jsval aEventData); + + /** + * Parent process only. Register new scalars to record them from addons. This + * allows registering multiple scalars for a category. They will be valid only for + * the current Firefox session. + * Note that scalars shipping in Firefox should be registered in Scalars.yaml. + * + * @param aCategoryName The unique category the scalars are registered in. + * @param aScalarData An object that contains registration data for multiple scalars in the form: + * { + * "sample_scalar": { + * "kind": Ci.nsITelemetry.SCALAR_TYPE_COUNT, + * "keyed": true, //optional, defaults to false + * "record_on_release: true, // optional, defaults to false + * "expired": false // optional, defaults to false. + * }, + * ... + * } + * @param aScalarData.<name>.kind One of the scalar types defined in this file (SCALAR_TYPE_*) + * @param aScalarData.<name>.keyed Optional, whether this is a keyed scalar or not. Defaults to false. + * @param aScalarData.<name>.record_on_release Optional, whether to record this data on release. + * Defaults to false. + * @param aScalarData.<name>.expired Optional, whether this scalar entry is expired. This allows + * recording it without error, but it will be discarded. Defaults to false. + */ + [implicit_jscontext] + void registerScalars(in ACString aCategoryName, in jsval aScalarData); + + /** + * Parent process only. Register dynamic builtin scalars. The parameters + * have the same meaning as the usual |registerScalars| function. + * + * This function is only meant to be used to support the "artifact build"/ + * "build faster" developers by allowing to add new scalars without rebuilding + * the C++ components including the headers files. + */ + [implicit_jscontext] + void registerBuiltinScalars(in ACString aCategoryName, in jsval aScalarData); + + /** + * Resets all the stored events. This is intended to be only used in tests. + * Events recorded but not yet flushed to the parent process storage won't be cleared. + * Override the pref. `toolkit.telemetry.ipcBatchTimeout` to reduce the time to flush events. + */ + void clearEvents(); + + /** + * Get a list of all registered stores. + * + * The list is deduplicated, but unordered. + */ + [implicit_jscontext] + jsval getAllStores(); + + /** + * Does early, cheap initialization for native telemetry data providers. + * Currently, this includes only MemoryTelemetry. + */ + void earlyInit(); + + /** + * Does late, expensive initialization for native telemetry data providers. + * Currently, this includes only MemoryTelemetry. + * + * This should only be called after startup has completed and the event loop + * is idle. + */ + void delayedInit(); + + /** + * Shuts down native telemetry providers. Currently, this includes only + * MemoryTelemetry. + */ + void shutdown(); + + /** + * Gathers telemetry data for memory usage and records it to the data store. + * Returns a promise which resolves when asynchronous data collection has + * completed and all data has been recorded. + */ + [implicit_jscontext] + Promise gatherMemory(); + + /** + * Serializes the per-origin data in plain text, optionally clearing + * the storage. Only to be used by about:telemetry. + * + * The returned structure looks like: + * { metric: {origin1: count1, origin2: count2, ...}, ...} + * + * @param aClear Whether to clear the storage. Default false. + * @return a snapshot of the per-origin data. + */ + [implicit_jscontext] + jsval getOriginSnapshot([optional] in boolean aClear); + + /** + * Encodes the per-origin information then serializes it. + * Returns a Promise. + * + * @param aClear Whether to clear the storage. Default false. + * @return Promise that resolves to the serialized encoded data. + */ + [implicit_jscontext] + Promise getEncodedOriginSnapshot([optional] in boolean aClear); + + /** + * Clears Firefox Origin Telemetry. Only to be used in tests. + */ + void clearOrigins(); +}; |