OLD | NEW |
1 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #ifndef NET_URL_REQUEST_REQUEST_TRACKER_H_ | 5 #include "chrome/browser/net/passive_log_collector.h" |
6 #define NET_URL_REQUEST_REQUEST_TRACKER_H_ | 6 |
7 | 7 #include <algorithm> |
8 #include <vector> | 8 |
9 | 9 #include "base/string_util.h" |
10 #include "base/ref_counted.h" | 10 #include "chrome/browser/chrome_thread.h" |
11 #include "base/linked_list.h" | 11 |
12 #include "base/logging.h" | 12 namespace { |
13 #include "googleurl/src/gurl.h" | 13 const size_t kMaxNumEntriesPerLog = 50; |
14 #include "net/base/load_log.h" | 14 const size_t kMaxConnectJobGraveyardSize = 3; |
15 | 15 const size_t kMaxRequestGraveyardSize = 25; |
16 // Class to track all of the live instances of Request associated with a | 16 const size_t kMaxLiveRequests = 200; |
17 // particular URLRequestContext. It keeps a circular queue of the LoadLogs | 17 |
18 // for recently deceased requests. | 18 // Sort function on source ID. |
19 template<typename Request> | 19 bool OrderBySourceID(const PassiveLogCollector::RequestInfo& a, |
20 class RequestTracker { | 20 const PassiveLogCollector::RequestInfo& b) { |
21 public: | 21 return a.entries[0].source.id < b.entries[0].source.id; |
22 struct RecentRequestInfo { | 22 } |
23 GURL original_url; | 23 |
24 scoped_refptr<net::LoadLog> load_log; | 24 void AddEntryToRequestInfo(const net::NetLog::Entry& entry, |
25 }; | 25 bool is_unbounded, |
26 | 26 PassiveLogCollector::RequestInfo* out_info) { |
27 // Helper class to make Request insertable into a base::LinkedList, | 27 // Start dropping new entries when the log has gotten too big. |
28 // without making the public interface expose base::LinkNode. | 28 if (out_info->entries.size() + 1 <= kMaxNumEntriesPerLog || is_unbounded) { |
29 class Node : public base::LinkNode<Node> { | 29 out_info->entries.push_back(entry); |
30 public: | 30 } else { |
31 Node(Request* request) : request_(request) {} | 31 out_info->num_entries_truncated += 1; |
32 ~Node() {} | 32 out_info->entries[kMaxNumEntriesPerLog - 1] = entry; |
33 | 33 } |
34 Request* request() const { return request_; } | 34 } |
35 | 35 |
36 private: | 36 void AppendToRequestInfo(const PassiveLogCollector::RequestInfo& info, |
37 Request* request_; | 37 bool is_unbounded, |
38 }; | 38 PassiveLogCollector::RequestInfo* out_info) { |
39 | 39 for (size_t i = 0; i < info.entries.size(); ++i) |
40 typedef std::vector<RecentRequestInfo> RecentRequestInfoList; | 40 AddEntryToRequestInfo(info.entries[i], is_unbounded, out_info); |
41 typedef bool (*RecentRequestsFilterFunc)(const GURL&); | 41 } |
42 | 42 |
43 // The maximum number of entries for |graveyard_|, when in bounded mode. | 43 } // namespace |
44 static const size_t kMaxGraveyardSize; | 44 |
45 | 45 //---------------------------------------------------------------------------- |
46 // The maximum size of URLs to stuff into RecentRequestInfo, when in bounded | 46 // PassiveLogCollector |
47 // mode. | 47 //---------------------------------------------------------------------------- |
48 static const size_t kMaxGraveyardURLSize; | 48 |
49 | 49 PassiveLogCollector::PassiveLogCollector() |
50 // The maximum number of entries to use for LoadLogs when in bounded mode. | 50 : url_request_tracker_(&connect_job_tracker_), |
51 static const size_t kBoundedLoadLogMaxEntries; | 51 socket_stream_tracker_(&connect_job_tracker_) { |
52 | 52 } |
53 RequestTracker() | 53 |
54 : next_graveyard_index_(0), | 54 PassiveLogCollector::~PassiveLogCollector() { |
55 graveyard_filter_func_(NULL), | 55 } |
56 is_unbounded_(false) { | 56 |
57 } | 57 void PassiveLogCollector::OnAddEntry(const net::NetLog::Entry& entry) { |
58 | 58 switch (entry.source.type) { |
59 ~RequestTracker() {} | 59 case net::NetLog::SOURCE_URL_REQUEST: |
60 | 60 url_request_tracker_.OnAddEntry(entry); |
61 // Returns a list of Requests that are alive. | 61 break; |
62 std::vector<Request*> GetLiveRequests() { | 62 case net::NetLog::SOURCE_SOCKET_STREAM: |
63 std::vector<Request*> list; | 63 socket_stream_tracker_.OnAddEntry(entry); |
64 for (base::LinkNode<Node>* node = live_instances_.head(); | 64 break; |
65 node != live_instances_.end(); | 65 case net::NetLog::SOURCE_CONNECT_JOB: |
66 node = node->next()) { | 66 connect_job_tracker_.OnAddEntry(entry); |
67 Request* request = node->value()->request(); | 67 break; |
68 list.push_back(request); | 68 default: |
| 69 // Drop all other logged events. |
| 70 break; |
| 71 } |
| 72 } |
| 73 |
| 74 void PassiveLogCollector::Clear() { |
| 75 connect_job_tracker_.Clear(); |
| 76 url_request_tracker_.Clear(); |
| 77 socket_stream_tracker_.Clear(); |
| 78 } |
| 79 |
| 80 //---------------------------------------------------------------------------- |
| 81 // RequestTrackerBase |
| 82 //---------------------------------------------------------------------------- |
| 83 |
| 84 PassiveLogCollector::RequestTrackerBase::RequestTrackerBase( |
| 85 size_t max_graveyard_size) |
| 86 : max_graveyard_size_(max_graveyard_size), |
| 87 next_graveyard_index_(0), |
| 88 is_unbounded_(false) { |
| 89 } |
| 90 |
| 91 void PassiveLogCollector::RequestTrackerBase::OnAddEntry( |
| 92 const net::NetLog::Entry& entry) { |
| 93 RequestInfo& info = live_requests_[entry.source.id]; |
| 94 Action result = DoAddEntry(entry, &info); |
| 95 |
| 96 switch (result) { |
| 97 case ACTION_MOVE_TO_GRAVEYARD: |
| 98 InsertIntoGraveyard(info); |
| 99 // (fall-through) |
| 100 case ACTION_DELETE: |
| 101 RemoveFromLiveRequests(info); |
| 102 break; |
| 103 default: |
| 104 break; |
| 105 } |
| 106 |
| 107 if (live_requests_.size() > kMaxLiveRequests) { |
| 108 // This is a safety net in case something went wrong, to avoid continually |
| 109 // growing memory. |
| 110 LOG(WARNING) << "The passive log data has grown larger " |
| 111 "than expected, resetting"; |
| 112 live_requests_.clear(); |
| 113 } |
| 114 } |
| 115 |
| 116 PassiveLogCollector::RequestInfoList |
| 117 PassiveLogCollector::RequestTrackerBase::GetLiveRequests() const { |
| 118 RequestInfoList list; |
| 119 |
| 120 // Copy all of the live requests into the vector. |
| 121 for (SourceIDToInfoMap::const_iterator it = live_requests_.begin(); |
| 122 it != live_requests_.end(); |
| 123 ++it) { |
| 124 list.push_back(it->second); |
| 125 } |
| 126 |
| 127 std::sort(list.begin(), list.end(), OrderBySourceID); |
| 128 return list; |
| 129 } |
| 130 |
| 131 void PassiveLogCollector::RequestTrackerBase::ClearRecentlyDeceased() { |
| 132 next_graveyard_index_ = 0; |
| 133 graveyard_.clear(); |
| 134 } |
| 135 |
| 136 // Returns a list of recently completed Requests. |
| 137 PassiveLogCollector::RequestInfoList |
| 138 PassiveLogCollector::RequestTrackerBase::GetRecentlyDeceased() const { |
| 139 RequestInfoList list; |
| 140 |
| 141 // Copy the items from |graveyard_| (our circular queue of recently |
| 142 // deceased request infos) into a vector, ordered from oldest to newest. |
| 143 for (size_t i = 0; i < graveyard_.size(); ++i) { |
| 144 size_t index = (next_graveyard_index_ + i) % graveyard_.size(); |
| 145 list.push_back(graveyard_[index]); |
| 146 } |
| 147 return list; |
| 148 } |
| 149 |
| 150 const PassiveLogCollector::RequestInfo* |
| 151 PassiveLogCollector::RequestTrackerBase::GetRequestInfoFromGraveyard( |
| 152 int source_id) const { |
| 153 // Scan through the graveyard to find an entry for |source_id|. |
| 154 for (size_t i = 0; i < graveyard_.size(); ++i) { |
| 155 if (graveyard_[i].entries[0].source.id == source_id) { |
| 156 return &graveyard_[i]; |
69 } | 157 } |
70 return list; | 158 } |
71 } | 159 return NULL; |
72 | 160 } |
73 // Clears the circular buffer of RecentRequestInfos. | 161 |
74 void ClearRecentlyDeceased() { | 162 void PassiveLogCollector::RequestTrackerBase::RemoveFromLiveRequests( |
75 next_graveyard_index_ = 0; | 163 const RequestInfo& info) { |
76 graveyard_.clear(); | 164 // Remove from |live_requests_|. |
77 } | 165 SourceIDToInfoMap::iterator it = live_requests_.find( |
78 | 166 info.entries[0].source.id); |
79 // Returns a list of recently completed Requests. | 167 DCHECK(it != live_requests_.end()); |
80 const RecentRequestInfoList GetRecentlyDeceased() { | 168 live_requests_.erase(it); |
81 RecentRequestInfoList list; | 169 } |
82 | 170 |
83 // Copy the items from |graveyard_| (our circular queue of recently | 171 void PassiveLogCollector::RequestTrackerBase::SetUnbounded( |
84 // deceased request infos) into a vector, ordered from oldest to newest. | 172 bool unbounded) { |
85 for (size_t i = 0; i < graveyard_.size(); ++i) { | 173 // No change. |
86 size_t index = (next_graveyard_index_ + i) % graveyard_.size(); | 174 if (is_unbounded_ == unbounded) |
87 list.push_back(graveyard_[index]); | 175 return; |
| 176 |
| 177 // If we are going from unbounded to bounded, we need to trim the |
| 178 // graveyard. For simplicity we will simply clear it. |
| 179 if (is_unbounded_ && !unbounded) |
| 180 ClearRecentlyDeceased(); |
| 181 |
| 182 is_unbounded_ = unbounded; |
| 183 } |
| 184 |
| 185 void PassiveLogCollector::RequestTrackerBase::Clear() { |
| 186 ClearRecentlyDeceased(); |
| 187 live_requests_.clear(); |
| 188 } |
| 189 |
| 190 void PassiveLogCollector::RequestTrackerBase::InsertIntoGraveyard( |
| 191 const RequestInfo& info) { |
| 192 if (is_unbounded_) { |
| 193 graveyard_.push_back(info); |
| 194 return; |
| 195 } |
| 196 |
| 197 // Otherwise enforce a bound on the graveyard size, by treating it as a |
| 198 // circular buffer. |
| 199 if (graveyard_.size() < max_graveyard_size_) { |
| 200 // Still growing to maximum capacity. |
| 201 DCHECK_EQ(next_graveyard_index_, graveyard_.size()); |
| 202 graveyard_.push_back(info); |
| 203 } else { |
| 204 // At maximum capacity, overwite the oldest entry. |
| 205 graveyard_[next_graveyard_index_] = info; |
| 206 } |
| 207 next_graveyard_index_ = (next_graveyard_index_ + 1) % max_graveyard_size_; |
| 208 } |
| 209 |
| 210 //---------------------------------------------------------------------------- |
| 211 // ConnectJobTracker |
| 212 //---------------------------------------------------------------------------- |
| 213 |
| 214 const size_t PassiveLogCollector::ConnectJobTracker::kMaxGraveyardSize = 3; |
| 215 |
| 216 PassiveLogCollector::ConnectJobTracker::ConnectJobTracker() |
| 217 : RequestTrackerBase(kMaxGraveyardSize) { |
| 218 } |
| 219 |
| 220 PassiveLogCollector::RequestTrackerBase::Action |
| 221 PassiveLogCollector::ConnectJobTracker::DoAddEntry( |
| 222 const net::NetLog::Entry& entry, |
| 223 RequestInfo* out_info) { |
| 224 // Save the entry (possibly truncating). |
| 225 AddEntryToRequestInfo(entry, is_unbounded(), out_info); |
| 226 |
| 227 // If this is the end of the connect job, move the request to the graveyard. |
| 228 if (entry.type == net::NetLog::Entry::TYPE_EVENT && |
| 229 entry.event.type == net::NetLog::TYPE_SOCKET_POOL_CONNECT_JOB && |
| 230 entry.event.phase == net::NetLog::PHASE_END) { |
| 231 return ACTION_MOVE_TO_GRAVEYARD; |
| 232 } |
| 233 |
| 234 return ACTION_NONE; |
| 235 } |
| 236 |
| 237 //---------------------------------------------------------------------------- |
| 238 // RequestTracker |
| 239 //---------------------------------------------------------------------------- |
| 240 |
| 241 const size_t PassiveLogCollector::RequestTracker::kMaxGraveyardSize = 25; |
| 242 const size_t PassiveLogCollector::RequestTracker::kMaxGraveyardURLSize = 1000; |
| 243 |
| 244 PassiveLogCollector::RequestTracker::RequestTracker( |
| 245 ConnectJobTracker* connect_job_tracker) |
| 246 : RequestTrackerBase(kMaxGraveyardSize), |
| 247 connect_job_tracker_(connect_job_tracker) { |
| 248 } |
| 249 |
| 250 PassiveLogCollector::RequestTrackerBase::Action |
| 251 PassiveLogCollector::RequestTracker::DoAddEntry( |
| 252 const net::NetLog::Entry& entry, |
| 253 RequestInfo* out_info) { |
| 254 |
| 255 if (entry.type == net::NetLog::Entry::TYPE_EVENT && |
| 256 entry.event.type == net::NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_ID) { |
| 257 // If this was notification that a ConnectJob was bound to the request, |
| 258 // copy all the logged data for that ConnectJob. |
| 259 AddConnectJobInfo(entry, out_info); |
| 260 } else { |
| 261 // Otherwise just append this entry to the request info. |
| 262 AddEntryToRequestInfo(entry, is_unbounded(), out_info); |
| 263 } |
| 264 |
| 265 // If this was the start of a URLRequest/SocketStream, extract the URL. |
| 266 if (out_info->entries.size() == 1 && |
| 267 entry.type == net::NetLog::Entry::TYPE_EVENT && |
| 268 entry.event.type == net::NetLog::TYPE_REQUEST_ALIVE && |
| 269 entry.event.phase == net::NetLog::PHASE_BEGIN) { |
| 270 out_info->url = entry.string; |
| 271 out_info->entries[0].string = std::string(); |
| 272 |
| 273 // Paranoia check: truncate the URL if it is really big. |
| 274 if (out_info->url.size() > kMaxGraveyardURLSize) |
| 275 out_info->url = out_info->url.substr(0, kMaxGraveyardURLSize); |
| 276 } |
| 277 |
| 278 // If the request has ended, move it to the graveyard. |
| 279 if (entry.type == net::NetLog::Entry::TYPE_EVENT && |
| 280 entry.event.type == net::NetLog::TYPE_REQUEST_ALIVE && |
| 281 entry.event.phase == net::NetLog::PHASE_END) { |
| 282 if (StartsWithASCII(out_info->url, "chrome://", false)) { |
| 283 // Avoid sending "chrome://" requests to the graveyard, since it just |
| 284 // adds to clutter. |
| 285 return ACTION_DELETE; |
88 } | 286 } |
89 return list; | 287 return ACTION_MOVE_TO_GRAVEYARD; |
90 } | 288 } |
91 | 289 |
92 void Add(Request* request) { | 290 return ACTION_NONE; |
93 live_instances_.Append(&request->request_tracker_node_); | 291 } |
94 } | 292 |
95 | 293 void PassiveLogCollector::RequestTracker::AddConnectJobInfo( |
96 void Remove(Request* request) { | 294 const net::NetLog::Entry& entry, |
97 // Remove from |live_instances_|. | 295 RequestInfo* live_entry) { |
98 request->request_tracker_node_.RemoveFromList(); | 296 // We have just been notified of which ConnectJob the |
99 | 297 // URLRequest/SocketStream was assigned. Lookup all the data we captured |
100 RecentRequestInfo info; | 298 // for the ConnectJob, and append it to the URLRequest/SocketStream's |
101 request->GetInfoForTracker(&info); | 299 // RequestInfo. |
102 | 300 |
103 if (!is_unbounded_) { | 301 // TODO(eroman): This should NOT be plumbed through via |error_code| ! |
104 // Paranoia check: truncate |info.original_url| if it is really big. | 302 int connect_job_id = entry.error_code; |
105 const std::string& spec = info.original_url.possibly_invalid_spec(); | 303 |
106 if (spec.size() > kMaxGraveyardURLSize) | 304 const RequestInfo* connect_job_info = |
107 info.original_url = GURL(spec.substr(0, kMaxGraveyardURLSize)); | 305 connect_job_tracker_->GetRequestInfoFromGraveyard(connect_job_id); |
108 } | 306 |
109 | 307 if (connect_job_info) { |
110 if (ShouldInsertIntoGraveyard(info)) { | 308 // Append the ConnectJob information we found. |
111 // Add into |graveyard_|. | 309 AppendToRequestInfo(*connect_job_info, is_unbounded(), live_entry); |
112 InsertIntoGraveyard(info); | 310 } else { |
113 } | 311 // If we couldn't find the information for the ConnectJob, append a |
114 } | 312 // generic message instead. |
115 | 313 net::NetLog::Entry e(entry); |
116 // This function lets you exclude requests from being saved to the graveyard. | 314 e.type = net::NetLog::Entry::TYPE_STRING; |
117 // The graveyard is a circular buffer of the most recently completed | 315 e.string = StringPrintf("Used ConnectJob id=%d", connect_job_id); |
118 // requests. Pass NULL turn off filtering. Otherwise pass in a function | 316 AddEntryToRequestInfo(e, is_unbounded(), live_entry); |
119 // returns false to exclude requests, true otherwise. | 317 } |
120 void SetGraveyardFilter(RecentRequestsFilterFunc filter_func) { | 318 } |
121 graveyard_filter_func_ = filter_func; | |
122 } | |
123 | |
124 bool IsUnbounded() const { | |
125 return is_unbounded_; | |
126 } | |
127 | |
128 void SetUnbounded(bool unbounded) { | |
129 // No change. | |
130 if (is_unbounded_ == unbounded) | |
131 return; | |
132 | |
133 // If we are going from unbounded to bounded, we need to trim the | |
134 // graveyard. For simplicity we will simply clear it. | |
135 if (is_unbounded_ && !unbounded) | |
136 ClearRecentlyDeceased(); | |
137 | |
138 is_unbounded_ = unbounded; | |
139 } | |
140 | |
141 // Creates a LoadLog using the unbounded/bounded constraints that | |
142 // apply to this tracker. | |
143 net::LoadLog* CreateLoadLog() { | |
144 if (IsUnbounded()) | |
145 return new net::LoadLog(net::LoadLog::kUnbounded); | |
146 return new net::LoadLog(kBoundedLoadLogMaxEntries); | |
147 } | |
148 | |
149 private: | |
150 bool ShouldInsertIntoGraveyard(const RecentRequestInfo& info) { | |
151 if (!graveyard_filter_func_) | |
152 return true; | |
153 return graveyard_filter_func_(info.original_url); | |
154 } | |
155 | |
156 void InsertIntoGraveyard(const RecentRequestInfo& info) { | |
157 if (is_unbounded_) { | |
158 graveyard_.push_back(info); | |
159 return; | |
160 } | |
161 | |
162 // Otherwise enforce a bound on the graveyard size, by treating it as a | |
163 // circular buffer. | |
164 if (graveyard_.size() < kMaxGraveyardSize) { | |
165 // Still growing to maximum capacity. | |
166 DCHECK_EQ(next_graveyard_index_, graveyard_.size()); | |
167 graveyard_.push_back(info); | |
168 } else { | |
169 // At maximum capacity, overwite the oldest entry. | |
170 graveyard_[next_graveyard_index_] = info; | |
171 } | |
172 next_graveyard_index_ = (next_graveyard_index_ + 1) % kMaxGraveyardSize; | |
173 } | |
174 | |
175 base::LinkedList<Node> live_instances_; | |
176 | |
177 size_t next_graveyard_index_; | |
178 RecentRequestInfoList graveyard_; | |
179 RecentRequestsFilterFunc graveyard_filter_func_; | |
180 bool is_unbounded_; | |
181 }; | |
182 | |
183 template<typename Request> | |
184 const size_t RequestTracker<Request>::kMaxGraveyardSize = 25; | |
185 | |
186 template<typename Request> | |
187 const size_t RequestTracker<Request>::kMaxGraveyardURLSize = 1000; | |
188 | |
189 template<typename Request> | |
190 const size_t RequestTracker<Request>::kBoundedLoadLogMaxEntries = 50; | |
191 | |
192 #endif // NET_URL_REQUEST_REQUEST_TRACKER_H_ | |
OLD | NEW |