File: page_load_metrics_memory_tracker.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (185 lines) | stat: -rw-r--r-- 6,607 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/page_load_metrics/browser/page_load_metrics_memory_tracker.h"

#include <map>

#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "components/page_load_metrics/browser/features.h"
#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/graph/process_node.h"
#include "components/performance_manager/public/render_frame_host_proxy.h"

namespace page_load_metrics {

namespace {

using performance_manager::v8_memory::V8DetailedMemoryExecutionContextData;
using performance_manager::v8_memory::V8DetailedMemoryProcessData;
using performance_manager::v8_memory::V8DetailedMemoryRequest;

// WeakPtrs cannot be used as the key to a map without a custom comparator, so
// we use a raw pointer for the map key and bundle the WeakPtr in a struct
// to be used as the value.
struct ObserverWeakPtrAndMemoryUpdates {
  base::WeakPtr<MetricsWebContentsObserver> weak_ptr;
  std::vector<MemoryUpdate> updates;
  ObserverWeakPtrAndMemoryUpdates(
      base::WeakPtr<MetricsWebContentsObserver> wk_ptr,
      MemoryUpdate update)
      : weak_ptr(wk_ptr) {
    updates.emplace_back(update);
  }
};

}  // namespace

// Results of the V8PerAdFrameMemoryPollParamsStudy indicated that at the
// ~99.8th percentile, collecting at 10-second or 60-second intervals
// yields nearly equivalent results, as does using kBounded or kLazy mode.
// As there is about 10% to 20% overhead total GC time, we chose the less
// aggressive kLazy mode with a 60-second polling interval.
// For further results please see crbug.com/1116087.
PageLoadMetricsMemoryTracker::PageLoadMetricsMemoryTracker() {
  if (base::FeatureList::IsEnabled(
          page_load_metrics::features::kV8PerFrameMemoryMonitoring)) {
    memory_request_ = std::make_unique<V8DetailedMemoryRequest>(
        base::Seconds(60), V8DetailedMemoryRequest::MeasurementMode::kLazy);
    memory_request_->AddObserver(this);
    memory_request_->StartMeasurement();
  }
}

PageLoadMetricsMemoryTracker::~PageLoadMetricsMemoryTracker() = default;

void PageLoadMetricsMemoryTracker::Shutdown() {
  if (memory_request_) {
    memory_request_->RemoveObserver(this);
    memory_request_.reset();
  }
}

void PageLoadMetricsMemoryTracker::OnV8MemoryMeasurementAvailable(
    const performance_manager::ProcessNode* process_node,
    const V8DetailedMemoryProcessData* process_data) {
  std::map<MetricsWebContentsObserver*, ObserverWeakPtrAndMemoryUpdates>
      memory_update_map;

  // Iterate through frames with available measurements.
  for (const performance_manager::FrameNode* frame_node :
       process_node->GetFrameNodes()) {
    const auto* frame_data =
        V8DetailedMemoryExecutionContextData::ForFrameNode(frame_node);
    if (!frame_data) {
      continue;
    }

    content::RenderFrameHost* rfh = frame_node->GetRenderFrameHostProxy().Get();

    // We lose a small amount of data due to a RenderFrameHost
    // sometimes no longer being alive by the time that a report is received.
    // UMA suggests we miss about 0.078% of updates on desktop and about 0.11%
    // on mobile (as measured 10/30/2020).
    // See crbug.com/1116087.
    if (!rfh) {
      continue;
    }

    int64_t delta_bytes =
        UpdateMemoryUsageAndGetDelta(rfh, frame_data->v8_bytes_used());

    // Only send updates that are nontrivial.
    if (delta_bytes == 0) {
      continue;
    }

    // Note that at this point, we are guaranteed that the frame is alive, and
    // frames cannot exist without an owning WebContents.
    content::WebContents* web_contents =
        content::WebContents::FromRenderFrameHost(rfh);
    MetricsWebContentsObserver* observer =
        MetricsWebContentsObserver::FromWebContents(web_contents);

    if (!observer) {
      continue;
    }

    auto emplace_pair = memory_update_map.emplace(std::make_pair(
        observer, ObserverWeakPtrAndMemoryUpdates(
                      observer->AsWeakPtr(),
                      MemoryUpdate(rfh->GetGlobalId(), delta_bytes))));

    if (!emplace_pair.second) {
      emplace_pair.first->second.updates.emplace_back(
          MemoryUpdate(rfh->GetGlobalId(), delta_bytes));
    }
  }

  // Dispatch memory updates to each observer. Note that we store references to
  // MetricsWebContentsObservers as weakptrs. This is done to ensure that if a
  // WebContents was torn down synchronously as the result of a memory update
  // in a different WebContents, we would not have a dangling pointer.
  for (const auto& map_pair : memory_update_map) {
    MetricsWebContentsObserver* observer = map_pair.second.weak_ptr.get();

    if (!observer) {
      continue;
    }

    observer->OnV8MemoryChanged(map_pair.second.updates);
  }
}

void PageLoadMetricsMemoryTracker::OnRenderFrameDeleted(
    content::RenderFrameHost* render_frame_host,
    MetricsWebContentsObserver* observer) {
  DCHECK(render_frame_host);
  DCHECK(observer);

  auto it = per_frame_memory_usage_map_.find(render_frame_host->GetRoutingID());

  if (it == per_frame_memory_usage_map_.end()) {
    return;
  }

  // The routing id for |render_frame_host| has been found in our usage map.
  // We assume that the renderer has released the frame and that its
  // contents will be picked up by the next GC. So for all intents and
  // purposes, the memory is freed at this point, and we remove the entry from
  // our usage map and notify observers of the delta.
  int64_t delta_bytes = -it->second;
  per_frame_memory_usage_map_.erase(it);

  // Only send updates that are nontrivial.
  if (delta_bytes == 0) {
    return;
  }

  std::vector<MemoryUpdate> update(
      {MemoryUpdate(render_frame_host->GetGlobalId(), delta_bytes)});
  observer->OnV8MemoryChanged(update);
}

int64_t PageLoadMetricsMemoryTracker::UpdateMemoryUsageAndGetDelta(
    content::RenderFrameHost* render_frame_host,
    uint64_t current_bytes_used) {
  DCHECK(render_frame_host);

  int64_t delta_bytes = current_bytes_used;
  int routing_id = render_frame_host->GetRoutingID();
  auto it = per_frame_memory_usage_map_.find(routing_id);

  if (it != per_frame_memory_usage_map_.end()) {
    delta_bytes -= it->second;
    it->second = current_bytes_used;
  } else {
    per_frame_memory_usage_map_[routing_id] = current_bytes_used;
  }

  return delta_bytes;
}

}  // namespace page_load_metrics