The log and exp calls in base::Histogram::InitializeBucketRange() were showing up in profiles. This patch uses the precomputed buckets for exponential histograms instead of computing them at runtime. Though linear histograms do show up in the profile that prompted this change, they contribute much less, and due to the trivial nature of generating these, it's unlikely that a static cache would provide much if any speedup. MozReview-Commit-ID: IavFwoWjFhk
2128 lines
64 KiB
C++
2128 lines
64 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::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::HistogramID;
|
|
using mozilla::Telemetry::ProcessID;
|
|
using mozilla::Telemetry::HistogramCount;
|
|
using mozilla::Telemetry::Common::LogToBrowserConsole;
|
|
using mozilla::Telemetry::Common::RecordedProcessType;
|
|
using mozilla::Telemetry::Common::AutoHashtable;
|
|
using mozilla::Telemetry::Common::GetNameForProcessID;
|
|
using mozilla::Telemetry::Common::IsExpiredVersion;
|
|
using mozilla::Telemetry::Common::CanRecordDataset;
|
|
using mozilla::Telemetry::Common::IsInDataset;
|
|
|
|
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
|
|
|
|
namespace {
|
|
|
|
typedef nsDataHashtable<nsCStringHashKey, HistogramID> StringToHistogramIdMap;
|
|
|
|
// Hardcoded probes
|
|
struct HistogramInfo {
|
|
uint32_t min;
|
|
uint32_t max;
|
|
uint32_t bucketCount;
|
|
uint32_t histogramType;
|
|
uint32_t name_offset;
|
|
uint32_t expiration_offset;
|
|
uint32_t dataset;
|
|
uint32_t label_index;
|
|
uint32_t label_count;
|
|
RecordedProcessType record_in_processes;
|
|
bool keyed;
|
|
|
|
const char *name() const;
|
|
const char *expiration() const;
|
|
nsresult label_id(const char* label, uint32_t* labelId) const;
|
|
};
|
|
|
|
enum reflectStatus {
|
|
REFLECT_OK,
|
|
REFLECT_FAILURE
|
|
};
|
|
|
|
enum class SessionType {
|
|
Session = 0,
|
|
Subsession = 1,
|
|
Count,
|
|
};
|
|
|
|
class KeyedHistogram {
|
|
public:
|
|
KeyedHistogram(HistogramID id, const HistogramInfo& info);
|
|
~KeyedHistogram();
|
|
nsresult GetHistogram(const nsCString& name, Histogram** histogram, bool subsession);
|
|
Histogram* GetHistogram(const nsCString& name, bool subsession);
|
|
uint32_t GetHistogramType() const { return mHistogramInfo.histogramType; }
|
|
nsresult GetJSKeys(JSContext* cx, JS::CallArgs& args);
|
|
nsresult GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj,
|
|
bool subsession, bool clearSubsession);
|
|
|
|
nsresult Add(const nsCString& key, uint32_t aSample, ProcessID aProcessType);
|
|
void Clear(bool subsession);
|
|
|
|
HistogramID GetHistogramID() const { return mId; }
|
|
|
|
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 HistogramID mId;
|
|
const HistogramInfo& mHistogramInfo;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE STATE, SHARED BY ALL THREADS
|
|
|
|
namespace {
|
|
|
|
// Set to true once this global state has been initialized
|
|
bool gInitDone = false;
|
|
|
|
// Whether we are collecting the base, opt-out, Histogram data.
|
|
bool gCanRecordBase = false;
|
|
// Whether we are collecting the extended, opt-in, Histogram data.
|
|
bool gCanRecordExtended = false;
|
|
|
|
// The storage for actual Histogram instances.
|
|
// We use separate ones for plain and keyed histograms.
|
|
Histogram* gHistogramStorage[HistogramCount][uint32_t(ProcessID::Count)][uint32_t(SessionType::Count)] = {};
|
|
// Keyed histograms internally map string keys to individual Histogram instances.
|
|
// KeyedHistogram keeps track of session & subsession histograms internally.
|
|
KeyedHistogram* gKeyedHistogramStorage[HistogramCount][uint32_t(ProcessID::Count)] = {};
|
|
|
|
// Cache of histogram name to a histogram id.
|
|
StringToHistogramIdMap gNameToHistogramIDMap(HistogramCount);
|
|
|
|
// To simplify logic below we use a single histogram instance for all expired histograms.
|
|
Histogram* gExpiredHistogram = nullptr;
|
|
|
|
// This tracks whether recording is enabled for specific histograms.
|
|
// To utilize C++ initialization rules, we invert the meaning to "disabled".
|
|
bool gHistogramRecordingDisabled[HistogramCount] = {};
|
|
|
|
// This is for gHistogramInfos, gHistogramStringTable
|
|
#include "TelemetryHistogramData.inc"
|
|
|
|
} // namespace
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE CONSTANTS
|
|
|
|
namespace {
|
|
|
|
// List of histogram IDs which should have recording disabled initially.
|
|
const 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
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// The core storage access functions.
|
|
// They wrap access to the histogram storage and lookup caches.
|
|
|
|
namespace {
|
|
|
|
// Factory function for histogram instances.
|
|
Histogram*
|
|
internal_CreateHistogramInstance(const HistogramInfo& info, int bucketsOffset);
|
|
|
|
bool
|
|
internal_IsHistogramEnumId(HistogramID aID)
|
|
{
|
|
static_assert(((HistogramID)-1 > 0), "ID should be unsigned.");
|
|
return aID < HistogramCount;
|
|
}
|
|
|
|
// Look up a plain histogram by id.
|
|
Histogram*
|
|
internal_GetHistogramById(HistogramID histogramId, ProcessID processId, SessionType sessionType,
|
|
bool instantiate = true)
|
|
{
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(histogramId));
|
|
MOZ_ASSERT(!gHistogramInfos[histogramId].keyed);
|
|
MOZ_ASSERT(processId < ProcessID::Count);
|
|
MOZ_ASSERT(sessionType < SessionType::Count);
|
|
|
|
Histogram* h = gHistogramStorage[histogramId][uint32_t(processId)][uint32_t(sessionType)];
|
|
if (h || !instantiate) {
|
|
return h;
|
|
}
|
|
|
|
const HistogramInfo& info = gHistogramInfos[histogramId];
|
|
const int bucketsOffset = gExponentialBucketLowerBoundIndex[histogramId];
|
|
h = internal_CreateHistogramInstance(info, bucketsOffset);
|
|
MOZ_ASSERT(h);
|
|
gHistogramStorage[histogramId][uint32_t(processId)][uint32_t(sessionType)] = h;
|
|
return h;
|
|
}
|
|
|
|
// Look up a keyed histogram by id.
|
|
KeyedHistogram*
|
|
internal_GetKeyedHistogramById(HistogramID histogramId, ProcessID processId,
|
|
bool instantiate = true)
|
|
{
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(histogramId));
|
|
MOZ_ASSERT(gHistogramInfos[histogramId].keyed);
|
|
MOZ_ASSERT(processId < ProcessID::Count);
|
|
|
|
KeyedHistogram* kh = gKeyedHistogramStorage[histogramId][uint32_t(processId)];
|
|
if (kh || !instantiate) {
|
|
return kh;
|
|
}
|
|
|
|
const HistogramInfo& info = gHistogramInfos[histogramId];
|
|
kh = new KeyedHistogram(histogramId, info);
|
|
gKeyedHistogramStorage[histogramId][uint32_t(processId)] = kh;
|
|
|
|
return kh;
|
|
}
|
|
|
|
// Look up a histogram id from a histogram name.
|
|
nsresult
|
|
internal_GetHistogramIdByName(const nsACString& name, HistogramID* id)
|
|
{
|
|
const bool found = gNameToHistogramIDMap.Get(name, id);
|
|
if (!found) {
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Clear a histogram from storage.
|
|
void
|
|
internal_ClearHistogramById(HistogramID histogramId, ProcessID processId, SessionType sessionType)
|
|
{
|
|
delete gHistogramStorage[histogramId][uint32_t(processId)][uint32_t(sessionType)];
|
|
gHistogramStorage[histogramId][uint32_t(processId)][uint32_t(sessionType)] = nullptr;
|
|
}
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: Misc small helpers
|
|
|
|
namespace {
|
|
|
|
bool
|
|
internal_CanRecordBase() {
|
|
return gCanRecordBase;
|
|
}
|
|
|
|
bool
|
|
internal_CanRecordExtended() {
|
|
return gCanRecordExtended;
|
|
}
|
|
|
|
// Note: this is completely unrelated to mozilla::IsEmpty.
|
|
bool
|
|
internal_IsEmpty(const Histogram *h)
|
|
{
|
|
return h->is_empty();
|
|
}
|
|
|
|
bool
|
|
internal_IsExpired(Histogram* h)
|
|
{
|
|
return h == gExpiredHistogram;
|
|
}
|
|
|
|
void
|
|
internal_SetHistogramRecordingEnabled(HistogramID id, bool aEnabled)
|
|
{
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(id));
|
|
gHistogramRecordingDisabled[id] = !aEnabled;
|
|
}
|
|
|
|
bool
|
|
internal_IsRecordingEnabled(HistogramID id)
|
|
{
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(id));
|
|
return !gHistogramRecordingDisabled[id];
|
|
}
|
|
|
|
nsresult
|
|
internal_GetRegisteredHistogramIds(bool keyed, uint32_t dataset,
|
|
uint32_t *aCount, char*** aHistograms)
|
|
{
|
|
nsTArray<char*> collection;
|
|
|
|
for (const auto & h : gHistogramInfos) {
|
|
if (IsExpiredVersion(h.expiration()) ||
|
|
h.keyed != keyed ||
|
|
!IsInDataset(h.dataset, dataset)) {
|
|
continue;
|
|
}
|
|
|
|
const char* id = h.name();
|
|
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::name() const
|
|
{
|
|
return &gHistogramStringTable[this->name_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(const HistogramInfo& info)
|
|
{
|
|
if (info.histogramType != nsITelemetry::HISTOGRAM_BOOLEAN
|
|
&& info.histogramType != nsITelemetry::HISTOGRAM_FLAG
|
|
&& info.histogramType != nsITelemetry::HISTOGRAM_COUNT) {
|
|
// Sanity checks for histogram parameters.
|
|
if (info.min >= info.max) {
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
if (info.bucketCount <= 2) {
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
if (info.min < 1) {
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
Histogram*
|
|
internal_CreateHistogramInstance(const HistogramInfo& passedInfo, int bucketsOffset)
|
|
{
|
|
if (NS_FAILED(internal_CheckHistogramArguments(passedInfo))) {
|
|
MOZ_ASSERT(false, "Failed histogram argument checks.");
|
|
return nullptr;
|
|
}
|
|
|
|
// To keep the code simple we map all the calls to expired histograms to the same histogram instance.
|
|
// We create that instance lazily when needed.
|
|
const bool isExpired = IsExpiredVersion(passedInfo.expiration());
|
|
HistogramInfo info = passedInfo;
|
|
|
|
if (isExpired) {
|
|
if (gExpiredHistogram) {
|
|
return gExpiredHistogram;
|
|
}
|
|
|
|
info.min = 1;
|
|
info.max = 2;
|
|
info.bucketCount = 3;
|
|
info.histogramType = nsITelemetry::HISTOGRAM_LINEAR;
|
|
}
|
|
|
|
Histogram::Flags flags = Histogram::kNoFlags;
|
|
Histogram* h = nullptr;
|
|
switch (info.histogramType) {
|
|
case nsITelemetry::HISTOGRAM_EXPONENTIAL:
|
|
h = Histogram::FactoryGet(info.min, info.max, info.bucketCount, flags,
|
|
&gExponentialBucketLowerBounds[bucketsOffset]);
|
|
break;
|
|
case nsITelemetry::HISTOGRAM_LINEAR:
|
|
case nsITelemetry::HISTOGRAM_CATEGORICAL:
|
|
h = LinearHistogram::FactoryGet(info.min, info.max, info.bucketCount, flags);
|
|
break;
|
|
case nsITelemetry::HISTOGRAM_BOOLEAN:
|
|
h = BooleanHistogram::FactoryGet(flags);
|
|
break;
|
|
case nsITelemetry::HISTOGRAM_FLAG:
|
|
h = FlagHistogram::FactoryGet(flags);
|
|
break;
|
|
case nsITelemetry::HISTOGRAM_COUNT:
|
|
h = CountHistogram::FactoryGet(flags);
|
|
break;
|
|
default:
|
|
MOZ_ASSERT(false, "Invalid histogram type");
|
|
return nullptr;
|
|
}
|
|
|
|
if (isExpired) {
|
|
gExpiredHistogram = h;
|
|
}
|
|
|
|
return h;
|
|
}
|
|
|
|
nsresult
|
|
internal_HistogramAdd(Histogram& histogram,
|
|
const HistogramID id,
|
|
int32_t value,
|
|
ProcessID aProcessType)
|
|
{
|
|
// Check if we are allowed to record the data.
|
|
bool canRecordDataset = CanRecordDataset(gHistogramInfos[id].dataset,
|
|
internal_CanRecordBase(),
|
|
internal_CanRecordExtended());
|
|
// If `histogram` is a non-parent-process histogram, then recording-enabled
|
|
// has been checked in its owner process.
|
|
if (!canRecordDataset ||
|
|
(aProcessType == ProcessID::Parent && !internal_IsRecordingEnabled(id))) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
} // 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)
|
|
{
|
|
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, HistogramID id)
|
|
{
|
|
// Only flag histograms are serialized when they are empty.
|
|
// This has historical reasons, changing this will require downstream changes.
|
|
// The cheaper path here is to just deprecate flag histograms in favor
|
|
// of scalars.
|
|
uint32_t type = gHistogramInfos[id].histogramType;
|
|
if (internal_IsEmpty(h) && type != nsITelemetry::HISTOGRAM_FLAG) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE: class KeyedHistogram
|
|
|
|
namespace {
|
|
|
|
KeyedHistogram::KeyedHistogram(HistogramID id, const HistogramInfo& info)
|
|
: mHistogramMap()
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
, mSubsessionMap()
|
|
#endif
|
|
, mId(id)
|
|
, mHistogramInfo(info)
|
|
{
|
|
}
|
|
|
|
KeyedHistogram::~KeyedHistogram()
|
|
{
|
|
for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) {
|
|
Histogram* h = iter.Get()->mData;
|
|
if (h == gExpiredHistogram) {
|
|
continue;
|
|
}
|
|
delete h;
|
|
}
|
|
mHistogramMap.Clear();
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
for (auto iter = mSubsessionMap.Iter(); !iter.Done(); iter.Next()) {
|
|
Histogram* h = iter.Get()->mData;
|
|
if (h == gExpiredHistogram) {
|
|
continue;
|
|
}
|
|
delete h;
|
|
}
|
|
mSubsessionMap.Clear();
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int bucketsOffset = gExponentialBucketLowerBoundIndex[mId];
|
|
Histogram* h = internal_CreateHistogramInstance(mHistogramInfo, bucketsOffset);
|
|
if (!h) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
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,
|
|
ProcessID aProcessType)
|
|
{
|
|
bool canRecordDataset = CanRecordDataset(mHistogramInfo.dataset,
|
|
internal_CanRecordBase(),
|
|
internal_CanRecordExtended());
|
|
// If `histogram` is a non-parent-process histogram, then recording-enabled
|
|
// has been checked in its owner process.
|
|
if (!canRecordDataset ||
|
|
(aProcessType == ProcessID::Parent && !internal_IsRecordingEnabled(mId))) {
|
|
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()) {
|
|
Histogram* h = iter.Get()->mData;
|
|
if (h == gExpiredHistogram) {
|
|
continue;
|
|
}
|
|
delete h;
|
|
}
|
|
mSubsessionMap.Clear();
|
|
if (onlySubsession) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
for (auto iter = mHistogramMap.Iter(); !iter.Done(); iter.Next()) {
|
|
Histogram* h = iter.Get()->mData;
|
|
if (h == gExpiredHistogram) {
|
|
continue;
|
|
}
|
|
delete h;
|
|
}
|
|
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;
|
|
}
|
|
|
|
} // 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 {
|
|
|
|
bool
|
|
internal_RemoteAccumulate(HistogramID aId, uint32_t aSample)
|
|
{
|
|
if (XRE_IsParentProcess()) {
|
|
return false;
|
|
}
|
|
|
|
if (!internal_IsRecordingEnabled(aId)) {
|
|
return true;
|
|
}
|
|
|
|
TelemetryIPCAccumulator::AccumulateChildHistogram(aId, aSample);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
internal_RemoteAccumulate(HistogramID aId,
|
|
const nsCString& aKey, uint32_t aSample)
|
|
{
|
|
if (XRE_IsParentProcess()) {
|
|
return false;
|
|
}
|
|
|
|
if (!internal_IsRecordingEnabled(aId)) {
|
|
return true;
|
|
}
|
|
|
|
TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(aId, aKey, aSample);
|
|
return true;
|
|
}
|
|
|
|
void internal_Accumulate(HistogramID aId, uint32_t aSample)
|
|
{
|
|
if (!internal_CanRecordBase() ||
|
|
internal_RemoteAccumulate(aId, aSample)) {
|
|
return;
|
|
}
|
|
|
|
Histogram *h = internal_GetHistogramById(aId, ProcessID::Parent, SessionType::Session);
|
|
MOZ_ASSERT(h);
|
|
internal_HistogramAdd(*h, aId, aSample, ProcessID::Parent);
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
h = internal_GetHistogramById(aId, ProcessID::Parent, SessionType::Subsession);
|
|
MOZ_ASSERT(h);
|
|
internal_HistogramAdd(*h, aId, aSample, ProcessID::Parent);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
internal_Accumulate(HistogramID aId,
|
|
const nsCString& aKey, uint32_t aSample)
|
|
{
|
|
if (!gInitDone || !internal_CanRecordBase() ||
|
|
internal_RemoteAccumulate(aId, aKey, aSample)) {
|
|
return;
|
|
}
|
|
|
|
KeyedHistogram* keyed = internal_GetKeyedHistogramById(aId, ProcessID::Parent);
|
|
MOZ_ASSERT(keyed);
|
|
keyed->Add(aKey, aSample, ProcessID::Parent);
|
|
}
|
|
|
|
void
|
|
internal_AccumulateChild(ProcessID aProcessType, HistogramID aId, uint32_t aSample)
|
|
{
|
|
if (!internal_CanRecordBase()) {
|
|
return;
|
|
}
|
|
|
|
if (Histogram* h = internal_GetHistogramById(aId, aProcessType, SessionType::Session)) {
|
|
internal_HistogramAdd(*h, aId, aSample, aProcessType);
|
|
} else {
|
|
NS_WARNING("Failed GetHistogramById for CHILD");
|
|
}
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
if (Histogram* h = internal_GetHistogramById(aId, aProcessType, SessionType::Subsession)) {
|
|
internal_HistogramAdd(*h, aId, aSample, aProcessType);
|
|
} else {
|
|
NS_WARNING("Failed GetHistogramById for CHILD");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
internal_AccumulateChildKeyed(ProcessID aProcessType, HistogramID aId,
|
|
const nsCString& aKey, uint32_t aSample)
|
|
{
|
|
if (!gInitDone || !internal_CanRecordBase()) {
|
|
return;
|
|
}
|
|
|
|
KeyedHistogram* keyed = internal_GetKeyedHistogramById(aId, aProcessType);
|
|
MOZ_ASSERT(keyed);
|
|
keyed->Add(aKey, aSample, aProcessType);
|
|
}
|
|
|
|
void
|
|
internal_ClearHistogram(HistogramID id, bool onlySubsession)
|
|
{
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
if (!XRE_IsParentProcess()) {
|
|
return;
|
|
}
|
|
|
|
// Handle keyed histograms.
|
|
if (gHistogramInfos[id].keyed) {
|
|
for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
|
|
KeyedHistogram* kh = internal_GetKeyedHistogramById(id, static_cast<ProcessID>(process), /* instantiate = */ false);
|
|
if (kh) {
|
|
kh->Clear(onlySubsession);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle plain histograms.
|
|
// Define the session types we want to clear.
|
|
nsTArray<SessionType> sessionTypes;
|
|
if (!onlySubsession) {
|
|
sessionTypes.AppendElement(SessionType::Session);
|
|
}
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
sessionTypes.AppendElement(SessionType::Subsession);
|
|
#endif
|
|
|
|
// Now reset the histograms instances for all processes.
|
|
for (SessionType sessionType : sessionTypes) {
|
|
for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
|
|
internal_ClearHistogramById(id,
|
|
static_cast<ProcessID>(process),
|
|
sessionType);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // 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 {
|
|
|
|
void internal_JSHistogram_finalize(JSFreeOp*, JSObject*);
|
|
|
|
static const JSClassOps sJSHistogramClassOps = {
|
|
nullptr, /* addProperty */
|
|
nullptr, /* delProperty */
|
|
nullptr, /* getProperty */
|
|
nullptr, /* setProperty */
|
|
nullptr, /* enumerate */
|
|
nullptr, /* newEnumerate */
|
|
nullptr, /* resolve */
|
|
nullptr, /* mayResolve */
|
|
internal_JSHistogram_finalize
|
|
};
|
|
|
|
static const JSClass sJSHistogramClass = {
|
|
"JSHistogram", /* name */
|
|
JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE, /* flags */
|
|
&sJSHistogramClassOps
|
|
};
|
|
|
|
struct JSHistogramData {
|
|
HistogramID histogramId;
|
|
};
|
|
|
|
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) {
|
|
JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class");
|
|
return false;
|
|
}
|
|
|
|
JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(data);
|
|
HistogramID id = data->histogramId;
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(id));
|
|
uint32_t type = gHistogramInfos[id].histogramType;
|
|
|
|
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;
|
|
if ((type == nsITelemetry::HISTOGRAM_COUNT) && (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 ((args.length() > 0) && args[0].isString() &&
|
|
gHistogramInfos[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 = gHistogramInfos[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(id, 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) {
|
|
JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class");
|
|
return false;
|
|
}
|
|
|
|
JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(data);
|
|
HistogramID id = data->histogramId;
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(id));
|
|
|
|
// This is not good standard behavior given that we have histogram instances
|
|
// covering multiple processes and two session types.
|
|
// However, changing this requires some broader changes to callers.
|
|
Histogram* h = internal_GetHistogramById(id, ProcessID::Parent, SessionType::Session);
|
|
MOZ_ASSERT(h);
|
|
|
|
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_OK:
|
|
args.rval().setObject(*snapshot);
|
|
return true;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unhandled reflection status.");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
internal_JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
{
|
|
JSObject *obj = JS_THIS_OBJECT(cx, vp);
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSHistogramClass) {
|
|
JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class");
|
|
return false;
|
|
}
|
|
|
|
JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(data);
|
|
HistogramID id = data->histogramId;
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(id));
|
|
|
|
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
|
|
|
|
internal_ClearHistogram(id, onlySubsession);
|
|
|
|
return true;
|
|
}
|
|
|
|
// NOTE: Runs without protection from |gTelemetryHistogramMutex|.
|
|
// See comment at the top of this section.
|
|
nsresult
|
|
internal_WrapAndReturnHistogram(HistogramID id, 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;
|
|
}
|
|
|
|
JSHistogramData* data = new JSHistogramData{id};
|
|
JS_SetPrivate(obj, data);
|
|
ret.setObject(*obj);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
internal_JSHistogram_finalize(JSFreeOp*, JSObject* obj)
|
|
{
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSHistogramClass) {
|
|
MOZ_ASSERT_UNREACHABLE("Should have the right JS class.");
|
|
return;
|
|
}
|
|
|
|
JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(data);
|
|
delete data;
|
|
}
|
|
|
|
} // 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 {
|
|
|
|
void internal_JSKeyedHistogram_finalize(JSFreeOp*, JSObject*);
|
|
|
|
static const JSClassOps sJSKeyedHistogramClassOps = {
|
|
nullptr, /* addProperty */
|
|
nullptr, /* delProperty */
|
|
nullptr, /* getProperty */
|
|
nullptr, /* setProperty */
|
|
nullptr, /* enumerate */
|
|
nullptr, /* newEnumerate */
|
|
nullptr, /* resolve */
|
|
nullptr, /* mayResolve */
|
|
internal_JSKeyedHistogram_finalize
|
|
};
|
|
|
|
static const JSClass sJSKeyedHistogramClass = {
|
|
"JSKeyedHistogram", /* name */
|
|
JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE, /* flags */
|
|
&sJSKeyedHistogramClassOps
|
|
};
|
|
|
|
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) {
|
|
JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class");
|
|
return false;
|
|
}
|
|
|
|
JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(data);
|
|
HistogramID id = data->histogramId;
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(id));
|
|
|
|
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();
|
|
|
|
// This is not good standard behavior given that we have histogram instances
|
|
// covering multiple processes and two session types.
|
|
// However, changing this requires some broader changes to callers.
|
|
KeyedHistogram* keyed = internal_GetKeyedHistogramById(id, ProcessID::Parent, /* instantiate = */ true);
|
|
if (!keyed) {
|
|
JS_ReportErrorASCII(cx, "Failed to look up keyed histogram");
|
|
return false;
|
|
}
|
|
|
|
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:
|
|
JS_ReportErrorASCII(cx, "Failed to reflect histogram");
|
|
return false;
|
|
case REFLECT_OK:
|
|
args.rval().setObject(*snapshot);
|
|
return true;
|
|
default:
|
|
MOZ_CRASH("unhandled reflection status");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
internal_JSKeyedHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
|
|
{
|
|
JSObject *obj = JS_THIS_OBJECT(cx, vp);
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSKeyedHistogramClass) {
|
|
JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class");
|
|
return false;
|
|
}
|
|
|
|
JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(data);
|
|
HistogramID id = data->histogramId;
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(id));
|
|
|
|
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 = gHistogramInfos[id].histogramType;
|
|
|
|
// 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.
|
|
|
|
// 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 = gHistogramInfos[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;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal_Accumulate(id, 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) {
|
|
JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class");
|
|
return false;
|
|
}
|
|
|
|
JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(data);
|
|
HistogramID id = data->histogramId;
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(id));
|
|
|
|
// This is not good standard behavior given that we have histogram instances
|
|
// covering multiple processes and two session types.
|
|
// However, changing this requires some broader changes to callers.
|
|
KeyedHistogram* keyed = internal_GetKeyedHistogramById(id, ProcessID::Parent);
|
|
MOZ_ASSERT(keyed);
|
|
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) {
|
|
JS_ReportErrorASCII(cx, "Wrong JS class, expected JSKeyedHistogram class");
|
|
return false;
|
|
}
|
|
|
|
JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(data);
|
|
HistogramID id = data->histogramId;
|
|
MOZ_ASSERT(internal_IsHistogramEnumId(id));
|
|
|
|
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();
|
|
|
|
// This is not good standard behavior given that we have histogram instances
|
|
// covering multiple processes and two session types.
|
|
// However, changing this requires some broader changes to callers.
|
|
KeyedHistogram* keyed = internal_GetKeyedHistogramById(id, ProcessID::Parent, /* instantiate = */ false);
|
|
if (!keyed) {
|
|
return true;
|
|
}
|
|
|
|
#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(HistogramID id, 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;
|
|
}
|
|
|
|
JSHistogramData* data = new JSHistogramData{id};
|
|
JS_SetPrivate(obj, data);
|
|
ret.setObject(*obj);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
internal_JSKeyedHistogram_finalize(JSFreeOp*, JSObject* obj)
|
|
{
|
|
if (!obj ||
|
|
JS_GetClass(obj) != &sJSKeyedHistogramClass) {
|
|
MOZ_ASSERT_UNREACHABLE("Should have the right JS class.");
|
|
return;
|
|
}
|
|
|
|
JSHistogramData* data = static_cast<JSHistogramData*>(JS_GetPrivate(obj));
|
|
MOZ_ASSERT(data);
|
|
delete data;
|
|
}
|
|
|
|
} // 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.
|
|
|
|
void TelemetryHistogram::InitializeGlobalState(bool canRecordBase,
|
|
bool canRecordExtended)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
MOZ_ASSERT(!gInitDone, "TelemetryHistogram::InitializeGlobalState "
|
|
"may only be called once");
|
|
|
|
gCanRecordBase = canRecordBase;
|
|
gCanRecordExtended = canRecordExtended;
|
|
|
|
// gNameToHistogramIDMap 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 < HistogramCount; i++) {
|
|
gNameToHistogramIDMap.Put(nsDependentCString(gHistogramInfos[i].name()), HistogramID(i));
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
gNameToHistogramIDMap.MarkImmutable();
|
|
#endif
|
|
|
|
// 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) ==
|
|
gHistogramInfos[mozilla::Telemetry::GC_MINOR_REASON].bucketCount &&
|
|
(JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
|
|
gHistogramInfos[mozilla::Telemetry::GC_MINOR_REASON_LONG].bucketCount &&
|
|
(JS::gcreason::NUM_TELEMETRY_REASONS + 1) ==
|
|
gHistogramInfos[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) ==
|
|
gHistogramInfos[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;
|
|
gNameToHistogramIDMap.Clear();
|
|
gInitDone = false;
|
|
|
|
// FactoryGet `new`s Histograms for us, but requires us to manually delete.
|
|
for (size_t i = 0; i < HistogramCount; ++i) {
|
|
for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
|
|
delete gKeyedHistogramStorage[i][process];
|
|
gKeyedHistogramStorage[i][process] = nullptr;
|
|
for (uint32_t session = 0; session <
|
|
static_cast<uint32_t>(SessionType::Count); ++session) {
|
|
if (gHistogramStorage[i][process][session] == gExpiredHistogram) {
|
|
continue;
|
|
}
|
|
delete gHistogramStorage[i][process][session];
|
|
gHistogramStorage[i][process][session] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
delete gExpiredHistogram;
|
|
gExpiredHistogram = nullptr;
|
|
}
|
|
|
|
#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 < HistogramCount; ++i) {
|
|
const HistogramInfo& h = gHistogramInfos[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(HistogramID aID,
|
|
bool aEnabled)
|
|
{
|
|
if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) {
|
|
MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
|
|
return;
|
|
}
|
|
|
|
const HistogramInfo& h = gHistogramInfos[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& name,
|
|
bool aEnabled)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
HistogramID id;
|
|
if (NS_FAILED(internal_GetHistogramIdByName(name, &id))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
const HistogramInfo& hi = gHistogramInfos[id];
|
|
if (CanRecordInProcess(hi.record_in_processes, XRE_GetProcessType())) {
|
|
internal_SetHistogramRecordingEnabled(id, aEnabled);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
void
|
|
TelemetryHistogram::Accumulate(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(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;
|
|
}
|
|
HistogramID id;
|
|
nsresult rv = internal_GetHistogramIdByName(nsDependentCString(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;
|
|
}
|
|
HistogramID id;
|
|
nsresult rv = internal_GetHistogramIdByName(nsDependentCString(name), &id);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
internal_Accumulate(id, key, sample);
|
|
}
|
|
}
|
|
|
|
void
|
|
TelemetryHistogram::AccumulateCategorical(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(gHistogramInfos[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)
|
|
{
|
|
HistogramID id;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
nsresult rv = internal_GetHistogramIdByName(name, &id);
|
|
if (NS_FAILED(rv)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (gHistogramInfos[id].keyed) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
// Runs without protection from |gTelemetryHistogramMutex|
|
|
return internal_WrapAndReturnHistogram(id, cx, ret);
|
|
}
|
|
|
|
nsresult
|
|
TelemetryHistogram::GetKeyedHistogramById(const nsACString &name,
|
|
JSContext *cx,
|
|
JS::MutableHandle<JS::Value> ret)
|
|
{
|
|
HistogramID id;
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
nsresult rv = internal_GetHistogramIdByName(name, &id);
|
|
if (NS_FAILED(rv)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (!gHistogramInfos[id].keyed) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
// Runs without protection from |gTelemetryHistogramMutex|
|
|
return internal_WrapAndReturnKeyedHistogram(id, cx, ret);
|
|
}
|
|
|
|
const char*
|
|
TelemetryHistogram::GetHistogramName(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 = gHistogramInfos[id];
|
|
return h.name();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
SessionType sessionType = SessionType(subsession);
|
|
#else
|
|
SessionType sessionType = SessionType::Session;
|
|
#endif
|
|
|
|
for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
|
|
JS::Rooted<JSObject*> processObject(cx, JS_NewPlainObject(cx));
|
|
if (!processObject) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
if (!JS_DefineProperty(cx, root_obj, GetNameForProcessID(ProcessID(process)),
|
|
processObject, JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
for (size_t i = 0; i < HistogramCount; ++i) {
|
|
const HistogramInfo& info = gHistogramInfos[i];
|
|
if (info.keyed) {
|
|
continue;
|
|
}
|
|
|
|
HistogramID id = HistogramID(i);
|
|
|
|
if (!CanRecordInProcess(info.record_in_processes, ProcessID(process)) ||
|
|
((ProcessID(process) == ProcessID::Gpu) && !includeGPUProcess)) {
|
|
continue;
|
|
}
|
|
|
|
bool shouldInstantiate =
|
|
info.histogramType == nsITelemetry::HISTOGRAM_FLAG;
|
|
Histogram* h = internal_GetHistogramById(id, ProcessID(process),
|
|
sessionType,
|
|
shouldInstantiate);
|
|
if (!h || internal_IsExpired(h) || !internal_ShouldReflectHistogram(h, id)) {
|
|
continue;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> hobj(cx, JS_NewPlainObject(cx));
|
|
if (!hobj) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
switch (internal_ReflectHistogramSnapshot(cx, hobj, h)) {
|
|
case REFLECT_FAILURE:
|
|
return NS_ERROR_FAILURE;
|
|
case REFLECT_OK:
|
|
if (!JS_DefineProperty(cx, processObject, gHistogramInfos[id].name(),
|
|
hobj, JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
if ((sessionType == SessionType::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,
|
|
bool subsession,
|
|
bool clearSubsession)
|
|
{
|
|
// Runs without protection from |gTelemetryHistogramMutex|
|
|
JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
|
|
if (!obj) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
ret.setObject(*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();
|
|
}
|
|
|
|
for (uint32_t process = 0; process < static_cast<uint32_t>(ProcessID::Count); ++process) {
|
|
JS::Rooted<JSObject*> processObject(cx, JS_NewPlainObject(cx));
|
|
if (!processObject) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
if (!JS_DefineProperty(cx, obj, GetNameForProcessID(ProcessID(process)),
|
|
processObject, JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
for (size_t id = 0; id < HistogramCount; ++id) {
|
|
const HistogramInfo& info = gHistogramInfos[id];
|
|
if (!info.keyed) {
|
|
continue;
|
|
}
|
|
|
|
if (!CanRecordInProcess(info.record_in_processes, ProcessID(process)) ||
|
|
((ProcessID(process) == ProcessID::Gpu) && !includeGPUProcess)) {
|
|
continue;
|
|
}
|
|
|
|
KeyedHistogram* keyed = internal_GetKeyedHistogramById(HistogramID(id),
|
|
ProcessID(process),
|
|
/* instantiate = */ false);
|
|
if (!keyed) {
|
|
continue;
|
|
}
|
|
|
|
JS::RootedObject snapshot(cx, JS_NewPlainObject(cx));
|
|
if (!snapshot) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot, subsession,
|
|
clearSubsession))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (!JS_DefineProperty(cx, processObject, gHistogramInfos[id].name(),
|
|
snapshot, JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
size_t
|
|
TelemetryHistogram::GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf
|
|
aMallocSizeOf)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
// TODO
|
|
return 0;
|
|
}
|
|
|
|
size_t
|
|
TelemetryHistogram::GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf
|
|
aMallocSizeOf)
|
|
{
|
|
StaticMutexAutoLock locker(gTelemetryHistogramMutex);
|
|
// TODO
|
|
return 0;
|
|
}
|