LCOV - code coverage report
Current view: top level - toolkit/components/telemetry - TelemetryEvent.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 55 343 16.0 %
Date: 2017-07-14 16:53:18 Functions: 11 35 31.4 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include <prtime.h>
       8             : #include "nsITelemetry.h"
       9             : #include "nsHashKeys.h"
      10             : #include "nsDataHashtable.h"
      11             : #include "nsClassHashtable.h"
      12             : #include "nsTArray.h"
      13             : #include "mozilla/StaticMutex.h"
      14             : #include "mozilla/Unused.h"
      15             : #include "mozilla/Maybe.h"
      16             : #include "mozilla/StaticPtr.h"
      17             : #include "mozilla/Pair.h"
      18             : #include "jsapi.h"
      19             : #include "nsJSUtils.h"
      20             : #include "nsXULAppAPI.h"
      21             : #include "nsUTF8Utils.h"
      22             : 
      23             : #include "TelemetryCommon.h"
      24             : #include "TelemetryEvent.h"
      25             : #include "TelemetryEventData.h"
      26             : #include "ipc/TelemetryIPCAccumulator.h"
      27             : 
      28             : using mozilla::StaticMutex;
      29             : using mozilla::StaticMutexAutoLock;
      30             : using mozilla::ArrayLength;
      31             : using mozilla::Maybe;
      32             : using mozilla::Nothing;
      33             : using mozilla::StaticAutoPtr;
      34             : using mozilla::TimeStamp;
      35             : using mozilla::Telemetry::Common::AutoHashtable;
      36             : using mozilla::Telemetry::Common::IsExpiredVersion;
      37             : using mozilla::Telemetry::Common::CanRecordDataset;
      38             : using mozilla::Telemetry::Common::IsInDataset;
      39             : using mozilla::Telemetry::Common::MsSinceProcessStart;
      40             : using mozilla::Telemetry::Common::LogToBrowserConsole;
      41             : using mozilla::Telemetry::Common::CanRecordInProcess;
      42             : using mozilla::Telemetry::Common::GetNameForProcessID;
      43             : using mozilla::Telemetry::EventExtraEntry;
      44             : using mozilla::Telemetry::ChildEventData;
      45             : using mozilla::Telemetry::ProcessID;
      46             : 
      47             : namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
      48             : 
      49             : ////////////////////////////////////////////////////////////////////////
      50             : ////////////////////////////////////////////////////////////////////////
      51             : //
      52             : // Naming: there are two kinds of functions in this file:
      53             : //
      54             : // * Functions taking a StaticMutexAutoLock: these can only be reached via
      55             : //   an interface function (TelemetryEvent::*). They expect the interface
      56             : //   function to have acquired |gTelemetryEventsMutex|, so they do not
      57             : //   have to be thread-safe.
      58             : //
      59             : // * Functions named TelemetryEvent::*. This is the external interface.
      60             : //   Entries and exits to these functions are serialised using
      61             : //   |gTelemetryEventsMutex|.
      62             : //
      63             : // Avoiding races and deadlocks:
      64             : //
      65             : // All functions in the external interface (TelemetryEvent::*) are
      66             : // serialised using the mutex |gTelemetryEventsMutex|. This means
      67             : // that the external interface is thread-safe, and the internal
      68             : // functions can ignore thread safety. But it also brings a danger
      69             : // of deadlock if any function in the external interface can get back
      70             : // to that interface. That is, we will deadlock on any call chain like
      71             : // this:
      72             : //
      73             : // TelemetryEvent::* -> .. any functions .. -> TelemetryEvent::*
      74             : //
      75             : // To reduce the danger of that happening, observe the following rules:
      76             : //
      77             : // * No function in TelemetryEvent::* may directly call, nor take the
      78             : //   address of, any other function in TelemetryEvent::*.
      79             : //
      80             : // * No internal function may call, nor take the address
      81             : //   of, any function in TelemetryEvent::*.
      82             : 
      83             : ////////////////////////////////////////////////////////////////////////
      84             : ////////////////////////////////////////////////////////////////////////
      85             : //
      86             : // PRIVATE TYPES
      87             : 
      88             : namespace {
      89             : 
      90             : const uint32_t kEventCount = mozilla::Telemetry::EventID::EventCount;
      91             : // This is a special event id used to mark expired events, to make expiry checks
      92             : // faster at runtime.
      93             : const uint32_t kExpiredEventId = kEventCount + 1;
      94             : static_assert(kEventCount < kExpiredEventId, "Should not overflow.");
      95             : 
      96             : // This is the hard upper limit on the number of event records we keep in storage.
      97             : // If we cross this limit, we will drop any further event recording until elements
      98             : // are removed from storage.
      99             : const uint32_t kMaxEventRecords = 1000;
     100             : // Maximum length of any passed value string, in UTF8 byte sequence length.
     101             : const uint32_t kMaxValueByteLength = 80;
     102             : // Maximum length of any string value in the extra dictionary, in UTF8 byte sequence length.
     103             : const uint32_t kMaxExtraValueByteLength = 80;
     104             : 
     105             : typedef nsDataHashtable<nsCStringHashKey, uint32_t> StringUintMap;
     106             : typedef nsClassHashtable<nsCStringHashKey, nsCString> StringMap;
     107             : 
     108             : enum class RecordEventResult {
     109             :   Ok,
     110             :   UnknownEvent,
     111             :   InvalidExtraKey,
     112             :   StorageLimitReached,
     113             :   ExpiredEvent,
     114             :   WrongProcess,
     115             : };
     116             : 
     117             : typedef nsTArray<EventExtraEntry> ExtraArray;
     118             : 
     119           0 : class EventRecord {
     120             : public:
     121           0 :   EventRecord(double timestamp, uint32_t eventId, const Maybe<nsCString>& value,
     122             :               const ExtraArray& extra)
     123           0 :     : mTimestamp(timestamp)
     124             :     , mEventId(eventId)
     125             :     , mValue(value)
     126           0 :     , mExtra(extra)
     127           0 :   {}
     128             : 
     129           0 :   EventRecord(const EventRecord& other)
     130           0 :     : mTimestamp(other.mTimestamp)
     131           0 :     , mEventId(other.mEventId)
     132             :     , mValue(other.mValue)
     133           0 :     , mExtra(other.mExtra)
     134           0 :   {}
     135             : 
     136             :   EventRecord& operator=(const EventRecord& other) = delete;
     137             : 
     138           0 :   double Timestamp() const { return mTimestamp; }
     139           0 :   uint32_t EventId() const { return mEventId; }
     140           0 :   const Maybe<nsCString>& Value() const { return mValue; }
     141           0 :   const ExtraArray& Extra() const { return mExtra; }
     142             : 
     143             :   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
     144             : 
     145             : private:
     146             :   const double mTimestamp;
     147             :   const uint32_t mEventId;
     148             :   const Maybe<nsCString> mValue;
     149             :   const ExtraArray mExtra;
     150             : };
     151             : 
     152             : // Implements the methods for EventInfo.
     153             : const char*
     154          78 : EventInfo::method() const
     155             : {
     156          78 :   return &gEventsStringTable[this->method_offset];
     157             : }
     158             : 
     159             : const char*
     160          78 : EventInfo::object() const
     161             : {
     162          78 :   return &gEventsStringTable[this->object_offset];
     163             : }
     164             : 
     165             : // Implements the methods for CommonEventInfo.
     166             : const char*
     167         165 : CommonEventInfo::category() const
     168             : {
     169         165 :   return &gEventsStringTable[this->category_offset];
     170             : }
     171             : 
     172             : const char*
     173          78 : CommonEventInfo::expiration_version() const
     174             : {
     175          78 :   return &gEventsStringTable[this->expiration_version_offset];
     176             : }
     177             : 
     178             : const char*
     179           0 : CommonEventInfo::extra_key(uint32_t index) const
     180             : {
     181           0 :   MOZ_ASSERT(index < this->extra_count);
     182           0 :   uint32_t key_index = gExtraKeysTable[this->extra_index + index];
     183           0 :   return &gEventsStringTable[key_index];
     184             : }
     185             : 
     186             : // Implementation for the EventRecord class.
     187             : size_t
     188           0 : EventRecord::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
     189             : {
     190           0 :   size_t n = 0;
     191             : 
     192           0 :   if (mValue) {
     193           0 :     n += mValue.value().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
     194             :   }
     195             : 
     196           0 :   n += mExtra.ShallowSizeOfExcludingThis(aMallocSizeOf);
     197           0 :   for (uint32_t i = 0; i < mExtra.Length(); ++i) {
     198           0 :     n += mExtra[i].key.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
     199           0 :     n += mExtra[i].value.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
     200             :   }
     201             : 
     202           0 :   return n;
     203             : }
     204             : 
     205             : nsCString
     206          78 : UniqueEventName(const nsACString& category, const nsACString& method, const nsACString& object)
     207             : {
     208          78 :   nsCString name;
     209          78 :   name.Append(category);
     210          78 :   name.AppendLiteral("#");
     211          78 :   name.Append(method);
     212          78 :   name.AppendLiteral("#");
     213          78 :   name.Append(object);
     214          78 :   return name;
     215             : }
     216             : 
     217             : nsCString
     218          78 : UniqueEventName(const EventInfo& info)
     219             : {
     220         156 :   return UniqueEventName(nsDependentCString(info.common_info.category()),
     221         156 :                          nsDependentCString(info.method()),
     222         234 :                          nsDependentCString(info.object()));
     223             : }
     224             : 
     225             : bool
     226          72 : IsExpiredDate(uint32_t expires_days_since_epoch) {
     227          72 :   if (expires_days_since_epoch == 0) {
     228          63 :     return false;
     229             :   }
     230             : 
     231           9 :   const uint32_t days_since_epoch = PR_Now() / (PRTime(PR_USEC_PER_SEC) * 24 * 60 * 60);
     232           9 :   return expires_days_since_epoch <= days_since_epoch;
     233             : }
     234             : 
     235             : void
     236           0 : TruncateToByteLength(nsCString& str, uint32_t length)
     237             : {
     238             :   // last will be the index of the first byte of the current multi-byte sequence.
     239           0 :   uint32_t last = RewindToPriorUTF8Codepoint(str.get(), length);
     240           0 :   str.Truncate(last);
     241           0 : }
     242             : 
     243             : } // anonymous namespace
     244             : 
     245             : ////////////////////////////////////////////////////////////////////////
     246             : ////////////////////////////////////////////////////////////////////////
     247             : //
     248             : // PRIVATE STATE, SHARED BY ALL THREADS
     249             : 
     250             : namespace {
     251             : 
     252             : // Set to true once this global state has been initialized.
     253             : bool gInitDone = false;
     254             : 
     255             : bool gCanRecordBase;
     256             : bool gCanRecordExtended;
     257             : 
     258             : // The EventName -> EventID cache map.
     259           3 : StringUintMap gEventNameIDMap(kEventCount);
     260             : 
     261             : // The CategoryName -> CategoryID cache map.
     262           3 : StringUintMap gCategoryNameIDMap;
     263             : 
     264             : // This tracks the IDs of the categories for which recording is enabled.
     265           3 : nsTHashtable<nsUint32HashKey> gEnabledCategories;
     266             : 
     267             : // The main event storage. Events are inserted here, keyed by process id and
     268             : // in recording order.
     269             : typedef nsTArray<EventRecord> EventRecordArray;
     270           3 : nsClassHashtable<nsUint32HashKey, EventRecordArray> gEventRecords;
     271             : 
     272             : } // namespace
     273             : 
     274             : ////////////////////////////////////////////////////////////////////////
     275             : ////////////////////////////////////////////////////////////////////////
     276             : //
     277             : // PRIVATE: thread-safe helpers for event recording.
     278             : 
     279             : namespace {
     280             : 
     281             : bool
     282           0 : CanRecordEvent(const StaticMutexAutoLock& lock, const CommonEventInfo& info,
     283             :                ProcessID process)
     284             : {
     285           0 :   if (!gCanRecordBase) {
     286           0 :     return false;
     287             :   }
     288             : 
     289           0 :   if (!CanRecordDataset(info.dataset, gCanRecordBase, gCanRecordExtended)) {
     290           0 :     return false;
     291             :   }
     292             : 
     293           0 :   if (!CanRecordInProcess(info.record_in_processes, process)) {
     294           0 :     return false;
     295             :   }
     296             : 
     297           0 :   return gEnabledCategories.GetEntry(info.category_offset);
     298             : }
     299             : 
     300             : EventRecordArray*
     301           0 : GetEventRecordsForProcess(const StaticMutexAutoLock& lock, ProcessID processType)
     302             : {
     303           0 :   EventRecordArray* eventRecords = nullptr;
     304           0 :   if (!gEventRecords.Get(uint32_t(processType), &eventRecords)) {
     305           0 :     eventRecords = new EventRecordArray();
     306           0 :     gEventRecords.Put(uint32_t(processType), eventRecords);
     307             :   }
     308           0 :   return eventRecords;
     309             : }
     310             : 
     311             : bool
     312           0 : GetEventId(const StaticMutexAutoLock& lock, const nsACString& category,
     313             :            const nsACString& method, const nsACString& object,
     314             :            uint32_t* eventId)
     315             : {
     316           0 :   MOZ_ASSERT(eventId);
     317           0 :   const nsCString& name = UniqueEventName(category, method, object);
     318           0 :   return gEventNameIDMap.Get(name, eventId);
     319             : }
     320             : 
     321             : RecordEventResult
     322           0 : RecordEvent(const StaticMutexAutoLock& lock, ProcessID processType,
     323             :             double timestamp, const nsACString& category,
     324             :             const nsACString& method, const nsACString& object,
     325             :             const Maybe<nsCString>& value, const ExtraArray& extra)
     326             : {
     327           0 :   EventRecordArray* eventRecords = GetEventRecordsForProcess(lock, processType);
     328             : 
     329             :   // Apply hard limit on event count in storage.
     330           0 :   if (eventRecords->Length() >= kMaxEventRecords) {
     331           0 :     return RecordEventResult::StorageLimitReached;
     332             :   }
     333             : 
     334             :   // Look up the event id.
     335             :   uint32_t eventId;
     336           0 :   if (!GetEventId(lock, category, method, object, &eventId)) {
     337           0 :     return RecordEventResult::UnknownEvent;
     338             :   }
     339             : 
     340             :   // If the event is expired or not enabled for this process, we silently drop this call.
     341             :   // We don't want recording for expired probes to be an error so code doesn't
     342             :   // have to be removed at a specific time or version.
     343             :   // Even logging warnings would become very noisy.
     344           0 :   if (eventId == kExpiredEventId) {
     345           0 :     return RecordEventResult::ExpiredEvent;
     346             :   }
     347             : 
     348             :   // Check whether we can record this event.
     349           0 :   const CommonEventInfo& common = gEventInfo[eventId].common_info;
     350           0 :   if (!CanRecordEvent(lock, common, processType)) {
     351           0 :     return RecordEventResult::Ok;
     352             :   }
     353             : 
     354             :   // Check whether the extra keys passed are valid.
     355           0 :   nsTHashtable<nsCStringHashKey> validExtraKeys;
     356           0 :   for (uint32_t i = 0; i < common.extra_count; ++i) {
     357           0 :     validExtraKeys.PutEntry(nsDependentCString(common.extra_key(i)));
     358             :   }
     359             : 
     360           0 :   for (uint32_t i = 0; i < extra.Length(); ++i) {
     361           0 :     if (!validExtraKeys.GetEntry(extra[i].key)) {
     362           0 :       return RecordEventResult::InvalidExtraKey;
     363             :     }
     364             :   }
     365             : 
     366             :   // Add event record.
     367           0 :   eventRecords->AppendElement(EventRecord(timestamp, eventId, value, extra));
     368           0 :   return RecordEventResult::Ok;
     369             : }
     370             : 
     371             : RecordEventResult
     372           0 : ShouldRecordChildEvent(const StaticMutexAutoLock& lock, const nsACString& category,
     373             :                        const nsACString& method, const nsACString& object)
     374             : {
     375             :   uint32_t eventId;
     376           0 :   if (!GetEventId(lock, category, method, object, &eventId)) {
     377           0 :     return RecordEventResult::UnknownEvent;
     378             :   }
     379             : 
     380           0 :   if (eventId == kExpiredEventId) {
     381           0 :     return RecordEventResult::ExpiredEvent;
     382             :   }
     383             : 
     384           0 :   const auto processes = gEventInfo[eventId].common_info.record_in_processes;
     385           0 :   if (!CanRecordInProcess(processes, XRE_GetProcessType())) {
     386           0 :     return RecordEventResult::WrongProcess;
     387             :   }
     388             : 
     389           0 :   return RecordEventResult::Ok;
     390             : }
     391             : 
     392             : } // anonymous namespace
     393             : 
     394             : ////////////////////////////////////////////////////////////////////////
     395             : ////////////////////////////////////////////////////////////////////////
     396             : //
     397             : // PRIVATE: thread-unsafe helpers for event handling.
     398             : 
     399             : namespace {
     400             : 
     401             : nsresult
     402           0 : SerializeEventsArray(const EventRecordArray& events,
     403             :                     JSContext* cx,
     404             :                     JS::MutableHandleObject result)
     405             : {
     406             :   // We serialize the events to a JS array.
     407           0 :   JS::RootedObject eventsArray(cx, JS_NewArrayObject(cx, events.Length()));
     408           0 :   if (!eventsArray) {
     409           0 :     return NS_ERROR_FAILURE;
     410             :   }
     411             : 
     412           0 :   for (uint32_t i = 0; i < events.Length(); ++i) {
     413           0 :     const EventRecord& record = events[i];
     414           0 :     const EventInfo& info = gEventInfo[record.EventId()];
     415             : 
     416             :     // Each entry is an array of one of the forms:
     417             :     // [timestamp, category, method, object, value]
     418             :     // [timestamp, category, method, object, null, extra]
     419             :     // [timestamp, category, method, object, value, extra]
     420           0 :     JS::AutoValueVector items(cx);
     421             : 
     422             :     // Add timestamp.
     423           0 :     JS::Rooted<JS::Value> val(cx);
     424           0 :     if (!items.append(JS::NumberValue(floor(record.Timestamp())))) {
     425           0 :       return NS_ERROR_FAILURE;
     426             :     }
     427             : 
     428             :     // Add category, method, object.
     429             :     const char* strings[] = {
     430           0 :       info.common_info.category(),
     431           0 :       info.method(),
     432           0 :       info.object(),
     433           0 :     };
     434           0 :     for (const char* s : strings) {
     435           0 :       const NS_ConvertUTF8toUTF16 wide(s);
     436           0 :       if (!items.append(JS::StringValue(JS_NewUCStringCopyN(cx, wide.Data(), wide.Length())))) {
     437           0 :         return NS_ERROR_FAILURE;
     438             :       }
     439             :     }
     440             : 
     441             :     // Add the optional string value only when needed.
     442             :     // When the value field is empty and extra is not set, we can save a little space that way.
     443             :     // We still need to submit a null value if extra is set, to match the form:
     444             :     // [ts, category, method, object, null, extra]
     445           0 :     if (record.Value()) {
     446           0 :       const NS_ConvertUTF8toUTF16 wide(record.Value().value());
     447           0 :       if (!items.append(JS::StringValue(JS_NewUCStringCopyN(cx, wide.Data(), wide.Length())))) {
     448           0 :         return NS_ERROR_FAILURE;
     449             :       }
     450           0 :     } else if (!record.Extra().IsEmpty()) {
     451           0 :       if (!items.append(JS::NullValue())) {
     452           0 :         return NS_ERROR_FAILURE;
     453             :       }
     454             :     }
     455             : 
     456             :     // Add the optional extra dictionary.
     457             :     // To save a little space, only add it when it is not empty.
     458           0 :     if (!record.Extra().IsEmpty()) {
     459           0 :       JS::RootedObject obj(cx, JS_NewPlainObject(cx));
     460           0 :       if (!obj) {
     461           0 :         return NS_ERROR_FAILURE;
     462             :       }
     463             : 
     464             :       // Add extra key & value entries.
     465           0 :       const ExtraArray& extra = record.Extra();
     466           0 :       for (uint32_t i = 0; i < extra.Length(); ++i) {
     467           0 :         const NS_ConvertUTF8toUTF16 wide(extra[i].value);
     468           0 :         JS::Rooted<JS::Value> value(cx);
     469           0 :         value.setString(JS_NewUCStringCopyN(cx, wide.Data(), wide.Length()));
     470             : 
     471           0 :         if (!JS_DefineProperty(cx, obj, extra[i].key.get(), value, JSPROP_ENUMERATE)) {
     472           0 :           return NS_ERROR_FAILURE;
     473             :         }
     474             :       }
     475           0 :       val.setObject(*obj);
     476             : 
     477           0 :       if (!items.append(val)) {
     478           0 :         return NS_ERROR_FAILURE;
     479             :       }
     480             :     }
     481             : 
     482             :     // Add the record to the events array.
     483           0 :     JS::RootedObject itemsArray(cx, JS_NewArrayObject(cx, items));
     484           0 :     if (!JS_DefineElement(cx, eventsArray, i, itemsArray, JSPROP_ENUMERATE)) {
     485           0 :       return NS_ERROR_FAILURE;
     486             :     }
     487             :   }
     488             : 
     489           0 :   result.set(eventsArray);
     490           0 :   return NS_OK;
     491             : }
     492             : 
     493             : } // anonymous namespace
     494             : 
     495             : ////////////////////////////////////////////////////////////////////////
     496             : ////////////////////////////////////////////////////////////////////////
     497             : //
     498             : // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryEvents::
     499             : 
     500             : // This is a StaticMutex rather than a plain Mutex (1) so that
     501             : // it gets initialised in a thread-safe manner the first time
     502             : // it is used, and (2) because it is never de-initialised, and
     503             : // a normal Mutex would show up as a leak in BloatView.  StaticMutex
     504             : // also has the "OffTheBooks" property, so it won't show as a leak
     505             : // in BloatView.
     506             : // Another reason to use a StaticMutex instead of a plain Mutex is
     507             : // that, due to the nature of Telemetry, we cannot rely on having a
     508             : // mutex initialized in InitializeGlobalState. Unfortunately, we
     509             : // cannot make sure that no other function is called before this point.
     510           3 : static StaticMutex gTelemetryEventsMutex;
     511             : 
     512             : void
     513           3 : TelemetryEvent::InitializeGlobalState(bool aCanRecordBase, bool aCanRecordExtended)
     514             : {
     515           6 :   StaticMutexAutoLock locker(gTelemetryEventsMutex);
     516           3 :   MOZ_ASSERT(!gInitDone, "TelemetryEvent::InitializeGlobalState "
     517             :              "may only be called once");
     518             : 
     519           3 :   gCanRecordBase = aCanRecordBase;
     520           3 :   gCanRecordExtended = aCanRecordExtended;
     521             : 
     522             :   // Populate the static event name->id cache. Note that the event names are
     523             :   // statically allocated and come from the automatically generated TelemetryEventData.h.
     524           3 :   const uint32_t eventCount = static_cast<uint32_t>(mozilla::Telemetry::EventID::EventCount);
     525          81 :   for (uint32_t i = 0; i < eventCount; ++i) {
     526          78 :     const EventInfo& info = gEventInfo[i];
     527          78 :     uint32_t eventId = i;
     528             : 
     529             :     // If this event is expired or not recorded in this process, mark it with
     530             :     // a special event id.
     531             :     // This avoids doing repeated checks at runtime.
     532         150 :     if (IsExpiredVersion(info.common_info.expiration_version()) ||
     533          72 :         IsExpiredDate(info.common_info.expiration_day)) {
     534          12 :       eventId = kExpiredEventId;
     535             :     }
     536             : 
     537          78 :     gEventNameIDMap.Put(UniqueEventName(info), eventId);
     538          78 :     if (!gCategoryNameIDMap.Contains(nsDependentCString(info.common_info.category()))) {
     539          18 :       gCategoryNameIDMap.Put(nsDependentCString(info.common_info.category()),
     540          18 :                              info.common_info.category_offset);
     541             :     }
     542             :   }
     543             : 
     544             : #ifdef DEBUG
     545           3 :   gEventNameIDMap.MarkImmutable();
     546           3 :   gCategoryNameIDMap.MarkImmutable();
     547             : #endif
     548           3 :   gInitDone = true;
     549           3 : }
     550             : 
     551             : void
     552           0 : TelemetryEvent::DeInitializeGlobalState()
     553             : {
     554           0 :   StaticMutexAutoLock locker(gTelemetryEventsMutex);
     555           0 :   MOZ_ASSERT(gInitDone);
     556             : 
     557           0 :   gCanRecordBase = false;
     558           0 :   gCanRecordExtended = false;
     559             : 
     560           0 :   gEventNameIDMap.Clear();
     561           0 :   gCategoryNameIDMap.Clear();
     562           0 :   gEnabledCategories.Clear();
     563           0 :   gEventRecords.Clear();
     564             : 
     565           0 :   gInitDone = false;
     566           0 : }
     567             : 
     568             : void
     569           0 : TelemetryEvent::SetCanRecordBase(bool b)
     570             : {
     571           0 :   StaticMutexAutoLock locker(gTelemetryEventsMutex);
     572           0 :   gCanRecordBase = b;
     573           0 : }
     574             : 
     575             : void
     576           3 : TelemetryEvent::SetCanRecordExtended(bool b) {
     577           6 :   StaticMutexAutoLock locker(gTelemetryEventsMutex);
     578           3 :   gCanRecordExtended = b;
     579           3 : }
     580             : 
     581             : nsresult
     582           0 : TelemetryEvent::RecordChildEvents(ProcessID aProcessType,
     583             :                                   const nsTArray<mozilla::Telemetry::ChildEventData>& aEvents)
     584             : {
     585           0 :   MOZ_ASSERT(XRE_IsParentProcess());
     586           0 :   StaticMutexAutoLock locker(gTelemetryEventsMutex);
     587           0 :   for (uint32_t i = 0; i < aEvents.Length(); ++i) {
     588           0 :     const mozilla::Telemetry::ChildEventData e = aEvents[i];
     589             : 
     590             :     // Timestamps from child processes are absolute. We fix them up here to be
     591             :     // relative to the main process start time.
     592             :     // This allows us to put events from all processes on the same timeline.
     593           0 :     double relativeTimestamp = (e.timestamp - TimeStamp::ProcessCreation()).ToMilliseconds();
     594             : 
     595           0 :     ::RecordEvent(locker, aProcessType, relativeTimestamp, e.category, e.method, e.object, e.value, e.extra);
     596             :   }
     597           0 :   return NS_OK;
     598             : }
     599             : 
     600             : nsresult
     601           0 : TelemetryEvent::RecordEvent(const nsACString& aCategory, const nsACString& aMethod,
     602             :                             const nsACString& aObject, JS::HandleValue aValue,
     603             :                             JS::HandleValue aExtra, JSContext* cx,
     604             :                             uint8_t optional_argc)
     605             : {
     606             :   // Check value argument.
     607           0 :   if ((optional_argc > 0) && !aValue.isNull() && !aValue.isString()) {
     608           0 :     LogToBrowserConsole(nsIScriptError::warningFlag,
     609           0 :                         NS_LITERAL_STRING("Invalid type for value parameter."));
     610           0 :     return NS_OK;
     611             :   }
     612             : 
     613             :   // Extract value parameter.
     614           0 :   Maybe<nsCString> value;
     615           0 :   if (aValue.isString()) {
     616           0 :     nsAutoJSString jsStr;
     617           0 :     if (!jsStr.init(cx, aValue)) {
     618           0 :       LogToBrowserConsole(nsIScriptError::warningFlag,
     619           0 :                           NS_LITERAL_STRING("Invalid string value for value parameter."));
     620           0 :       return NS_OK;
     621             :     }
     622             : 
     623           0 :     nsCString str = NS_ConvertUTF16toUTF8(jsStr);
     624           0 :     if (str.Length() > kMaxValueByteLength) {
     625           0 :       LogToBrowserConsole(nsIScriptError::warningFlag,
     626           0 :                           NS_LITERAL_STRING("Value parameter exceeds maximum string length, truncating."));
     627           0 :       TruncateToByteLength(str, kMaxValueByteLength);
     628             :     }
     629           0 :     value = mozilla::Some(str);
     630             :   }
     631             : 
     632             :   // Check extra argument.
     633           0 :   if ((optional_argc > 1) && !aExtra.isNull() && !aExtra.isObject()) {
     634           0 :     LogToBrowserConsole(nsIScriptError::warningFlag,
     635           0 :                         NS_LITERAL_STRING("Invalid type for extra parameter."));
     636           0 :     return NS_OK;
     637             :   }
     638             : 
     639             :   // Extract extra dictionary.
     640           0 :   ExtraArray extra;
     641           0 :   if (aExtra.isObject()) {
     642           0 :     JS::RootedObject obj(cx, &aExtra.toObject());
     643           0 :     JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx));
     644           0 :     if (!JS_Enumerate(cx, obj, &ids)) {
     645           0 :       LogToBrowserConsole(nsIScriptError::warningFlag,
     646           0 :                           NS_LITERAL_STRING("Failed to enumerate object."));
     647           0 :       return NS_OK;
     648             :     }
     649             : 
     650           0 :     for (size_t i = 0, n = ids.length(); i < n; i++) {
     651           0 :       nsAutoJSString key;
     652           0 :       if (!key.init(cx, ids[i])) {
     653           0 :         LogToBrowserConsole(nsIScriptError::warningFlag,
     654           0 :                             NS_LITERAL_STRING("Extra dictionary should only contain string keys."));
     655           0 :         return NS_OK;
     656             :       }
     657             : 
     658           0 :       JS::Rooted<JS::Value> value(cx);
     659           0 :       if (!JS_GetPropertyById(cx, obj, ids[i], &value)) {
     660           0 :         LogToBrowserConsole(nsIScriptError::warningFlag,
     661           0 :                             NS_LITERAL_STRING("Failed to get extra property."));
     662           0 :         return NS_OK;
     663             :       }
     664             : 
     665           0 :       nsAutoJSString jsStr;
     666           0 :       if (!value.isString() || !jsStr.init(cx, value)) {
     667           0 :         LogToBrowserConsole(nsIScriptError::warningFlag,
     668           0 :                             NS_LITERAL_STRING("Extra properties should have string values."));
     669           0 :         return NS_OK;
     670             :       }
     671             : 
     672           0 :       nsCString str = NS_ConvertUTF16toUTF8(jsStr);
     673           0 :       if (str.Length() > kMaxExtraValueByteLength) {
     674           0 :         LogToBrowserConsole(nsIScriptError::warningFlag,
     675           0 :                             NS_LITERAL_STRING("Extra value exceeds maximum string length, truncating."));
     676           0 :         TruncateToByteLength(str, kMaxExtraValueByteLength);
     677             :       }
     678             : 
     679           0 :       extra.AppendElement(EventExtraEntry{NS_ConvertUTF16toUTF8(key), str});
     680             :     }
     681             :   }
     682             : 
     683             :   // Lock for accessing internal data.
     684             :   // While the lock is being held, no complex calls like JS calls can be made,
     685             :   // as all of these could record Telemetry, which would result in deadlock.
     686             :   RecordEventResult res;
     687           0 :   if (!XRE_IsParentProcess()) {
     688             :     {
     689           0 :       StaticMutexAutoLock lock(gTelemetryEventsMutex);
     690           0 :       res = ::ShouldRecordChildEvent(lock, aCategory, aMethod, aObject);
     691             :     }
     692             : 
     693           0 :     if (res == RecordEventResult::Ok) {
     694           0 :       TelemetryIPCAccumulator::RecordChildEvent(TimeStamp::NowLoRes(), aCategory,
     695           0 :                                                 aMethod, aObject, value, extra);
     696             :     }
     697             :   } else {
     698           0 :     StaticMutexAutoLock lock(gTelemetryEventsMutex);
     699             : 
     700           0 :     if (!gInitDone) {
     701           0 :       return NS_ERROR_FAILURE;
     702             :     }
     703             : 
     704             :     // Get the current time.
     705           0 :     double timestamp = -1;
     706           0 :     if (NS_WARN_IF(NS_FAILED(MsSinceProcessStart(&timestamp)))) {
     707           0 :       return NS_ERROR_FAILURE;
     708             :     }
     709             : 
     710           0 :     res = ::RecordEvent(lock, ProcessID::Parent, timestamp, aCategory, aMethod, aObject, value, extra);
     711             :   }
     712             : 
     713             :   // Trigger warnings or errors where needed.
     714           0 :   switch (res) {
     715             :     case RecordEventResult::UnknownEvent: {
     716           0 :       JS_ReportErrorASCII(cx, R"(Unknown event: ["%s", "%s", "%s"])",
     717           0 :                           PromiseFlatCString(aCategory).get(),
     718           0 :                           PromiseFlatCString(aMethod).get(),
     719           0 :                           PromiseFlatCString(aObject).get());
     720           0 :       return NS_ERROR_INVALID_ARG;
     721             :     }
     722             :     case RecordEventResult::InvalidExtraKey:
     723           0 :       LogToBrowserConsole(nsIScriptError::warningFlag,
     724           0 :                           NS_LITERAL_STRING("Invalid extra key for event."));
     725           0 :       return NS_OK;
     726             :     case RecordEventResult::StorageLimitReached:
     727           0 :       LogToBrowserConsole(nsIScriptError::warningFlag,
     728           0 :                           NS_LITERAL_STRING("Event storage limit reached."));
     729           0 :       return NS_OK;
     730             :     default:
     731           0 :       return NS_OK;
     732             :   }
     733             : }
     734             : 
     735             : nsresult
     736           0 : TelemetryEvent::CreateSnapshots(uint32_t aDataset, bool aClear, JSContext* cx,
     737             :                                 uint8_t optional_argc, JS::MutableHandleValue aResult)
     738             : {
     739           0 :   if (!XRE_IsParentProcess()) {
     740           0 :     return NS_ERROR_FAILURE;
     741             :   }
     742             : 
     743             :   // Creating a JS snapshot of the events is a two-step process:
     744             :   // (1) Lock the storage and copy the events into function-local storage.
     745             :   // (2) Serialize the events into JS.
     746             :   // We can't hold a lock for (2) because we will run into deadlocks otherwise
     747             :   // from JS recording Telemetry.
     748             : 
     749             :   // (1) Extract the events from storage with a lock held.
     750           0 :   nsTArray<mozilla::Pair<const char*, EventRecordArray>> processEvents;
     751             :   {
     752           0 :     StaticMutexAutoLock locker(gTelemetryEventsMutex);
     753             : 
     754           0 :     if (!gInitDone) {
     755           0 :       return NS_ERROR_FAILURE;
     756             :     }
     757             : 
     758           0 :     for (auto iter = gEventRecords.Iter(); !iter.Done(); iter.Next()) {
     759           0 :       const EventRecordArray* eventStorage = static_cast<EventRecordArray*>(iter.Data());
     760           0 :       EventRecordArray events;
     761             : 
     762           0 :       const uint32_t len = eventStorage->Length();
     763           0 :       for (uint32_t i = 0; i < len; ++i) {
     764           0 :         const EventRecord& record = (*eventStorage)[i];
     765           0 :         const EventInfo& info = gEventInfo[record.EventId()];
     766             : 
     767           0 :         if (IsInDataset(info.common_info.dataset, aDataset)) {
     768           0 :           events.AppendElement(record);
     769             :         }
     770             :       }
     771             : 
     772           0 :       if (events.Length()) {
     773           0 :         const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
     774           0 :         processEvents.AppendElement(mozilla::MakePair(processName, events));
     775             :       }
     776             :     }
     777             : 
     778           0 :     if (aClear) {
     779           0 :       gEventRecords.Clear();
     780             :     }
     781             :   }
     782             : 
     783             :   // (2) Serialize the events to a JS object.
     784           0 :   JS::RootedObject rootObj(cx, JS_NewPlainObject(cx));
     785           0 :   if (!rootObj) {
     786           0 :     return NS_ERROR_FAILURE;
     787             :   }
     788             : 
     789           0 :   const uint32_t processLength = processEvents.Length();
     790           0 :   for (uint32_t i = 0; i < processLength; ++i)
     791             :   {
     792           0 :     JS::RootedObject eventsArray(cx);
     793           0 :     if (NS_FAILED(SerializeEventsArray(processEvents[i].second(), cx, &eventsArray))) {
     794           0 :       return NS_ERROR_FAILURE;
     795             :     }
     796             : 
     797           0 :     if (!JS_DefineProperty(cx, rootObj, processEvents[i].first(), eventsArray, JSPROP_ENUMERATE)) {
     798           0 :       return NS_ERROR_FAILURE;
     799             :     }
     800             :   }
     801             : 
     802           0 :   aResult.setObject(*rootObj);
     803           0 :   return NS_OK;
     804             : }
     805             : 
     806             : /**
     807             :  * Resets all the stored events. This is intended to be only used in tests.
     808             :  */
     809             : void
     810           0 : TelemetryEvent::ClearEvents()
     811             : {
     812           0 :   StaticMutexAutoLock lock(gTelemetryEventsMutex);
     813             : 
     814           0 :   if (!gInitDone) {
     815           0 :     return;
     816             :   }
     817             : 
     818           0 :   gEventRecords.Clear();
     819             : }
     820             : 
     821             : void
     822           0 : TelemetryEvent::SetEventRecordingEnabled(const nsACString& category, bool enabled)
     823             : {
     824           0 :   StaticMutexAutoLock locker(gTelemetryEventsMutex);
     825             : 
     826             :   uint32_t categoryId;
     827           0 :   if (!gCategoryNameIDMap.Get(category, &categoryId)) {
     828           0 :     LogToBrowserConsole(nsIScriptError::warningFlag,
     829           0 :                         NS_LITERAL_STRING("Unkown category for SetEventRecordingEnabled."));
     830           0 :     return;
     831             :   }
     832             : 
     833           0 :   if (enabled) {
     834           0 :     gEnabledCategories.PutEntry(categoryId);
     835             :   } else {
     836           0 :     gEnabledCategories.RemoveEntry(categoryId);
     837             :   }
     838             : }
     839             : 
     840             : size_t
     841           0 : TelemetryEvent::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
     842             : {
     843           0 :   StaticMutexAutoLock locker(gTelemetryEventsMutex);
     844           0 :   size_t n = 0;
     845             : 
     846             : 
     847           0 :   n += gEventRecords.ShallowSizeOfExcludingThis(aMallocSizeOf);
     848           0 :   for (auto iter = gEventRecords.Iter(); !iter.Done(); iter.Next()) {
     849           0 :     EventRecordArray* eventRecords = static_cast<EventRecordArray*>(iter.Data());
     850           0 :     n += eventRecords->ShallowSizeOfIncludingThis(aMallocSizeOf);
     851             : 
     852           0 :     const uint32_t len = eventRecords->Length();
     853           0 :     for (uint32_t i = 0; i < len; ++i) {
     854           0 :       n += (*eventRecords)[i].SizeOfExcludingThis(aMallocSizeOf);
     855             :     }
     856             :   }
     857             : 
     858           0 :   n += gEventNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
     859           0 :   for (auto iter = gEventNameIDMap.ConstIter(); !iter.Done(); iter.Next()) {
     860           0 :     n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
     861             :   }
     862             : 
     863           0 :   n += gCategoryNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
     864           0 :   for (auto iter = gCategoryNameIDMap.ConstIter(); !iter.Done(); iter.Next()) {
     865           0 :     n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
     866             :   }
     867             : 
     868           0 :   n += gEnabledCategories.ShallowSizeOfExcludingThis(aMallocSizeOf);
     869             : 
     870           0 :   return n;
     871           9 : }

Generated by: LCOV version 1.13