Bug 1369955: Add perfstream module. Define perfstream::Measure and Group types, with StaticGroup helper.
See perfstream/Measure.h for documentation and rationale.
MozReview-Commit-ID: B5UWDty5xQt
--- a/moz.build
+++ b/moz.build
@@ -68,16 +68,17 @@ CONFIGURE_SUBST_FILES += [
'config/emptyvars.mk',
]
if CONFIG['ENABLE_CLANG_PLUGIN']:
DIRS += ['build/clang-plugin']
DIRS += [
'config',
+ 'perfstream',
'python',
'taskcluster',
'testing/mozbase',
'third_party/python',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] or not CONFIG['MOZ_BUILD_APP']:
# These python manifests are included here so they get picked up without an objdir
new file mode 100644
--- /dev/null
+++ b/perfstream/Measure.cpp
@@ -0,0 +1,67 @@
+/* -*- 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/perfstream/Measure.h"
+
+#include <string.h>
+
+#include "mozilla/Move.h"
+#include "mozilla/UniquePtr.h"
+
+using mozilla::Maybe;
+using mozilla::Move;
+using mozilla::perfstream::Description;
+using mozilla::perfstream::Measure;
+using mozilla::perfstream::MeasurePtr;
+using mozilla::perfstream::Group;
+using mozilla::perfstream::GroupPtr;
+
+Maybe<MeasurePtr>
+Group::GetMeasureByPath(const char** aPath, size_t aPathLength) const
+{
+ /* Getting from this Group to a Measure must require at least one step. */
+ MOZ_ASSERT(aPathLength > 0);
+
+ /*
+ * On the first iteration, we don't have an owned pointer, so use a hybrid
+ * approach: either here == this and !ownedHere, or here == ownedHere.get().
+ */
+ const Group* here = this;
+ GroupPtr ownedHere;
+
+ /* Walk down through all the parent groups. */
+ for (size_t i; i < aPathLength - 1; i++) {
+ auto next = here->GetSubgroup(aPath[i]);
+ if (!next)
+ return Nothing();
+ ownedHere = Move(*next);
+ here = ownedHere.get();
+ }
+
+ /* Look up the Measure under the final path component. */
+ return here->GetMeasure(aPath[aPathLength - 1]);
+}
+
+Maybe<GroupPtr>
+Group::GetSubgroupByPath(const char** aPath, size_t aPathLength) const
+{
+ /*
+ * On the first iteration, we don't have an owned pointer, so use a hybrid
+ * approach: either here == this and !ownedHere, or here == ownedHere.get().
+ */
+ const Group* here = this;
+ GroupPtr ownedHere;
+
+ for (size_t i; i < aPathLength; i++) {
+ auto next = here->GetSubgroup(aPath[i]);
+ if (!next)
+ return Nothing();
+ ownedHere = Move(*next);
+ here = ownedHere.get();
+ }
+
+ return Some(Move(ownedHere));
+}
new file mode 100755
--- /dev/null
+++ b/perfstream/Measure.h
@@ -0,0 +1,312 @@
+/* -*- 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/. */
+
+/**
+ * A common registry tree of performance data sources. Sources can be
+ * enumerated, enabled, and disabled.
+ *
+ * - If you are implementing some sort of instrumentation, including your
+ * measurements in this registry makes your instrumentation immediately
+ * controllable via environment variables, GUIs for interactive users, chrome
+ * JavaScript APIs for addons, and so on. With help messages and pop-up text.
+ * Silver-platter service with minimal effort.
+ *
+ * - If you would like to control the collection of performance data, this tree
+ * aspires to be the central clearinghouse for enumerating and controlling data
+ * sources.
+ *
+ * This header declares two main types: Measure and Group.
+ *
+ * - A Measure object refers to some performance data source that can be enabled
+ * and disabled. It has an identifier (any null-terminated UTF8 string) and
+ * open-ended metadata (a map from strings to strings), for holding tooltips,
+ * summaries, localization item names, and URLs for more detailed help.
+ *
+ * - A Group object has Measures and sub-Groups as its children. It also has an
+ * identifier and metadata. You can enumerate a Group's children, or look them
+ * up one at a time by their identifiers.
+ *
+ * For example, the 'SpiderMonkey' Group has a 'TraceLogger' subgroup, which
+ * contains a 'BaselineCompiler' Measure. To indicate that Measure, use the
+ * identifier path ['SpiderMonkey', 'TraceLogger', 'BaselineCompiler'].
+ *
+ * Measure and Group objects can live as long as their owner requires them to.
+ * If the underlying data source goes away (the JavaScript runtime gets freed,
+ * for example), Measures or Groups referring to it are still safe to use;
+ * operations fail in a reported, recoverable way.
+ *
+ * The tree can change as the program runs. For example, there might be a Group
+ * named 'Worker Threads' with a subgroup for each live worker thread.
+ *
+ * How one actually obtains the root Group depends on the application.
+ *
+ * Goals:
+ *
+ * - Encourage developers to add performance data sources by providing an
+ * off-the-shelf controller. Simply adding your data source to this tree makes
+ * it immediately visible to anyone using the interfaces that consult it.
+ *
+ * - Encourage the creation of special-purpose sources valuable to developers of
+ * some specific module, even if they are mysterious to anyone else. The tree
+ * structure keeps such measurements out of the way of people who don't need
+ * them, and the sources can be disabled by default, so they don't impose
+ * overhead unless they're needed.
+ *
+ * - Encourable the creation of generic controllers (like an environment
+ * variable checked at startup, or the Gecko Profiler addon, say) that have no
+ * understanding of the fiddly details of each source: when it's safe to turn
+ * on and off, when it gets deallocated, and so on. The interface is
+ * simplified and safe.
+ */
+
+#ifndef mozilla_perfstream_Measure_h
+#define mozilla_perfstream_Measure_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Move.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+
+namespace mozilla {
+namespace perfstream {
+
+class Measure;
+using MeasurePtr = UniquePtr<Measure>;
+using MeasureVector = Vector<MeasurePtr, 0, MallocAllocPolicy>;
+
+class Group;
+using GroupPtr = UniquePtr<Group>;
+using GroupVector = Vector<GroupPtr, 0, MallocAllocPolicy>;
+
+/**
+ * Identifier, description, etc. for a Measure or Group.
+ *
+ * All strings are null-terminated UTF-8. They are borrowed from the Measure or
+ * Group. they must live at least as long as it does, but not longer.
+ */
+struct Description {
+ /**
+ * The identifier for this measure/group, used in programmatic APIs like
+ * environment variables or WebIDL interfaces. This is used as a component in
+ * paths, like ["JavaScript", "IonMonkey"].
+ *
+ * This is never nullptr.
+ */
+ const char* mIdentifier;
+
+ /**
+ * If not nullptr, a null-terminated array of strings providing further
+ * details about this measure/group, like a textual description for tooltips,
+ * a URL for help, and so on. Each string starts with an identifier for the
+ * metadata, like "tooltip.en-us", followed by a colon and a space, followed by
+ * free-form text.
+ *
+ * For example:
+ * ["tooltip.en-us: Time spent in the IonMonkey JavaScript compiler.",
+ * "tooltip-fluent: measure.spidermonkey.ionmonkey", // l20n identifier
+ * "help-url: https://wiki.mozilla.org/IonMonkey",
+ * nullptr]
+ */
+ const char* const* mMetadata;
+};
+
+/**
+ * A Measure is a handle on some source of performance data.
+ *
+ * The Measure abstract base class is a simple, safe interface designed to
+ * support both static, compiled-in measures as well as measures that are
+ * dynamically created and destroyed. A Measure provides methods to enable and
+ * disable data collection, find the current collection state, and get metadata
+ * about the measure (for menu entries, tooltips, and so on).
+ *
+ * To make Measures easier to work with, this API lets its users own the
+ * Measures it returns: the caller gets a UniquePtr<Measure> that is safe to use
+ * for as long as they want to hold on to it, and which they are free to
+ * destruct at any time. If a Measure outlives the underlying component that it
+ * observes, it should simply report itself as "disabled", and attempts to
+ * enable it should have no effect.
+ *
+ * This means that the API must hand out a fresh Measure object for each
+ * request, and in general there may be many Measure objects alive at a time
+ * that all refer to the same underlying component.
+ *
+ * The underlying component to which a Measure refers is called an "inner
+ * measure".
+ *
+ * Consistent with its role as a lightweight reference, dropping a Measure
+ * object should have no effect on the inner measure; in particular, it
+ * shouldn't stop data collection. It should be safe for users of this API to
+ * create a Measure, enable data collection, drop the Measure, let data
+ * accumulate, and then look up the Measure afresh to disable data collection.
+ *
+ * Measures may be not be passed between threads. However, this does not
+ * preclude Measure implementations that are safe to use on one thread and which
+ * control an inner measure on another. Such an implementation would be
+ * responsible for all necessary synchronization, event queue interactions, and
+ * so on. For example, the Gecko main thread could have Measures that use the
+ * APIs for communicating with web workers to query and control the worker's
+ * measures.
+ *
+ * Some advice for writing concrete subclasses of Measure:
+ *
+ * - Using UniquePtr to manage Measure lifetimes makes the interface simpler to
+ * use because it protects clients from the details of the inner measure's
+ * lifetimes. This is an important quality in an interface designed to let
+ * naive code control a variety of performance measurement mechanisms.
+ *
+ * But a consequence of this is that the interface creates and destroys
+ * Measures liberally, so it is important for concrete subclasses of Measure
+ * to be lightweight. Typically, a Measure should be no more than a vtable and
+ * a pointer (or some other sort of reference) to the inner measure. The
+ * concrete subclass for a TraceLogger item is only three words long: a
+ * vtable, a JSContext pointer, and a TraceLoggerTextId enum value.
+ *
+ * - The interface defined here doesn't say what type inner measures must be.
+ * Ideally, they would be structures already needed to implement for the
+ * underlying data collection process, as opposed to types introduced solely
+ * to support perfstream. In that case, the Measure concrete subclass can
+ * simply point to that existing data structure.
+ *
+ * - As mentioned, a live Measure must always be safe to use. This means that if
+ * it points to an inner measure that may disappear as the program runs
+ * (perhaps it was created by content, and that page gets closed), the Measure
+ * must become "neutralized" in some fashion when this occurs. Think of weak
+ * pointers getting nulled out when their referent is dropped. This is needed
+ * for operations on the orphaned Measure to return benign error codes, rather
+ * than trying to use a dangling pointer or the equivalent.
+ *
+ * - Querying a Measure's state should be cheap.
+ */
+class Measure {
+public:
+ /**
+ * Return true if this Measure is currently enabled.
+ *
+ * If the inner measure no longer exists, this returns false.
+ */
+ virtual bool IsEnabled() const = 0;
+
+ /**
+ * Enable this Measure. Return the resulting this.IsEnabled() state.
+ *
+ * Enabling a Measure may have no effect. For example, the inner measure may
+ * no longer exist, or may not be in a condition that is safe to change.
+ */
+ virtual bool Enable() = 0;
+
+ /**
+ * Disable this Measure. Return the resulting this.IsEnabled() state
+ *
+ * Disabling a Measure may have no effect. For example, the inner measure may
+ * not be in a condition that is safe to change.
+ */
+ virtual bool Disable() = 0;
+
+ /**
+ * Return a Description for this measure: its identifier and metadata.
+ *
+ * The strings returned are borrowed from this Measure; they last at least as
+ * long as it does, but not necessarily any longer. If you need to hold on to
+ * them, you should make copies.
+ */
+ virtual Description GetDescription() const = 0;
+
+ Measure() = default;
+ virtual ~Measure() { }
+
+private:
+ /**
+ * Measures do not support copy construction or copy assignment.
+ */
+ Measure(const Measure&) = delete;
+ void operator=(const Measure&) = delete;
+};
+
+/**
+ * A group of Measures and sub-groups.
+ *
+ * A Group represents an interior node in the tree, with Measures and other
+ * Groups as its children.
+ *
+ * The names of all a Group's children, measures and subgroups, must be
+ * distinct.
+ */
+struct Group {
+ /**
+ * Return all this group's child Measures, or Nothing if we run out of memory.
+ */
+ virtual Maybe<MeasureVector> GetMeasures() const {
+ return Some(MeasureVector());
+ }
+
+ /**
+ * Return the child Measure of this Group whose identifier is aIdentifier.
+ * Return Nothing if there is no such child measure, or if we run out of
+ * memory.
+ */
+ virtual Maybe<MeasurePtr> GetMeasure(const char* aIdentifier) const {
+ return Nothing();
+ }
+
+ /**
+ * Return the measure referred to by the array of aPathLength identifiers at
+ * aPath. Return Nothing if there is no such Measure, or if we run out of
+ * memory.
+ */
+ virtual Maybe<MeasurePtr> GetMeasureByPath(const char** aPath, size_t aPathLength) const;
+
+ /**
+ * Return all this group's subgroups. If we run out of memory, return Nothing.
+ */
+ virtual Maybe<GroupVector> GetSubgroups() const {
+ return Some(GroupVector());
+ }
+
+ /**
+ * Return the subgroup of this Group whose identifier is aIdentifier. Return
+ * Nothing if there is no such subgroup, or we ran out of memory.
+ */
+ virtual Maybe<GroupPtr> GetSubgroup(const char* aIdentifier) const {
+ return Nothing();
+ }
+
+ /**
+ * Return the subgroup referred to by the array of aPathLength identifiers at
+ * aPath. Return Nothing if there is no such subgroup, or if we run out of
+ * memory.
+ */
+ virtual Maybe<GroupPtr> GetSubgroupByPath(const char** aPath, size_t aPathLength) const;
+
+ /**
+ * Return a Description for this group: its identifier and metadata.
+ *
+ * The strings returned are borrowed from this Group; they last at least as
+ * long as it does, but not necessarily any longer. If you need to hold on to
+ * them, you should make copies.
+ */
+ virtual Description GetDescription() const = 0;
+
+ /**
+ * Return a copy of this Group, or nullptr if we ran out of memory.
+ */
+ virtual GroupPtr Clone() const = 0;
+
+ Group() = default;
+ virtual ~Group() { }
+
+private:
+ /**
+ * Groups do not support copy construction or copy assignment.
+ */
+ Group(const Group&) = delete;
+ void operator=(const Group&) = delete;
+};
+
+} // namespace perfstream
+} // namespace mozilla
+
+#endif /* mozilla_perfstream_Measure_h */
new file mode 100644
--- /dev/null
+++ b/perfstream/StaticGroup.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* A template for simple Groups derived from a single enumeration method. */
+
+#ifndef mozilla_perfstream_StaticGroup_h
+#define mozilla_perfstream_StaticGroup_h
+
+#include "mozilla/perfstream/Measure.h"
+#include "mozilla/UniquePtr.h"
+
+#include <functional>
+#include <string.h>
+
+namespace mozilla {
+namespace perfstream {
+
+/**
+ * A Group derived from a simple implementation class.
+ *
+ * The Impl template parameter should be a struct including the following
+ * members:
+ *
+ * - Copy and move constructors.
+ *
+ * - template<typename F> bool EnumerateMeasures(F) const: An internal iterator
+ * over the child Measures of this Group, applying the functor F to each
+ * child. It may assume F is a function of type:
+ *
+ * bool F(const char* identifier, std::function<MeasurePtr()> constructor);
+ *
+ * EnumerateMeasures should pass F the child's identifier and a callable that
+ * it can use to actually construct that child. If F returns false,
+ * enumeration should stop.
+ *
+ * EnumerateMeasures should return true, unless some call to F returned false.
+ * If the Group has no measure children, EnumerateMeasures can simply return
+ * true immediately.
+ *
+ * - template<typename F> bool EnumerateSubgroups(F) const: As above, but
+ * enumerating groups instead of measures. The constructor should return a
+ * GroupPtr.
+ *
+ * - static const Description sDescription: A Description value for the group.
+ * All strings here must be statically allocated.
+ *
+ * For the moment, StaticGroup doesn't support subgroups.
+ */
+template<typename Impl>
+class StaticGroup: public mozilla::perfstream::Group {
+ Impl mImpl;
+
+ typedef mozilla::perfstream::MeasurePtr MeasurePtr;
+ typedef mozilla::perfstream::MeasureVector MeasureVector;
+
+public:
+ explicit StaticGroup(const Impl& aImpl) : mImpl(aImpl) { }
+ explicit StaticGroup(Impl&& aImpl) : mImpl(Move(aImpl)) { }
+ StaticGroup(const StaticGroup& rhs) : mImpl(rhs.aImpl) { }
+ StaticGroup(StaticGroup&& rhs) : mImpl(Move(rhs.aImpl)) { }
+
+ Maybe<MeasureVector> GetMeasures() const override {
+ auto children = MeasureVector();
+
+ bool ok = mImpl.EnumerateMeasures([&](const char* id,
+ std::function<MeasurePtr()> constructor) {
+ auto child = constructor();
+ return child && children.append(Move(child));
+ });
+ if (!ok)
+ return mozilla::Nothing();
+
+ return mozilla::Some(Move(children));
+ }
+
+ Maybe<MeasurePtr> GetMeasure(const char* aIdentifier) const override {
+ MeasurePtr child = nullptr;
+ mImpl.EnumerateMeasures([&](const char* id,
+ std::function<MeasurePtr()> constructor) {
+ if (strcmp(id, aIdentifier) == 0) {
+ child = constructor();
+ return false;
+ }
+ return true;
+ });
+ if (!child)
+ return Nothing();
+ return Some(Move(child));
+ }
+
+ Maybe<GroupVector> GetSubgroups() const override {
+ auto subgroups = GroupVector();
+
+ bool ok = mImpl.EnumerateSubgroups([&](const char* id,
+ std::function<GroupPtr()> constructor) {
+ auto subgroup = constructor();
+ return subgroup && subgroups.append(Move(subgroup));
+ });
+ if (!ok)
+ return mozilla::Nothing();
+
+ return mozilla::Some(Move(subgroups));
+ }
+
+ Maybe<GroupPtr> GetSubgroup(const char* aIdentifier) const override {
+ GroupPtr child = nullptr;
+ mImpl.EnumerateSubgroups([&](const char* id,
+ std::function<GroupPtr()> constructor) {
+ if (strcmp(id, aIdentifier) == 0) {
+ child = constructor();
+ return false;
+ }
+ return true;
+ });
+ if (!child)
+ return Nothing();
+ return Some(Move(child));
+ }
+
+ Description GetDescription() const override {
+ return Impl::sDescription;
+ }
+
+ GroupPtr Clone() const override {
+ return mozilla::MakeUnique<StaticGroup>(mImpl);
+ }
+};
+
+} // namespace perfstream
+} // namespace mozilla
+
+#endif /* mozilla_perfstream_StaticGroup_h */
new file mode 100644
--- /dev/null
+++ b/perfstream/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Gecko Profiler")
+
+Library('perfstream')
+
+SOURCES += [
+ 'Measure.cpp',
+]
+
+EXPORTS.mozilla.perfstream += [
+ 'Measure.h',
+ 'StaticGroup.h',
+]