зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 64b95c8f9f12 (bug 1302663)
--HG-- extra : rebase_source : 042b24c918ce8bc04974b838cf1c4612739ef49b
This commit is contained in:
Родитель
c855df4890
Коммит
fa25c7a85c
|
@ -44,7 +44,6 @@
|
|||
#include "TelemetryCommon.h"
|
||||
#include "TelemetryHistogram.h"
|
||||
#include "TelemetryScalar.h"
|
||||
#include "TelemetryEvent.h"
|
||||
#include "WebrtcTelemetry.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "nsHashKeys.h"
|
||||
|
@ -1863,7 +1862,6 @@ NS_IMETHODIMP
|
|||
TelemetryImpl::SetCanRecordBase(bool canRecord) {
|
||||
TelemetryHistogram::SetCanRecordBase(canRecord);
|
||||
TelemetryScalar::SetCanRecordBase(canRecord);
|
||||
TelemetryEvent::SetCanRecordBase(canRecord);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1884,7 +1882,6 @@ NS_IMETHODIMP
|
|||
TelemetryImpl::SetCanRecordExtended(bool canRecord) {
|
||||
TelemetryHistogram::SetCanRecordExtended(canRecord);
|
||||
TelemetryScalar::SetCanRecordExtended(canRecord);
|
||||
TelemetryEvent::SetCanRecordExtended(canRecord);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1918,9 +1915,6 @@ TelemetryImpl::CreateTelemetryInstance()
|
|||
// Only record scalars from the parent process.
|
||||
TelemetryScalar::InitializeGlobalState(XRE_IsParentProcess(), XRE_IsParentProcess());
|
||||
|
||||
// Only record events from the parent process.
|
||||
TelemetryEvent::InitializeGlobalState(XRE_IsParentProcess(), XRE_IsParentProcess());
|
||||
|
||||
// Now, create and initialize the Telemetry global state.
|
||||
sTelemetry = new TelemetryImpl();
|
||||
|
||||
|
@ -1946,7 +1940,6 @@ TelemetryImpl::ShutdownTelemetry()
|
|||
// so as to release any heap storage that would otherwise be kept alive by it.
|
||||
TelemetryHistogram::DeInitializeGlobalState();
|
||||
TelemetryScalar::DeInitializeGlobalState();
|
||||
TelemetryEvent::DeInitializeGlobalState();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -2311,7 +2304,13 @@ TelemetryImpl::GetFileIOReports(JSContext *cx, JS::MutableHandleValue ret)
|
|||
NS_IMETHODIMP
|
||||
TelemetryImpl::MsSinceProcessStart(double* aResult)
|
||||
{
|
||||
return Telemetry::Common::MsSinceProcessStart(aResult);
|
||||
bool error;
|
||||
*aResult = (TimeStamp::NowLoRes() -
|
||||
TimeStamp::ProcessCreation(error)).ToMilliseconds();
|
||||
if (error) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Telemetry Scalars IDL Implementation
|
||||
|
@ -2377,31 +2376,6 @@ TelemetryImpl::ClearScalars()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
// Telemetry Event IDL implementation.
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::RecordEvent(const nsACString & aCategory, const nsACString & aMethod,
|
||||
const nsACString & aObject, JS::HandleValue aValue,
|
||||
JS::HandleValue aExtra, JSContext* aCx, uint8_t optional_argc)
|
||||
{
|
||||
return TelemetryEvent::RecordEvent(aCategory, aMethod, aObject, aValue, aExtra, aCx, optional_argc);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
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::ClearEvents()
|
||||
{
|
||||
TelemetryEvent::ClearEvents();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::FlushBatchedChildTelemetry()
|
||||
{
|
||||
|
@ -2440,7 +2414,6 @@ TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
|
|||
|
||||
n += TelemetryHistogram::GetHistogramSizesofIncludingThis(aMallocSizeOf);
|
||||
n += TelemetryScalar::GetScalarSizesOfIncludingThis(aMallocSizeOf);
|
||||
n += TelemetryEvent::SizeOfIncludingThis(aMallocSizeOf);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
|
||||
#include "nsITelemetry.h"
|
||||
#include "nsVersionComparator.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsIConsoleService.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#include "TelemetryCommon.h"
|
||||
|
||||
|
@ -65,40 +62,6 @@ CanRecordDataset(uint32_t aDataset, bool aCanRecordBase, bool aCanRecordExtended
|
|||
return false;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MsSinceProcessStart(double* aResult)
|
||||
{
|
||||
bool error;
|
||||
*aResult = (TimeStamp::NowLoRes() -
|
||||
TimeStamp::ProcessCreation(error)).ToMilliseconds();
|
||||
if (error) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg)
|
||||
{
|
||||
if (!NS_IsMainThread()) {
|
||||
nsString msg(aMsg);
|
||||
nsCOMPtr<nsIRunnable> task =
|
||||
NS_NewRunnableFunction([aLogLevel, msg]() { LogToBrowserConsole(aLogLevel, msg); });
|
||||
NS_DispatchToMainThread(task.forget(), NS_DISPATCH_NORMAL);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
|
||||
if (!console) {
|
||||
NS_WARNING("Failed to log message to console.");
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
|
||||
error->Init(aMsg, EmptyString(), EmptyString(), 0, 0, aLogLevel, "chrome javascript");
|
||||
console->LogMessage(error);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Telemetry
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
#include "nsTHashtable.h"
|
||||
#include "jsapi.h"
|
||||
#include "nsIScriptError.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace Telemetry {
|
||||
|
@ -51,23 +50,6 @@ bool IsExpiredVersion(const char* aExpiration);
|
|||
bool IsInDataset(uint32_t aDataset, uint32_t aContainingDataset);
|
||||
bool CanRecordDataset(uint32_t aDataset, bool aCanRecordBase, bool aCanRecordExtended);
|
||||
|
||||
/**
|
||||
* Return the number of milliseconds since process start using monotonic
|
||||
* timestamps (unaffected by system clock changes).
|
||||
*
|
||||
* @return NS_OK on success, NS_ERROR_NOT_AVAILABLE if TimeStamp doesn't have the data.
|
||||
*/
|
||||
nsresult MsSinceProcessStart(double* aResult);
|
||||
|
||||
/**
|
||||
* Dumps a log message to the Browser Console using the provided level.
|
||||
*
|
||||
* @param aLogLevel The level to use when displaying the message in the browser console
|
||||
* (e.g. nsIScriptError::warningFlag, ...).
|
||||
* @param aMsg The text message to print to the console.
|
||||
*/
|
||||
void LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg);
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Telemetry
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -1,683 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <prtime.h>
|
||||
#include "nsITelemetry.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/StaticMutex.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "jsapi.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsUTF8Utils.h"
|
||||
|
||||
#include "TelemetryCommon.h"
|
||||
#include "TelemetryEvent.h"
|
||||
#include "TelemetryEventData.h"
|
||||
|
||||
using mozilla::StaticMutex;
|
||||
using mozilla::StaticMutexAutoLock;
|
||||
using mozilla::ArrayLength;
|
||||
using mozilla::Maybe;
|
||||
using mozilla::Nothing;
|
||||
using mozilla::Pair;
|
||||
using mozilla::StaticAutoPtr;
|
||||
using mozilla::Telemetry::Common::AutoHashtable;
|
||||
using mozilla::Telemetry::Common::IsExpiredVersion;
|
||||
using mozilla::Telemetry::Common::CanRecordDataset;
|
||||
using mozilla::Telemetry::Common::IsInDataset;
|
||||
using mozilla::Telemetry::Common::MsSinceProcessStart;
|
||||
using mozilla::Telemetry::Common::LogToBrowserConsole;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Naming: there are two kinds of functions in this file:
|
||||
//
|
||||
// * Functions taking a StaticMutexAutoLock: these can only be reached via
|
||||
// an interface function (TelemetryEvent::*). They expect the interface
|
||||
// function to have acquired |gTelemetryEventsMutex|, so they do not
|
||||
// have to be thread-safe.
|
||||
//
|
||||
// * Functions named TelemetryEvent::*. This is the external interface.
|
||||
// Entries and exits to these functions are serialised using
|
||||
// |gTelemetryEventsMutex|.
|
||||
//
|
||||
// Avoiding races and deadlocks:
|
||||
//
|
||||
// All functions in the external interface (TelemetryEvent::*) are
|
||||
// serialised using the mutex |gTelemetryEventsMutex|. This means
|
||||
// that the external interface is thread-safe, and the internal
|
||||
// functions can ignore thread safety. But it also brings a danger
|
||||
// of deadlock if any function in the external interface can get back
|
||||
// to that interface. That is, we will deadlock on any call chain like
|
||||
// this:
|
||||
//
|
||||
// TelemetryEvent::* -> .. any functions .. -> TelemetryEvent::*
|
||||
//
|
||||
// To reduce the danger of that happening, observe the following rules:
|
||||
//
|
||||
// * No function in TelemetryEvent::* may directly call, nor take the
|
||||
// address of, any other function in TelemetryEvent::*.
|
||||
//
|
||||
// * No internal function may call, nor take the address
|
||||
// of, any function in TelemetryEvent::*.
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PRIVATE TYPES
|
||||
|
||||
namespace {
|
||||
|
||||
const uint32_t kEventCount = mozilla::Telemetry::EventID::EventCount;
|
||||
// This is a special event id used to mark expired events, to make expiry checks
|
||||
// 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
|
||||
// are removed from storage.
|
||||
const uint32_t kMaxEventRecords = 10000;
|
||||
// Maximum length of any passed value string, in UTF8 byte sequence length.
|
||||
const uint32_t kMaxValueByteLength = 100;
|
||||
// Maximum length of any string value in the extra dictionary, in UTF8 byte sequence length.
|
||||
const uint32_t kMaxExtraValueByteLength = 100;
|
||||
|
||||
typedef nsDataHashtable<nsCStringHashKey, uint32_t> EventMapType;
|
||||
typedef nsClassHashtable<nsCStringHashKey, nsCString> StringMap;
|
||||
|
||||
enum class RecordEventResult {
|
||||
Ok,
|
||||
UnknownEvent,
|
||||
InvalidExtraKey,
|
||||
StorageLimitReached,
|
||||
};
|
||||
|
||||
struct ExtraEntry {
|
||||
const nsCString key;
|
||||
const nsCString value;
|
||||
};
|
||||
|
||||
typedef nsTArray<ExtraEntry> ExtraArray;
|
||||
|
||||
class EventRecord {
|
||||
public:
|
||||
EventRecord(double timestamp, uint32_t eventId, const Maybe<nsCString>& value,
|
||||
const ExtraArray& extra)
|
||||
: mTimestamp(timestamp)
|
||||
, mEventId(eventId)
|
||||
, mValue(value)
|
||||
, mExtra(extra)
|
||||
{}
|
||||
|
||||
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; }
|
||||
uint32_t EventId() const { return mEventId; }
|
||||
const Maybe<nsCString>& Value() const { return mValue; }
|
||||
const ExtraArray& Extra() const { return mExtra; }
|
||||
|
||||
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
private:
|
||||
const double mTimestamp;
|
||||
const uint32_t mEventId;
|
||||
const Maybe<nsCString> mValue;
|
||||
const ExtraArray mExtra;
|
||||
};
|
||||
|
||||
// Implements the methods for EventInfo.
|
||||
const char*
|
||||
EventInfo::method() const
|
||||
{
|
||||
return &gEventsStringTable[this->method_offset];
|
||||
}
|
||||
|
||||
const char*
|
||||
EventInfo::object() const
|
||||
{
|
||||
return &gEventsStringTable[this->object_offset];
|
||||
}
|
||||
|
||||
// Implements the methods for CommonEventInfo.
|
||||
const char*
|
||||
CommonEventInfo::category() const
|
||||
{
|
||||
return &gEventsStringTable[this->category_offset];
|
||||
}
|
||||
|
||||
const char*
|
||||
CommonEventInfo::expiration_version() const
|
||||
{
|
||||
return &gEventsStringTable[this->expiration_version_offset];
|
||||
}
|
||||
|
||||
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 &gEventsStringTable[key_index];
|
||||
}
|
||||
|
||||
// Implementation for the EventRecord class.
|
||||
size_t
|
||||
EventRecord::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
|
||||
{
|
||||
size_t n = 0;
|
||||
|
||||
if (mValue) {
|
||||
n += mValue.value().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
}
|
||||
|
||||
n += mExtra.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
||||
for (uint32_t i = 0; i < mExtra.Length(); ++i) {
|
||||
n += mExtra[i].key.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
n += mExtra[i].value.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
nsCString
|
||||
UniqueEventName(const nsACString& category, const nsACString& method, const nsACString& object)
|
||||
{
|
||||
nsCString name;
|
||||
name.Append(category);
|
||||
name.AppendLiteral("#");
|
||||
name.Append(method);
|
||||
name.AppendLiteral("#");
|
||||
name.Append(object);
|
||||
return name;
|
||||
}
|
||||
|
||||
nsCString
|
||||
UniqueEventName(const EventInfo& info)
|
||||
{
|
||||
return UniqueEventName(nsDependentCString(info.common_info.category()),
|
||||
nsDependentCString(info.method()),
|
||||
nsDependentCString(info.object()));
|
||||
}
|
||||
|
||||
bool
|
||||
IsExpiredDate(uint32_t expires_days_since_epoch) {
|
||||
if (expires_days_since_epoch == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t days_since_epoch = PR_Now() / (PRTime(PR_USEC_PER_SEC) * 24 * 60 * 60);
|
||||
return expires_days_since_epoch <= days_since_epoch;
|
||||
}
|
||||
|
||||
void
|
||||
TruncateToByteLength(nsCString& str, uint32_t length)
|
||||
{
|
||||
// last will be the index of the first byte of the current multi-byte sequence.
|
||||
uint32_t last = RewindToPriorUTF8Codepoint(str.get(), length);
|
||||
str.Truncate(last);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PRIVATE STATE, SHARED BY ALL THREADS
|
||||
|
||||
namespace {
|
||||
|
||||
// Set to true once this global state has been initialized.
|
||||
bool gInitDone = false;
|
||||
|
||||
bool gCanRecordBase;
|
||||
bool gCanRecordExtended;
|
||||
|
||||
// The Name -> ID cache map.
|
||||
EventMapType gEventNameIDMap(kEventCount);
|
||||
|
||||
// The main event storage. Events are inserted here in recording order.
|
||||
StaticAutoPtr<nsTArray<EventRecord>> gEventRecords;
|
||||
|
||||
} // namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PRIVATE: thread-unsafe helpers for event recording.
|
||||
|
||||
namespace {
|
||||
|
||||
bool
|
||||
CanRecordEvent(const StaticMutexAutoLock& lock, const CommonEventInfo& info)
|
||||
{
|
||||
if (!gCanRecordBase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CanRecordDataset(info.dataset, gCanRecordBase, gCanRecordExtended);
|
||||
}
|
||||
|
||||
RecordEventResult
|
||||
RecordEvent(const StaticMutexAutoLock& lock, double timestamp,
|
||||
const nsACString& category, const nsACString& method,
|
||||
const nsACString& object, const Maybe<nsCString>& value,
|
||||
const ExtraArray& extra)
|
||||
{
|
||||
// Apply hard limit on event count in storage.
|
||||
if (gEventRecords->Length() >= kMaxEventRecords) {
|
||||
return RecordEventResult::StorageLimitReached;
|
||||
}
|
||||
|
||||
// Look up the event id.
|
||||
const nsCString& name = UniqueEventName(category, method, object);
|
||||
uint32_t eventId;
|
||||
if (!gEventNameIDMap.Get(name, &eventId)) {
|
||||
return RecordEventResult::UnknownEvent;
|
||||
}
|
||||
|
||||
// If the event is expired, 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 (eventId == kExpiredEventId) {
|
||||
return RecordEventResult::Ok;
|
||||
}
|
||||
|
||||
// Check whether we can record this event.
|
||||
const CommonEventInfo& common = gEventInfo[eventId].common_info;
|
||||
if (!CanRecordEvent(lock, common)) {
|
||||
return RecordEventResult::Ok;
|
||||
}
|
||||
|
||||
// Check whether the extra keys passed are valid.
|
||||
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.
|
||||
gEventRecords->AppendElement(EventRecord(timestamp, eventId, value, extra));
|
||||
return RecordEventResult::Ok;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryEvents::
|
||||
|
||||
// This is a StaticMutex rather than a plain Mutex (1) so that
|
||||
// it gets initialised in a thread-safe manner the first time
|
||||
// it is used, and (2) because it is never de-initialised, and
|
||||
// a normal Mutex would show up as a leak in BloatView. StaticMutex
|
||||
// also has the "OffTheBooks" property, so it won't show as a leak
|
||||
// in BloatView.
|
||||
// Another reason to use a StaticMutex instead of a plain Mutex is
|
||||
// that, due to the nature of Telemetry, we cannot rely on having a
|
||||
// mutex initialized in InitializeGlobalState. Unfortunately, we
|
||||
// cannot make sure that no other function is called before this point.
|
||||
static StaticMutex gTelemetryEventsMutex;
|
||||
|
||||
void
|
||||
TelemetryEvent::InitializeGlobalState(bool aCanRecordBase, bool aCanRecordExtended)
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryEventsMutex);
|
||||
MOZ_ASSERT(!gInitDone, "TelemetryEvent::InitializeGlobalState "
|
||||
"may only be called once");
|
||||
|
||||
gCanRecordBase = aCanRecordBase;
|
||||
gCanRecordExtended = aCanRecordExtended;
|
||||
|
||||
gEventRecords = new nsTArray<EventRecord>();
|
||||
|
||||
// Populate the static event name->id cache. Note that the event names are
|
||||
// statically allocated and come from the automatically generated TelemetryEventData.h.
|
||||
const uint32_t eventCount = static_cast<uint32_t>(mozilla::Telemetry::EventID::EventCount);
|
||||
for (uint32_t i = 0; i < eventCount; ++i) {
|
||||
const EventInfo& info = gEventInfo[i];
|
||||
uint32_t eventId = i;
|
||||
|
||||
// If this event is expired, mark it with a special event id.
|
||||
// This avoids doing expensive expiry checks at runtime.
|
||||
if (IsExpiredVersion(info.common_info.expiration_version()) ||
|
||||
IsExpiredDate(info.common_info.expiration_day)) {
|
||||
eventId = kExpiredEventId;
|
||||
}
|
||||
|
||||
gEventNameIDMap.Put(UniqueEventName(info), eventId);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
gEventNameIDMap.MarkImmutable();
|
||||
#endif
|
||||
gInitDone = true;
|
||||
}
|
||||
|
||||
void
|
||||
TelemetryEvent::DeInitializeGlobalState()
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryEventsMutex);
|
||||
MOZ_ASSERT(gInitDone);
|
||||
|
||||
gCanRecordBase = false;
|
||||
gCanRecordExtended = false;
|
||||
|
||||
gEventNameIDMap.Clear();
|
||||
gEventRecords->Clear();
|
||||
gEventRecords = nullptr;
|
||||
|
||||
gInitDone = false;
|
||||
}
|
||||
|
||||
void
|
||||
TelemetryEvent::SetCanRecordBase(bool b)
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryEventsMutex);
|
||||
gCanRecordBase = b;
|
||||
}
|
||||
|
||||
void
|
||||
TelemetryEvent::SetCanRecordExtended(bool b) {
|
||||
StaticMutexAutoLock locker(gTelemetryEventsMutex);
|
||||
gCanRecordExtended = b;
|
||||
}
|
||||
|
||||
nsresult
|
||||
TelemetryEvent::RecordEvent(const nsACString& aCategory, const nsACString& aMethod,
|
||||
const nsACString& aObject, JS::HandleValue aValue,
|
||||
JS::HandleValue aExtra, JSContext* cx,
|
||||
uint8_t optional_argc)
|
||||
{
|
||||
// Currently only recording in the parent process is supported.
|
||||
if (!XRE_IsParentProcess()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Get the current time.
|
||||
double timestamp = -1;
|
||||
nsresult rv = MsSinceProcessStart(×tamp);
|
||||
if (NS_FAILED(rv)) {
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_STRING("Failed to get time since process start."));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Check value argument.
|
||||
if ((optional_argc > 0) && !aValue.isNull() && !aValue.isString()) {
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_STRING("Invalid type for value parameter."));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Extract value parameter.
|
||||
Maybe<nsCString> value;
|
||||
if (aValue.isString()) {
|
||||
nsAutoJSString jsStr;
|
||||
if (!jsStr.init(cx, aValue)) {
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_STRING("Invalid string value for value parameter."));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCString str = NS_ConvertUTF16toUTF8(jsStr);
|
||||
if (str.Length() > kMaxValueByteLength) {
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_STRING("Value parameter exceeds maximum string length, truncating."));
|
||||
TruncateToByteLength(str, kMaxValueByteLength);
|
||||
}
|
||||
value = mozilla::Some(str);
|
||||
}
|
||||
|
||||
// Check extra argument.
|
||||
if ((optional_argc > 1) && !aExtra.isNull() && !aExtra.isObject()) {
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_STRING("Invalid type for extra parameter."));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Extract extra dictionary.
|
||||
ExtraArray extra;
|
||||
if (aExtra.isObject()) {
|
||||
JS::RootedObject obj(cx, &aExtra.toObject());
|
||||
JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx));
|
||||
if (!JS_Enumerate(cx, obj, &ids)) {
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_STRING("Failed to enumerate object."));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
for (size_t i = 0, n = ids.length(); i < n; i++) {
|
||||
nsAutoJSString key;
|
||||
if (!key.init(cx, ids[i])) {
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_STRING("Extra dictionary should only contain string keys."));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> value(cx);
|
||||
if (!JS_GetPropertyById(cx, obj, ids[i], &value)) {
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_STRING("Failed to get extra property."));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoJSString jsStr;
|
||||
if (!value.isString() || !jsStr.init(cx, value)) {
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_STRING("Extra properties should have string values."));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCString str = NS_ConvertUTF16toUTF8(jsStr);
|
||||
if (str.Length() > kMaxExtraValueByteLength) {
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_STRING("Extra value exceeds maximum string length, truncating."));
|
||||
TruncateToByteLength(str, kMaxExtraValueByteLength);
|
||||
}
|
||||
|
||||
extra.AppendElement(ExtraEntry{NS_ConvertUTF16toUTF8(key), str});
|
||||
}
|
||||
}
|
||||
|
||||
// Lock for accessing internal data.
|
||||
// While the lock is being held, no complex calls like JS calls can be made,
|
||||
// as all of these could record Telemetry, which would result in deadlock.
|
||||
RecordEventResult res;
|
||||
{
|
||||
StaticMutexAutoLock lock(gTelemetryEventsMutex);
|
||||
|
||||
if (!gInitDone) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
res = ::RecordEvent(lock, timestamp, aCategory, aMethod, aObject, value, extra);
|
||||
}
|
||||
|
||||
// Trigger warnings or errors where needed.
|
||||
switch (res) {
|
||||
case RecordEventResult::UnknownEvent:
|
||||
JS_ReportErrorASCII(cx, "Unknown event.");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
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."));
|
||||
return NS_OK;
|
||||
default:
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
TelemetryEvent::CreateSnapshots(uint32_t aDataset, bool aClear, JSContext* cx,
|
||||
uint8_t optional_argc, JS::MutableHandleValue aResult)
|
||||
{
|
||||
// Extract the events from storage.
|
||||
nsTArray<EventRecord> events;
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryEventsMutex);
|
||||
|
||||
if (!gInitDone) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
uint32_t len = gEventRecords->Length();
|
||||
for (uint32_t i = 0; i < len; ++i) {
|
||||
const EventRecord& record = (*gEventRecords)[i];
|
||||
const EventInfo& info = gEventInfo[record.EventId()];
|
||||
|
||||
if (IsInDataset(info.common_info.dataset, aDataset)) {
|
||||
events.AppendElement(record);
|
||||
}
|
||||
}
|
||||
|
||||
if (aClear) {
|
||||
gEventRecords->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// We serialize the events to a JS array.
|
||||
JS::RootedObject eventsArray(cx, JS_NewArrayObject(cx, events.Length()));
|
||||
if (!eventsArray) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < events.Length(); ++i) {
|
||||
const EventRecord& record = events[i];
|
||||
const EventInfo& info = gEventInfo[record.EventId()];
|
||||
|
||||
// Each entry is an array of the form:
|
||||
// [timestamp, category, method, object, value, extra]
|
||||
JS::RootedObject itemsArray(cx, JS_NewArrayObject(cx, 6));
|
||||
if (!itemsArray) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Add timestamp.
|
||||
JS::Rooted<JS::Value> val(cx);
|
||||
uint32_t itemIndex = 0;
|
||||
val.setDouble(floor(record.Timestamp()));
|
||||
if (!JS_DefineElement(cx, itemsArray, itemIndex++, val, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Add category, method, object.
|
||||
const char* strings[] = {
|
||||
info.common_info.category(),
|
||||
info.method(),
|
||||
info.object(),
|
||||
};
|
||||
for (uint32_t s = 0; s < ArrayLength(strings); ++s) {
|
||||
const NS_ConvertUTF8toUTF16 wide(strings[s]);
|
||||
val.setString(JS_NewUCStringCopyN(cx, wide.Data(), wide.Length()));
|
||||
if (!JS_DefineElement(cx, itemsArray, itemIndex++, val, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the optional string value.
|
||||
if (!record.Value()) {
|
||||
val.setNull();
|
||||
} else {
|
||||
const NS_ConvertUTF8toUTF16 wide(record.Value().value());
|
||||
val.setString(JS_NewUCStringCopyN(cx, wide.Data(), wide.Length()));
|
||||
}
|
||||
if (!JS_DefineElement(cx, itemsArray, itemIndex++, val, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Add the optional extra dictionary.
|
||||
if (record.Extra().IsEmpty()) {
|
||||
val.setNull();
|
||||
} else {
|
||||
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
|
||||
if (!obj) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
const ExtraArray& extra = record.Extra();
|
||||
for (uint32_t i = 0; i < extra.Length(); ++i) {
|
||||
const NS_ConvertUTF8toUTF16 wide(extra[i].value);
|
||||
JS::Rooted<JS::Value> value(cx);
|
||||
value.setString(JS_NewUCStringCopyN(cx, wide.Data(), wide.Length()));
|
||||
|
||||
if (!JS_DefineProperty(cx, obj, extra[i].key.get(), value, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
val.setObject(*obj);
|
||||
}
|
||||
if (!JS_DefineElement(cx, itemsArray, itemIndex++, val, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Add the record to the events array.
|
||||
if (!JS_DefineElement(cx, eventsArray, i, itemsArray, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
aResult.setObject(*eventsArray);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all the stored events. This is intended to be only used in tests.
|
||||
*/
|
||||
void
|
||||
TelemetryEvent::ClearEvents()
|
||||
{
|
||||
StaticMutexAutoLock lock(gTelemetryEventsMutex);
|
||||
|
||||
if (!gInitDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
gEventRecords->Clear();
|
||||
}
|
||||
|
||||
size_t
|
||||
TelemetryEvent::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
|
||||
{
|
||||
StaticMutexAutoLock locker(gTelemetryEventsMutex);
|
||||
size_t n = 0;
|
||||
|
||||
n += gEventRecords->ShallowSizeOfIncludingThis(aMallocSizeOf);
|
||||
for (uint32_t i = 0; i < gEventRecords->Length(); ++i) {
|
||||
n += (*gEventRecords)[i].SizeOfExcludingThis(aMallocSizeOf);
|
||||
}
|
||||
|
||||
n += gEventNameIDMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
||||
for (auto iter = gEventNameIDMap.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef TelemetryEvent_h__
|
||||
#define TelemetryEvent_h__
|
||||
|
||||
#include "mozilla/TelemetryEventEnums.h"
|
||||
|
||||
// This module is internal to Telemetry. It encapsulates Telemetry's
|
||||
// event recording and storage logic. It should only be used by
|
||||
// Telemetry.cpp. These functions should not be used anywhere else.
|
||||
// For the public interface to Telemetry functionality, see Telemetry.h.
|
||||
|
||||
namespace TelemetryEvent {
|
||||
|
||||
void InitializeGlobalState(bool canRecordBase, bool canRecordExtended);
|
||||
void DeInitializeGlobalState();
|
||||
|
||||
void SetCanRecordBase(bool b);
|
||||
void SetCanRecordExtended(bool b);
|
||||
|
||||
// JS API Endpoints.
|
||||
nsresult RecordEvent(const nsACString& aCategory, const nsACString& aMethod,
|
||||
const nsACString& aObject, JS::HandleValue aValue,
|
||||
JS::HandleValue aExtra, JSContext* aCx,
|
||||
uint8_t optional_argc);
|
||||
nsresult CreateSnapshots(uint32_t aDataset, bool aClear, JSContext* aCx,
|
||||
uint8_t optional_argc, JS::MutableHandleValue aResult);
|
||||
|
||||
// Only to be used for testing.
|
||||
void ClearEvents();
|
||||
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
|
||||
|
||||
} // namespace TelemetryEvent
|
||||
|
||||
#endif // TelemetryEvent_h__
|
|
@ -12,6 +12,8 @@
|
|||
#include "nsClassHashtable.h"
|
||||
#include "nsIXPConnect.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIConsoleService.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/StaticMutex.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
@ -26,7 +28,6 @@ using mozilla::Telemetry::Common::AutoHashtable;
|
|||
using mozilla::Telemetry::Common::IsExpiredVersion;
|
||||
using mozilla::Telemetry::Common::CanRecordDataset;
|
||||
using mozilla::Telemetry::Common::IsInDataset;
|
||||
using mozilla::Telemetry::Common::LogToBrowserConsole;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -751,6 +752,35 @@ KeyedScalarStorageMapType gKeyedScalarStorageMap;
|
|||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Dumps a log message to the Browser Console using the provided level.
|
||||
*
|
||||
* @param aLogLevel The level to use when displaying the message in the browser console
|
||||
* (e.g. nsIScriptError::warningFlag, ...).
|
||||
* @param aMsg The text message to print to the console.
|
||||
*/
|
||||
void
|
||||
internal_LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg)
|
||||
{
|
||||
if (!NS_IsMainThread()) {
|
||||
nsString msg(aMsg);
|
||||
nsCOMPtr<nsIRunnable> task =
|
||||
NS_NewRunnableFunction([aLogLevel, msg]() { internal_LogToBrowserConsole(aLogLevel, msg); });
|
||||
NS_DispatchToMainThread(task.forget(), NS_DISPATCH_NORMAL);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
|
||||
if (!console) {
|
||||
NS_WARNING("Failed to log message to console.");
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
|
||||
error->Init(aMsg, EmptyString(), EmptyString(), 0, 0, aLogLevel, "chrome javascript");
|
||||
console->LogMessage(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the error should be logged.
|
||||
*
|
||||
|
@ -811,7 +841,7 @@ internal_LogScalarError(const nsACString& aScalarName, ScalarResult aSr)
|
|||
return;
|
||||
}
|
||||
|
||||
LogToBrowserConsole(nsIScriptError::warningFlag, errorMessage);
|
||||
internal_LogToBrowserConsole(nsIScriptError::warningFlag, errorMessage);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -32,7 +32,6 @@ XPIDL_SOURCES += [
|
|||
XPIDL_MODULE = 'telemetry'
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'!TelemetryEventEnums.h',
|
||||
'!TelemetryHistogramEnums.h',
|
||||
'!TelemetryScalarEnums.h',
|
||||
'ProcessedStack.h',
|
||||
|
@ -44,7 +43,6 @@ EXPORTS.mozilla += [
|
|||
SOURCES += [
|
||||
'Telemetry.cpp',
|
||||
'TelemetryCommon.cpp',
|
||||
'TelemetryEvent.cpp',
|
||||
'TelemetryHistogram.cpp',
|
||||
'TelemetryScalar.cpp',
|
||||
'WebrtcTelemetry.cpp',
|
||||
|
|
|
@ -331,7 +331,7 @@ interface nsITelemetry : nsISupports
|
|||
readonly attribute jsval fileIOReports;
|
||||
|
||||
/**
|
||||
* Return the number of milliseconds since process start using monotonic
|
||||
* Return the number of seconds since process start using monotonic
|
||||
* timestamps (unaffected by system clock changes).
|
||||
* @throws NS_ERROR_NOT_AVAILABLE if TimeStamp doesn't have the data.
|
||||
*/
|
||||
|
@ -430,40 +430,4 @@ interface nsITelemetry : nsISupports
|
|||
* process. This is intended only to be used on process shutdown.
|
||||
*/
|
||||
void flushBatchedChildTelemetry();
|
||||
|
||||
/**
|
||||
* Record an event in Telemetry.
|
||||
*
|
||||
* @param aCategory The category name.
|
||||
* @param aMethod The method name.
|
||||
* @param aMethod The object name.
|
||||
* @param aValue An optional string value to record.
|
||||
* @param aExtra An optional object of the form (string -> string).
|
||||
* It should only contain registered extra keys.
|
||||
*
|
||||
* @throws NS_ERROR_INVALID_ARG When trying to record an unknown event.
|
||||
*/
|
||||
[implicit_jscontext, optional_argc]
|
||||
void recordEvent(in ACString aCategory, in ACString aMethod, in ACString aObject, [optional] in jsval aValue, [optional] in jsval extra);
|
||||
|
||||
/**
|
||||
* Serializes the recorded events to a JSON-appropriate array and optionally resets them.
|
||||
* The returned structure looks like this:
|
||||
* [
|
||||
* // [timestamp, category, method, object, stringValue, extraValues]
|
||||
* [43245, "category1", "method1", "object1", "string value", null],
|
||||
* [43258, "category1", "method2", "object1", null, {"key1": "string value"}],
|
||||
* ...
|
||||
* ]
|
||||
*
|
||||
* @param aDataset DATASET_RELEASE_CHANNEL_OPTOUT or DATASET_RELEASE_CHANNEL_OPTIN.
|
||||
* @param [aClear=false] Whether to clear out the scalars after snapshotting.
|
||||
*/
|
||||
[implicit_jscontext, optional_argc]
|
||||
jsval snapshotBuiltinEvents(in uint32_t aDataset, [optional] in boolean aClear);
|
||||
|
||||
/**
|
||||
* Resets all the stored events. This is intended to be only used in tests.
|
||||
*/
|
||||
void clearEvents();
|
||||
};
|
||||
|
|
|
@ -1,232 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const OPTIN = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
|
||||
const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
|
||||
|
||||
function checkEventFormat(events) {
|
||||
Assert.ok(Array.isArray(events), "Events should be serialized to an array.");
|
||||
for (let e of events) {
|
||||
Assert.ok(Array.isArray(e), "Event should be an array.");
|
||||
Assert.equal(e.length, 6, "Event should have 6 elements.");
|
||||
|
||||
Assert.equal(typeof(e[0]), "number", "Element 0 should be a number.");
|
||||
Assert.equal(typeof(e[1]), "string", "Element 1 should be a string.");
|
||||
Assert.equal(typeof(e[2]), "string", "Element 2 should be a string.");
|
||||
Assert.equal(typeof(e[3]), "string", "Element 3 should be a string.");
|
||||
|
||||
Assert.ok(e[4] === null || typeof(e[4]) == "string",
|
||||
"Event element 4 should be null or a string.");
|
||||
Assert.ok(e[5] === null || typeof(e[5]) == "object",
|
||||
"Event element 4 should be null or an object.");
|
||||
|
||||
let extra = e[5];
|
||||
if (extra) {
|
||||
Assert.ok(Object.keys(extra).every(k => typeof(k) == "string"),
|
||||
"All extra keys should be strings.");
|
||||
Assert.ok(Object.values(extra).every(v => typeof(v) == "string"),
|
||||
"All extra values should be strings.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_task(function* test_recording() {
|
||||
Telemetry.clearEvents();
|
||||
|
||||
// Record some events.
|
||||
let expected = [
|
||||
{optout: false, event: ["telemetry.test", "test1", "object1"]},
|
||||
{optout: false, event: ["telemetry.test", "test2", "object2"]},
|
||||
|
||||
{optout: false, event: ["telemetry.test", "test1", "object1", "value"]},
|
||||
{optout: false, event: ["telemetry.test", "test1", "object1", "value", null]},
|
||||
{optout: false, event: ["telemetry.test", "test1", "object1", null, {"key1": "value1"}]},
|
||||
{optout: false, event: ["telemetry.test", "test1", "object1", "value", {"key1": "value1", "key2": "value2"}]},
|
||||
|
||||
{optout: true, event: ["telemetry.test", "test_optout", "object1"]},
|
||||
{optout: false, event: ["telemetry.test.second", "test", "object1"]},
|
||||
{optout: false, event: ["telemetry.test.second", "test", "object1", null, {"key1": "value1"}]},
|
||||
];
|
||||
|
||||
for (let entry of expected) {
|
||||
entry.tsBefore = Math.floor(Telemetry.msSinceProcessStart());
|
||||
try {
|
||||
Telemetry.recordEvent(...entry.event);
|
||||
} catch (ex) {
|
||||
Assert.ok(false, `Failed to record event ${JSON.stringify(entry.event)}: ${ex}`);
|
||||
}
|
||||
entry.tsAfter = Math.floor(Telemetry.msSinceProcessStart());
|
||||
}
|
||||
|
||||
// The following should not result in any recorded events.
|
||||
Assert.throws(() => Telemetry.recordEvent("unknown.category", "test1", "object1"),
|
||||
/Error: Unknown event\./,
|
||||
"Should throw on unknown category.");
|
||||
Assert.throws(() => Telemetry.recordEvent("telemetry.test", "unknown", "object1"),
|
||||
/Error: Unknown event\./,
|
||||
"Should throw on unknown method.");
|
||||
Assert.throws(() => Telemetry.recordEvent("telemetry.test", "test1", "unknown"),
|
||||
/Error: Unknown event\./,
|
||||
"Should throw on unknown object.");
|
||||
|
||||
let checkEvents = (events, expectedEvents) => {
|
||||
checkEventFormat(events);
|
||||
Assert.equal(events.length, expectedEvents.length,
|
||||
"Snapshot should have the right number of events.");
|
||||
|
||||
for (let i = 0; i < events.length; ++i) {
|
||||
let {tsBefore, tsAfter} = expectedEvents[i];
|
||||
let ts = events[i][0];
|
||||
Assert.greaterOrEqual(ts, tsBefore, "The recorded timestamp should be greater than the one before recording.");
|
||||
Assert.lessOrEqual(ts, tsAfter, "The recorded timestamp should be less than the one after recording.");
|
||||
|
||||
let recordedData = events[i].slice(1);
|
||||
let expectedData = expectedEvents[i].event.slice();
|
||||
for (let j=expectedData.length; j<5; ++j) {
|
||||
expectedData.push(null);
|
||||
}
|
||||
Assert.deepEqual(recordedData, expectedData, "The recorded event data should match.");
|
||||
}
|
||||
};
|
||||
|
||||
// Check that the expected events were recorded.
|
||||
let events = Telemetry.snapshotBuiltinEvents(OPTIN, false);
|
||||
checkEvents(events, expected);
|
||||
|
||||
// Check serializing only opt-out events.
|
||||
events = Telemetry.snapshotBuiltinEvents(OPTOUT, false);
|
||||
filtered = expected.filter(e => e.optout == true);
|
||||
checkEvents(events, filtered);
|
||||
});
|
||||
|
||||
add_task(function* test_clear() {
|
||||
Telemetry.clearEvents();
|
||||
|
||||
const COUNT = 10;
|
||||
for (let i = 0; i < COUNT; ++i) {
|
||||
Telemetry.recordEvent("telemetry.test", "test1", "object1");
|
||||
Telemetry.recordEvent("telemetry.test.second", "test", "object1");
|
||||
}
|
||||
|
||||
// Check that events were recorded.
|
||||
// The events are cleared by passing the respective flag.
|
||||
let events = Telemetry.snapshotBuiltinEvents(OPTIN, true);
|
||||
Assert.equal(events.length, 2 * COUNT, `Should have recorded ${2 * COUNT} events.`);
|
||||
|
||||
// Now the events should be cleared.
|
||||
events = Telemetry.snapshotBuiltinEvents(OPTIN, false);
|
||||
Assert.equal(events.length, 0, `Should have cleared the events.`);
|
||||
});
|
||||
|
||||
add_task(function* test_expiry() {
|
||||
Telemetry.clearEvents();
|
||||
|
||||
// Recording call with event that is expired by version.
|
||||
Telemetry.recordEvent("telemetry.test", "test_expired_version", "object1");
|
||||
let events = Telemetry.snapshotBuiltinEvents(OPTIN, true);
|
||||
Assert.equal(events.length, 0, "Should not record event with expired version.");
|
||||
|
||||
// Recording call with event that is expired by date.
|
||||
Telemetry.recordEvent("telemetry.test", "test_expired_date", "object1");
|
||||
events = Telemetry.snapshotBuiltinEvents(OPTIN, true);
|
||||
Assert.equal(events.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", "test_not_expired_optout", "object1");
|
||||
events = Telemetry.snapshotBuiltinEvents(OPTOUT, true);
|
||||
Assert.equal(events.length, 1, "Should record event when date and version are not expired.");
|
||||
});
|
||||
|
||||
add_task(function* test_invalidParams() {
|
||||
Telemetry.clearEvents();
|
||||
|
||||
// Recording call with wrong type for value argument.
|
||||
Telemetry.recordEvent("telemetry.test", "test1", "object1", 1);
|
||||
let events = Telemetry.snapshotBuiltinEvents(OPTIN, true);
|
||||
Assert.equal(events.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");
|
||||
events = Telemetry.snapshotBuiltinEvents(OPTIN, true);
|
||||
Assert.equal(events.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"});
|
||||
events = Telemetry.snapshotBuiltinEvents(OPTIN, true);
|
||||
Assert.equal(events.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});
|
||||
events = Telemetry.snapshotBuiltinEvents(OPTIN, true);
|
||||
Assert.equal(events.length, 0, "Should not record event when extra argument with invalid value type is passed.");
|
||||
});
|
||||
|
||||
add_task(function* test_storageLimit() {
|
||||
Telemetry.clearEvents();
|
||||
|
||||
// Record more events than the storage limit allows.
|
||||
let LIMIT = 10000;
|
||||
let COUNT = LIMIT + 10;
|
||||
for (let i = 0; i < COUNT; ++i) {
|
||||
Telemetry.recordEvent("telemetry.test", "test1", "object1", String(i));
|
||||
}
|
||||
|
||||
// Check that the right events were recorded.
|
||||
let events = Telemetry.snapshotBuiltinEvents(OPTIN, true);
|
||||
Assert.equal(events.length, LIMIT, `Should have only recorded ${LIMIT} events`);
|
||||
Assert.ok(events.every((e, idx) => e[4] === String(idx)),
|
||||
"Should have recorded all events from before hitting the limit.");
|
||||
});
|
||||
|
||||
add_task(function* test_valueLimits() {
|
||||
Telemetry.clearEvents();
|
||||
|
||||
// Record values that are at or over the limits for string lengths.
|
||||
let LIMIT = 100;
|
||||
let expected = [
|
||||
["telemetry.test", "test1", "object1", "a".repeat(LIMIT - 10), null],
|
||||
["telemetry.test", "test1", "object1", "a".repeat(LIMIT ), null],
|
||||
["telemetry.test", "test1", "object1", "a".repeat(LIMIT + 1), null],
|
||||
["telemetry.test", "test1", "object1", "a".repeat(LIMIT + 10), null],
|
||||
|
||||
["telemetry.test", "test1", "object1", null, {key1: "a".repeat(LIMIT - 10)}],
|
||||
["telemetry.test", "test1", "object1", null, {key1: "a".repeat(LIMIT )}],
|
||||
["telemetry.test", "test1", "object1", null, {key1: "a".repeat(LIMIT + 1)}],
|
||||
["telemetry.test", "test1", "object1", null, {key1: "a".repeat(LIMIT + 10)}],
|
||||
];
|
||||
|
||||
for (let event of expected) {
|
||||
Telemetry.recordEvent(...event);
|
||||
if (event[3]) {
|
||||
event[3] = event[3].substr(0, 100);
|
||||
}
|
||||
if (event[4]) {
|
||||
event[4].key1 = event[4].key1.substr(0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the right events were recorded.
|
||||
let events = Telemetry.snapshotBuiltinEvents(OPTIN, true);
|
||||
Assert.equal(events.length, expected.length,
|
||||
"Should have recorded the expected number 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.");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_unicodeValues() {
|
||||
Telemetry.clearEvents();
|
||||
|
||||
// Record string values containing unicode characters.
|
||||
let value = "漢語";
|
||||
Telemetry.recordEvent("telemetry.test", "test1", "object1", value);
|
||||
Telemetry.recordEvent("telemetry.test", "test1", "object1", null, {"key1": value});
|
||||
|
||||
// Check that the values were correctly recorded.
|
||||
let events = Telemetry.snapshotBuiltinEvents(OPTIN, true);
|
||||
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.");
|
||||
});
|
|
@ -60,4 +60,3 @@ tags = addons
|
|||
[test_TelemetryScalars.js]
|
||||
[test_TelemetryTimestamps.js]
|
||||
skip-if = toolkit == 'android'
|
||||
[test_TelemetryEvents.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче