js/public/UbiNodeCensus.h
author Jan de Mooij <jdemooij@mozilla.com>
Mon, 27 Sep 2021 11:15:49 +0000
changeset 593265 5144ad09cfb7011b98c5422b78b82936b1343541
parent 589112 fb7bc8bee56b869843e355626f32b14b131a9df6
permissions -rw-r--r--
Bug 1732281 part 2 - Transpile ValueToIteratorResult in Warp. r=iain Depends on D126514 Differential Revision: https://phabricator.services.mozilla.com/D126515

/* -*- 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 js_UbiNodeCensus_h
#define js_UbiNodeCensus_h

#include "mozilla/Attributes.h"

#include <algorithm>
#include <utility>

#include "js/UbiNode.h"
#include "js/UbiNodeBreadthFirst.h"

// A census is a ubi::Node traversal that assigns each node to one or more
// buckets, and returns a report with the size of each bucket.
//
// We summarize the results of a census with counts broken down according to
// criteria selected by the API consumer code that is requesting the census. For
// example, the following breakdown might give an interesting overview of the
// heap:
//
//   - all nodes
//     - objects
//       - objects with a specific [[Class]] *
//     - strings
//     - scripts
//     - DOM nodes
//       - nsINodes with a specific name (found in nsINode::NodeName()) *
//     - all other Node types
//       - nodes with a specific ubi::Node::typeName *
//
// Obviously, the parts of this tree marked with * represent many separate
// counts, depending on how many distinct [[Class]] values and ubi::Node type
// names we encounter.
//
// The supported types of breakdowns are documented in
// js/src/doc/Debugger/Debugger.Memory.md.
//
// When we parse the 'breakdown' argument to takeCensus, we build a tree of
// CountType nodes. For example, for the breakdown shown in the
// Debugger.Memory.prototype.takeCensus, documentation:
//
//    {
//      by: "coarseType",
//      objects: { by: "objectClass" },
//      other:    { by: "internalType" },
//      domNode: { by: "descriptiveType" }
//    }
//
// we would build the following tree of CountType subclasses:
//
//    ByCoarseType
//      objects: ByObjectClass
//        each class: SimpleCount
//      scripts: SimpleCount
//      strings: SimpleCount
//      other: ByUbinodeType
//        each type: SimpleCount
//      domNode: ByDomObjectClass
//        each type: SimpleCount
//
// The interior nodes are all breakdown types that categorize nodes according to
// one characteristic or another; and the leaf nodes are all SimpleType.
//
// Each CountType has its own concrete C++ type that holds the counts it
// produces. SimpleCount::Count just holds totals. ByObjectClass::Count has a
// hash table whose keys are object class names and whose values are counts of
// some other type (in the example above, SimpleCount).
//
// To keep actual count nodes small, they have no vtable. Instead, each count
// points to its CountType, which knows how to carry out all the operations we
// need on a Count. A CountType can produce new count nodes; process nodes as we
// visit them; build a JS object reporting the results; and destruct count
// nodes.

namespace JS {
namespace ubi {

struct Census;

class CountBase;

struct CountDeleter {
  JS_PUBLIC_API void operator()(CountBase*);
};

using CountBasePtr = js::UniquePtr<CountBase, CountDeleter>;

// Abstract base class for CountType nodes.
struct CountType {
  explicit CountType() = default;
  virtual ~CountType() = default;

  // Destruct a count tree node that this type instance constructed.
  virtual void destructCount(CountBase& count) = 0;

  // Return a fresh node for the count tree that categorizes nodes according
  // to this type. Return a nullptr on OOM.
  virtual CountBasePtr makeCount() = 0;

  // Trace |count| and all its children, for garbage collection.
  virtual void traceCount(CountBase& count, JSTracer* trc) = 0;

  // Implement the 'count' method for counts returned by this CountType
  // instance's 'newCount' method.
  [[nodiscard]] virtual bool count(CountBase& count,
                                   mozilla::MallocSizeOf mallocSizeOf,
                                   const Node& node) = 0;

  // Implement the 'report' method for counts returned by this CountType
  // instance's 'newCount' method.
  [[nodiscard]] virtual bool report(JSContext* cx, CountBase& count,
                                    MutableHandleValue report) = 0;
};

using CountTypePtr = js::UniquePtr<CountType>;

// An abstract base class for count tree nodes.
class CountBase {
  // In lieu of a vtable, each CountBase points to its type, which
  // carries not only the implementations of the CountBase methods, but also
  // additional parameters for the type's behavior, as specified in the
  // breakdown argument passed to takeCensus.
  CountType& type;

 protected:
  ~CountBase() = default;

 public:
  explicit CountBase(CountType& type)
      : type(type), total_(0), smallestNodeIdCounted_(SIZE_MAX) {}

  // Categorize and count |node| as appropriate for this count's type.
  [[nodiscard]] bool count(mozilla::MallocSizeOf mallocSizeOf,
                           const Node& node) {
    total_++;

    auto id = node.identifier();
    if (id < smallestNodeIdCounted_) {
      smallestNodeIdCounted_ = id;
    }

#ifdef DEBUG
    size_t oldTotal = total_;
#endif

    bool ret = type.count(*this, mallocSizeOf, node);

    MOZ_ASSERT(total_ == oldTotal,
               "CountType::count should not increment total_, CountBase::count "
               "handles that");

    return ret;
  }

  // Construct a JavaScript object reporting the counts recorded in this
  // count, and store it in |report|. Return true on success, or false on
  // failure.
  [[nodiscard]] bool report(JSContext* cx, MutableHandleValue report) {
    return type.report(cx, *this, report);
  }

  // Down-cast this CountBase to its true type, based on its 'type' member,
  // and run its destructor.
  void destruct() { return type.destructCount(*this); }

  // Trace this count for garbage collection.
  void trace(JSTracer* trc) { type.traceCount(*this, trc); }

  size_t total_;

  // The smallest JS::ubi::Node::identifier() passed to this instance's
  // count() method. This provides a stable way to sort sets.
  Node::Id smallestNodeIdCounted_;
};

using RootedCount = JS::Rooted<CountBasePtr>;

// Common data for a census traversal, shared across all CountType nodes.
struct Census {
  JSContext* const cx;
  // If the targetZones set is non-empty, then only consider nodes whose zone
  // is an element of the set. If the targetZones set is empty, then nodes in
  // all zones are considered.
  JS::ZoneSet targetZones;

  explicit Census(JSContext* cx) : cx(cx) {}
};

// A BreadthFirst handler type that conducts a census, using a CountBase to
// categorize and count each node.
class CensusHandler {
  Census& census;
  JS::Handle<CountBasePtr> rootCount;
  mozilla::MallocSizeOf mallocSizeOf;

 public:
  CensusHandler(Census& census, JS::Handle<CountBasePtr> rootCount,
                mozilla::MallocSizeOf mallocSizeOf)
      : census(census), rootCount(rootCount), mallocSizeOf(mallocSizeOf) {}

  [[nodiscard]] bool report(JSContext* cx, MutableHandleValue report) {
    return rootCount->report(cx, report);
  }

  // This class needs to retain no per-node data.
  class NodeData {};

  [[nodiscard]] JS_PUBLIC_API bool operator()(
      BreadthFirst<CensusHandler>& traversal, Node origin, const Edge& edge,
      NodeData* referentData, bool first);
};

using CensusTraversal = BreadthFirst<CensusHandler>;

// Examine the census options supplied by the API consumer, and (among other
// things) use that to build a CountType tree.
[[nodiscard]] JS_PUBLIC_API bool ParseCensusOptions(JSContext* cx,
                                                    Census& census,
                                                    HandleObject options,
                                                    CountTypePtr& outResult);

// Parse the breakdown language (as described in
// js/src/doc/Debugger/Debugger.Memory.md) into a CountTypePtr. A null pointer
// is returned on error and is reported to the cx.
JS_PUBLIC_API CountTypePtr ParseBreakdown(JSContext* cx,
                                          HandleValue breakdownValue);

}  // namespace ubi
}  // namespace JS

#endif  // js_UbiNodeCensus_h