|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "content/browser/histogram_synchronizer.h" | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/lazy_instance.h" | 
|  | #include "base/location.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/metrics/histogram.h" | 
|  | #include "base/metrics/histogram_delta_serialization.h" | 
|  | #include "base/pickle.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/threading/thread.h" | 
|  | #include "base/threading/thread_restrictions.h" | 
|  | #include "content/browser/histogram_controller.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/histogram_fetcher.h" | 
|  | #include "content/public/common/content_constants.h" | 
|  |  | 
|  | using base::Time; | 
|  | using base::TimeDelta; | 
|  | using base::TimeTicks; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Negative numbers are never used as sequence numbers.  We explicitly pick a | 
|  | // negative number that is "so negative" that even when we add one (as is done | 
|  | // when we generated the next sequence number) that it will still be negative. | 
|  | // We have code that handles wrapping around on an overflow into negative | 
|  | // territory. | 
|  | static const int kNeverUsableSequenceNumber = -2; | 
|  |  | 
|  | }  // anonymous namespace | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | // The "RequestContext" structure describes an individual request received from | 
|  | // the UI. All methods are accessible on UI thread. | 
|  | class HistogramSynchronizer::RequestContext { | 
|  | public: | 
|  | // A map from sequence_number_ to the actual RequestContexts. | 
|  | typedef std::map<int, RequestContext*> RequestContextMap; | 
|  |  | 
|  | RequestContext(const base::Closure& callback, int sequence_number) | 
|  | : callback_(callback), | 
|  | sequence_number_(sequence_number), | 
|  | received_process_group_count_(0), | 
|  | processes_pending_(0) { | 
|  | } | 
|  | ~RequestContext() {} | 
|  |  | 
|  | void SetReceivedProcessGroupCount(bool done) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | received_process_group_count_ = done; | 
|  | } | 
|  |  | 
|  | // Methods for book keeping of processes_pending_. | 
|  | void AddProcessesPending(int processes_pending) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | processes_pending_ += processes_pending; | 
|  | } | 
|  |  | 
|  | void DecrementProcessesPending() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | --processes_pending_; | 
|  | } | 
|  |  | 
|  | // Records that we are waiting for one less histogram data from a process for | 
|  | // the given sequence number. If |received_process_group_count_| and | 
|  | // |processes_pending_| are zero, then delete the current object by calling | 
|  | // Unregister. | 
|  | void DeleteIfAllDone() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | if (processes_pending_ <= 0 && received_process_group_count_) | 
|  | RequestContext::Unregister(sequence_number_); | 
|  | } | 
|  |  | 
|  | // Register |callback| in |outstanding_requests_| map for the given | 
|  | // |sequence_number|. | 
|  | static void Register(const base::Closure& callback, int sequence_number) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | RequestContext* request = new RequestContext(callback, sequence_number); | 
|  | outstanding_requests_.Get()[sequence_number] = request; | 
|  | } | 
|  |  | 
|  | // Find the |RequestContext| in |outstanding_requests_| map for the given | 
|  | // |sequence_number|. | 
|  | static RequestContext* GetRequestContext(int sequence_number) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | RequestContextMap::iterator it = | 
|  | outstanding_requests_.Get().find(sequence_number); | 
|  | if (it == outstanding_requests_.Get().end()) | 
|  | return NULL; | 
|  |  | 
|  | RequestContext* request = it->second; | 
|  | DCHECK_EQ(sequence_number, request->sequence_number_); | 
|  | return request; | 
|  | } | 
|  |  | 
|  | // Delete the entry for the given |sequence_number| from | 
|  | // |outstanding_requests_| map. This method is called when all changes have | 
|  | // been acquired, or when the wait time expires (whichever is sooner). | 
|  | static void Unregister(int sequence_number) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | RequestContextMap::iterator it = | 
|  | outstanding_requests_.Get().find(sequence_number); | 
|  | if (it == outstanding_requests_.Get().end()) | 
|  | return; | 
|  |  | 
|  | RequestContext* request = it->second; | 
|  | DCHECK_EQ(sequence_number, request->sequence_number_); | 
|  | bool received_process_group_count = request->received_process_group_count_; | 
|  | int unresponsive_processes = request->processes_pending_; | 
|  |  | 
|  | request->callback_.Run(); | 
|  |  | 
|  | delete request; | 
|  | outstanding_requests_.Get().erase(it); | 
|  |  | 
|  | UMA_HISTOGRAM_BOOLEAN("Histogram.ReceivedProcessGroupCount", | 
|  | received_process_group_count); | 
|  | UMA_HISTOGRAM_COUNTS("Histogram.PendingProcessNotResponding", | 
|  | unresponsive_processes); | 
|  | } | 
|  |  | 
|  | // Delete all the entries in |outstanding_requests_| map. | 
|  | static void OnShutdown() { | 
|  | // Just in case we have any pending tasks, clear them out. | 
|  | while (!outstanding_requests_.Get().empty()) { | 
|  | RequestContextMap::iterator it = outstanding_requests_.Get().begin(); | 
|  | delete it->second; | 
|  | outstanding_requests_.Get().erase(it); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Requests are made to asynchronously send data to the |callback_|. | 
|  | base::Closure callback_; | 
|  |  | 
|  | // The sequence number used by the most recent update request to contact all | 
|  | // processes. | 
|  | int sequence_number_; | 
|  |  | 
|  | // Indicates if we have received all pending processes count. | 
|  | bool received_process_group_count_; | 
|  |  | 
|  | // The number of pending processes (all renderer processes and browser child | 
|  | // processes) that have not yet responded to requests. | 
|  | int processes_pending_; | 
|  |  | 
|  | // Map of all outstanding RequestContexts, from sequence_number_ to | 
|  | // RequestContext. | 
|  | static base::LazyInstance<RequestContextMap>::Leaky outstanding_requests_; | 
|  | }; | 
|  |  | 
|  | // static | 
|  | base::LazyInstance | 
|  | <HistogramSynchronizer::RequestContext::RequestContextMap>::Leaky | 
|  | HistogramSynchronizer::RequestContext::outstanding_requests_ = | 
|  | LAZY_INSTANCE_INITIALIZER; | 
|  |  | 
|  | HistogramSynchronizer::HistogramSynchronizer() | 
|  | : lock_(), | 
|  | callback_thread_(NULL), | 
|  | last_used_sequence_number_(kNeverUsableSequenceNumber), | 
|  | async_sequence_number_(kNeverUsableSequenceNumber) { | 
|  | HistogramController::GetInstance()->Register(this); | 
|  | } | 
|  |  | 
|  | HistogramSynchronizer::~HistogramSynchronizer() { | 
|  | RequestContext::OnShutdown(); | 
|  |  | 
|  | // Just in case we have any pending tasks, clear them out. | 
|  | SetCallbackTaskAndThread(NULL, base::Closure()); | 
|  | } | 
|  |  | 
|  | HistogramSynchronizer* HistogramSynchronizer::GetInstance() { | 
|  | return base::Singleton< | 
|  | HistogramSynchronizer, | 
|  | base::LeakySingletonTraits<HistogramSynchronizer>>::get(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void HistogramSynchronizer::FetchHistograms() { | 
|  | if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | 
|  | BrowserThread::PostTask( | 
|  | BrowserThread::UI, FROM_HERE, | 
|  | base::Bind(&HistogramSynchronizer::FetchHistograms)); | 
|  | return; | 
|  | } | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | HistogramSynchronizer* current_synchronizer = | 
|  | HistogramSynchronizer::GetInstance(); | 
|  | if (current_synchronizer == NULL) | 
|  | return; | 
|  |  | 
|  | current_synchronizer->RegisterAndNotifyAllProcesses( | 
|  | HistogramSynchronizer::UNKNOWN, | 
|  | base::TimeDelta::FromMinutes(1)); | 
|  | } | 
|  |  | 
|  | void FetchHistogramsAsynchronously(base::MessageLoop* callback_thread, | 
|  | const base::Closure& callback, | 
|  | base::TimeDelta wait_time) { | 
|  | HistogramSynchronizer::FetchHistogramsAsynchronously( | 
|  | callback_thread, callback, wait_time); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void HistogramSynchronizer::FetchHistogramsAsynchronously( | 
|  | base::MessageLoop* callback_thread, | 
|  | const base::Closure& callback, | 
|  | base::TimeDelta wait_time) { | 
|  | DCHECK(callback_thread != NULL); | 
|  | DCHECK(!callback.is_null()); | 
|  |  | 
|  | HistogramSynchronizer* current_synchronizer = | 
|  | HistogramSynchronizer::GetInstance(); | 
|  | current_synchronizer->SetCallbackTaskAndThread( | 
|  | callback_thread, callback); | 
|  |  | 
|  | current_synchronizer->RegisterAndNotifyAllProcesses( | 
|  | HistogramSynchronizer::ASYNC_HISTOGRAMS, wait_time); | 
|  | } | 
|  |  | 
|  | void HistogramSynchronizer::RegisterAndNotifyAllProcesses( | 
|  | ProcessHistogramRequester requester, | 
|  | base::TimeDelta wait_time) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | int sequence_number = GetNextAvailableSequenceNumber(requester); | 
|  |  | 
|  | base::Closure callback = base::Bind( | 
|  | &HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback, | 
|  | base::Unretained(this), | 
|  | sequence_number); | 
|  |  | 
|  | RequestContext::Register(callback, sequence_number); | 
|  |  | 
|  | // Get histogram data from renderer and browser child processes. | 
|  | HistogramController::GetInstance()->GetHistogramData(sequence_number); | 
|  |  | 
|  | // Post a task that would be called after waiting for wait_time.  This acts | 
|  | // as a watchdog, to cancel the requests for non-responsive processes. | 
|  | BrowserThread::PostDelayedTask( | 
|  | BrowserThread::UI, FROM_HERE, | 
|  | base::Bind(&RequestContext::Unregister, sequence_number), | 
|  | wait_time); | 
|  | } | 
|  |  | 
|  | void HistogramSynchronizer::OnPendingProcesses(int sequence_number, | 
|  | int pending_processes, | 
|  | bool end) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | RequestContext* request = RequestContext::GetRequestContext(sequence_number); | 
|  | if (!request) | 
|  | return; | 
|  | request->AddProcessesPending(pending_processes); | 
|  | request->SetReceivedProcessGroupCount(end); | 
|  | request->DeleteIfAllDone(); | 
|  | } | 
|  |  | 
|  | void HistogramSynchronizer::OnHistogramDataCollected( | 
|  | int sequence_number, | 
|  | const std::vector<std::string>& pickled_histograms) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | base::HistogramDeltaSerialization::DeserializeAndAddSamples( | 
|  | pickled_histograms); | 
|  |  | 
|  | RequestContext* request = RequestContext::GetRequestContext(sequence_number); | 
|  | if (!request) | 
|  | return; | 
|  |  | 
|  | // Delete request if we have heard back from all child processes. | 
|  | request->DecrementProcessesPending(); | 
|  | request->DeleteIfAllDone(); | 
|  | } | 
|  |  | 
|  | void HistogramSynchronizer::SetCallbackTaskAndThread( | 
|  | base::MessageLoop* callback_thread, | 
|  | const base::Closure& callback) { | 
|  | base::Closure old_callback; | 
|  | base::MessageLoop* old_thread = NULL; | 
|  | { | 
|  | base::AutoLock auto_lock(lock_); | 
|  | old_callback = callback_; | 
|  | callback_ = callback; | 
|  | old_thread = callback_thread_; | 
|  | callback_thread_ = callback_thread; | 
|  | // Prevent premature calling of our new callbacks. | 
|  | async_sequence_number_ = kNeverUsableSequenceNumber; | 
|  | } | 
|  | // Just in case there was a task pending.... | 
|  | InternalPostTask(old_thread, old_callback); | 
|  | } | 
|  |  | 
|  | void HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback( | 
|  | int sequence_number) { | 
|  | base::Closure callback; | 
|  | base::MessageLoop* thread = NULL; | 
|  | { | 
|  | base::AutoLock lock(lock_); | 
|  | if (sequence_number != async_sequence_number_) | 
|  | return; | 
|  | callback = callback_; | 
|  | thread = callback_thread_; | 
|  | callback_.Reset(); | 
|  | callback_thread_ = NULL; | 
|  | } | 
|  | InternalPostTask(thread, callback); | 
|  | } | 
|  |  | 
|  | void HistogramSynchronizer::InternalPostTask(base::MessageLoop* thread, | 
|  | const base::Closure& callback) { | 
|  | if (callback.is_null() || !thread) | 
|  | return; | 
|  | thread->task_runner()->PostTask(FROM_HERE, callback); | 
|  | } | 
|  |  | 
|  | int HistogramSynchronizer::GetNextAvailableSequenceNumber( | 
|  | ProcessHistogramRequester requester) { | 
|  | base::AutoLock auto_lock(lock_); | 
|  | ++last_used_sequence_number_; | 
|  | // Watch out for wrapping to a negative number. | 
|  | if (last_used_sequence_number_ < 0) { | 
|  | // Bypass the reserved number, which is used when a renderer spontaneously | 
|  | // decides to send some histogram data. | 
|  | last_used_sequence_number_ = | 
|  | kHistogramSynchronizerReservedSequenceNumber + 1; | 
|  | } | 
|  | DCHECK_NE(last_used_sequence_number_, | 
|  | kHistogramSynchronizerReservedSequenceNumber); | 
|  | if (requester == ASYNC_HISTOGRAMS) | 
|  | async_sequence_number_ = last_used_sequence_number_; | 
|  | return last_used_sequence_number_; | 
|  | } | 
|  |  | 
|  | }  // namespace content |