Backed out 3 changesets (bug 1302681) for build failures in TelemetryEvent.cpp a=backout

Backed out changeset 9c0bdff48a0a (bug 1302681)
Backed out changeset 8e79158a7a1c (bug 1302681)
Backed out changeset 7068c8d4448c (bug 1302681)

MozReview-Commit-ID: Y7pfsOJRqe
This commit is contained in:
Wes Kocher 2017-07-27 12:53:32 -07:00
Родитель 442d7cef56
Коммит 807c023ca7
11 изменённых файлов: 126 добавлений и 860 удалений

Просмотреть файл

@ -35,9 +35,9 @@ struct CommonEventInfo {
mozilla::Telemetry::Common::RecordedProcessType record_in_processes;
// Convenience functions for accessing event strings.
const nsCString expiration_version() const;
const nsCString category() const;
const nsCString extra_key(uint32_t index) const;
const char* expiration_version() const;
const char* category() const;
const char* extra_key(uint32_t index) const;
};
struct EventInfo {
@ -48,8 +48,8 @@ struct EventInfo {
uint32_t method_offset;
uint32_t object_offset;
const nsCString method() const;
const nsCString object() const;
const char* method() const;
const char* object() const;
};
} // namespace

Просмотреть файл

@ -19,8 +19,3 @@ extension:
gpu:
gecko_enum: GeckoProcessType_GPU
description: This is the compositor or GPU process.
dynamic:
gecko_enum: GeckoProcessType_Default
description: >
This is not a real process, it is used to logically group add-on probes.
It contains data of any probes registered at runtime by add-ons.

Просмотреть файл

@ -1807,20 +1807,12 @@ TelemetryImpl::RecordEvent(const nsACString & aCategory, const nsACString & aMet
}
NS_IMETHODIMP
TelemetryImpl::SnapshotEvents(uint32_t aDataset, bool aClear, JSContext* aCx,
TelemetryImpl::SnapshotBuiltinEvents(uint32_t aDataset, bool aClear, JSContext* aCx,
uint8_t optional_argc, JS::MutableHandleValue aResult)
{
return TelemetryEvent::CreateSnapshots(aDataset, aClear, aCx, optional_argc, aResult);
}
NS_IMETHODIMP
TelemetryImpl::RegisterEvents(const nsACString& aCategory,
JS::Handle<JS::Value> aEventData,
JSContext* cx)
{
return TelemetryEvent::RegisterEvents(aCategory, aEventData, cx);
}
NS_IMETHODIMP
TelemetryImpl::ClearEvents()
{

Просмотреть файл

@ -5,7 +5,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <prtime.h>
#include <limits>
#include "nsITelemetry.h"
#include "nsHashKeys.h"
#include "nsDataHashtable.h"
@ -20,7 +19,6 @@
#include "nsJSUtils.h"
#include "nsXULAppAPI.h"
#include "nsUTF8Utils.h"
#include "nsPrintfCString.h"
#include "TelemetryCommon.h"
#include "TelemetryEvent.h"
@ -91,10 +89,9 @@ namespace {
const uint32_t kEventCount = mozilla::Telemetry::EventID::EventCount;
// This is a special event id used to mark expired events, to make expiry checks
// cheap at runtime.
const uint32_t kExpiredEventId = std::numeric_limits<uint32_t>::max();
static_assert(kExpiredEventId > kEventCount,
"Built-in event count should be less than the expired event id.");
// faster at runtime.
const uint32_t kExpiredEventId = kEventCount + 1;
static_assert(kEventCount < kExpiredEventId, "Should not overflow.");
// This is the hard upper limit on the number of event records we keep in storage.
// If we cross this limit, we will drop any further event recording until elements
@ -104,60 +101,10 @@ const uint32_t kMaxEventRecords = 1000;
const uint32_t kMaxValueByteLength = 80;
// Maximum length of any string value in the extra dictionary, in UTF8 byte sequence length.
const uint32_t kMaxExtraValueByteLength = 80;
// Maximum length of dynamic method names, in UTF8 byte sequence length.
const uint32_t kMaxMethodNameByteLength = 20;
// Maximum length of dynamic object names, in UTF8 byte sequence length.
const uint32_t kMaxObjectNameByteLength = 20;
// Maximum length of extra key names, in UTF8 byte sequence length.
const uint32_t kMaxExtraKeyNameByteLength = 15;
// The maximum number of valid extra keys for an event.
const uint32_t kMaxExtraKeyCount = 10;
typedef nsDataHashtable<nsCStringHashKey, uint32_t> StringUintMap;
typedef nsClassHashtable<nsCStringHashKey, nsCString> StringMap;
struct EventKey {
uint32_t id;
bool dynamic;
};
struct DynamicEventInfo {
DynamicEventInfo(const nsACString& category, const nsACString& method,
const nsACString& object, const nsTArray<nsCString>& extra_keys,
bool recordOnRelease)
: category(category)
, method(method)
, object(object)
, extra_keys(extra_keys)
, recordOnRelease(recordOnRelease)
{}
DynamicEventInfo(const DynamicEventInfo&) = default;
DynamicEventInfo& operator=(const DynamicEventInfo&) = delete;
const nsCString category;
const nsCString method;
const nsCString object;
const nsTArray<nsCString> extra_keys;
const bool recordOnRelease;
size_t
SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t n = 0;
n += category.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
n += method.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
n += object.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
n += extra_keys.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (auto& key : extra_keys) {
n += key.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
}
return n;
}
};
enum class RecordEventResult {
Ok,
UnknownEvent,
@ -167,29 +114,29 @@ enum class RecordEventResult {
WrongProcess,
};
enum class RegisterEventResult {
Ok,
AlreadyRegistered,
};
typedef nsTArray<EventExtraEntry> ExtraArray;
class EventRecord {
public:
EventRecord(double timestamp, const EventKey& key, const Maybe<nsCString>& value,
EventRecord(double timestamp, uint32_t eventId, const Maybe<nsCString>& value,
const ExtraArray& extra)
: mTimestamp(timestamp)
, mEventKey(key)
, mEventId(eventId)
, mValue(value)
, mExtra(extra)
{}
EventRecord(const EventRecord& other) = default;
EventRecord(const EventRecord& other)
: mTimestamp(other.mTimestamp)
, mEventId(other.mEventId)
, mValue(other.mValue)
, mExtra(other.mExtra)
{}
EventRecord& operator=(const EventRecord& other) = delete;
double Timestamp() const { return mTimestamp; }
const EventKey& GetEventKey() const { return mEventKey; }
uint32_t EventId() const { return mEventId; }
const Maybe<nsCString>& Value() const { return mValue; }
const ExtraArray& Extra() const { return mExtra; }
@ -197,43 +144,43 @@ public:
private:
const double mTimestamp;
const EventKey mEventKey;
const uint32_t mEventId;
const Maybe<nsCString> mValue;
const ExtraArray mExtra;
};
// Implements the methods for EventInfo.
const nsCString
const char*
EventInfo::method() const
{
return nsCString(&gEventsStringTable[this->method_offset]);
return &gEventsStringTable[this->method_offset];
}
const nsCString
const char*
EventInfo::object() const
{
return nsCString(&gEventsStringTable[this->object_offset]);
return &gEventsStringTable[this->object_offset];
}
// Implements the methods for CommonEventInfo.
const nsCString
const char*
CommonEventInfo::category() const
{
return nsCString(&gEventsStringTable[this->category_offset]);
return &gEventsStringTable[this->category_offset];
}
const nsCString
const char*
CommonEventInfo::expiration_version() const
{
return nsCString(&gEventsStringTable[this->expiration_version_offset]);
return &gEventsStringTable[this->expiration_version_offset];
}
const nsCString
const char*
CommonEventInfo::extra_key(uint32_t index) const
{
MOZ_ASSERT(index < this->extra_count);
uint32_t key_index = gExtraKeysTable[this->extra_index + index];
return nsCString(&gEventsStringTable[key_index]);
return &gEventsStringTable[key_index];
}
// Implementation for the EventRecord class.
@ -270,17 +217,9 @@ UniqueEventName(const nsACString& category, const nsACString& method, const nsAC
nsCString
UniqueEventName(const EventInfo& info)
{
return UniqueEventName(info.common_info.category(),
info.method(),
info.object());
}
nsCString
UniqueEventName(const DynamicEventInfo& info)
{
return UniqueEventName(info.category,
info.method,
info.object);
return UniqueEventName(nsDependentCString(info.common_info.category()),
nsDependentCString(info.method()),
nsDependentCString(info.object()));
}
bool
@ -316,23 +255,20 @@ bool gInitDone = false;
bool gCanRecordBase;
bool gCanRecordExtended;
// The EventName -> EventKey cache map.
nsClassHashtable<nsCStringHashKey, EventKey> gEventNameIDMap(kEventCount);
// The EventName -> EventID cache map.
StringUintMap gEventNameIDMap(kEventCount);
// The CategoryName -> CategoryID cache map.
StringUintMap gCategoryNameIDMap;
// This tracks the IDs of the categories for which recording is enabled.
nsTHashtable<nsCStringHashKey> gEnabledCategories;
nsTHashtable<nsUint32HashKey> gEnabledCategories;
// The main event storage. Events are inserted here, keyed by process id and
// in recording order.
typedef nsTArray<EventRecord> EventRecordArray;
nsClassHashtable<nsUint32HashKey, EventRecordArray> gEventRecords;
// The details on dynamic events that are recorded from addons are registered here.
nsTArray<DynamicEventInfo> gDynamicEventInfo;
} // namespace
////////////////////////////////////////////////////////////////////////
@ -342,60 +278,27 @@ nsTArray<DynamicEventInfo> gDynamicEventInfo;
namespace {
unsigned int
GetDataset(const StaticMutexAutoLock& lock, const EventKey& eventKey)
{
if (!eventKey.dynamic) {
return gEventInfo[eventKey.id].common_info.dataset;
}
return gDynamicEventInfo[eventKey.id].recordOnRelease ?
nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT :
nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;
}
nsCString
GetCategory(const StaticMutexAutoLock& lock, const EventKey& eventKey)
{
if (!eventKey.dynamic) {
return gEventInfo[eventKey.id].common_info.category();
}
return gDynamicEventInfo[eventKey.id].category;
}
bool
CanRecordEvent(const StaticMutexAutoLock& lock, const EventKey& eventKey,
CanRecordEvent(const StaticMutexAutoLock& lock, const CommonEventInfo& info,
ProcessID process)
{
if (!gCanRecordBase) {
return false;
}
if (!CanRecordDataset(GetDataset(lock, eventKey), gCanRecordBase, gCanRecordExtended)) {
if (!CanRecordDataset(info.dataset, gCanRecordBase, gCanRecordExtended)) {
return false;
}
// We don't allow specifying a process to record in for dynamic events.
if (!eventKey.dynamic) {
const CommonEventInfo& info = gEventInfo[eventKey.id].common_info;
if (!CanRecordInProcess(info.record_in_processes, process)) {
return false;
}
if (!CanRecordInProcess(info.record_in_processes, process)) {
return false;
}
return gEnabledCategories.GetEntry(GetCategory(lock, eventKey));
}
bool
IsExpired(const EventKey& key)
{
return key.id == kExpiredEventId;
return gEnabledCategories.GetEntry(info.category_offset);
}
EventRecordArray*
GetEventRecordsForProcess(const StaticMutexAutoLock& lock, ProcessID processType,
const EventKey& eventKey)
GetEventRecordsForProcess(const StaticMutexAutoLock& lock, ProcessID processType)
{
EventRecordArray* eventRecords = nullptr;
if (!gEventRecords.Get(uint32_t(processType), &eventRecords)) {
@ -405,41 +308,14 @@ GetEventRecordsForProcess(const StaticMutexAutoLock& lock, ProcessID processType
return eventRecords;
}
EventKey*
GetEventKey(const StaticMutexAutoLock& lock, const nsACString& category,
const nsACString& method, const nsACString& object)
bool
GetEventId(const StaticMutexAutoLock& lock, const nsACString& category,
const nsACString& method, const nsACString& object,
uint32_t* eventId)
{
EventKey* event;
MOZ_ASSERT(eventId);
const nsCString& name = UniqueEventName(category, method, object);
if (!gEventNameIDMap.Get(name, &event)) {
return nullptr;
}
return event;
}
static bool
CheckExtraKeysValid(const EventKey& eventKey, const ExtraArray& extra)
{
nsTHashtable<nsCStringHashKey> validExtraKeys;
if (!eventKey.dynamic) {
const CommonEventInfo& common = gEventInfo[eventKey.id].common_info;
for (uint32_t i = 0; i < common.extra_count; ++i) {
validExtraKeys.PutEntry(common.extra_key(i));
}
} else {
const DynamicEventInfo& info = gDynamicEventInfo[eventKey.id];
for (uint32_t i = 0, len = info.extra_keys.Length(); i < len; ++i) {
validExtraKeys.PutEntry(info.extra_keys[i]);
}
}
for (uint32_t i = 0; i < extra.Length(); ++i) {
if (!validExtraKeys.GetEntry(extra[i].key)) {
return false;
}
}
return true;
return gEventNameIDMap.Get(name, eventId);
}
RecordEventResult
@ -448,43 +324,47 @@ RecordEvent(const StaticMutexAutoLock& lock, ProcessID processType,
const nsACString& method, const nsACString& object,
const Maybe<nsCString>& value, const ExtraArray& extra)
{
// Look up the event id.
EventKey* eventKey = GetEventKey(lock, category, method, object);
if (!eventKey) {
return RecordEventResult::UnknownEvent;
}
if (eventKey->dynamic) {
processType = ProcessID::Dynamic;
}
EventRecordArray* eventRecords = GetEventRecordsForProcess(lock, processType, *eventKey);
EventRecordArray* eventRecords = GetEventRecordsForProcess(lock, processType);
// Apply hard limit on event count in storage.
if (eventRecords->Length() >= kMaxEventRecords) {
return RecordEventResult::StorageLimitReached;
}
// Look up the event id.
uint32_t eventId;
if (!GetEventId(lock, category, method, object, &eventId)) {
return RecordEventResult::UnknownEvent;
}
// If the event is expired or not enabled for this process, we silently drop this call.
// We don't want recording for expired probes to be an error so code doesn't
// have to be removed at a specific time or version.
// Even logging warnings would become very noisy.
if (IsExpired(*eventKey)) {
if (eventId == kExpiredEventId) {
return RecordEventResult::ExpiredEvent;
}
// Check whether we can record this event.
if (!CanRecordEvent(lock, *eventKey, processType)) {
const CommonEventInfo& common = gEventInfo[eventId].common_info;
if (!CanRecordEvent(lock, common, processType)) {
return RecordEventResult::Ok;
}
// Check whether the extra keys passed are valid.
if (!CheckExtraKeysValid(*eventKey, extra)) {
return RecordEventResult::InvalidExtraKey;
nsTHashtable<nsCStringHashKey> validExtraKeys;
for (uint32_t i = 0; i < common.extra_count; ++i) {
validExtraKeys.PutEntry(nsDependentCString(common.extra_key(i)));
}
for (uint32_t i = 0; i < extra.Length(); ++i) {
if (!validExtraKeys.GetEntry(extra[i].key)) {
return RecordEventResult::InvalidExtraKey;
}
}
// Add event record.
eventRecords->AppendElement(EventRecord(timestamp, *eventKey, value, extra));
eventRecords->AppendElement(EventRecord(timestamp, eventId, value, extra));
return RecordEventResult::Ok;
}
@ -492,18 +372,16 @@ RecordEventResult
ShouldRecordChildEvent(const StaticMutexAutoLock& lock, const nsACString& category,
const nsACString& method, const nsACString& object)
{
EventKey* eventKey = GetEventKey(lock, category, method, object);
if (!eventKey) {
// This event is unknown in this process, but it might be a dynamic event
// that was registered in the parent process.
return RecordEventResult::Ok;
uint32_t eventId;
if (!GetEventId(lock, category, method, object, &eventId)) {
return RecordEventResult::UnknownEvent;
}
if (IsExpired(*eventKey)) {
if (eventId == kExpiredEventId) {
return RecordEventResult::ExpiredEvent;
}
const auto processes = gEventInfo[eventKey->id].common_info.record_in_processes;
const auto processes = gEventInfo[eventId].common_info.record_in_processes;
if (!CanRecordInProcess(processes, XRE_GetProcessType())) {
return RecordEventResult::WrongProcess;
}
@ -511,33 +389,6 @@ ShouldRecordChildEvent(const StaticMutexAutoLock& lock, const nsACString& catego
return RecordEventResult::Ok;
}
RegisterEventResult
RegisterEvents(const StaticMutexAutoLock& lock, const nsACString& category,
const nsTArray<DynamicEventInfo>& eventInfos,
const nsTArray<bool>& eventExpired)
{
MOZ_ASSERT(eventInfos.Length() == eventExpired.Length(), "Event data array sizes should match.");
// Check that none of the events are already registered.
for (auto& info : eventInfos) {
if (gEventNameIDMap.Get(UniqueEventName(info))) {
return RegisterEventResult::AlreadyRegistered;
}
}
// Register the new events.
for (uint32_t i = 0, len = eventInfos.Length(); i < len; ++i) {
gDynamicEventInfo.AppendElement(eventInfos[i]);
uint32_t eventId = eventExpired[i] ? kExpiredEventId : gDynamicEventInfo.Length() - 1;
gEventNameIDMap.Put(UniqueEventName(eventInfos[i]), new EventKey{eventId, true});
}
// Now after successful registration enable recording for this category.
gEnabledCategories.PutEntry(category);
return RegisterEventResult::Ok;
}
} // anonymous namespace
////////////////////////////////////////////////////////////////////////
@ -550,8 +401,7 @@ namespace {
nsresult
SerializeEventsArray(const EventRecordArray& events,
JSContext* cx,
JS::MutableHandleObject result,
unsigned int dataset)
JS::MutableHandleObject result)
{
// We serialize the events to a JS array.
JS::RootedObject eventsArray(cx, JS_NewArrayObject(cx, events.Length()));
@ -561,6 +411,7 @@ SerializeEventsArray(const EventRecordArray& events,
for (uint32_t i = 0; i < events.Length(); ++i) {
const EventRecord& record = events[i];
const EventInfo& info = gEventInfo[record.EventId()];
// Each entry is an array of one of the forms:
// [timestamp, category, method, object, value]
@ -575,21 +426,12 @@ SerializeEventsArray(const EventRecordArray& events,
}
// Add category, method, object.
nsCString strings[3];
const EventKey& eventKey = record.GetEventKey();
if (!eventKey.dynamic) {
const EventInfo& info = gEventInfo[eventKey.id];
strings[0] = info.common_info.category();
strings[1] = info.method();
strings[2] = info.object();
} else {
const DynamicEventInfo& info = gDynamicEventInfo[eventKey.id];
strings[0] = info.category;
strings[1] = info.method;
strings[2] = info.object;
}
for (const nsCString& s : strings) {
const char* strings[] = {
info.common_info.category(),
info.method(),
info.object(),
};
for (const char* s : strings) {
const NS_ConvertUTF8toUTF16 wide(s);
if (!items.append(JS::StringValue(JS_NewUCStringCopyN(cx, wide.Data(), wide.Length())))) {
return NS_ERROR_FAILURE;
@ -687,19 +529,20 @@ TelemetryEvent::InitializeGlobalState(bool aCanRecordBase, bool aCanRecordExtend
// If this event is expired or not recorded in this process, mark it with
// a special event id.
// This avoids doing repeated checks at runtime.
if (IsExpiredVersion(info.common_info.expiration_version().get()) ||
if (IsExpiredVersion(info.common_info.expiration_version()) ||
IsExpiredDate(info.common_info.expiration_day)) {
eventId = kExpiredEventId;
}
gEventNameIDMap.Put(UniqueEventName(info), new EventKey{eventId, false});
if (!gCategoryNameIDMap.Contains(info.common_info.category())) {
gCategoryNameIDMap.Put(info.common_info.category(),
gEventNameIDMap.Put(UniqueEventName(info), eventId);
if (!gCategoryNameIDMap.Contains(nsDependentCString(info.common_info.category()))) {
gCategoryNameIDMap.Put(nsDependentCString(info.common_info.category()),
info.common_info.category_offset);
}
}
#ifdef DEBUG
gEventNameIDMap.MarkImmutable();
gCategoryNameIDMap.MarkImmutable();
#endif
gInitDone = true;
@ -876,14 +719,10 @@ TelemetryEvent::RecordEvent(const nsACString& aCategory, const nsACString& aMeth
PromiseFlatCString(aObject).get());
return NS_ERROR_INVALID_ARG;
}
case RecordEventResult::InvalidExtraKey: {
nsPrintfCString msg(R"(Invalid extra key for event ["%s", "%s", "%s"].)",
PromiseFlatCString(aCategory).get(),
PromiseFlatCString(aMethod).get(),
PromiseFlatCString(aObject).get());
LogToBrowserConsole(nsIScriptError::warningFlag, NS_ConvertUTF8toUTF16(msg));
case RecordEventResult::InvalidExtraKey:
LogToBrowserConsole(nsIScriptError::warningFlag,
NS_LITERAL_STRING("Invalid extra key for event."));
return NS_OK;
}
case RecordEventResult::StorageLimitReached:
LogToBrowserConsole(nsIScriptError::warningFlag,
NS_LITERAL_STRING("Event storage limit reached."));
@ -893,223 +732,6 @@ TelemetryEvent::RecordEvent(const nsACString& aCategory, const nsACString& aMeth
}
}
static bool
GetArrayPropertyValues(JSContext* cx, JS::HandleObject obj, const char* property,
nsTArray<nsCString>* results)
{
JS::RootedValue value(cx);
if (!JS_GetProperty(cx, obj, property, &value)) {
JS_ReportErrorASCII(cx, R"(Missing required property "%s" for event)", property);
return false;
}
bool isArray = false;
if (!JS_IsArrayObject(cx, value, &isArray) || !isArray) {
JS_ReportErrorASCII(cx, R"(Property "%s" for event should be an array)", property);
return false;
}
JS::RootedObject arrayObj(cx, &value.toObject());
uint32_t arrayLength;
if (!JS_GetArrayLength(cx, arrayObj, &arrayLength)) {
return false;
}
for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; ++arrayIdx) {
JS::Rooted<JS::Value> element(cx);
if (!JS_GetElement(cx, arrayObj, arrayIdx, &element)) {
return false;
}
if (!element.isString()) {
JS_ReportErrorASCII(cx, R"(Array entries for event property "%s" should be strings)", property);
return false;
}
nsAutoJSString jsStr;
if (!jsStr.init(cx, element)) {
return false;
}
results->AppendElement(NS_ConvertUTF16toUTF8(jsStr));
}
return true;
}
static bool
IsStringCharValid(const char aChar, const bool allowInfixPeriod)
{
return (aChar >= 'A' && aChar <= 'Z')
|| (aChar >= 'a' && aChar <= 'z')
|| (aChar >= '0' && aChar <= '9')
|| (allowInfixPeriod && (aChar == '.'));
}
static bool
IsValidIdentifierString(const nsACString& str, const size_t maxLength,
const bool allowInfixPeriod)
{
// Check string length.
if (str.Length() > maxLength) {
return false;
}
// Check string characters.
const char* first = str.BeginReading();
const char* end = str.EndReading();
for (const char* cur = first; cur < end; ++cur) {
const bool allowPeriod = allowInfixPeriod && (cur != first) && (cur != (end - 1));
if (!IsStringCharValid(*cur, allowPeriod)) {
return false;
}
}
return true;
}
nsresult
TelemetryEvent::RegisterEvents(const nsACString& aCategory,
JS::Handle<JS::Value> aEventData,
JSContext* cx)
{
if (!IsValidIdentifierString(aCategory, 30, true)) {
JS_ReportErrorASCII(cx, "Category parameter should match the identifier pattern.");
return NS_ERROR_INVALID_ARG;
}
if (!aEventData.isObject()) {
JS_ReportErrorASCII(cx, "Event data parameter should be an object");
return NS_ERROR_INVALID_ARG;
}
JS::RootedObject obj(cx, &aEventData.toObject());
JS::Rooted<JS::IdVector> eventPropertyIds(cx, JS::IdVector(cx));
if (!JS_Enumerate(cx, obj, &eventPropertyIds)) {
return NS_ERROR_FAILURE;
}
// Collect the event data into local storage first.
// Only after successfully validating all contained events will we register them into global storage.
nsTArray<DynamicEventInfo> newEventInfos;
nsTArray<bool> newEventExpired;
for (size_t i = 0, n = eventPropertyIds.length(); i < n; i++) {
nsAutoJSString eventName;
if (!eventName.init(cx, eventPropertyIds[i])) {
return NS_ERROR_FAILURE;
}
if (!IsValidIdentifierString(NS_ConvertUTF16toUTF8(eventName), kMaxMethodNameByteLength, false)) {
JS_ReportErrorASCII(cx, "Event names should match the identifier pattern.");
return NS_ERROR_INVALID_ARG;
}
JS::RootedValue value(cx);
if (!JS_GetPropertyById(cx, obj, eventPropertyIds[i], &value) || !value.isObject()) {
return NS_ERROR_FAILURE;
}
JS::RootedObject eventObj(cx, &value.toObject());
// Extract the event registration data.
nsTArray<nsCString> methods;
nsTArray<nsCString> objects;
nsTArray<nsCString> extra_keys;
bool expired = false;
bool recordOnRelease = false;
// The methods & objects properties are required.
if (!GetArrayPropertyValues(cx, eventObj, "methods", &methods)) {
return NS_ERROR_FAILURE;
}
if (!GetArrayPropertyValues(cx, eventObj, "objects", &objects)) {
return NS_ERROR_FAILURE;
}
// extra_keys is optional.
bool hasProperty = false;
if (JS_HasProperty(cx, eventObj, "extra_keys", &hasProperty) && hasProperty) {
if (!GetArrayPropertyValues(cx, eventObj, "extra_keys", &extra_keys)) {
return NS_ERROR_FAILURE;
}
}
// expired is optional.
if (JS_HasProperty(cx, eventObj, "expired", &hasProperty) && hasProperty) {
JS::RootedValue temp(cx);
if (!JS_GetProperty(cx, eventObj, "expired", &temp) || !temp.isBoolean()) {
return NS_ERROR_FAILURE;
}
expired = temp.toBoolean();
}
// record_on_release is optional.
if (JS_HasProperty(cx, eventObj, "record_on_release", &hasProperty) && hasProperty) {
JS::RootedValue temp(cx);
if (!JS_GetProperty(cx, eventObj, "record_on_release", &temp) || !temp.isBoolean()) {
return NS_ERROR_FAILURE;
}
recordOnRelease = temp.toBoolean();
}
// Validate methods.
for (auto& method : methods) {
if (!IsValidIdentifierString(method, kMaxMethodNameByteLength, false)) {
JS_ReportErrorASCII(cx, "Method names should match the identifier pattern.");
return NS_ERROR_INVALID_ARG;
}
}
// Validate objects.
for (auto& object : objects) {
if (!IsValidIdentifierString(object, kMaxObjectNameByteLength, false)) {
JS_ReportErrorASCII(cx, "Object names should match the identifier pattern.");
return NS_ERROR_INVALID_ARG;
}
}
// Validate extra keys.
if (extra_keys.Length() > kMaxExtraKeyCount) {
JS_ReportErrorASCII(cx, "No more than 10 extra keys can be registered.");
return NS_ERROR_INVALID_ARG;
}
for (auto& key : extra_keys) {
if (!IsValidIdentifierString(key, kMaxExtraKeyNameByteLength, false)) {
JS_ReportErrorASCII(cx, "Extra key names should match the identifier pattern.");
return NS_ERROR_INVALID_ARG;
}
}
// Append event infos to be registered.
for (auto& method : methods) {
for (auto& object : objects) {
// We defer the actual registration here in case any other event description is invalid.
// In that case we don't need to roll back any partial registration.
DynamicEventInfo info{nsCString(aCategory), method, object,
nsTArray<nsCString>(extra_keys), recordOnRelease};
newEventInfos.AppendElement(info);
newEventExpired.AppendElement(expired);
}
}
}
RegisterEventResult res = ::RegisterEvents(StaticMutexAutoLock(gTelemetryEventsMutex),
aCategory, newEventInfos, newEventExpired);
switch (res) {
case RegisterEventResult::AlreadyRegistered:
JS_ReportErrorASCII(cx, "Attempt to register event that is already registered.");
return NS_ERROR_INVALID_ARG;
default:
break;
}
return NS_OK;
}
nsresult
TelemetryEvent::CreateSnapshots(uint32_t aDataset, bool aClear, JSContext* cx,
uint8_t optional_argc, JS::MutableHandleValue aResult)
@ -1140,7 +762,9 @@ TelemetryEvent::CreateSnapshots(uint32_t aDataset, bool aClear, JSContext* cx,
const uint32_t len = eventStorage->Length();
for (uint32_t i = 0; i < len; ++i) {
const EventRecord& record = (*eventStorage)[i];
if (IsInDataset(GetDataset(locker, record.GetEventKey()), aDataset)) {
const EventInfo& info = gEventInfo[record.EventId()];
if (IsInDataset(info.common_info.dataset, aDataset)) {
events.AppendElement(record);
}
}
@ -1166,7 +790,7 @@ TelemetryEvent::CreateSnapshots(uint32_t aDataset, bool aClear, JSContext* cx,
for (uint32_t i = 0; i < processLength; ++i)
{
JS::RootedObject eventsArray(cx);
if (NS_FAILED(SerializeEventsArray(processEvents[i].second(), cx, &eventsArray, aDataset))) {
if (NS_FAILED(SerializeEventsArray(processEvents[i].second(), cx, &eventsArray))) {
return NS_ERROR_FAILURE;
}
@ -1207,9 +831,9 @@ TelemetryEvent::SetEventRecordingEnabled(const nsACString& category, bool enable
}
if (enabled) {
gEnabledCategories.PutEntry(category);
gEnabledCategories.PutEntry(categoryId);
} else {
gEnabledCategories.RemoveEntry(category);
gEnabledCategories.RemoveEntry(categoryId);
}
}
@ -1243,10 +867,5 @@ TelemetryEvent::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
n += gEnabledCategories.ShallowSizeOfExcludingThis(aMallocSizeOf);
n += gDynamicEventInfo.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (auto& info : gDynamicEventInfo) {
n += info.SizeOfExcludingThis(aMallocSizeOf);
}
return n;
}

Просмотреть файл

@ -33,11 +33,7 @@ nsresult RecordEvent(const nsACString& aCategory, const nsACString& aMethod,
const nsACString& aObject, JS::HandleValue aValue,
JS::HandleValue aExtra, JSContext* aCx,
uint8_t optional_argc);
void SetEventRecordingEnabled(const nsACString& aCategory, bool aEnabled);
nsresult RegisterEvents(const nsACString& aCategory, JS::Handle<JS::Value> aEventData,
JSContext* cx);
nsresult CreateSnapshots(uint32_t aDataset, bool aClear, JSContext* aCx,
uint8_t optional_argc, JS::MutableHandleValue aResult);

Просмотреть файл

@ -1009,8 +1009,8 @@ var Impl = {
return [];
}
let snapshot = Telemetry.snapshotEvents(this.getDatasetType(),
clearSubsession);
let snapshot = Telemetry.snapshotBuiltinEvents(this.getDatasetType(),
clearSubsession);
// Don't return the test events outside of test environments.
if (!this._testing) {
@ -1323,9 +1323,6 @@ var Impl = {
keyedHistograms: keyedHistograms["extension"],
events: events["extension"] || [],
},
dynamic: {
events: events["dynamic"] || [],
},
};
// Only include the GPU process if we've accumulated data for it.

Просмотреть файл

@ -66,7 +66,7 @@ The YAML definition file
========================
Any event recorded into Firefox Telemetry must be registered before it can be recorded.
For any code that ships as part of Firefox that happens in `Events.yaml <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Events.yaml>`_.
This happens in `Events.yaml <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Events.yaml>`_.
The probes in the definition file are represented in a fixed-depth, three-level structure. The first level contains *category* names (grouping multiple events together), the second level contains *event* names, under which the events properties are listed. E.g.:
@ -155,9 +155,6 @@ Example:
// event: [982134, "ui", "completion", "search-bar", "yahoo",
// {"qerylen": "7", "results": "23"}]
``setEventRecordingEnabled()``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: js
Services.telemetry.setEventRecordingEnabled(category, enabled);
@ -173,49 +170,12 @@ Example:
Services.telemetry.setEventRecordingEnabled("ui", false);
// ... now "ui" events will not be recorded anymore.
``registerEvents()``
~~~~~~~~~~~~~~~~~~~~
.. code-block:: js
Services.telemetry.registerEvents(category, eventData);
Register new events from add-ons.
* ``category`` - *(required, string)* The category the events are in.
* ``eventData`` - *(required, object)* An object of the form ``{eventName1: event1Data, ...}``, where each events data is an object with the entries:
* ``methods`` - *(required, list of strings)* The valid event methods.
* ``objects`` - *(required, list of strings)* The valid event objects.
* ``extra_keys`` - *(optional, list of strings)* The valid extra keys for the event.
* ``record_on_release`` - *(optional, bool)*
For events recorded from add-ons, registration happens at runtime. Any new events must first be registered through this function before they can be recorded.
The registered categories will automatically be enabled for recording.
After registration, the events can be recorded through the ``recordEvent()`` function. They will be submitted in the main pings payload under ``processes.dynamic.events``.
New events registered here are subject to the same limitations as the ones registered through ``Events.yaml``, although the naming was in parts updated to recent policy changes.
Example:
.. code-block:: js
Services.telemetry.registerEvents("myAddon.interaction", {
"click": {
methods: ["click"],
objects: ["red_button", "blue_button"],
}
});
// Now events can be recorded.
Services.telemetry.recordEvent("myAddon.interaction", "click", "red_button");
Internal API
------------
~~~~~~~~~~~~
.. code-block:: js
Services.telemetry.snapshotEvents(dataset, clear);
Services.telemetry.snapshotBuiltinEvents(dataset, clear);
Services.telemetry.clearEvents();
These functions are only supposed to be used by Telemetry internally or in tests.
@ -226,4 +186,3 @@ Version History
- Firefox 52: Initial event support (`bug 1302663 <https://bugzilla.mozilla.org/show_bug.cgi?id=1302663>`_).
- Firefox 53: Event recording disabled by default (`bug 1329139 <https://bugzilla.mozilla.org/show_bug.cgi?id=1329139>`_).
- Firefox 54: Added child process events (`bug 1313326 <https://bugzilla.mozilla.org/show_bug.cgi?id=1313326>`_).
- Firefox 56: Added support for recording new probes from add-ons (`bug 1302681 <bug https://bugzilla.mozilla.org/show_bug.cgi?id=1302681>`_).

Просмотреть файл

@ -498,35 +498,7 @@ interface nsITelemetry : nsISupports
* @param [aClear=false] Whether to clear out the scalars after snapshotting.
*/
[implicit_jscontext, optional_argc]
jsval snapshotEvents(in uint32_t aDataset, [optional] in boolean aClear);
/**
* Register new events to record them from addons. This allows registering multiple
* events for a category. They will be valid only for the current Firefox session.
* Note that events shipping in Firefox should be registered in Events.yaml.
*
* @param aCategory The unique category the events are registered in.
* @param aEventData An object that contains registration data for 1-N events of the form:
* {
* "categoryName": {
* "methods": ["test1"],
* "objects": ["object1"],
* "record_on_release": false,
* "extra_keys": ["key1", "key2"], // optional
* "expired": false // optional, defaults to false.
* },
* ...
* }
* @param aEventData.<name>.methods List of methods for this event entry.
* @param aEventData.<name>.objects List of objects for this event entry.
* @param aEventData.<name>.extra_keys Optional, list of allowed extra keys for this event entry.
* @param aEventData.<name>.record_on_release Optional, whether to record this data on release.
* Defaults to false.
* @param aEventData.<name>.expired Optional, whether this event entry is expired. This allows
* recording it without error, but it will be discarded. Defaults to false.
*/
[implicit_jscontext]
void registerEvents(in ACString aCategory, in jsval aEventData);
jsval snapshotBuiltinEvents(in uint32_t aDataset, [optional] in boolean aClear);
/**
* Resets all the stored events. This is intended to be only used in tests.

Просмотреть файл

@ -36,18 +36,11 @@ const UNRECORDED_PARENT_EVENTS = [
["telemetry.test", "content_only", "object1"],
];
const RECORDED_DYNAMIC_EVENTS = [
["telemetry.test.dynamic", "test1", "object1"],
["telemetry.test.dynamic", "test2", "object1"],
];
function run_child_test() {
// Record some events in the "content" process.
RECORDED_CONTENT_EVENTS.forEach(e => Telemetry.recordEvent(...e));
// These events should not be recorded for the content process.
UNRECORDED_CONTENT_EVENTS.forEach(e => Telemetry.recordEvent(...e));
// Record some dynamic events from the content process.
RECORDED_DYNAMIC_EVENTS.forEach(e => Telemetry.recordEvent(...e));
}
/**
@ -57,9 +50,8 @@ function run_child_test() {
async function waitForContentEvents() {
await ContentTaskUtils.waitForCondition(() => {
const snapshot =
Telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
return Object.keys(snapshot).includes("content") &&
Object.keys(snapshot).includes("dynamic");
Telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
return Object.keys(snapshot).includes("content");
});
}
@ -82,21 +74,6 @@ add_task(async function() {
// Enable recording for the test event category.
Telemetry.setEventRecordingEnabled("telemetry.test", true);
// Register dynamic test events.
Telemetry.registerEvents("telemetry.test.dynamic", {
// Event with only required fields.
"test1": {
methods: ["test1"],
objects: ["object1"],
},
// Event with extra_keys.
"test2": {
methods: ["test2", "test2b"],
objects: ["object1"],
extra_keys: ["key1", "key2"],
},
});
// Run test in child, don't wait for it to finish: just wait for the
// MESSAGE_CHILD_TEST_DONE.
const timestampBeforeChildEvents = Telemetry.msSinceProcessStart();
@ -123,8 +100,6 @@ add_task(async function() {
Assert.ok("events" in payload.processes.parent, "Main process section should have events.");
Assert.ok("content" in payload.processes, "Should have child process section");
Assert.ok("events" in payload.processes.content, "Child process section should have events.");
Assert.ok("dynamic" in payload.processes, "Should have dynamic process section");
Assert.ok("events" in payload.processes.dynamic, "Dynamic process section should have events.");
// Check that the expected events are present from the content process.
let contentEvents = payload.processes.content.events.map(e => e.slice(1));
@ -140,13 +115,6 @@ add_task(async function() {
Assert.deepEqual(parentEvents[i], RECORDED_PARENT_EVENTS[i], "Should have recorded expected event.");
}
// Check that the expected dynamic events are present.
let dynamicEvents = payload.processes.dynamic.events.map(e => e.slice(1));
Assert.equal(dynamicEvents.length, RECORDED_DYNAMIC_EVENTS.length, "Should match expected event count.");
for (let i = 0; i < RECORDED_DYNAMIC_EVENTS.length; ++i) {
Assert.deepEqual(dynamicEvents[i], RECORDED_DYNAMIC_EVENTS[i], "Should have recorded expected event.");
}
// Check that the event timestamps are in the expected ranges.
let contentTimestamps = payload.processes.content.events.map(e => e[0]);
let parentTimestamps = payload.processes.parent.events.map(e => e[0]);
@ -159,9 +127,9 @@ add_task(async function() {
// Make sure all events are cleared from storage properly.
let snapshot =
Telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
Telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
Assert.greaterOrEqual(Object.keys(snapshot).length, 2, "Should have events from at least two processes.");
snapshot =
Telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
Telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
Assert.equal(Object.keys(snapshot).length, 0, "Should have cleared all events from storage.");
});

Просмотреть файл

@ -44,13 +44,13 @@ add_task(async function test_recording_state() {
// Both test categories should be off by default.
events.forEach(e => Telemetry.recordEvent(...e));
let snapshot = Telemetry.snapshotEvents(OPTIN, true);
let snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.equal(Object.keys(snapshot).length, 0, "Should not have recorded any events.");
// Enable one test category and see that we record correctly.
Telemetry.setEventRecordingEnabled("telemetry.test", true);
events.forEach(e => Telemetry.recordEvent(...e));
snapshot = Telemetry.snapshotEvents(OPTIN, true);
snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.ok(("parent" in snapshot), "Should have entry for main process.");
Assert.equal(snapshot.parent.length, 1, "Should have recorded one event.");
Assert.equal(snapshot.parent[0][1], "telemetry.test", "Should have recorded one event in telemetry.test");
@ -58,7 +58,7 @@ add_task(async function test_recording_state() {
// Also enable the other test category and see that we record correctly.
Telemetry.setEventRecordingEnabled("telemetry.test.second", true);
events.forEach(e => Telemetry.recordEvent(...e));
snapshot = Telemetry.snapshotEvents(OPTIN, true);
snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.ok(("parent" in snapshot), "Should have entry for main process.");
Assert.equal(snapshot.parent.length, 2, "Should have recorded two events.");
Assert.equal(snapshot.parent[0][1], "telemetry.test", "Should have recorded one event in telemetry.test");
@ -67,7 +67,7 @@ add_task(async function test_recording_state() {
// Now turn of one category again and check that this works as expected.
Telemetry.setEventRecordingEnabled("telemetry.test", false);
events.forEach(e => Telemetry.recordEvent(...e));
snapshot = Telemetry.snapshotEvents(OPTIN, true);
snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.ok(("parent" in snapshot), "Should have entry for main process.");
Assert.equal(snapshot.parent.length, 1, "Should have recorded one event.");
Assert.equal(snapshot.parent[0][1], "telemetry.test.second", "Should have recorded one event in telemetry.test.second");
@ -145,12 +145,12 @@ add_task(async function test_recording() {
};
// Check that the expected events were recorded.
let snapshot = Telemetry.snapshotEvents(OPTIN, false);
let snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, false);
Assert.ok(("parent" in snapshot), "Should have entry for main process.");
checkEvents(snapshot.parent, expected);
// Check serializing only opt-out events.
snapshot = Telemetry.snapshotEvents(OPTOUT, false);
snapshot = Telemetry.snapshotBuiltinEvents(OPTOUT, false);
Assert.ok(("parent" in snapshot), "Should have entry for main process.");
let filtered = expected.filter(e => e.optout == true);
checkEvents(snapshot.parent, filtered);
@ -167,12 +167,12 @@ add_task(async function test_clear() {
// Check that events were recorded.
// The events are cleared by passing the respective flag.
let snapshot = Telemetry.snapshotEvents(OPTIN, true);
let snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.ok(("parent" in snapshot), "Should have entry for main process.");
Assert.equal(snapshot.parent.length, 2 * COUNT, `Should have recorded ${2 * COUNT} events.`);
// Now the events should be cleared.
snapshot = Telemetry.snapshotEvents(OPTIN, false);
snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, false);
Assert.equal(Object.keys(snapshot).length, 0, `Should have cleared the events.`);
});
@ -181,17 +181,17 @@ add_task(async function test_expiry() {
// Recording call with event that is expired by version.
Telemetry.recordEvent("telemetry.test", "expired_version", "object1");
let snapshot = Telemetry.snapshotEvents(OPTIN, true);
let snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.equal(Object.keys(snapshot).length, 0, "Should not record event with expired version.");
// Recording call with event that is expired by date.
Telemetry.recordEvent("telemetry.test", "expired_date", "object1");
snapshot = Telemetry.snapshotEvents(OPTIN, true);
snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.equal(Object.keys(snapshot).length, 0, "Should not record event with expired date.");
// Recording call with event that has expiry_version and expiry_date in the future.
Telemetry.recordEvent("telemetry.test", "not_expired_optout", "object1");
snapshot = Telemetry.snapshotEvents(OPTOUT, true);
snapshot = Telemetry.snapshotBuiltinEvents(OPTOUT, true);
Assert.ok(("parent" in snapshot), "Should have entry for main process.");
Assert.equal(snapshot.parent.length, 1, "Should record event when date and version are not expired.");
});
@ -201,22 +201,22 @@ add_task(async function test_invalidParams() {
// Recording call with wrong type for value argument.
Telemetry.recordEvent("telemetry.test", "test1", "object1", 1);
let snapshot = Telemetry.snapshotEvents(OPTIN, true);
let snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.equal(Object.keys(snapshot).length, 0, "Should not record event when value argument with invalid type is passed.");
// Recording call with wrong type for extra argument.
Telemetry.recordEvent("telemetry.test", "test1", "object1", null, "invalid");
snapshot = Telemetry.snapshotEvents(OPTIN, true);
snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.equal(Object.keys(snapshot).length, 0, "Should not record event when extra argument with invalid type is passed.");
// Recording call with unknown extra key.
Telemetry.recordEvent("telemetry.test", "test1", "object1", null, {"key3": "x"});
snapshot = Telemetry.snapshotEvents(OPTIN, true);
snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.equal(Object.keys(snapshot).length, 0, "Should not record event when extra argument with invalid key is passed.");
// Recording call with invalid value type.
Telemetry.recordEvent("telemetry.test", "test1", "object1", null, {"key3": 1});
snapshot = Telemetry.snapshotEvents(OPTIN, true);
snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.equal(Object.keys(snapshot).length, 0, "Should not record event when extra argument with invalid value type is passed.");
});
@ -231,7 +231,7 @@ add_task(async function test_storageLimit() {
}
// Check that the right events were recorded.
let snapshot = Telemetry.snapshotEvents(OPTIN, true);
let snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.ok(("parent" in snapshot), "Should have entry for main process.");
let events = snapshot.parent;
Assert.equal(events.length, LIMIT, `Should have only recorded ${LIMIT} events`);
@ -274,7 +274,7 @@ add_task(async function test_valueLimits() {
}
// Check that the right events were recorded.
let snapshot = Telemetry.snapshotEvents(OPTIN, true);
let snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.ok(("parent" in snapshot), "Should have entry for main process.");
let events = snapshot.parent;
Assert.equal(events.length, expected.length,
@ -294,239 +294,10 @@ add_task(async function test_unicodeValues() {
Telemetry.recordEvent("telemetry.test", "test1", "object1", null, {"key1": value});
// Check that the values were correctly recorded.
let snapshot = Telemetry.snapshotEvents(OPTIN, true);
let snapshot = Telemetry.snapshotBuiltinEvents(OPTIN, true);
Assert.ok(("parent" in snapshot), "Should have entry for main process.");
let events = snapshot.parent;
Assert.equal(events.length, 2, "Should have recorded 2 events.");
Assert.equal(events[0][4], value, "Should have recorded the right value.");
Assert.equal(events[1][5]["key1"], value, "Should have recorded the right extra value.");
});
add_task(function* test_dynamicEvents() {
Telemetry.clearEvents();
Telemetry.canRecordExtended = true;
// Register some test events.
Telemetry.registerEvents("telemetry.test.dynamic", {
// Event with only required fields.
"test1": {
methods: ["test1"],
objects: ["object1"],
},
// Event with extra_keys.
"test2": {
methods: ["test2", "test2b"],
objects: ["object1"],
extra_keys: ["key1", "key2"],
},
// Expired event.
"test3": {
methods: ["test3"],
objects: ["object1"],
expired: true,
},
// A release-channel recording event.
"test4": {
methods: ["test4"],
objects: ["object1"],
record_on_release: true,
},
});
// Record some valid events.
Telemetry.recordEvent("telemetry.test.dynamic", "test1", "object1");
Telemetry.recordEvent("telemetry.test.dynamic", "test2", "object1", null,
{"key1": "foo", "key2": "bar"});
Telemetry.recordEvent("telemetry.test.dynamic", "test3", "object1", "some value");
Telemetry.recordEvent("telemetry.test.dynamic", "test4", "object1", null);
// Test recording an unknown event.
Assert.throws(() => Telemetry.recordEvent("telemetry.test.dynamic", "unknown", "unknown"),
/Error: Unknown event: \["telemetry\.test\.dynamic", "unknown", "unknown"\]/,
"Should throw when recording an unknown dynamic event.");
// Now check that the snapshot contains the expected data.
let snapshot = Telemetry.snapshotEvents(OPTIN, false);
Assert.ok(("dynamic" in snapshot), "Should have dynamic events in the snapshot.");
let expected = [
["telemetry.test.dynamic", "test1", "object1"],
["telemetry.test.dynamic", "test2", "object1", null, {key1: "foo", key2: "bar"}],
// "test3" is epxired, so it should not be recorded.
["telemetry.test.dynamic", "test4", "object1"],
];
let events = snapshot.dynamic;
Assert.equal(events.length, expected.length, "Should have recorded the right amount of events.");
for (let i = 0; i < expected.length; ++i) {
Assert.deepEqual(events[i].slice(1), expected[i],
"Should have recorded the expected event data.");
}
// Check that the opt-out snapshot contains only the one expected event.
snapshot = Telemetry.snapshotEvents(OPTOUT, false);
Assert.ok(("dynamic" in snapshot), "Should have dynamic events in the snapshot.");
Assert.equal(snapshot.dynamic.length, 1, "Should have one opt-out event in the snapshot.");
expected = ["telemetry.test.dynamic", "test4", "object1"];
Assert.deepEqual(snapshot.dynamic[0].slice(1), expected);
// Recording with unknown extra keys should be ignored and print an error.
Telemetry.clearEvents();
Telemetry.recordEvent("telemetry.test.dynamic", "test1", "object1", null, {"key1": "foo"});
Telemetry.recordEvent("telemetry.test.dynamic", "test2", "object1", null, {"key1": "foo", "unknown": "bar"});
snapshot = Telemetry.snapshotEvents(OPTIN, true);
Assert.ok(!("dynamic" in snapshot), "Should have not recorded dynamic events with unknown extra keys.");
// Other built-in events should not show up in the "dynamic" bucket of the snapshot.
Telemetry.recordEvent("telemetry.test", "test1", "object1");
snapshot = Telemetry.snapshotEvents(OPTIN, true);
Assert.ok(!("dynamic" in snapshot), "Should have not recorded built-in event into dynamic bucket.");
// Test that recording opt-in and opt-out events works as expected.
Telemetry.clearEvents();
Telemetry.canRecordExtended = false;
Telemetry.recordEvent("telemetry.test.dynamic", "test1", "object1");
Telemetry.recordEvent("telemetry.test.dynamic", "test4", "object1");
expected = [
// Only "test4" should have been recorded.
["telemetry.test.dynamic", "test4", "object1"],
];
snapshot = Telemetry.snapshotEvents(OPTIN, true);
Assert.equal(snapshot.dynamic.length, 1, "Should have one opt-out event in the snapshot.");
Assert.deepEqual(snapshot.dynamic.map(e => e.slice(1)), expected);
});
add_task(function* test_dynamicEventRegistrationValidation() {
Telemetry.canRecordExtended = true;
Telemetry.clearEvents();
// Test registration of invalid categories.
Assert.throws(() => Telemetry.registerEvents("telemetry+test+dynamic", {
"test1": {
methods: ["test1"],
objects: ["object1"],
},
}),
/Category parameter should match the identifier pattern\./,
"Should throw when registering category names with invalid characters.");
Assert.throws(() => Telemetry.registerEvents("telemetry.test.test.test.test.test.test.test.test", {
"test1": {
methods: ["test1"],
objects: ["object1"],
},
}),
/Category parameter should match the identifier pattern\./,
"Should throw when registering overly long category names.");
// Test registration of invalid event names.
Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic1", {
"test?1": {
methods: ["test1"],
objects: ["object1"],
},
}),
/Event names should match the identifier pattern\./,
"Should throw when registering event names with invalid characters.");
Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic2", {
"test1test1test1test1test1test1test1": {
methods: ["test1"],
objects: ["object1"],
},
}),
/Event names should match the identifier pattern\./,
"Should throw when registering overly long event names.");
// Test registration of invalid method names.
Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic3", {
"test1": {
methods: ["test?1"],
objects: ["object1"],
},
}),
/Method names should match the identifier pattern\./,
"Should throw when registering method names with invalid characters.");
Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic", {
"test1": {
methods: ["test1test1test1test1test1test1test1"],
objects: ["object1"],
},
}),
/Method names should match the identifier pattern\./,
"Should throw when registering overly long method names.");
// Test registration of invalid object names.
Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic4", {
"test1": {
methods: ["test1"],
objects: ["object?1"],
},
}),
/Object names should match the identifier pattern\./,
"Should throw when registering object names with invalid characters.");
Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic5", {
"test1": {
methods: ["test1"],
objects: ["object1object1object1object1object1object1"],
},
}),
/Object names should match the identifier pattern\./,
"Should throw when registering overly long object names.");
// Test validation of invalid key names.
Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic6", {
"test1": {
methods: ["test1"],
objects: ["object1"],
extra_keys: ["a?1"],
},
}),
/Extra key names should match the identifier pattern\./,
"Should throw when registering extra key names with invalid characters.");
// Test validation of key names that are too long - we allow a maximum of 15 characters.
Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic7", {
"test1": {
methods: ["test1"],
objects: ["object1"],
extra_keys: ["a012345678901234"],
},
}),
/Extra key names should match the identifier pattern\./,
"Should throw when registering extra key names which are too long.");
Telemetry.registerEvents("telemetry.test.dynamic8", {
"test1": {
methods: ["test1"],
objects: ["object1"],
extra_keys: ["a01234567890123"],
},
});
// Test validation of extra key count - we only allow 10.
Assert.throws(() => Telemetry.registerEvents("telemetry.test.dynamic9", {
"test1": {
methods: ["test1"],
objects: ["object1"],
extra_keys: ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11"],
},
}),
/No more than 10 extra keys can be registered\./,
"Should throw when registering too many extra keys.");
Telemetry.registerEvents("telemetry.test.dynamic10", {
"test1": {
methods: ["test1"],
objects: ["object1"],
extra_keys: ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10"],
},
});
// Test registering an event thats already registered through Events.yaml.
Assert.throws(() => Telemetry.registerEvents("telemetry.test", {
"test1": {
methods: ["test1"],
objects: ["object1"],
},
}),
/Attempt to register event that is already registered\./,
"Should throw when registering event that already was registered.");
});

Просмотреть файл

@ -1780,7 +1780,7 @@ var Events = {
let eventsSection = document.getElementById("events");
removeAllChildNodes(eventsSection);
if (!aPayload.processes || !Object.values(aPayload.processes).find(p => "events" in p)) {
if (!aPayload.processes || !aPayload.processes.parent) {
return;
}
@ -1793,9 +1793,8 @@ var Events = {
return;
}
const hasData = Object.values(aPayload.processes).find(p => {
return ("events" in p) && (Object.keys(p.events).length > 0);
});
let events = aPayload.processes[selectedProcess].events;
const hasData = events && Object.keys(events).length > 0;
setHasData("events-section", hasData);
if (!hasData) {
return;
@ -1810,7 +1809,6 @@ var Events = {
"extraHeader",
].map(h => bundle.GetStringFromName(h));
let events = aPayload.processes[selectedProcess].events;
const table = GenericTable.render(events, headings);
eventsSection.appendChild(table);
},
@ -2094,8 +2092,7 @@ var HistogramSection = {
}
if (hgramsProcess &&
"processes" in aPayload &&
hgramsProcess in aPayload.processes &&
"histograms" in aPayload.processes[hgramsProcess]) {
hgramsProcess in aPayload.processes) {
histograms = aPayload.processes[hgramsProcess].histograms;
}