With bug 1374419 solved, static asserts in TelemetryHistogram.cpp can be improved. Specifically, JS::gcreason::NUM_TELEMETRY_REASONS can now be compared against GC_MINOR_REASON, GC_MINOR_REASON_LONG, and GC_REASON_2 in the now constexpr gHistograms. Likewise, mozilla::StartupTimeline::MAX_EVENT_ID can be compared against STARTUP_MEASUREMENT_ERRORS. This commit modifies the static asserts to perform these comparisons in place of ones against hardcoded values and modifies the accompanying comment to remove the TODO section.
2371 lines
70 KiB
C++
2371 lines
70 KiB
C++
/* -*- 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 "jsapi.h"
|
|
#include "jsfriendapi.h"
|
|
#include "js/GCAPI.h"
|
|
#include "nsString.h"
|
|
#include "nsTHashtable.h"
|
|
#include "nsHashKeys.h"
|
|
#include "nsBaseHashtable.h"
|
|
#include "nsClassHashtable.h"
|
|
#include "nsITelemetry.h"
|
|
|
|
#include "mozilla/dom/ToJSValue.h"
|
|
#include "mozilla/gfx/GPUProcessManager.h"
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/StartupTimeline.h"
|
|
#include "mozilla/StaticMutex.h"
|
|
#include "mozilla/Unused.h"
|
|
|
|
#include "TelemetryCommon.h"
|
|
#include "TelemetryHistogram.h"
|
|
#include "ipc/TelemetryIPCAccumulator.h"
|
|
|
|
#include "base/histogram.h"
|
|
|
|
using base::Histogram;
|
|
using base::StatisticsRecorder;
|
|
using base::BooleanHistogram;
|
|
using base::CountHistogram;
|
|
using base::FlagHistogram;
|
|
using base::LinearHistogram;
|
|
using mozilla::StaticMutex;
|
|
using mozilla::StaticMutexAutoLock;
|
|
using mozilla::Telemetry::Accumulation;
|
|
using mozilla::Telemetry::KeyedAccumulation;
|
|
using mozilla::Telemetry::ProcessID;
|
|
using mozilla::Telemetry::Common::LogToBrowserConsole;
|
|
using mozilla::Telemetry::Common::RecordedProcessType;
|
|
|
|
namespace TelemetryIPCAccumulator = mozilla::TelemetryIPCAccumulator;
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Naming: there are two kinds of functions in this file:
|
|
//
|
|
// * Functions named internal_*: these can only be reached via an
|
|
// interface function (TelemetryHistogram::*). They mostly expect
|
|
// the interface function to have acquired
|
|
// |gTelemetryHistogramMutex|, so they do not have to be
|
|
// thread-safe. However, those internal_* functions that are
|
|
// reachable from internal_WrapAndReturnHistogram and
|
|
// internal_WrapAndReturnKeyedHistogram can sometimes be called
|
|
// without |gTelemetryHistogramMutex|, and so might be racey.
|
|
//
|
|
// * Functions named TelemetryHistogram::*. This is the external interface.
|
|
// Entries and exits to these functions are serialised using
|
|
// |gTelemetryHistogramMutex|, except for GetKeyedHistogramSnapshots and
|
|
// CreateHistogramSnapshots.
|
|
//
|
|
// Avoiding races and deadlocks:
|
|
//
|
|
// All functions in the external interface (TelemetryHistogram::*) are
|
|
// serialised using the mutex |gTelemetryHistogramMutex|. This means
|
|
// that the external interface is thread-safe, and many of 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
|
|
//
|
|
// TelemetryHistogram::* -> .. any functions .. -> TelemetryHistogram::*
|
|
//
|
|
// To reduce the danger of that happening, observe the following rules:
|
|
//
|
|
// * No function in TelemetryHistogram::* may directly call, nor take the
|
|
// address of, any other function in TelemetryHistogram::*.
|
|
//
|
|
// * No internal function internal_* may call, nor take the address
|
|
// of, any function in TelemetryHistogram::*.
|
|
//
|
|
// internal_WrapAndReturnHistogram and
|
|
// internal_WrapAndReturnKeyedHistogram are not protected by
|
|
// |gTelemetryHistogramMutex| because they make calls to the JS
|
|
// engine, but that can in turn call back to Telemetry and hence back
|
|
// to a TelemetryHistogram:: function, in order to report GC and other
|
|
// statistics. This would lead to deadlock due to attempted double
|
|
// acquisition of |gTelemetryHistogramMutex|, if the internal_* functions
|
|
// were required to be protected by |gTelemetryHistogramMutex|. To
|
|
// break that cycle, we relax that requirement. Unfortunately this
|
|
// means that this file is not guaranteed race-free.
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE TYPES
|
|
|
|
#define EXPIRED_ID "__expired__"
|
|
#define SUBSESSION_HISTOGRAM_PREFIX "sub#"
|
|
#define KEYED_HISTOGRAM_NAME_SEPARATOR "#"
|
|
#define CONTENT_HISTOGRAM_SUFFIX "#content"
|
|
#define GPU_HISTOGRAM_SUFFIX "#gpu"
|
|
#define EXTENSION_HISTOGRAM_SUFFIX "#extension"
|
|
|
|
namespace {
|
|
|
|
using mozilla::Telemetry::Common::AutoHashtable;
|
|
using mozilla::Telemetry::Common::IsExpiredVersion;
|
|
using mozilla::Telemetry::Common::CanRecordDataset;
|
|
using mozilla::Telemetry::Common::IsInDataset;
|
|
|
|
class KeyedHistogram;
|
|
|
|
typedef nsBaseHashtableET<nsDepCharHashKey, mozilla::Telemetry::HistogramID>
|
|
CharPtrEntryType;
|
|
|
|
typedef AutoHashtable<CharPtrEntryType> HistogramMapType;
|
|
|
|
typedef nsClassHashtable<nsCStringHashKey, KeyedHistogram>
|
|
KeyedHistogramMapType;
|
|
|
|
// Hardcoded probes
|
|
struct HistogramInfo {
|
|
uint32_t min;
|
|
uint32_t max;
|
|
uint32_t bucketCount;
|
|
uint32_t histogramType;
|
|
uint32_t id_offset;
|
|
uint32_t expiration_offset;
|
|
uint32_t dataset;
|
|
uint32_t label_index;
|
|
uint32_t label_count;
|
|
RecordedProcessType record_in_processes;
|
|
bool keyed;
|
|
|
|
const char *id() const;
|
|
const char *expiration() const;
|
|
nsresult label_id(const char* label, uint32_t* labelId) const;
|
|
};
|
|
|
|
enum reflectStatus {
|
|
REFLECT_OK,
|
|
REFLECT_CORRUPT,
|
|
REFLECT_FAILURE
|
|
};
|
|
|
|
typedef StatisticsRecorder::Histograms::iterator HistogramIterator;
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE STATE, SHARED BY ALL THREADS
|
|
|
|
namespace {
|
|
|
|
// Set to true once this global state has been initialized
|
|
bool gInitDone = false;
|
|
|
|
bool gCanRecordBase = false;
|
|
bool gCanRecordExtended = false;
|
|
|
|
HistogramMapType gHistogramMap(mozilla::Telemetry::HistogramCount);
|
|
|
|
KeyedHistogramMapType gKeyedHistograms;
|
|
|
|
bool gCorruptHistograms[mozilla::Telemetry::HistogramCount];
|
|
|
|
// This is for gHistograms, gHistogramStringTable
|
|
#include "TelemetryHistogramData.inc"
|
|
|
|
// The singleton StatisticsRecorder object for this process.
|
|
base::StatisticsRecorder* gStatisticsRecorder = nullptr;
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE CONSTANTS
|
|
|
|
namespace {
|
|
|
|
// List of histogram IDs which should have recording disabled initially.
|
|
const mozilla::Telemetry::HistogramID kRecordingInitiallyDisabledIDs[] = {
|
|
mozilla::Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
|
|
|
|
// The array must not be empty. Leave these item here.
|
|
mozilla::Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD,
|
|
mozilla::Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: Misc small helpers
|
|
|
|
namespace {
|
|
|
|
bool
|
|
internal_CanRecordBase() {
|
|
return gCanRecordBase;
|
|
}
|
|
|
|
bool
|
|
internal_CanRecordExtended() {
|
|
return gCanRecordExtended;
|
|
}
|
|
|
|
bool
|
|
internal_IsHistogramEnumId(mozilla::Telemetry::HistogramID aID)
|
|
{
|
|
static_assert(((mozilla::Telemetry::HistogramID)-1 > 0), "ID should be unsigned.");
|
|
return aID < mozilla::Telemetry::HistogramCount;
|
|
}
|
|
|
|
// Note: this is completely unrelated to mozilla::IsEmpty.
|
|
bool
|
|
internal_IsEmpty(const Histogram *h)
|
|
{
|
|
Histogram::SampleSet ss;
|
|
h->SnapshotSample(&ss);
|
|
return ss.counts(0) == 0 && ss.sum() == 0;
|
|
}
|
|
|
|
bool
|
|
internal_IsExpired(const Histogram *histogram)
|
|
{
|
|
return histogram->histogram_name() == EXPIRED_ID;
|
|
}
|
|
|
|
nsresult
|
|
internal_GetRegisteredHistogramIds(bool keyed, uint32_t dataset,
|
|
uint32_t *aCount, char*** aHistograms)
|
|
{
|
|
nsTArray<char*> collection;
|
|
|
|
for (const auto & h : gHistograms) {
|
|
if (IsExpiredVersion(h.expiration()) ||
|
|
h.keyed != keyed ||
|
|
!IsInDataset(h.dataset, dataset)) {
|
|
continue;
|
|
}
|
|
|
|
const char* id = h.id();
|
|
const size_t len = strlen(id);
|
|
collection.AppendElement(static_cast<char*>(nsMemory::Clone(id, len+1)));
|
|
}
|
|
|
|
const size_t bytes = collection.Length() * sizeof(char*);
|
|
char** histograms = static_cast<char**>(moz_xmalloc(bytes));
|
|
memcpy(histograms, collection.Elements(), bytes);
|
|
*aHistograms = histograms;
|
|
*aCount = collection.Length();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
const char *
|
|
HistogramInfo::id() const
|
|
{
|
|
return &gHistogramStringTable[this->id_offset];
|
|
}
|
|
|
|
const char *
|
|
HistogramInfo::expiration() const
|
|
{
|
|
return &gHistogramStringTable[this->expiration_offset];
|
|
}
|
|
|
|
nsresult
|
|
HistogramInfo::label_id(const char* label, uint32_t* labelId) const
|
|
{
|
|
MOZ_ASSERT(label);
|
|
MOZ_ASSERT(this->histogramType == nsITelemetry::HISTOGRAM_CATEGORICAL);
|
|
if (this->histogramType != nsITelemetry::HISTOGRAM_CATEGORICAL) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < this->label_count; ++i) {
|
|
// gHistogramLabelTable contains the indices of the label strings in the
|
|
// gHistogramStringTable.
|
|
// They are stored in-order and consecutively, from the offset label_index
|
|
// to (label_index + label_count).
|
|
uint32_t string_offset = gHistogramLabelTable[this->label_index + i];
|
|
const char* const str = &gHistogramStringTable[string_offset];
|
|
if (::strcmp(label, str) == 0) {
|
|
*labelId = i;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: Histogram Get, Add, Clone, Clear functions
|
|
|
|
namespace {
|
|
|
|
nsresult
|
|
internal_CheckHistogramArguments(uint32_t histogramType,
|
|
uint32_t min, uint32_t max,
|
|
uint32_t bucketCount, bool haveOptArgs)
|
|
{
|
|
if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN
|
|
&& histogramType != nsITelemetry::HISTOGRAM_FLAG
|
|
&& histogramType != nsITelemetry::HISTOGRAM_COUNT) {
|
|
// The min, max & bucketCount arguments are not optional for this type.
|
|
if (!haveOptArgs)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
// Sanity checks for histogram parameters.
|
|
if (min >= max)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
if (bucketCount <= 2)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
if (min < 1)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/*
|
|
* min, max & bucketCount are optional for boolean, flag & count histograms.
|
|
* haveOptArgs has to be set if the caller provides them.
|
|
*/
|
|
nsresult
|
|
internal_HistogramGet(const char *name, const char *expiration,
|
|
uint32_t histogramType, uint32_t min, uint32_t max,
|
|
uint32_t bucketCount, bool haveOptArgs,
|
|
Histogram **result)
|
|
{
|
|
nsresult rv = internal_CheckHistogramArguments(histogramType, min, max,
|
|
bucketCount, haveOptArgs);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
if (IsExpiredVersion(expiration)) {
|
|
name = EXPIRED_ID;
|
|
min = 1;
|
|
max = 2;
|
|
bucketCount = 3;
|
|
histogramType = nsITelemetry::HISTOGRAM_LINEAR;
|
|
}
|
|
|
|
switch (histogramType) {
|
|
case nsITelemetry::HISTOGRAM_EXPONENTIAL:
|
|
*result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
|
|
break;
|
|
case nsITelemetry::HISTOGRAM_LINEAR:
|
|
case nsITelemetry::HISTOGRAM_CATEGORICAL:
|
|
*result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
|
|
break;
|
|
case nsITelemetry::HISTOGRAM_BOOLEAN:
|
|
*result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
|
|
break;
|
|
case nsITelemetry::HISTOGRAM_FLAG:
|
|
*result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
|
|
break;
|
|
case nsITelemetry::HISTOGRAM_COUNT:
|
|
*result = CountHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
|
|
break;
|
|
default:
|
|
NS_ASSERTION(false, "Invalid histogram type");
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// Read the process type from the given histogram name. The process type, if
|
|
// one exists, is embedded in a suffix.
|
|
mozilla::Telemetry::ProcessID
|
|
GetProcessFromName(const nsACString& aString)
|
|
{
|
|
if (StringEndsWith(aString, NS_LITERAL_CSTRING(CONTENT_HISTOGRAM_SUFFIX))) {
|
|
return ProcessID::Content;
|
|
}
|
|
if (StringEndsWith(aString, NS_LITERAL_CSTRING(GPU_HISTOGRAM_SUFFIX))) {
|
|
return ProcessID::Gpu;
|
|
}
|
|
if (StringEndsWith(aString, NS_LITERAL_CSTRING(EXTENSION_HISTOGRAM_SUFFIX))) {
|
|
return ProcessID::Extension;
|
|
}
|
|
return ProcessID::Parent;
|
|
}
|
|
|
|
const char*
|
|
SuffixForProcessType(mozilla::Telemetry::ProcessID aProcessType)
|
|
{
|
|
switch (aProcessType) {
|
|
case ProcessID::Parent:
|
|
return nullptr;
|
|
case ProcessID::Content:
|
|
return CONTENT_HISTOGRAM_SUFFIX;
|
|
case ProcessID::Gpu:
|
|
return GPU_HISTOGRAM_SUFFIX;
|
|
case ProcessID::Extension:
|
|
return EXTENSION_HISTOGRAM_SUFFIX;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("unknown process type");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
CharPtrEntryType*
|
|
internal_GetHistogramMapEntry(const char* aName)
|
|
{
|
|
nsDependentCString name(aName);
|
|
ProcessID process = GetProcessFromName(name);
|
|
const char* suffix = SuffixForProcessType(process);
|
|
if (!suffix) {
|
|
return gHistogramMap.GetEntry(aName);
|
|
}
|
|
|
|
auto root = Substring(name, 0, name.Length() - strlen(suffix));
|
|
return gHistogramMap.GetEntry(PromiseFlatCString(root).get());
|
|
}
|
|
|
|
nsresult
|
|
internal_GetHistogramEnumId(const char *name, mozilla::Telemetry::HistogramID *id)
|
|
{
|
|
if (!gInitDone) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
CharPtrEntryType *entry = internal_GetHistogramMapEntry(name);
|
|
if (!entry) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
*id = entry->mData;
|
|
return NS_OK;
|
|
}
|
|
|
|
// O(1) histogram lookup by numeric id
|
|
nsresult
|
|
internal_GetHistogramByEnumId(mozilla::Telemetry::HistogramID id, Histogram **ret,
|
|
ProcessID aProcessType)
|
|
{
|
|
static Histogram* knownHistograms[mozilla::Telemetry::HistogramCount] = {0};
|
|
static Histogram* knownContentHistograms[mozilla::Telemetry::HistogramCount] = {0};
|
|
static Histogram* knownGPUHistograms[mozilla::Telemetry::HistogramCount] = {0};
|
|
static Histogram* knownExtensionHistograms[mozilla::Telemetry::HistogramCount] = {0};
|
|
|
|
Histogram** knownList = nullptr;
|
|
|
|
switch (aProcessType) {
|
|
case ProcessID::Parent:
|
|
knownList = knownHistograms;
|
|
break;
|
|
case ProcessID::Content:
|
|
knownList = knownContentHistograms;
|
|
break;
|
|
case ProcessID::Gpu:
|
|
knownList = knownGPUHistograms;
|
|
break;
|
|
case ProcessID::Extension:
|
|
knownList = knownExtensionHistograms;
|
|
break;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("unknown process type");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
Histogram* h = knownList[id];
|
|
if (h) {
|
|
*ret = h;
|
|
return NS_OK;
|
|
}
|
|
|
|
const HistogramInfo &p = gHistograms[id];
|
|
if (p.keyed) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsAutoCString histogramName;
|
|
histogramName.Append(p.id());
|
|
if (const char* suffix = SuffixForProcessType(aProcessType)) {
|
|
histogramName.AppendASCII(suffix);
|
|
}
|
|
|
|
nsresult rv = internal_HistogramGet(histogramName.get(), p.expiration(),
|
|
p.histogramType, p.min, p.max,
|
|
p.bucketCount, true, &h);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
#ifdef DEBUG
|
|
// Check that the C++ Histogram code computes the same ranges as the
|
|
// Python histogram code.
|
|
if (!IsExpiredVersion(p.expiration())) {
|
|
const struct bounds &b = gBucketLowerBoundIndex[id];
|
|
if (b.length != 0) {
|
|
MOZ_ASSERT(size_t(b.length) == h->bucket_count(),
|
|
"C++/Python bucket # mismatch");
|
|
for (int i = 0; i < b.length; ++i) {
|
|
MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i),
|
|
"C++/Python bucket mismatch");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
knownList[id] = h;
|
|
*ret = h;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
internal_GetHistogramByName(const nsACString &name, Histogram **ret)
|
|
{
|
|
mozilla::Telemetry::HistogramID id;
|
|
nsresult rv
|
|
= internal_GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
ProcessID process = GetProcessFromName(name);
|
|
rv = internal_GetHistogramByEnumId(id, ret, process);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
|
|
/**
|
|
* This clones a histogram |existing| with the id |existingId| to a
|
|
* new histogram with the name |newName|.
|
|
* For simplicity this is limited to registered histograms.
|
|
*/
|
|
Histogram*
|
|
internal_CloneHistogram(const nsACString& newName,
|
|
mozilla::Telemetry::HistogramID existingId,
|
|
Histogram& existing)
|
|
{
|
|
const HistogramInfo &info = gHistograms[existingId];
|
|
Histogram *clone = nullptr;
|
|
nsresult rv;
|
|
|
|
rv = internal_HistogramGet(PromiseFlatCString(newName).get(),
|
|
info.expiration(),
|
|
info.histogramType, existing.declared_min(),
|
|
existing.declared_max(), existing.bucket_count(),
|
|
true, &clone);
|
|
if (NS_FAILED(rv)) {
|
|
return nullptr;
|
|
}
|
|
|
|
Histogram::SampleSet ss;
|
|
existing.SnapshotSample(&ss);
|
|
clone->AddSampleSet(ss);
|
|
|
|
return clone;
|
|
}
|
|
|
|
ProcessID
|
|
GetProcessFromName(const std::string& aString)
|
|
{
|
|
nsDependentCString string(aString.c_str(), aString.length());
|
|
return GetProcessFromName(string);
|
|
}
|
|
|
|
Histogram*
|
|
internal_GetSubsessionHistogram(Histogram& existing)
|
|
{
|
|
mozilla::Telemetry::HistogramID id;
|
|
nsresult rv
|
|
= internal_GetHistogramEnumId(existing.histogram_name().c_str(), &id);
|
|
if (NS_FAILED(rv) || gHistograms[id].keyed) {
|
|
return nullptr;
|
|
}
|
|
|
|
static Histogram* subsession[mozilla::Telemetry::HistogramCount] = {};
|
|
static Histogram* subsessionContent[mozilla::Telemetry::HistogramCount] = {};
|
|
static Histogram* subsessionGPU[mozilla::Telemetry::HistogramCount] = {};
|
|
static Histogram* subsessionExtension[mozilla::Telemetry::HistogramCount] = {};
|
|
|
|
Histogram** cache = nullptr;
|
|
|
|
ProcessID process = GetProcessFromName(existing.histogram_name());
|
|
switch (process) {
|
|
case ProcessID::Parent:
|
|
cache = subsession;
|
|
break;
|
|
case ProcessID::Content:
|
|
cache = subsessionContent;
|
|
break;
|
|
case ProcessID::Gpu:
|
|
cache = subsessionGPU;
|
|
break;
|
|
case ProcessID::Extension:
|
|
cache = subsessionExtension;
|
|
break;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("unknown process type");
|
|
return nullptr;
|
|
}
|
|
|
|
if (Histogram* cached = cache[id]) {
|
|
return cached;
|
|
}
|
|
|
|
NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX);
|
|
nsDependentCString existingName(gHistograms[id].id());
|
|
if (StringBeginsWith(existingName, prefix)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCString subsessionName(prefix);
|
|
subsessionName.Append(existing.histogram_name().c_str());
|
|
|
|
Histogram* clone = internal_CloneHistogram(subsessionName, id, existing);
|
|
cache[id] = clone;
|
|
return clone;
|
|
}
|
|
#endif
|
|
|
|
nsresult
|
|
internal_HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset)
|
|
{
|
|
// Check if we are allowed to record the data.
|
|
bool canRecordDataset = CanRecordDataset(dataset,
|
|
internal_CanRecordBase(),
|
|
internal_CanRecordExtended());
|
|
if (!canRecordDataset || !histogram.IsRecordingEnabled()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
if (Histogram* subsession = internal_GetSubsessionHistogram(histogram)) {
|
|
subsession->Add(value);
|
|
}
|
|
#endif
|
|
|
|
// It is safe to add to the histogram now: the subsession histogram was already
|
|
// cloned from this so we won't add the sample twice.
|
|
histogram.Add(value);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
internal_HistogramAdd(Histogram& histogram, int32_t value)
|
|
{
|
|
uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;
|
|
// We only really care about the dataset of the histogram if we are not recording
|
|
// extended telemetry. Otherwise, we always record histogram data.
|
|
if (!internal_CanRecordExtended()) {
|
|
mozilla::Telemetry::HistogramID id;
|
|
nsresult rv
|
|
= internal_GetHistogramEnumId(histogram.histogram_name().c_str(), &id);
|
|
if (NS_FAILED(rv)) {
|
|
// If we can't look up the dataset, it might be because the histogram was added
|
|
// at runtime. Since we're not recording extended telemetry, bail out.
|
|
return NS_OK;
|
|
}
|
|
dataset = gHistograms[id].dataset;
|
|
}
|
|
|
|
return internal_HistogramAdd(histogram, value, dataset);
|
|
}
|
|
|
|
void
|
|
internal_HistogramClear(Histogram& aHistogram, bool onlySubsession)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
if (!XRE_IsParentProcess()) {
|
|
return;
|
|
}
|
|
if (!onlySubsession) {
|
|
aHistogram.Clear();
|
|
}
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
if (Histogram* subsession = internal_GetSubsessionHistogram(aHistogram)) {
|
|
subsession->Clear();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: Histogram corruption helpers
|
|
|
|
namespace {
|
|
|
|
void internal_Accumulate(mozilla::Telemetry::HistogramID aHistogram, uint32_t aSample);
|
|
|
|
void
|
|
internal_IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs)
|
|
{
|
|
for (auto h : hs) {
|
|
mozilla::Telemetry::HistogramID id;
|
|
nsresult rv = internal_GetHistogramEnumId(h->histogram_name().c_str(), &id);
|
|
// This histogram isn't a static histogram, just ignore it.
|
|
if (NS_FAILED(rv)) {
|
|
continue;
|
|
}
|
|
|
|
if (gCorruptHistograms[id]) {
|
|
continue;
|
|
}
|
|
|
|
Histogram::SampleSet ss;
|
|
h->SnapshotSample(&ss);
|
|
|
|
Histogram::Inconsistencies check = h->FindCorruption(ss);
|
|
bool corrupt = (check != Histogram::NO_INCONSISTENCIES);
|
|
|
|
if (corrupt) {
|
|
mozilla::Telemetry::HistogramID corruptID = mozilla::Telemetry::HistogramCount;
|
|
if (check & Histogram::RANGE_CHECKSUM_ERROR) {
|
|
corruptID = mozilla::Telemetry::RANGE_CHECKSUM_ERRORS;
|
|
} else if (check & Histogram::BUCKET_ORDER_ERROR) {
|
|
corruptID = mozilla::Telemetry::BUCKET_ORDER_ERRORS;
|
|
} else if (check & Histogram::COUNT_HIGH_ERROR) {
|
|
corruptID = mozilla::Telemetry::TOTAL_COUNT_HIGH_ERRORS;
|
|
} else if (check & Histogram::COUNT_LOW_ERROR) {
|
|
corruptID = mozilla::Telemetry::TOTAL_COUNT_LOW_ERRORS;
|
|
}
|
|
internal_Accumulate(corruptID, 1);
|
|
}
|
|
|
|
gCorruptHistograms[id] = corrupt;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: Histogram reflection helpers
|
|
|
|
namespace {
|
|
|
|
bool
|
|
internal_FillRanges(JSContext *cx, JS::Handle<JSObject*> array, Histogram *h)
|
|
{
|
|
JS::Rooted<JS::Value> range(cx);
|
|
for (size_t i = 0; i < h->bucket_count(); i++) {
|
|
range.setInt32(h->ranges(i));
|
|
if (!JS_DefineElement(cx, array, i, range, JSPROP_ENUMERATE))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
enum reflectStatus
|
|
internal_ReflectHistogramAndSamples(JSContext *cx,
|
|
JS::Handle<JSObject*> obj, Histogram *h,
|
|
const Histogram::SampleSet &ss)
|
|
{
|
|
// We don't want to reflect corrupt histograms.
|
|
if (h->FindCorruption(ss) != Histogram::NO_INCONSISTENCIES) {
|
|
return REFLECT_CORRUPT;
|
|
}
|
|
|
|
if (!(JS_DefineProperty(cx, obj, "min",
|
|
h->declared_min(), JSPROP_ENUMERATE)
|
|
&& JS_DefineProperty(cx, obj, "max",
|
|
h->declared_max(), JSPROP_ENUMERATE)
|
|
&& JS_DefineProperty(cx, obj, "histogram_type",
|
|
h->histogram_type(), JSPROP_ENUMERATE)
|
|
&& JS_DefineProperty(cx, obj, "sum",
|
|
double(ss.sum()), JSPROP_ENUMERATE))) {
|
|
return REFLECT_FAILURE;
|
|
}
|
|
|
|
const size_t count = h->bucket_count();
|
|
JS::Rooted<JSObject*> rarray(cx, JS_NewArrayObject(cx, count));
|
|
if (!rarray) {
|
|
return REFLECT_FAILURE;
|
|
}
|
|
if (!(internal_FillRanges(cx, rarray, h)
|
|
&& JS_DefineProperty(cx, obj, "ranges", rarray, JSPROP_ENUMERATE))) {
|
|
return REFLECT_FAILURE;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> counts_array(cx, JS_NewArrayObject(cx, count));
|
|
if (!counts_array) {
|
|
return REFLECT_FAILURE;
|
|
}
|
|
if (!JS_DefineProperty(cx, obj, "counts", counts_array, JSPROP_ENUMERATE)) {
|
|
return REFLECT_FAILURE;
|
|
}
|
|
for (size_t i = 0; i < count; i++) {
|
|
if (!JS_DefineElement(cx, counts_array, i,
|
|
ss.counts(i), JSPROP_ENUMERATE)) {
|
|
return REFLECT_FAILURE;
|
|
}
|
|
}
|
|
|
|
return REFLECT_OK;
|
|
}
|
|
|
|
enum reflectStatus
|
|
internal_ReflectHistogramSnapshot(JSContext *cx,
|
|
JS::Handle<JSObject*> obj, Histogram *h)
|
|
{
|
|
Histogram::SampleSet ss;
|
|
h->SnapshotSample(&ss);
|
|
return internal_ReflectHistogramAndSamples(cx, obj, h, ss);
|
|
}
|
|
|
|
bool
|
|
internal_ShouldReflectHistogram(Histogram *h)
|
|
{
|
|
const char *name = h->histogram_name().c_str();
|
|
mozilla::Telemetry::HistogramID id;
|
|
nsresult rv = internal_GetHistogramEnumId(name, &id);
|
|
if (NS_FAILED(rv)) {
|
|
// GetHistogramEnumId generally should not fail. But a lookup
|
|
// failure shouldn't prevent us from reflecting histograms into JS.
|
|
//
|
|
// However, these two histograms are created by Histogram itself for
|
|
// tracking corruption. We have our own histograms for that, so
|
|
// ignore these two.
|
|
if (strcmp(name, "Histogram.InconsistentCountHigh") == 0
|
|
|| strcmp(name, "Histogram.InconsistentCountLow") == 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return !gCorruptHistograms[id];
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: class KeyedHistogram
|
|
|
|
namespace {
|
|
|
|
class KeyedHistogram {
|
|
public:
|
|
KeyedHistogram(const nsACString &name, const nsACString &expiration,
|
|
uint32_t histogramType, uint32_t min, uint32_t max,
|
|
uint32_t bucketCount, uint32_t dataset);
|
|
nsresult GetHistogram(const nsCString& name, Histogram** histogram, bool subsession);
|
|
Histogram* GetHistogram(const nsCString& name, bool subsession);
|
|
uint32_t GetHistogramType() const { return mHistogramType; }
|
|
nsresult GetJSKeys(JSContext* cx, JS::CallArgs& args);
|
|
nsresult GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj,
|
|
bool subsession, bool clearSubsession);
|
|
|
|
void SetRecordingEnabled(bool aEnabled) { mRecordingEnabled = aEnabled; };
|
|
bool IsRecordingEnabled() const { return mRecordingEnabled; };
|
|
|
|
nsresult Add(const nsCString& key, uint32_t aSample);
|
|
void Clear(bool subsession);
|
|
|
|
nsresult GetEnumId(mozilla::Telemetry::HistogramID& id);
|
|
|
|
private:
|
|
typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
|
|
typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
|
|
KeyedHistogramMapType mHistogramMap;
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
KeyedHistogramMapType mSubsessionMap;
|
|
#endif
|
|
|
|
static bool ReflectKeyedHistogram(KeyedHistogramEntry* entry,
|
|
JSContext* cx,
|
|
JS::Handle<JSObject*> obj);
|
|
|
|
const nsCString mName;
|
|
const nsCString mExpiration;
|
|
const uint32_t mHistogramType;
|
|
const uint32_t mMin;
|
|
const uint32_t mMax;
|
|
const uint32_t mBucketCount;
|
|
const uint32_t mDataset;
|
|
mozilla::Atomic<bool, mozilla::Relaxed> mRecordingEnabled;
|
|
};
|
|
|
|
KeyedHistogram::KeyedHistogram(const nsACString &name,
|
|
const nsACString &expiration,
|
|
uint32_t histogramType,
|
|
uint32_t min, uint32_t max,
|
|
uint32_t bucketCount, uint32_t dataset)
|
|
: mHistogramMap()
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
, mSubsessionMap()
|
|
#endif
|
|
, mName(name)
|
|
, mExpiration(expiration)
|
|
, mHistogramType(histogramType)
|
|
, mMin(min)
|
|
, mMax(max)
|
|
, mBucketCount(bucketCount)
|
|
, mDataset(dataset)
|
|
, mRecordingEnabled(true)
|
|
{
|
|
}
|
|
|
|
nsresult
|
|
KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram,
|
|
bool subsession)
|
|
{
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap;
|
|
#else
|
|
KeyedHistogramMapType& map = mHistogramMap;
|
|
#endif
|
|
KeyedHistogramEntry* entry = map.GetEntry(key);
|
|
if (entry) {
|
|
*histogram = entry->mData;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCString histogramName;
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
if (subsession) {
|
|
histogramName.AppendLiteral(SUBSESSION_HISTOGRAM_PREFIX);
|
|
}
|
|
#endif
|
|
histogramName.Append(mName);
|
|
histogramName.AppendLiteral(KEYED_HISTOGRAM_NAME_SEPARATOR);
|
|
histogramName.Append(key);
|
|
|
|
Histogram* h;
|
|
nsresult rv = internal_HistogramGet(histogramName.get(), mExpiration.get(),
|
|
mHistogramType, mMin, mMax, mBucketCount,
|
|
true, &h);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
|
|
*histogram = h;
|
|
|
|
entry = map.PutEntry(key);
|
|
if (MOZ_UNLIKELY(!entry)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
entry->mData = h;
|
|
return NS_OK;
|
|
}
|
|
|
|
Histogram*
|
|
KeyedHistogram::GetHistogram(const nsCString& key, bool subsession)
|
|
{
|
|
Histogram* h = nullptr;
|
|
if (NS_FAILED(GetHistogram(key, &h, subsession))) {
|
|
return nullptr;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
nsresult
|
|
KeyedHistogram::Add(const nsCString& key, uint32_t sample)
|
|
{
|
|
bool canRecordDataset = CanRecordDataset(mDataset,
|
|
internal_CanRecordBase(),
|
|
internal_CanRecordExtended());
|
|
if (!canRecordDataset || !IsRecordingEnabled()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
Histogram* histogram = GetHistogram(key, false);
|
|
MOZ_ASSERT(histogram);
|
|
if (!histogram) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
Histogram* subsession = GetHistogram(key, true);
|
|
MOZ_ASSERT(subsession);
|
|
if (!subsession) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
#endif
|
|
|
|
histogram->Add(sample);
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
subsession->Add(sample);
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
KeyedHistogram::Clear(bool onlySubsession)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
if (!XRE_IsParentProcess()) {
|
|
return;
|
|
}
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
for (auto iter = mSubsessionMap.Iter(); !iter.Done(); iter.Next()) {
|
|
iter.Get()->mData->Clear();
|
|
}
|
|
mSubsessionMap.Clear();
|
|
if (onlySubsession) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) {
|
|
iter.Get()->mData->Clear();
|
|
}
|
|
mHistogramMap.Clear();
|
|
}
|
|
|
|
nsresult
|
|
KeyedHistogram::GetJSKeys(JSContext* cx, JS::CallArgs& args)
|
|
{
|
|
JS::AutoValueVector keys(cx);
|
|
if (!keys.reserve(mHistogramMap.Count())) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) {
|
|
JS::RootedValue jsKey(cx);
|
|
const NS_ConvertUTF8toUTF16 key(iter.Get()->GetKey());
|
|
jsKey.setString(JS_NewUCStringCopyN(cx, key.Data(), key.Length()));
|
|
if (!keys.append(jsKey)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
JS::RootedObject jsKeys(cx, JS_NewArrayObject(cx, keys));
|
|
if (!jsKeys) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
args.rval().setObject(*jsKeys);
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
KeyedHistogram::ReflectKeyedHistogram(KeyedHistogramEntry* entry,
|
|
JSContext* cx, JS::Handle<JSObject*> obj)
|
|
{
|
|
JS::RootedObject histogramSnapshot(cx, JS_NewPlainObject(cx));
|
|
if (!histogramSnapshot) {
|
|
return false;
|
|
}
|
|
|
|
if (internal_ReflectHistogramSnapshot(cx, histogramSnapshot,
|
|
entry->mData) != REFLECT_OK) {
|
|
return false;
|
|
}
|
|
|
|
const NS_ConvertUTF8toUTF16 key(entry->GetKey());
|
|
if (!JS_DefineUCProperty(cx, obj, key.Data(), key.Length(),
|
|
histogramSnapshot, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
nsresult
|
|
KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj,
|
|
bool subsession, bool clearSubsession)
|
|
{
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap;
|
|
#else
|
|
KeyedHistogramMapType& map = mHistogramMap;
|
|
#endif
|
|
if (!map.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
if (subsession && clearSubsession) {
|
|
Clear(true);
|
|
}
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
KeyedHistogram::GetEnumId(mozilla::Telemetry::HistogramID& id)
|
|
{
|
|
return internal_GetHistogramEnumId(mName.get(), &id);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: KeyedHistogram helpers
|
|
|
|
namespace {
|
|
|
|
KeyedHistogram*
|
|
internal_GetKeyedHistogramById(const nsACString &name)
|
|
{
|
|
if (!gInitDone) {
|
|
return nullptr;
|
|
}
|
|
|
|
KeyedHistogram* keyed = nullptr;
|
|
gKeyedHistograms.Get(name, &keyed);
|
|
return keyed;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: thread-unsafe helpers for the external interface
|
|
|
|
// 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.
|
|
static StaticMutex gTelemetryHistogramMutex;
|
|
|
|
namespace {
|
|
|
|
void
|
|
internal_SetHistogramRecordingEnabled(mozilla::Telemetry::HistogramID aID, bool aEnabled)
|
|
{
|
|
if (gHistograms[aID].keyed) {
|
|
const nsDependentCString id(gHistograms[aID].id());
|
|
KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
|
|
if (keyed) {
|
|
keyed->SetRecordingEnabled(aEnabled);
|
|
return;
|
|
}
|
|
} else {
|
|
Histogram *h;
|
|
nsresult rv = internal_GetHistogramByEnumId(aID, &h, ProcessID::Parent);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
h->SetRecordingEnabled(aEnabled);
|
|
return;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found");
|
|
}
|
|
|
|
bool
|
|
internal_RemoteAccumulate(mozilla::Telemetry::HistogramID aId, uint32_t aSample)
|
|
{
|
|
if (XRE_IsParentProcess()) {
|
|
return false;
|
|
}
|
|
Histogram *h;
|
|
nsresult rv = internal_GetHistogramByEnumId(aId, &h, ProcessID::Parent);
|
|
if (NS_SUCCEEDED(rv) && !h->IsRecordingEnabled()) {
|
|
return true;
|
|
}
|
|
TelemetryIPCAccumulator::AccumulateChildHistogram(aId, aSample);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
internal_RemoteAccumulate(mozilla::Telemetry::HistogramID aId,
|
|
const nsCString& aKey, uint32_t aSample)
|
|
{
|
|
if (XRE_IsParentProcess()) {
|
|
return false;
|
|
}
|
|
const HistogramInfo& th = gHistograms[aId];
|
|
KeyedHistogram* keyed
|
|
= internal_GetKeyedHistogramById(nsDependentCString(th.id()));
|
|
MOZ_ASSERT(keyed);
|
|
if (!keyed->IsRecordingEnabled()) {
|
|
return false;
|
|
}
|
|
TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(aId, aKey, aSample);
|
|
return true;
|
|
}
|
|
|
|
void internal_Accumulate(mozilla::Telemetry::HistogramID aHistogram, uint32_t aSample)
|
|
{
|
|
if (!internal_CanRecordBase() ||
|
|
internal_RemoteAccumulate(aHistogram, aSample)) {
|
|
return;
|
|
}
|
|
Histogram *h;
|
|
nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h, ProcessID::Parent);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
|
|
}
|
|
}
|
|
|
|
void
|
|
internal_Accumulate(mozilla::Telemetry::HistogramID aID,
|
|
const nsCString& aKey, uint32_t aSample)
|
|
{
|
|
if (!gInitDone || !internal_CanRecordBase() ||
|
|
internal_RemoteAccumulate(aID, aKey, aSample)) {
|
|
return;
|
|
}
|
|
const HistogramInfo& th = gHistograms[aID];
|
|
KeyedHistogram* keyed
|
|
= internal_GetKeyedHistogramById(nsDependentCString(th.id()));
|
|
MOZ_ASSERT(keyed);
|
|
keyed->Add(aKey, aSample);
|
|
}
|
|
|
|
void
|
|
internal_Accumulate(Histogram& aHistogram, uint32_t aSample)
|
|
{
|
|
if (XRE_IsParentProcess()) {
|
|
internal_HistogramAdd(aHistogram, aSample);
|
|
return;
|
|
}
|
|
|
|
mozilla::Telemetry::HistogramID id;
|
|
nsresult rv = internal_GetHistogramEnumId(aHistogram.histogram_name().c_str(), &id);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
internal_RemoteAccumulate(id, aSample);
|
|
}
|
|
}
|
|
|
|
void
|
|
internal_Accumulate(KeyedHistogram& aKeyed,
|
|
const nsCString& aKey, uint32_t aSample)
|
|
{
|
|
if (XRE_IsParentProcess()) {
|
|
aKeyed.Add(aKey, aSample);
|
|
return;
|
|
}
|
|
|
|
mozilla::Telemetry::HistogramID id;
|
|
if (NS_SUCCEEDED(aKeyed.GetEnumId(id))) {
|
|
internal_RemoteAccumulate(id, aKey, aSample);
|
|
}
|
|
}
|
|
|
|
void
|
|
internal_AccumulateChild(ProcessID aProcessType, mozilla::Telemetry::HistogramID aId, uint32_t aSample)
|
|
{
|
|
if (!internal_CanRecordBase()) {
|
|
return;
|
|
}
|
|
Histogram* h;
|
|
nsresult rv = internal_GetHistogramByEnumId(aId, &h, aProcessType);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
internal_HistogramAdd(*h, aSample, gHistograms[aId].dataset);
|
|
} else {
|
|
NS_WARNING("NS_FAILED GetHistogramByEnumId for CHILD");
|
|
}
|
|
}
|
|
|
|
void
|
|
internal_AccumulateChildKeyed(ProcessID aProcessType, mozilla::Telemetry::HistogramID aId,
|
|
const nsCString& aKey, uint32_t aSample)
|
|
{
|
|
if (!gInitDone || !internal_CanRecordBase()) {
|
|
return;
|
|
}
|
|
|
|
const char* suffix = SuffixForProcessType(aProcessType);
|
|
if (!suffix) {
|
|
MOZ_ASSERT_UNREACHABLE("suffix should not be null");
|
|
return;
|
|
}
|
|
|
|
const HistogramInfo& th = gHistograms[aId];
|
|
|
|
nsAutoCString id;
|
|
id.Append(th.id());
|
|
id.AppendASCII(suffix);
|
|
|
|
KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
|
|
MOZ_ASSERT(keyed);
|
|
keyed->Add(aKey, aSample);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: JSHistogram_* functions
|
|
|
|
// NOTE: the functions in this section:
|
|
//
|
|
// internal_JSHistogram_Add
|
|
// internal_JSHistogram_Snapshot
|
|
// internal_JSHistogram_Clear
|
|
// internal_WrapAndReturnHistogram
|
|
//
|
|
// all run without protection from |gTelemetryHistogramMutex|. If they
|
|
// held |gTelemetryHistogramMutex|, there would be the possibility of
|
|
// deadlock because the JS_ calls that they make may call back into the
|
|
// TelemetryHistogram interface, hence trying to re-acquire the mutex.
|
|
//
|
|
// This means that these functions potentially race against threads, but
|
|
// that seems preferable to risking deadlock.
|
|
|
|
namespace {
|
|
|
|
static const JSClass sJSHistogramClass = {
|
|
"JSHistogram", /* name */
|
|
JSCLASS_HAS_PRIVATE /* flags */
|
|
};
|
|
|
|
bool
|
|
internal_JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
{
|
|
JSObject *obj = JS_THIS_OBJECT(cx, vp);
|
|
MOZ_ASSERT(obj);
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSHistogramClass) {
|
|
return false;
|
|
}
|
|
|
|
Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(h);
|
|
Histogram::ClassType type = h->histogram_type();
|
|
|
|
JS::CallArgs args = CallArgsFromVp(argc, vp);
|
|
// This function should always return |undefined| and never fail but
|
|
// rather report failures using the console.
|
|
args.rval().setUndefined();
|
|
|
|
if (!internal_CanRecordBase()) {
|
|
return true;
|
|
}
|
|
|
|
uint32_t value = 0;
|
|
mozilla::Telemetry::HistogramID id;
|
|
if ((type == base::CountHistogram::COUNT_HISTOGRAM) && (args.length() == 0)) {
|
|
// If we don't have an argument for the count histogram, assume an increment of 1.
|
|
// Otherwise, make sure to run some sanity checks on the argument.
|
|
value = 1;
|
|
} else if (type == base::LinearHistogram::LINEAR_HISTOGRAM &&
|
|
(args.length() > 0) && args[0].isString() &&
|
|
NS_SUCCEEDED(internal_GetHistogramEnumId(h->histogram_name().c_str(), &id)) &&
|
|
gHistograms[id].histogramType == nsITelemetry::HISTOGRAM_CATEGORICAL) {
|
|
// For categorical histograms we allow passing a string argument that specifies the label.
|
|
nsAutoJSString label;
|
|
if (!label.init(cx, args[0])) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Invalid string parameter"));
|
|
return true;
|
|
}
|
|
|
|
// Get label id value.
|
|
nsresult rv = gHistograms[id].label_id(NS_ConvertUTF16toUTF8(label).get(), &value);
|
|
if (NS_FAILED(rv)) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag,
|
|
NS_LITERAL_STRING("Unknown label for categorical histogram"));
|
|
return true;
|
|
}
|
|
} else {
|
|
// All other accumulations expect one numerical argument.
|
|
if (!args.length()) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Expected one argument"));
|
|
return true;
|
|
}
|
|
|
|
if (!(args[0].isNumber() || args[0].isBoolean())) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Not a number"));
|
|
return true;
|
|
}
|
|
|
|
if (!JS::ToUint32(cx, args[0], &value)) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Failed to convert argument"));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
internal_Accumulate(*h, value);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
internal_JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
{
|
|
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
|
JSObject *obj = JS_THIS_OBJECT(cx, vp);
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSHistogramClass) {
|
|
return false;
|
|
}
|
|
|
|
Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
|
|
JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
|
|
if (!snapshot)
|
|
return false;
|
|
|
|
switch (internal_ReflectHistogramSnapshot(cx, snapshot, h)) {
|
|
case REFLECT_FAILURE:
|
|
return false;
|
|
case REFLECT_CORRUPT:
|
|
JS_ReportErrorASCII(cx, "Histogram is corrupt");
|
|
return false;
|
|
case REFLECT_OK:
|
|
args.rval().setObject(*snapshot);
|
|
return true;
|
|
default:
|
|
MOZ_CRASH("unhandled reflection status");
|
|
}
|
|
}
|
|
|
|
bool
|
|
internal_JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
{
|
|
JSObject *obj = JS_THIS_OBJECT(cx, vp);
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSHistogramClass) {
|
|
return false;
|
|
}
|
|
|
|
bool onlySubsession = false;
|
|
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
|
// This function should always return |undefined| and never fail but
|
|
// rather report failures using the console.
|
|
args.rval().setUndefined();
|
|
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
if (args.length() >= 1) {
|
|
if (!args[0].isBoolean()) {
|
|
JS_ReportErrorASCII(cx, "Not a boolean");
|
|
return false;
|
|
}
|
|
|
|
onlySubsession = JS::ToBoolean(args[0]);
|
|
}
|
|
#endif
|
|
|
|
Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(h);
|
|
if (h) {
|
|
internal_HistogramClear(*h, onlySubsession);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// NOTE: Runs without protection from |gTelemetryHistogramMutex|.
|
|
// See comment at the top of this section.
|
|
nsresult
|
|
internal_WrapAndReturnHistogram(Histogram *h, JSContext *cx,
|
|
JS::MutableHandle<JS::Value> ret)
|
|
{
|
|
JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &sJSHistogramClass));
|
|
if (!obj)
|
|
return NS_ERROR_FAILURE;
|
|
// The 3 functions that are wrapped up here are eventually called
|
|
// by the same thread that runs this function.
|
|
if (!(JS_DefineFunction(cx, obj, "add", internal_JSHistogram_Add, 1, 0)
|
|
&& JS_DefineFunction(cx, obj, "snapshot",
|
|
internal_JSHistogram_Snapshot, 0, 0)
|
|
&& JS_DefineFunction(cx, obj, "clear", internal_JSHistogram_Clear, 0, 0))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
JS_SetPrivate(obj, h);
|
|
ret.setObject(*obj);
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: JSKeyedHistogram_* functions
|
|
|
|
// NOTE: the functions in this section:
|
|
//
|
|
// internal_KeyedHistogram_SnapshotImpl
|
|
// internal_JSKeyedHistogram_Add
|
|
// internal_JSKeyedHistogram_Keys
|
|
// internal_JSKeyedHistogram_Snapshot
|
|
// internal_JSKeyedHistogram_SubsessionSnapshot
|
|
// internal_JSKeyedHistogram_SnapshotSubsessionAndClear
|
|
// internal_JSKeyedHistogram_Clear
|
|
// internal_WrapAndReturnKeyedHistogram
|
|
//
|
|
// Same comments as above, at the JSHistogram_* section, regarding
|
|
// deadlock avoidance, apply.
|
|
|
|
namespace {
|
|
|
|
static const JSClass sJSKeyedHistogramClass = {
|
|
"JSKeyedHistogram", /* name */
|
|
JSCLASS_HAS_PRIVATE /* flags */
|
|
};
|
|
|
|
bool
|
|
internal_KeyedHistogram_SnapshotImpl(JSContext *cx, unsigned argc,
|
|
JS::Value *vp,
|
|
bool subsession, bool clearSubsession)
|
|
{
|
|
JSObject *obj = JS_THIS_OBJECT(cx, vp);
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSKeyedHistogramClass) {
|
|
return false;
|
|
}
|
|
|
|
KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
|
|
if (!keyed) {
|
|
return false;
|
|
}
|
|
|
|
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
|
|
|
if (args.length() == 0) {
|
|
JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
|
|
if (!snapshot) {
|
|
JS_ReportErrorASCII(cx, "Failed to create object");
|
|
return false;
|
|
}
|
|
|
|
if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot, subsession, clearSubsession))) {
|
|
JS_ReportErrorASCII(cx, "Failed to reflect keyed histograms");
|
|
return false;
|
|
}
|
|
|
|
args.rval().setObject(*snapshot);
|
|
return true;
|
|
}
|
|
|
|
nsAutoJSString key;
|
|
if (!args[0].isString() || !key.init(cx, args[0])) {
|
|
JS_ReportErrorASCII(cx, "Not a string");
|
|
return false;
|
|
}
|
|
|
|
Histogram* h = nullptr;
|
|
nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h, subsession);
|
|
if (NS_FAILED(rv)) {
|
|
JS_ReportErrorASCII(cx, "Failed to get histogram");
|
|
return false;
|
|
}
|
|
|
|
JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
|
|
if (!snapshot) {
|
|
return false;
|
|
}
|
|
|
|
switch (internal_ReflectHistogramSnapshot(cx, snapshot, h)) {
|
|
case REFLECT_FAILURE:
|
|
return false;
|
|
case REFLECT_CORRUPT:
|
|
JS_ReportErrorASCII(cx, "Histogram is corrupt");
|
|
return false;
|
|
case REFLECT_OK:
|
|
args.rval().setObject(*snapshot);
|
|
return true;
|
|
default:
|
|
MOZ_CRASH("unhandled reflection status");
|
|
}
|
|
}
|
|
|
|
bool
|
|
internal_JSKeyedHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
{
|
|
JSObject *obj = JS_THIS_OBJECT(cx, vp);
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSKeyedHistogramClass) {
|
|
return false;
|
|
}
|
|
|
|
KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
|
|
if (!keyed) {
|
|
return false;
|
|
}
|
|
|
|
JS::CallArgs args = CallArgsFromVp(argc, vp);
|
|
// This function should always return |undefined| and never fail but
|
|
// rather report failures using the console.
|
|
args.rval().setUndefined();
|
|
if (args.length() < 1) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Expected one argument"));
|
|
return true;
|
|
}
|
|
|
|
nsAutoJSString key;
|
|
if (!args[0].isString() || !key.init(cx, args[0])) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Not a string"));
|
|
return true;
|
|
}
|
|
|
|
const uint32_t type = keyed->GetHistogramType();
|
|
|
|
// If we don't have an argument for the count histogram, assume an increment of 1.
|
|
// Otherwise, make sure to run some sanity checks on the argument.
|
|
uint32_t value = 1;
|
|
if ((type != nsITelemetry::HISTOGRAM_COUNT) || (args.length() == 2)) {
|
|
if (args.length() < 2) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag,
|
|
NS_LITERAL_STRING("Expected two arguments for this histogram type"));
|
|
return true;
|
|
}
|
|
|
|
if (type == nsITelemetry::HISTOGRAM_CATEGORICAL && args[1].isString()) {
|
|
// For categorical histograms we allow passing a string argument that specifies the label.
|
|
mozilla::Telemetry::HistogramID id;
|
|
if (NS_FAILED(keyed->GetEnumId(id))) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Failed to get histogram id."));
|
|
return true;
|
|
}
|
|
|
|
// Get label string.
|
|
nsAutoJSString label;
|
|
if (!label.init(cx, args[1])) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Invalid string parameter"));
|
|
return true;
|
|
}
|
|
|
|
// Get label id value.
|
|
nsresult rv = gHistograms[id].label_id(NS_ConvertUTF16toUTF8(label).get(), &value);
|
|
if (NS_FAILED(rv)) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag,
|
|
NS_LITERAL_STRING("Unknown label for categorical histogram"));
|
|
return true;
|
|
}
|
|
} else {
|
|
// All other accumulations expect one numerical argument.
|
|
if (!(args[1].isNumber() || args[1].isBoolean())) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Not a number"));
|
|
return true;
|
|
}
|
|
|
|
if (!JS::ToUint32(cx, args[1], &value)) {
|
|
LogToBrowserConsole(nsIScriptError::errorFlag, NS_LITERAL_STRING("Failed to convert argument"));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
internal_Accumulate(*keyed, NS_ConvertUTF16toUTF8(key), value);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
internal_JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
{
|
|
JSObject *obj = JS_THIS_OBJECT(cx, vp);
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSKeyedHistogramClass) {
|
|
return false;
|
|
}
|
|
|
|
KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
|
|
if (!keyed) {
|
|
return false;
|
|
}
|
|
|
|
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
|
return NS_SUCCEEDED(keyed->GetJSKeys(cx, args));
|
|
}
|
|
|
|
bool
|
|
internal_JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
{
|
|
return internal_KeyedHistogram_SnapshotImpl(cx, argc, vp, false, false);
|
|
}
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
bool
|
|
internal_JSKeyedHistogram_SubsessionSnapshot(JSContext *cx,
|
|
unsigned argc, JS::Value *vp)
|
|
{
|
|
return internal_KeyedHistogram_SnapshotImpl(cx, argc, vp, true, false);
|
|
}
|
|
#endif
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
bool
|
|
internal_JSKeyedHistogram_SnapshotSubsessionAndClear(JSContext *cx,
|
|
unsigned argc,
|
|
JS::Value *vp)
|
|
{
|
|
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
|
if (args.length() != 0) {
|
|
JS_ReportErrorASCII(cx, "No key arguments supported for snapshotSubsessionAndClear");
|
|
}
|
|
|
|
return internal_KeyedHistogram_SnapshotImpl(cx, argc, vp, true, true);
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
internal_JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
{
|
|
JSObject *obj = JS_THIS_OBJECT(cx, vp);
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSKeyedHistogramClass) {
|
|
return false;
|
|
}
|
|
|
|
KeyedHistogram* keyed = static_cast<KeyedHistogram*>(JS_GetPrivate(obj));
|
|
if (!keyed) {
|
|
return false;
|
|
}
|
|
|
|
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
|
// This function should always return |undefined| and never fail but
|
|
// rather report failures using the console.
|
|
args.rval().setUndefined();
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
bool onlySubsession = false;
|
|
|
|
if (args.length() >= 1) {
|
|
if (!(args[0].isNumber() || args[0].isBoolean())) {
|
|
JS_ReportErrorASCII(cx, "Not a boolean");
|
|
return false;
|
|
}
|
|
|
|
onlySubsession = JS::ToBoolean(args[0]);
|
|
}
|
|
|
|
keyed->Clear(onlySubsession);
|
|
#else
|
|
keyed->Clear(false);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
// NOTE: Runs without protection from |gTelemetryHistogramMutex|.
|
|
// See comment at the top of this section.
|
|
nsresult
|
|
internal_WrapAndReturnKeyedHistogram(KeyedHistogram *h, JSContext *cx,
|
|
JS::MutableHandle<JS::Value> ret)
|
|
{
|
|
JS::Rooted<JSObject*> obj(cx, JS_NewObject(cx, &sJSKeyedHistogramClass));
|
|
if (!obj)
|
|
return NS_ERROR_FAILURE;
|
|
// The 6 functions that are wrapped up here are eventually called
|
|
// by the same thread that runs this function.
|
|
if (!(JS_DefineFunction(cx, obj, "add", internal_JSKeyedHistogram_Add, 2, 0)
|
|
&& JS_DefineFunction(cx, obj, "snapshot",
|
|
internal_JSKeyedHistogram_Snapshot, 1, 0)
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
&& JS_DefineFunction(cx, obj, "subsessionSnapshot",
|
|
internal_JSKeyedHistogram_SubsessionSnapshot, 1, 0)
|
|
&& JS_DefineFunction(cx, obj, "snapshotSubsessionAndClear",
|
|
internal_JSKeyedHistogram_SnapshotSubsessionAndClear, 0, 0)
|
|
#endif
|
|
&& JS_DefineFunction(cx, obj, "keys",
|
|
internal_JSKeyedHistogram_Keys, 0, 0)
|
|
&& JS_DefineFunction(cx, obj, "clear",
|
|
internal_JSKeyedHistogram_Clear, 0, 0))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
JS_SetPrivate(obj, h);
|
|
ret.setObject(*obj);
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryHistogram::
|
|
|
|
// All of these functions are actually in namespace TelemetryHistogram::,
|
|
// but the ::TelemetryHistogram prefix is given explicitly. This is
|
|
// because it is critical to see which calls from these functions are
|
|
// to another function in this interface. Mis-identifying "inwards
|
|
// calls" from "calls to another function in this interface" will lead
|
|
// to deadlocking and/or races. See comments at the top of the file
|
|
// for further (important!) details.
|
|
|
|
// Create and destroy the singleton StatisticsRecorder object.
|
|
void TelemetryHistogram::CreateStatisticsRecorder()
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
MOZ_ASSERT(!gStatisticsRecorder);
|
|
gStatisticsRecorder = new base::StatisticsRecorder();
|
|
}
|
|
|
|
void TelemetryHistogram::DestroyStatisticsRecorder()
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
MOZ_ASSERT(gStatisticsRecorder);
|
|
if (gStatisticsRecorder) {
|
|
delete gStatisticsRecorder;
|
|
gStatisticsRecorder = nullptr;
|
|
}
|
|
}
|
|
|
|
void TelemetryHistogram::InitializeGlobalState(bool canRecordBase,
|
|
bool canRecordExtended)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
MOZ_ASSERT(!gInitDone, "TelemetryHistogram::InitializeGlobalState "
|
|
"may only be called once");
|
|
|
|
gCanRecordBase = canRecordBase;
|
|
gCanRecordExtended = canRecordExtended;
|
|
|
|
// gHistogramMap should have been pre-sized correctly at the
|
|
// declaration point further up in this file.
|
|
|
|
// Populate the static histogram name->id cache.
|
|
// Note that the histogram names are statically allocated.
|
|
for (uint32_t i = 0; i < mozilla::Telemetry::HistogramCount; i++) {
|
|
CharPtrEntryType *entry = gHistogramMap.PutEntry(gHistograms[i].id());
|
|
entry->mData = (mozilla::Telemetry::HistogramID) i;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
gHistogramMap.MarkImmutable();
|
|
#endif
|
|
|
|
mozilla::PodArrayZero(gCorruptHistograms);
|
|
|
|
// Create registered keyed histograms
|
|
for (const auto & h : gHistograms) {
|
|
if (!h.keyed) {
|
|
continue;
|
|
}
|
|
|
|
const nsDependentCString id(h.id());
|
|
const nsDependentCString expiration(h.expiration());
|
|
gKeyedHistograms.Put(id, new KeyedHistogram(id, expiration, h.histogramType,
|
|
h.min, h.max, h.bucketCount, h.dataset));
|
|
if (XRE_IsParentProcess()) {
|
|
// We must create registered child keyed histograms as well or else the
|
|
// same code in TelemetrySession.jsm that fails without parent keyed
|
|
// histograms will fail without child keyed histograms.
|
|
nsCString contentId(id);
|
|
contentId.AppendLiteral(CONTENT_HISTOGRAM_SUFFIX);
|
|
gKeyedHistograms.Put(contentId,
|
|
new KeyedHistogram(id, expiration, h.histogramType,
|
|
h.min, h.max, h.bucketCount, h.dataset));
|
|
|
|
nsCString gpuId(id);
|
|
gpuId.AppendLiteral(GPU_HISTOGRAM_SUFFIX);
|
|
gKeyedHistograms.Put(gpuId,
|
|
new KeyedHistogram(id, expiration, h.histogramType,
|
|
h.min, h.max, h.bucketCount, h.dataset));
|
|
|
|
nsCString extensionId(id);
|
|
extensionId.AppendLiteral(EXTENSION_HISTOGRAM_SUFFIX);
|
|
gKeyedHistograms.Put(extensionId,
|
|
new KeyedHistogram(id, expiration, h.histogramType,
|
|
h.min, h.max, h.bucketCount, h.dataset));
|
|
}
|
|
}
|
|
|
|
// Some Telemetry histograms depend on the value of C++ constants and hardcode
|
|
// their values in Histograms.json.
|
|
// We add static asserts here for those values to match so that future changes
|
|
// don't go unnoticed.
|
|
static_assert((JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
|
|
gHistograms[mozilla::Telemetry::GC_MINOR_REASON].bucketCount &&
|
|
(JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
|
|
gHistograms[mozilla::Telemetry::GC_MINOR_REASON_LONG].bucketCount &&
|
|
(JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
|
|
gHistograms[mozilla::Telemetry::GC_REASON_2].bucketCount,
|
|
"NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json."
|
|
" If this was an intentional change, update the n_values for the "
|
|
"following in Histograms.json: GC_MINOR_REASON, GC_MINOR_REASON_LONG, "
|
|
"GC_REASON_2");
|
|
|
|
static_assert((mozilla::StartupTimeline::MAX_EVENT_ID + 1) ==
|
|
gHistograms[mozilla::Telemetry::STARTUP_MEASUREMENT_ERRORS].bucketCount,
|
|
"MAX_EVENT_ID is assumed to be a fixed value in Histograms.json. If this"
|
|
" was an intentional change, update the n_values for the following in "
|
|
"Histograms.json: STARTUP_MEASUREMENT_ERRORS");
|
|
|
|
|
|
gInitDone = true;
|
|
}
|
|
|
|
void TelemetryHistogram::DeInitializeGlobalState()
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
gCanRecordBase = false;
|
|
gCanRecordExtended = false;
|
|
gHistogramMap.Clear();
|
|
gKeyedHistograms.Clear();
|
|
gInitDone = false;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
bool TelemetryHistogram::GlobalStateHasBeenInitialized() {
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
return gInitDone;
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
TelemetryHistogram::CanRecordBase() {
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
return internal_CanRecordBase();
|
|
}
|
|
|
|
void
|
|
TelemetryHistogram::SetCanRecordBase(bool b) {
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
gCanRecordBase = b;
|
|
}
|
|
|
|
bool
|
|
TelemetryHistogram::CanRecordExtended() {
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
return internal_CanRecordExtended();
|
|
}
|
|
|
|
void
|
|
TelemetryHistogram::SetCanRecordExtended(bool b) {
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
gCanRecordExtended = b;
|
|
}
|
|
|
|
|
|
void
|
|
TelemetryHistogram::InitHistogramRecordingEnabled()
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
auto processType = XRE_GetProcessType();
|
|
for (size_t i = 0; i < mozilla::ArrayLength(gHistograms); ++i) {
|
|
const HistogramInfo& h = gHistograms[i];
|
|
mozilla::Telemetry::HistogramID id = mozilla::Telemetry::HistogramID(i);
|
|
internal_SetHistogramRecordingEnabled(id,
|
|
CanRecordInProcess(h.record_in_processes,
|
|
processType));
|
|
}
|
|
|
|
for (auto recordingInitiallyDisabledID : kRecordingInitiallyDisabledIDs) {
|
|
internal_SetHistogramRecordingEnabled(recordingInitiallyDisabledID,
|
|
false);
|
|
}
|
|
}
|
|
|
|
void
|
|
TelemetryHistogram::SetHistogramRecordingEnabled(mozilla::Telemetry::HistogramID aID,
|
|
bool aEnabled)
|
|
{
|
|
if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) {
|
|
MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
const HistogramInfo& h = gHistograms[aID];
|
|
if (!CanRecordInProcess(h.record_in_processes, XRE_GetProcessType())) {
|
|
// Don't permit record_in_process-disabled recording to be re-enabled.
|
|
return;
|
|
}
|
|
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
internal_SetHistogramRecordingEnabled(aID, aEnabled);
|
|
}
|
|
|
|
|
|
nsresult
|
|
TelemetryHistogram::SetHistogramRecordingEnabled(const nsACString &id,
|
|
bool aEnabled)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
|
|
mozilla::Telemetry::HistogramID hId;
|
|
nsresult rv = internal_GetHistogramEnumId(PromiseFlatCString(id).get(), &hId);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
const HistogramInfo& hi = gHistograms[hId];
|
|
if (!CanRecordInProcess(hi.record_in_processes, XRE_GetProcessType())) {
|
|
return NS_OK;
|
|
}
|
|
|
|
Histogram *h;
|
|
rv = internal_GetHistogramByName(id, &h);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
h->SetRecordingEnabled(aEnabled);
|
|
return NS_OK;
|
|
}
|
|
|
|
KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
|
|
if (keyed) {
|
|
keyed->SetRecordingEnabled(aEnabled);
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
|
|
void
|
|
TelemetryHistogram::Accumulate(mozilla::Telemetry::HistogramID aID,
|
|
uint32_t aSample)
|
|
{
|
|
if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) {
|
|
MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
internal_Accumulate(aID, aSample);
|
|
}
|
|
|
|
void
|
|
TelemetryHistogram::Accumulate(mozilla::Telemetry::HistogramID aID,
|
|
const nsCString& aKey, uint32_t aSample)
|
|
{
|
|
if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) {
|
|
MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
internal_Accumulate(aID, aKey, aSample);
|
|
}
|
|
|
|
void
|
|
TelemetryHistogram::Accumulate(const char* name, uint32_t sample)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
if (!internal_CanRecordBase()) {
|
|
return;
|
|
}
|
|
mozilla::Telemetry::HistogramID id;
|
|
nsresult rv = internal_GetHistogramEnumId(name, &id);
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
internal_Accumulate(id, sample);
|
|
}
|
|
|
|
void
|
|
TelemetryHistogram::Accumulate(const char* name,
|
|
const nsCString& key, uint32_t sample)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
if (!internal_CanRecordBase()) {
|
|
return;
|
|
}
|
|
mozilla::Telemetry::HistogramID id;
|
|
nsresult rv = internal_GetHistogramEnumId(name, &id);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
internal_Accumulate(id, key, sample);
|
|
}
|
|
}
|
|
|
|
void
|
|
TelemetryHistogram::AccumulateCategorical(mozilla::Telemetry::HistogramID aId,
|
|
const nsCString& label)
|
|
{
|
|
if (NS_WARN_IF(!internal_IsHistogramEnumId(aId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
if (!internal_CanRecordBase()) {
|
|
return;
|
|
}
|
|
uint32_t labelId = 0;
|
|
if (NS_FAILED(gHistograms[aId].label_id(label.get(), &labelId))) {
|
|
return;
|
|
}
|
|
internal_Accumulate(aId, labelId);
|
|
}
|
|
|
|
void
|
|
TelemetryHistogram::AccumulateChild(ProcessID aProcessType,
|
|
const nsTArray<Accumulation>& aAccumulations)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
if (!internal_CanRecordBase()) {
|
|
return;
|
|
}
|
|
for (uint32_t i = 0; i < aAccumulations.Length(); ++i) {
|
|
if (NS_WARN_IF(!internal_IsHistogramEnumId(aAccumulations[i].mId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
|
|
continue;
|
|
}
|
|
internal_AccumulateChild(aProcessType, aAccumulations[i].mId, aAccumulations[i].mSample);
|
|
}
|
|
}
|
|
|
|
void
|
|
TelemetryHistogram::AccumulateChildKeyed(ProcessID aProcessType,
|
|
const nsTArray<KeyedAccumulation>& aAccumulations)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
if (!internal_CanRecordBase()) {
|
|
return;
|
|
}
|
|
for (uint32_t i = 0; i < aAccumulations.Length(); ++i) {
|
|
if (NS_WARN_IF(!internal_IsHistogramEnumId(aAccumulations[i].mId))) {
|
|
MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
|
|
continue;
|
|
}
|
|
internal_AccumulateChildKeyed(aProcessType,
|
|
aAccumulations[i].mId,
|
|
aAccumulations[i].mKey,
|
|
aAccumulations[i].mSample);
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
TelemetryHistogram::GetHistogramById(const nsACString &name, JSContext *cx,
|
|
JS::MutableHandle<JS::Value> ret)
|
|
{
|
|
Histogram *h = nullptr;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
nsresult rv = internal_GetHistogramByName(name, &h);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
}
|
|
// Runs without protection from |gTelemetryHistogramMutex|
|
|
return internal_WrapAndReturnHistogram(h, cx, ret);
|
|
}
|
|
|
|
nsresult
|
|
TelemetryHistogram::GetKeyedHistogramById(const nsACString &name,
|
|
JSContext *cx,
|
|
JS::MutableHandle<JS::Value> ret)
|
|
{
|
|
KeyedHistogram* keyed = nullptr;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
if (!gKeyedHistograms.Get(name, &keyed)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
// Runs without protection from |gTelemetryHistogramMutex|
|
|
return internal_WrapAndReturnKeyedHistogram(keyed, cx, ret);
|
|
}
|
|
|
|
const char*
|
|
TelemetryHistogram::GetHistogramName(mozilla::Telemetry::HistogramID id)
|
|
{
|
|
if (NS_WARN_IF(!internal_IsHistogramEnumId(id))) {
|
|
MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
|
|
return nullptr;
|
|
}
|
|
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
const HistogramInfo& h = gHistograms[id];
|
|
return h.id();
|
|
}
|
|
|
|
nsresult
|
|
TelemetryHistogram::CreateHistogramSnapshots(JSContext *cx,
|
|
JS::MutableHandle<JS::Value> ret,
|
|
bool subsession,
|
|
bool clearSubsession)
|
|
{
|
|
// Runs without protection from |gTelemetryHistogramMutex|
|
|
JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
|
|
if (!root_obj)
|
|
return NS_ERROR_FAILURE;
|
|
ret.setObject(*root_obj);
|
|
|
|
// Include the GPU process in histogram snapshots only if we actually tried
|
|
// to launch a process for it.
|
|
bool includeGPUProcess = false;
|
|
if (auto gpm = mozilla::gfx::GPUProcessManager::Get()) {
|
|
includeGPUProcess = gpm->AttemptedGPUProcess();
|
|
}
|
|
|
|
// Ensure that all the HISTOGRAM_FLAG & HISTOGRAM_COUNT histograms have
|
|
// been created, so that their values are snapshotted.
|
|
auto processType = XRE_GetProcessType();
|
|
for (size_t i = 0; i < mozilla::Telemetry::HistogramCount; ++i) {
|
|
const HistogramInfo& hi = gHistograms[i];
|
|
if (hi.keyed || !CanRecordInProcess(hi.record_in_processes, processType)) {
|
|
continue;
|
|
}
|
|
const uint32_t type = hi.histogramType;
|
|
if (type == nsITelemetry::HISTOGRAM_FLAG ||
|
|
type == nsITelemetry::HISTOGRAM_COUNT) {
|
|
Histogram *h;
|
|
mozilla::DebugOnly<nsresult> rv;
|
|
mozilla::Telemetry::HistogramID id = mozilla::Telemetry::HistogramID(i);
|
|
|
|
for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
|
|
if ((ProcessID(process) == ProcessID::Gpu) && !includeGPUProcess) {
|
|
continue;
|
|
}
|
|
rv = internal_GetHistogramByEnumId(id, &h, ProcessID(process));
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
}
|
|
}
|
|
|
|
StatisticsRecorder::Histograms hs;
|
|
StatisticsRecorder::GetHistograms(&hs);
|
|
|
|
// We identify corrupt histograms first, rather than interspersing it
|
|
// in the loop below, to ensure that our corruption statistics don't
|
|
// depend on histogram enumeration order.
|
|
//
|
|
// Of course, we hope that all of these corruption-statistics
|
|
// histograms are not themselves corrupt...
|
|
internal_IdentifyCorruptHistograms(hs);
|
|
|
|
// OK, now we can actually reflect things.
|
|
JS::Rooted<JSObject*> hobj(cx);
|
|
for (size_t i = 0; i < mozilla::Telemetry::HistogramCount; ++i) {
|
|
const HistogramInfo& hi = gHistograms[i];
|
|
if (hi.keyed) {
|
|
continue;
|
|
}
|
|
|
|
Histogram* h = nullptr;
|
|
mozilla::Telemetry::HistogramID id = mozilla::Telemetry::HistogramID(i);
|
|
|
|
for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
|
|
if (!CanRecordInProcess(hi.record_in_processes, ProcessID(process)) ||
|
|
((ProcessID(process) == ProcessID::Gpu) && !includeGPUProcess)) {
|
|
continue;
|
|
}
|
|
nsresult rv = internal_GetHistogramByEnumId(id, &h, ProcessID(process));
|
|
if (NS_WARN_IF(NS_FAILED(rv)) || !internal_ShouldReflectHistogram(h) ||
|
|
internal_IsEmpty(h) || internal_IsExpired(h)) {
|
|
continue;
|
|
}
|
|
|
|
Histogram* original = h;
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
if (subsession) {
|
|
h = internal_GetSubsessionHistogram(*h);
|
|
if (!h) {
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
hobj = JS_NewPlainObject(cx);
|
|
if (!hobj) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
switch (internal_ReflectHistogramSnapshot(cx, hobj, h)) {
|
|
case REFLECT_CORRUPT:
|
|
// We can still hit this case even if ShouldReflectHistograms
|
|
// returns true. The histogram lies outside of our control
|
|
// somehow; just skip it.
|
|
continue;
|
|
case REFLECT_FAILURE:
|
|
return NS_ERROR_FAILURE;
|
|
case REFLECT_OK:
|
|
if (!JS_DefineProperty(cx, root_obj, original->histogram_name().c_str(),
|
|
hobj, JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
if (subsession && clearSubsession) {
|
|
h->Clear();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
TelemetryHistogram::RegisteredHistograms(uint32_t aDataset, uint32_t *aCount,
|
|
char*** aHistograms)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
return internal_GetRegisteredHistogramIds(false,
|
|
aDataset, aCount, aHistograms);
|
|
}
|
|
|
|
nsresult
|
|
TelemetryHistogram::RegisteredKeyedHistograms(uint32_t aDataset,
|
|
uint32_t *aCount,
|
|
char*** aHistograms)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
return internal_GetRegisteredHistogramIds(true,
|
|
aDataset, aCount, aHistograms);
|
|
}
|
|
|
|
nsresult
|
|
TelemetryHistogram::GetKeyedHistogramSnapshots(JSContext *cx,
|
|
JS::MutableHandle<JS::Value> ret)
|
|
{
|
|
// Runs without protection from |gTelemetryHistogramMutex|
|
|
JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
|
|
if (!obj) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
for (auto iter = gKeyedHistograms.Iter(); !iter.Done(); iter.Next()) {
|
|
JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
|
|
if (!snapshot) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (!NS_SUCCEEDED(iter.Data()->GetJSSnapshot(cx, snapshot, false, false))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (!JS_DefineProperty(cx, obj, PromiseFlatCString(iter.Key()).get(),
|
|
snapshot, JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
ret.setObject(*obj);
|
|
return NS_OK;
|
|
}
|
|
|
|
size_t
|
|
TelemetryHistogram::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf
|
|
aMallocSizeOf)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
return gHistogramMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
size_t
|
|
TelemetryHistogram::GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf
|
|
aMallocSizeOf)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
StatisticsRecorder::Histograms hs;
|
|
StatisticsRecorder::GetHistograms(&hs);
|
|
size_t n = 0;
|
|
for (auto h : hs) {
|
|
n += h->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
return n;
|
|
}
|