Backed out changeset 64b95c8f9f12 (bug 1302663)

--HG--
extra : rebase_source : 042b24c918ce8bc04974b838cf1c4612739ef49b
This commit is contained in:
Carsten "Tomcat" Book 2016-11-18 14:27:50 +01:00
Родитель c855df4890
Коммит fa25c7a85c
10 изменённых файлов: 40 добавлений и 1085 удалений

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

@ -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(&timestamp);
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]