Source code

Revision control

Other Tools

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 "Telemetry.h"
8
#include "TelemetryEvent.h"
9
#include <prtime.h>
10
#include <limits>
11
#include "ipc/TelemetryIPCAccumulator.h"
12
#include "jsapi.h"
13
#include "mozilla/Maybe.h"
14
#include "mozilla/Pair.h"
15
#include "mozilla/Preferences.h"
16
#include "mozilla/Services.h"
17
#include "mozilla/StaticMutex.h"
18
#include "mozilla/StaticPtr.h"
19
#include "mozilla/Unused.h"
20
#include "nsClassHashtable.h"
21
#include "nsDataHashtable.h"
22
#include "nsHashKeys.h"
23
#include "nsIObserverService.h"
24
#include "nsITelemetry.h"
25
#include "nsJSUtils.h"
26
#include "nsPrintfCString.h"
27
#include "nsTArray.h"
28
#include "nsUTF8Utils.h"
29
#include "nsXULAppAPI.h"
30
#include "TelemetryCommon.h"
31
#include "TelemetryEventData.h"
32
#include "TelemetryScalar.h"
33
34
using mozilla::ArrayLength;
35
using mozilla::Maybe;
36
using mozilla::Nothing;
37
using mozilla::StaticAutoPtr;
38
using mozilla::StaticMutex;
39
using mozilla::StaticMutexAutoLock;
40
using mozilla::TimeStamp;
41
using mozilla::Telemetry::ChildEventData;
42
using mozilla::Telemetry::EventExtraEntry;
43
using mozilla::Telemetry::LABELS_TELEMETRY_EVENT_RECORDING_ERROR;
44
using mozilla::Telemetry::LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR;
45
using mozilla::Telemetry::ProcessID;
46
using mozilla::Telemetry::Common::AutoHashtable;
47
using mozilla::Telemetry::Common::CanRecordDataset;
48
using mozilla::Telemetry::Common::CanRecordInProcess;
49
using mozilla::Telemetry::Common::CanRecordProduct;
50
using mozilla::Telemetry::Common::GetCurrentProduct;
51
using mozilla::Telemetry::Common::GetNameForProcessID;
52
using mozilla::Telemetry::Common::IsExpiredVersion;
53
using mozilla::Telemetry::Common::IsInDataset;
54
using mozilla::Telemetry::Common::IsValidIdentifierString;
55
using mozilla::Telemetry::Common::LogToBrowserConsole;
56
using mozilla::Telemetry::Common::MsSinceProcessStart;
57
using mozilla::Telemetry::Common::ToJSString;
58
59
namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
60
61
////////////////////////////////////////////////////////////////////////
62
////////////////////////////////////////////////////////////////////////
63
//
64
// Naming: there are two kinds of functions in this file:
65
//
66
// * Functions taking a StaticMutexAutoLock: these can only be reached via
67
// an interface function (TelemetryEvent::*). They expect the interface
68
// function to have acquired |gTelemetryEventsMutex|, so they do not
69
// have to be thread-safe.
70
//
71
// * Functions named TelemetryEvent::*. This is the external interface.
72
// Entries and exits to these functions are serialised using
73
// |gTelemetryEventsMutex|.
74
//
75
// Avoiding races and deadlocks:
76
//
77
// All functions in the external interface (TelemetryEvent::*) are
78
// serialised using the mutex |gTelemetryEventsMutex|. This means
79
// that the external interface is thread-safe, and the internal
80
// functions can ignore thread safety. But it also brings a danger
81
// of deadlock if any function in the external interface can get back
82
// to that interface. That is, we will deadlock on any call chain like
83
// this:
84
//
85
// TelemetryEvent::* -> .. any functions .. -> TelemetryEvent::*
86
//
87
// To reduce the danger of that happening, observe the following rules:
88
//
89
// * No function in TelemetryEvent::* may directly call, nor take the
90
// address of, any other function in TelemetryEvent::*.
91
//
92
// * No internal function may call, nor take the address
93
// of, any function in TelemetryEvent::*.
94
95
////////////////////////////////////////////////////////////////////////
96
////////////////////////////////////////////////////////////////////////
97
//
98
// PRIVATE TYPES
99
100
namespace {
101
102
const uint32_t kEventCount =
103
static_cast<uint32_t>(mozilla::Telemetry::EventID::EventCount);
104
// This is a special event id used to mark expired events, to make expiry checks
105
// cheap at runtime.
106
const uint32_t kExpiredEventId = std::numeric_limits<uint32_t>::max();
107
static_assert(kExpiredEventId > kEventCount,
108
"Built-in event count should be less than the expired event id.");
109
110
// Maximum length of any passed value string, in UTF8 byte sequence length.
111
const uint32_t kMaxValueByteLength = 80;
112
// Maximum length of any string value in the extra dictionary, in UTF8 byte
113
// sequence length.
114
const uint32_t kMaxExtraValueByteLength = 80;
115
// Maximum length of dynamic method names, in UTF8 byte sequence length.
116
const uint32_t kMaxMethodNameByteLength = 20;
117
// Maximum length of dynamic object names, in UTF8 byte sequence length.
118
const uint32_t kMaxObjectNameByteLength = 20;
119
// Maximum length of extra key names, in UTF8 byte sequence length.
120
const uint32_t kMaxExtraKeyNameByteLength = 15;
121
// The maximum number of valid extra keys for an event.
122
const uint32_t kMaxExtraKeyCount = 10;
123
124
struct EventKey {
125
uint32_t id;
126
bool dynamic;
127
};
128
129
struct DynamicEventInfo {
130
DynamicEventInfo(const nsACString& category, const nsACString& method,
131
const nsACString& object, nsTArray<nsCString>& extra_keys,
132
bool recordOnRelease, bool builtin)
133
: category(category),
134
method(method),
135
object(object),
136
extra_keys(extra_keys),
137
recordOnRelease(recordOnRelease),
138
builtin(builtin) {}
139
140
DynamicEventInfo(const DynamicEventInfo&) = default;
141
DynamicEventInfo& operator=(const DynamicEventInfo&) = delete;
142
143
const nsCString category;
144
const nsCString method;
145
const nsCString object;
146
const nsTArray<nsCString> extra_keys;
147
const bool recordOnRelease;
148
const bool builtin;
149
150
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
151
size_t n = 0;
152
153
n += category.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
154
n += method.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
155
n += object.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
156
n += extra_keys.ShallowSizeOfExcludingThis(aMallocSizeOf);
157
for (auto& key : extra_keys) {
158
n += key.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
159
}
160
161
return n;
162
}
163
};
164
165
enum class RecordEventResult {
166
Ok,
167
UnknownEvent,
168
InvalidExtraKey,
169
StorageLimitReached,
170
ExpiredEvent,
171
WrongProcess,
172
};
173
174
typedef nsTArray<EventExtraEntry> ExtraArray;
175
176
class EventRecord {
177
public:
178
EventRecord(double timestamp, const EventKey& key,
179
const Maybe<nsCString>& value, const ExtraArray& extra)
180
: mTimestamp(timestamp), mEventKey(key), mValue(value), mExtra(extra) {}
181
182
EventRecord(const EventRecord& other) = default;
183
184
EventRecord& operator=(const EventRecord& other) = delete;
185
186
double Timestamp() const { return mTimestamp; }
187
const EventKey& GetEventKey() const { return mEventKey; }
188
const Maybe<nsCString>& Value() const { return mValue; }
189
const ExtraArray& Extra() const { return mExtra; }
190
191
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
192
193
private:
194
const double mTimestamp;
195
const EventKey mEventKey;
196
const Maybe<nsCString> mValue;
197
const ExtraArray mExtra;
198
};
199
200
// Implements the methods for EventInfo.
201
const nsDependentCString EventInfo::method() const {
202
return nsDependentCString(&gEventsStringTable[this->method_offset]);
203
}
204
205
const nsDependentCString EventInfo::object() const {
206
return nsDependentCString(&gEventsStringTable[this->object_offset]);
207
}
208
209
// Implements the methods for CommonEventInfo.
210
const nsDependentCString CommonEventInfo::category() const {
211
return nsDependentCString(&gEventsStringTable[this->category_offset]);
212
}
213
214
const nsDependentCString CommonEventInfo::expiration_version() const {
215
return nsDependentCString(
216
&gEventsStringTable[this->expiration_version_offset]);
217
}
218
219
const nsDependentCString CommonEventInfo::extra_key(uint32_t index) const {
220
MOZ_ASSERT(index < this->extra_count);
221
uint32_t key_index = gExtraKeysTable[this->extra_index + index];
222
return nsDependentCString(&gEventsStringTable[key_index]);
223
}
224
225
// Implementation for the EventRecord class.
226
size_t EventRecord::SizeOfExcludingThis(
227
mozilla::MallocSizeOf aMallocSizeOf) const {
228
size_t n = 0;
229
230
if (mValue) {
231
n += mValue.value().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
232
}
233
234
n += mExtra.ShallowSizeOfExcludingThis(aMallocSizeOf);
235
for (uint32_t i = 0; i < mExtra.Length(); ++i) {
236
n += mExtra[i].key.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
237
n += mExtra[i].value.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
238
}
239
240
return n;
241
}
242
243
nsCString UniqueEventName(const nsACString& category, const nsACString& method,
244
const nsACString& object) {
245
nsCString name;
246
name.Append(category);
247
name.AppendLiteral("#");
248
name.Append(method);
249
name.AppendLiteral("#");
250
name.Append(object);
251
return name;
252
}
253
254
nsCString UniqueEventName(const EventInfo& info) {
255
return UniqueEventName(info.common_info.category(), info.method(),
256
info.object());
257
}
258
259
nsCString UniqueEventName(const DynamicEventInfo& info) {
260
return UniqueEventName(info.category, info.method, info.object);
261
}
262
263
void TruncateToByteLength(nsCString& str, uint32_t length) {
264
// last will be the index of the first byte of the current multi-byte
265
// sequence.
266
uint32_t last = RewindToPriorUTF8Codepoint(str.get(), length);
267
str.Truncate(last);
268
}
269
270
} // anonymous namespace
271
272
////////////////////////////////////////////////////////////////////////
273
////////////////////////////////////////////////////////////////////////
274
//
275
// PRIVATE STATE, SHARED BY ALL THREADS
276
277
namespace {
278
279
// Set to true once this global state has been initialized.
280
bool gInitDone = false;
281
282
bool gCanRecordBase;
283
bool gCanRecordExtended;
284
285
// The EventName -> EventKey cache map.
286
nsClassHashtable<nsCStringHashKey, EventKey> gEventNameIDMap(kEventCount);
287
288
// The CategoryName set.
289
nsTHashtable<nsCStringHashKey> gCategoryNames;
290
291
// This tracks the IDs of the categories for which recording is enabled.
292
nsTHashtable<nsCStringHashKey> gEnabledCategories;
293
294
// The main event storage. Events are inserted here, keyed by process id and
295
// in recording order.
296
typedef nsUint32HashKey ProcessIDHashKey;
297
typedef nsTArray<EventRecord> EventRecordArray;
298
typedef nsClassHashtable<ProcessIDHashKey, EventRecordArray>
299
EventRecordsMapType;
300
301
EventRecordsMapType gEventRecords;
302
303
// The details on dynamic events that are recorded from addons are registered
304
// here.
305
StaticAutoPtr<nsTArray<DynamicEventInfo>> gDynamicEventInfo;
306
307
} // namespace
308
309
////////////////////////////////////////////////////////////////////////
310
////////////////////////////////////////////////////////////////////////
311
//
312
// PRIVATE: thread-safe helpers for event recording.
313
314
namespace {
315
316
unsigned int GetDataset(const StaticMutexAutoLock& lock,
317
const EventKey& eventKey) {
318
if (!eventKey.dynamic) {
319
return gEventInfo[eventKey.id].common_info.dataset;
320
}
321
322
if (!gDynamicEventInfo) {
323
return nsITelemetry::DATASET_PRERELEASE_CHANNELS;
324
}
325
326
return (*gDynamicEventInfo)[eventKey.id].recordOnRelease
327
? nsITelemetry::DATASET_ALL_CHANNELS
328
: nsITelemetry::DATASET_PRERELEASE_CHANNELS;
329
}
330
331
nsCString GetCategory(const StaticMutexAutoLock& lock,
332
const EventKey& eventKey) {
333
if (!eventKey.dynamic) {
334
return gEventInfo[eventKey.id].common_info.category();
335
}
336
337
if (!gDynamicEventInfo) {
338
return NS_LITERAL_CSTRING("");
339
}
340
341
return (*gDynamicEventInfo)[eventKey.id].category;
342
}
343
344
bool CanRecordEvent(const StaticMutexAutoLock& lock, const EventKey& eventKey,
345
ProcessID process) {
346
if (!gCanRecordBase) {
347
return false;
348
}
349
350
if (!CanRecordDataset(GetDataset(lock, eventKey), gCanRecordBase,
351
gCanRecordExtended)) {
352
return false;
353
}
354
355
// We don't allow specifying a process to record in for dynamic events.
356
if (!eventKey.dynamic) {
357
const CommonEventInfo& info = gEventInfo[eventKey.id].common_info;
358
359
if (!CanRecordProduct(info.products)) {
360
return false;
361
}
362
363
if (!CanRecordInProcess(info.record_in_processes, process)) {
364
return false;
365
}
366
}
367
368
return true;
369
}
370
371
bool IsExpired(const EventKey& key) { return key.id == kExpiredEventId; }
372
373
EventRecordArray* GetEventRecordsForProcess(const StaticMutexAutoLock& lock,
374
ProcessID processType,
375
const EventKey& eventKey) {
376
EventRecordArray* eventRecords = nullptr;
377
if (!gEventRecords.Get(uint32_t(processType), &eventRecords)) {
378
eventRecords = new EventRecordArray();
379
gEventRecords.Put(uint32_t(processType), eventRecords);
380
}
381
return eventRecords;
382
}
383
384
EventKey* GetEventKey(const StaticMutexAutoLock& lock,
385
const nsACString& category, const nsACString& method,
386
const nsACString& object) {
387
EventKey* event;
388
const nsCString& name = UniqueEventName(category, method, object);
389
if (!gEventNameIDMap.Get(name, &event)) {
390
return nullptr;
391
}
392
return event;
393
}
394
395
static bool CheckExtraKeysValid(const EventKey& eventKey,
396
const ExtraArray& extra) {
397
nsTHashtable<nsCStringHashKey> validExtraKeys;
398
if (!eventKey.dynamic) {
399
const CommonEventInfo& common = gEventInfo[eventKey.id].common_info;
400
for (uint32_t i = 0; i < common.extra_count; ++i) {
401
validExtraKeys.PutEntry(common.extra_key(i));
402
}
403
} else if (gDynamicEventInfo) {
404
const DynamicEventInfo& info = (*gDynamicEventInfo)[eventKey.id];
405
for (uint32_t i = 0, len = info.extra_keys.Length(); i < len; ++i) {
406
validExtraKeys.PutEntry(info.extra_keys[i]);
407
}
408
}
409
410
for (uint32_t i = 0; i < extra.Length(); ++i) {
411
if (!validExtraKeys.GetEntry(extra[i].key)) {
412
return false;
413
}
414
}
415
416
return true;
417
}
418
419
RecordEventResult RecordEvent(const StaticMutexAutoLock& lock,
420
ProcessID processType, double timestamp,
421
const nsACString& category,
422
const nsACString& method,
423
const nsACString& object,
424
const Maybe<nsCString>& value,
425
const ExtraArray& extra) {
426
// Look up the event id.
427
EventKey* eventKey = GetEventKey(lock, category, method, object);
428
if (!eventKey) {
429
mozilla::Telemetry::AccumulateCategorical(
430
LABELS_TELEMETRY_EVENT_RECORDING_ERROR::UnknownEvent);
431
return RecordEventResult::UnknownEvent;
432
}
433
434
// If the event is expired or not enabled for this process, we silently drop
435
// this call. We don't want recording for expired probes to be an error so
436
// code doesn't have to be removed at a specific time or version. Even logging
437
// warnings would become very noisy.
438
if (IsExpired(*eventKey)) {
439
mozilla::Telemetry::AccumulateCategorical(
440
LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Expired);
441
return RecordEventResult::ExpiredEvent;
442
}
443
444
// Fixup the process id only for non-builtin (e.g. supporting build faster)
445
// dynamic events.
446
auto dynamicNonBuiltin =
447
eventKey->dynamic && !(*gDynamicEventInfo)[eventKey->id].builtin;
448
if (dynamicNonBuiltin) {
449
processType = ProcessID::Dynamic;
450
}
451
452
// Check whether the extra keys passed are valid.
453
if (!CheckExtraKeysValid(*eventKey, extra)) {
454
mozilla::Telemetry::AccumulateCategorical(
455
LABELS_TELEMETRY_EVENT_RECORDING_ERROR::ExtraKey);
456
return RecordEventResult::InvalidExtraKey;
457
}
458
459
// Check whether we can record this event.
460
if (!CanRecordEvent(lock, *eventKey, processType)) {
461
return RecordEventResult::Ok;
462
}
463
464
// Count the number of times this event has been recorded, even if its
465
// category does not have recording enabled.
466
TelemetryScalar::SummarizeEvent(UniqueEventName(category, method, object),
467
processType, dynamicNonBuiltin);
468
469
// Check whether this event's category has recording enabled
470
if (!gEnabledCategories.GetEntry(GetCategory(lock, *eventKey))) {
471
return RecordEventResult::Ok;
472
}
473
474
static bool sEventPingEnabled = mozilla::Preferences::GetBool(
475
"toolkit.telemetry.eventping.enabled", true);
476
if (!sEventPingEnabled) {
477
return RecordEventResult::Ok;
478
}
479
480
EventRecordArray* eventRecords =
481
GetEventRecordsForProcess(lock, processType, *eventKey);
482
eventRecords->AppendElement(EventRecord(timestamp, *eventKey, value, extra));
483
484
// Notify observers when we hit the "event" ping event record limit.
485
static uint32_t sEventPingLimit = mozilla::Preferences::GetUint(
486
"toolkit.telemetry.eventping.eventLimit", 1000);
487
if (eventRecords->Length() == sEventPingLimit) {
488
return RecordEventResult::StorageLimitReached;
489
}
490
491
return RecordEventResult::Ok;
492
}
493
494
RecordEventResult ShouldRecordChildEvent(const StaticMutexAutoLock& lock,
495
const nsACString& category,
496
const nsACString& method,
497
const nsACString& object) {
498
EventKey* eventKey = GetEventKey(lock, category, method, object);
499
if (!eventKey) {
500
// This event is unknown in this process, but it might be a dynamic event
501
// that was registered in the parent process.
502
return RecordEventResult::Ok;
503
}
504
505
if (IsExpired(*eventKey)) {
506
return RecordEventResult::ExpiredEvent;
507
}
508
509
const auto processes =
510
gEventInfo[eventKey->id].common_info.record_in_processes;
511
if (!CanRecordInProcess(processes, XRE_GetProcessType())) {
512
return RecordEventResult::WrongProcess;
513
}
514
515
return RecordEventResult::Ok;
516
}
517
518
void RegisterEvents(const StaticMutexAutoLock& lock, const nsACString& category,
519
const nsTArray<DynamicEventInfo>& eventInfos,
520
const nsTArray<bool>& eventExpired, bool aBuiltin) {
521
MOZ_ASSERT(eventInfos.Length() == eventExpired.Length(),
522
"Event data array sizes should match.");
523
524
// Register the new events.
525
if (!gDynamicEventInfo) {
526
gDynamicEventInfo = new nsTArray<DynamicEventInfo>();
527
}
528
529
for (uint32_t i = 0, len = eventInfos.Length(); i < len; ++i) {
530
const nsCString& eventName = UniqueEventName(eventInfos[i]);
531
532
// Re-registering events can happen for two reasons and we don't print
533
// warnings:
534
//
535
// * When add-ons update.
536
// We don't support changing their definition, but the expiry might have
537
// changed.
538
// * When dynamic builtins ("build faster") events are registered.
539
// The dynamic definition takes precedence then.
540
EventKey* existing = nullptr;
541
if (!aBuiltin && gEventNameIDMap.Get(eventName, &existing)) {
542
if (eventExpired[i]) {
543
existing->id = kExpiredEventId;
544
}
545
continue;
546
}
547
548
gDynamicEventInfo->AppendElement(eventInfos[i]);
549
uint32_t eventId =
550
eventExpired[i] ? kExpiredEventId : gDynamicEventInfo->Length() - 1;
551
gEventNameIDMap.Put(eventName, new EventKey{eventId, true});
552
}
553
554
// If it is a builtin, add the category name in order to enable it later.
555
if (aBuiltin) {
556
gCategoryNames.PutEntry(category);
557
}
558
559
if (!aBuiltin) {
560
// Now after successful registration enable recording for this category
561
// (if not a dynamic builtin).
562
gEnabledCategories.PutEntry(category);
563
}
564
}
565
566
} // anonymous namespace
567
568
////////////////////////////////////////////////////////////////////////
569
////////////////////////////////////////////////////////////////////////
570
//
571
// PRIVATE: thread-unsafe helpers for event handling.
572
573
namespace {
574
575
nsresult SerializeEventsArray(const EventRecordArray& events, JSContext* cx,
576
JS::MutableHandleObject result,
577
unsigned int dataset) {
578
// We serialize the events to a JS array.
579
JS::RootedObject eventsArray(cx, JS_NewArrayObject(cx, events.Length()));
580
if (!eventsArray) {
581
return NS_ERROR_FAILURE;
582
}
583
584
for (uint32_t i = 0; i < events.Length(); ++i) {
585
const EventRecord& record = events[i];
586
587
// Each entry is an array of one of the forms:
588
// [timestamp, category, method, object, value]
589
// [timestamp, category, method, object, null, extra]
590
// [timestamp, category, method, object, value, extra]
591
JS::RootedVector<JS::Value> items(cx);
592
593
// Add timestamp.
594
JS::Rooted<JS::Value> val(cx);
595
if (!items.append(JS::NumberValue(floor(record.Timestamp())))) {
596
return NS_ERROR_FAILURE;
597
}
598
599
// Add category, method, object.
600
auto addCategoryMethodObjectValues = [&](const nsACString& category,
601
const nsACString& method,
602
const nsACString& object) -> bool {
603
return items.append(JS::StringValue(ToJSString(cx, category))) &&
604
items.append(JS::StringValue(ToJSString(cx, method))) &&
605
items.append(JS::StringValue(ToJSString(cx, object)));
606
};
607
608
const EventKey& eventKey = record.GetEventKey();
609
if (!eventKey.dynamic) {
610
const EventInfo& info = gEventInfo[eventKey.id];
611
if (!addCategoryMethodObjectValues(info.common_info.category(),
612
info.method(), info.object())) {
613
return NS_ERROR_FAILURE;
614
}
615
} else if (gDynamicEventInfo) {
616
const DynamicEventInfo& info = (*gDynamicEventInfo)[eventKey.id];
617
if (!addCategoryMethodObjectValues(info.category, info.method,
618
info.object)) {
619
return NS_ERROR_FAILURE;
620
}
621
}
622
623
// Add the optional string value only when needed.
624
// When the value field is empty and extra is not set, we can save a little
625
// space that way. We still need to submit a null value if extra is set, to
626
// match the form: [ts, category, method, object, null, extra]
627
if (record.Value()) {
628
if (!items.append(
629
JS::StringValue(ToJSString(cx, record.Value().value())))) {
630
return NS_ERROR_FAILURE;
631
}
632
} else if (!record.Extra().IsEmpty()) {
633
if (!items.append(JS::NullValue())) {
634
return NS_ERROR_FAILURE;
635
}
636
}
637
638
// Add the optional extra dictionary.
639
// To save a little space, only add it when it is not empty.
640
if (!record.Extra().IsEmpty()) {
641
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
642
if (!obj) {
643
return NS_ERROR_FAILURE;
644
}
645
646
// Add extra key & value entries.
647
const ExtraArray& extra = record.Extra();
648
for (uint32_t i = 0; i < extra.Length(); ++i) {
649
JS::Rooted<JS::Value> value(cx);
650
value.setString(ToJSString(cx, extra[i].value));
651
652
if (!JS_DefineProperty(cx, obj, extra[i].key.get(), value,
653
JSPROP_ENUMERATE)) {
654
return NS_ERROR_FAILURE;
655
}
656
}
657
val.setObject(*obj);
658
659
if (!items.append(val)) {
660
return NS_ERROR_FAILURE;
661
}
662
}
663
664
// Add the record to the events array.
665
JS::RootedObject itemsArray(cx, JS_NewArrayObject(cx, items));
666
if (!JS_DefineElement(cx, eventsArray, i, itemsArray, JSPROP_ENUMERATE)) {
667
return NS_ERROR_FAILURE;
668
}
669
}
670
671
result.set(eventsArray);
672
return NS_OK;
673
}
674
675
} // anonymous namespace
676
677
////////////////////////////////////////////////////////////////////////
678
////////////////////////////////////////////////////////////////////////
679
//
680
// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryEvents::
681
682
// This is a StaticMutex rather than a plain Mutex (1) so that
683
// it gets initialised in a thread-safe manner the first time
684
// it is used, and (2) because it is never de-initialised, and
685
// a normal Mutex would show up as a leak in BloatView. StaticMutex
686
// also has the "OffTheBooks" property, so it won't show as a leak
687
// in BloatView.
688
// Another reason to use a StaticMutex instead of a plain Mutex is
689
// that, due to the nature of Telemetry, we cannot rely on having a
690
// mutex initialized in InitializeGlobalState. Unfortunately, we
691
// cannot make sure that no other function is called before this point.
692
static StaticMutex gTelemetryEventsMutex;
693
694
void TelemetryEvent::InitializeGlobalState(bool aCanRecordBase,
695
bool aCanRecordExtended) {
696
StaticMutexAutoLock locker(gTelemetryEventsMutex);
697
MOZ_ASSERT(!gInitDone,
698
"TelemetryEvent::InitializeGlobalState "
699
"may only be called once");
700
701
gCanRecordBase = aCanRecordBase;
702
gCanRecordExtended = aCanRecordExtended;
703
704
// Populate the static event name->id cache. Note that the event names are
705
// statically allocated and come from the automatically generated
706
// TelemetryEventData.h.
707
const uint32_t eventCount =
708
static_cast<uint32_t>(mozilla::Telemetry::EventID::EventCount);
709
for (uint32_t i = 0; i < eventCount; ++i) {
710
const EventInfo& info = gEventInfo[i];
711
uint32_t eventId = i;
712
713
// If this event is expired or not recorded in this process, mark it with
714
// a special event id.
715
// This avoids doing repeated checks at runtime.
716
if (IsExpiredVersion(info.common_info.expiration_version().get())) {
717
eventId = kExpiredEventId;
718
}
719
720
gEventNameIDMap.Put(UniqueEventName(info), new EventKey{eventId, false});
721
gCategoryNames.PutEntry(info.common_info.category());
722
}
723
724
gInitDone = true;
725
}
726
727
void TelemetryEvent::DeInitializeGlobalState() {
728
StaticMutexAutoLock locker(gTelemetryEventsMutex);
729
MOZ_ASSERT(gInitDone);
730
731
gCanRecordBase = false;
732
gCanRecordExtended = false;
733
734
gEventNameIDMap.Clear();
735
gCategoryNames.Clear();
736
gEnabledCategories.Clear();
737
gEventRecords.Clear();
738
739
gDynamicEventInfo = nullptr;
740
741
gInitDone = false;
742
}
743
744
void TelemetryEvent::SetCanRecordBase(bool b) {
745
StaticMutexAutoLock locker(gTelemetryEventsMutex);
746
gCanRecordBase = b;
747
}
748
749
void TelemetryEvent::SetCanRecordExtended(bool b) {
750
StaticMutexAutoLock locker(gTelemetryEventsMutex);
751
gCanRecordExtended = b;
752
}
753
754
nsresult TelemetryEvent::RecordChildEvents(
755
ProcessID aProcessType,
756
const nsTArray<mozilla::Telemetry::ChildEventData>& aEvents) {
757
MOZ_ASSERT(XRE_IsParentProcess());
758
StaticMutexAutoLock locker(gTelemetryEventsMutex);
759
for (uint32_t i = 0; i < aEvents.Length(); ++i) {
760
const mozilla::Telemetry::ChildEventData& e = aEvents[i];
761
762
// Timestamps from child processes are absolute. We fix them up here to be
763
// relative to the main process start time.
764
// This allows us to put events from all processes on the same timeline.
765
double relativeTimestamp =
766
(e.timestamp - TimeStamp::ProcessCreation()).ToMilliseconds();
767
768
::RecordEvent(locker, aProcessType, relativeTimestamp, e.category, e.method,
769
e.object, e.value, e.extra);
770
}
771
return NS_OK;
772
}
773
774
nsresult TelemetryEvent::RecordEvent(const nsACString& aCategory,
775
const nsACString& aMethod,
776
const nsACString& aObject,
777
JS::HandleValue aValue,
778
JS::HandleValue aExtra, JSContext* cx,
779
uint8_t optional_argc) {
780
// Check value argument.
781
if ((optional_argc > 0) && !aValue.isNull() && !aValue.isString()) {
782
LogToBrowserConsole(nsIScriptError::warningFlag,
783
NS_LITERAL_STRING("Invalid type for value parameter."));
784
mozilla::Telemetry::AccumulateCategorical(
785
LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Value);
786
return NS_OK;
787
}
788
789
// Extract value parameter.
790
Maybe<nsCString> value;
791
if (aValue.isString()) {
792
nsAutoJSString jsStr;
793
if (!jsStr.init(cx, aValue)) {
794
LogToBrowserConsole(
795
nsIScriptError::warningFlag,
796
NS_LITERAL_STRING("Invalid string value for value parameter."));
797
mozilla::Telemetry::AccumulateCategorical(
798
LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Value);
799
return NS_OK;
800
}
801
802
nsCString str = NS_ConvertUTF16toUTF8(jsStr);
803
if (str.Length() > kMaxValueByteLength) {
804
LogToBrowserConsole(
805
nsIScriptError::warningFlag,
806
NS_LITERAL_STRING(
807
"Value parameter exceeds maximum string length, truncating."));
808
TruncateToByteLength(str, kMaxValueByteLength);
809
}
810
value = mozilla::Some(str);
811
}
812
813
// Check extra argument.
814
if ((optional_argc > 1) && !aExtra.isNull() && !aExtra.isObject()) {
815
LogToBrowserConsole(nsIScriptError::warningFlag,
816
NS_LITERAL_STRING("Invalid type for extra parameter."));
817
mozilla::Telemetry::AccumulateCategorical(
818
LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Extra);
819
return NS_OK;
820
}
821
822
// Extract extra dictionary.
823
ExtraArray extra;
824
if (aExtra.isObject()) {
825
JS::RootedObject obj(cx, &aExtra.toObject());
826
JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx));
827
if (!JS_Enumerate(cx, obj, &ids)) {
828
LogToBrowserConsole(nsIScriptError::warningFlag,
829
NS_LITERAL_STRING("Failed to enumerate object."));
830
mozilla::Telemetry::AccumulateCategorical(
831
LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Extra);
832
return NS_OK;
833
}
834
835
for (size_t i = 0, n = ids.length(); i < n; i++) {
836
nsAutoJSString key;
837
if (!key.init(cx, ids[i])) {
838
LogToBrowserConsole(
839
nsIScriptError::warningFlag,
840
NS_LITERAL_STRING(
841
"Extra dictionary should only contain string keys."));
842
mozilla::Telemetry::AccumulateCategorical(
843
LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Extra);
844
return NS_OK;
845
}
846
847
JS::Rooted<JS::Value> value(cx);
848
if (!JS_GetPropertyById(cx, obj, ids[i], &value)) {
849
LogToBrowserConsole(nsIScriptError::warningFlag,
850
NS_LITERAL_STRING("Failed to get extra property."));
851
mozilla::Telemetry::AccumulateCategorical(
852
LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Extra);
853
return NS_OK;
854
}
855
856
nsAutoJSString jsStr;
857
if (!value.isString() || !jsStr.init(cx, value)) {
858
LogToBrowserConsole(
859
nsIScriptError::warningFlag,
860
NS_LITERAL_STRING("Extra properties should have string values."));
861
mozilla::Telemetry::AccumulateCategorical(
862
LABELS_TELEMETRY_EVENT_RECORDING_ERROR::Extra);
863
return NS_OK;
864
}
865
866
nsCString str = NS_ConvertUTF16toUTF8(jsStr);
867
if (str.Length() > kMaxExtraValueByteLength) {
868
LogToBrowserConsole(
869
nsIScriptError::warningFlag,
870
NS_LITERAL_STRING(
871
"Extra value exceeds maximum string length, truncating."));
872
TruncateToByteLength(str, kMaxExtraValueByteLength);
873
}
874
875
extra.AppendElement(EventExtraEntry{NS_ConvertUTF16toUTF8(key), str});
876
}
877
}
878
879
// Lock for accessing internal data.
880
// While the lock is being held, no complex calls like JS calls can be made,
881
// as all of these could record Telemetry, which would result in deadlock.
882
RecordEventResult res;
883
if (!XRE_IsParentProcess()) {
884
{
885
StaticMutexAutoLock lock(gTelemetryEventsMutex);
886
res = ::ShouldRecordChildEvent(lock, aCategory, aMethod, aObject);
887
}
888
889
if (res == RecordEventResult::Ok) {
890
TelemetryIPCAccumulator::RecordChildEvent(
891
TimeStamp::NowLoRes(), aCategory, aMethod, aObject, value, extra);
892
}
893
} else {
894
StaticMutexAutoLock lock(gTelemetryEventsMutex);
895
896
if (!gInitDone) {
897
return NS_ERROR_FAILURE;
898
}
899
900
// Get the current time.
901
double timestamp = -1;
902
if (NS_WARN_IF(NS_FAILED(MsSinceProcessStart(&timestamp)))) {
903
return NS_ERROR_FAILURE;
904
}
905
906
res = ::RecordEvent(lock, ProcessID::Parent, timestamp, aCategory, aMethod,
907
aObject, value, extra);
908
}
909
910
// Trigger warnings or errors where needed.
911
switch (res) {
912
case RecordEventResult::UnknownEvent: {
913
JS_ReportErrorASCII(cx, R"(Unknown event: ["%s", "%s", "%s"])",
914
PromiseFlatCString(aCategory).get(),
915
PromiseFlatCString(aMethod).get(),
916
PromiseFlatCString(aObject).get());
917
return NS_OK;
918
}
919
case RecordEventResult::InvalidExtraKey: {
920
nsPrintfCString msg(R"(Invalid extra key for event ["%s", "%s", "%s"].)",
921
PromiseFlatCString(aCategory).get(),
922
PromiseFlatCString(aMethod).get(),
923
PromiseFlatCString(aObject).get());
924
LogToBrowserConsole(nsIScriptError::warningFlag,
925
NS_ConvertUTF8toUTF16(msg));
926
return NS_OK;
927
}
928
case RecordEventResult::StorageLimitReached: {
929
LogToBrowserConsole(nsIScriptError::warningFlag,
930
NS_LITERAL_STRING("Event storage limit reached."));
931
nsCOMPtr<nsIObserverService> serv =
932
mozilla::services::GetObserverService();
933
if (serv) {
934
serv->NotifyObservers(nullptr, "event-telemetry-storage-limit-reached",
935
nullptr);
936
}
937
return NS_OK;
938
}
939
default:
940
return NS_OK;
941
}
942
}
943
944
void TelemetryEvent::RecordEventNative(
945
mozilla::Telemetry::EventID aId, const mozilla::Maybe<nsCString>& aValue,
946
const mozilla::Maybe<ExtraArray>& aExtra) {
947
// Truncate aValue if present and necessary.
948
mozilla::Maybe<nsCString> value;
949
if (aValue) {
950
nsCString valueStr = aValue.ref();
951
if (valueStr.Length() > kMaxValueByteLength) {
952
TruncateToByteLength(valueStr, kMaxValueByteLength);
953
}
954
value = mozilla::Some(valueStr);
955
}
956
957
// Truncate any over-long extra values.
958
ExtraArray extra;
959
if (aExtra) {
960
extra = aExtra.ref();
961
for (auto& item : extra) {
962
if (item.value.Length() > kMaxExtraValueByteLength) {
963
TruncateToByteLength(item.value, kMaxExtraValueByteLength);
964
}
965
}
966
}
967
968
const EventInfo& info = gEventInfo[static_cast<uint32_t>(aId)];
969
const nsCString category(info.common_info.category());
970
const nsCString method(info.method());
971
const nsCString object(info.object());
972
if (!XRE_IsParentProcess()) {
973
RecordEventResult res;
974
{
975
StaticMutexAutoLock lock(gTelemetryEventsMutex);
976
res = ::ShouldRecordChildEvent(lock, category, method, object);
977
}
978
979
if (res == RecordEventResult::Ok) {
980
TelemetryIPCAccumulator::RecordChildEvent(TimeStamp::NowLoRes(), category,
981
method, object, value, extra);
982
}
983
} else {
984
StaticMutexAutoLock lock(gTelemetryEventsMutex);
985
986
if (!gInitDone) {
987
return;
988
}
989
990
// Get the current time.
991
double timestamp = -1;
992
if (NS_WARN_IF(NS_FAILED(MsSinceProcessStart(&timestamp)))) {
993
return;
994
}
995
996
::RecordEvent(lock, ProcessID::Parent, timestamp, category, method, object,
997
value, extra);
998
}
999
}
1000
1001
static bool GetArrayPropertyValues(JSContext* cx, JS::HandleObject obj,
1002
const char* property,
1003
nsTArray<nsCString>* results) {
1004
JS::RootedValue value(cx);
1005
if (!JS_GetProperty(cx, obj, property, &value)) {
1006
JS_ReportErrorASCII(cx, R"(Missing required property "%s" for event)",
1007
property);
1008
return false;
1009
}
1010
1011
bool isArray = false;
1012
if (!JS_IsArrayObject(cx, value, &isArray) || !isArray) {
1013
JS_ReportErrorASCII(cx, R"(Property "%s" for event should be an array)",
1014
property);
1015
return false;
1016
}
1017
1018
JS::RootedObject arrayObj(cx, &value.toObject());
1019
uint32_t arrayLength;
1020
if (!JS_GetArrayLength(cx, arrayObj, &arrayLength)) {
1021
return false;
1022
}
1023
1024
for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; ++arrayIdx) {
1025
JS::Rooted<JS::Value> element(cx);
1026
if (!JS_GetElement(cx, arrayObj, arrayIdx, &element)) {
1027
return false;
1028
}
1029
1030
if (!element.isString()) {
1031
JS_ReportErrorASCII(
1032
cx, R"(Array entries for event property "%s" should be strings)",
1033
property);
1034
return false;
1035
}
1036
1037
nsAutoJSString jsStr;
1038
if (!jsStr.init(cx, element)) {
1039
return false;
1040
}
1041
1042
results->AppendElement(NS_ConvertUTF16toUTF8(jsStr));
1043
}
1044
1045
return true;
1046
}
1047
1048
nsresult TelemetryEvent::RegisterEvents(const nsACString& aCategory,
1049
JS::Handle<JS::Value> aEventData,
1050
bool aBuiltin, JSContext* cx) {
1051
MOZ_ASSERT(XRE_IsParentProcess(),
1052
"Events can only be registered in the parent process");
1053
1054
if (!IsValidIdentifierString(aCategory, 30, true, true)) {
1055
JS_ReportErrorASCII(
1056
cx, "Category parameter should match the identifier pattern.");
1057
mozilla::Telemetry::AccumulateCategorical(
1058
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Category);
1059
return NS_ERROR_INVALID_ARG;
1060
}
1061
1062
if (!aEventData.isObject()) {
1063
JS_ReportErrorASCII(cx, "Event data parameter should be an object");
1064
mozilla::Telemetry::AccumulateCategorical(
1065
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other);
1066
return NS_ERROR_INVALID_ARG;
1067
}
1068
1069
JS::RootedObject obj(cx, &aEventData.toObject());
1070
JS::Rooted<JS::IdVector> eventPropertyIds(cx, JS::IdVector(cx));
1071
if (!JS_Enumerate(cx, obj, &eventPropertyIds)) {
1072
mozilla::Telemetry::AccumulateCategorical(
1073
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other);
1074
return NS_ERROR_FAILURE;
1075
}
1076
1077
// Collect the event data into local storage first.
1078
// Only after successfully validating all contained events will we register
1079
// them into global storage.
1080
nsTArray<DynamicEventInfo> newEventInfos;
1081
nsTArray<bool> newEventExpired;
1082
1083
for (size_t i = 0, n = eventPropertyIds.length(); i < n; i++) {
1084
nsAutoJSString eventName;
1085
if (!eventName.init(cx, eventPropertyIds[i])) {
1086
mozilla::Telemetry::AccumulateCategorical(
1087
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other);
1088
return NS_ERROR_FAILURE;
1089
}
1090
1091
if (!IsValidIdentifierString(NS_ConvertUTF16toUTF8(eventName),
1092
kMaxMethodNameByteLength, false, true)) {
1093
JS_ReportErrorASCII(cx,
1094
"Event names should match the identifier pattern.");
1095
mozilla::Telemetry::AccumulateCategorical(
1096
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Name);
1097
return NS_ERROR_INVALID_ARG;
1098
}
1099
1100
JS::RootedValue value(cx);
1101
if (!JS_GetPropertyById(cx, obj, eventPropertyIds[i], &value) ||
1102
!value.isObject()) {
1103
mozilla::Telemetry::AccumulateCategorical(
1104
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other);
1105
return NS_ERROR_FAILURE;
1106
}
1107
JS::RootedObject eventObj(cx, &value.toObject());
1108
1109
// Extract the event registration data.
1110
nsTArray<nsCString> methods;
1111
nsTArray<nsCString> objects;
1112
nsTArray<nsCString> extra_keys;
1113
bool expired = false;
1114
bool recordOnRelease = false;
1115
1116
// The methods & objects properties are required.
1117
if (!GetArrayPropertyValues(cx, eventObj, "methods", &methods)) {
1118
mozilla::Telemetry::AccumulateCategorical(
1119
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other);
1120
return NS_ERROR_FAILURE;
1121
}
1122
1123
if (!GetArrayPropertyValues(cx, eventObj, "objects", &objects)) {
1124
mozilla::Telemetry::AccumulateCategorical(
1125
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other);
1126
return NS_ERROR_FAILURE;
1127
}
1128
1129
// extra_keys is optional.
1130
bool hasProperty = false;
1131
if (JS_HasProperty(cx, eventObj, "extra_keys", &hasProperty) &&
1132
hasProperty) {
1133
if (!GetArrayPropertyValues(cx, eventObj, "extra_keys", &extra_keys)) {
1134
mozilla::Telemetry::AccumulateCategorical(
1135
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other);
1136
return NS_ERROR_FAILURE;
1137
}
1138
}
1139
1140
// expired is optional.
1141
if (JS_HasProperty(cx, eventObj, "expired", &hasProperty) && hasProperty) {
1142
JS::RootedValue temp(cx);
1143
if (!JS_GetProperty(cx, eventObj, "expired", &temp) ||
1144
!temp.isBoolean()) {
1145
mozilla::Telemetry::AccumulateCategorical(
1146
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other);
1147
return NS_ERROR_FAILURE;
1148
}
1149
1150
expired = temp.toBoolean();
1151
}
1152
1153
// record_on_release is optional.
1154
if (JS_HasProperty(cx, eventObj, "record_on_release", &hasProperty) &&
1155
hasProperty) {
1156
JS::RootedValue temp(cx);
1157
if (!JS_GetProperty(cx, eventObj, "record_on_release", &temp) ||
1158
!temp.isBoolean()) {
1159
mozilla::Telemetry::AccumulateCategorical(
1160
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Other);
1161
return NS_ERROR_FAILURE;
1162
}
1163
1164
recordOnRelease = temp.toBoolean();
1165
}
1166
1167
// Validate methods.
1168
for (auto& method : methods) {
1169
if (!IsValidIdentifierString(method, kMaxMethodNameByteLength, false,
1170
true)) {
1171
JS_ReportErrorASCII(
1172
cx, "Method names should match the identifier pattern.");
1173
mozilla::Telemetry::AccumulateCategorical(
1174
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Method);
1175
return NS_ERROR_INVALID_ARG;
1176
}
1177
}
1178
1179
// Validate objects.
1180
for (auto& object : objects) {
1181
if (!IsValidIdentifierString(object, kMaxObjectNameByteLength, false,
1182
true)) {
1183
JS_ReportErrorASCII(
1184
cx, "Object names should match the identifier pattern.");
1185
mozilla::Telemetry::AccumulateCategorical(
1186
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::Object);
1187
return NS_ERROR_INVALID_ARG;
1188
}
1189
}
1190
1191
// Validate extra keys.
1192
if (extra_keys.Length() > kMaxExtraKeyCount) {
1193
JS_ReportErrorASCII(cx, "No more than 10 extra keys can be registered.");
1194
mozilla::Telemetry::AccumulateCategorical(
1195
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::ExtraKeys);
1196
return NS_ERROR_INVALID_ARG;
1197
}
1198
for (auto& key : extra_keys) {
1199
if (!IsValidIdentifierString(key, kMaxExtraKeyNameByteLength, false,
1200
true)) {
1201
JS_ReportErrorASCII(
1202
cx, "Extra key names should match the identifier pattern.");
1203
mozilla::Telemetry::AccumulateCategorical(
1204
LABELS_TELEMETRY_EVENT_REGISTRATION_ERROR::ExtraKeys);
1205
return NS_ERROR_INVALID_ARG;
1206
}
1207
}
1208
1209
// Append event infos to be registered.
1210
for (auto& method : methods) {
1211
for (auto& object : objects) {
1212
// We defer the actual registration here in case any other event
1213
// description is invalid. In that case we don't need to roll back any
1214
// partial registration.
1215
DynamicEventInfo info{aCategory, method, object,
1216
extra_keys, recordOnRelease, aBuiltin};
1217
newEventInfos.AppendElement(info);
1218
newEventExpired.AppendElement(expired);
1219
}
1220
}
1221
}
1222
1223
{
1224
StaticMutexAutoLock locker(gTelemetryEventsMutex);
1225
RegisterEvents(locker, aCategory, newEventInfos, newEventExpired, aBuiltin);
1226
}
1227
1228
return NS_OK;
1229
}
1230
1231
nsresult TelemetryEvent::CreateSnapshots(uint32_t aDataset, bool aClear,
1232
uint32_t aEventLimit, JSContext* cx,
1233
uint8_t optional_argc,
1234
JS::MutableHandleValue aResult) {
1235
if (!XRE_IsParentProcess()) {
1236
return NS_ERROR_FAILURE;
1237
}
1238
1239
// Creating a JS snapshot of the events is a two-step process:
1240
// (1) Lock the storage and copy the events into function-local storage.
1241
// (2) Serialize the events into JS.
1242
// We can't hold a lock for (2) because we will run into deadlocks otherwise
1243
// from JS recording Telemetry.
1244
1245
// (1) Extract the events from storage with a lock held.
1246
nsTArray<mozilla::Pair<const char*, EventRecordArray>> processEvents;
1247
nsTArray<mozilla::Pair<uint32_t, EventRecordArray>> leftovers;
1248
{
1249
StaticMutexAutoLock locker(gTelemetryEventsMutex);
1250
1251
if (!gInitDone) {
1252
return NS_ERROR_FAILURE;
1253
}
1254
1255
// The snapshotting function is the same for both static and dynamic builtin
1256
// events. We can use the same function and store the events in the same
1257
// output storage.
1258
auto snapshotter = [aDataset, &locker, &processEvents, &leftovers, aClear,
1259
optional_argc,
1260
aEventLimit](EventRecordsMapType& aProcessStorage) {
1261
for (auto iter = aProcessStorage.Iter(); !iter.Done(); iter.Next()) {
1262
const EventRecordArray* eventStorage =
1263
static_cast<EventRecordArray*>(iter.Data());
1264
EventRecordArray events;
1265
EventRecordArray leftoverEvents;
1266
1267
const uint32_t len = eventStorage->Length();
1268
for (uint32_t i = 0; i < len; ++i) {
1269
const EventRecord& record = (*eventStorage)[i];
1270
if (IsInDataset(GetDataset(locker, record.GetEventKey()), aDataset)) {
1271
// If we have a limit, adhere to it. If we have a limit and are
1272
// going to clear, save the leftovers for later.
1273
if (optional_argc < 2 || events.Length() < aEventLimit) {
1274
events.AppendElement(record);
1275
} else if (aClear) {
1276
leftoverEvents.AppendElement(record);
1277
}
1278
}
1279
}
1280
1281
if (events.Length()) {
1282
const char* processName = GetNameForProcessID(ProcessID(iter.Key()));
1283
processEvents.AppendElement(
1284
mozilla::MakePair(processName, std::move(events)));
1285
if (leftoverEvents.Length()) {
1286
leftovers.AppendElement(
1287
mozilla::MakePair(iter.Key(), std::move(leftoverEvents)));
1288
}
1289
}
1290
}
1291
};
1292
1293
// Take a snapshot of the plain and dynamic builtin events.
1294
snapshotter(gEventRecords);
1295
if (aClear) {
1296
gEventRecords.Clear();
1297
for (auto pair : leftovers) {
1298
gEventRecords.Put(pair.first(),
1299
new EventRecordArray(std::move(pair.second())));
1300
}
1301
leftovers.Clear();
1302
}
1303
}
1304
1305
// (2) Serialize the events to a JS object.
1306
JS::RootedObject rootObj(cx, JS_NewPlainObject(cx));
1307
if (!rootObj) {
1308
return NS_ERROR_FAILURE;
1309
}
1310
1311
const uint32_t processLength = processEvents.Length();
1312
for (uint32_t i = 0; i < processLength; ++i) {
1313
JS::RootedObject eventsArray(cx);
1314
if (NS_FAILED(SerializeEventsArray(processEvents[i].second(), cx,
1315
&eventsArray, aDataset))) {
1316
return NS_ERROR_FAILURE;
1317
}
1318
1319
if (!JS_DefineProperty(cx, rootObj, processEvents[i].first(), eventsArray,
1320
JSPROP_ENUMERATE)) {
1321
return NS_ERROR_FAILURE;
1322
}
1323
}
1324
1325
aResult.setObject(*rootObj);
1326
return NS_OK;
1327
}
1328
1329
/**
1330
* Resets all the stored events. This is intended to be only used in tests.
1331
*/
1332
void TelemetryEvent::ClearEvents() {
1333
StaticMutexAutoLock lock(gTelemetryEventsMutex);
1334
1335
if (!gInitDone) {
1336
return;
1337
}
1338
1339
gEventRecords.Clear();
1340
}
1341
1342
void TelemetryEvent::SetEventRecordingEnabled(const nsACString& category,
1343
bool enabled) {
1344
StaticMutexAutoLock locker(gTelemetryEventsMutex);
1345
1346
if (!gCategoryNames.Contains(category)) {
1347
LogToBrowserConsole(
1348
nsIScriptError::warningFlag,
1349
NS_ConvertUTF8toUTF16(
1350
NS_LITERAL_CSTRING(
1351
"Unknown category for SetEventRecordingEnabled: ") +
1352
category));
1353
return;
1354
}
1355
1356
if (enabled) {
1357
gEnabledCategories.PutEntry(category);
1358
} else {
1359
gEnabledCategories.RemoveEntry(category);
1360
}
1361
}
1362
1363
size_t TelemetryEvent::SizeOfIncludingThis(
1364
mozilla::MallocSizeOf aMallocSizeOf) {
1365
StaticMutexAutoLock locker(gTelemetryEventsMutex);
1366
size_t n = 0;
1367
1368
auto getSizeOfRecords = [aMallocSizeOf](auto& storageMap) {
1369
size_t partial = storageMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
1370
for (auto iter = storageMap.Iter(); !iter.Done(); iter.Next()) {
1371
EventRecordArray* eventRecords =
1372
static_cast<EventRecordArray*>(iter.Data());
1373
partial += eventRecords->ShallowSizeOfIncludingThis(aMallocSizeOf);
1374
1375
const uint32_t len = eventRecords->Length();
1376
for (uint32_t i = 0; i < len; ++i) {
1377
partial += (*eventRecords)[i].SizeOfExcludingThis(aMallocSizeOf);
1378
}
1379
}
1380
return partial;
1381
};
1382
1383
n += getSizeOfRecords(gEventRecords);
1384
1385
n += gEventNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
1386
for (auto iter = gEventNameIDMap.ConstIter(); !iter.Done(); iter.Next()) {
1387
n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
1388
}
1389
1390
n += gCategoryNames.ShallowSizeOfExcludingThis(aMallocSizeOf);
1391
n += gEnabledCategories.ShallowSizeOfExcludingThis(aMallocSizeOf);
1392
1393
if (gDynamicEventInfo) {
1394
n += gDynamicEventInfo->ShallowSizeOfIncludingThis(aMallocSizeOf);
1395
for (auto& info : *gDynamicEventInfo) {
1396
n += info.SizeOfExcludingThis(aMallocSizeOf);
1397
}
1398
}
1399
1400
return n;
1401
}