diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /toolkit/components/telemetry/tests/gtest | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/telemetry/tests/gtest')
10 files changed, 2464 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/gtest/TelemetryFixture.cpp b/toolkit/components/telemetry/tests/gtest/TelemetryFixture.cpp new file mode 100644 index 0000000000..35742654f6 --- /dev/null +++ b/toolkit/components/telemetry/tests/gtest/TelemetryFixture.cpp @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "TelemetryFixture.h" +#include "mozilla/dom/SimpleGlobalObject.h" + +using namespace mozilla; + +void TelemetryTestFixture::SetUp() { + mTelemetry = do_GetService("@mozilla.org/base/telemetry;1"); + + mCleanGlobal = dom::SimpleGlobalObject::Create( + dom::SimpleGlobalObject::GlobalType::BindingDetail); + + // The test must fail if we failed getting the global. + ASSERT_NE(mCleanGlobal, nullptr) + << "SimpleGlobalObject must return a valid global object."; +} + +AutoJSContextWithGlobal::AutoJSContextWithGlobal(JSObject* aGlobalObject) + : mCx(nullptr) { + // The JS API must initialize correctly. + JS::Rooted<JSObject*> globalObject(dom::RootingCx(), aGlobalObject); + MOZ_ALWAYS_TRUE(mJsAPI.Init(globalObject)); +} + +JSContext* AutoJSContextWithGlobal::GetJSContext() const { return mJsAPI.cx(); } diff --git a/toolkit/components/telemetry/tests/gtest/TelemetryFixture.h b/toolkit/components/telemetry/tests/gtest/TelemetryFixture.h new file mode 100644 index 0000000000..f89aecf6a8 --- /dev/null +++ b/toolkit/components/telemetry/tests/gtest/TelemetryFixture.h @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#ifndef TelemetryFixture_h_ +#define TelemetryFixture_h_ + +#include "gtest/gtest.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsITelemetry.h" + +class TelemetryTestFixture : public ::testing::Test { + protected: + TelemetryTestFixture() : mCleanGlobal(nullptr) {} + virtual void SetUp(); + + JSObject* mCleanGlobal; + + nsCOMPtr<nsITelemetry> mTelemetry; +}; + +// AutoJSAPI is annotated with MOZ_STACK_CLASS and thus cannot be +// used as a member of TelemetryTestFixture, since gtest instantiates +// that on the heap. To work around the problem, use the following class +// at the beginning of each Telemetry test. +// Note: this is very similar to AutoJSContext, but it allows to pass a +// global JS object in. +class MOZ_RAII AutoJSContextWithGlobal { + public: + explicit AutoJSContextWithGlobal(JSObject* aGlobalObject); + JSContext* GetJSContext() const; + + protected: + mozilla::dom::AutoJSAPI mJsAPI; + JSContext* mCx; +}; + +#endif // TelemetryFixture_h_ diff --git a/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp b/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp new file mode 100644 index 0000000000..1848052c2e --- /dev/null +++ b/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp @@ -0,0 +1,372 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "TelemetryTestHelpers.h" + +#include "core/TelemetryCommon.h" +#include "gtest/gtest.h" +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject +#include "js/CallAndConstruct.h" // JS_CallFunctionName +#include "js/PropertyAndElement.h" // JS_Enumerate, JS_GetElement, JS_GetProperty +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Unused.h" +#include "nsTArray.h" +#include "nsPrintfCString.h" + +using namespace mozilla; + +// Helper methods provided to simplify writing tests and meant to be used in C++ +// Gtests. +namespace TelemetryTestHelpers { + +void CheckUintScalar(const char* aName, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, uint32_t expectedValue) { + // Validate the value of the test scalar. + JS::Rooted<JS::Value> value(aCx); + JS::Rooted<JSObject*> scalarObj(aCx, &aSnapshot.toObject()); + ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value)) + << "The test scalar must be reported."; + JS_GetProperty(aCx, scalarObj, aName, &value); + + ASSERT_TRUE(value.isInt32()) + << "The scalar value must be of the correct type."; + ASSERT_TRUE(value.toInt32() >= 0) + << "The uint scalar type must contain a value >= 0."; + ASSERT_EQ(static_cast<uint32_t>(value.toInt32()), expectedValue) + << "The scalar value must match the expected value."; +} + +void CheckBoolScalar(const char* aName, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, bool expectedValue) { + // Validate the value of the test scalar. + JS::Rooted<JS::Value> value(aCx); + JS::Rooted<JSObject*> scalarObj(aCx, &aSnapshot.toObject()); + ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value)) + << "The test scalar must be reported."; + ASSERT_TRUE(value.isBoolean()) + << "The scalar value must be of the correct type."; + ASSERT_EQ(static_cast<bool>(value.toBoolean()), expectedValue) + << "The scalar value must match the expected value."; +} + +void CheckStringScalar(const char* aName, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, + const char* expectedValue) { + // Validate the value of the test scalar. + JS::Rooted<JS::Value> value(aCx); + JS::Rooted<JSObject*> scalarObj(aCx, &aSnapshot.toObject()); + ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value)) + << "The test scalar must be reported."; + ASSERT_TRUE(value.isString()) + << "The scalar value must be of the correct type."; + + bool sameString; + ASSERT_TRUE( + JS_StringEqualsAscii(aCx, value.toString(), expectedValue, &sameString)) + << "JS String comparison failed"; + ASSERT_TRUE(sameString) + << "The scalar value must match the expected string"; +} + +void CheckKeyedUintScalar(const char* aName, const char* aKey, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, + uint32_t expectedValue) { + JS::Rooted<JS::Value> keyedScalar(aCx); + JS::Rooted<JSObject*> scalarObj(aCx, &aSnapshot.toObject()); + // Get the aName keyed scalar object from the scalars snapshot. + ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar)) + << "The keyed scalar must be reported."; + + CheckUintScalar(aKey, aCx, keyedScalar, expectedValue); +} + +void CheckKeyedBoolScalar(const char* aName, const char* aKey, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, bool expectedValue) { + JS::Rooted<JS::Value> keyedScalar(aCx); + JS::Rooted<JSObject*> scalarObj(aCx, &aSnapshot.toObject()); + // Get the aName keyed scalar object from the scalars snapshot. + ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar)) + << "The keyed scalar must be reported."; + + CheckBoolScalar(aKey, aCx, keyedScalar, expectedValue); +} + +void CheckNumberOfProperties(const char* aName, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, + uint32_t expectedNumProperties) { + JS::Rooted<JS::Value> keyedScalar(aCx); + JS::Rooted<JSObject*> scalarObj(aCx, &aSnapshot.toObject()); + // Get the aName keyed scalar object from the scalars snapshot. + ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar)) + << "The keyed scalar must be reported."; + + JS::Rooted<JSObject*> keyedScalarObj(aCx, &keyedScalar.toObject()); + JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx)); + ASSERT_TRUE(JS_Enumerate(aCx, keyedScalarObj, &ids)) + << "We must be able to get keyed scalar members."; + + ASSERT_EQ(expectedNumProperties, ids.length()) + << "The scalar must report the expected number of properties."; +} + +bool EventPresent(JSContext* aCx, const JS::RootedValue& aSnapshot, + const nsACString& aCategory, const nsACString& aMethod, + const nsACString& aObject) { + EXPECT_FALSE(aSnapshot.isNullOrUndefined()) + << "Event snapshot must not be null/undefined."; + bool isArray = false; + EXPECT_TRUE(JS::IsArrayObject(aCx, aSnapshot, &isArray) && isArray) + << "The snapshot must be an array."; + JS::Rooted<JSObject*> arrayObj(aCx, &aSnapshot.toObject()); + uint32_t arrayLength = 0; + EXPECT_TRUE(JS::GetArrayLength(aCx, arrayObj, &arrayLength)) + << "Array must have a length."; + EXPECT_TRUE(arrayLength > 0) << "Array must have at least one element."; + + for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; ++arrayIdx) { + JS::Rooted<JS::Value> element(aCx); + EXPECT_TRUE(JS_GetElement(aCx, arrayObj, arrayIdx, &element)) + << "Must be able to get element."; + EXPECT_TRUE(JS::IsArrayObject(aCx, element, &isArray) && isArray) + << "Element must be an array."; + JS::Rooted<JSObject*> eventArray(aCx, &element.toObject()); + uint32_t eventLength; + EXPECT_TRUE(JS::GetArrayLength(aCx, eventArray, &eventLength)) + << "Event array must have a length."; + EXPECT_TRUE(eventLength >= 4) + << "Event array must have at least 4 elements (timestamp, category, " + "method, object)."; + + JS::Rooted<JS::Value> str(aCx); + nsAutoJSString jsStr; + EXPECT_TRUE(JS_GetElement(aCx, eventArray, 1, &str)) + << "Must be able to get category."; + EXPECT_TRUE(str.isString()) << "Category must be a string."; + EXPECT_TRUE(jsStr.init(aCx, str)) + << "Category must be able to be init'd to a jsstring."; + if (NS_ConvertUTF16toUTF8(jsStr) != aCategory) { + continue; + } + + EXPECT_TRUE(JS_GetElement(aCx, eventArray, 2, &str)) + << "Must be able to get method."; + EXPECT_TRUE(str.isString()) << "Method must be a string."; + EXPECT_TRUE(jsStr.init(aCx, str)) + << "Method must be able to be init'd to a jsstring."; + if (NS_ConvertUTF16toUTF8(jsStr) != aMethod) { + continue; + } + + EXPECT_TRUE(JS_GetElement(aCx, eventArray, 3, &str)) + << "Must be able to get object."; + EXPECT_TRUE(str.isString()) << "Object must be a string."; + EXPECT_TRUE(jsStr.init(aCx, str)) + << "Object must be able to be init'd to a jsstring."; + if (NS_ConvertUTF16toUTF8(jsStr) != aObject) { + continue; + } + + // We found it! + return true; + } + + // We didn't find it! + return false; +} + +nsTArray<nsString> EventValuesToArray(JSContext* aCx, + const JS::RootedValue& aSnapshot, + const nsAString& aCategory, + const nsAString& aMethod, + const nsAString& aObject) { + constexpr int kIndexOfCategory = 1; + constexpr int kIndexOfMethod = 2; + constexpr int kIndexOfObject = 3; + constexpr int kIndexOfValueString = 4; + + nsTArray<nsString> valueArray; + if (aSnapshot.isNullOrUndefined()) { + return valueArray; + } + + bool isArray = false; + EXPECT_TRUE(JS::IsArrayObject(aCx, aSnapshot, &isArray) && isArray) + << "The snapshot must be an array."; + + JS::Rooted<JSObject*> arrayObj(aCx, &aSnapshot.toObject()); + + uint32_t arrayLength = 0; + EXPECT_TRUE(JS::GetArrayLength(aCx, arrayObj, &arrayLength)) + << "Array must have a length."; + + JS::Rooted<JS::Value> jsVal(aCx); + nsAutoJSString jsStr; + + for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; ++arrayIdx) { + JS::Rooted<JS::Value> element(aCx); + EXPECT_TRUE(JS_GetElement(aCx, arrayObj, arrayIdx, &element)) + << "Must be able to get element."; + + EXPECT_TRUE(JS::IsArrayObject(aCx, element, &isArray) && isArray) + << "Element must be an array."; + + JS::Rooted<JSObject*> eventArray(aCx, &element.toObject()); + uint32_t eventLength; + EXPECT_TRUE(JS::GetArrayLength(aCx, eventArray, &eventLength)) + << "Event array must have a length."; + EXPECT_TRUE(eventLength >= kIndexOfValueString) + << "Event array must have at least 4 elements (timestamp, category, " + "method, object)."; + + EXPECT_TRUE(JS_GetElement(aCx, eventArray, kIndexOfCategory, &jsVal)) + << "Must be able to get category."; + EXPECT_TRUE(jsVal.isString()) << "Category must be a string."; + EXPECT_TRUE(jsStr.init(aCx, jsVal)) + << "Category must be able to be init'd to a jsstring."; + if (jsStr != aCategory) { + continue; + } + + EXPECT_TRUE(JS_GetElement(aCx, eventArray, kIndexOfMethod, &jsVal)) + << "Must be able to get method."; + EXPECT_TRUE(jsVal.isString()) << "Method must be a string."; + EXPECT_TRUE(jsStr.init(aCx, jsVal)) + << "Method must be able to be init'd to a jsstring."; + if (jsStr != aMethod) { + continue; + } + + EXPECT_TRUE(JS_GetElement(aCx, eventArray, kIndexOfObject, &jsVal)) + << "Must be able to get object."; + EXPECT_TRUE(jsVal.isString()) << "Object must be a string."; + EXPECT_TRUE(jsStr.init(aCx, jsVal)) + << "Object must be able to be init'd to a jsstring."; + if (jsStr != aObject) { + continue; + } + + if (!JS_GetElement(aCx, eventArray, kIndexOfValueString, &jsVal)) { + continue; + } + + nsString str; + EXPECT_TRUE(AssignJSString(aCx, str, jsVal.toString())) + << "Value must be able to be init'd to a string."; + Unused << valueArray.EmplaceBack(std::move(str)); + } + + return valueArray; +} + +void GetEventSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult, + ProcessID aProcessType) { + nsCOMPtr<nsITelemetry> telemetry = + do_GetService("@mozilla.org/base/telemetry;1"); + + JS::Rooted<JS::Value> eventSnapshot(aCx); + nsresult rv; + rv = telemetry->SnapshotEvents(1 /* PRERELEASE_CHANNELS */, false /* clear */, + 0 /* eventLimit */, aCx, 1 /* argc */, + &eventSnapshot); + ASSERT_EQ(rv, NS_OK) << "Snapshotting events must not fail."; + ASSERT_TRUE(eventSnapshot.isObject()) + << "The snapshot must be an object."; + + JS::Rooted<JS::Value> processEvents(aCx); + JS::Rooted<JSObject*> eventObj(aCx, &eventSnapshot.toObject()); + Unused << JS_GetProperty(aCx, eventObj, + Telemetry::Common::GetNameForProcessID(aProcessType), + &processEvents); + + aResult.set(processEvents); +} + +void GetScalarsSnapshot(bool aKeyed, JSContext* aCx, + JS::MutableHandle<JS::Value> aResult, + ProcessID aProcessType) { + nsCOMPtr<nsITelemetry> telemetry = + do_GetService("@mozilla.org/base/telemetry;1"); + + // Get a snapshot of the scalars. + JS::Rooted<JS::Value> scalarsSnapshot(aCx); + nsresult rv; + + if (aKeyed) { + rv = telemetry->GetSnapshotForKeyedScalars( + "main"_ns, false, false /* filter */, aCx, &scalarsSnapshot); + } else { + rv = telemetry->GetSnapshotForScalars("main"_ns, false, false /* filter */, + aCx, &scalarsSnapshot); + } + + // Validate the snapshot. + ASSERT_EQ(rv, NS_OK) << "Creating a snapshot of the data must not fail."; + ASSERT_TRUE(scalarsSnapshot.isObject()) + << "The snapshot must be an object."; + + JS::Rooted<JS::Value> processScalars(aCx); + JS::Rooted<JSObject*> scalarObj(aCx, &scalarsSnapshot.toObject()); + // Don't complain if no scalars for the process can be found. Just + // return an empty object. + Unused << JS_GetProperty(aCx, scalarObj, + Telemetry::Common::GetNameForProcessID(aProcessType), + &processScalars); + + aResult.set(processScalars); +} + +void GetAndClearHistogram(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry, + const nsACString& name, bool is_keyed) { + JS::Rooted<JS::Value> testHistogram(cx); + nsresult rv = + is_keyed ? mTelemetry->GetKeyedHistogramById(name, cx, &testHistogram) + : mTelemetry->GetHistogramById(name, cx, &testHistogram); + + ASSERT_EQ(rv, NS_OK) << "Cannot fetch histogram"; + + // Clear the stored value + JS::Rooted<JSObject*> testHistogramObj(cx, &testHistogram.toObject()); + JS::Rooted<JS::Value> rval(cx); + ASSERT_TRUE(JS_CallFunctionName(cx, testHistogramObj, "clear", + JS::HandleValueArray::empty(), &rval)) + << "Cannot clear histogram"; +} + +void GetProperty(JSContext* cx, const char* name, JS::Handle<JS::Value> valueIn, + JS::MutableHandle<JS::Value> valueOut) { + JS::Rooted<JS::Value> property(cx); + JS::Rooted<JSObject*> valueInObj(cx, &valueIn.toObject()); + ASSERT_TRUE(JS_GetProperty(cx, valueInObj, name, &property)) + << "Cannot get property '" << name << "'"; + valueOut.set(property); +} + +void GetElement(JSContext* cx, uint32_t index, JS::Handle<JS::Value> valueIn, + JS::MutableHandle<JS::Value> valueOut) { + JS::Rooted<JS::Value> element(cx); + JS::Rooted<JSObject*> valueInObj(cx, &valueIn.toObject()); + ASSERT_TRUE(JS_GetElement(cx, valueInObj, index, &element)) + << "Cannot get element at index '" << index << "'"; + valueOut.set(element); +} + +void GetSnapshots(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry, + const char* name, JS::MutableHandle<JS::Value> valueOut, + bool is_keyed) { + JS::Rooted<JS::Value> snapshots(cx); + nsresult rv = is_keyed + ? mTelemetry->GetSnapshotForKeyedHistograms( + "main"_ns, false, false /* filter */, cx, &snapshots) + : mTelemetry->GetSnapshotForHistograms( + "main"_ns, false, false /* filter */, cx, &snapshots); + + JS::Rooted<JS::Value> snapshot(cx); + GetProperty(cx, "parent", snapshots, &snapshot); + + ASSERT_EQ(rv, NS_OK) << "Cannot call histogram snapshots"; + valueOut.set(snapshot); +} + +} // namespace TelemetryTestHelpers diff --git a/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.h b/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.h new file mode 100644 index 0000000000..b2eab48c73 --- /dev/null +++ b/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.h @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#ifndef TelemetryTestHelpers_h_ +#define TelemetryTestHelpers_h_ + +#include "js/TypeDecls.h" +#include "mozilla/TelemetryProcessEnums.h" +#include "nsITelemetry.h" + +using mozilla::Telemetry::ProcessID; + +namespace TelemetryTestHelpers { + +void CheckUintScalar(const char* aName, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, uint32_t expectedValue); + +void CheckBoolScalar(const char* aName, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, bool expectedValue); + +void CheckStringScalar(const char* aName, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, + const char* expectedValue); + +void CheckKeyedUintScalar(const char* aName, const char* aKey, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, + uint32_t expectedValue); + +void CheckKeyedBoolScalar(const char* aName, const char* aKey, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, bool expectedValue); + +void CheckNumberOfProperties(const char* aName, JSContext* aCx, + JS::Handle<JS::Value> aSnapshot, + uint32_t expectedNumProperties); + +bool EventPresent(JSContext* aCx, const JS::RootedValue& aSnapshot, + const nsACString& aCategory, const nsACString& aMethod, + const nsACString& aObject); + +nsTArray<nsString> EventValuesToArray(JSContext* aCx, + const JS::RootedValue& aSnapshot, + const nsAString& aCategory, + const nsAString& aMethod, + const nsAString& aObject); + +void GetEventSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult, + ProcessID aProcessType = ProcessID::Parent); + +void GetScalarsSnapshot(bool aKeyed, JSContext* aCx, + JS::MutableHandle<JS::Value> aResult, + ProcessID aProcessType = ProcessID::Parent); + +void GetAndClearHistogram(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry, + const nsACString& name, bool is_keyed); + +void GetProperty(JSContext* cx, const char* name, JS::Handle<JS::Value> valueIn, + JS::MutableHandle<JS::Value> valueOut); + +void GetElement(JSContext* cx, uint32_t index, JS::Handle<JS::Value> valueIn, + JS::MutableHandle<JS::Value> valueOut); + +void GetSnapshots(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry, + const char* name, JS::MutableHandle<JS::Value> valueOut, + bool is_keyed); +} // namespace TelemetryTestHelpers + +#endif diff --git a/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp b/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp new file mode 100644 index 0000000000..3e21c7378c --- /dev/null +++ b/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp @@ -0,0 +1,158 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "TelemetryFixture.h" +#include "TelemetryTestHelpers.h" +#include "other/CombinedStacks.h" +#include "other/ProcessedStack.h" +#include "nsPrintfCString.h" + +using namespace mozilla::Telemetry; +using namespace TelemetryTestHelpers; + +TEST_F(TelemetryTestFixture, CombinedStacks) { + const size_t kMaxStacksKept = 10; + CombinedStacks stacks(kMaxStacksKept); + + size_t iterations = kMaxStacksKept * 2; + for (size_t i = 0; i < iterations; ++i) { + ProcessedStack stack; + ProcessedStack::Frame frame = {static_cast<uint16_t>(i)}; + const nsAutoString& name = + NS_ConvertUTF8toUTF16(nsPrintfCString("test%zu", i)); + ProcessedStack::Module module = {name}; + + stack.AddFrame(frame); + stack.AddModule(module); + stacks.AddStack(stack); + } + + ASSERT_EQ(stacks.GetStackCount(), kMaxStacksKept) << "Wrong number of stacks"; + ASSERT_EQ(stacks.GetModuleCount(), kMaxStacksKept * 2) + << "Wrong number of modules"; + + for (size_t i = 0; i < kMaxStacksKept; ++i) { + ProcessedStack::Frame frame = stacks.GetStack(i)[0]; + ASSERT_EQ(frame.mOffset, kMaxStacksKept + i) + << "Frame is not returning expected value"; + + ProcessedStack::Module module = stacks.GetModule(frame.mModIndex); + nsPrintfCString moduleName("test%hu", frame.mModIndex); + ASSERT_TRUE(module.mName.Equals(NS_ConvertUTF8toUTF16(moduleName))) + << "Module should have expected name"; + } + + for (size_t i = 0; i < kMaxStacksKept; ++i) { + stacks.RemoveStack(kMaxStacksKept - i - 1); + ASSERT_EQ(stacks.GetStackCount(), kMaxStacksKept - i - 1) + << "Stack should be removed"; + } +} + +template <int N> +ProcessedStack MakeStack(const nsLiteralString (&aModules)[N], + const uintptr_t (&aOffsets)[N]) { + ProcessedStack stack; + for (int i = 0; i < N; ++i) { + ProcessedStack::Frame frame = {aOffsets[i]}; + if (aModules[i].IsEmpty()) { + frame.mModIndex = std::numeric_limits<uint16_t>::max(); + } else { + frame.mModIndex = stack.GetNumModules(); + stack.AddModule(ProcessedStack::Module{aModules[i]}); + } + stack.AddFrame(frame); + } + return stack; +} + +TEST(CombinedStacks, Combine) +{ + const nsLiteralString moduleSet1[] = {u"mod1"_ns, u"mod2"_ns, u"base"_ns}; + const nsLiteralString moduleSet2[] = {u"modX"_ns, u""_ns, u"modZ"_ns, + u"base"_ns}; + // [0] 00 mod1+100 + // 01 mod2+200 + // 02 base+300 + // [1] 00 mod1+1000 + // 01 mod2+2000 + // 02 base+3000 + // [2] 00 modX+100 + // 01 <no module>+200 + // 02 modZ+300 + // 03 base+400 + // [3] 00 modX+1000 + // 01 <no module>+3000 + // 02 modZ+2000 + // 03 base+4000 + const ProcessedStack testStacks[] = { + MakeStack(moduleSet1, {100ul, 200ul, 300ul}), + MakeStack(moduleSet1, {1000ul, 2000ul, 3000ul}), + MakeStack(moduleSet2, {100ul, 200ul, 300ul, 400ul}), + MakeStack(moduleSet2, {1000ul, 2000ul, 3000ul, 4000ul}), + }; + + // combined1 <-- testStacks[0] + testStacks[1] + // combined2 <-- testStacks[2] + testStacks[3] + CombinedStacks combined1, combined2; + combined1.AddStack(testStacks[0]); + combined1.AddStack(testStacks[1]); + combined2.AddStack(testStacks[2]); + combined2.AddStack(testStacks[3]); + + EXPECT_EQ(combined1.GetModuleCount(), mozilla::ArrayLength(moduleSet1)); + EXPECT_EQ(combined1.GetStackCount(), 2u); + EXPECT_EQ(combined2.GetModuleCount(), mozilla::ArrayLength(moduleSet2) - 1); + EXPECT_EQ(combined2.GetStackCount(), 2u); + + // combined1 <-- combined1 + combined2 + combined1.AddStacks(combined2); + + EXPECT_EQ(combined1.GetModuleCount(), 5u); // {mod1, mod2, modX, modZ, base} + EXPECT_EQ(combined1.GetStackCount(), mozilla::ArrayLength(testStacks)); + + for (size_t i = 0; i < combined1.GetStackCount(); ++i) { + const auto& expectedStack = testStacks[i]; + const auto& actualStack = combined1.GetStack(i); + EXPECT_EQ(actualStack.size(), expectedStack.GetStackSize()); + if (actualStack.size() != expectedStack.GetStackSize()) { + continue; + } + + for (size_t j = 0; j < actualStack.size(); ++j) { + const auto& expectedFrame = expectedStack.GetFrame(j); + const auto& actualFrame = actualStack[j]; + + EXPECT_EQ(actualFrame.mOffset, expectedFrame.mOffset); + + if (expectedFrame.mModIndex == std::numeric_limits<uint16_t>::max()) { + EXPECT_EQ(actualFrame.mModIndex, std::numeric_limits<uint16_t>::max()); + } else { + EXPECT_EQ(combined1.GetModule(actualFrame.mModIndex), + expectedStack.GetModule(expectedFrame.mModIndex)); + } + } + } + + // Only testStacks[3] will be stored into oneStack + CombinedStacks oneStack(1); + oneStack.AddStacks(combined1); + + EXPECT_EQ(oneStack.GetStackCount(), 1u); + EXPECT_EQ(oneStack.GetStack(0).size(), testStacks[3].GetStackSize()); + + for (size_t i = 0; i < oneStack.GetStack(0).size(); ++i) { + const auto& expectedFrame = testStacks[3].GetFrame(i); + const auto& actualFrame = oneStack.GetStack(0)[i]; + + EXPECT_EQ(actualFrame.mOffset, expectedFrame.mOffset); + + if (expectedFrame.mModIndex == std::numeric_limits<uint16_t>::max()) { + EXPECT_EQ(actualFrame.mModIndex, std::numeric_limits<uint16_t>::max()); + } else { + EXPECT_EQ(oneStack.GetModule(actualFrame.mModIndex), + testStacks[3].GetModule(expectedFrame.mModIndex)); + } + } +} diff --git a/toolkit/components/telemetry/tests/gtest/TestCounters.cpp b/toolkit/components/telemetry/tests/gtest/TestCounters.cpp new file mode 100644 index 0000000000..79b4c66bb9 --- /dev/null +++ b/toolkit/components/telemetry/tests/gtest/TestCounters.cpp @@ -0,0 +1,173 @@ +/* vim:set ts=2 sw=2 sts=0 et: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "gtest/gtest.h" +#include "js/Conversions.h" +#include "mozilla/Telemetry.h" +#include "TelemetryFixture.h" +#include "TelemetryTestHelpers.h" + +using namespace mozilla; +using namespace TelemetryTestHelpers; + +TEST_F(TelemetryTestFixture, AutoCounter) { + const uint32_t kExpectedValue = 100; + AutoJSContextWithGlobal cx(mCleanGlobal); + + const char* telemetryTestCountName = + Telemetry::GetHistogramName(Telemetry::TELEMETRY_TEST_COUNT); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns, + false); + + // Accumulate in the histogram + { + Telemetry::AutoCounter<Telemetry::TELEMETRY_TEST_COUNT> autoCounter; + autoCounter += kExpectedValue / 2; + } + // This counter should not accumulate since it does not go out of scope + Telemetry::AutoCounter<Telemetry::TELEMETRY_TEST_COUNT> autoCounter; + autoCounter += kExpectedValue; + // Accumulate a second time in the histogram + { + Telemetry::AutoCounter<Telemetry::TELEMETRY_TEST_COUNT> autoCounter; + autoCounter += kExpectedValue / 2; + } + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, telemetryTestCountName, &snapshot, + false); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), telemetryTestCountName, snapshot, &histogram); + + // Get "sum" property from histogram + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", histogram, &sum); + + // Check that the "sum" stored in the histogram matches with |kExpectedValue| + uint32_t uSum = 0; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, kExpectedValue) + << "The histogram is not returning expected value"; +} + +TEST_F(TelemetryTestFixture, AutoCounterUnderflow) { + const uint32_t kExpectedValue = 0; + AutoJSContextWithGlobal cx(mCleanGlobal); + + const char* telemetryTestCountName = + Telemetry::GetHistogramName(Telemetry::TELEMETRY_TEST_COUNT); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns, + false); + + // Accumulate in the histogram + { + Telemetry::AutoCounter<Telemetry::TELEMETRY_TEST_COUNT> autoCounter; + autoCounter += -1; + } + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, telemetryTestCountName, &snapshot, + false); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), telemetryTestCountName, snapshot, &histogram); + + // Get "sum" property from histogram + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", histogram, &sum); + + // Check that the "sum" stored in the histogram matches with |kExpectedValue| + uint32_t uSum = 42; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, kExpectedValue) + << "The histogram is supposed to return 0 when an underflow occurs."; +} + +TEST_F(TelemetryTestFixture, RuntimeAutoCounter) { + const uint32_t kExpectedValue = 100; + AutoJSContextWithGlobal cx(mCleanGlobal); + + const char* telemetryTestCountName = + Telemetry::GetHistogramName(Telemetry::TELEMETRY_TEST_COUNT); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns, + false); + + // Accumulate in the histogram + { + Telemetry::RuntimeAutoCounter autoCounter(Telemetry::TELEMETRY_TEST_COUNT); + autoCounter += kExpectedValue / 2; + } + // This counter should not accumulate since it does not go out of scope + Telemetry::RuntimeAutoCounter autoCounter(Telemetry::TELEMETRY_TEST_COUNT); + autoCounter += kExpectedValue; + // Accumulate a second time in the histogram + { + Telemetry::RuntimeAutoCounter autoCounter(Telemetry::TELEMETRY_TEST_COUNT); + autoCounter += kExpectedValue / 2; + } + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, telemetryTestCountName, &snapshot, + false); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), telemetryTestCountName, snapshot, &histogram); + + // Get "sum" property from histogram + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", histogram, &sum); + + // Check that the "sum" stored in the histogram matches with |kExpectedValue| + uint32_t uSum = 0; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, kExpectedValue) + << "The histogram is not returning expected value"; +} + +TEST_F(TelemetryTestFixture, RuntimeAutoCounterUnderflow) { + const uint32_t kExpectedValue = 0; + AutoJSContextWithGlobal cx(mCleanGlobal); + + const char* telemetryTestCountName = + Telemetry::GetHistogramName(Telemetry::TELEMETRY_TEST_COUNT); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns, + false); + + // Accumulate in the histogram + { + Telemetry::RuntimeAutoCounter autoCounter(Telemetry::TELEMETRY_TEST_COUNT, + kExpectedValue); + autoCounter += -1; + } + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, telemetryTestCountName, &snapshot, + false); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), telemetryTestCountName, snapshot, &histogram); + + // Get "sum" property from histogram + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", histogram, &sum); + + // Check that the "sum" stored in the histogram matches with |kExpectedValue| + uint32_t uSum = 42; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, kExpectedValue) + << "The histogram is supposed to return 0 when an underflow occurs."; +} diff --git a/toolkit/components/telemetry/tests/gtest/TestEvents.cpp b/toolkit/components/telemetry/tests/gtest/TestEvents.cpp new file mode 100644 index 0000000000..be08f234a9 --- /dev/null +++ b/toolkit/components/telemetry/tests/gtest/TestEvents.cpp @@ -0,0 +1,115 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "core/TelemetryEvent.h" +#include "gtest/gtest.h" +#include "js/Array.h" // JS::GetArrayLength +#include "js/PropertyAndElement.h" // JS_GetElement, JS_GetProperty +#include "mozilla/Maybe.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "TelemetryFixture.h" +#include "TelemetryTestHelpers.h" + +using namespace mozilla; +using namespace TelemetryTestHelpers; + +// Test that we can properly record events using the C++ API. +TEST_F(TelemetryTestFixture, RecordEventNative) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get events from other tests. + Unused << mTelemetry->ClearEvents(); + + const nsLiteralCString category("telemetry.test"); + const nsLiteralCString method("test1"); + const nsLiteralCString method2("test2"); + const nsLiteralCString object("object1"); + const nsLiteralCString object2("object2"); + const nsLiteralCString value("value"); + const nsLiteralCString valueLong( + "this value is much too long and must be truncated to fit in the limit " + "which at time of writing was 80 bytes."); + const nsLiteralCString extraKey("key1"); + const nsLiteralCString extraValue("extra value"); + const nsLiteralCString extraValueLong( + "this extra value is much too long and must be truncated to fit in the " + "limit which at time of writing was 80 bytes."); + + // Try recording before category's enabled. + Telemetry::RecordEvent(Telemetry::EventID::TelemetryTest_Test1_Object1, + Nothing(), Nothing()); + + // Ensure "telemetry.test" is enabled + Telemetry::SetEventRecordingEnabled(category, true); + + // Try recording after it's enabled. + Telemetry::RecordEvent(Telemetry::EventID::TelemetryTest_Test2_Object1, + Nothing(), Nothing()); + + // Try recording with normal value, extra + CopyableTArray<EventExtraEntry> extra( + {EventExtraEntry{extraKey, extraValue}}); + Telemetry::RecordEvent(Telemetry::EventID::TelemetryTest_Test1_Object2, + mozilla::Some(value), mozilla::Some(extra)); + + // Try recording with too-long value, extra + CopyableTArray<EventExtraEntry> longish( + {EventExtraEntry{extraKey, extraValueLong}}); + Telemetry::RecordEvent(Telemetry::EventID::TelemetryTest_Test2_Object2, + mozilla::Some(valueLong), mozilla::Some(longish)); + + JS::Rooted<JS::Value> eventsSnapshot(cx.GetJSContext()); + GetEventSnapshot(cx.GetJSContext(), &eventsSnapshot); + + ASSERT_TRUE(!EventPresent(cx.GetJSContext(), eventsSnapshot, category, method, + object)) + << "Test event must not be present when recorded before enabled."; + ASSERT_TRUE(EventPresent(cx.GetJSContext(), eventsSnapshot, category, method2, + object)) + << "Test event must be present."; + ASSERT_TRUE(EventPresent(cx.GetJSContext(), eventsSnapshot, category, method, + object2)) + << "Test event with value and extra must be present."; + ASSERT_TRUE(EventPresent(cx.GetJSContext(), eventsSnapshot, category, method2, + object2)) + << "Test event with truncated value and extra must be present."; + + // Ensure that the truncations happened appropriately. + JSContext* aCx = cx.GetJSContext(); + JS::Rooted<JSObject*> arrayObj(aCx, &eventsSnapshot.toObject()); + JS::Rooted<JS::Value> eventRecord(aCx); + ASSERT_TRUE(JS_GetElement(aCx, arrayObj, 2, &eventRecord)) + << "Must be able to get record."; + JS::Rooted<JSObject*> recordArray(aCx, &eventRecord.toObject()); + uint32_t recordLength; + ASSERT_TRUE(JS::GetArrayLength(aCx, recordArray, &recordLength)) + << "Event record array must have length."; + ASSERT_TRUE(recordLength == 6) + << "Event record must have 6 elements."; + + JS::Rooted<JS::Value> str(aCx); + nsAutoJSString jsStr; + // The value string is at index 4 + ASSERT_TRUE(JS_GetElement(aCx, recordArray, 4, &str)) + << "Must be able to get value."; + ASSERT_TRUE(jsStr.init(aCx, str)) + << "Value must be able to be init'd to a jsstring."; + ASSERT_EQ(NS_ConvertUTF16toUTF8(jsStr).Length(), (uint32_t)80) + << "Value must have been truncated to 80 bytes."; + + // Extra is at index 5 + JS::Rooted<JS::Value> obj(aCx); + ASSERT_TRUE(JS_GetElement(aCx, recordArray, 5, &obj)) + << "Must be able to get extra."; + JS::Rooted<JSObject*> extraObj(aCx, &obj.toObject()); + JS::Rooted<JS::Value> extraVal(aCx); + ASSERT_TRUE(JS_GetProperty(aCx, extraObj, extraKey.get(), &extraVal)) + << "Must be able to get the extra key's value."; + ASSERT_TRUE(jsStr.init(aCx, extraVal)) + << "Extra must be able to be init'd to a jsstring."; + ASSERT_EQ(NS_ConvertUTF16toUTF8(jsStr).Length(), (uint32_t)80) + << "Extra must have been truncated to 80 bytes."; +} diff --git a/toolkit/components/telemetry/tests/gtest/TestHistograms.cpp b/toolkit/components/telemetry/tests/gtest/TestHistograms.cpp new file mode 100644 index 0000000000..28b6cce751 --- /dev/null +++ b/toolkit/components/telemetry/tests/gtest/TestHistograms.cpp @@ -0,0 +1,891 @@ +/* vim:set ts=2 sw=2 sts=0 et: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "gtest/gtest.h" +#include "js/Conversions.h" +#include "mozilla/Telemetry.h" +#include "TelemetryFixture.h" +#include "TelemetryTestHelpers.h" + +using namespace mozilla; +using namespace TelemetryTestHelpers; + +TEST_F(TelemetryTestFixture, AccumulateCountHistogram) { + const uint32_t kExpectedValue = 200; + AutoJSContextWithGlobal cx(mCleanGlobal); + + const char* telemetryTestCountName = + Telemetry::GetHistogramName(Telemetry::TELEMETRY_TEST_COUNT); + ASSERT_STREQ(telemetryTestCountName, "TELEMETRY_TEST_COUNT") + << "The histogram name is wrong"; + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns, + false); + + // Accumulate in the histogram + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_COUNT, kExpectedValue / 2); + Telemetry::Accumulate("TELEMETRY_TEST_COUNT", kExpectedValue / 2); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, telemetryTestCountName, &snapshot, + false); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), telemetryTestCountName, snapshot, &histogram); + + // Get "sum" property from histogram + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", histogram, &sum); + + // Check that the "sum" stored in the histogram matches with |kExpectedValue| + uint32_t uSum = 0; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, kExpectedValue) + << "The histogram is not returning expected value"; +} + +TEST_F(TelemetryTestFixture, AccumulateKeyedCountHistogram) { + const uint32_t kExpectedValue = 100; + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_KEYED_COUNT"_ns, true); + + // Accumulate data in the provided key within the histogram + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_COUNT, "sample"_ns, + kExpectedValue); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_COUNT", + &snapshot, true); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_COUNT", snapshot, + &histogram); + + // Get "sample" property from histogram + JS::Rooted<JS::Value> expectedKeyData(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sample", histogram, &expectedKeyData); + + // Get "sum" property from keyed data + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum); + + // Check that the sum stored in the histogram matches with |kExpectedValue| + uint32_t uSum = 0; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, kExpectedValue) + << "The histogram is not returning expected sum"; +} + +TEST_F(TelemetryTestFixture, TestKeyedKeysHistogram) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + JS::Rooted<JS::Value> testHistogram(cx.GetJSContext()); + JS::Rooted<JS::Value> rval(cx.GetJSContext()); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_KEYED_KEYS"_ns, true); + + // Test the accumulation on both the allowed and unallowed keys, using + // the API that accepts histogram IDs. + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_KEYS, "not-allowed"_ns, + 1); + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_KEYS, "testkey"_ns, 0); + // Do the same, using the API that accepts the histogram name as a string. + Telemetry::Accumulate("TELEMETRY_TEST_KEYED_KEYS", "not-allowed"_ns, 1); + Telemetry::Accumulate("TELEMETRY_TEST_KEYED_KEYS", "CommonKey"_ns, 1); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_KEYS", + &snapshot, true); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_KEYS", snapshot, + &histogram); + + // Get "testkey" property from histogram and check that it stores the correct + // data. + JS::Rooted<JS::Value> expectedKeyData(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "testkey", histogram, &expectedKeyData); + ASSERT_TRUE(!expectedKeyData.isUndefined()) + << "Cannot find the expected key in the histogram data"; + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum); + uint32_t uSum = 0; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, 0U) + << "The histogram is not returning expected sum for 'testkey'"; + + // Do the same for the "CommonKey" property. + GetProperty(cx.GetJSContext(), "CommonKey", histogram, &expectedKeyData); + ASSERT_TRUE(!expectedKeyData.isUndefined()) + << "Cannot find the expected key in the histogram data"; + GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum); + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, 1U) + << "The histogram is not returning expected sum for 'CommonKey'"; + + GetProperty(cx.GetJSContext(), "not-allowed", histogram, &expectedKeyData); + ASSERT_TRUE(expectedKeyData.isUndefined()) + << "Unallowed keys must not be recorded in the histogram data"; + + // The 'not-allowed' key accumulation for 'TELEMETRY_TESTED_KEYED_KEYS' was + // attemtped twice, so we expect the count of + // 'telemetry.accumulate_unknown_histogram_keys' to be 2 + const uint32_t expectedAccumulateUnknownCount = 2; + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + CheckKeyedUintScalar("telemetry.accumulate_unknown_histogram_keys", + "TELEMETRY_TEST_KEYED_KEYS", cx.GetJSContext(), + scalarsSnapshot, expectedAccumulateUnknownCount); +} + +TEST_F(TelemetryTestFixture, AccumulateCategoricalHistogram) { + const uint32_t kExpectedValue = 2; + + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_CATEGORICAL"_ns, false); + + // Accumulate one unit into the categorical histogram with label + // Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel + Telemetry::AccumulateCategorical( + Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel); + + // Accumulate another unit into the same categorical histogram using a string + // label + Telemetry::AccumulateCategorical(Telemetry::TELEMETRY_TEST_CATEGORICAL, + "CommonLabel"_ns); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL", + &snapshot, false); + + // Get our histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot, + &histogram); + + // Get values object from histogram. Each entry in the object maps to a label + // in the histogram. + JS::Rooted<JS::Value> values(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "values", histogram, &values); + + // Get the value for the label we care about + JS::Rooted<JS::Value> value(cx.GetJSContext()); + GetElement(cx.GetJSContext(), + static_cast<uint32_t>( + Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel), + values, &value); + + // Check that the value stored in the histogram matches with |kExpectedValue| + uint32_t uValue = 0; + JS::ToUint32(cx.GetJSContext(), value, &uValue); + ASSERT_EQ(uValue, kExpectedValue) + << "The histogram is not returning expected value"; +} + +TEST_F(TelemetryTestFixture, AccumulateKeyedCategoricalHistogram) { + const uint32_t kSampleExpectedValue = 2; + const uint32_t kOtherSampleExpectedValue = 1; + + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_KEYED_CATEGORICAL"_ns, true); + + // Accumulate one unit into the categorical histogram with label + // Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel + Telemetry::AccumulateCategoricalKeyed( + "sample"_ns, + Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel); + // Accumulate another unit into the same categorical histogram + Telemetry::AccumulateCategoricalKeyed( + "sample"_ns, + Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel); + // Accumulate another unit into a different categorical histogram + Telemetry::AccumulateCategoricalKeyed( + "other-sample"_ns, + Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_KEYED_CATEGORICAL", &snapshot, true); + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_CATEGORICAL", snapshot, + &histogram); + + // Check that the sample histogram contains the values we expect + JS::Rooted<JS::Value> sample(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sample", histogram, &sample); + // Get values object from sample. Each entry in the object maps to a label in + // the histogram. + JS::Rooted<JS::Value> sampleValues(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "values", sample, &sampleValues); + // Get the value for the label we care about + JS::Rooted<JS::Value> sampleValue(cx.GetJSContext()); + GetElement( + cx.GetJSContext(), + static_cast<uint32_t>( + Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel), + sampleValues, &sampleValue); + // Check that the value stored in the histogram matches with + // |kSampleExpectedValue| + uint32_t uSampleValue = 0; + JS::ToUint32(cx.GetJSContext(), sampleValue, &uSampleValue); + ASSERT_EQ(uSampleValue, kSampleExpectedValue) + << "The sample histogram is not returning expected value"; + + // Check that the other-sample histogram contains the values we expect + JS::Rooted<JS::Value> otherSample(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "other-sample", histogram, &otherSample); + // Get values object from the other-sample. Each entry in the object maps to a + // label in the histogram. + JS::Rooted<JS::Value> otherValues(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "values", otherSample, &otherValues); + // Get the value for the label we care about + JS::Rooted<JS::Value> otherValue(cx.GetJSContext()); + GetElement( + cx.GetJSContext(), + static_cast<uint32_t>( + Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel), + otherValues, &otherValue); + // Check that the value stored in the histogram matches with + // |kOtherSampleExpectedValue| + uint32_t uOtherValue = 0; + JS::ToUint32(cx.GetJSContext(), otherValue, &uOtherValue); + ASSERT_EQ(uOtherValue, kOtherSampleExpectedValue) + << "The other-sample histogram is not returning expected value"; +} + +TEST_F(TelemetryTestFixture, AccumulateCountHistogram_MultipleSamples) { + nsTArray<uint32_t> samples({4, 4, 4}); + const uint32_t kExpectedSum = 12; + + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns, + false); + + // Accumulate in histogram + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_COUNT, samples); + + // Get a snapshot of all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT", &snapshot, + false); + + // Get histogram from snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_COUNT", snapshot, &histogram); + + // Get "sum" from histogram + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", histogram, &sum); + + // Check that sum matches with aValue + uint32_t uSum = 0; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, kExpectedSum) + << "This histogram is not returning expected value"; +} + +TEST_F(TelemetryTestFixture, AccumulateLinearHistogram_MultipleSamples) { + nsTArray<uint32_t> samples({4, 4, 4}); + const uint32_t kExpectedCount = 3; + + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_LINEAR"_ns, false); + + // Accumulate in the histogram + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_LINEAR, samples); + + // Get a snapshot of all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_LINEAR", + &snapshot, false); + + // Get histogram from snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_LINEAR", snapshot, &histogram); + + // Get "values" object from histogram + JS::Rooted<JS::Value> values(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "values", histogram, &values); + + // Index 0 is only for values less than 'low'. Values within range start at + // index 1 + JS::Rooted<JS::Value> count(cx.GetJSContext()); + const uint32_t index = 1; + GetElement(cx.GetJSContext(), index, values, &count); + + // Check that this count matches with nSamples + uint32_t uCount = 0; + JS::ToUint32(cx.GetJSContext(), count, &uCount); + ASSERT_EQ(uCount, kExpectedCount) + << "The histogram did not accumulate the correct number of values"; +} + +TEST_F(TelemetryTestFixture, AccumulateLinearHistogram_DifferentSamples) { + nsTArray<uint32_t> samples( + {4, 8, 2147483646, uint32_t(INT_MAX) + 1, UINT32_MAX}); + + AutoJSContextWithGlobal cx(mCleanGlobal); + + mTelemetry->ClearScalars(); + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_LINEAR"_ns, false); + + // Accumulate in histogram + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_LINEAR, samples); + + // Get a snapshot of all histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_LINEAR", + &snapshot, false); + + // Get histogram from snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_LINEAR", snapshot, &histogram); + + // Get values object from histogram + JS::Rooted<JS::Value> values(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "values", histogram, &values); + + // Get values in first and last buckets + JS::Rooted<JS::Value> countFirst(cx.GetJSContext()); + JS::Rooted<JS::Value> countLast(cx.GetJSContext()); + const uint32_t firstIndex = 1; + // Buckets are indexed by their start value + const uint32_t lastIndex = INT32_MAX - 1; + GetElement(cx.GetJSContext(), firstIndex, values, &countFirst); + GetElement(cx.GetJSContext(), lastIndex, values, &countLast); + + // Check that the values match + uint32_t uCountFirst = 0; + uint32_t uCountLast = 0; + JS::ToUint32(cx.GetJSContext(), countFirst, &uCountFirst); + JS::ToUint32(cx.GetJSContext(), countLast, &uCountLast); + + const uint32_t kExpectedCountFirst = 2; + // We expect 2147483646 to be in the last bucket, as well the two samples + // above 2^31 (prior to bug 1438335, values between INT_MAX and UINT32_MAX + // would end up as 0s) + const uint32_t kExpectedCountLast = 3; + ASSERT_EQ(uCountFirst, kExpectedCountFirst) + << "The first bucket did not accumulate the correct number of values"; + ASSERT_EQ(uCountLast, kExpectedCountLast) + << "The last bucket did not accumulate the correct number of values"; + + // We accumulated two values that had to be clamped. We expect the count in + // 'telemetry.accumulate_clamped_values' to be 2 (only one storage). + const uint32_t expectedAccumulateClampedCount = 2; + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + CheckKeyedUintScalar("telemetry.accumulate_clamped_values", + "TELEMETRY_TEST_LINEAR", cx.GetJSContext(), + scalarsSnapshot, expectedAccumulateClampedCount); +} + +TEST_F(TelemetryTestFixture, AccumulateKeyedCountHistogram_MultipleSamples) { + const nsTArray<uint32_t> samples({5, 10, 15}); + const uint32_t kExpectedSum = 5 + 10 + 15; + + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_KEYED_COUNT"_ns, true); + + // Accumulate data in the provided key within the histogram + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_COUNT, "sample"_ns, + samples); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_COUNT", + &snapshot, true); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_COUNT", snapshot, + &histogram); + + // Get "sample" property from histogram + JS::Rooted<JS::Value> expectedKeyData(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sample", histogram, &expectedKeyData); + + // Get "sum" property from keyed data + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum); + + // Check that the sum stored in the histogram matches with |kExpectedSum| + uint32_t uSum = 0; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, kExpectedSum) + << "The histogram is not returning expected sum"; +} + +TEST_F(TelemetryTestFixture, TestKeyedLinearHistogram_MultipleSamples) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + mTelemetry->ClearScalars(); + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_KEYED_LINEAR"_ns, true); + + const nsTArray<uint32_t> samples({1, 5, 250000, UINT_MAX}); + // Test the accumulation on the key 'testkey', using + // the API that accepts histogram IDs. + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_LINEAR, "testkey"_ns, + samples); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_LINEAR", + &snapshot, true); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_LINEAR", snapshot, + &histogram); + + // Get "testkey" property from histogram. + JS::Rooted<JS::Value> expectedKeyData(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "testkey", histogram, &expectedKeyData); + ASSERT_TRUE(!expectedKeyData.isUndefined()) + << "Cannot find the expected key in the histogram data"; + + // Get values object from 'testkey' histogram. + JS::Rooted<JS::Value> values(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "values", expectedKeyData, &values); + + // Get values in first and last buckets. + JS::Rooted<JS::Value> countFirst(cx.GetJSContext()); + JS::Rooted<JS::Value> countLast(cx.GetJSContext()); + const uint32_t firstIndex = 1; + // Buckets are indexed by their start value + const uint32_t lastIndex = 250000; + GetElement(cx.GetJSContext(), firstIndex, values, &countFirst); + GetElement(cx.GetJSContext(), lastIndex, values, &countLast); + + // Check that the values match. + uint32_t uCountFirst = 0; + uint32_t uCountLast = 0; + JS::ToUint32(cx.GetJSContext(), countFirst, &uCountFirst); + JS::ToUint32(cx.GetJSContext(), countLast, &uCountLast); + + const uint32_t kExpectedCountFirst = 2; + const uint32_t kExpectedCountLast = 2; + ASSERT_EQ(uCountFirst, kExpectedCountFirst) + << "The first bucket did not accumulate the correct number of values for " + "key 'testkey'"; + ASSERT_EQ(uCountLast, kExpectedCountLast) + << "The last bucket did not accumulate the correct number of values for " + "key 'testkey'"; + + // We accumulated one keyed values that had to be clamped. We expect the + // count in 'telemetry.accumulate_clamped_values' to be 1 + const uint32_t expectedAccumulateClampedCount = 1; + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + CheckKeyedUintScalar("telemetry.accumulate_clamped_values", + "TELEMETRY_TEST_KEYED_LINEAR", cx.GetJSContext(), + scalarsSnapshot, expectedAccumulateClampedCount); +} + +TEST_F(TelemetryTestFixture, TestKeyedKeysHistogram_MultipleSamples) { + AutoJSContextWithGlobal cx(mCleanGlobal); + mTelemetry->ClearScalars(); + const nsTArray<uint32_t> samples({false, false, true, 32, true}); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_KEYED_KEYS"_ns, true); + + // Test the accumulation on both the allowed and unallowed keys, using + // the API that accepts histogram IDs. + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_KEYS, "not-allowed"_ns, + samples); + Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_KEYS, "testkey"_ns, + samples); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_KEYS", + &snapshot, true); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_KEYS", snapshot, + &histogram); + + // Get "testkey" property from histogram and check that it stores the correct + // data. + JS::Rooted<JS::Value> testKeyData(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "testkey", histogram, &testKeyData); + ASSERT_TRUE(!testKeyData.isUndefined()) + << "Cannot find the key 'testkey' in the histogram data"; + + JS::Rooted<JS::Value> values(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "values", testKeyData, &values); + + // Get values in buckets 0,1,2 + const uint32_t falseIndex = 0; + const uint32_t trueIndex = 1; + const uint32_t otherIndex = 2; + + JS::Rooted<JS::Value> countFalse(cx.GetJSContext()); + JS::Rooted<JS::Value> countTrue(cx.GetJSContext()); + JS::Rooted<JS::Value> countOther(cx.GetJSContext()); + + GetElement(cx.GetJSContext(), falseIndex, values, &countFalse); + GetElement(cx.GetJSContext(), trueIndex, values, &countTrue); + GetElement(cx.GetJSContext(), otherIndex, values, &countOther); + + uint32_t uCountFalse = 0; + uint32_t uCountTrue = 0; + uint32_t uCountOther = 0; + JS::ToUint32(cx.GetJSContext(), countFalse, &uCountFalse); + JS::ToUint32(cx.GetJSContext(), countTrue, &uCountTrue); + JS::ToUint32(cx.GetJSContext(), countOther, &uCountOther); + + const uint32_t kExpectedCountFalse = 2; + const uint32_t kExpectedCountTrue = 3; + const uint32_t kExpectedCountOther = 0; + + ASSERT_EQ(uCountFalse, kExpectedCountFalse) + << "The histogram did not accumulate the correct number of 'false' " + "booleans for key 'testkey'"; + ASSERT_EQ(uCountTrue, kExpectedCountTrue) + << "The histogram did not accumulate the correct number of 'true' " + "booleans for key 'testkey'"; + ASSERT_EQ(uCountOther, kExpectedCountOther) + << "The histogram did not accumulate the correct number of undefined " + "values for key 'testkey'"; + + // Here we check that we are not accumulating to a different (but still + // 'allowed') key. Get "CommonKey" property from histogram and check that it + // has no data. Since we accumulated no data to it, commonKeyData should be + // undefined. + JS::Rooted<JS::Value> commonKeyData(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "CommonKey", histogram, &commonKeyData); + ASSERT_TRUE(commonKeyData.isUndefined()) + << "Found data in key 'CommonKey' even though we accumulated no data to " + "it"; + + // Here we check that our function does not allow accumulation into unallowed + // keys. Get 'not-allowed' property from histogram and check that this also + // has no data. This should contain no data because this key is not allowed. + JS::Rooted<JS::Value> notAllowedKeyData(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "not-allowed", histogram, ¬AllowedKeyData); + ASSERT_TRUE(notAllowedKeyData.isUndefined()) + << "Found data in key 'not-allowed' even though accumuling data to it is " + "not allowed"; + + // The 'not-allowed' key accumulation for 'TELEMETRY_TESTED_KEYED_KEYS' was + // attemtped once, so we expect the count of + // 'telemetry.accumulate_unknown_histogram_keys' to be 1 + const uint32_t expectedAccumulateUnknownCount = 1; + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + CheckKeyedUintScalar("telemetry.accumulate_unknown_histogram_keys", + "TELEMETRY_TEST_KEYED_KEYS", cx.GetJSContext(), + scalarsSnapshot, expectedAccumulateUnknownCount); +} + +TEST_F(TelemetryTestFixture, + AccumulateCategoricalHistogram_MultipleStringLabels) { + const uint32_t kExpectedValue = 2; + const nsTArray<nsCString> labels({"CommonLabel"_ns, "CommonLabel"_ns}); + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_CATEGORICAL"_ns, false); + + // Accumulate the units into a categorical histogram using a string label + Telemetry::AccumulateCategorical(Telemetry::TELEMETRY_TEST_CATEGORICAL, + labels); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL", + &snapshot, false); + + // Get our histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot, + &histogram); + + // Get values object from histogram. Each entry in the object maps to a label + // in the histogram. + JS::Rooted<JS::Value> values(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "values", histogram, &values); + + // Get the value for the label we care about + JS::Rooted<JS::Value> value(cx.GetJSContext()); + GetElement(cx.GetJSContext(), + static_cast<uint32_t>( + Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel), + values, &value); + + // Check that the value stored in the histogram matches with |kExpectedValue| + uint32_t uValue = 0; + JS::ToUint32(cx.GetJSContext(), value, &uValue); + ASSERT_EQ(uValue, kExpectedValue) + << "The histogram is not returning expected value"; + + // Now we check for no accumulation when a bad label is present in the array. + // + // The 'values' property is not initialized unless data is accumulated so + // keeping another test to check for this case alone is wasteful as we will + // have to accumulate some data anyway. + + const nsTArray<nsCString> badLabelArray({"CommonLabel"_ns, "BadLabel"_ns}); + + // Try to accumulate the array into the histogram. + Telemetry::AccumulateCategorical(Telemetry::TELEMETRY_TEST_CATEGORICAL, + badLabelArray); + + // Get snapshot of all the histograms + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL", + &snapshot, false); + + // Get our histogram from the snapshot + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot, + &histogram); + + // Get values array from histogram + GetProperty(cx.GetJSContext(), "values", histogram, &values); + + // Get the value for the label we care about + GetElement(cx.GetJSContext(), + static_cast<uint32_t>( + Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel), + values, &value); + + // Check that the value stored in the histogram matches with |kExpectedValue| + uValue = 0; + JS::ToUint32(cx.GetJSContext(), value, &uValue); + ASSERT_EQ(uValue, kExpectedValue) + << "The histogram accumulated data when it should not have"; +} + +TEST_F(TelemetryTestFixture, + AccumulateCategoricalHistogram_MultipleEnumValues) { + const uint32_t kExpectedValue = 2; + const nsTArray<Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL> enumLabels( + {Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel, + Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel}); + + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_CATEGORICAL"_ns, false); + + // Accumulate the units into a categorical histogram using the enumLabels + // array + Telemetry::AccumulateCategorical< + Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL>(enumLabels); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL", + &snapshot, false); + + // Get our histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot, + &histogram); + + // Get values object from histogram. Each entry in the object maps to a label + // in the histogram. + JS::Rooted<JS::Value> values(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "values", histogram, &values); + + // Get the value for the label we care about + JS::Rooted<JS::Value> value(cx.GetJSContext()); + GetElement(cx.GetJSContext(), + static_cast<uint32_t>( + Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel), + values, &value); + + // Check that the value stored in the histogram matches with |kExpectedValue| + uint32_t uValue = 0; + JS::ToUint32(cx.GetJSContext(), value, &uValue); + ASSERT_EQ(uValue, kExpectedValue) + << "The histogram is not returning expected value"; +} + +TEST_F(TelemetryTestFixture, + AccumulateKeyedCategoricalHistogram_MultipleEnumValues) { + const uint32_t kExpectedCommonLabel = 2; + const uint32_t kExpectedLabel2 = 1; + const nsTArray<Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL> enumLabels( + {Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel, + Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel, + Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::Label2}); + + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_KEYED_CATEGORICAL"_ns, true); + + // Accumulate the array into the categorical keyed histogram + Telemetry::AccumulateCategoricalKeyed("sampleKey"_ns, enumLabels); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_KEYED_CATEGORICAL", &snapshot, true); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_CATEGORICAL", snapshot, + &histogram); + + // Check that the sampleKey histogram contains correct number of CommonLabel + // samples + JS::Rooted<JS::Value> sample(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sampleKey", histogram, &sample); + + // Get values object from the sample. Each entry in the object maps to a label + // in the histogram. + JS::Rooted<JS::Value> sampleKeyValues(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "values", sample, &sampleKeyValues); + + // Get the count of CommonLabel + JS::Rooted<JS::Value> commonLabelValue(cx.GetJSContext()); + GetElement( + cx.GetJSContext(), + static_cast<uint32_t>( + Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel), + sampleKeyValues, &commonLabelValue); + + // Check that the value stored in the histogram matches with + // |kExpectedCommonLabel| + uint32_t uCommonLabelValue = 0; + JS::ToUint32(cx.GetJSContext(), commonLabelValue, &uCommonLabelValue); + ASSERT_EQ(uCommonLabelValue, kExpectedCommonLabel) + << "The sampleKey histogram did not accumulate the correct number of " + "CommonLabel samples"; + + // Check that the sampleKey histogram contains the correct number of Label2 + // values Get the count of Label2 + JS::Rooted<JS::Value> label2Value(cx.GetJSContext()); + GetElement(cx.GetJSContext(), + static_cast<uint32_t>( + Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::Label2), + sampleKeyValues, &label2Value); + + // Check that the value stored in the histogram matches with |kExpectedLabel2| + uint32_t uLabel2Value = 0; + JS::ToUint32(cx.GetJSContext(), label2Value, &uLabel2Value); + ASSERT_EQ(uLabel2Value, kExpectedLabel2) + << "The sampleKey histogram did not accumulate the correct number of " + "Label2 samples"; +} + +TEST_F(TelemetryTestFixture, AccumulateTimeDelta) { + const uint32_t kExpectedValue = 100; + const TimeStamp start = TimeStamp::Now(); + const TimeDuration delta = TimeDuration::FromMilliseconds(50); + + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns, + false); + + // Accumulate in the histogram + Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start - delta, + start); + + Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start - delta, + start); + + Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start, start); + + // end > start timestamp gives zero contribution + Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start + delta, + start); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT", &snapshot, + false); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_COUNT", snapshot, &histogram); + + // Get "sum" property from histogram + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", histogram, &sum); + + // Check that the "sum" stored in the histogram matches with |kExpectedValue| + uint32_t uSum = 0; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, kExpectedValue) + << "The histogram is not returning expected value"; +} + +TEST_F(TelemetryTestFixture, AccumulateKeyedTimeDelta) { + const uint32_t kExpectedValue = 100; + const TimeStamp start = TimeStamp::Now(); + const TimeDuration delta = TimeDuration::FromMilliseconds(50); + + AutoJSContextWithGlobal cx(mCleanGlobal); + + GetAndClearHistogram(cx.GetJSContext(), mTelemetry, + "TELEMETRY_TEST_KEYED_COUNT"_ns, true); + + // Accumulate time delta in the provided key within the histogram + Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT, + "sample"_ns, start - delta, start); + + Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT, + "sample"_ns, start - delta, start); + + // end > start timestamp gives zero contribution + Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT, + "sample"_ns, start + delta, start); + + Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT, + "sample"_ns, start, start); + + // Get a snapshot for all the histograms + JS::Rooted<JS::Value> snapshot(cx.GetJSContext()); + GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_COUNT", + &snapshot, true); + + // Get the histogram from the snapshot + JS::Rooted<JS::Value> histogram(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_COUNT", snapshot, + &histogram); + + // Get "sample" property from histogram + JS::Rooted<JS::Value> expectedKeyData(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sample", histogram, &expectedKeyData); + + // Get "sum" property from keyed data + JS::Rooted<JS::Value> sum(cx.GetJSContext()); + GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum); + + // Check that the sum stored in the histogram matches with |kExpectedValue| + uint32_t uSum = 0; + JS::ToUint32(cx.GetJSContext(), sum, &uSum); + ASSERT_EQ(uSum, kExpectedValue) + << "The histogram is not returning expected sum"; +} diff --git a/toolkit/components/telemetry/tests/gtest/TestScalars.cpp b/toolkit/components/telemetry/tests/gtest/TestScalars.cpp new file mode 100644 index 0000000000..4a95ebecd0 --- /dev/null +++ b/toolkit/components/telemetry/tests/gtest/TestScalars.cpp @@ -0,0 +1,591 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "core/TelemetryScalar.h" +#include "gtest/gtest.h" +#include "js/Conversions.h" +#include "js/PropertyAndElement.h" // JS_GetProperty, JS_HasProperty +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryProcessEnums.h" +#include "mozilla/Unused.h" +#include "nsJSUtils.h" // nsAutoJSString +#include "nsThreadUtils.h" +#include "TelemetryFixture.h" +#include "TelemetryTestHelpers.h" + +using namespace mozilla; +using namespace TelemetryTestHelpers; +using mozilla::Telemetry::ProcessID; + +#define EXPECTED_STRING "Nice, expected and creative string." + +// Test that we can properly write unsigned scalars using the C++ API. +TEST_F(TelemetryTestFixture, ScalarUnsigned) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + + // Set the test scalar to a known value. + const uint32_t kInitialValue = 1172015; + const uint32_t kExpectedUint = 1172017; + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, + kInitialValue); + Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, + kExpectedUint - kInitialValue); + + // Check the recorded value. + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); + CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(), + scalarsSnapshot, kExpectedUint); + + // Try to use SetMaximum. + const uint32_t kExpectedUintMaximum = kExpectedUint * 2; + Telemetry::ScalarSetMaximum( + Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, + kExpectedUintMaximum); + +// Make sure that calls of the unsupported type don't corrupt the stored value. +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, + false); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, + u"test"_ns); +#endif + + // Check the recorded value. + GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); + CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(), + scalarsSnapshot, kExpectedUintMaximum); +} + +// Test that the AutoScalarTimer records a proper uint32_t value to a +// scalar once it goes out of scope. +TEST_F(TelemetryTestFixture, AutoScalarTimer) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + { + Telemetry::AutoScalarTimer< + Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND> + timer; + } + + const char* kScalarName = "telemetry.test.unsigned_int_kind"; + + // Check that there's a recorded value that is greater than 0. Since + // this is a timer, we'll not check the non-deterministic value - just + // that it exists. + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); + + // Validate the value of the test scalar. + JS::Rooted<JS::Value> value(cx.GetJSContext()); + JS::Rooted<JSObject*> scalarObj(cx.GetJSContext(), + &scalarsSnapshot.toObject()); + ASSERT_TRUE(JS_GetProperty(cx.GetJSContext(), scalarObj, kScalarName, &value)) + << "The test scalar must be reported."; + + JS_GetProperty(cx.GetJSContext(), scalarObj, kScalarName, &value); + ASSERT_TRUE(value.isInt32()) + << "The scalar value must be of the correct type."; + ASSERT_TRUE(value.toInt32() >= 0) + << "The uint scalar type must contain a value >= 0."; +} + +// Test that we can properly write boolean scalars using the C++ API. +TEST_F(TelemetryTestFixture, ScalarBoolean) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + Unused << mTelemetry->ClearScalars(); + + // Set the test scalar to a known value. + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, true); + +// Make sure that calls of the unsupported type don't corrupt the stored value. +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, + static_cast<uint32_t>(12)); + Telemetry::ScalarSetMaximum(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, + 20); + Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, 2); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, + u"test"_ns); +#endif + + // Check the recorded value. + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); + CheckBoolScalar("telemetry.test.boolean_kind", cx.GetJSContext(), + scalarsSnapshot, true); +} + +// Test that we can properly write string scalars using the C++ API. +TEST_F(TelemetryTestFixture, ScalarString) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + Unused << mTelemetry->ClearScalars(); + + // Set the test scalar to a known value. + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, + NS_LITERAL_STRING_FROM_CSTRING(EXPECTED_STRING)); + +// Make sure that calls of the unsupported type don't corrupt the stored value. +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, + static_cast<uint32_t>(12)); + Telemetry::ScalarSetMaximum(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, + 20); + Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, 2); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, true); +#endif + + // Check the recorded value. + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); + CheckStringScalar("telemetry.test.string_kind", cx.GetJSContext(), + scalarsSnapshot, EXPECTED_STRING); +} + +// Test that we can properly write keyed unsigned scalars using the C++ API. +TEST_F(TelemetryTestFixture, KeyedScalarUnsigned) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + Unused << mTelemetry->ClearScalars(); + + // Set the test scalar to a known value. + const char* kScalarName = "telemetry.test.keyed_unsigned_int"; + const uint32_t kKey1Value = 1172015; + const uint32_t kKey2Value = 1172017; + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, + u"key1"_ns, kKey1Value); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, + u"key2"_ns, kKey1Value); + Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, + u"key2"_ns, 2); + +// Make sure that calls of the unsupported type don't corrupt the stored value. +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, + u"key1"_ns, false); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, + u"test"_ns); +#endif + + // Check the recorded value. + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + + // Check the keyed scalar we're interested in. + CheckKeyedUintScalar(kScalarName, "key1", cx.GetJSContext(), scalarsSnapshot, + kKey1Value); + CheckKeyedUintScalar(kScalarName, "key2", cx.GetJSContext(), scalarsSnapshot, + kKey2Value); + CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 2); + + // Try to use SetMaximum. + const uint32_t kExpectedUintMaximum = kKey1Value * 2; + Telemetry::ScalarSetMaximum( + Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, u"key1"_ns, + kExpectedUintMaximum); + + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + // The first key should be different and te second is expected to be the same. + CheckKeyedUintScalar(kScalarName, "key1", cx.GetJSContext(), scalarsSnapshot, + kExpectedUintMaximum); + CheckKeyedUintScalar(kScalarName, "key2", cx.GetJSContext(), scalarsSnapshot, + kKey2Value); + CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 2); +} + +TEST_F(TelemetryTestFixture, KeyedScalarBoolean) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + Unused << mTelemetry->ClearScalars(); + + // Set the test scalar to a known value. + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND, + u"key1"_ns, false); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND, + u"key2"_ns, true); + +// Make sure that calls of the unsupported type don't corrupt the stored value. +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND, + u"key1"_ns, static_cast<uint32_t>(12)); + Telemetry::ScalarSetMaximum( + Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND, u"key1"_ns, 20); + Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND, + u"key1"_ns, 2); +#endif + + // Check the recorded value. + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + + // Make sure that the keys contain the expected values. + const char* kScalarName = "telemetry.test.keyed_boolean_kind"; + CheckKeyedBoolScalar(kScalarName, "key1", cx.GetJSContext(), scalarsSnapshot, + false); + CheckKeyedBoolScalar(kScalarName, "key2", cx.GetJSContext(), scalarsSnapshot, + true); + CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 2); +} + +TEST_F(TelemetryTestFixture, NonMainThreadAdd) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + Unused << mTelemetry->ClearScalars(); + + // Define the function that will be called on the testing thread. + nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction( + "TelemetryTestFixture_NonMainThreadAdd_Test::TestBody", []() -> void { + Telemetry::ScalarAdd( + Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, 37); + }); + + // Spawn the testing thread and run the function. + nsCOMPtr<nsIThread> testingThread; + nsresult rv = + NS_NewNamedThread("Test thread", getter_AddRefs(testingThread), runnable); + ASSERT_EQ(rv, NS_OK); + + // Shutdown the thread. This also waits for the runnable to complete. + testingThread->Shutdown(); + + // Check the recorded value. + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); + CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(), + scalarsSnapshot, 37); +} + +TEST_F(TelemetryTestFixture, ScalarUnknownID) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + const uint32_t kTestFakeIds[] = { + static_cast<uint32_t>(Telemetry::ScalarID::ScalarCount), + static_cast<uint32_t>(Telemetry::ScalarID::ScalarCount) + 378537, + std::numeric_limits<uint32_t>::max()}; + + for (auto id : kTestFakeIds) { + Telemetry::ScalarID scalarId = static_cast<Telemetry::ScalarID>(id); + Telemetry::ScalarSet(scalarId, static_cast<uint32_t>(1)); + Telemetry::ScalarSet(scalarId, true); + Telemetry::ScalarSet(scalarId, u"test"_ns); + Telemetry::ScalarAdd(scalarId, 1); + Telemetry::ScalarSetMaximum(scalarId, 1); + + // Make sure that nothing was recorded in the plain scalars. + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); + ASSERT_TRUE(scalarsSnapshot.isUndefined()) + << "No scalar must be recorded"; + + // Same for the keyed scalars. + Telemetry::ScalarSet(scalarId, u"key1"_ns, static_cast<uint32_t>(1)); + Telemetry::ScalarSet(scalarId, u"key1"_ns, true); + Telemetry::ScalarAdd(scalarId, u"key1"_ns, 1); + Telemetry::ScalarSetMaximum(scalarId, u"key1"_ns, 1); + + // Make sure that nothing was recorded in the keyed scalars. + JS::Rooted<JS::Value> keyedSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &keyedSnapshot); + ASSERT_TRUE(keyedSnapshot.isUndefined()) + << "No keyed scalar must be recorded"; + } +#endif +} + +TEST_F(TelemetryTestFixture, ScalarEventSummary) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + + const char* kScalarName = "telemetry.event_counts"; + + const char* kLongestEvent = + "oohwowlookthiscategoryissolong#thismethodislongtooo#" + "thisobjectisnoslouch"; + TelemetryScalar::SummarizeEvent(nsCString(kLongestEvent), ProcessID::Parent, + false /* aDynamic */); + + // Check the recorded value. + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + + CheckKeyedUintScalar(kScalarName, kLongestEvent, cx.GetJSContext(), + scalarsSnapshot, 1); + +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + const char* kTooLongEvent = + "oohwowlookthiscategoryissolong#thismethodislongtooo#" + "thisobjectisnoslouch2"; + TelemetryScalar::SummarizeEvent(nsCString(kTooLongEvent), ProcessID::Parent, + false /* aDynamic */); + + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 1); +#endif // #ifndef DEBUG + + // Test we can fill the next 499 keys up to our 500 maximum + for (int i = 1; i < 500; i++) { + std::ostringstream eventName; + eventName << "category#method#object" << i; + TelemetryScalar::SummarizeEvent(nsCString(eventName.str().c_str()), + ProcessID::Parent, false /* aDynamic */); + } + + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 500); + +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + TelemetryScalar::SummarizeEvent(nsCString("whoops#too#many"), + ProcessID::Parent, false /* aDynamic */); + + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 500); +#endif // #ifndef DEBUG +} + +TEST_F(TelemetryTestFixture, ScalarEventSummary_Dynamic) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + + const char* kScalarName = "telemetry.dynamic_event_counts"; + const char* kLongestEvent = + "oohwowlookthiscategoryissolong#thismethodislongtooo#" + "thisobjectisnoslouch"; + TelemetryScalar::SummarizeEvent(nsCString(kLongestEvent), ProcessID::Parent, + true /* aDynamic */); + TelemetryScalar::SummarizeEvent(nsCString(kLongestEvent), ProcessID::Content, + true /* aDynamic */); + + // Check the recorded value. + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot, + ProcessID::Dynamic); + + // Recording in parent or content doesn't matter for dynamic scalars + // which all end up in the same place. + CheckKeyedUintScalar(kScalarName, kLongestEvent, cx.GetJSContext(), + scalarsSnapshot, 2); +} + +TEST_F(TelemetryTestFixture, WrongScalarOperator) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + + const uint32_t expectedValue = 1172015; + + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, + expectedValue); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, + NS_LITERAL_STRING_FROM_CSTRING(EXPECTED_STRING)); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, true); + + TelemetryScalar::DeserializationStarted(); + + Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, 1447); + Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, 1447); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, + true); + TelemetryScalar::ApplyPendingOperations(); + + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); + CheckStringScalar("telemetry.test.string_kind", cx.GetJSContext(), + scalarsSnapshot, EXPECTED_STRING); + CheckBoolScalar("telemetry.test.boolean_kind", cx.GetJSContext(), + scalarsSnapshot, true); + CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(), + scalarsSnapshot, expectedValue); +} + +TEST_F(TelemetryTestFixture, WrongKeyedScalarOperator) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + + const uint32_t kExpectedUint = 1172017; + + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, + u"key1"_ns, kExpectedUint); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND, + u"key2"_ns, true); + + TelemetryScalar::DeserializationStarted(); + + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, + u"key1"_ns, false); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND, + u"key2"_ns, static_cast<uint32_t>(13)); + + TelemetryScalar::ApplyPendingOperations(); + + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + CheckKeyedUintScalar("telemetry.test.keyed_unsigned_int", "key1", + cx.GetJSContext(), scalarsSnapshot, kExpectedUint); + CheckKeyedBoolScalar("telemetry.test.keyed_boolean_kind", "key2", + cx.GetJSContext(), scalarsSnapshot, true); +} + +TEST_F(TelemetryTestFixture, TestKeyedScalarAllowedKeys) { + AutoJSContextWithGlobal cx(mCleanGlobal); + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + + const uint32_t kExpectedUint = 1172017; + + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS, + u"only"_ns, kExpectedUint); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS, + u"meant"_ns, kExpectedUint); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS, + u"for"_ns, kExpectedUint); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS, + u"testing"_ns, kExpectedUint); + + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS, + u"invalid"_ns, kExpectedUint); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS, + u"not-valid"_ns, kExpectedUint); + + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + CheckKeyedUintScalar("telemetry.test.keyed_with_keys", "only", + cx.GetJSContext(), scalarsSnapshot, kExpectedUint); + CheckKeyedUintScalar("telemetry.test.keyed_with_keys", "meant", + cx.GetJSContext(), scalarsSnapshot, kExpectedUint); + CheckKeyedUintScalar("telemetry.test.keyed_with_keys", "for", + cx.GetJSContext(), scalarsSnapshot, kExpectedUint); + CheckKeyedUintScalar("telemetry.test.keyed_with_keys", "testing", + cx.GetJSContext(), scalarsSnapshot, kExpectedUint); + CheckNumberOfProperties("telemetry.test.keyed_with_keys", cx.GetJSContext(), + scalarsSnapshot, 4); + + CheckKeyedUintScalar("telemetry.keyed_scalars_unknown_keys", + "telemetry.test.keyed_with_keys", cx.GetJSContext(), + scalarsSnapshot, 2); +} + +// Test that we can properly handle too long key. +TEST_F(TelemetryTestFixture, TooLongKey) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + const char* kScalarName = "telemetry.test.keyed_unsigned_int"; + const uint32_t kKey1Value = 1172015; + + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, + u"123456789012345678901234567890123456789012345678901234" + u"5678901234567890morethanseventy"_ns, + kKey1Value); + + const uint32_t kDummyUint = 1172017; + + // add dummy value so that object can be created from snapshot + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS, + u"dummy"_ns, kDummyUint); + // Check the recorded value + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + + bool foundp = true; + JS::Rooted<JSObject*> scalarObj(cx.GetJSContext(), + &scalarsSnapshot.toObject()); + ASSERT_TRUE( + JS_HasProperty(cx.GetJSContext(), scalarObj, kScalarName, &foundp)); + EXPECT_FALSE(foundp); +#endif // #ifndef DEBUG +} + +// Test that we can properly handle empty key +TEST_F(TelemetryTestFixture, EmptyKey) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + const char* kScalarName = "telemetry.test.keyed_unsigned_int"; + const uint32_t kKey1Value = 1172015; + + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, + u""_ns, kKey1Value); + + const uint32_t kDummyUint = 1172017; + + // add dummy value so that object can be created from snapshot + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS, + u"dummy"_ns, kDummyUint); + + // Check the recorded value + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + + bool foundp = true; + JS::Rooted<JSObject*> scalarObj(cx.GetJSContext(), + &scalarsSnapshot.toObject()); + ASSERT_TRUE( + JS_HasProperty(cx.GetJSContext(), scalarObj, kScalarName, &foundp)); + EXPECT_FALSE(foundp); +#endif // #ifndef DEBUG +} + +// Test that we can properly handle too many keys +TEST_F(TelemetryTestFixture, TooManyKeys) { + AutoJSContextWithGlobal cx(mCleanGlobal); + + // Make sure we don't get scalars from other tests. + Unused << mTelemetry->ClearScalars(); + +// Don't run this part in debug builds as that intentionally asserts. +#ifndef DEBUG + const char* kScalarName = "telemetry.test.keyed_unsigned_int"; + const uint32_t kKey1Value = 1172015; + + for (int i = 0; i < 150; i++) { + std::u16string key = u"key"; + char16_t n = i + '0'; + key.push_back(n); + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, + nsString(key.c_str()), kKey1Value); + } + + // Check the recorded value + JS::Rooted<JS::Value> scalarsSnapshot(cx.GetJSContext()); + GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot); + + // Check 100 keys are present. + CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 100); +#endif // #ifndef DEBUG +} diff --git a/toolkit/components/telemetry/tests/gtest/moz.build b/toolkit/components/telemetry/tests/gtest/moz.build new file mode 100644 index 0000000000..cdf67dc63d --- /dev/null +++ b/toolkit/components/telemetry/tests/gtest/moz.build @@ -0,0 +1,29 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +Library("telemetrytest") + +LOCAL_INCLUDES += [ + "../..", +] + +UNIFIED_SOURCES = [ + "TelemetryFixture.cpp", + "TelemetryTestHelpers.cpp", +] + +if CONFIG["OS_TARGET"] != "Android": + UNIFIED_SOURCES += [ + "TestCombinedStacks.cpp", + "TestCounters.cpp", + "TestEvents.cpp", + "TestHistograms.cpp", + "TestScalars.cpp", + ] + +FINAL_LIBRARY = "xul-gtest" + +include("/ipc/chromium/chromium-config.mozbuild") |