summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/core
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/core')
-rw-r--r--toolkit/components/telemetry/core/EventInfo.h57
-rw-r--r--toolkit/components/telemetry/core/ScalarInfo.h94
-rw-r--r--toolkit/components/telemetry/core/Stopwatch.cpp750
-rw-r--r--toolkit/components/telemetry/core/Stopwatch.h84
-rw-r--r--toolkit/components/telemetry/core/Telemetry.cpp2079
-rw-r--r--toolkit/components/telemetry/core/Telemetry.h590
-rw-r--r--toolkit/components/telemetry/core/TelemetryCommon.cpp209
-rw-r--r--toolkit/components/telemetry/core/TelemetryCommon.h197
-rw-r--r--toolkit/components/telemetry/core/TelemetryEvent.cpp1387
-rw-r--r--toolkit/components/telemetry/core/TelemetryEvent.h71
-rw-r--r--toolkit/components/telemetry/core/TelemetryHistogram.cpp3681
-rw-r--r--toolkit/components/telemetry/core/TelemetryHistogram.h124
-rw-r--r--toolkit/components/telemetry/core/TelemetryOrigin.cpp618
-rw-r--r--toolkit/components/telemetry/core/TelemetryOrigin.h46
-rw-r--r--toolkit/components/telemetry/core/TelemetryOriginData.inc2488
-rw-r--r--toolkit/components/telemetry/core/TelemetryScalar.cpp4192
-rw-r--r--toolkit/components/telemetry/core/TelemetryScalar.h133
-rw-r--r--toolkit/components/telemetry/core/TelemetryUserInteraction.cpp101
-rw-r--r--toolkit/components/telemetry/core/TelemetryUserInteraction.h20
-rw-r--r--toolkit/components/telemetry/core/UserInteractionInfo.h30
-rw-r--r--toolkit/components/telemetry/core/components.conf21
-rw-r--r--toolkit/components/telemetry/core/ipc/TelemetryComms.h400
-rw-r--r--toolkit/components/telemetry/core/ipc/TelemetryIPC.cpp59
-rw-r--r--toolkit/components/telemetry/core/ipc/TelemetryIPC.h114
-rw-r--r--toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.cpp348
-rw-r--r--toolkit/components/telemetry/core/ipc/TelemetryIPCAccumulator.h56
-rw-r--r--toolkit/components/telemetry/core/nsITelemetry.idl702
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(&timestamp)))) {
+ 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(&timestamp)))) {
+ 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();
+};