diff --git a/toolkit/components/glean/api/src/ffi/mod.rs b/toolkit/components/glean/api/src/ffi/mod.rs index 9d3a70ff2403..ffbab3b0dbf3 100644 --- a/toolkit/components/glean/api/src/ffi/mod.rs +++ b/toolkit/components/glean/api/src/ffi/mod.rs @@ -22,6 +22,38 @@ define_metric_ffi!(TIMESPAN_MAP { stop -> fog_timespan_stop(), }); +// The String functions are custom because test_get needs to use an outparam. +// If we can make test_get optional, we can go back to using the macro to +// generate the rest of the functions, or something. + +#[no_mangle] +pub extern "C" fn fog_string_test_has_value(id: u32, storage_name: FfiStr) -> u8 { + match crate::metrics::__glean_metric_maps::STRING_MAP.get(&id.into()) { + Some(metric) => metric.test_get_value(storage_name.as_str()).is_some() as u8, + None => panic!("No metric for id {}", id), + } +} + +#[cfg(feature = "with_gecko")] +#[no_mangle] +pub extern "C" fn fog_string_test_get_value(id: u32, storage_name: FfiStr, value: &mut nsACString) { + match crate::metrics::__glean_metric_maps::STRING_MAP.get(&id.into()) { + Some(metric) => { + value.assign(&metric.test_get_value(storage_name.as_str()).unwrap()); + } + None => panic!("No metric for id {}", id), + } +} + +#[cfg(feature = "with_gecko")] +#[no_mangle] +pub extern "C" fn fog_string_set(id: u32, value: &nsACString) { + match crate::metrics::__glean_metric_maps::STRING_MAP.get(&id.into()) { + Some(metric) => metric.set(value.to_utf8()), + None => panic!("No metric for id {}", id), + } +} + // The Uuid functions are custom because test_get needs to use an outparam. // If we can make test_get optional, we can go back to using the macro to // generate the rest of the functions, or something. diff --git a/toolkit/components/glean/bindings/MetricTypes.h b/toolkit/components/glean/bindings/MetricTypes.h index 34cbd696d077..15dd1dd64ae6 100644 --- a/toolkit/components/glean/bindings/MetricTypes.h +++ b/toolkit/components/glean/bindings/MetricTypes.h @@ -7,6 +7,7 @@ #include "mozilla/glean/Counter.h" #include "mozilla/glean/Timespan.h" +#include "mozilla/glean/String.h" #include "mozilla/glean/Uuid.h" #endif // mozilla_Glean_MetricTypes_h diff --git a/toolkit/components/glean/bindings/private/String.cpp b/toolkit/components/glean/bindings/private/String.cpp new file mode 100644 index 000000000000..98d7dffa3a0b --- /dev/null +++ b/toolkit/components/glean/bindings/private/String.cpp @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/glean/String.h" + +#include "nsString.h" +#include "mozilla/Components.h" +#include "nsIClassInfoImpl.h" + +namespace mozilla { +namespace glean { + +NS_IMPL_CLASSINFO(GleanString, nullptr, 0, {0}) +NS_IMPL_ISUPPORTS_CI(GleanString, nsIGleanString) + +NS_IMETHODIMP +GleanString::Set(const nsACString& value, JSContext* cx) { + this->mString.Set(value); + return NS_OK; +} + +NS_IMETHODIMP +GleanString::TestHasValue(const nsACString& aStorageName, JSContext* cx, + bool* result) { + *result = this->mString.TestHasValue(PromiseFlatCString(aStorageName).get()); + return NS_OK; +} + +NS_IMETHODIMP +GleanString::TestGetValue(const nsACString& aStorageName, JSContext* cx, + nsACString& result) { + result.Assign( + this->mString.TestGetValue(PromiseFlatCString(aStorageName).get())); + return NS_OK; +} + +} // namespace glean +} // namespace mozilla diff --git a/toolkit/components/glean/bindings/private/String.h b/toolkit/components/glean/bindings/private/String.h new file mode 100644 index 000000000000..d4a2cb7c6be9 --- /dev/null +++ b/toolkit/components/glean/bindings/private/String.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_glean_GleanString_h +#define mozilla_glean_GleanString_h + +#include "nsIGleanMetrics.h" +#include "nsString.h" + +namespace mozilla { +namespace glean { + +namespace impl { +extern "C" { +void fog_string_set(uint32_t id, const nsACString& value); +uint32_t fog_string_test_has_value(uint32_t id, const char* storageName); +void fog_string_test_get_value(uint32_t id, const char* storageName, + nsACString& value); +} + +class StringMetric { + public: + constexpr explicit StringMetric(uint32_t id) : mId(id) {} + + /* + * Set to the specified value. + * + * Truncates the value if it is longer than 100 bytes and logs an error. + * See https://mozilla.github.io/glean/book/user/metrics/string.html#limits. + * + * @param value The string to set the metric to. + */ + void Set(const nsACString& value) const { fog_string_set(mId, value); } + + /** + * **Test-only API** + * + * Tests whether a value is stored for the metric. + * + * This function will attempt to await the last parent-process task (if any) + * writing to the the metric's storage engine before returning a value. + * This function will not wait for data from child processes. + * + * Parent process only. Panics in child processes. + * + * @param aStorageName the name of the ping to retrieve the metric for. + * @return true if metric value exists, otherwise false + */ + bool TestHasValue(const char* aStorageName) const { + return fog_string_test_has_value(mId, aStorageName) != 0; + } + + /** + * **Test-only API** + * + * Gets the currently stored value as a string. + * + * This function will attempt to await the last parent-process task (if any) + * writing to the the metric's storage engine before returning a value. + * This function will not wait for data from child processes. + * + * This doesn't clear the stored value. + * Parent process only. Panics in child processes. + * + * @return value of the stored metric. + */ + nsCString TestGetValue(const char* aStorageName) const { + nsCString ret; + fog_string_test_get_value(mId, aStorageName, ret); + return ret; + } + + private: + const uint32_t mId; +}; +} // namespace impl + +class GleanString final : public nsIGleanString { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIGLEANSTRING + + explicit GleanString(uint32_t id) : mString(id){}; + + private: + virtual ~GleanString() = default; + + const impl::StringMetric mString; +}; + +} // namespace glean +} // namespace mozilla + +#endif /* mozilla_glean_GleanString.h */ diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/util.py b/toolkit/components/glean/build_scripts/glean_parser_ext/util.py index c11885255281..42b29909de82 100644 --- a/toolkit/components/glean/build_scripts/glean_parser_ext/util.py +++ b/toolkit/components/glean/build_scripts/glean_parser_ext/util.py @@ -29,7 +29,7 @@ def generate_metric_ids(objs): return lambda metric: metric_id_mapping[(metric.category, metric.name)] -IMPLEMENTED_CPP_TYPES = ["counter", "timespan", "uuid"] +IMPLEMENTED_CPP_TYPES = ["counter", "string", "timespan", "uuid"] def is_implemented_metric_type(typ): diff --git a/toolkit/components/glean/gtest/TestFog.cpp b/toolkit/components/glean/gtest/TestFog.cpp index d4b58a71b18b..f27495809752 100644 --- a/toolkit/components/glean/gtest/TestFog.cpp +++ b/toolkit/components/glean/gtest/TestFog.cpp @@ -59,6 +59,18 @@ TEST(FOG, TestCppCounterWorks) ASSERT_EQ(42, mozilla::glean::test_only::bad_code.TestGetValue("test-ping")); } +TEST(FOG, TestCppStringWorks) +{ + auto kValue = "cheez!"_ns; + mozilla::glean::test_only::cheesy_string.Set(kValue); + + ASSERT_TRUE( + mozilla::glean::test_only::cheesy_string.TestHasValue("test-ping")); + ASSERT_STREQ( + kValue.get(), + mozilla::glean::test_only::cheesy_string.TestGetValue("test-ping").get()); +} + TEST(FOG, TestCppTimespanWorks) { mozilla::glean::test_only::can_we_time_it.Start(); diff --git a/toolkit/components/glean/moz.build b/toolkit/components/glean/moz.build index af8ce301a063..2d4986c0cc16 100644 --- a/toolkit/components/glean/moz.build +++ b/toolkit/components/glean/moz.build @@ -23,6 +23,7 @@ if CONFIG["MOZ_GLEAN"]: "bindings/Glean.h", "bindings/MetricTypes.h", "bindings/private/Counter.h", + "bindings/private/String.h", "bindings/private/Timespan.h", "bindings/private/Uuid.h", ] @@ -31,6 +32,7 @@ if CONFIG["MOZ_GLEAN"]: "bindings/Category.cpp", "bindings/Glean.cpp", "bindings/private/Counter.cpp", + "bindings/private/String.cpp", "bindings/private/Timespan.cpp", "bindings/private/Uuid.cpp", "ipc/FOGIPC.cpp", diff --git a/toolkit/components/glean/test_metrics.yaml b/toolkit/components/glean/test_metrics.yaml index 4dbff75c954e..119ec60f0472 100644 --- a/toolkit/components/glean/test_metrics.yaml +++ b/toolkit/components/glean/test_metrics.yaml @@ -47,6 +47,23 @@ test_only: send_in_pings: - test-ping + cheesy_string: + type: string + description: | + Only the cheesiest of strings. + This is a test-only metric. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1673662 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1673662#c1 + data_sensitivity: + - technical + notification_emails: + - glean-team@mozilla.com + expires: never + send_in_pings: + - test-ping + what_id_it: # Using a different metrics yaml style for fun. type: uuid description: | diff --git a/toolkit/components/glean/xpcom/nsIGleanMetrics.idl b/toolkit/components/glean/xpcom/nsIGleanMetrics.idl index 08c666d68524..2c1d1aa53e19 100644 --- a/toolkit/components/glean/xpcom/nsIGleanMetrics.idl +++ b/toolkit/components/glean/xpcom/nsIGleanMetrics.idl @@ -51,6 +51,52 @@ interface nsIGleanCounter : nsISupports long testGetValue(in ACString aStorageName); }; +[scriptable, uuid(d84a3555-46f1-48c1-9122-e8e88b069d2b)] +interface nsIGleanString : nsISupports +{ + /* + * Set to the specified value. + * + * @param value The string to set the metric to. + */ + [implicit_jscontext] + void set(in ACString value); + + /** + * **Test-only API** + * + * Tests whether a value is stored for the metric. + * + * This function will attempt to await the last parent-process task (if any) + * writing to the the metric's storage engine before returning a value. + * This function will not wait for data from child processes. + * + * Parent process only. Panics in child processes. + * + * @param aStorageName the name of the ping to retrieve the metric for. + * @return true if metric value exists, otherwise false + */ + [implicit_jscontext] + bool testHasValue(in ACString aStorageName); + + /** + * **Test-only API** + * + * Gets the currently stored value as a string. + * + * This function will attempt to await the last parent-process task (if any) + * writing to the the metric's storage engine before returning a value. + * This function will not wait for data from child processes. + * + * This doesn't clear the stored value. + * Parent process only. Panics in child processes. + * + * @return value of the stored metric. + */ + [implicit_jscontext] + ACString testGetValue(in ACString aStorageName); +}; + [scriptable, uuid(2586530c-030f-11eb-93cb-cbf30d25225a)] interface nsIGleanTimespan : nsISupports { diff --git a/toolkit/components/glean/xpcshell/test_Glean.js b/toolkit/components/glean/xpcshell/test_Glean.js index b33b3f6c2de2..bc5532ef08e6 100644 --- a/toolkit/components/glean/xpcshell/test_Glean.js +++ b/toolkit/components/glean/xpcshell/test_Glean.js @@ -32,6 +32,14 @@ add_task(function test_fog_counter_works() { Assert.equal(31, Glean.test_only.bad_code.testGetValue("test-ping")); }); +add_task(async function test_fog_string_works() { + const value = "a cheesy string!"; + Glean.test_only.cheesy_string.set(value); + + Assert.ok(Glean.test_only.cheesy_string.testHasValue("test-ping")); + Assert.equal(value, Glean.test_only.cheesy_string.testGetValue("test-ping")); +}); + add_task(async function test_fog_timespan_works() { // We start, briefly sleep and then stop. // That guarantees some time to measure.