Backed out 7 changesets (bug 1942424) for causing frequent crashes. a=backout
[gecko.git] / toolkit / components / telemetry / core / TelemetryHistogram.cpp
bloba1e484c548e46b02d07b11117bc4bfef447b451a
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"
9 #include <limits>
10 #include "base/histogram.h"
11 #include "ipc/TelemetryIPCAccumulator.h"
12 #include "jsapi.h"
13 #include "jsfriendapi.h"
14 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject
15 #include "js/GCAPI.h"
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"
26 #include "nsString.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
89 // chain like this
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
117 // in BloatView.
118 static StaticMutex gTelemetryHistogramMutex MOZ_UNANNOTATED;
120 ////////////////////////////////////////////////////////////////////////
121 ////////////////////////////////////////////////////////////////////////
123 // PRIVATE TYPES
125 namespace {
127 // Hardcoded probes
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 {
134 uint32_t min;
135 uint32_t max;
136 uint32_t bucketCount;
137 uint32_t name_offset;
138 uint32_t expiration_offset;
139 uint32_t label_count;
140 uint32_t key_count;
141 uint32_t store_count;
142 uint16_t label_index;
143 uint16_t key_index;
144 uint16_t store_index;
145 RecordedProcessType record_in_processes;
146 bool keyed;
147 uint8_t histogramType;
148 uint8_t dataset;
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.
193 class Histogram {
194 public:
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);
201 ~Histogram();
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 {
219 if (mIsExpired) {
220 PROFILER_MARKER_TEXT("HistogramError", TELEMETRY,
221 mozilla::MarkerStack::Capture(),
222 "accessing expired histogram");
224 return mIsExpired;
227 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
229 private:
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.
239 bool mIsExpired;
242 class KeyedHistogram {
243 public:
244 KeyedHistogram(HistogramID id, const HistogramInfo& info, bool expired);
245 ~KeyedHistogram();
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 {
268 if (mIsExpired) {
269 PROFILER_MARKER_TEXT("HistogramError", TELEMETRY,
270 mozilla::MarkerStack::Capture(),
271 "accessing expired histogram");
273 return mIsExpired;
276 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
278 private:
279 typedef nsClassHashtable<nsCStringHashKey, base::Histogram>
280 KeyedHistogramMapType;
281 typedef nsClassHashtable<nsCStringHashKey, KeyedHistogramMapType>
282 StoreMapType;
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;
290 bool mIsExpired;
293 } // namespace
295 ////////////////////////////////////////////////////////////////////////
296 ////////////////////////////////////////////////////////////////////////
298 // PRIVATE STATE, SHARED BY ALL THREADS
300 namespace {
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
314 // instances.
315 KeyedHistogram** gKeyedHistogramStorage;
317 // To simplify logic below we use a single histogram instance for all expired
318 // histograms.
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"
331 } // namespace
333 ////////////////////////////////////////////////////////////////////////
334 ////////////////////////////////////////////////////////////////////////
336 // PRIVATE CONSTANTS
338 namespace {
340 const char* TEST_HISTOGRAM_PREFIX = "TELEMETRY_TEST_";
342 } // namespace
344 ////////////////////////////////////////////////////////////////////////
345 ////////////////////////////////////////////////////////////////////////
347 // The core storage access functions.
348 // They wrap access to the histogram storage and lookup caches.
350 namespace {
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) {
370 size_t index =
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.");
382 size_t index =
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,
409 int bucketsOffset);
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,
422 ProcessID processId,
423 bool instantiate = true) {
424 MOZ_ASSERT(internal_IsHistogramEnumId(histogramId));
425 MOZ_ASSERT(!gHistogramInfos[histogramId].keyed);
426 MOZ_ASSERT(processId < ProcessID::Count);
428 Histogram* h =
429 internal_GetHistogramFromStorage(aLock, histogramId, processId);
430 if (h || !instantiate) {
431 return h;
434 h = internal_CreateHistogramInstance(histogramId);
435 MOZ_ASSERT(h);
436 internal_SetHistogramInStorage(aLock, histogramId, processId, h);
438 return h;
441 // Look up a keyed histogram by id.
442 KeyedHistogram* internal_GetKeyedHistogramById(HistogramID histogramId,
443 ProcessID processId,
444 bool instantiate = true) {
445 MOZ_ASSERT(internal_IsHistogramEnumId(histogramId));
446 MOZ_ASSERT(gHistogramInfos[histogramId].keyed);
447 MOZ_ASSERT(processId < ProcessID::Count);
449 KeyedHistogram* kh =
450 internal_GetKeyedHistogramFromStorage(histogramId, processId);
451 if (kh || !instantiate) {
452 return kh;
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
459 // keyed histogram.
460 if (isExpired) {
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;
468 } else {
469 kh = new KeyedHistogram(histogramId, info, false /* expired */);
472 internal_SetKeyedHistogramInStorage(histogramId, processId, kh);
474 return kh;
477 // Look up a histogram id from a histogram name.
478 nsresult internal_GetHistogramIdByName(const StaticMutexAutoLock& aLock,
479 const nsACString& name,
480 HistogramID* id) {
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);
490 return NS_OK;
493 return NS_ERROR_ILLEGAL_VALUE;
496 } // namespace
498 ////////////////////////////////////////////////////////////////////////
499 ////////////////////////////////////////////////////////////////////////
501 // PRIVATE: Misc small helpers
503 namespace {
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 {
546 MOZ_ASSERT(label);
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) {
560 *labelId = i;
561 return NS_OK;
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) {
573 return true;
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)) {
584 return true;
588 // |key| was not found.
589 return false;
592 bool HistogramInfo::is_single_store() const {
593 return store_count == 1 && store_index == UINT16_MAX;
596 } // namespace
598 ////////////////////////////////////////////////////////////////////////
599 ////////////////////////////////////////////////////////////////////////
601 // PRIVATE: Histogram Get, Add, Clone, Clear functions
603 namespace {
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;
618 if (info.min < 1) {
619 return NS_ERROR_ILLEGAL_VALUE;
623 return NS_OK;
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.");
631 return nullptr;
634 const bool isExpired = IsExpiredVersion(info.expiration());
636 if (isExpired) {
637 if (!gExpiredHistogram) {
638 gExpiredHistogram = new Histogram(histogramId, info, /* expired */ true);
641 return gExpiredHistogram;
644 Histogram* wrapper = new Histogram(histogramId, info, /* expired */ false);
646 return wrapper;
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.");
653 return nullptr;
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,
667 flags, buckets);
668 break;
669 case nsITelemetry::HISTOGRAM_LINEAR:
670 case nsITelemetry::HISTOGRAM_CATEGORICAL:
671 h = LinearHistogram::FactoryGet(info.min, info.max, info.bucketCount,
672 flags, buckets);
673 break;
674 case nsITelemetry::HISTOGRAM_BOOLEAN:
675 h = BooleanHistogram::FactoryGet(flags, buckets);
676 break;
677 case nsITelemetry::HISTOGRAM_FLAG:
678 h = FlagHistogram::FactoryGet(flags, buckets);
679 break;
680 case nsITelemetry::HISTOGRAM_COUNT:
681 h = CountHistogram::FactoryGet(flags, buckets);
682 break;
683 default:
684 MOZ_ASSERT(false, "Invalid histogram type");
685 return nullptr;
688 return h;
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) {
699 return NS_OK;
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()));
708 return NS_OK;
711 // Don't record if the current platform is not enabled
712 if (!CanRecordProduct(h.products)) {
713 return NS_OK;
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);
723 value = INT_MAX;
726 histogram.Add(value);
728 return NS_OK;
731 } // namespace
733 ////////////////////////////////////////////////////////////////////////
734 ////////////////////////////////////////////////////////////////////////
736 // PRIVATE: Histogram reflection helpers
738 namespace {
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
747 * snapshot.
749 nsresult internal_GetHistogramAndSamples(const StaticMutexAutoLock& aLock,
750 const base::Histogram* h,
751 HistogramSnapshotData& aSnapshot) {
752 MOZ_ASSERT(h);
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();
775 return NS_OK;
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,
794 JSPROP_ENUMERATE) &&
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
804 // histograms.
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;
829 bool first = true;
830 size_t last = 0;
832 for (size_t i = 0; i < count; i++) {
833 auto value = aSnapshot.mBucketCounts[i];
834 if (value == 0) {
835 continue;
838 if (i > 0 && first) {
839 auto range = aSnapshot.mBucketRanges[i - 1];
840 if (!JS_DefineProperty(cx, values, nsPrintfCString("%d", range).get(), 0,
841 JSPROP_ENUMERATE)) {
842 return NS_ERROR_FAILURE;
846 first = false;
847 last = i + 1;
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,
859 JSPROP_ENUMERATE)) {
860 return NS_ERROR_FAILURE;
864 return NS_OK;
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
872 // of scalars.
873 uint32_t type = gHistogramInfos[id].histogramType;
874 if (internal_IsEmpty(aLock, h) && type != nsITelemetry::HISTOGRAM_FLAG) {
875 return false;
878 // Don't reflect the histogram if it's not allowed in this product.
879 if (!CanRecordProduct(gHistogramInfos[id].products)) {
880 return false;
883 return true;
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
896 * stored.
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);
909 ++process) {
910 HistogramSnapshotsArray& hArray = aOutSnapshot[process];
912 for (size_t i = 0; i < HistogramCount; ++i) {
913 const HistogramInfo& info = gHistogramInfos[i];
914 if (info.keyed) {
915 continue;
918 HistogramID id = HistogramID(i);
920 if (!CanRecordInProcess(info.record_in_processes, ProcessID(process)) ||
921 ((ProcessID(process) == ProcessID::Gpu) && !aIncludeGPU)) {
922 continue;
925 if (!IsInDataset(info.dataset, aDataset)) {
926 continue;
929 bool shouldInstantiate =
930 info.histogramType == nsITelemetry::HISTOGRAM_FLAG;
931 Histogram* w = internal_GetHistogramById(aLock, id, ProcessID(process),
932 shouldInstantiate);
933 if (!w || w->IsExpired()) {
934 continue;
937 base::Histogram* h = nullptr;
938 if (!w->GetHistogram(aStore, &h)) {
939 continue;
942 if (!internal_ShouldReflectHistogram(aLock, h, id)) {
943 continue;
946 const char* name = info.name();
947 if (aFilterTest && strncmp(TEST_HISTOGRAM_PREFIX, name,
948 strlen(TEST_HISTOGRAM_PREFIX)) == 0) {
949 if (aClearSubsession) {
950 h->Clear();
952 continue;
955 HistogramSnapshotData snapshotData;
956 if (NS_FAILED(internal_GetHistogramAndSamples(aLock, h, snapshotData))) {
957 continue;
960 if (!hArray.emplaceBack(HistogramSnapshotInfo{snapshotData, id})) {
961 return NS_ERROR_OUT_OF_MEMORY;
964 if (aClearSubsession) {
965 h->Clear();
969 return NS_OK;
972 } // namespace
974 ////////////////////////////////////////////////////////////////////////
975 ////////////////////////////////////////////////////////////////////////
977 // PRIVATE: class Histogram
979 namespace {
981 Histogram::Histogram(HistogramID histogramId, const HistogramInfo& info,
982 bool expired)
983 : mSingleStore(nullptr), mIsExpired(expired) {
984 if (IsExpired()) {
985 return;
988 const int bucketsOffset = gHistogramBucketLowerBoundIndex[histogramId];
990 if (info.is_single_store()) {
991 mSingleStore = internal_CreateBaseHistogramInstance(info, bucketsOffset);
992 } else {
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()) {
1009 return;
1012 if (IsExpired()) {
1013 return;
1016 if (mSingleStore != nullptr) {
1017 mSingleStore->Add(sample);
1018 } else {
1019 for (auto iter = mStorage.Iter(); !iter.Done(); iter.Next()) {
1020 auto& h = iter.Data();
1021 h->Add(sample);
1026 void Histogram::Clear(const nsACString& store) {
1027 MOZ_ASSERT(XRE_IsParentProcess(),
1028 "Only clear histograms in the parent process");
1029 if (!XRE_IsParentProcess()) {
1030 return;
1033 if (mSingleStore != nullptr) {
1034 if (store.EqualsASCII("main")) {
1035 mSingleStore->Clear();
1037 } else {
1038 base::Histogram* h = nullptr;
1039 bool found = GetHistogram(store, &h);
1040 if (!found) {
1041 return;
1043 MOZ_ASSERT(h, "Should have found a valid histogram in the named store");
1045 h->Clear();
1049 bool Histogram::GetHistogram(const nsACString& store, base::Histogram** h) {
1050 MOZ_ASSERT(!IsExpired());
1051 if (IsExpired()) {
1052 return false;
1055 if (mSingleStore != nullptr) {
1056 if (store.EqualsASCII("main")) {
1057 *h = mSingleStore;
1058 return true;
1061 return false;
1064 return mStorage.Get(store, h);
1067 size_t Histogram::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
1068 size_t n = 0;
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);
1084 return n;
1087 } // namespace
1089 ////////////////////////////////////////////////////////////////////////
1090 ////////////////////////////////////////////////////////////////////////
1092 // PRIVATE: class KeyedHistogram and internal_ReflectKeyedHistogram
1094 namespace {
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,
1108 info, keyData))) {
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;
1119 return NS_OK;
1122 KeyedHistogram::KeyedHistogram(HistogramID id, const HistogramInfo& info,
1123 bool expired)
1124 : mSingleStore(nullptr),
1125 mId(id),
1126 mHistogramInfo(info),
1127 mIsExpired(expired) {
1128 if (IsExpired()) {
1129 return;
1132 if (info.is_single_store()) {
1133 mSingleStore = new KeyedHistogramMapType;
1134 } else {
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) {
1148 if (IsExpired()) {
1149 MOZ_ASSERT(false,
1150 "KeyedHistogram::GetHistogram called on an expired histogram.");
1151 return NS_ERROR_FAILURE;
1154 KeyedHistogramMapType* histogramMap;
1155 bool found;
1157 if (mSingleStore != nullptr) {
1158 histogramMap = mSingleStore;
1159 } else {
1160 found = mStorage.Get(aStore, &histogramMap);
1161 if (!found) {
1162 return NS_ERROR_FAILURE;
1166 found = histogramMap->Get(key, histogram);
1167 if (found) {
1168 return NS_OK;
1171 int bucketsOffset = gHistogramBucketLowerBoundIndex[mId];
1172 auto h = UniquePtr<base::Histogram>{
1173 internal_CreateBaseHistogramInstance(mHistogramInfo, bucketsOffset)};
1174 if (!h) {
1175 return NS_ERROR_FAILURE;
1178 h->ClearFlags(base::Histogram::kUmaTargetedHistogramFlag);
1179 *histogram = h.get();
1181 bool inserted =
1182 histogramMap->InsertOrUpdate(key, std::move(h), mozilla::fallible);
1183 if (MOZ_UNLIKELY(!inserted)) {
1184 return NS_ERROR_OUT_OF_MEMORY;
1186 return NS_OK;
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))) {
1193 return nullptr;
1195 return 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))) {
1213 return NS_OK;
1216 // Don't record if expired.
1217 if (IsExpired()) {
1218 return NS_OK;
1221 // Don't record if the current platform is not enabled
1222 if (!CanRecordProduct(gHistogramInfos[mId].products)) {
1223 return NS_OK;
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);
1233 sample = INT_MAX;
1236 base::Histogram* histogram;
1237 if (mSingleStore != nullptr) {
1238 histogram = GetHistogram("main"_ns, key);
1239 if (!histogram) {
1240 MOZ_ASSERT(false, "Missing histogram in single store.");
1241 return NS_ERROR_FAILURE;
1244 histogram->Add(sample);
1245 } else {
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);
1252 if (histogram) {
1253 histogram->Add(sample);
1254 } else {
1255 return NS_ERROR_FAILURE;
1260 return NS_OK;
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()) {
1267 return;
1270 if (IsExpired()) {
1271 return;
1274 if (mSingleStore) {
1275 if (aStore.EqualsASCII("main")) {
1276 mSingleStore->Clear();
1278 return;
1281 KeyedHistogramMapType* histogramMap;
1282 bool found = mStorage.Get(aStore, &histogramMap);
1283 if (!found) {
1284 return;
1287 histogramMap->Clear();
1290 bool KeyedHistogram::IsEmpty(const nsACString& aStore) const {
1291 if (mSingleStore != nullptr) {
1292 if (aStore.EqualsASCII("main")) {
1293 return mSingleStore->IsEmpty();
1296 return true;
1299 KeyedHistogramMapType* histogramMap;
1300 bool found = mStorage.Get(aStore, &histogramMap);
1301 if (!found) {
1302 return true;
1304 return histogramMap->IsEmpty();
1307 size_t KeyedHistogram::SizeOfIncludingThis(
1308 mozilla::MallocSizeOf aMallocSizeOf) {
1309 size_t n = 0;
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);
1325 return n;
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;
1334 } else {
1335 bool found = mStorage.Get(store, &histogramMap);
1336 if (!found) {
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;
1351 return NS_OK;
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)) {
1367 return rv;
1371 // Now that we have a copy of the data, mirror it to JS.
1372 return internal_ReflectKeyedHistogram(dataSnapshot, gHistogramInfos[mId], cx,
1373 obj);
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
1382 * returned.
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;
1395 } else {
1396 bool found = mStorage.Get(aStore, &histogramMap);
1397 if (!found) {
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();
1406 if (!keyData) {
1407 return NS_ERROR_FAILURE;
1410 HistogramSnapshotData keySnapshot;
1411 if (NS_FAILED(
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) {
1421 Clear(aStore);
1424 return NS_OK;
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
1436 * stored.
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);
1449 ++process) {
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];
1455 if (!info.keyed) {
1456 continue;
1459 if (!CanRecordInProcess(info.record_in_processes, ProcessID(process)) ||
1460 ((ProcessID(process) == ProcessID::Gpu) && !aIncludeGPU)) {
1461 continue;
1464 if (!IsInDataset(info.dataset, aDataset)) {
1465 continue;
1468 KeyedHistogram* keyed =
1469 internal_GetKeyedHistogramById(id, ProcessID(process),
1470 /* instantiate = */ false);
1471 if (!keyed || keyed->IsEmpty(aStore) || keyed->IsExpired()) {
1472 continue;
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);
1481 continue;
1484 // Take a snapshot of the keyed histogram data!
1485 KeyedHistogramSnapshotData snapshot;
1486 if (!NS_SUCCEEDED(
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;
1497 return NS_OK;
1500 } // namespace
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,
1511 uint32_t aSample) {
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}");
1532 return schema;
1536 } // namespace geckoprofiler::markers
1538 ////////////////////////////////////////////////////////////////////////
1539 ////////////////////////////////////////////////////////////////////////
1541 // PRIVATE: thread-unsafe helpers for the external interface
1543 namespace {
1545 bool internal_RemoteAccumulate(const StaticMutexAutoLock& aLock,
1546 HistogramID aId, uint32_t aSample) {
1547 if (XRE_IsParentProcess()) {
1548 return false;
1551 if (!internal_IsRecordingEnabled(aId)) {
1552 return true;
1555 PROFILER_MARKER("Histogram::Add", TELEMETRY, {}, HistogramMarker, aId,
1556 EmptyCString(), aSample);
1557 TelemetryIPCAccumulator::AccumulateChildHistogram(aId, aSample);
1558 return true;
1561 bool internal_RemoteAccumulate(const StaticMutexAutoLock& aLock,
1562 HistogramID aId, const nsCString& aKey,
1563 uint32_t aSample) {
1564 if (XRE_IsParentProcess()) {
1565 return false;
1568 if (!internal_IsRecordingEnabled(aId)) {
1569 return true;
1572 PROFILER_MARKER("Histogram::Add", TELEMETRY, {}, HistogramMarker, aId, aKey,
1573 aSample);
1574 TelemetryIPCAccumulator::AccumulateChildKeyedHistogram(aId, aKey, aSample);
1575 return true;
1578 void internal_Accumulate(const StaticMutexAutoLock& aLock, HistogramID aId,
1579 uint32_t aSample) {
1580 if (!internal_CanRecordBase() ||
1581 internal_RemoteAccumulate(aLock, aId, aSample)) {
1582 return;
1585 PROFILER_MARKER("Histogram::Add", TELEMETRY, {}, HistogramMarker, aId,
1586 EmptyCString(), aSample);
1587 Histogram* w = internal_GetHistogramById(aLock, aId, ProcessID::Parent);
1588 MOZ_ASSERT(w);
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)) {
1596 return;
1599 PROFILER_MARKER("Histogram::Add", TELEMETRY, {}, HistogramMarker, aId, aKey,
1600 aSample);
1601 KeyedHistogram* keyed =
1602 internal_GetKeyedHistogramById(aId, ProcessID::Parent);
1603 MOZ_ASSERT(keyed);
1604 keyed->Add(aKey, aSample, ProcessID::Parent);
1607 void internal_AccumulateChild(const StaticMutexAutoLock& aLock,
1608 ProcessID aProcessType, HistogramID aId,
1609 uint32_t aSample) {
1610 if (!internal_CanRecordBase()) {
1611 return;
1614 PROFILER_MARKER("ChildHistogram::Add", TELEMETRY, {}, HistogramMarker, aId,
1615 EmptyCString(), aSample);
1616 Histogram* w = internal_GetHistogramById(aLock, aId, aProcessType);
1617 if (w == nullptr) {
1618 NS_WARNING("Failed GetHistogramById for CHILD");
1619 } else {
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()) {
1628 return;
1631 PROFILER_MARKER("ChildHistogram::Add", TELEMETRY, {}, HistogramMarker, aId,
1632 aKey, aSample);
1633 KeyedHistogram* keyed = internal_GetKeyedHistogramById(aId, aProcessType);
1634 MOZ_ASSERT(keyed);
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()) {
1642 return;
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);
1651 if (kh) {
1652 kh->Clear(aStore);
1655 } else {
1656 // Reset the histograms instances for all processes.
1657 for (uint32_t process = 0;
1658 process < static_cast<uint32_t>(ProcessID::Count); ++process) {
1659 Histogram* h =
1660 internal_GetHistogramById(aLock, id, static_cast<ProcessID>(process),
1661 /* instantiate = */ false);
1662 if (h) {
1663 h->Clear(aStore);
1669 } // namespace
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.
1692 namespace {
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,
1721 uint32_t& aValue) {
1722 if (aElement.isString()) {
1723 // Strings only allowed for categorical histograms
1724 if (aHistogramType != nsITelemetry::HISTOGRAM_CATEGORICAL) {
1725 LogToBrowserConsole(
1726 nsIScriptError::errorFlag,
1727 nsLiteralString(
1728 u"String argument only allowed for categorical histogram"));
1729 return false;
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);
1737 return false;
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));
1748 return false;
1750 } else if (!(aElement.isNumber() || aElement.isBoolean())) {
1751 LogToBrowserConsole(nsIScriptError::errorFlag, u"Argument not a number"_ns);
1752 return false;
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;
1758 #ifdef DEBUG
1759 LogToBrowserConsole(nsIScriptError::errorFlag,
1760 u"Clamped large numeric value"_ns);
1761 #endif
1762 } else if (!JS::ToUint32(aCx, aElement, &aValue)) {
1763 LogToBrowserConsole(nsIScriptError::errorFlag,
1764 u"Failed to convert element to UInt32"_ns);
1765 return false;
1768 // If we're here then all type checks have passed and aValue contains the
1769 // coerced integer
1770 return true;
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;
1783 if (isKeyed) {
1784 firstArgIndex = 1;
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,
1792 nsLiteralString(
1793 u"Need at least one argument for non count type histogram"));
1794 return false;
1797 aArray.AppendElement(1);
1798 return true;
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);
1807 if (!isArray) {
1808 LogToBrowserConsole(
1809 nsIScriptError::errorFlag,
1810 nsLiteralString(
1811 u"The argument to accumulate can't be a non-array object"));
1812 return false;
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);
1819 return false;
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",
1827 arrayIdx);
1828 LogToBrowserConsole(nsIScriptError::errorFlag,
1829 NS_ConvertUTF8toUTF16(msg));
1830 return false;
1833 uint32_t value = 0;
1834 if (!internal_JSHistogram_CoerceValue(aCx, element, aId, aHistogramType,
1835 value)) {
1836 nsPrintfCString msg("Element at index %d failed type checks", arrayIdx);
1837 LogToBrowserConsole(nsIScriptError::errorFlag,
1838 NS_ConvertUTF8toUTF16(msg));
1839 return false;
1841 aArray.AppendElement(value);
1844 return true;
1847 uint32_t value = 0;
1848 if (!internal_JSHistogram_CoerceValue(aCx, args[firstArgIndex], aId,
1849 aHistogramType, value)) {
1850 return false;
1852 aArray.AppendElement(value);
1853 return true;
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");
1868 return false;
1871 JSObject* obj = &args.thisv().toObject();
1872 JSHistogramData* data = GetJSHistogramData(obj);
1873 MOZ_ASSERT(data);
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
1886 return true;
1890 StaticMutexAutoLock locker(gTelemetryHistogramMutex);
1891 for (uint32_t aValue : values) {
1892 internal_Accumulate(locker, id, aValue);
1895 return true;
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");
1904 return false;
1907 JSObject* obj = &args.thisv().toObject();
1908 JSHistogramData* data = GetJSHistogramData(obj);
1909 MOZ_ASSERT(data);
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));
1917 return true;
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);
1952 } else {
1953 JS_ReportErrorASCII(cx, "Expected at most one argument.");
1954 return NS_ERROR_FAILURE;
1957 return NS_OK;
1960 bool internal_JSHistogram_Snapshot(JSContext* cx, unsigned argc,
1961 JS::Value* vp) {
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");
1967 return false;
1970 if (!args.thisv().isObject() ||
1971 JS::GetClass(&args.thisv().toObject()) != &sJSHistogramClass) {
1972 JS_ReportErrorASCII(cx, "Wrong JS class, expected JSHistogram class");
1973 return false;
1976 JSObject* obj = &args.thisv().toObject();
1977 JSHistogramData* data = GetJSHistogramData(obj);
1978 MOZ_ASSERT(data);
1979 HistogramID id = data->histogramId;
1981 nsAutoString storeName;
1982 nsresult rv = internal_JS_StoreFromObjectArgument(cx, args, storeName);
1983 if (NS_FAILED(rv)) {
1984 return false;
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,
1999 // but don't fail
2000 args.rval().setUndefined();
2001 return true;
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))) {
2006 return false;
2010 JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
2011 if (!snapshot) {
2012 return false;
2015 if (NS_FAILED(internal_ReflectHistogramAndSamples(
2016 cx, snapshot, gHistogramInfos[id], dataSnapshot))) {
2017 return false;
2020 args.rval().setObject(*snapshot);
2021 return true;
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");
2028 return false;
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");
2036 return false;
2039 JSObject* obj = &args.thisv().toObject();
2040 JSHistogramData* data = GetJSHistogramData(obj);
2041 MOZ_ASSERT(data);
2043 nsAutoString storeName;
2044 nsresult rv = internal_JS_StoreFromObjectArgument(cx, args, storeName);
2045 if (NS_FAILED(rv)) {
2046 return false;
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));
2061 return true;
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));
2069 if (!obj) {
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,
2078 0) &&
2079 JS_DefineFunction(cx, obj, "clear", internal_JSHistogram_Clear, 1,
2080 0))) {
2081 return NS_ERROR_FAILURE;
2084 JSHistogramData* data = new JSHistogramData{id};
2085 JS::SetReservedSlot(obj, HistogramObjectDataSlot, JS::PrivateValue(data));
2086 ret.setObject(*obj);
2088 return NS_OK;
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.");
2094 return;
2097 JSHistogramData* data = GetJSHistogramData(obj);
2098 MOZ_ASSERT(data);
2099 delete data;
2102 } // namespace
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.
2121 namespace {
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,
2147 JS::Value* vp) {
2148 if (!XRE_IsParentProcess()) {
2149 JS_ReportErrorASCII(
2150 cx, "Keyed histograms can only be snapshotted in the parent process");
2151 return false;
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");
2159 return false;
2162 JSObject* obj = &args.thisv().toObject();
2163 JSHistogramData* data = GetJSKeyedHistogramData(obj);
2164 MOZ_ASSERT(data);
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);
2177 if (!keyed) {
2178 JS_ReportErrorASCII(cx, "Failed to look up keyed histogram");
2179 return false;
2182 nsAutoString storeName;
2183 nsresult rv;
2184 rv = internal_JS_StoreFromObjectArgument(cx, args, storeName);
2185 if (NS_FAILED(rv)) {
2186 return false;
2189 JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx));
2190 if (!snapshot) {
2191 JS_ReportErrorASCII(cx, "Failed to create object");
2192 return false;
2195 rv = keyed->GetJSSnapshot(cx, snapshot, NS_ConvertUTF16toUTF8(storeName),
2196 false);
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();
2201 return true;
2204 if (!NS_SUCCEEDED(rv)) {
2205 JS_ReportErrorASCII(cx, "Failed to reflect keyed histograms");
2206 return false;
2209 args.rval().setObject(*snapshot);
2210 return true;
2213 bool internal_JSKeyedHistogram_Add(JSContext* cx, unsigned argc,
2214 JS::Value* vp) {
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");
2220 return false;
2223 JSObject* obj = &args.thisv().toObject();
2224 JSHistogramData* data = GetJSKeyedHistogramData(obj);
2225 MOZ_ASSERT(data);
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);
2234 return true;
2237 nsAutoJSString key;
2238 if (!args[0].isString() || !key.init(cx, args[0])) {
2239 LogToBrowserConsole(nsIScriptError::errorFlag, u"Not a string"_ns);
2240 return true;
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);
2252 return true;
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
2261 return true;
2265 StaticMutexAutoLock locker(gTelemetryHistogramMutex);
2266 for (uint32_t aValue : values) {
2267 internal_Accumulate(locker, id, NS_ConvertUTF16toUTF8(key), aValue);
2270 return true;
2273 bool internal_JSKeyedHistogram_Name(JSContext* cx, unsigned argc,
2274 JS::Value* vp) {
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");
2280 return false;
2283 JSObject* obj = &args.thisv().toObject();
2284 JSHistogramData* data = GetJSKeyedHistogramData(obj);
2285 MOZ_ASSERT(data);
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));
2293 return true;
2296 bool internal_JSKeyedHistogram_Keys(JSContext* cx, unsigned argc,
2297 JS::Value* vp) {
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");
2303 return false;
2306 JSObject* obj = &args.thisv().toObject();
2307 JSHistogramData* data = GetJSKeyedHistogramData(obj);
2308 MOZ_ASSERT(data);
2309 HistogramID id = data->histogramId;
2311 nsAutoString storeName;
2312 nsresult rv = internal_JS_StoreFromObjectArgument(cx, args, storeName);
2313 if (NS_FAILED(rv)) {
2314 return false;
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);
2328 MOZ_ASSERT(keyed);
2329 if (!keyed) {
2330 return false;
2333 if (NS_FAILED(
2334 keyed->GetKeys(locker, NS_ConvertUTF16toUTF8(storeName), keys))) {
2335 return false;
2339 // Convert keys from nsTArray<nsCString> to JS array.
2340 JS::RootedVector<JS::Value> autoKeys(cx);
2341 if (!autoKeys.reserve(keys.Length())) {
2342 return false;
2345 for (const auto& key : keys) {
2346 JS::Rooted<JS::Value> jsKey(cx);
2347 jsKey.setString(ToJSString(cx, key));
2348 if (!autoKeys.append(jsKey)) {
2349 return false;
2353 JS::Rooted<JSObject*> jsKeys(cx, JS::NewArrayObject(cx, autoKeys));
2354 if (!jsKeys) {
2355 return false;
2358 args.rval().setObject(*jsKeys);
2359 return true;
2362 bool internal_JSKeyedHistogram_Clear(JSContext* cx, unsigned argc,
2363 JS::Value* vp) {
2364 if (!XRE_IsParentProcess()) {
2365 JS_ReportErrorASCII(
2366 cx, "Keyed histograms can only be cleared in the parent process");
2367 return false;
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");
2375 return false;
2378 JSObject* obj = &args.thisv().toObject();
2379 JSHistogramData* data = GetJSKeyedHistogramData(obj);
2380 MOZ_ASSERT(data);
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)) {
2390 return false;
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);
2404 if (!keyed) {
2405 return true;
2408 keyed->Clear(NS_ConvertUTF16toUTF8(storeName));
2411 return true;
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,
2423 0) &&
2424 JS_DefineFunction(cx, obj, "name", internal_JSKeyedHistogram_Name, 1,
2425 0) &&
2426 JS_DefineFunction(cx, obj, "snapshot",
2427 internal_JSKeyedHistogram_Snapshot, 1, 0) &&
2428 JS_DefineFunction(cx, obj, "keys", internal_JSKeyedHistogram_Keys, 1,
2429 0) &&
2430 JS_DefineFunction(cx, obj, "clear", internal_JSKeyedHistogram_Clear, 1,
2431 0))) {
2432 return NS_ERROR_FAILURE;
2435 JSHistogramData* data = new JSHistogramData{id};
2436 JS::SetReservedSlot(obj, HistogramObjectDataSlot, JS::PrivateValue(data));
2437 ret.setObject(*obj);
2439 return NS_OK;
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.");
2445 return;
2448 JSHistogramData* data = GetJSKeyedHistogramData(obj);
2449 MOZ_ASSERT(data);
2450 delete data;
2453 } // namespace
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()) {
2479 gHistogramStorage =
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.
2489 // clang-format off
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, "
2499 "GC_REASON_2");
2501 // clang-format on
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;
2531 #ifdef DEBUG
2532 bool TelemetryHistogram::GlobalStateHasBeenInitialized() {
2533 StaticMutexAutoLock locker(gTelemetryHistogramMutex);
2534 return gTelemetryHistogramInitDone;
2536 #endif
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.");
2573 return;
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.");
2584 return;
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,
2597 uint32_t aSample) {
2598 if (NS_WARN_IF(!internal_IsHistogramEnumId(aID))) {
2599 MOZ_ASSERT_UNREACHABLE("Histogram usage requires valid ids.");
2600 return;
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()),
2612 return;
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");
2623 return;
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()),
2639 return;
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;
2653 HistogramID id;
2654 nsresult rv =
2655 internal_GetHistogramIdByName(locker, nsDependentCString(name), &id);
2656 if (NS_FAILED(rv)) {
2657 return rv;
2659 internal_Accumulate(locker, id, sample);
2660 return NS_OK;
2663 nsresult TelemetryHistogram::Accumulate(const char* name, const nsCString& key,
2664 uint32_t sample) {
2665 bool keyNotAllowed = false;
2668 StaticMutexAutoLock locker(gTelemetryHistogramMutex);
2669 if (!internal_CanRecordBase()) {
2670 return NS_ERROR_NOT_AVAILABLE;
2672 HistogramID id;
2673 nsresult rv =
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
2677 // histogram.
2678 if (gHistogramInfos[id].allows_key(key)) {
2679 internal_Accumulate(locker, id, key, sample);
2680 return NS_OK;
2682 // We're holding |gTelemetryHistogramMutex|, so we can't print a message
2683 // here.
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.");
2702 return;
2705 StaticMutexAutoLock locker(gTelemetryHistogramMutex);
2706 if (!internal_CanRecordBase()) {
2707 return;
2709 uint32_t labelId = 0;
2710 if (NS_FAILED(gHistogramInfos[aId].label_id(label.get(), &labelId))) {
2711 return;
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.");
2720 return;
2723 if (!internal_CanRecordBase()) {
2724 return;
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))) {
2736 return;
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()) {
2755 return;
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.");
2760 continue;
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()) {
2773 return;
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.");
2778 continue;
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;
2795 return NS_OK;
2798 nsresult TelemetryHistogram::GetCategoricalHistogramLabels(
2799 JSContext* aCx, JS::MutableHandle<JS::Value> aResult) {
2800 JS::Rooted<JSObject*> root_obj(aCx, JS_NewPlainObject(aCx));
2801 if (!root_obj) {
2802 return NS_ERROR_FAILURE;
2804 aResult.setObject(*root_obj);
2806 for (const HistogramInfo& info : gHistogramInfos) {
2807 if (info.histogramType != nsITelemetry::HISTOGRAM_CATEGORICAL) {
2808 continue;
2811 const char* name = info.name();
2812 JS::Rooted<JSObject*> labels(aCx,
2813 JS::NewArrayObject(aCx, info.label_count));
2814 if (!labels) {
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;
2834 return NS_OK;
2837 nsresult TelemetryHistogram::GetHistogramById(
2838 const nsACString& name, JSContext* cx, JS::MutableHandle<JS::Value> ret) {
2839 HistogramID id;
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) {
2857 HistogramID id;
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.");
2876 return nullptr;
2879 const HistogramInfo& h = gHistogramInfos[id];
2880 return h.name();
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,
2896 bool aFilterTest) {
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));
2903 if (!root_obj) {
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)) {
2919 return 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));
2939 if (!hobj) {
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;
2954 return NS_OK;
2957 nsresult TelemetryHistogram::GetKeyedHistogramSnapshots(
2958 JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
2959 const nsACString& aStore, unsigned int aDataset, bool aClearSubsession,
2960 bool aFilterTest) {
2961 if (!XRE_IsParentProcess()) {
2962 return NS_ERROR_FAILURE;
2965 // Runs without protection from |gTelemetryHistogramMutex|
2966 JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
2967 if (!obj) {
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)) {
2984 return 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));
3003 if (!snapshot) {
3004 return NS_ERROR_FAILURE;
3007 if (!NS_SUCCEEDED(internal_ReflectKeyedHistogram(hData.data, info, aCx,
3008 snapshot))) {
3009 return NS_ERROR_FAILURE;
3012 if (!JS_DefineProperty(aCx, processObject, info.name(), snapshot,
3013 JSPROP_ENUMERATE)) {
3014 return NS_ERROR_FAILURE;
3018 return NS_OK;
3021 size_t TelemetryHistogram::GetHistogramSizesOfIncludingThis(
3022 mozilla::MallocSizeOf aMallocSizeOf) {
3023 StaticMutexAutoLock locker(gTelemetryHistogramMutex);
3025 size_t n = 0;
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);
3058 return n;