Bug 1675277 - Support FOG Labeled Metrics in C++ and JS r=janerik

Differential Revision: https://phabricator.services.mozilla.com/D103233
This commit is contained in:
Chris H-C 2021-02-02 16:46:40 +00:00
parent 7296c82cbf
commit 926bf6dca9
11 changed files with 340 additions and 9 deletions

View File

@ -1472,6 +1472,10 @@ DOMInterfaces = {
'nativeType': 'mozilla::glean::GleanPings',
'headerFile': 'mozilla/glean/bindings/GleanPings.h',
},
'GleanLabeled': {
'nativeType': 'mozilla::glean::GleanLabeled',
'headerFile': 'mozilla/glean/bindings/Labeled.h',
},
# WebRTC

View File

@ -26,3 +26,20 @@ interface GleanImpl {
*/
getter GleanCategory (DOMString identifier);
};
[ChromeOnly, Exposed=Window]
interface GleanLabeled {
/**
* Get a specific metric for a given label.
*
* If a set of acceptable labels were specified in the `metrics.yaml` file,
* and the given label is not in the set, it will be recorded under the
* special `OTHER_LABEL` label.
*
* If a set of acceptable labels was not specified in the `metrics.yaml` file,
* only the first 16 unique labels will be used.
* After that, any additional labels will be recorded under the special
* `OTHER_LABEL` label.
*/
getter nsISupports (DOMString identifier);
};

View File

@ -9,6 +9,7 @@
#include "mozilla/glean/bindings/Counter.h"
#include "mozilla/glean/bindings/Datetime.h"
#include "mozilla/glean/bindings/Event.h"
#include "mozilla/glean/bindings/Labeled.h"
#include "mozilla/glean/bindings/MemoryDistribution.h"
#include "mozilla/glean/bindings/String.h"
#include "mozilla/glean/bindings/StringList.h"

View File

@ -0,0 +1,64 @@
/* -*- 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/bindings/Labeled.h"
#include "mozilla/dom/GleanBinding.h"
#include "mozilla/glean/fog_ffi_generated.h"
#include "mozilla/glean/bindings/GleanJSMetricsLookup.h"
#include "mozilla/glean/bindings/MetricTypes.h"
#include "nsString.h"
namespace mozilla::glean {
namespace impl {
template <>
BooleanMetric Labeled<BooleanMetric>::Get(const nsACString& aLabel) const {
auto submetricId = fog_labeled_boolean_get(mId, &aLabel);
return BooleanMetric(submetricId);
}
template <>
CounterMetric Labeled<CounterMetric>::Get(const nsACString& aLabel) const {
auto submetricId = fog_labeled_counter_get(mId, &aLabel);
return CounterMetric(submetricId);
}
template <>
StringMetric Labeled<StringMetric>::Get(const nsACString& aLabel) const {
auto submetricId = fog_labeled_string_get(mId, &aLabel);
return StringMetric(submetricId);
}
} // namespace impl
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(GleanLabeled)
NS_IMPL_CYCLE_COLLECTING_ADDREF(GleanLabeled)
NS_IMPL_CYCLE_COLLECTING_RELEASE(GleanLabeled)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GleanLabeled)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
JSObject* GleanLabeled::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return dom::GleanLabeled_Binding::Wrap(aCx, this, aGivenProto);
}
already_AddRefed<nsISupports> GleanLabeled::NamedGetter(const nsAString& aName,
bool& aFound) {
auto label = NS_ConvertUTF16toUTF8(aName);
aFound = true;
return NewSubMetricFromIds(mTypeId, mId, label);
}
bool GleanLabeled::NameIsEnumerable(const nsAString& aName) { return false; }
void GleanLabeled::GetSupportedNames(nsTArray<nsString>& aNames) {
// We really don't know, so don't do anything.
}
} // namespace mozilla::glean

View File

@ -0,0 +1,75 @@
/* -*- 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_Labeled_h
#define mozilla_glean_Labeled_h
#include "nsIGleanMetrics.h"
#include "nsISupports.h"
#include "nsWrapperCache.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/glean/fog_ffi_generated.h"
namespace mozilla::glean {
namespace impl {
template <typename T>
class Labeled {
public:
constexpr explicit Labeled<T>(uint32_t id) : mId(id) {}
/**
* Gets a specific metric for a given label.
*
* If a set of acceptable labels were specified in the `metrics.yaml` file,
* and the given label is not in the set, it will be recorded under the
* special `OTHER_LABEL` label.
*
* If a set of acceptable labels was not specified in the `metrics.yaml` file,
* only the first 16 unique labels will be used.
* After that, any additional labels will be recorded under the special
* `OTHER_LABEL` label.
*
* @param aLabel - a snake_case string under 30 characters in length,
* otherwise the metric will be recorded under the special
* `OTHER_LABEL` label and an error will be recorded.
*/
T Get(const nsACString& aLabel) const;
private:
const uint32_t mId;
};
} // namespace impl
class GleanLabeled final : public nsISupports, public nsWrapperCache {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(GleanLabeled)
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
nsISupports* GetParentObject() { return nullptr; }
explicit GleanLabeled(uint32_t aId, uint32_t aTypeId)
: mId(aId), mTypeId(aTypeId){};
already_AddRefed<nsISupports> NamedGetter(const nsAString& aName,
bool& aFound);
bool NameIsEnumerable(const nsAString& aName);
void GetSupportedNames(nsTArray<nsString>& aNames);
private:
virtual ~GleanLabeled() = default;
const uint32_t mId;
const uint32_t mTypeId;
};
} // namespace mozilla::glean
#endif /* mozilla_glean_Labeled_h */

