passive_log_collector.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
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
3// found in the LICENSE file.
4
5#include "chrome/browser/net/passive_log_collector.h"
6
7#include <algorithm>
8
9#include "base/compiler_specific.h"
10#include "base/string_util.h"
11#include "base/format_macros.h"
12#include "chrome/browser/chrome_thread.h"
13#include "net/url_request/url_request_netlog_params.h"
14
15namespace {
16
17// TODO(eroman): Do something with the truncation count.
18
19const size_t kMaxNumEntriesPerLog = 30;
20
21void AddEntryToSourceInfo(const PassiveLogCollector::Entry& entry,
22                          PassiveLogCollector::SourceInfo* out_info) {
23  // Start dropping new entries when the log has gotten too big.
24  if (out_info->entries.size() + 1 <= kMaxNumEntriesPerLog) {
25    out_info->entries.push_back(entry);
26  } else {
27    out_info->num_entries_truncated += 1;
28    out_info->entries[kMaxNumEntriesPerLog - 1] = entry;
29  }
30}
31
32// Comparator to sort entries by their |order| property, ascending.
33bool SortByOrderComparator(const PassiveLogCollector::Entry& a,
34                           const PassiveLogCollector::Entry& b) {
35  return a.order < b.order;
36}
37
38}  // namespace
39
40//----------------------------------------------------------------------------
41// PassiveLogCollector
42//----------------------------------------------------------------------------
43
44PassiveLogCollector::PassiveLogCollector()
45    : Observer(net::NetLog::LOG_BASIC),
46      ALLOW_THIS_IN_INITIALIZER_LIST(connect_job_tracker_(this)),
47      ALLOW_THIS_IN_INITIALIZER_LIST(url_request_tracker_(this)),
48      ALLOW_THIS_IN_INITIALIZER_LIST(socket_stream_tracker_(this)),
49      num_events_seen_(0) {
50
51  // Define the mapping between source types and the tracker objects.
52  memset(&trackers_[0], 0, sizeof(trackers_));
53  trackers_[net::NetLog::SOURCE_NONE] = &global_source_tracker_;
54  trackers_[net::NetLog::SOURCE_URL_REQUEST] = &url_request_tracker_;
55  trackers_[net::NetLog::SOURCE_SOCKET_STREAM] = &socket_stream_tracker_;
56  trackers_[net::NetLog::SOURCE_CONNECT_JOB] = &connect_job_tracker_;
57  trackers_[net::NetLog::SOURCE_SOCKET] = &socket_tracker_;
58  trackers_[net::NetLog::SOURCE_INIT_PROXY_RESOLVER] =
59      &init_proxy_resolver_tracker_;
60  trackers_[net::NetLog::SOURCE_SPDY_SESSION] = &spdy_session_tracker_;
61  trackers_[net::NetLog::SOURCE_HOST_RESOLVER_IMPL_REQUEST] =
62      &dns_request_tracker_;
63  trackers_[net::NetLog::SOURCE_HOST_RESOLVER_IMPL_JOB] = &dns_job_tracker_;
64  // Make sure our mapping is up-to-date.
65  for (size_t i = 0; i < arraysize(trackers_); ++i)
66    DCHECK(trackers_[i]) << "Unhandled SourceType: " << i;
67}
68
69PassiveLogCollector::~PassiveLogCollector() {
70}
71
72void PassiveLogCollector::OnAddEntry(
73    net::NetLog::EventType type,
74    const base::TimeTicks& time,
75    const net::NetLog::Source& source,
76    net::NetLog::EventPhase phase,
77    net::NetLog::EventParameters* params) {
78  // Package the parameters into a single struct for convenience.
79  Entry entry(num_events_seen_++, type, time, source, phase, params);
80
81  SourceTrackerInterface* tracker = GetTrackerForSourceType(entry.source.type);
82  if (tracker)
83    tracker->OnAddEntry(entry);
84}
85
86PassiveLogCollector::SourceTrackerInterface*
87PassiveLogCollector::GetTrackerForSourceType(
88    net::NetLog::SourceType source_type) {
89  DCHECK_LE(source_type, static_cast<int>(arraysize(trackers_)));
90  DCHECK_GE(source_type, 0);
91  return trackers_[source_type];
92}
93
94void PassiveLogCollector::Clear() {
95  for (size_t i = 0; i < arraysize(trackers_); ++i)
96    trackers_[i]->Clear();
97}
98
99void PassiveLogCollector::GetAllCapturedEvents(EntryList* out) const {
100  out->clear();
101
102  // Append all of the captured entries held by the various trackers to
103  // |out|.
104  for (size_t i = 0; i < arraysize(trackers_); ++i)
105    trackers_[i]->AppendAllEntries(out);
106
107  // Now sort the list of entries by their insertion time (ascending).
108  std::sort(out->begin(), out->end(), &SortByOrderComparator);
109}
110
111std::string PassiveLogCollector::SourceInfo::GetURL() const {
112  // Note: we look at the first *two* entries, since the outer REQUEST_ALIVE
113  // doesn't actually contain any data.
114  for (size_t i = 0; i < 2 && i < entries.size(); ++i) {
115    const PassiveLogCollector::Entry& entry = entries[i];
116    if (entry.phase == net::NetLog::PHASE_BEGIN && entry.params) {
117      switch (entry.type) {
118        case net::NetLog::TYPE_URL_REQUEST_START_JOB:
119          return static_cast<URLRequestStartEventParameters*>(
120              entry.params.get())->url().possibly_invalid_spec();
121        case net::NetLog::TYPE_SOCKET_STREAM_CONNECT:
122          return static_cast<net::NetLogStringParameter*>(
123              entry.params.get())->value();
124        default:
125          break;
126      }
127    }
128  }
129  return std::string();
130}
131
132//----------------------------------------------------------------------------
133// GlobalSourceTracker
134//----------------------------------------------------------------------------
135
136PassiveLogCollector::GlobalSourceTracker::GlobalSourceTracker() {}
137PassiveLogCollector::GlobalSourceTracker::~GlobalSourceTracker() {}
138
139void PassiveLogCollector::GlobalSourceTracker::OnAddEntry(const Entry& entry) {
140  const size_t kMaxEntries = 30u;
141  entries_.push_back(entry);
142  if (entries_.size() > kMaxEntries)
143    entries_.pop_front();
144}
145
146void PassiveLogCollector::GlobalSourceTracker::Clear() {
147  entries_.clear();
148}
149
150void PassiveLogCollector::GlobalSourceTracker::AppendAllEntries(
151    EntryList* out) const {
152  out->insert(out->end(), entries_.begin(), entries_.end());
153}
154
155//----------------------------------------------------------------------------
156// SourceTracker
157//----------------------------------------------------------------------------
158
159PassiveLogCollector::SourceTracker::SourceTracker(
160    size_t max_num_sources,
161    size_t max_graveyard_size,
162    PassiveLogCollector* parent)
163    : max_num_sources_(max_num_sources),
164      max_graveyard_size_(max_graveyard_size),
165      parent_(parent) {
166}
167
168PassiveLogCollector::SourceTracker::~SourceTracker() {}
169
170void PassiveLogCollector::SourceTracker::OnAddEntry(const Entry& entry) {
171  // Lookup or insert a new entry into the bounded map.
172  SourceIDToInfoMap::iterator it = sources_.find(entry.source.id);
173  if (it == sources_.end()) {
174    if (sources_.size() >= max_num_sources_) {
175      LOG(WARNING) << "The passive log data has grown larger "
176                      "than expected, resetting";
177      Clear();
178    }
179    it = sources_.insert(
180        SourceIDToInfoMap::value_type(entry.source.id, SourceInfo())).first;
181    it->second.source_id = entry.source.id;
182  }
183
184  SourceInfo& info = it->second;
185  Action result = DoAddEntry(entry, &info);
186
187  if (result != ACTION_NONE) {
188    // We are either queuing it for deletion, or deleting it immediately.
189    // If someone else holds a reference to this source, defer the deletion
190    // until all the references are released.
191    info.is_alive = false;
192    if (info.reference_count == 0) {
193      switch (result) {
194        case ACTION_MOVE_TO_GRAVEYARD:
195          AddToDeletionQueue(info.source_id);
196          break;
197        case ACTION_DELETE:
198          DeleteSourceInfo(info.source_id);
199          break;
200        default:
201          NOTREACHED();
202          break;
203      }
204    }
205  }
206}
207
208void PassiveLogCollector::SourceTracker::DeleteSourceInfo(
209    uint32 source_id) {
210  SourceIDToInfoMap::iterator it = sources_.find(source_id);
211  if (it == sources_.end()) {
212    // TODO(eroman): Is this happening? And if so, why. Remove this
213    //               once the cause is understood.
214    LOG(WARNING) << "Tried to delete info for nonexistent source";
215    return;
216  }
217  // The source should not be in the deletion queue.
218  DCHECK(std::find(deletion_queue_.begin(), deletion_queue_.end(),
219                   source_id) == deletion_queue_.end());
220  ReleaseAllReferencesToDependencies(&(it->second));
221  sources_.erase(it);
222}
223
224void PassiveLogCollector::SourceTracker::Clear() {
225  deletion_queue_.clear();
226
227  // Release all references held to dependent sources.
228  for (SourceIDToInfoMap::iterator it = sources_.begin();
229       it != sources_.end();
230       ++it) {
231    ReleaseAllReferencesToDependencies(&(it->second));
232  }
233  sources_.clear();
234}
235
236void PassiveLogCollector::SourceTracker::AppendAllEntries(
237    EntryList* out) const {
238  // Append all of the entries for each of the sources.
239  for (SourceIDToInfoMap::const_iterator it = sources_.begin();
240       it != sources_.end();
241       ++it) {
242    const SourceInfo& info = it->second;
243    out->insert(out->end(), info.entries.begin(), info.entries.end());
244  }
245}
246
247void PassiveLogCollector::SourceTracker::AddToDeletionQueue(
248    uint32 source_id) {
249  DCHECK(sources_.find(source_id) != sources_.end());
250  DCHECK(!sources_.find(source_id)->second.is_alive);
251  DCHECK_GE(sources_.find(source_id)->second.reference_count, 0);
252  DCHECK_LE(deletion_queue_.size(), max_graveyard_size_);
253
254  DCHECK(std::find(deletion_queue_.begin(), deletion_queue_.end(),
255                   source_id) == deletion_queue_.end());
256  deletion_queue_.push_back(source_id);
257
258  // After the deletion queue has reached its maximum size, start
259  // deleting sources in FIFO order.
260  if (deletion_queue_.size() > max_graveyard_size_) {
261    uint32 oldest = deletion_queue_.front();
262    deletion_queue_.pop_front();
263    DeleteSourceInfo(oldest);
264  }
265}
266
267void PassiveLogCollector::SourceTracker::AdjustReferenceCountForSource(
268    int offset, uint32 source_id) {
269  DCHECK(offset == -1 || offset == 1) << "invalid offset: " << offset;
270
271  // In general it is invalid to call AdjustReferenceCountForSource() on
272  // source that doesn't exist. However, it is possible that if
273  // SourceTracker::Clear() was previously called this can happen.
274  SourceIDToInfoMap::iterator it = sources_.find(source_id);
275  if (it == sources_.end()) {
276    LOG(WARNING) << "Released a reference to nonexistent source.";
277    return;
278  }
279
280  SourceInfo& info = it->second;
281  DCHECK_GE(info.reference_count, 0);
282  info.reference_count += offset;
283
284  if (info.reference_count < 0) {
285    // In general this shouldn't happen, however it is possible to reach this
286    // state if SourceTracker::Clear() was called earlier.
287    LOG(WARNING) << "Released unmatched reference count.";
288    info.reference_count = 0;
289  }
290
291  if (!info.is_alive) {
292    if (info.reference_count == 1 && offset == 1) {
293      // If we just added a reference to a dead source that had no references,
294      // it must have been in the deletion queue, so remove it from the queue.
295      DeletionQueue::iterator it =
296          std::remove(deletion_queue_.begin(), deletion_queue_.end(),
297                      source_id);
298      DCHECK(it != deletion_queue_.end());
299      deletion_queue_.erase(it);
300    } else if (info.reference_count == 0) {
301      // If we just released the final reference to a dead source, go ahead
302      // and delete it right away.
303      DeleteSourceInfo(source_id);
304    }
305  }
306}
307
308void PassiveLogCollector::SourceTracker::AddReferenceToSourceDependency(
309    const net::NetLog::Source& source, SourceInfo* info) {
310  // Find the tracker which should be holding |source|.
311  DCHECK(parent_);
312  DCHECK_NE(source.type, net::NetLog::SOURCE_NONE);
313  SourceTracker* tracker = static_cast<SourceTracker*>(
314      parent_->GetTrackerForSourceType(source.type));
315  DCHECK(tracker);
316
317  // Tell the owning tracker to increment the reference count of |source|.
318  tracker->AdjustReferenceCountForSource(1, source.id);
319
320  // Make a note to release this reference once |info| is destroyed.
321  info->dependencies.push_back(source);
322}
323
324void
325PassiveLogCollector::SourceTracker::ReleaseAllReferencesToDependencies(
326    SourceInfo* info) {
327  // Release all references |info| was holding to other sources.
328  for (SourceDependencyList::const_iterator it = info->dependencies.begin();
329       it != info->dependencies.end(); ++it) {
330    const net::NetLog::Source& source = *it;
331
332    // Find the tracker which should be holding |source|.
333    DCHECK(parent_);
334    DCHECK_NE(source.type, net::NetLog::SOURCE_NONE);
335    SourceTracker* tracker = static_cast<SourceTracker*>(
336        parent_->GetTrackerForSourceType(source.type));
337    DCHECK(tracker);
338
339    // Tell the owning tracker to decrement the reference count of |source|.
340    tracker->AdjustReferenceCountForSource(-1, source.id);
341  }
342
343  info->dependencies.clear();
344}
345
346//----------------------------------------------------------------------------
347// ConnectJobTracker
348//----------------------------------------------------------------------------
349
350const size_t PassiveLogCollector::ConnectJobTracker::kMaxNumSources = 100;
351const size_t PassiveLogCollector::ConnectJobTracker::kMaxGraveyardSize = 15;
352
353PassiveLogCollector::ConnectJobTracker::ConnectJobTracker(
354    PassiveLogCollector* parent)
355    : SourceTracker(kMaxNumSources, kMaxGraveyardSize, parent) {
356}
357
358PassiveLogCollector::SourceTracker::Action
359PassiveLogCollector::ConnectJobTracker::DoAddEntry(const Entry& entry,
360                                                   SourceInfo* out_info) {
361  AddEntryToSourceInfo(entry, out_info);
362
363  if (entry.type == net::NetLog::TYPE_CONNECT_JOB_SET_SOCKET) {
364    const net::NetLog::Source& source_dependency =
365        static_cast<net::NetLogSourceParameter*>(entry.params.get())->value();
366    AddReferenceToSourceDependency(source_dependency, out_info);
367  }
368
369  // If this is the end of the connect job, move the source to the graveyard.
370  if (entry.type == net::NetLog::TYPE_SOCKET_POOL_CONNECT_JOB &&
371      entry.phase == net::NetLog::PHASE_END) {
372    return ACTION_MOVE_TO_GRAVEYARD;
373  }
374
375  return ACTION_NONE;
376}
377
378//----------------------------------------------------------------------------
379// SocketTracker
380//----------------------------------------------------------------------------
381
382const size_t PassiveLogCollector::SocketTracker::kMaxNumSources = 200;
383const size_t PassiveLogCollector::SocketTracker::kMaxGraveyardSize = 15;
384
385PassiveLogCollector::SocketTracker::SocketTracker()
386    : SourceTracker(kMaxNumSources, kMaxGraveyardSize, NULL) {
387}
388
389PassiveLogCollector::SourceTracker::Action
390PassiveLogCollector::SocketTracker::DoAddEntry(const Entry& entry,
391                                               SourceInfo* out_info) {
392  // TODO(eroman): aggregate the byte counts once truncation starts to happen,
393  //               to summarize transaction read/writes for each SOCKET_IN_USE
394  //               section.
395  if (entry.type == net::NetLog::TYPE_SOCKET_BYTES_SENT ||
396      entry.type == net::NetLog::TYPE_SOCKET_BYTES_RECEIVED) {
397    return ACTION_NONE;
398  }
399
400  AddEntryToSourceInfo(entry, out_info);
401
402  if (entry.type == net::NetLog::TYPE_SOCKET_ALIVE &&
403      entry.phase == net::NetLog::PHASE_END) {
404    return ACTION_MOVE_TO_GRAVEYARD;
405  }
406
407  return ACTION_NONE;
408}
409
410//----------------------------------------------------------------------------
411// RequestTracker
412//----------------------------------------------------------------------------
413
414const size_t PassiveLogCollector::RequestTracker::kMaxNumSources = 100;
415const size_t PassiveLogCollector::RequestTracker::kMaxGraveyardSize = 25;
416
417PassiveLogCollector::RequestTracker::RequestTracker(PassiveLogCollector* parent)
418    : SourceTracker(kMaxNumSources, kMaxGraveyardSize, parent) {
419}
420
421PassiveLogCollector::SourceTracker::Action
422PassiveLogCollector::RequestTracker::DoAddEntry(const Entry& entry,
423                                                SourceInfo* out_info) {
424  if (entry.type == net::NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB ||
425      entry.type == net::NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET) {
426    const net::NetLog::Source& source_dependency =
427        static_cast<net::NetLogSourceParameter*>(entry.params.get())->value();
428    AddReferenceToSourceDependency(source_dependency, out_info);
429  }
430
431  AddEntryToSourceInfo(entry, out_info);
432
433  // If the request has ended, move it to the graveyard.
434  if (entry.type == net::NetLog::TYPE_REQUEST_ALIVE &&
435      entry.phase == net::NetLog::PHASE_END) {
436    if (StartsWithASCII(out_info->GetURL(), "chrome://", false)) {
437      // Avoid sending "chrome://" requests to the graveyard, since it just
438      // adds to clutter.
439      return ACTION_DELETE;
440    }
441    return ACTION_MOVE_TO_GRAVEYARD;
442  }
443
444  return ACTION_NONE;
445}
446
447//----------------------------------------------------------------------------
448// InitProxyResolverTracker
449//----------------------------------------------------------------------------
450
451const size_t PassiveLogCollector::InitProxyResolverTracker::kMaxNumSources = 20;
452const size_t PassiveLogCollector::InitProxyResolverTracker::kMaxGraveyardSize =
453    3;
454
455PassiveLogCollector::InitProxyResolverTracker::InitProxyResolverTracker()
456    : SourceTracker(kMaxNumSources, kMaxGraveyardSize, NULL) {
457}
458
459PassiveLogCollector::SourceTracker::Action
460PassiveLogCollector::InitProxyResolverTracker::DoAddEntry(
461    const Entry& entry, SourceInfo* out_info) {
462  AddEntryToSourceInfo(entry, out_info);
463  if (entry.type == net::NetLog::TYPE_INIT_PROXY_RESOLVER &&
464      entry.phase == net::NetLog::PHASE_END) {
465    return ACTION_MOVE_TO_GRAVEYARD;
466  } else {
467    return ACTION_NONE;
468  }
469}
470
471//----------------------------------------------------------------------------
472// SpdySessionTracker
473//----------------------------------------------------------------------------
474
475const size_t PassiveLogCollector::SpdySessionTracker::kMaxNumSources = 50;
476const size_t PassiveLogCollector::SpdySessionTracker::kMaxGraveyardSize = 10;
477
478PassiveLogCollector::SpdySessionTracker::SpdySessionTracker()
479    : SourceTracker(kMaxNumSources, kMaxGraveyardSize, NULL) {
480}
481
482PassiveLogCollector::SourceTracker::Action
483PassiveLogCollector::SpdySessionTracker::DoAddEntry(const Entry& entry,
484                                                    SourceInfo* out_info) {
485  AddEntryToSourceInfo(entry, out_info);
486  if (entry.type == net::NetLog::TYPE_SPDY_SESSION &&
487      entry.phase == net::NetLog::PHASE_END) {
488    return ACTION_MOVE_TO_GRAVEYARD;
489  } else {
490    return ACTION_NONE;
491  }
492}
493
494//----------------------------------------------------------------------------
495// DNSRequestTracker
496//----------------------------------------------------------------------------
497
498const size_t PassiveLogCollector::DNSRequestTracker::kMaxNumSources = 200;
499const size_t PassiveLogCollector::DNSRequestTracker::kMaxGraveyardSize = 20;
500
501PassiveLogCollector::DNSRequestTracker::DNSRequestTracker()
502    : SourceTracker(kMaxNumSources, kMaxGraveyardSize, NULL) {
503}
504
505PassiveLogCollector::SourceTracker::Action
506PassiveLogCollector::DNSRequestTracker::DoAddEntry(const Entry& entry,
507                                                   SourceInfo* out_info) {
508  AddEntryToSourceInfo(entry, out_info);
509  if (entry.type == net::NetLog::TYPE_HOST_RESOLVER_IMPL_REQUEST &&
510      entry.phase == net::NetLog::PHASE_END) {
511    return ACTION_MOVE_TO_GRAVEYARD;
512  } else {
513    return ACTION_NONE;
514  }
515}
516
517//----------------------------------------------------------------------------
518// DNSJobTracker
519//----------------------------------------------------------------------------
520
521const size_t PassiveLogCollector::DNSJobTracker::kMaxNumSources = 100;
522const size_t PassiveLogCollector::DNSJobTracker::kMaxGraveyardSize = 15;
523
524PassiveLogCollector::DNSJobTracker::DNSJobTracker()
525    : SourceTracker(kMaxNumSources, kMaxGraveyardSize, NULL) {
526}
527
528PassiveLogCollector::SourceTracker::Action
529PassiveLogCollector::DNSJobTracker::DoAddEntry(const Entry& entry,
530                                               SourceInfo* out_info) {
531  AddEntryToSourceInfo(entry, out_info);
532  if (entry.type == net::NetLog::TYPE_HOST_RESOLVER_IMPL_JOB &&
533      entry.phase == net::NetLog::PHASE_END) {
534    return ACTION_MOVE_TO_GRAVEYARD;
535  } else {
536    return ACTION_NONE;
537  }
538}
539