Bug 1369955: Add perfstream module. Define perfstream::Measure and Group types, with StaticGroup helper. draft
authorJim Blandy <jimb@mozilla.com>
Fri, 06 Oct 2017 15:37:44 -0700
changeset 684579 d4580c831f968bcb7bce9a4f699f0e2ea92bb21d
parent 683863 d1e995c8640a191cd127e87273ec96cb2fabffa9
child 684580 23f51596ac386586bbe7505f82f671143edee43c
push id85652
push userbmo:jimb@mozilla.com
push dateMon, 23 Oct 2017 05:02:44 +0000
bugs1369955
milestone58.0a1
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
moz.build
perfstream/Measure.cpp
perfstream/Measure.h
perfstream/StaticGroup.h
perfstream/moz.build
--- 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',
+]