blob: 1dc8fa75db483be3d1b9186f8b10169365e42be8 [file] [log] [blame]
// 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 "components/metrics/profiler/tracking_synchronizer.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/tracked_objects.h"
#include "components/metrics/profiler/tracking_synchronizer_observer.h"
#include "components/variations/variations_associated_data.h"
using base::TimeTicks;
namespace metrics {
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.
const int kNeverUsableSequenceNumber = -2;
// This singleton instance should be started during the single threaded
// portion of main(). It initializes globals to provide support for all future
// calls. This object is created on the UI thread, and it is destroyed after
// all the other threads have gone away. As a result, it is ok to call it
// from the UI thread, or for about:profiler.
static TrackingSynchronizer* g_tracking_synchronizer = NULL;
} // namespace
// The "RequestContext" structure describes an individual request received
// from the UI. All methods are accessible on UI thread.
class TrackingSynchronizer::RequestContext {
public:
// A map from sequence_number_ to the actual RequestContexts.
typedef std::map<int, RequestContext*> RequestContextMap;
RequestContext(
const base::WeakPtr<TrackingSynchronizerObserver>& callback_object,
int sequence_number)
: callback_object_(callback_object),
sequence_number_(sequence_number),
received_process_group_count_(0),
processes_pending_(0) {
}
~RequestContext() {}
void SetReceivedProcessGroupCount(bool done) {
DCHECK(thread_checker_.CalledOnValidThread());
received_process_group_count_ = done;
}
// Methods for book keeping of processes_pending_.
void IncrementProcessesPending() {
DCHECK(thread_checker_.CalledOnValidThread());
++processes_pending_;
}
void AddProcessesPending(int processes_pending) {
DCHECK(thread_checker_.CalledOnValidThread());
processes_pending_ += processes_pending;
}
void DecrementProcessesPending() {
DCHECK(thread_checker_.CalledOnValidThread());
--processes_pending_;
}
// Records that we are waiting for one less tracking 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(thread_checker_.CalledOnValidThread());
if (processes_pending_ <= 0 && received_process_group_count_)
RequestContext::Unregister(sequence_number_);
}
// Register |callback_object| in |outstanding_requests_| map for the given
// |sequence_number|.
static RequestContext* Register(
int sequence_number,
const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) {
RequestContext* request = new RequestContext(
callback_object, sequence_number);
outstanding_requests_.Get()[sequence_number] = request;
return request;
}
// Find the |RequestContext| in |outstanding_requests_| map for the given
// |sequence_number|.
static RequestContext* GetRequestContext(int sequence_number) {
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) {
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_;
if (request->callback_object_.get())
request->callback_object_->FinishedReceivingProfilerData();
delete request;
outstanding_requests_.Get().erase(it);
UMA_HISTOGRAM_BOOLEAN("Profiling.ReceivedProcessGroupCount",
received_process_group_count);
UMA_HISTOGRAM_COUNTS("Profiling.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);
}
}
// Used to verify that methods are called on the UI thread (the thread on
// which this object was created).
base::ThreadChecker thread_checker_;
// Requests are made to asynchronously send data to the |callback_object_|.
base::WeakPtr<TrackingSynchronizerObserver> callback_object_;
// 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 (browser, 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
<TrackingSynchronizer::RequestContext::RequestContextMap>::Leaky
TrackingSynchronizer::RequestContext::outstanding_requests_ =
LAZY_INSTANCE_INITIALIZER;
// TrackingSynchronizer methods and members.
TrackingSynchronizer::TrackingSynchronizer(
std::unique_ptr<base::TickClock> clock,
const TrackingSynchronizerDelegateFactory& delegate_factory)
: last_used_sequence_number_(kNeverUsableSequenceNumber),
clock_(std::move(clock)) {
DCHECK(!g_tracking_synchronizer);
g_tracking_synchronizer = this;
phase_start_times_.push_back(clock_->NowTicks());
delegate_ = delegate_factory.Run(this);
}
TrackingSynchronizer::~TrackingSynchronizer() {
// Just in case we have any pending tasks, clear them out.
RequestContext::OnShutdown();
g_tracking_synchronizer = NULL;
}
// static
void TrackingSynchronizer::FetchProfilerDataAsynchronously(
const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) {
if (!g_tracking_synchronizer) {
// System teardown is happening.
return;
}
int sequence_number = g_tracking_synchronizer->RegisterAndNotifyAllProcesses(
callback_object);
// 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.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&RequestContext::Unregister, sequence_number),
base::TimeDelta::FromMinutes(1));
}
// static
void TrackingSynchronizer::OnProfilingPhaseCompleted(
ProfilerEventProto::ProfilerEvent profiling_event) {
if (!g_tracking_synchronizer) {
// System teardown is happening.
return;
}
g_tracking_synchronizer->NotifyAllProcessesOfProfilingPhaseCompletion(
profiling_event);
}
void TrackingSynchronizer::OnPendingProcesses(int sequence_number,
int pending_processes,
bool end) {
DCHECK(thread_checker_.CalledOnValidThread());
RequestContext* request = RequestContext::GetRequestContext(sequence_number);
if (!request)
return;
request->AddProcessesPending(pending_processes);
request->SetReceivedProcessGroupCount(end);
request->DeleteIfAllDone();
}
void TrackingSynchronizer::OnProfilerDataCollected(
int sequence_number,
const tracked_objects::ProcessDataSnapshot& profiler_data,
ProfilerEventProto::TrackedObject::ProcessType process_type) {
DCHECK(thread_checker_.CalledOnValidThread());
DecrementPendingProcessesAndSendData(sequence_number, profiler_data,
process_type);
}
int TrackingSynchronizer::RegisterAndNotifyAllProcesses(
const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) {
DCHECK(thread_checker_.CalledOnValidThread());
int sequence_number = GetNextAvailableSequenceNumber();
RequestContext* request =
RequestContext::Register(sequence_number, callback_object);
// Increment pending process count for sending browser's profiler data.
request->IncrementProcessesPending();
const int current_profiling_phase = phase_completion_events_sequence_.size();
delegate_->GetProfilerDataForChildProcesses(sequence_number,
current_profiling_phase);
// Send process data snapshot from browser process.
tracked_objects::ProcessDataSnapshot process_data_snapshot;
tracked_objects::ThreadData::Snapshot(current_profiling_phase,
&process_data_snapshot);
DecrementPendingProcessesAndSendData(
sequence_number, process_data_snapshot,
ProfilerEventProto::TrackedObject::BROWSER);
return sequence_number;
}
void TrackingSynchronizer::RegisterPhaseCompletion(
ProfilerEventProto::ProfilerEvent profiling_event) {
phase_completion_events_sequence_.push_back(profiling_event);
phase_start_times_.push_back(clock_->NowTicks());
}
void TrackingSynchronizer::NotifyAllProcessesOfProfilingPhaseCompletion(
ProfilerEventProto::ProfilerEvent profiling_event) {
DCHECK(thread_checker_.CalledOnValidThread());
if (variations::GetVariationParamValue("UMALogPhasedProfiling",
"send_split_profiles") == "false") {
return;
}
int profiling_phase = phase_completion_events_sequence_.size();
// If you hit this check, stop and think. You just added a new profiling
// phase. Each profiling phase takes additional memory in DeathData's list of
// snapshots. We cannot grow it indefinitely. Consider collapsing older phases
// after they were sent to UMA server, or other ways to save memory.
DCHECK_LT(profiling_phase, 1);
RegisterPhaseCompletion(profiling_event);
delegate_->OnProfilingPhaseCompleted(profiling_phase);
// Notify browser process.
tracked_objects::ThreadData::OnProfilingPhaseCompleted(profiling_phase);
}
void TrackingSynchronizer::SendData(
const tracked_objects::ProcessDataSnapshot& profiler_data,
ProfilerEventProto::TrackedObject::ProcessType process_type,
TrackingSynchronizerObserver* observer) const {
// We are going to loop though past profiling phases and notify the request
// about each phase that is contained in profiler_data. past_events
// will track the set of past profiling events as we go.
ProfilerEvents past_events;
// Go through all completed phases, and through the current one. The current
// one is not in phase_completion_events_sequence_, but note the <=
// comparison.
for (size_t phase = 0; phase <= phase_completion_events_sequence_.size();
++phase) {
auto it = profiler_data.phased_snapshots.find(phase);
if (it != profiler_data.phased_snapshots.end()) {
// If the phase is contained in the received snapshot, notify the
// request.
const base::TimeTicks phase_start = phase_start_times_[phase];
const base::TimeTicks phase_finish = phase + 1 < phase_start_times_.size()
? phase_start_times_[phase + 1]
: clock_->NowTicks();
observer->ReceivedProfilerData(
ProfilerDataAttributes(phase, profiler_data.process_id, process_type,
phase_start, phase_finish),
it->second, past_events);
}
if (phase < phase_completion_events_sequence_.size()) {
past_events.push_back(phase_completion_events_sequence_[phase]);
}
}
}
void TrackingSynchronizer::DecrementPendingProcessesAndSendData(
int sequence_number,
const tracked_objects::ProcessDataSnapshot& profiler_data,
ProfilerEventProto::TrackedObject::ProcessType process_type) {
DCHECK(thread_checker_.CalledOnValidThread());
RequestContext* request = RequestContext::GetRequestContext(sequence_number);
if (!request)
return;
TrackingSynchronizerObserver* observer = request->callback_object_.get();
if (observer)
SendData(profiler_data, process_type, observer);
// Delete request if we have heard back from all child processes.
request->DecrementProcessesPending();
request->DeleteIfAllDone();
}
int TrackingSynchronizer::GetNextAvailableSequenceNumber() {
DCHECK(thread_checker_.CalledOnValidThread());
++last_used_sequence_number_;
// Watch out for wrapping to a negative number.
if (last_used_sequence_number_ < 0)
last_used_sequence_number_ = 1;
return last_used_sequence_number_;
}
} // namespace metrics