1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "TelemetryHistogram.h"
10 #include "base/histogram.h"
11 #include "ipc/TelemetryIPCAccumulator.h"
13 #include "jsfriendapi.h"
14 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject
16 #include "js/Object.h" // JS::GetClass, JS::GetMaybePtrFromReservedSlot, JS::SetReservedSlot
17 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineFunction, JS_DefineProperty, JS_DefineUCProperty, JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById
18 #include "mozilla/dom/ToJSValue.h"
19 #include "mozilla/gfx/GPUProcessManager.h"
20 #include "mozilla/Atomics.h"
21 #include "mozilla/JSONWriter.h"
22 #include "mozilla/StartupTimeline.h"
23 #include "mozilla/StaticMutex.h"
24 #include "mozilla/Unused.h"
25 #include "nsClassHashtable.h"
27 #include "nsHashKeys.h"
28 #include "nsITelemetry.h"
29 #include "nsPrintfCString.h"
30 #include "TelemetryHistogramNameMap.h"
31 #include "TelemetryScalar.h"
33 using base::BooleanHistogram
;
34 using base::CountHistogram
;
35 using base::FlagHistogram
;
36 using base::LinearHistogram
;
37 using mozilla::MakeUnique
;
38 using mozilla::StaticMutex
;
39 using mozilla::StaticMutexAutoLock
;
40 using mozilla::UniquePtr
;
41 using mozilla::Telemetry::HistogramAccumulation
;
42 using mozilla::Telemetry::HistogramCount
;
43 using mozilla::Telemetry::HistogramID
;
44 using mozilla::Telemetry::HistogramIDByNameLookup
;
45 using mozilla::Telemetry::KeyedHistogramAccumulation
;
46 using mozilla::Telemetry::ProcessID
;
47 using mozilla::Telemetry::Common::CanRecordDataset
;
48 using mozilla::Telemetry::Common::CanRecordProduct
;
49 using mozilla::Telemetry::Common::GetCurrentProduct
;
50 using mozilla::Telemetry::Common::GetIDForProcessName
;
51 using mozilla::Telemetry::Common::GetNameForProcessID
;
52 using mozilla::Telemetry::Common::IsExpiredVersion
;
53 using mozilla::Telemetry::Common::IsInDataset
;
54 using mozilla::Telemetry::Common::LogToBrowserConsole
;
55 using mozilla::Telemetry::Common::RecordedProcessType
;
56 using mozilla::Telemetry::Common::StringHashSet
;
57 using mozilla::Telemetry::Common::SupportedProduct
;
58 using mozilla::Telemetry::Common::ToJSString
;
60 namespace TelemetryIPCAccumulator
= mozilla::TelemetryIPCAccumulator
;
62 ////////////////////////////////////////////////////////////////////////
63 ////////////////////////////////////////////////////////////////////////
65 // Naming: there are two kinds of functions in this file:
67 // * Functions named internal_*: these can only be reached via an
68 // interface function (TelemetryHistogram::*). They mostly expect
69 // the interface function to have acquired
70 // |gTelemetryHistogramMutex|, so they do not have to be
71 // thread-safe. However, those internal_* functions that are
72 // reachable from internal_WrapAndReturnHistogram and
73 // internal_WrapAndReturnKeyedHistogram can sometimes be called
74 // without |gTelemetryHistogramMutex|, and so might be racey.
76 // * Functions named TelemetryHistogram::*. This is the external interface.
77 // Entries and exits to these functions are serialised using
78 // |gTelemetryHistogramMutex|, except for GetKeyedHistogramSnapshots and
79 // CreateHistogramSnapshots.
81 // Avoiding races and deadlocks:
83 // All functions in the external interface (TelemetryHistogram::*) are
84 // serialised using the mutex |gTelemetryHistogramMutex|. This means
85 // that the external interface is thread-safe, and many of the
86 // internal_* functions can ignore thread safety. But it also brings
87 // a danger of deadlock if any function in the external interface can
88 // get back to that interface. That is, we will deadlock on any call
91 // TelemetryHistogram::* -> .. any functions .. -> TelemetryHistogram::*
93 // To reduce the danger of that happening, observe the following rules:
95 // * No function in TelemetryHistogram::* may directly call, nor take the
96 // address of, any other function in TelemetryHistogram::*.
98 // * No internal function internal_* may call, nor take the address
99 // of, any function in TelemetryHistogram::*.
101 // internal_WrapAndReturnHistogram and
102 // internal_WrapAndReturnKeyedHistogram are not protected by
103 // |gTelemetryHistogramMutex| because they make calls to the JS
104 // engine, but that can in turn call back to Telemetry and hence back
105 // to a TelemetryHistogram:: function, in order to report GC and other
106 // statistics. This would lead to deadlock due to attempted double
107 // acquisition of |gTelemetryHistogramMutex|, if the internal_* functions
108 // were required to be protected by |gTelemetryHistogramMutex|. To
109 // break that cycle, we relax that requirement. Unfortunately this
110 // means that this file is not guaranteed race-free.
112 // This is a StaticMutex rather than a plain Mutex (1) so that
113 // it gets initialised in a thread-safe manner the first time
114 // it is used, and (2) because it is never de-initialised, and
115 // a normal Mutex would show up as a leak in BloatView. StaticMutex
116 // also has the "OffTheBooks" property, so it won't show as a leak
118 static StaticMutex gTelemetryHistogramMutex MOZ_UNANNOTATED
;
120 ////////////////////////////////////////////////////////////////////////
121 ////////////////////////////////////////////////////////////////////////
129 // The order of elements here is important to minimize the memory footprint of a
130 // HistogramInfo instance.
132 // Any adjustements need to be reflected in gen_histogram_data.py
133 struct HistogramInfo
{
136 uint32_t bucketCount
;
137 uint32_t name_offset
;
138 uint32_t expiration_offset
;
139 uint32_t label_count
;
141 uint32_t store_count
;
142 uint16_t label_index
;
144 uint16_t store_index
;
145 RecordedProcessType record_in_processes
;
147 uint8_t histogramType
;
149 SupportedProduct products
;
151 const char* name() const;
152 const char* expiration() const;
153 nsresult
label_id(const char* label
, uint32_t* labelId
) const;
154 bool allows_key(const nsACString
& key
) const;
155 bool is_single_store() const;
158 // Structs used to keep information about the histograms for which a
159 // snapshot should be created.
160 struct HistogramSnapshotData
{
161 CopyableTArray
<base::Histogram::Sample
> mBucketRanges
;
162 CopyableTArray
<base::Histogram::Count
> mBucketCounts
;
163 int64_t mSampleSum
; // Same type as base::Histogram::SampleSet::sum_
166 struct HistogramSnapshotInfo
{
167 HistogramSnapshotData data
;
168 HistogramID histogramID
;
171 typedef mozilla::Vector
<HistogramSnapshotInfo
> HistogramSnapshotsArray
;
172 typedef mozilla::Vector
<HistogramSnapshotsArray
> HistogramProcessSnapshotsArray
;
174 // The following is used to handle snapshot information for keyed histograms.
175 typedef nsTHashMap
<nsCStringHashKey
, HistogramSnapshotData
>
176 KeyedHistogramSnapshotData
;
178 struct KeyedHistogramSnapshotInfo
{
179 KeyedHistogramSnapshotData data
;
180 HistogramID histogramId
;
183 typedef mozilla::Vector
<KeyedHistogramSnapshotInfo
>
184 KeyedHistogramSnapshotsArray
;
185 typedef mozilla::Vector
<KeyedHistogramSnapshotsArray
>
186 KeyedHistogramProcessSnapshotsArray
;
189 * A Histogram storage engine.
191 * Takes care of recording data into multiple stores if necessary.
196 * Create a new histogram instance from the given info.
198 * If the histogram is already expired, this does not allocate.
200 Histogram(HistogramID histogramId
, const HistogramInfo
& info
, bool expired
);
204 * Add a sample to this histogram in all registered stores.
206 void Add(uint32_t sample
);
209 * Clear the named store for this histogram.
211 void Clear(const nsACString
& store
);
214 * Get the histogram instance from the named store.
216 bool GetHistogram(const nsACString
& store
, base::Histogram
** h
);
218 bool IsExpired() const {
220 PROFILER_MARKER_TEXT("HistogramError", TELEMETRY
,
221 mozilla::MarkerStack::Capture(),
222 "accessing expired histogram");
227 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
);
230 // String -> Histogram*
231 typedef nsClassHashtable
<nsCStringHashKey
, base::Histogram
> HistogramStore
;
232 HistogramStore mStorage
;
234 // A valid pointer if this histogram belongs to only the main store
235 base::Histogram
* mSingleStore
;
237 // We don't track stores for expired histograms.
238 // We just store a single flag and all other operations become a no-op.
242 class KeyedHistogram
{
244 KeyedHistogram(HistogramID id
, const HistogramInfo
& info
, bool expired
);
246 nsresult
GetHistogram(const nsCString
& aStore
, const nsCString
& key
,
247 base::Histogram
** histogram
);
248 base::Histogram
* GetHistogram(const nsCString
& aStore
, const nsCString
& key
);
249 uint32_t GetHistogramType() const { return mHistogramInfo
.histogramType
; }
250 nsresult
GetKeys(const StaticMutexAutoLock
& aLock
, const nsCString
& store
,
251 nsTArray
<nsCString
>& aKeys
);
252 // Note: unlike other methods, GetJSSnapshot is thread safe.
253 nsresult
GetJSSnapshot(JSContext
* cx
, JS::Handle
<JSObject
*> obj
,
254 const nsACString
& aStore
, bool clearSubsession
);
255 nsresult
GetSnapshot(const StaticMutexAutoLock
& aLock
,
256 const nsACString
& aStore
,
257 KeyedHistogramSnapshotData
& aSnapshot
,
258 bool aClearSubsession
);
260 nsresult
Add(const nsCString
& key
, uint32_t aSample
, ProcessID aProcessType
);
261 void Clear(const nsACString
& aStore
);
263 HistogramID
GetHistogramID() const { return mId
; }
265 bool IsEmpty(const nsACString
& aStore
) const;
267 bool IsExpired() const {
269 PROFILER_MARKER_TEXT("HistogramError", TELEMETRY
,
270 mozilla::MarkerStack::Capture(),
271 "accessing expired histogram");
276 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
);
279 typedef nsClassHashtable
<nsCStringHashKey
, base::Histogram
>
280 KeyedHistogramMapType
;
281 typedef nsClassHashtable
<nsCStringHashKey
, KeyedHistogramMapType
>
284 StoreMapType mStorage
;
285 // A valid map if this histogram belongs to only the main store
286 KeyedHistogramMapType
* mSingleStore
;
288 const HistogramID mId
;
289 const HistogramInfo
& mHistogramInfo
;
295 ////////////////////////////////////////////////////////////////////////
296 ////////////////////////////////////////////////////////////////////////
298 // PRIVATE STATE, SHARED BY ALL THREADS
302 // Set to true once this global state has been initialized
303 bool gTelemetryHistogramInitDone
= false;
305 // Whether we are collecting the base, opt-out, Histogram data.
306 bool gTelemetryHistogramCanRecordBase
= false;
307 // Whether we are collecting the extended, opt-in, Histogram data.
308 bool gTelemetryHistogramCanRecordExtended
= false;
310 // The storage for actual Histogram instances.
311 // We use separate ones for plain and keyed histograms.
312 Histogram
** gHistogramStorage
;
313 // Keyed histograms internally map string keys to individual Histogram
315 KeyedHistogram
** gKeyedHistogramStorage
;
317 // To simplify logic below we use a single histogram instance for all expired
319 Histogram
* gExpiredHistogram
= nullptr;
321 // The single placeholder for expired keyed histograms.
322 KeyedHistogram
* gExpiredKeyedHistogram
= nullptr;
324 // This tracks whether recording is enabled for specific histograms.
325 // To utilize C++ initialization rules, we invert the meaning to "disabled".
326 bool gHistogramRecordingDisabled
[HistogramCount
] = {};
328 // This is for gHistogramInfos, gHistogramStringTable
329 #include "TelemetryHistogramData.inc"
333 ////////////////////////////////////////////////////////////////////////
334 ////////////////////////////////////////////////////////////////////////
340 const char* TEST_HISTOGRAM_PREFIX
= "TELEMETRY_TEST_";
344 ////////////////////////////////////////////////////////////////////////
345 ////////////////////////////////////////////////////////////////////////
347 // The core storage access functions.
348 // They wrap access to the histogram storage and lookup caches.
352 size_t internal_KeyedHistogramStorageIndex(HistogramID aHistogramId
,
353 ProcessID aProcessId
) {
354 return aHistogramId
* size_t(ProcessID::Count
) + size_t(aProcessId
);
357 size_t internal_HistogramStorageIndex(const StaticMutexAutoLock
& aLock
,
358 HistogramID aHistogramId
,
359 ProcessID aProcessId
) {
360 static_assert(HistogramCount
< std::numeric_limits
<size_t>::max() /
361 size_t(ProcessID::Count
),
362 "Too many histograms and processes to store in a 1D array.");
364 return aHistogramId
* size_t(ProcessID::Count
) + size_t(aProcessId
);
367 Histogram
* internal_GetHistogramFromStorage(const StaticMutexAutoLock
& aLock
,
368 HistogramID aHistogramId
,
369 ProcessID aProcessId
) {
371 internal_HistogramStorageIndex(aLock
, aHistogramId
, aProcessId
);
372 return gHistogramStorage
[index
];
375 void internal_SetHistogramInStorage(const StaticMutexAutoLock
& aLock
,
376 HistogramID aHistogramId
,
377 ProcessID aProcessId
,
378 Histogram
* aHistogram
) {
379 MOZ_ASSERT(XRE_IsParentProcess(),
380 "Histograms are stored only in the parent process.");
383 internal_HistogramStorageIndex(aLock
, aHistogramId
, aProcessId
);
384 MOZ_ASSERT(!gHistogramStorage
[index
],
385 "Mustn't overwrite storage without clearing it first.");
386 gHistogramStorage
[index
] = aHistogram
;
389 KeyedHistogram
* internal_GetKeyedHistogramFromStorage(HistogramID aHistogramId
,
390 ProcessID aProcessId
) {
391 size_t index
= internal_KeyedHistogramStorageIndex(aHistogramId
, aProcessId
);
392 return gKeyedHistogramStorage
[index
];
395 void internal_SetKeyedHistogramInStorage(HistogramID aHistogramId
,
396 ProcessID aProcessId
,
397 KeyedHistogram
* aKeyedHistogram
) {
398 MOZ_ASSERT(XRE_IsParentProcess(),
399 "Keyed Histograms are stored only in the parent process.");
401 size_t index
= internal_KeyedHistogramStorageIndex(aHistogramId
, aProcessId
);
402 MOZ_ASSERT(!gKeyedHistogramStorage
[index
],
403 "Mustn't overwrite storage without clearing it first");
404 gKeyedHistogramStorage
[index
] = aKeyedHistogram
;
407 // Factory function for base::Histogram instances.
408 base::Histogram
* internal_CreateBaseHistogramInstance(const HistogramInfo
& info
,
411 // Factory function for histogram instances.
412 Histogram
* internal_CreateHistogramInstance(HistogramID histogramId
);
414 bool internal_IsHistogramEnumId(HistogramID aID
) {
415 static_assert(((HistogramID
)-1 > 0), "ID should be unsigned.");
416 return aID
< HistogramCount
;
419 // Look up a plain histogram by id.
420 Histogram
* internal_GetHistogramById(const StaticMutexAutoLock
& aLock
,
421 HistogramID histogramId
,
423 bool instantiate
= true) {
424 MOZ_ASSERT(internal_IsHistogramEnumId(histogramId
));
425 MOZ_ASSERT(!gHistogramInfos
[histogramId
].keyed
);
426 MOZ_ASSERT(processId
< ProcessID::Count
);
429 internal_GetHistogramFromStorage(aLock
, histogramId
, processId
);
430 if (h
|| !instantiate
) {
434 h
= internal_CreateHistogramInstance(histogramId
);
436 internal_SetHistogramInStorage(aLock
, histogramId
, processId
, h
);
441 // Look up a keyed histogram by id.
442 KeyedHistogram
* internal_GetKeyedHistogramById(HistogramID histogramId
,
444 bool instantiate
= true) {
445 MOZ_ASSERT(internal_IsHistogramEnumId(histogramId
));
446 MOZ_ASSERT(gHistogramInfos
[histogramId
].keyed
);
447 MOZ_ASSERT(processId
< ProcessID::Count
);
450 internal_GetKeyedHistogramFromStorage(histogramId
, processId
);
451 if (kh
|| !instantiate
) {
455 const HistogramInfo
& info
= gHistogramInfos
[histogramId
];
456 const bool isExpired
= IsExpiredVersion(info
.expiration());
458 // If the keyed histogram is expired, set its storage to the expired
461 if (!gExpiredKeyedHistogram
) {
462 // If we don't have an expired keyed histogram, create one.
463 gExpiredKeyedHistogram
=
464 new KeyedHistogram(histogramId
, info
, true /* expired */);
465 MOZ_ASSERT(gExpiredKeyedHistogram
);
467 kh
= gExpiredKeyedHistogram
;
469 kh
= new KeyedHistogram(histogramId
, info
, false /* expired */);
472 internal_SetKeyedHistogramInStorage(histogramId
, processId
, kh
);
477 // Look up a histogram id from a histogram name.
478 nsresult
internal_GetHistogramIdByName(const StaticMutexAutoLock
& aLock
,
479 const nsACString
& name
,
481 const uint32_t idx
= HistogramIDByNameLookup(name
);
482 MOZ_ASSERT(idx
< HistogramCount
,
483 "Intermediate lookup should always give a valid index.");
485 // The lookup hashes the input and uses it as an index into the value array.
486 // Hash collisions can still happen for unknown values,
487 // therefore we check that the name matches.
488 if (name
.Equals(gHistogramInfos
[idx
].name())) {
489 *id
= HistogramID(idx
);
493 return NS_ERROR_ILLEGAL_VALUE
;
498 ////////////////////////////////////////////////////////////////////////
499 ////////////////////////////////////////////////////////////////////////
501 // PRIVATE: Misc small helpers
505 bool internal_CanRecordBase() { return gTelemetryHistogramCanRecordBase
; }
507 bool internal_CanRecordExtended() {
508 return gTelemetryHistogramCanRecordExtended
;
511 bool internal_AttemptedGPUProcess() {
512 // Check if it was tried to launch a process.
513 bool attemptedGPUProcess
= false;
514 if (auto gpm
= mozilla::gfx::GPUProcessManager::Get()) {
515 attemptedGPUProcess
= gpm
->AttemptedGPUProcess();
517 return attemptedGPUProcess
;
520 // Note: this is completely unrelated to mozilla::IsEmpty.
521 bool internal_IsEmpty(const StaticMutexAutoLock
& aLock
,
522 const base::Histogram
* h
) {
523 return h
->is_empty();
526 void internal_SetHistogramRecordingEnabled(const StaticMutexAutoLock
& aLock
,
527 HistogramID id
, bool aEnabled
) {
528 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
529 gHistogramRecordingDisabled
[id
] = !aEnabled
;
532 bool internal_IsRecordingEnabled(HistogramID id
) {
533 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
534 return !gHistogramRecordingDisabled
[id
];
537 const char* HistogramInfo::name() const {
538 return &gHistogramStringTable
[this->name_offset
];
541 const char* HistogramInfo::expiration() const {
542 return &gHistogramStringTable
[this->expiration_offset
];
545 nsresult
HistogramInfo::label_id(const char* label
, uint32_t* labelId
) const {
547 MOZ_ASSERT(this->histogramType
== nsITelemetry::HISTOGRAM_CATEGORICAL
);
548 if (this->histogramType
!= nsITelemetry::HISTOGRAM_CATEGORICAL
) {
549 return NS_ERROR_FAILURE
;
552 for (uint32_t i
= 0; i
< this->label_count
; ++i
) {
553 // gHistogramLabelTable contains the indices of the label strings in the
554 // gHistogramStringTable.
555 // They are stored in-order and consecutively, from the offset label_index
556 // to (label_index + label_count).
557 uint32_t string_offset
= gHistogramLabelTable
[this->label_index
+ i
];
558 const char* const str
= &gHistogramStringTable
[string_offset
];
559 if (::strcmp(label
, str
) == 0) {
565 return NS_ERROR_FAILURE
;
568 bool HistogramInfo::allows_key(const nsACString
& key
) const {
569 MOZ_ASSERT(this->keyed
);
571 // If we didn't specify a list of allowed keys, just return true.
572 if (this->key_count
== 0) {
576 // Otherwise, check if |key| is in the list of allowed keys.
577 for (uint32_t i
= 0; i
< this->key_count
; ++i
) {
578 // gHistogramKeyTable contains the indices of the key strings in the
579 // gHistogramStringTable. They are stored in-order and consecutively,
580 // from the offset key_index to (key_index + key_count).
581 uint32_t string_offset
= gHistogramKeyTable
[this->key_index
+ i
];
582 const char* const str
= &gHistogramStringTable
[string_offset
];
583 if (key
.EqualsASCII(str
)) {
588 // |key| was not found.
592 bool HistogramInfo::is_single_store() const {
593 return store_count
== 1 && store_index
== UINT16_MAX
;
598 ////////////////////////////////////////////////////////////////////////
599 ////////////////////////////////////////////////////////////////////////
601 // PRIVATE: Histogram Get, Add, Clone, Clear functions
605 nsresult
internal_CheckHistogramArguments(const HistogramInfo
& info
) {
606 if (info
.histogramType
!= nsITelemetry::HISTOGRAM_BOOLEAN
&&
607 info
.histogramType
!= nsITelemetry::HISTOGRAM_FLAG
&&
608 info
.histogramType
!= nsITelemetry::HISTOGRAM_COUNT
) {
609 // Sanity checks for histogram parameters.
610 if (info
.min
>= info
.max
) {
611 return NS_ERROR_ILLEGAL_VALUE
;
614 if (info
.bucketCount
<= 2) {
615 return NS_ERROR_ILLEGAL_VALUE
;
619 return NS_ERROR_ILLEGAL_VALUE
;
626 Histogram
* internal_CreateHistogramInstance(HistogramID histogramId
) {
627 const HistogramInfo
& info
= gHistogramInfos
[histogramId
];
629 if (NS_FAILED(internal_CheckHistogramArguments(info
))) {
630 MOZ_ASSERT(false, "Failed histogram argument checks.");
634 const bool isExpired
= IsExpiredVersion(info
.expiration());
637 if (!gExpiredHistogram
) {
638 gExpiredHistogram
= new Histogram(histogramId
, info
, /* expired */ true);
641 return gExpiredHistogram
;
644 Histogram
* wrapper
= new Histogram(histogramId
, info
, /* expired */ false);
649 base::Histogram
* internal_CreateBaseHistogramInstance(
650 const HistogramInfo
& passedInfo
, int bucketsOffset
) {
651 if (NS_FAILED(internal_CheckHistogramArguments(passedInfo
))) {
652 MOZ_ASSERT(false, "Failed histogram argument checks.");
656 // We don't actually store data for expired histograms at all.
657 MOZ_ASSERT(!IsExpiredVersion(passedInfo
.expiration()));
659 HistogramInfo info
= passedInfo
;
660 const int* buckets
= &gHistogramBucketLowerBounds
[bucketsOffset
];
662 base::Histogram::Flags flags
= base::Histogram::kNoFlags
;
663 base::Histogram
* h
= nullptr;
664 switch (info
.histogramType
) {
665 case nsITelemetry::HISTOGRAM_EXPONENTIAL
:
666 h
= base::Histogram::FactoryGet(info
.min
, info
.max
, info
.bucketCount
,
669 case nsITelemetry::HISTOGRAM_LINEAR
:
670 case nsITelemetry::HISTOGRAM_CATEGORICAL
:
671 h
= LinearHistogram::FactoryGet(info
.min
, info
.max
, info
.bucketCount
,
674 case nsITelemetry::HISTOGRAM_BOOLEAN
:
675 h
= BooleanHistogram::FactoryGet(flags
, buckets
);
677 case nsITelemetry::HISTOGRAM_FLAG
:
678 h
= FlagHistogram::FactoryGet(flags
, buckets
);
680 case nsITelemetry::HISTOGRAM_COUNT
:
681 h
= CountHistogram::FactoryGet(flags
, buckets
);
684 MOZ_ASSERT(false, "Invalid histogram type");
691 nsresult
internal_HistogramAdd(const StaticMutexAutoLock
& aLock
,
692 Histogram
& histogram
, const HistogramID id
,
693 uint32_t value
, ProcessID aProcessType
) {
694 // Check if we are allowed to record the data.
695 const HistogramInfo
& h
= gHistogramInfos
[id
];
696 bool canRecordDataset
= CanRecordDataset(h
.dataset
, internal_CanRecordBase(),
697 internal_CanRecordExtended());
698 if (!canRecordDataset
) {
702 // If `histogram` is a non-parent-process histogram, then recording-enabled
703 // has been checked in its owner process.
704 if (aProcessType
== ProcessID::Parent
&& !internal_IsRecordingEnabled(id
)) {
705 PROFILER_MARKER_TEXT(
706 "HistogramError", TELEMETRY
, mozilla::MarkerStack::Capture(),
707 nsPrintfCString("CannotRecordInProcess: %s", h
.name()));
711 // Don't record if the current platform is not enabled
712 if (!CanRecordProduct(h
.products
)) {
716 // The internal representation of a base::Histogram's buckets uses `int`.
717 // Clamp large values of `value` to be INT_MAX so they continue to be treated
718 // as large values (instead of negative ones).
719 if (value
> INT_MAX
) {
720 TelemetryScalar::Add(
721 mozilla::Telemetry::ScalarID::TELEMETRY_ACCUMULATE_CLAMPED_VALUES
,
722 NS_ConvertASCIItoUTF16(h
.name()), 1);
726 histogram
.Add(value
);
733 ////////////////////////////////////////////////////////////////////////
734 ////////////////////////////////////////////////////////////////////////
736 // PRIVATE: Histogram reflection helpers
741 * Copy histograms and samples to Mozilla-friendly structures.
742 * Please note that this version does not make use of JS contexts.
744 * @param {StaticMutexAutoLock} the proof we hold the mutex.
745 * @param {Histogram} the histogram to reflect.
746 * @return {nsresult} NS_ERROR_FAILURE if we fail to allocate memory for the
749 nsresult
internal_GetHistogramAndSamples(const StaticMutexAutoLock
& aLock
,
750 const base::Histogram
* h
,
751 HistogramSnapshotData
& aSnapshot
) {
754 // Convert the ranges of the buckets to a nsTArray.
755 const size_t bucketCount
= h
->bucket_count();
756 for (size_t i
= 0; i
< bucketCount
; i
++) {
757 // XXX(Bug 1631371) Check if this should use a fallible operation as it
758 // pretended earlier, or change the return type to void.
759 aSnapshot
.mBucketRanges
.AppendElement(h
->ranges(i
));
762 // Get a snapshot of the samples.
763 base::Histogram::SampleSet ss
= h
->SnapshotSample();
765 // Get the number of samples in each bucket.
766 for (size_t i
= 0; i
< bucketCount
; i
++) {
767 // XXX(Bug 1631371) Check if this should use a fallible operation as it
768 // pretended earlier, or change the return type to void.
769 aSnapshot
.mBucketCounts
.AppendElement(ss
.counts(i
));
772 // Finally, save the |sum|. We don't need to reflect declared_min,
773 // declared_max and histogram_type as they are in gHistogramInfo.
774 aSnapshot
.mSampleSum
= ss
.sum();
779 * Reflect a histogram snapshot into a JavaScript object.
780 * The returned histogram object will have the following properties:
782 * bucket_count - Number of buckets of this histogram
783 * histogram_type - HISTOGRAM_EXPONENTIAL, HISTOGRAM_LINEAR,
784 * HISTOGRAM_BOOLEAN, HISTOGRAM_FLAG, HISTOGRAM_COUNT, or HISTOGRAM_CATEGORICAL
785 * sum - sum of the bucket contents
786 * range - A 2-item array of minimum and maximum bucket size
787 * values - Map from bucket start to the bucket's count
789 nsresult
internal_ReflectHistogramAndSamples(
790 JSContext
* cx
, JS::Handle
<JSObject
*> obj
,
791 const HistogramInfo
& aHistogramInfo
,
792 const HistogramSnapshotData
& aSnapshot
) {
793 if (!(JS_DefineProperty(cx
, obj
, "bucket_count", aHistogramInfo
.bucketCount
,
795 JS_DefineProperty(cx
, obj
, "histogram_type",
796 aHistogramInfo
.histogramType
, JSPROP_ENUMERATE
) &&
797 JS_DefineProperty(cx
, obj
, "sum", double(aSnapshot
.mSampleSum
),
798 JSPROP_ENUMERATE
))) {
799 return NS_ERROR_FAILURE
;
802 // Don't rely on the bucket counts from "aHistogramInfo": it may
803 // differ from the length of aSnapshot.mBucketCounts due to expired
805 const size_t count
= aSnapshot
.mBucketCounts
.Length();
806 MOZ_ASSERT(count
== aSnapshot
.mBucketRanges
.Length(),
807 "The number of buckets and the number of counts must match.");
809 // Create the "range" property and add it to the final object.
810 JS::Rooted
<JSObject
*> rarray(cx
, JS::NewArrayObject(cx
, 2));
811 if (rarray
== nullptr ||
812 !JS_DefineProperty(cx
, obj
, "range", rarray
, JSPROP_ENUMERATE
)) {
813 return NS_ERROR_FAILURE
;
815 // Add [min, max] into the range array
816 if (!JS_DefineElement(cx
, rarray
, 0, aHistogramInfo
.min
, JSPROP_ENUMERATE
)) {
817 return NS_ERROR_FAILURE
;
819 if (!JS_DefineElement(cx
, rarray
, 1, aHistogramInfo
.max
, JSPROP_ENUMERATE
)) {
820 return NS_ERROR_FAILURE
;
823 JS::Rooted
<JSObject
*> values(cx
, JS_NewPlainObject(cx
));
824 if (values
== nullptr ||
825 !JS_DefineProperty(cx
, obj
, "values", values
, JSPROP_ENUMERATE
)) {
826 return NS_ERROR_FAILURE
;
832 for (size_t i
= 0; i
< count
; i
++) {
833 auto value
= aSnapshot
.mBucketCounts
[i
];
838 if (i
> 0 && first
) {
839 auto range
= aSnapshot
.mBucketRanges
[i
- 1];
840 if (!JS_DefineProperty(cx
, values
, nsPrintfCString("%d", range
).get(), 0,
842 return NS_ERROR_FAILURE
;
849 auto range
= aSnapshot
.mBucketRanges
[i
];
850 if (!JS_DefineProperty(cx
, values
, nsPrintfCString("%d", range
).get(),
851 value
, JSPROP_ENUMERATE
)) {
852 return NS_ERROR_FAILURE
;
856 if (last
> 0 && last
< count
) {
857 auto range
= aSnapshot
.mBucketRanges
[last
];
858 if (!JS_DefineProperty(cx
, values
, nsPrintfCString("%d", range
).get(), 0,
860 return NS_ERROR_FAILURE
;
867 bool internal_ShouldReflectHistogram(const StaticMutexAutoLock
& aLock
,
868 base::Histogram
* h
, HistogramID id
) {
869 // Only flag histograms are serialized when they are empty.
870 // This has historical reasons, changing this will require downstream changes.
871 // The cheaper path here is to just deprecate flag histograms in favor
873 uint32_t type
= gHistogramInfos
[id
].histogramType
;
874 if (internal_IsEmpty(aLock
, h
) && type
!= nsITelemetry::HISTOGRAM_FLAG
) {
878 // Don't reflect the histogram if it's not allowed in this product.
879 if (!CanRecordProduct(gHistogramInfos
[id
].products
)) {
887 * Helper function to get a snapshot of the histograms.
889 * @param {aLock} the lock proof.
890 * @param {aStore} the name of the store to snapshot.
891 * @param {aDataset} the dataset for which the snapshot is being requested.
892 * @param {aClearSubsession} whether or not to clear the data after
893 * taking the snapshot.
894 * @param {aIncludeGPU} whether or not to include data for the GPU.
895 * @param {aOutSnapshot} the container in which the snapshot data will be
897 * @return {nsresult} NS_OK if the snapshot was successfully taken or
898 * NS_ERROR_OUT_OF_MEMORY if it failed to allocate memory.
900 nsresult
internal_GetHistogramsSnapshot(
901 const StaticMutexAutoLock
& aLock
, const nsACString
& aStore
,
902 unsigned int aDataset
, bool aClearSubsession
, bool aIncludeGPU
,
903 bool aFilterTest
, HistogramProcessSnapshotsArray
& aOutSnapshot
) {
904 if (!aOutSnapshot
.resize(static_cast<uint32_t>(ProcessID::Count
))) {
905 return NS_ERROR_OUT_OF_MEMORY
;
908 for (uint32_t process
= 0; process
< static_cast<uint32_t>(ProcessID::Count
);
910 HistogramSnapshotsArray
& hArray
= aOutSnapshot
[process
];
912 for (size_t i
= 0; i
< HistogramCount
; ++i
) {
913 const HistogramInfo
& info
= gHistogramInfos
[i
];
918 HistogramID id
= HistogramID(i
);
920 if (!CanRecordInProcess(info
.record_in_processes
, ProcessID(process
)) ||
921 ((ProcessID(process
) == ProcessID::Gpu
) && !aIncludeGPU
)) {
925 if (!IsInDataset(info
.dataset
, aDataset
)) {
929 bool shouldInstantiate
=
930 info
.histogramType
== nsITelemetry::HISTOGRAM_FLAG
;
931 Histogram
* w
= internal_GetHistogramById(aLock
, id
, ProcessID(process
),
933 if (!w
|| w
->IsExpired()) {
937 base::Histogram
* h
= nullptr;
938 if (!w
->GetHistogram(aStore
, &h
)) {
942 if (!internal_ShouldReflectHistogram(aLock
, h
, id
)) {
946 const char* name
= info
.name();
947 if (aFilterTest
&& strncmp(TEST_HISTOGRAM_PREFIX
, name
,
948 strlen(TEST_HISTOGRAM_PREFIX
)) == 0) {
949 if (aClearSubsession
) {
955 HistogramSnapshotData snapshotData
;
956 if (NS_FAILED(internal_GetHistogramAndSamples(aLock
, h
, snapshotData
))) {
960 if (!hArray
.emplaceBack(HistogramSnapshotInfo
{snapshotData
, id
})) {
961 return NS_ERROR_OUT_OF_MEMORY
;
964 if (aClearSubsession
) {
974 ////////////////////////////////////////////////////////////////////////
975 ////////////////////////////////////////////////////////////////////////
977 // PRIVATE: class Histogram
981 Histogram::Histogram(HistogramID histogramId
, const HistogramInfo
& info
,
983 : mSingleStore(nullptr), mIsExpired(expired
) {
988 const int bucketsOffset
= gHistogramBucketLowerBoundIndex
[histogramId
];
990 if (info
.is_single_store()) {
991 mSingleStore
= internal_CreateBaseHistogramInstance(info
, bucketsOffset
);
993 for (uint32_t i
= 0; i
< info
.store_count
; i
++) {
994 auto store
= nsDependentCString(
995 &gHistogramStringTable
[gHistogramStoresTable
[info
.store_index
+ i
]]);
996 mStorage
.InsertOrUpdate(store
, UniquePtr
<base::Histogram
>(
997 internal_CreateBaseHistogramInstance(
998 info
, bucketsOffset
)));
1003 Histogram::~Histogram() { delete mSingleStore
; }
1005 void Histogram::Add(uint32_t sample
) {
1006 MOZ_ASSERT(XRE_IsParentProcess(),
1007 "Only add to histograms in the parent process");
1008 if (!XRE_IsParentProcess()) {
1016 if (mSingleStore
!= nullptr) {
1017 mSingleStore
->Add(sample
);
1019 for (auto iter
= mStorage
.Iter(); !iter
.Done(); iter
.Next()) {
1020 auto& h
= iter
.Data();
1026 void Histogram::Clear(const nsACString
& store
) {
1027 MOZ_ASSERT(XRE_IsParentProcess(),
1028 "Only clear histograms in the parent process");
1029 if (!XRE_IsParentProcess()) {
1033 if (mSingleStore
!= nullptr) {
1034 if (store
.EqualsASCII("main")) {
1035 mSingleStore
->Clear();
1038 base::Histogram
* h
= nullptr;
1039 bool found
= GetHistogram(store
, &h
);
1043 MOZ_ASSERT(h
, "Should have found a valid histogram in the named store");
1049 bool Histogram::GetHistogram(const nsACString
& store
, base::Histogram
** h
) {
1050 MOZ_ASSERT(!IsExpired());
1055 if (mSingleStore
!= nullptr) {
1056 if (store
.EqualsASCII("main")) {
1064 return mStorage
.Get(store
, h
);
1067 size_t Histogram::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) {
1069 n
+= aMallocSizeOf(this);
1071 * In theory mStorage.SizeOfExcludingThis should included the data part of the
1072 * map, but the numbers seemed low, so we are only taking the shallow size and
1073 * do the iteration here.
1075 n
+= mStorage
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1076 for (const auto& h
: mStorage
.Values()) {
1077 n
+= h
->SizeOfIncludingThis(aMallocSizeOf
);
1079 if (mSingleStore
!= nullptr) {
1080 // base::Histogram doesn't have SizeOfExcludingThis, so we are overcounting
1081 // the pointer here.
1082 n
+= mSingleStore
->SizeOfIncludingThis(aMallocSizeOf
);
1089 ////////////////////////////////////////////////////////////////////////
1090 ////////////////////////////////////////////////////////////////////////
1092 // PRIVATE: class KeyedHistogram and internal_ReflectKeyedHistogram
1096 nsresult
internal_ReflectKeyedHistogram(
1097 const KeyedHistogramSnapshotData
& aSnapshot
, const HistogramInfo
& info
,
1098 JSContext
* aCx
, JS::Handle
<JSObject
*> aObj
) {
1099 for (const auto& entry
: aSnapshot
) {
1100 const HistogramSnapshotData
& keyData
= entry
.GetData();
1102 JS::Rooted
<JSObject
*> histogramSnapshot(aCx
, JS_NewPlainObject(aCx
));
1103 if (!histogramSnapshot
) {
1104 return NS_ERROR_FAILURE
;
1107 if (NS_FAILED(internal_ReflectHistogramAndSamples(aCx
, histogramSnapshot
,
1109 return NS_ERROR_FAILURE
;
1112 const NS_ConvertUTF8toUTF16
key(entry
.GetKey());
1113 if (!JS_DefineUCProperty(aCx
, aObj
, key
.Data(), key
.Length(),
1114 histogramSnapshot
, JSPROP_ENUMERATE
)) {
1115 return NS_ERROR_FAILURE
;
1122 KeyedHistogram::KeyedHistogram(HistogramID id
, const HistogramInfo
& info
,
1124 : mSingleStore(nullptr),
1126 mHistogramInfo(info
),
1127 mIsExpired(expired
) {
1132 if (info
.is_single_store()) {
1133 mSingleStore
= new KeyedHistogramMapType
;
1135 for (uint32_t i
= 0; i
< info
.store_count
; i
++) {
1136 auto store
= nsDependentCString(
1137 &gHistogramStringTable
[gHistogramStoresTable
[info
.store_index
+ i
]]);
1138 mStorage
.InsertOrUpdate(store
, MakeUnique
<KeyedHistogramMapType
>());
1143 KeyedHistogram::~KeyedHistogram() { delete mSingleStore
; }
1145 nsresult
KeyedHistogram::GetHistogram(const nsCString
& aStore
,
1146 const nsCString
& key
,
1147 base::Histogram
** histogram
) {
1150 "KeyedHistogram::GetHistogram called on an expired histogram.");
1151 return NS_ERROR_FAILURE
;
1154 KeyedHistogramMapType
* histogramMap
;
1157 if (mSingleStore
!= nullptr) {
1158 histogramMap
= mSingleStore
;
1160 found
= mStorage
.Get(aStore
, &histogramMap
);
1162 return NS_ERROR_FAILURE
;
1166 found
= histogramMap
->Get(key
, histogram
);
1171 int bucketsOffset
= gHistogramBucketLowerBoundIndex
[mId
];
1172 auto h
= UniquePtr
<base::Histogram
>{
1173 internal_CreateBaseHistogramInstance(mHistogramInfo
, bucketsOffset
)};
1175 return NS_ERROR_FAILURE
;
1178 h
->ClearFlags(base::Histogram::kUmaTargetedHistogramFlag
);
1179 *histogram
= h
.get();
1182 histogramMap
->InsertOrUpdate(key
, std::move(h
), mozilla::fallible
);
1183 if (MOZ_UNLIKELY(!inserted
)) {
1184 return NS_ERROR_OUT_OF_MEMORY
;
1189 base::Histogram
* KeyedHistogram::GetHistogram(const nsCString
& aStore
,
1190 const nsCString
& key
) {
1191 base::Histogram
* h
= nullptr;
1192 if (NS_FAILED(GetHistogram(aStore
, key
, &h
))) {
1198 nsresult
KeyedHistogram::Add(const nsCString
& key
, uint32_t sample
,
1199 ProcessID aProcessType
) {
1200 MOZ_ASSERT(XRE_IsParentProcess(),
1201 "Only add to keyed histograms in the parent process");
1202 if (!XRE_IsParentProcess()) {
1203 return NS_ERROR_FAILURE
;
1206 bool canRecordDataset
=
1207 CanRecordDataset(mHistogramInfo
.dataset
, internal_CanRecordBase(),
1208 internal_CanRecordExtended());
1209 // If `histogram` is a non-parent-process histogram, then recording-enabled
1210 // has been checked in its owner process.
1211 if (!canRecordDataset
|| (aProcessType
== ProcessID::Parent
&&
1212 !internal_IsRecordingEnabled(mId
))) {
1216 // Don't record if expired.
1221 // Don't record if the current platform is not enabled
1222 if (!CanRecordProduct(gHistogramInfos
[mId
].products
)) {
1226 // The internal representation of a base::Histogram's buckets uses `int`.
1227 // Clamp large values of `sample` to be INT_MAX so they continue to be treated
1228 // as large values (instead of negative ones).
1229 if (sample
> INT_MAX
) {
1230 TelemetryScalar::Add(
1231 mozilla::Telemetry::ScalarID::TELEMETRY_ACCUMULATE_CLAMPED_VALUES
,
1232 NS_ConvertASCIItoUTF16(mHistogramInfo
.name()), 1);
1236 base::Histogram
* histogram
;
1237 if (mSingleStore
!= nullptr) {
1238 histogram
= GetHistogram("main"_ns
, key
);
1240 MOZ_ASSERT(false, "Missing histogram in single store.");
1241 return NS_ERROR_FAILURE
;
1244 histogram
->Add(sample
);
1246 for (uint32_t i
= 0; i
< mHistogramInfo
.store_count
; i
++) {
1247 auto store
= nsDependentCString(
1248 &gHistogramStringTable
1249 [gHistogramStoresTable
[mHistogramInfo
.store_index
+ i
]]);
1250 base::Histogram
* histogram
= GetHistogram(store
, key
);
1251 MOZ_ASSERT(histogram
);
1253 histogram
->Add(sample
);
1255 return NS_ERROR_FAILURE
;
1263 void KeyedHistogram::Clear(const nsACString
& aStore
) {
1264 MOZ_ASSERT(XRE_IsParentProcess(),
1265 "Only clear keyed histograms in the parent process");
1266 if (!XRE_IsParentProcess()) {
1275 if (aStore
.EqualsASCII("main")) {
1276 mSingleStore
->Clear();
1281 KeyedHistogramMapType
* histogramMap
;
1282 bool found
= mStorage
.Get(aStore
, &histogramMap
);
1287 histogramMap
->Clear();
1290 bool KeyedHistogram::IsEmpty(const nsACString
& aStore
) const {
1291 if (mSingleStore
!= nullptr) {
1292 if (aStore
.EqualsASCII("main")) {
1293 return mSingleStore
->IsEmpty();
1299 KeyedHistogramMapType
* histogramMap
;
1300 bool found
= mStorage
.Get(aStore
, &histogramMap
);
1304 return histogramMap
->IsEmpty();
1307 size_t KeyedHistogram::SizeOfIncludingThis(
1308 mozilla::MallocSizeOf aMallocSizeOf
) {
1310 n
+= aMallocSizeOf(this);
1312 * In theory mStorage.SizeOfExcludingThis should included the data part of the
1313 * map, but the numbers seemed low, so we are only taking the shallow size and
1314 * do the iteration here.
1316 n
+= mStorage
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1317 for (const auto& h
: mStorage
.Values()) {
1318 n
+= h
->SizeOfIncludingThis(aMallocSizeOf
);
1320 if (mSingleStore
!= nullptr) {
1321 // base::Histogram doesn't have SizeOfExcludingThis, so we are overcounting
1322 // the pointer here.
1323 n
+= mSingleStore
->SizeOfExcludingThis(aMallocSizeOf
);
1328 nsresult
KeyedHistogram::GetKeys(const StaticMutexAutoLock
& aLock
,
1329 const nsCString
& store
,
1330 nsTArray
<nsCString
>& aKeys
) {
1331 KeyedHistogramMapType
* histogramMap
;
1332 if (mSingleStore
!= nullptr) {
1333 histogramMap
= mSingleStore
;
1335 bool found
= mStorage
.Get(store
, &histogramMap
);
1337 return NS_ERROR_FAILURE
;
1341 if (!aKeys
.SetCapacity(histogramMap
->Count(), mozilla::fallible
)) {
1342 return NS_ERROR_OUT_OF_MEMORY
;
1345 for (const auto& key
: histogramMap
->Keys()) {
1346 if (!aKeys
.AppendElement(key
, mozilla::fallible
)) {
1347 return NS_ERROR_OUT_OF_MEMORY
;
1354 nsresult
KeyedHistogram::GetJSSnapshot(JSContext
* cx
, JS::Handle
<JSObject
*> obj
,
1355 const nsACString
& aStore
,
1356 bool clearSubsession
) {
1357 // Get a snapshot of the data.
1358 KeyedHistogramSnapshotData dataSnapshot
;
1360 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
1361 MOZ_ASSERT(internal_IsHistogramEnumId(mId
));
1363 // Take a snapshot of the data here, protected by the lock, and then,
1364 // outside of the lock protection, mirror it to a JS structure.
1365 nsresult rv
= GetSnapshot(locker
, aStore
, dataSnapshot
, clearSubsession
);
1366 if (NS_FAILED(rv
)) {
1371 // Now that we have a copy of the data, mirror it to JS.
1372 return internal_ReflectKeyedHistogram(dataSnapshot
, gHistogramInfos
[mId
], cx
,
1377 * Return a histogram snapshot for the named store.
1379 * If getting the snapshot succeeds, NS_OK is returned and `aSnapshot` contains
1380 * the snapshot data. If the histogram is not available in the named store,
1381 * NS_ERROR_NO_CONTENT is returned. For other errors, NS_ERROR_FAILURE is
1384 nsresult
KeyedHistogram::GetSnapshot(const StaticMutexAutoLock
& aLock
,
1385 const nsACString
& aStore
,
1386 KeyedHistogramSnapshotData
& aSnapshot
,
1387 bool aClearSubsession
) {
1388 KeyedHistogramMapType
* histogramMap
;
1389 if (mSingleStore
!= nullptr) {
1390 if (!aStore
.EqualsASCII("main")) {
1391 return NS_ERROR_NO_CONTENT
;
1394 histogramMap
= mSingleStore
;
1396 bool found
= mStorage
.Get(aStore
, &histogramMap
);
1398 // Nothing in the main store is fine, it's just handled as empty
1399 return NS_ERROR_NO_CONTENT
;
1403 // Snapshot every key.
1404 for (const auto& entry
: *histogramMap
) {
1405 base::Histogram
* keyData
= entry
.GetWeak();
1407 return NS_ERROR_FAILURE
;
1410 HistogramSnapshotData keySnapshot
;
1412 internal_GetHistogramAndSamples(aLock
, keyData
, keySnapshot
))) {
1413 return NS_ERROR_FAILURE
;
1416 // Append to the final snapshot.
1417 aSnapshot
.InsertOrUpdate(entry
.GetKey(), std::move(keySnapshot
));
1420 if (aClearSubsession
) {
1428 * Helper function to get a snapshot of the keyed histograms.
1430 * @param {aLock} the lock proof.
1431 * @param {aDataset} the dataset for which the snapshot is being requested.
1432 * @param {aClearSubsession} whether or not to clear the data after
1433 * taking the snapshot.
1434 * @param {aIncludeGPU} whether or not to include data for the GPU.
1435 * @param {aOutSnapshot} the container in which the snapshot data will be
1437 * @return {nsresult} NS_OK if the snapshot was successfully taken or
1438 * NS_ERROR_OUT_OF_MEMORY if it failed to allocate memory.
1440 nsresult
internal_GetKeyedHistogramsSnapshot(
1441 const StaticMutexAutoLock
& aLock
, const nsACString
& aStore
,
1442 unsigned int aDataset
, bool aClearSubsession
, bool aIncludeGPU
,
1443 bool aFilterTest
, KeyedHistogramProcessSnapshotsArray
& aOutSnapshot
) {
1444 if (!aOutSnapshot
.resize(static_cast<uint32_t>(ProcessID::Count
))) {
1445 return NS_ERROR_OUT_OF_MEMORY
;
1448 for (uint32_t process
= 0; process
< static_cast<uint32_t>(ProcessID::Count
);
1450 KeyedHistogramSnapshotsArray
& hArray
= aOutSnapshot
[process
];
1452 for (size_t i
= 0; i
< HistogramCount
; ++i
) {
1453 HistogramID id
= HistogramID(i
);
1454 const HistogramInfo
& info
= gHistogramInfos
[id
];
1459 if (!CanRecordInProcess(info
.record_in_processes
, ProcessID(process
)) ||
1460 ((ProcessID(process
) == ProcessID::Gpu
) && !aIncludeGPU
)) {
1464 if (!IsInDataset(info
.dataset
, aDataset
)) {
1468 KeyedHistogram
* keyed
=
1469 internal_GetKeyedHistogramById(id
, ProcessID(process
),
1470 /* instantiate = */ false);
1471 if (!keyed
|| keyed
->IsEmpty(aStore
) || keyed
->IsExpired()) {
1475 const char* name
= info
.name();
1476 if (aFilterTest
&& strncmp(TEST_HISTOGRAM_PREFIX
, name
,
1477 strlen(TEST_HISTOGRAM_PREFIX
)) == 0) {
1478 if (aClearSubsession
) {
1479 keyed
->Clear(aStore
);
1484 // Take a snapshot of the keyed histogram data!
1485 KeyedHistogramSnapshotData snapshot
;
1487 keyed
->GetSnapshot(aLock
, aStore
, snapshot
, aClearSubsession
))) {
1488 return NS_ERROR_FAILURE
;
1491 if (!hArray
.emplaceBack(
1492 KeyedHistogramSnapshotInfo
{std::move(snapshot
), id
})) {
1493 return NS_ERROR_OUT_OF_MEMORY
;
1502 namespace geckoprofiler::markers
{
1504 struct HistogramMarker
{
1505 static constexpr mozilla::Span
<const char> MarkerTypeName() {
1506 return mozilla::MakeStringSpan("Hist");
1508 static void StreamJSONMarkerData(
1509 mozilla::baseprofiler::SpliceableJSONWriter
& aWriter
,
1510 mozilla::Telemetry::HistogramID aId
, const nsCString
& key
,
1512 aWriter
.UniqueStringProperty(
1513 "id", mozilla::MakeStringSpan(GetHistogramName(aId
)));
1514 if (!key
.IsEmpty()) {
1515 aWriter
.StringProperty("key", key
);
1517 aWriter
.IntProperty("val", aSample
);
1519 using MS
= mozilla::MarkerSchema
;
1520 static MS
MarkerTypeDisplay() {
1521 MS schema
{MS::Location::MarkerChart
, MS::Location::MarkerTable
};
1522 schema
.AddKeyLabelFormatSearchable("id", "Histogram Name",
1523 MS::Format::UniqueString
,
1524 MS::Searchable::Searchable
);
1525 schema
.AddKeyLabelFormat("key", "Key", MS::Format::String
);
1526 schema
.AddKeyLabelFormat("val", "Sample", MS::Format::Integer
);
1527 schema
.SetTooltipLabel(
1528 "{marker.data.id}[{marker.data.key}] {marker.data.val}");
1529 schema
.SetTableLabel(
1530 "{marker.name} - {marker.data.id}[{marker.data.key}]: "
1531 "{marker.data.val}");
1536 } // namespace geckoprofiler::markers
1538 ////////////////////////////////////////////////////////////////////////
1539 ////////////////////////////////////////////////////////////////////////
1541 // PRIVATE: thread-unsafe helpers for the external interface
1545 bool internal_RemoteAccumulate(const StaticMutexAutoLock
& aLock
,
1546 HistogramID aId
, uint32_t aSample
) {
1547 if (XRE_IsParentProcess()) {
1551 if (!internal_IsRecordingEnabled(aId
)) {
1555 PROFILER_MARKER("Histogram::Add", TELEMETRY
, {}, HistogramMarker
, aId
,
1556 EmptyCString(), aSample
);
1557 TelemetryIPCAccumulator::AccumulateChildHistogram(aId
, aSample
);
1561 bool internal_RemoteAccumulate(const StaticMutexAutoLock
& aLock
,
1562 HistogramID aId
, const nsCString
& aKey
,
1564 if (XRE_IsParentProcess()) {
1568 if (!internal_IsRecordingEnabled(aId
)) {
1572 PROFILER_MARKER("Histogram::Add", TELEMETRY
, {}, HistogramMarker
, aId
, aKey
,
1574 TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(aId
, aKey
, aSample
);
1578 void internal_Accumulate(const StaticMutexAutoLock
& aLock
, HistogramID aId
,
1580 if (!internal_CanRecordBase() ||
1581 internal_RemoteAccumulate(aLock
, aId
, aSample
)) {
1585 PROFILER_MARKER("Histogram::Add", TELEMETRY
, {}, HistogramMarker
, aId
,
1586 EmptyCString(), aSample
);
1587 Histogram
* w
= internal_GetHistogramById(aLock
, aId
, ProcessID::Parent
);
1589 internal_HistogramAdd(aLock
, *w
, aId
, aSample
, ProcessID::Parent
);
1592 void internal_Accumulate(const StaticMutexAutoLock
& aLock
, HistogramID aId
,
1593 const nsCString
& aKey
, uint32_t aSample
) {
1594 if (!gTelemetryHistogramInitDone
|| !internal_CanRecordBase() ||
1595 internal_RemoteAccumulate(aLock
, aId
, aKey
, aSample
)) {
1599 PROFILER_MARKER("Histogram::Add", TELEMETRY
, {}, HistogramMarker
, aId
, aKey
,
1601 KeyedHistogram
* keyed
=
1602 internal_GetKeyedHistogramById(aId
, ProcessID::Parent
);
1604 keyed
->Add(aKey
, aSample
, ProcessID::Parent
);
1607 void internal_AccumulateChild(const StaticMutexAutoLock
& aLock
,
1608 ProcessID aProcessType
, HistogramID aId
,
1610 if (!internal_CanRecordBase()) {
1614 PROFILER_MARKER("ChildHistogram::Add", TELEMETRY
, {}, HistogramMarker
, aId
,
1615 EmptyCString(), aSample
);
1616 Histogram
* w
= internal_GetHistogramById(aLock
, aId
, aProcessType
);
1618 NS_WARNING("Failed GetHistogramById for CHILD");
1620 internal_HistogramAdd(aLock
, *w
, aId
, aSample
, aProcessType
);
1624 void internal_AccumulateChildKeyed(const StaticMutexAutoLock
& aLock
,
1625 ProcessID aProcessType
, HistogramID aId
,
1626 const nsCString
& aKey
, uint32_t aSample
) {
1627 if (!gTelemetryHistogramInitDone
|| !internal_CanRecordBase()) {
1631 PROFILER_MARKER("ChildHistogram::Add", TELEMETRY
, {}, HistogramMarker
, aId
,
1633 KeyedHistogram
* keyed
= internal_GetKeyedHistogramById(aId
, aProcessType
);
1635 keyed
->Add(aKey
, aSample
, aProcessType
);
1638 void internal_ClearHistogram(const StaticMutexAutoLock
& aLock
, HistogramID id
,
1639 const nsACString
& aStore
) {
1640 MOZ_ASSERT(XRE_IsParentProcess());
1641 if (!XRE_IsParentProcess()) {
1645 // Handle keyed histograms.
1646 if (gHistogramInfos
[id
].keyed
) {
1647 for (uint32_t process
= 0;
1648 process
< static_cast<uint32_t>(ProcessID::Count
); ++process
) {
1649 KeyedHistogram
* kh
= internal_GetKeyedHistogramById(
1650 id
, static_cast<ProcessID
>(process
), /* instantiate = */ false);
1656 // Reset the histograms instances for all processes.
1657 for (uint32_t process
= 0;
1658 process
< static_cast<uint32_t>(ProcessID::Count
); ++process
) {
1660 internal_GetHistogramById(aLock
, id
, static_cast<ProcessID
>(process
),
1661 /* instantiate = */ false);
1671 ////////////////////////////////////////////////////////////////////////
1672 ////////////////////////////////////////////////////////////////////////
1674 // PRIVATE: JSHistogram_* functions
1676 // NOTE: the functions in this section:
1678 // internal_JSHistogram_Add
1679 // internal_JSHistogram_Name
1680 // internal_JSHistogram_Snapshot
1681 // internal_JSHistogram_Clear
1682 // internal_WrapAndReturnHistogram
1684 // all run without protection from |gTelemetryHistogramMutex|. If they
1685 // held |gTelemetryHistogramMutex|, there would be the possibility of
1686 // deadlock because the JS_ calls that they make may call back into the
1687 // TelemetryHistogram interface, hence trying to re-acquire the mutex.
1689 // This means that these functions potentially race against threads, but
1690 // that seems preferable to risking deadlock.
1694 static constexpr uint32_t HistogramObjectDataSlot
= 0;
1695 static constexpr uint32_t HistogramObjectSlotCount
=
1696 HistogramObjectDataSlot
+ 1;
1698 void internal_JSHistogram_finalize(JS::GCContext
*, JSObject
*);
1700 static const JSClassOps sJSHistogramClassOps
= {nullptr, /* addProperty */
1701 nullptr, /* delProperty */
1702 nullptr, /* enumerate */
1703 nullptr, /* newEnumerate */
1704 nullptr, /* resolve */
1705 nullptr, /* mayResolve */
1706 internal_JSHistogram_finalize
};
1708 static const JSClass sJSHistogramClass
= {
1709 "JSHistogram", /* name */
1710 JSCLASS_HAS_RESERVED_SLOTS(HistogramObjectSlotCount
) |
1711 JSCLASS_FOREGROUND_FINALIZE
, /* flags */
1712 &sJSHistogramClassOps
};
1714 struct JSHistogramData
{
1715 HistogramID histogramId
;
1718 bool internal_JSHistogram_CoerceValue(JSContext
* aCx
,
1719 JS::Handle
<JS::Value
> aElement
,
1720 HistogramID aId
, uint32_t aHistogramType
,
1722 if (aElement
.isString()) {
1723 // Strings only allowed for categorical histograms
1724 if (aHistogramType
!= nsITelemetry::HISTOGRAM_CATEGORICAL
) {
1725 LogToBrowserConsole(
1726 nsIScriptError::errorFlag
,
1728 u
"String argument only allowed for categorical histogram"));
1732 // Label is given by the string argument
1733 nsAutoJSString label
;
1734 if (!label
.init(aCx
, aElement
)) {
1735 LogToBrowserConsole(nsIScriptError::errorFlag
,
1736 u
"Invalid string parameter"_ns
);
1740 // Get the label id for accumulation
1741 nsresult rv
= gHistogramInfos
[aId
].label_id(
1742 NS_ConvertUTF16toUTF8(label
).get(), &aValue
);
1743 if (NS_FAILED(rv
)) {
1744 nsPrintfCString
msg("'%s' is an invalid string label",
1745 NS_ConvertUTF16toUTF8(label
).get());
1746 LogToBrowserConsole(nsIScriptError::errorFlag
,
1747 NS_ConvertUTF8toUTF16(msg
));
1750 } else if (!(aElement
.isNumber() || aElement
.isBoolean())) {
1751 LogToBrowserConsole(nsIScriptError::errorFlag
, u
"Argument not a number"_ns
);
1753 } else if (aElement
.isNumber() && aElement
.toNumber() > UINT32_MAX
) {
1754 // Clamp large numerical arguments to aValue's acceptable values.
1755 // JS::ToUint32 will take aElement modulo 2^32 before returning it, which
1756 // may result in a smaller final value.
1757 aValue
= UINT32_MAX
;
1759 LogToBrowserConsole(nsIScriptError::errorFlag
,
1760 u
"Clamped large numeric value"_ns
);
1762 } else if (!JS::ToUint32(aCx
, aElement
, &aValue
)) {
1763 LogToBrowserConsole(nsIScriptError::errorFlag
,
1764 u
"Failed to convert element to UInt32"_ns
);
1768 // If we're here then all type checks have passed and aValue contains the
1773 bool internal_JSHistogram_GetValueArray(JSContext
* aCx
, JS::CallArgs
& args
,
1774 uint32_t aHistogramType
,
1775 HistogramID aId
, bool isKeyed
,
1776 nsTArray
<uint32_t>& aArray
) {
1777 // This function populates aArray with the values extracted from args. Handles
1778 // keyed and non-keyed histograms, and single and array of values. Also
1779 // performs sanity checks on the arguments. Returns true upon successful
1780 // population, false otherwise.
1782 uint32_t firstArgIndex
= 0;
1787 // Special case of no argument (or only key) and count histogram
1788 if (args
.length() == firstArgIndex
) {
1789 if (!(aHistogramType
== nsITelemetry::HISTOGRAM_COUNT
)) {
1790 LogToBrowserConsole(
1791 nsIScriptError::errorFlag
,
1793 u
"Need at least one argument for non count type histogram"));
1797 aArray
.AppendElement(1);
1801 if (args
[firstArgIndex
].isObject() && !args
[firstArgIndex
].isString()) {
1802 JS::Rooted
<JSObject
*> arrayObj(aCx
, &args
[firstArgIndex
].toObject());
1804 bool isArray
= false;
1805 JS::IsArrayObject(aCx
, arrayObj
, &isArray
);
1808 LogToBrowserConsole(
1809 nsIScriptError::errorFlag
,
1811 u
"The argument to accumulate can't be a non-array object"));
1815 uint32_t arrayLength
= 0;
1816 if (!JS::GetArrayLength(aCx
, arrayObj
, &arrayLength
)) {
1817 LogToBrowserConsole(nsIScriptError::errorFlag
,
1818 u
"Failed while trying to get array length"_ns
);
1822 for (uint32_t arrayIdx
= 0; arrayIdx
< arrayLength
; arrayIdx
++) {
1823 JS::Rooted
<JS::Value
> element(aCx
);
1825 if (!JS_GetElement(aCx
, arrayObj
, arrayIdx
, &element
)) {
1826 nsPrintfCString
msg("Failed while trying to get element at index %d",
1828 LogToBrowserConsole(nsIScriptError::errorFlag
,
1829 NS_ConvertUTF8toUTF16(msg
));
1834 if (!internal_JSHistogram_CoerceValue(aCx
, element
, aId
, aHistogramType
,
1836 nsPrintfCString
msg("Element at index %d failed type checks", arrayIdx
);
1837 LogToBrowserConsole(nsIScriptError::errorFlag
,
1838 NS_ConvertUTF8toUTF16(msg
));
1841 aArray
.AppendElement(value
);
1848 if (!internal_JSHistogram_CoerceValue(aCx
, args
[firstArgIndex
], aId
,
1849 aHistogramType
, value
)) {
1852 aArray
.AppendElement(value
);
1856 static JSHistogramData
* GetJSHistogramData(JSObject
* obj
) {
1857 MOZ_ASSERT(JS::GetClass(obj
) == &sJSHistogramClass
);
1858 return JS::GetMaybePtrFromReservedSlot
<JSHistogramData
>(
1859 obj
, HistogramObjectDataSlot
);
1862 bool internal_JSHistogram_Add(JSContext
* cx
, unsigned argc
, JS::Value
* vp
) {
1863 JS::CallArgs args
= CallArgsFromVp(argc
, vp
);
1865 if (!args
.thisv().isObject() ||
1866 JS::GetClass(&args
.thisv().toObject()) != &sJSHistogramClass
) {
1867 JS_ReportErrorASCII(cx
, "Wrong JS class, expected JSHistogram class");
1871 JSObject
* obj
= &args
.thisv().toObject();
1872 JSHistogramData
* data
= GetJSHistogramData(obj
);
1874 HistogramID id
= data
->histogramId
;
1875 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
1876 uint32_t type
= gHistogramInfos
[id
].histogramType
;
1878 // This function should always return |undefined| and never fail but
1879 // rather report failures using the console.
1880 args
.rval().setUndefined();
1882 nsTArray
<uint32_t> values
;
1883 if (!internal_JSHistogram_GetValueArray(cx
, args
, type
, id
, false, values
)) {
1884 // Either GetValueArray or CoerceValue utility function will have printed a
1885 // meaningful error message, so we simply return true
1890 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
1891 for (uint32_t aValue
: values
) {
1892 internal_Accumulate(locker
, id
, aValue
);
1898 bool internal_JSHistogram_Name(JSContext
* cx
, unsigned argc
, JS::Value
* vp
) {
1899 JS::CallArgs args
= CallArgsFromVp(argc
, vp
);
1901 if (!args
.thisv().isObject() ||
1902 JS::GetClass(&args
.thisv().toObject()) != &sJSHistogramClass
) {
1903 JS_ReportErrorASCII(cx
, "Wrong JS class, expected JSHistogram class");
1907 JSObject
* obj
= &args
.thisv().toObject();
1908 JSHistogramData
* data
= GetJSHistogramData(obj
);
1910 HistogramID id
= data
->histogramId
;
1911 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
1912 const char* name
= gHistogramInfos
[id
].name();
1914 auto cname
= NS_ConvertASCIItoUTF16(name
);
1915 args
.rval().setString(ToJSString(cx
, cname
));
1921 * Extract the store name from JavaScript function arguments.
1922 * The first and only argument needs to be an object with a "store" property.
1923 * If no arguments are given it defaults to "main".
1925 nsresult
internal_JS_StoreFromObjectArgument(JSContext
* cx
,
1926 const JS::CallArgs
& args
,
1927 nsAutoString
& aStoreName
) {
1928 if (args
.length() == 0) {
1929 aStoreName
.AssignLiteral("main");
1930 } else if (args
.length() == 1) {
1931 if (!args
[0].isObject()) {
1932 JS_ReportErrorASCII(cx
, "Expected object argument.");
1933 return NS_ERROR_FAILURE
;
1936 JS::Rooted
<JS::Value
> storeValue(cx
);
1937 JS::Rooted
<JSObject
*> argsObject(cx
, &args
[0].toObject());
1938 if (!JS_GetProperty(cx
, argsObject
, "store", &storeValue
)) {
1939 JS_ReportErrorASCII(cx
,
1940 "Expected object argument to have property 'store'.");
1941 return NS_ERROR_FAILURE
;
1944 nsAutoJSString store
;
1945 if (!storeValue
.isString() || !store
.init(cx
, storeValue
)) {
1946 JS_ReportErrorASCII(
1947 cx
, "Expected object argument's 'store' property to be a string.");
1948 return NS_ERROR_FAILURE
;
1951 aStoreName
.Assign(store
);
1953 JS_ReportErrorASCII(cx
, "Expected at most one argument.");
1954 return NS_ERROR_FAILURE
;
1960 bool internal_JSHistogram_Snapshot(JSContext
* cx
, unsigned argc
,
1962 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
1964 if (!XRE_IsParentProcess()) {
1965 JS_ReportErrorASCII(
1966 cx
, "Histograms can only be snapshotted in the parent process");
1970 if (!args
.thisv().isObject() ||
1971 JS::GetClass(&args
.thisv().toObject()) != &sJSHistogramClass
) {
1972 JS_ReportErrorASCII(cx
, "Wrong JS class, expected JSHistogram class");
1976 JSObject
* obj
= &args
.thisv().toObject();
1977 JSHistogramData
* data
= GetJSHistogramData(obj
);
1979 HistogramID id
= data
->histogramId
;
1981 nsAutoString storeName
;
1982 nsresult rv
= internal_JS_StoreFromObjectArgument(cx
, args
, storeName
);
1983 if (NS_FAILED(rv
)) {
1987 HistogramSnapshotData dataSnapshot
;
1989 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
1990 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
1992 // This is not good standard behavior given that we have histogram instances
1993 // covering multiple processes.
1994 // However, changing this requires some broader changes to callers.
1995 Histogram
* w
= internal_GetHistogramById(locker
, id
, ProcessID::Parent
);
1996 base::Histogram
* h
= nullptr;
1997 if (!w
->GetHistogram(NS_ConvertUTF16toUTF8(storeName
), &h
)) {
1998 // When it's not in the named store, let's skip the snapshot completely,
2000 args
.rval().setUndefined();
2003 // Take a snapshot of the data here, protected by the lock, and then,
2004 // outside of the lock protection, mirror it to a JS structure
2005 if (NS_FAILED(internal_GetHistogramAndSamples(locker
, h
, dataSnapshot
))) {
2010 JS::Rooted
<JSObject
*> snapshot(cx
, JS_NewPlainObject(cx
));
2015 if (NS_FAILED(internal_ReflectHistogramAndSamples(
2016 cx
, snapshot
, gHistogramInfos
[id
], dataSnapshot
))) {
2020 args
.rval().setObject(*snapshot
);
2024 bool internal_JSHistogram_Clear(JSContext
* cx
, unsigned argc
, JS::Value
* vp
) {
2025 if (!XRE_IsParentProcess()) {
2026 JS_ReportErrorASCII(cx
,
2027 "Histograms can only be cleared in the parent process");
2031 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
2033 if (!args
.thisv().isObject() ||
2034 JS::GetClass(&args
.thisv().toObject()) != &sJSHistogramClass
) {
2035 JS_ReportErrorASCII(cx
, "Wrong JS class, expected JSHistogram class");
2039 JSObject
* obj
= &args
.thisv().toObject();
2040 JSHistogramData
* data
= GetJSHistogramData(obj
);
2043 nsAutoString storeName
;
2044 nsresult rv
= internal_JS_StoreFromObjectArgument(cx
, args
, storeName
);
2045 if (NS_FAILED(rv
)) {
2049 // This function should always return |undefined| and never fail but
2050 // rather report failures using the console.
2051 args
.rval().setUndefined();
2053 HistogramID id
= data
->histogramId
;
2055 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2057 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
2058 internal_ClearHistogram(locker
, id
, NS_ConvertUTF16toUTF8(storeName
));
2064 // NOTE: Runs without protection from |gTelemetryHistogramMutex|.
2065 // See comment at the top of this section.
2066 nsresult
internal_WrapAndReturnHistogram(HistogramID id
, JSContext
* cx
,
2067 JS::MutableHandle
<JS::Value
> ret
) {
2068 JS::Rooted
<JSObject
*> obj(cx
, JS_NewObject(cx
, &sJSHistogramClass
));
2070 return NS_ERROR_FAILURE
;
2073 // The 3 functions that are wrapped up here are eventually called
2074 // by the same thread that runs this function.
2075 if (!(JS_DefineFunction(cx
, obj
, "add", internal_JSHistogram_Add
, 1, 0) &&
2076 JS_DefineFunction(cx
, obj
, "name", internal_JSHistogram_Name
, 1, 0) &&
2077 JS_DefineFunction(cx
, obj
, "snapshot", internal_JSHistogram_Snapshot
, 1,
2079 JS_DefineFunction(cx
, obj
, "clear", internal_JSHistogram_Clear
, 1,
2081 return NS_ERROR_FAILURE
;
2084 JSHistogramData
* data
= new JSHistogramData
{id
};
2085 JS::SetReservedSlot(obj
, HistogramObjectDataSlot
, JS::PrivateValue(data
));
2086 ret
.setObject(*obj
);
2091 void internal_JSHistogram_finalize(JS::GCContext
* gcx
, JSObject
* obj
) {
2092 if (!obj
|| JS::GetClass(obj
) != &sJSHistogramClass
) {
2093 MOZ_ASSERT_UNREACHABLE("Should have the right JS class.");
2097 JSHistogramData
* data
= GetJSHistogramData(obj
);
2104 ////////////////////////////////////////////////////////////////////////
2105 ////////////////////////////////////////////////////////////////////////
2107 // PRIVATE: JSKeyedHistogram_* functions
2109 // NOTE: the functions in this section:
2111 // internal_JSKeyedHistogram_Add
2112 // internal_JSKeyedHistogram_Name
2113 // internal_JSKeyedHistogram_Keys
2114 // internal_JSKeyedHistogram_Snapshot
2115 // internal_JSKeyedHistogram_Clear
2116 // internal_WrapAndReturnKeyedHistogram
2118 // Same comments as above, at the JSHistogram_* section, regarding
2119 // deadlock avoidance, apply.
2123 void internal_JSKeyedHistogram_finalize(JS::GCContext
*, JSObject
*);
2125 static const JSClassOps sJSKeyedHistogramClassOps
= {
2126 nullptr, /* addProperty */
2127 nullptr, /* delProperty */
2128 nullptr, /* enumerate */
2129 nullptr, /* newEnumerate */
2130 nullptr, /* resolve */
2131 nullptr, /* mayResolve */
2132 internal_JSKeyedHistogram_finalize
};
2134 static const JSClass sJSKeyedHistogramClass
= {
2135 "JSKeyedHistogram", /* name */
2136 JSCLASS_HAS_RESERVED_SLOTS(HistogramObjectSlotCount
) |
2137 JSCLASS_FOREGROUND_FINALIZE
, /* flags */
2138 &sJSKeyedHistogramClassOps
};
2140 static JSHistogramData
* GetJSKeyedHistogramData(JSObject
* obj
) {
2141 MOZ_ASSERT(JS::GetClass(obj
) == &sJSKeyedHistogramClass
);
2142 return JS::GetMaybePtrFromReservedSlot
<JSHistogramData
>(
2143 obj
, HistogramObjectDataSlot
);
2146 bool internal_JSKeyedHistogram_Snapshot(JSContext
* cx
, unsigned argc
,
2148 if (!XRE_IsParentProcess()) {
2149 JS_ReportErrorASCII(
2150 cx
, "Keyed histograms can only be snapshotted in the parent process");
2154 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
2156 if (!args
.thisv().isObject() ||
2157 JS::GetClass(&args
.thisv().toObject()) != &sJSKeyedHistogramClass
) {
2158 JS_ReportErrorASCII(cx
, "Wrong JS class, expected JSKeyedHistogram class");
2162 JSObject
* obj
= &args
.thisv().toObject();
2163 JSHistogramData
* data
= GetJSKeyedHistogramData(obj
);
2165 HistogramID id
= data
->histogramId
;
2166 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
2168 // This function should always return |undefined| and never fail but
2169 // rather report failures using the console.
2170 args
.rval().setUndefined();
2172 // This is not good standard behavior given that we have histogram instances
2173 // covering multiple processes.
2174 // However, changing this requires some broader changes to callers.
2175 KeyedHistogram
* keyed
= internal_GetKeyedHistogramById(
2176 id
, ProcessID::Parent
, /* instantiate = */ true);
2178 JS_ReportErrorASCII(cx
, "Failed to look up keyed histogram");
2182 nsAutoString storeName
;
2184 rv
= internal_JS_StoreFromObjectArgument(cx
, args
, storeName
);
2185 if (NS_FAILED(rv
)) {
2189 JS::Rooted
<JSObject
*> snapshot(cx
, JS_NewPlainObject(cx
));
2191 JS_ReportErrorASCII(cx
, "Failed to create object");
2195 rv
= keyed
->GetJSSnapshot(cx
, snapshot
, NS_ConvertUTF16toUTF8(storeName
),
2198 // If the store is not available, we return nothing and don't fail
2199 if (rv
== NS_ERROR_NO_CONTENT
) {
2200 args
.rval().setUndefined();
2204 if (!NS_SUCCEEDED(rv
)) {
2205 JS_ReportErrorASCII(cx
, "Failed to reflect keyed histograms");
2209 args
.rval().setObject(*snapshot
);
2213 bool internal_JSKeyedHistogram_Add(JSContext
* cx
, unsigned argc
,
2215 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
2217 if (!args
.thisv().isObject() ||
2218 JS::GetClass(&args
.thisv().toObject()) != &sJSKeyedHistogramClass
) {
2219 JS_ReportErrorASCII(cx
, "Wrong JS class, expected JSKeyedHistogram class");
2223 JSObject
* obj
= &args
.thisv().toObject();
2224 JSHistogramData
* data
= GetJSKeyedHistogramData(obj
);
2226 HistogramID id
= data
->histogramId
;
2227 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
2229 // This function should always return |undefined| and never fail but
2230 // rather report failures using the console.
2231 args
.rval().setUndefined();
2232 if (args
.length() < 1) {
2233 LogToBrowserConsole(nsIScriptError::errorFlag
, u
"Expected one argument"_ns
);
2238 if (!args
[0].isString() || !key
.init(cx
, args
[0])) {
2239 LogToBrowserConsole(nsIScriptError::errorFlag
, u
"Not a string"_ns
);
2243 // Check if we're allowed to record in the provided key, for this histogram.
2244 if (!gHistogramInfos
[id
].allows_key(NS_ConvertUTF16toUTF8(key
))) {
2245 nsPrintfCString
msg("%s - key '%s' not allowed for this keyed histogram",
2246 gHistogramInfos
[id
].name(),
2247 NS_ConvertUTF16toUTF8(key
).get());
2248 LogToBrowserConsole(nsIScriptError::errorFlag
, NS_ConvertUTF8toUTF16(msg
));
2249 TelemetryScalar::Add(mozilla::Telemetry::ScalarID::
2250 TELEMETRY_ACCUMULATE_UNKNOWN_HISTOGRAM_KEYS
,
2251 NS_ConvertASCIItoUTF16(gHistogramInfos
[id
].name()), 1);
2255 const uint32_t type
= gHistogramInfos
[id
].histogramType
;
2257 nsTArray
<uint32_t> values
;
2258 if (!internal_JSHistogram_GetValueArray(cx
, args
, type
, id
, true, values
)) {
2259 // Either GetValueArray or CoerceValue utility function will have printed a
2260 // meaningful error message so we simple return true
2265 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2266 for (uint32_t aValue
: values
) {
2267 internal_Accumulate(locker
, id
, NS_ConvertUTF16toUTF8(key
), aValue
);
2273 bool internal_JSKeyedHistogram_Name(JSContext
* cx
, unsigned argc
,
2275 JS::CallArgs args
= CallArgsFromVp(argc
, vp
);
2277 if (!args
.thisv().isObject() ||
2278 JS::GetClass(&args
.thisv().toObject()) != &sJSKeyedHistogramClass
) {
2279 JS_ReportErrorASCII(cx
, "Wrong JS class, expected JSKeyedHistogram class");
2283 JSObject
* obj
= &args
.thisv().toObject();
2284 JSHistogramData
* data
= GetJSKeyedHistogramData(obj
);
2286 HistogramID id
= data
->histogramId
;
2287 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
2288 const char* name
= gHistogramInfos
[id
].name();
2290 auto cname
= NS_ConvertASCIItoUTF16(name
);
2291 args
.rval().setString(ToJSString(cx
, cname
));
2296 bool internal_JSKeyedHistogram_Keys(JSContext
* cx
, unsigned argc
,
2298 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
2300 if (!args
.thisv().isObject() ||
2301 JS::GetClass(&args
.thisv().toObject()) != &sJSKeyedHistogramClass
) {
2302 JS_ReportErrorASCII(cx
, "Wrong JS class, expected JSKeyedHistogram class");
2306 JSObject
* obj
= &args
.thisv().toObject();
2307 JSHistogramData
* data
= GetJSKeyedHistogramData(obj
);
2309 HistogramID id
= data
->histogramId
;
2311 nsAutoString storeName
;
2312 nsresult rv
= internal_JS_StoreFromObjectArgument(cx
, args
, storeName
);
2313 if (NS_FAILED(rv
)) {
2317 nsTArray
<nsCString
> keys
;
2319 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2320 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
2322 // This is not good standard behavior given that we have histogram instances
2323 // covering multiple processes.
2324 // However, changing this requires some broader changes to callers.
2325 KeyedHistogram
* keyed
=
2326 internal_GetKeyedHistogramById(id
, ProcessID::Parent
);
2334 keyed
->GetKeys(locker
, NS_ConvertUTF16toUTF8(storeName
), keys
))) {
2339 // Convert keys from nsTArray<nsCString> to JS array.
2340 JS::RootedVector
<JS::Value
> autoKeys(cx
);
2341 if (!autoKeys
.reserve(keys
.Length())) {
2345 for (const auto& key
: keys
) {
2346 JS::Rooted
<JS::Value
> jsKey(cx
);
2347 jsKey
.setString(ToJSString(cx
, key
));
2348 if (!autoKeys
.append(jsKey
)) {
2353 JS::Rooted
<JSObject
*> jsKeys(cx
, JS::NewArrayObject(cx
, autoKeys
));
2358 args
.rval().setObject(*jsKeys
);
2362 bool internal_JSKeyedHistogram_Clear(JSContext
* cx
, unsigned argc
,
2364 if (!XRE_IsParentProcess()) {
2365 JS_ReportErrorASCII(
2366 cx
, "Keyed histograms can only be cleared in the parent process");
2370 JS::CallArgs args
= JS::CallArgsFromVp(argc
, vp
);
2372 if (!args
.thisv().isObject() ||
2373 JS::GetClass(&args
.thisv().toObject()) != &sJSKeyedHistogramClass
) {
2374 JS_ReportErrorASCII(cx
, "Wrong JS class, expected JSKeyedHistogram class");
2378 JSObject
* obj
= &args
.thisv().toObject();
2379 JSHistogramData
* data
= GetJSKeyedHistogramData(obj
);
2381 HistogramID id
= data
->histogramId
;
2383 // This function should always return |undefined| and never fail but
2384 // rather report failures using the console.
2385 args
.rval().setUndefined();
2387 nsAutoString storeName
;
2388 nsresult rv
= internal_JS_StoreFromObjectArgument(cx
, args
, storeName
);
2389 if (NS_FAILED(rv
)) {
2393 KeyedHistogram
* keyed
= nullptr;
2395 MOZ_ASSERT(internal_IsHistogramEnumId(id
));
2396 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2398 // This is not good standard behavior given that we have histogram instances
2399 // covering multiple processes.
2400 // However, changing this requires some broader changes to callers.
2401 keyed
= internal_GetKeyedHistogramById(id
, ProcessID::Parent
,
2402 /* instantiate = */ false);
2408 keyed
->Clear(NS_ConvertUTF16toUTF8(storeName
));
2414 // NOTE: Runs without protection from |gTelemetryHistogramMutex|.
2415 // See comment at the top of this section.
2416 nsresult
internal_WrapAndReturnKeyedHistogram(
2417 HistogramID id
, JSContext
* cx
, JS::MutableHandle
<JS::Value
> ret
) {
2418 JS::Rooted
<JSObject
*> obj(cx
, JS_NewObject(cx
, &sJSKeyedHistogramClass
));
2419 if (!obj
) return NS_ERROR_FAILURE
;
2420 // The 6 functions that are wrapped up here are eventually called
2421 // by the same thread that runs this function.
2422 if (!(JS_DefineFunction(cx
, obj
, "add", internal_JSKeyedHistogram_Add
, 2,
2424 JS_DefineFunction(cx
, obj
, "name", internal_JSKeyedHistogram_Name
, 1,
2426 JS_DefineFunction(cx
, obj
, "snapshot",
2427 internal_JSKeyedHistogram_Snapshot
, 1, 0) &&
2428 JS_DefineFunction(cx
, obj
, "keys", internal_JSKeyedHistogram_Keys
, 1,
2430 JS_DefineFunction(cx
, obj
, "clear", internal_JSKeyedHistogram_Clear
, 1,
2432 return NS_ERROR_FAILURE
;
2435 JSHistogramData
* data
= new JSHistogramData
{id
};
2436 JS::SetReservedSlot(obj
, HistogramObjectDataSlot
, JS::PrivateValue(data
));
2437 ret
.setObject(*obj
);
2442 void internal_JSKeyedHistogram_finalize(JS::GCContext
* gcx
, JSObject
* obj
) {
2443 if (!obj
|| JS::GetClass(obj
) != &sJSKeyedHistogramClass
) {
2444 MOZ_ASSERT_UNREACHABLE("Should have the right JS class.");
2448 JSHistogramData
* data
= GetJSKeyedHistogramData(obj
);
2455 ////////////////////////////////////////////////////////////////////////
2456 ////////////////////////////////////////////////////////////////////////
2458 // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryHistogram::
2460 // All of these functions are actually in namespace TelemetryHistogram::,
2461 // but the ::TelemetryHistogram prefix is given explicitly. This is
2462 // because it is critical to see which calls from these functions are
2463 // to another function in this interface. Mis-identifying "inwards
2464 // calls" from "calls to another function in this interface" will lead
2465 // to deadlocking and/or races. See comments at the top of the file
2466 // for further (important!) details.
2468 void TelemetryHistogram::InitializeGlobalState(bool canRecordBase
,
2469 bool canRecordExtended
) {
2470 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2471 MOZ_ASSERT(!gTelemetryHistogramInitDone
,
2472 "TelemetryHistogram::InitializeGlobalState "
2473 "may only be called once");
2475 gTelemetryHistogramCanRecordBase
= canRecordBase
;
2476 gTelemetryHistogramCanRecordExtended
= canRecordExtended
;
2478 if (XRE_IsParentProcess()) {
2480 new Histogram
* [HistogramCount
* size_t(ProcessID::Count
)] {};
2481 gKeyedHistogramStorage
=
2482 new KeyedHistogram
* [HistogramCount
* size_t(ProcessID::Count
)] {};
2485 // Some Telemetry histograms depend on the value of C++ constants and hardcode
2486 // their values in Histograms.json.
2487 // We add static asserts here for those values to match so that future changes
2488 // don't go unnoticed.
2490 static_assert((uint32_t(JS::GCReason::NUM_TELEMETRY_REASONS
) + 1) ==
2491 gHistogramInfos
[mozilla::Telemetry::GC_MINOR_REASON
].bucketCount
&&
2492 (uint32_t(JS::GCReason::NUM_TELEMETRY_REASONS
) + 1) ==
2493 gHistogramInfos
[mozilla::Telemetry::GC_MINOR_REASON_LONG
].bucketCount
&&
2494 (uint32_t(JS::GCReason::NUM_TELEMETRY_REASONS
) + 1) ==
2495 gHistogramInfos
[mozilla::Telemetry::GC_REASON_2
].bucketCount
,
2496 "NUM_TELEMETRY_REASONS is assumed to be a fixed value in Histograms.json."
2497 " If this was an intentional change, update the n_values for the "
2498 "following in Histograms.json: GC_MINOR_REASON, GC_MINOR_REASON_LONG, "
2503 gTelemetryHistogramInitDone
= true;
2506 void TelemetryHistogram::DeInitializeGlobalState() {
2507 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2508 gTelemetryHistogramCanRecordBase
= false;
2509 gTelemetryHistogramCanRecordExtended
= false;
2510 gTelemetryHistogramInitDone
= false;
2512 // FactoryGet `new`s Histograms for us, but requires us to manually delete.
2513 if (XRE_IsParentProcess()) {
2514 for (size_t i
= 0; i
< HistogramCount
* size_t(ProcessID::Count
); ++i
) {
2515 if (gKeyedHistogramStorage
[i
] != gExpiredKeyedHistogram
) {
2516 delete gKeyedHistogramStorage
[i
];
2518 if (gHistogramStorage
[i
] != gExpiredHistogram
) {
2519 delete gHistogramStorage
[i
];
2522 delete[] gHistogramStorage
;
2523 delete[] gKeyedHistogramStorage
;
2525 delete gExpiredHistogram
;
2526 gExpiredHistogram
= nullptr;
2527 delete gExpiredKeyedHistogram
;
2528 gExpiredKeyedHistogram
= nullptr;
2532 bool TelemetryHistogram::GlobalStateHasBeenInitialized() {
2533 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2534 return gTelemetryHistogramInitDone
;
2538 bool TelemetryHistogram::CanRecordBase() {
2539 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2540 return internal_CanRecordBase();
2543 void TelemetryHistogram::SetCanRecordBase(bool b
) {
2544 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2545 gTelemetryHistogramCanRecordBase
= b
;
2548 bool TelemetryHistogram::CanRecordExtended() {
2549 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2550 return internal_CanRecordExtended();
2553 void TelemetryHistogram::SetCanRecordExtended(bool b
) {
2554 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2555 gTelemetryHistogramCanRecordExtended
= b
;
2558 void TelemetryHistogram::InitHistogramRecordingEnabled() {
2559 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2560 auto processType
= XRE_GetProcessType();
2561 for (size_t i
= 0; i
< HistogramCount
; ++i
) {
2562 const HistogramInfo
& h
= gHistogramInfos
[i
];
2563 mozilla::Telemetry::HistogramID id
= mozilla::Telemetry::HistogramID(i
);
2564 bool canRecordInProcess
=
2565 CanRecordInProcess(h
.record_in_processes
, processType
);
2566 internal_SetHistogramRecordingEnabled(locker
, id
, canRecordInProcess
);
2570 void TelemetryHistogram::Accumulate(HistogramID aID
, uint32_t aSample
) {
2571 if (NS_WARN_IF(!internal_IsHistogramEnumId(aID
))) {
2572 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
2576 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2577 internal_Accumulate(locker
, aID
, aSample
);
2580 void TelemetryHistogram::Accumulate(HistogramID aID
,
2581 const nsTArray
<uint32_t>& aSamples
) {
2582 if (NS_WARN_IF(!internal_IsHistogramEnumId(aID
))) {
2583 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
2587 MOZ_ASSERT(!gHistogramInfos
[aID
].keyed
,
2588 "Cannot accumulate into a keyed histogram. No key given.");
2590 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2591 for (uint32_t sample
: aSamples
) {
2592 internal_Accumulate(locker
, aID
, sample
);
2596 void TelemetryHistogram::Accumulate(HistogramID aID
, const nsCString
& aKey
,
2598 if (NS_WARN_IF(!internal_IsHistogramEnumId(aID
))) {
2599 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
2603 // Check if we're allowed to record in the provided key, for this histogram.
2604 if (!gHistogramInfos
[aID
].allows_key(aKey
)) {
2605 nsPrintfCString
msg("%s - key '%s' not allowed for this keyed histogram",
2606 gHistogramInfos
[aID
].name(), aKey
.get());
2607 LogToBrowserConsole(nsIScriptError::errorFlag
, NS_ConvertUTF8toUTF16(msg
));
2608 TelemetryScalar::Add(mozilla::Telemetry::ScalarID::
2609 TELEMETRY_ACCUMULATE_UNKNOWN_HISTOGRAM_KEYS
,
2610 NS_ConvertASCIItoUTF16(gHistogramInfos
[aID
].name()),
2615 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2616 internal_Accumulate(locker
, aID
, aKey
, aSample
);
2619 void TelemetryHistogram::Accumulate(HistogramID aID
, const nsCString
& aKey
,
2620 const nsTArray
<uint32_t>& aSamples
) {
2621 if (NS_WARN_IF(!internal_IsHistogramEnumId(aID
))) {
2622 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids");
2626 // Check that this histogram is keyed
2627 MOZ_ASSERT(gHistogramInfos
[aID
].keyed
,
2628 "Cannot accumulate into a non-keyed histogram using a key.");
2630 // Check if we're allowed to record in the provided key, for this histogram.
2631 if (!gHistogramInfos
[aID
].allows_key(aKey
)) {
2632 nsPrintfCString
msg("%s - key '%s' not allowed for this keyed histogram",
2633 gHistogramInfos
[aID
].name(), aKey
.get());
2634 LogToBrowserConsole(nsIScriptError::errorFlag
, NS_ConvertUTF8toUTF16(msg
));
2635 TelemetryScalar::Add(mozilla::Telemetry::ScalarID::
2636 TELEMETRY_ACCUMULATE_UNKNOWN_HISTOGRAM_KEYS
,
2637 NS_ConvertASCIItoUTF16(gHistogramInfos
[aID
].name()),
2642 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2643 for (uint32_t sample
: aSamples
) {
2644 internal_Accumulate(locker
, aID
, aKey
, sample
);
2648 nsresult
TelemetryHistogram::Accumulate(const char* name
, uint32_t sample
) {
2649 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2650 if (!internal_CanRecordBase()) {
2651 return NS_ERROR_NOT_AVAILABLE
;
2655 internal_GetHistogramIdByName(locker
, nsDependentCString(name
), &id
);
2656 if (NS_FAILED(rv
)) {
2659 internal_Accumulate(locker
, id
, sample
);
2663 nsresult
TelemetryHistogram::Accumulate(const char* name
, const nsCString
& key
,
2665 bool keyNotAllowed
= false;
2668 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2669 if (!internal_CanRecordBase()) {
2670 return NS_ERROR_NOT_AVAILABLE
;
2674 internal_GetHistogramIdByName(locker
, nsDependentCString(name
), &id
);
2675 if (NS_SUCCEEDED(rv
)) {
2676 // Check if we're allowed to record in the provided key, for this
2678 if (gHistogramInfos
[id
].allows_key(key
)) {
2679 internal_Accumulate(locker
, id
, key
, sample
);
2682 // We're holding |gTelemetryHistogramMutex|, so we can't print a message
2684 keyNotAllowed
= true;
2688 if (keyNotAllowed
) {
2689 LogToBrowserConsole(nsIScriptError::errorFlag
,
2690 u
"Key not allowed for this keyed histogram"_ns
);
2691 TelemetryScalar::Add(mozilla::Telemetry::ScalarID::
2692 TELEMETRY_ACCUMULATE_UNKNOWN_HISTOGRAM_KEYS
,
2693 NS_ConvertASCIItoUTF16(name
), 1);
2695 return NS_ERROR_FAILURE
;
2698 void TelemetryHistogram::AccumulateCategorical(HistogramID aId
,
2699 const nsCString
& label
) {
2700 if (NS_WARN_IF(!internal_IsHistogramEnumId(aId
))) {
2701 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
2705 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2706 if (!internal_CanRecordBase()) {
2709 uint32_t labelId
= 0;
2710 if (NS_FAILED(gHistogramInfos
[aId
].label_id(label
.get(), &labelId
))) {
2713 internal_Accumulate(locker
, aId
, labelId
);
2716 void TelemetryHistogram::AccumulateCategorical(
2717 HistogramID aId
, const nsTArray
<nsCString
>& aLabels
) {
2718 if (NS_WARN_IF(!internal_IsHistogramEnumId(aId
))) {
2719 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
2723 if (!internal_CanRecordBase()) {
2727 // We use two loops, one for getting label_ids and another one for actually
2728 // accumulating the values. This ensures that in the case of an invalid label
2729 // in the array, no values are accumulated. In any call to this API, either
2730 // all or (in case of error) none of the values will be accumulated.
2732 nsTArray
<uint32_t> intSamples(aLabels
.Length());
2733 for (const nsCString
& label
: aLabels
) {
2734 uint32_t labelId
= 0;
2735 if (NS_FAILED(gHistogramInfos
[aId
].label_id(label
.get(), &labelId
))) {
2738 intSamples
.AppendElement(labelId
);
2741 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2743 for (uint32_t sample
: intSamples
) {
2744 internal_Accumulate(locker
, aId
, sample
);
2748 void TelemetryHistogram::AccumulateChild(
2749 ProcessID aProcessType
,
2750 const nsTArray
<HistogramAccumulation
>& aAccumulations
) {
2751 MOZ_ASSERT(XRE_IsParentProcess());
2753 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2754 if (!internal_CanRecordBase()) {
2757 for (uint32_t i
= 0; i
< aAccumulations
.Length(); ++i
) {
2758 if (NS_WARN_IF(!internal_IsHistogramEnumId(aAccumulations
[i
].mId
))) {
2759 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
2762 internal_AccumulateChild(locker
, aProcessType
, aAccumulations
[i
].mId
,
2763 aAccumulations
[i
].mSample
);
2767 void TelemetryHistogram::AccumulateChildKeyed(
2768 ProcessID aProcessType
,
2769 const nsTArray
<KeyedHistogramAccumulation
>& aAccumulations
) {
2770 MOZ_ASSERT(XRE_IsParentProcess());
2771 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2772 if (!internal_CanRecordBase()) {
2775 for (uint32_t i
= 0; i
< aAccumulations
.Length(); ++i
) {
2776 if (NS_WARN_IF(!internal_IsHistogramEnumId(aAccumulations
[i
].mId
))) {
2777 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
2780 internal_AccumulateChildKeyed(locker
, aProcessType
, aAccumulations
[i
].mId
,
2781 aAccumulations
[i
].mKey
,
2782 aAccumulations
[i
].mSample
);
2786 nsresult
TelemetryHistogram::GetAllStores(StringHashSet
& set
) {
2787 for (uint32_t storeIdx
: gHistogramStoresTable
) {
2788 const char* name
= &gHistogramStringTable
[storeIdx
];
2789 nsAutoCString store
;
2790 store
.AssignASCII(name
);
2791 if (!set
.Insert(store
, mozilla::fallible
)) {
2792 return NS_ERROR_FAILURE
;
2798 nsresult
TelemetryHistogram::GetCategoricalHistogramLabels(
2799 JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aResult
) {
2800 JS::Rooted
<JSObject
*> root_obj(aCx
, JS_NewPlainObject(aCx
));
2802 return NS_ERROR_FAILURE
;
2804 aResult
.setObject(*root_obj
);
2806 for (const HistogramInfo
& info
: gHistogramInfos
) {
2807 if (info
.histogramType
!= nsITelemetry::HISTOGRAM_CATEGORICAL
) {
2811 const char* name
= info
.name();
2812 JS::Rooted
<JSObject
*> labels(aCx
,
2813 JS::NewArrayObject(aCx
, info
.label_count
));
2815 return NS_ERROR_FAILURE
;
2818 if (!JS_DefineProperty(aCx
, root_obj
, name
, labels
, JSPROP_ENUMERATE
)) {
2819 return NS_ERROR_FAILURE
;
2822 for (uint32_t i
= 0; i
< info
.label_count
; ++i
) {
2823 uint32_t string_offset
= gHistogramLabelTable
[info
.label_index
+ i
];
2824 const char* const label
= &gHistogramStringTable
[string_offset
];
2825 auto clabel
= NS_ConvertASCIItoUTF16(label
);
2826 JS::Rooted
<JS::Value
> value(aCx
);
2827 value
.setString(ToJSString(aCx
, clabel
));
2828 if (!JS_DefineElement(aCx
, labels
, i
, value
, JSPROP_ENUMERATE
)) {
2829 return NS_ERROR_FAILURE
;
2837 nsresult
TelemetryHistogram::GetHistogramById(
2838 const nsACString
& name
, JSContext
* cx
, JS::MutableHandle
<JS::Value
> ret
) {
2841 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2842 nsresult rv
= internal_GetHistogramIdByName(locker
, name
, &id
);
2843 if (NS_FAILED(rv
)) {
2844 return NS_ERROR_FAILURE
;
2847 if (gHistogramInfos
[id
].keyed
) {
2848 return NS_ERROR_FAILURE
;
2851 // Runs without protection from |gTelemetryHistogramMutex|
2852 return internal_WrapAndReturnHistogram(id
, cx
, ret
);
2855 nsresult
TelemetryHistogram::GetKeyedHistogramById(
2856 const nsACString
& name
, JSContext
* cx
, JS::MutableHandle
<JS::Value
> ret
) {
2859 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2860 nsresult rv
= internal_GetHistogramIdByName(locker
, name
, &id
);
2861 if (NS_FAILED(rv
)) {
2862 return NS_ERROR_FAILURE
;
2865 if (!gHistogramInfos
[id
].keyed
) {
2866 return NS_ERROR_FAILURE
;
2869 // Runs without protection from |gTelemetryHistogramMutex|
2870 return internal_WrapAndReturnKeyedHistogram(id
, cx
, ret
);
2873 const char* TelemetryHistogram::GetHistogramName(HistogramID id
) {
2874 if (NS_WARN_IF(!internal_IsHistogramEnumId(id
))) {
2875 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
2879 const HistogramInfo
& h
= gHistogramInfos
[id
];
2883 uint8_t TelemetryHistogram::GetHistogramType(HistogramID id
) {
2884 if (NS_WARN_IF(!internal_IsHistogramEnumId(id
))) {
2885 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
2886 return std::numeric_limits
<uint8_t>::max();
2889 const HistogramInfo
& h
= gHistogramInfos
[id
];
2890 return h
.histogramType
;
2893 nsresult
TelemetryHistogram::CreateHistogramSnapshots(
2894 JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aResult
,
2895 const nsACString
& aStore
, unsigned int aDataset
, bool aClearSubsession
,
2897 if (!XRE_IsParentProcess()) {
2898 return NS_ERROR_FAILURE
;
2901 // Runs without protection from |gTelemetryHistogramMutex|
2902 JS::Rooted
<JSObject
*> root_obj(aCx
, JS_NewPlainObject(aCx
));
2904 return NS_ERROR_FAILURE
;
2906 aResult
.setObject(*root_obj
);
2908 // Include the GPU process in histogram snapshots only if we actually tried
2909 // to launch a process for it.
2910 bool includeGPUProcess
= internal_AttemptedGPUProcess();
2912 HistogramProcessSnapshotsArray processHistArray
;
2914 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2915 nsresult rv
= internal_GetHistogramsSnapshot(
2916 locker
, aStore
, aDataset
, aClearSubsession
, includeGPUProcess
,
2917 aFilterTest
, processHistArray
);
2918 if (NS_FAILED(rv
)) {
2923 // Make the JS calls on the stashed histograms for every process
2924 for (uint32_t process
= 0; process
< processHistArray
.length(); ++process
) {
2925 JS::Rooted
<JSObject
*> processObject(aCx
, JS_NewPlainObject(aCx
));
2926 if (!processObject
) {
2927 return NS_ERROR_FAILURE
;
2929 if (!JS_DefineProperty(aCx
, root_obj
,
2930 GetNameForProcessID(ProcessID(process
)),
2931 processObject
, JSPROP_ENUMERATE
)) {
2932 return NS_ERROR_FAILURE
;
2935 for (const HistogramSnapshotInfo
& hData
: processHistArray
[process
]) {
2936 HistogramID id
= hData
.histogramID
;
2938 JS::Rooted
<JSObject
*> hobj(aCx
, JS_NewPlainObject(aCx
));
2940 return NS_ERROR_FAILURE
;
2943 if (NS_FAILED(internal_ReflectHistogramAndSamples(
2944 aCx
, hobj
, gHistogramInfos
[id
], hData
.data
))) {
2945 return NS_ERROR_FAILURE
;
2948 if (!JS_DefineProperty(aCx
, processObject
, gHistogramInfos
[id
].name(),
2949 hobj
, JSPROP_ENUMERATE
)) {
2950 return NS_ERROR_FAILURE
;
2957 nsresult
TelemetryHistogram::GetKeyedHistogramSnapshots(
2958 JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aResult
,
2959 const nsACString
& aStore
, unsigned int aDataset
, bool aClearSubsession
,
2961 if (!XRE_IsParentProcess()) {
2962 return NS_ERROR_FAILURE
;
2965 // Runs without protection from |gTelemetryHistogramMutex|
2966 JS::Rooted
<JSObject
*> obj(aCx
, JS_NewPlainObject(aCx
));
2968 return NS_ERROR_FAILURE
;
2970 aResult
.setObject(*obj
);
2972 // Include the GPU process in histogram snapshots only if we actually tried
2973 // to launch a process for it.
2974 bool includeGPUProcess
= internal_AttemptedGPUProcess();
2976 // Get a snapshot of all the data while holding the mutex.
2977 KeyedHistogramProcessSnapshotsArray processHistArray
;
2979 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
2980 nsresult rv
= internal_GetKeyedHistogramsSnapshot(
2981 locker
, aStore
, aDataset
, aClearSubsession
, includeGPUProcess
,
2982 aFilterTest
, processHistArray
);
2983 if (NS_FAILED(rv
)) {
2988 // Mirror the snapshot data to JS, now that we released the mutex.
2989 for (uint32_t process
= 0; process
< processHistArray
.length(); ++process
) {
2990 JS::Rooted
<JSObject
*> processObject(aCx
, JS_NewPlainObject(aCx
));
2991 if (!processObject
) {
2992 return NS_ERROR_FAILURE
;
2994 if (!JS_DefineProperty(aCx
, obj
, GetNameForProcessID(ProcessID(process
)),
2995 processObject
, JSPROP_ENUMERATE
)) {
2996 return NS_ERROR_FAILURE
;
2999 for (const KeyedHistogramSnapshotInfo
& hData
: processHistArray
[process
]) {
3000 const HistogramInfo
& info
= gHistogramInfos
[hData
.histogramId
];
3002 JS::Rooted
<JSObject
*> snapshot(aCx
, JS_NewPlainObject(aCx
));
3004 return NS_ERROR_FAILURE
;
3007 if (!NS_SUCCEEDED(internal_ReflectKeyedHistogram(hData
.data
, info
, aCx
,
3009 return NS_ERROR_FAILURE
;
3012 if (!JS_DefineProperty(aCx
, processObject
, info
.name(), snapshot
,
3013 JSPROP_ENUMERATE
)) {
3014 return NS_ERROR_FAILURE
;
3021 size_t TelemetryHistogram::GetHistogramSizesOfIncludingThis(
3022 mozilla::MallocSizeOf aMallocSizeOf
) {
3023 StaticMutexAutoLock
locker(gTelemetryHistogramMutex
);
3027 // If we allocated the array, let's count the number of pointers in there and
3028 // each entry's size.
3029 if (gKeyedHistogramStorage
) {
3030 n
+= HistogramCount
* size_t(ProcessID::Count
) * sizeof(KeyedHistogram
*);
3031 for (size_t i
= 0; i
< HistogramCount
* size_t(ProcessID::Count
); ++i
) {
3032 if (gKeyedHistogramStorage
[i
] &&
3033 gKeyedHistogramStorage
[i
] != gExpiredKeyedHistogram
) {
3034 n
+= gKeyedHistogramStorage
[i
]->SizeOfIncludingThis(aMallocSizeOf
);
3039 // If we allocated the array, let's count the number of pointers in there.
3040 if (gHistogramStorage
) {
3041 n
+= HistogramCount
* size_t(ProcessID::Count
) * sizeof(Histogram
*);
3042 for (size_t i
= 0; i
< HistogramCount
* size_t(ProcessID::Count
); ++i
) {
3043 if (gHistogramStorage
[i
] && gHistogramStorage
[i
] != gExpiredHistogram
) {
3044 n
+= gHistogramStorage
[i
]->SizeOfIncludingThis(aMallocSizeOf
);
3049 // We only allocate the expired (keyed) histogram once.
3050 if (gExpiredKeyedHistogram
) {
3051 n
+= gExpiredKeyedHistogram
->SizeOfIncludingThis(aMallocSizeOf
);
3054 if (gExpiredHistogram
) {
3055 n
+= gExpiredHistogram
->SizeOfIncludingThis(aMallocSizeOf
);