Commit 196c3f26 authored by Joe Mason's avatar Joe Mason Committed by Chromium LUCI CQ

[PM] Add WebMemoryAggregator class and tests

A follow-up will use this to fill in results for WebMeasureMemory.

Bug: 1085129
Change-Id: If3c4ff777b4e4ae80f7a695c42398ea58f7ef2cc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2557730
Commit-Queue: Joe Mason <joenotcharles@chromium.org>
Reviewed-by: default avatarNicolás Peña Moreno <npm@chromium.org>
Reviewed-by: default avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835232}
parent bfb5e2c0
...@@ -28,18 +28,23 @@ module performance_manager.mojom; ...@@ -28,18 +28,23 @@ module performance_manager.mojom;
// Information about ExecutionContext reported in the memory usage breakdown. // Information about ExecutionContext reported in the memory usage breakdown.
struct WebMemoryAttribution { struct WebMemoryAttribution {
// Specifies the scope (or type) of the context. // Specifies the scope (or type) of the context.
// The invariant mentioned above ensures that cross-origin contexts have
// type kWindow corresponding to iframes and that information is already
// known to the calling origin because it created the iframe.
enum Scope { enum Scope {
kCrossOriginAggregated, // Dummy scope for cross-origin iframes.
kWindow, kWindow,
// TODO(1085129): Add worker scopes once they are implemented. // TODO(1085129): Add worker scopes once they are implemented.
}; };
Scope scope; Scope scope;
// The current URL of the context. It is null for cross-origin contexts. // The current URL of the context. It is null for cross-origin contexts.
// This is a string instead of url.mojom.Url because it is only used for
// reporting so there's no need to serialize to GURL, which has a lot of
// overhead.
string? url; string? url;
// The src attribute of the container. Can be null if the container has no
// TODO(1085129): Add src and id fields once they are implemented. // such attribute.
string? src;
// The id attribute of the container. Can be null if the container has no
// such attribute.
string? id;
}; };
// Describes a memory region and attributes it to a set of contexts. // Describes a memory region and attributes it to a set of contexts.
......
...@@ -11,9 +11,11 @@ ...@@ -11,9 +11,11 @@
#include "components/performance_manager/public/mojom/web_memory.mojom.h" #include "components/performance_manager/public/mojom/web_memory.mojom.h"
#include "components/performance_manager/public/v8_memory/v8_detailed_memory.h" #include "components/performance_manager/public/v8_memory/v8_detailed_memory.h"
#include "third_party/blink/public/common/tokens/tokens.h" #include "third_party/blink/public/common/tokens/tokens.h"
#include "url/origin.h"
namespace performance_manager { namespace performance_manager {
class FrameNode;
class ProcessNode; class ProcessNode;
namespace v8_memory { namespace v8_memory {
...@@ -21,6 +23,8 @@ namespace v8_memory { ...@@ -21,6 +23,8 @@ namespace v8_memory {
// A helper class for implementing WebMeasureMemory(). This manages a request // A helper class for implementing WebMeasureMemory(). This manages a request
// object that sends a V8 detailed memory request to the renderer, and formats // object that sends a V8 detailed memory request to the renderer, and formats
// the result into a mojom::WebMemoryMeasurement. // the result into a mojom::WebMemoryMeasurement.
// TODO(crbug.com/1085129): Extend this to measure all renderers that are
// reachable from the requesting node.
class WebMemoryMeasurer { class WebMemoryMeasurer {
public: public:
using MeasurementCallback = using MeasurementCallback =
...@@ -29,9 +33,11 @@ class WebMemoryMeasurer { ...@@ -29,9 +33,11 @@ class WebMemoryMeasurer {
WebMemoryMeasurer(const blink::LocalFrameToken&, WebMemoryMeasurer(const blink::LocalFrameToken&,
V8DetailedMemoryRequest::MeasurementMode, V8DetailedMemoryRequest::MeasurementMode,
MeasurementCallback); MeasurementCallback);
~WebMemoryMeasurer(); ~WebMemoryMeasurer();
WebMemoryMeasurer(const WebMemoryMeasurer& other) = delete;
WebMemoryMeasurer& operator=(const WebMemoryMeasurer& other) = delete;
V8DetailedMemoryRequestOneShot* request() const { return request_.get(); } V8DetailedMemoryRequestOneShot* request() const { return request_.get(); }
// A callback for V8DetailedMemoryRequestOneShot. // A callback for V8DetailedMemoryRequestOneShot.
...@@ -44,6 +50,119 @@ class WebMemoryMeasurer { ...@@ -44,6 +50,119 @@ class WebMemoryMeasurer {
std::unique_ptr<V8DetailedMemoryRequestOneShot> request_; std::unique_ptr<V8DetailedMemoryRequestOneShot> request_;
}; };
// Traverses the graph of execution contexts to find the results of the last
// memory measurement and aggregates them according to the rules defined in
// https://wicg.github.io/performance-measure-memory. (This implements the
// draft of 20 October 2020.)
class WebMemoryAggregator {
public:
// Constructs an aggregator for the results of a memory request from
// |requesting_node|. This expects the caller to check if |requesting_node|
// is allowed to measure memory according to the spec.
//
// The aggregation is performed by calling AggregateMemoryResult. The graph
// traversal will not start directly from |requesting_node|, but from the
// highest node in the frame tree that is visible to it as found by
// FindAggregationStartNode. (This allows a same-origin subframe to request
// memory for the whole page it's embedded in.)
explicit WebMemoryAggregator(const FrameNode* requesting_node);
~WebMemoryAggregator();
WebMemoryAggregator(const WebMemoryAggregator& other) = delete;
WebMemoryAggregator& operator=(const WebMemoryAggregator& other) = delete;
// The various ways a node can be treated during the aggregation.
enum class NodeAggregationType {
// Node is same-origin to |requesting_node|; will be a new aggregation
// point with scope "Window".
kSameOriginAggregationPoint,
// Node is cross-origin with |requesting_node| but its parent is not; will
// be a new aggregation point with scope
// "cross-origin-aggregated".
kCrossOriginAggregationPoint,
// Node is cross-origin with |requesting_node| and so is its parent; will
// be aggregated into its parent's aggregation point.
kCrossOriginAggregated,
// Node is in a different browsing context group; will not be added to the
// aggregation.
kInvisible,
};
// Returns the origin of |requesting_node|.
const url::Origin& requesting_origin() const { return requesting_origin_; }
// Returns the way that |frame_node| should be treated during the
// aggregation. |aggregation_start_node_| must be reachable from
// |frame_node| by following parent/child or opener links. This will always
// be true if |frame_node| comes from a call to VisitFrame.
NodeAggregationType FindNodeAggregationType(const FrameNode* frame_node);
// Performs the aggregation.
mojom::WebMemoryMeasurementPtr AggregateMeasureMemoryResult();
private:
// FrameNodeVisitor that recursively adds |frame_node| and its children to
// the aggregation. |enclosing_aggregation_point| is the aggregation point
// that |frame_node|'s parent or opener is in. Always returns true to
// continue traversal.
bool VisitFrame(mojom::WebMemoryBreakdownEntry* enclosing_aggregation_point,
const FrameNode* frame_node);
// PageNodeVisitor that recursively adds |page_node|'s main frames and their
// children to the aggregation. |enclosing_aggregation_point| is the
// aggregation point that |page_node|'s opener is in. Always returns true to
// continue traversal.
bool VisitOpenedPage(
mojom::WebMemoryBreakdownEntry* enclosing_aggregation_point,
const PageNode* page_node);
// The origin of |requesting_node|. Cached so it doesn't have to be
// recalculated in each call to VisitFrame.
const url::Origin requesting_origin_;
// The node that the graph traversal should start from, found from
// |requesting_node| using FindAggregationStartNode.
const FrameNode* aggregation_start_node_;
// Stores the result of the aggregation. This is populated by
// AggregateMeasureMemoryResult.
mojom::WebMemoryMeasurementPtr aggregation_result_;
};
namespace internal {
// These functions are used in the implementation and exposed in the header for
// testing.
// Returns |frame_node|'s parent or opener if the parent or opener is
// same-origin with |origin|, nullptr otherwise.
const FrameNode* GetSameOriginParentOrOpener(const FrameNode* frame_node,
const url::Origin& origin);
// Walks back the chain of parents and openers from |requesting_node| to find
// the farthest ancestor that should be visible to it (all intermediate nodes
// in the chain are same-origin).
const FrameNode* FindAggregationStartNode(const FrameNode* requesting_node);
// Creates a new breakdown entry with the given |scope| and |url|, and adds it
// to the list in |measurement|. Returns a pointer to the newly created entry.
mojom::WebMemoryBreakdownEntry* CreateBreakdownEntry(
mojom::WebMemoryAttribution::Scope scope,
base::Optional<std::string> url,
mojom::WebMemoryMeasurement* measurement);
// Sets the id and src attributes of |breakdown| using those stored in the
// V8ContextTracker for the given |frame_node|.
void SetBreakdownAttributionFromFrame(
const FrameNode* frame_node,
mojom::WebMemoryBreakdownEntry* breakdown);
// Copies the id and src attributes from |from| to |to|.
void CopyBreakdownAttribution(const mojom::WebMemoryBreakdownEntry* from,
mojom::WebMemoryBreakdownEntry* to);
} // namespace internal
} // namespace v8_memory } // namespace v8_memory
} // namespace performance_manager } // namespace performance_manager
......
...@@ -114,9 +114,12 @@ namespace { ...@@ -114,9 +114,12 @@ namespace {
// These functions convert WebMemory* mojo structs to IDL and JS values. // These functions convert WebMemory* mojo structs to IDL and JS values.
WTF::String ConvertScope(WebMemoryAttribution::Scope scope) { WTF::String ConvertScope(WebMemoryAttribution::Scope scope) {
using Scope = WebMemoryAttribution::Scope;
switch (scope) { switch (scope) {
case WebMemoryAttribution::Scope::kWindow: case Scope::kWindow:
return "Window"; return "Window";
case Scope::kCrossOriginAggregated:
return "cross-origin-aggregated";
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment