// 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/logging.h" #include "base/metrics/histogram.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 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(BrowserThread::CurrentlyOn(BrowserThread::UI)); received_process_group_count_ = done; } // Methods for book keeping of processes_pending_. void AddProcessesPending(int processes_pending) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); processes_pending_ += processes_pending; } void DecrementProcessesPending() { DCHECK(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(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::Leaky outstanding_requests_; }; // static base::LazyInstance ::Leaky HistogramSynchronizer::RequestContext::outstanding_requests_ = LAZY_INSTANCE_INITIALIZER; HistogramSynchronizer::HistogramSynchronizer() : lock_(), callback_thread_(NULL), last_used_sequence_number_(kNeverUsableSequenceNumber), async_sequence_number_(kNeverUsableSequenceNumber) { content::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 Singleton >::get(); } // static void HistogramSynchronizer::FetchHistograms() { if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&HistogramSynchronizer::FetchHistograms)); return; } DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); HistogramSynchronizer* current_synchronizer = HistogramSynchronizer::GetInstance(); if (current_synchronizer == NULL) return; current_synchronizer->RegisterAndNotifyAllProcesses( HistogramSynchronizer::UNKNOWN, base::TimeDelta::FromMinutes(1)); } void FetchHistogramsAsynchronously(MessageLoop* callback_thread, const base::Closure& callback, base::TimeDelta wait_time) { HistogramSynchronizer::FetchHistogramsAsynchronously( callback_thread, callback, wait_time); } // static void HistogramSynchronizer::FetchHistogramsAsynchronously( 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(BrowserThread::CurrentlyOn(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. content::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(BrowserThread::CurrentlyOn(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& pickled_histograms) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); RequestContext* request = RequestContext::GetRequestContext(sequence_number); for (std::vector::const_iterator it = pickled_histograms.begin(); it < pickled_histograms.end(); ++it) { base::Histogram::DeserializeHistogramInfo(*it); } if (!request) return; // Delete request if we have heard back from all child processes. request->DecrementProcessesPending(); request->DeleteIfAllDone(); } void HistogramSynchronizer::SetCallbackTaskAndThread( MessageLoop* callback_thread, const base::Closure& callback) { base::Closure old_callback; 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; 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(MessageLoop* thread, const base::Closure& callback) { if (callback.is_null() || !thread) return; thread->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