View File

@ -48,6 +48,9 @@ def type_name(obj):
Returns the C++ type to use for a given metric object.
"""
if getattr(obj, "labeled", False):
class_name = util.Camelize(obj.type[8:]) # strips "labeled_" off the front.
return "Labeled<impl::{}Metric>".format(class_name)
generate_enums = getattr(obj, "_generate_enums", []) # Extra Keys? Reasons?
if len(generate_enums):
for name, suffix in generate_enums:

View File

@ -71,12 +71,26 @@ def metric_identifier(category, metric_name):
return f"{category}.{util.camelize(metric_name)}"
def type_name(type):
def type_name(obj):
"""
Returns the C++ type to use for a given metric object.
"""
return "Glean" + util.Camelize(type)
if getattr(obj, "labeled", False):
return "GleanLabeled"
return "Glean" + util.Camelize(obj.type)
def subtype_name(obj):
"""
Returns the subtype name for labeled metrics.
(e.g. 'boolean' for 'labeled_boolean').
Returns "" for non-labeled metrics.
"""
if getattr(obj, "labeled", False):
type = obj.type[8:] # strips "labeled_" off the front
return "Glean" + util.Camelize(type)
return ""
def output_js(objs, output_fd, options={}):
@ -118,7 +132,6 @@ def write_metrics(objs, output_fd, template_filename):
template = util.get_jinja2_template(
template_filename,
filters=(("type_name", type_name),),
)
assert (
@ -142,11 +155,12 @@ def write_metrics(objs, output_fd, template_filename):
for metric in objs.values():
identifier = metric_identifier(category_name, metric.name)
if metric.type in metric_type_ids:
type_id = metric_type_ids[metric.type]
metric_type_tuple = (type_name(metric), subtype_name(metric))
if metric_type_tuple in metric_type_ids:
type_id, _ = metric_type_ids[metric_type_tuple]
else:
type_id = len(metric_type_ids) + 1
metric_type_ids[metric.type] = type_id
metric_type_ids[metric_type_tuple] = (type_id, metric.type)
idx = metric_string_table.stringIndex(identifier)
metric_id = get_metric_id(metric)

View File

@ -14,6 +14,7 @@ Jinja2 template is not. Please file bugs! #}
#include "mozilla/PerfectHash.h"
#include "mozilla/Maybe.h"
#include "mozilla/glean/bindings/MetricTypes.h"
#include "mozilla/glean/fog_ffi_generated.h"
#define GLEAN_INDEX_BITS ({{index_bits}})
#define GLEAN_ID_BITS ({{id_bits}})
@ -39,10 +40,10 @@ static already_AddRefed<nsISupports> NewMetricFromId(uint32_t id) {
uint32_t metricId = GLEAN_METRIC_ID(id);
switch (typeId) {
{% for type, type_id in metric_type_ids.items() %}
case {{ type_id }}: /* {{ type|Camelize }} */
{% for (type_name, subtype_name), (type_id, original_type) in metric_type_ids.items() %}
case {{ type_id }}: /* {{ original_type }} */
{
return MakeAndAddRef<{{type | type_name}}>(metricId);
return MakeAndAddRef<{{type_name}}>(metricId{% if subtype_name|length > 0 %}, {{ type_id }}{% endif %});
}
{% endfor %}
default:
@ -51,6 +52,22 @@ static already_AddRefed<nsISupports> NewMetricFromId(uint32_t id) {
}
}
static already_AddRefed<nsISupports> NewSubMetricFromIds(uint32_t aParentTypeId, uint32_t aParentMetricId, const nsACString& aLabel) {
switch (aParentTypeId) {
{% for (type_name, subtype_name), (type_id, original_type) in metric_type_ids.items() %}
{% if subtype_name|length > 0 %}
case {{ type_id }}: { /* {{ original_type }} */
return MakeAndAddRef<{{subtype_name}}>(impl::fog_{{original_type}}_get(aParentMetricId, &aLabel));
}
{% endif %}
{% endfor %}
default: {
MOZ_ASSERT_UNREACHABLE("Invalid type ID for submetric.");
return nullptr;
}
}
}
static Maybe<uint32_t> category_result_check(const nsACString& aKey, category_entry_t entry);
static Maybe<uint32_t> metric_result_check(const nsACString& aKey, metric_entry_t entry);

View File

@ -214,3 +214,50 @@ TEST(FOG, TestCppTimingDistWorks)
}
ASSERT_EQ(sampleCount, (uint64_t)2);
}
TEST(FOG, TestLabeledBooleanWorks)
{
ASSERT_EQ(mozilla::Nothing(),
test_only::mabels_like_balloons.Get("hot_air"_ns).TestGetValue());
test_only::mabels_like_balloons.Get("hot_air"_ns).Set(true);
test_only::mabels_like_balloons.Get("helium"_ns).Set(false);
ASSERT_EQ(
true,
test_only::mabels_like_balloons.Get("hot_air"_ns).TestGetValue().ref());
ASSERT_EQ(
false,
test_only::mabels_like_balloons.Get("helium"_ns).TestGetValue().ref());
}
TEST(FOG, TestLabeledCounterWorks)
{
ASSERT_EQ(mozilla::Nothing(),
test_only::mabels_kitchen_counters.Get("marble"_ns).TestGetValue());
test_only::mabels_kitchen_counters.Get("marble"_ns).Add(1);
test_only::mabels_kitchen_counters.Get("laminate"_ns).Add(2);
ASSERT_EQ(
1,
test_only::mabels_kitchen_counters.Get("marble"_ns).TestGetValue().ref());
ASSERT_EQ(2, test_only::mabels_kitchen_counters.Get("laminate"_ns)
.TestGetValue()
.ref());
}
TEST(FOG, TestLabeledStringWorks)
{
ASSERT_EQ(mozilla::Nothing(),
test_only::mabels_balloon_strings.Get("twine"_ns).TestGetValue());
test_only::mabels_balloon_strings.Get("twine"_ns).Set("seems acceptable"_ns);
test_only::mabels_balloon_strings.Get("parachute_cord"_ns)
.Set("preferred"_ns);
ASSERT_STREQ("seems acceptable",
test_only::mabels_balloon_strings.Get("twine"_ns)
.TestGetValue()
.ref()
.get());
ASSERT_STREQ("preferred",
test_only::mabels_balloon_strings.Get("parachute_cord"_ns)
.TestGetValue()
.ref()
.get());
}

View File

@ -39,6 +39,7 @@ if CONFIG["MOZ_GLEAN"]:
"bindings/private/Datetime.h",
"bindings/private/DistributionData.h",
"bindings/private/Event.h",
"bindings/private/Labeled.h",
"bindings/private/MemoryDistribution.h",
"bindings/private/Ping.h",
"bindings/private/String.h",
@ -64,6 +65,7 @@ if CONFIG["MOZ_GLEAN"]:
"bindings/private/Counter.cpp",
"bindings/private/Datetime.cpp",
"bindings/private/Event.cpp",
"bindings/private/Labeled.cpp",
"bindings/private/MemoryDistribution.cpp",
"bindings/private/Ping.cpp",
"bindings/private/String.cpp",

View File

@ -219,3 +219,90 @@ add_task(async function test_fog_timing_distribution_works() {
"Only two buckets with samples"
);
});
add_task(async function test_fog_labeled_boolean_works() {
Assert.equal(
undefined,
Glean.testOnly.mabelsLikeBalloons.at_parties.testGetValue(),
"New labels with no values should return undefined"
);
Glean.testOnly.mabelsLikeBalloons.at_parties.set(true);
Glean.testOnly.mabelsLikeBalloons.at_funerals.set(false);
Assert.equal(
true,
Glean.testOnly.mabelsLikeBalloons.at_parties.testGetValue()
);
Assert.equal(
false,
Glean.testOnly.mabelsLikeBalloons.at_funerals.testGetValue()
);
// What about invalid/__other__?
Assert.equal(
undefined,
Glean.testOnly.mabelsLikeBalloons.__other__.testGetValue()
);
Glean.testOnly.mabelsLikeBalloons.InvalidLabel.set(true);
Assert.equal(
true,
Glean.testOnly.mabelsLikeBalloons.__other__.testGetValue()
);
// TODO: Test that we have the right number and type of errors (bug 1683171)
});
add_task(async function test_fog_labeled_counter_works() {
Assert.equal(
undefined,
Glean.testOnly.mabelsKitchenCounters.near_the_sink.testGetValue(),
"New labels with no values should return undefined"
);
Glean.testOnly.mabelsKitchenCounters.near_the_sink.add(1);
Glean.testOnly.mabelsKitchenCounters.with_junk_on_them.add(2);
Assert.equal(
1,
Glean.testOnly.mabelsKitchenCounters.near_the_sink.testGetValue()
);
Assert.equal(
2,
Glean.testOnly.mabelsKitchenCounters.with_junk_on_them.testGetValue()
);
// What about invalid/__other__?
Assert.equal(
undefined,
Glean.testOnly.mabelsKitchenCounters.__other__.testGetValue()
);
Glean.testOnly.mabelsKitchenCounters.InvalidLabel.add(1);
Assert.equal(
1,
Glean.testOnly.mabelsKitchenCounters.__other__.testGetValue()
);
// TODO: Test that we have the right number and type of errors (bug 1683171)
});
add_task(async function test_fog_labeled_string_works() {
Assert.equal(
undefined,
Glean.testOnly.mabelsBalloonStrings.colour_of_99.testGetValue(),
"New labels with no values should return undefined"
);
Glean.testOnly.mabelsBalloonStrings.colour_of_99.set("crimson");
Glean.testOnly.mabelsBalloonStrings.string_lengths.set("various");
Assert.equal(
"crimson",
Glean.testOnly.mabelsBalloonStrings.colour_of_99.testGetValue()
);
Assert.equal(
"various",
Glean.testOnly.mabelsBalloonStrings.string_lengths.testGetValue()
);
// What about invalid/__other__?
Assert.equal(
undefined,
Glean.testOnly.mabelsBalloonStrings.__other__.testGetValue()
);
Glean.testOnly.mabelsBalloonStrings.InvalidLabel.set("valid");
Assert.equal(
"valid",
Glean.testOnly.mabelsBalloonStrings.__other__.testGetValue()
);
// TODO: Test that we have the right number and type of errors (bug 1683171)
});