#include "content/browser/metrics/histogram_synchronizer.h"
#include <utility>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/metrics/histogram_delta_serialization.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/pickle.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "components/metrics/histogram_controller.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/histogram_fetcher.h"
#include "content/public/common/content_constants.h"
namespace content {
using base::Time;
using base::TimeTicks;
namespace {
static const int kNeverUsableSequenceNumber = -2;
}
class HistogramSynchronizer::RequestContext {
public:
typedef std::map<int, RequestContext*> RequestContextMap;
RequestContext(base::OnceClosure callback, int sequence_number)
: callback_(std::move(callback)),
sequence_number_(sequence_number),
received_process_group_count_(false),
processes_pending_(0) {}
~RequestContext() {}
void SetReceivedProcessGroupCount(bool done) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
received_process_group_count_ = done;
}
void AddProcessesPending(int processes_pending) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
processes_pending_ += processes_pending;
}
void DecrementProcessesPending() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
--processes_pending_;
}
void DeleteIfAllDone() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (processes_pending_ <= 0 && received_process_group_count_)
RequestContext::Unregister(sequence_number_);
}
static void Register(base::OnceClosure callback, int sequence_number) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RequestContext* request =
new RequestContext(std::move(callback), sequence_number);
GetOutstandingRequests()[sequence_number] = request;
}
static RequestContext* GetRequestContext(int sequence_number) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto it = GetOutstandingRequests().find(sequence_number);
if (it == GetOutstandingRequests().end()) {
return nullptr;
}
RequestContext* request = it->second;
DCHECK_EQ(sequence_number, request->sequence_number_);
return request;
}
static void Unregister(int sequence_number) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto it = GetOutstandingRequests().find(sequence_number);
if (it == GetOutstandingRequests().end()) {
return;
}
RequestContext* request = it->second;
DCHECK_EQ(sequence_number, request->sequence_number_);
std::move(request->callback_).Run();
delete request;
GetOutstandingRequests().erase(it);
}
static void OnShutdown() {
while (!GetOutstandingRequests().empty()) {
auto it = GetOutstandingRequests().begin();
delete it->second;
GetOutstandingRequests().erase(it);
}
}
static RequestContextMap& GetOutstandingRequests() {
static base::NoDestructor<RequestContextMap> outstanding_requests;
return *outstanding_requests;
}
base::OnceClosure callback_;
int sequence_number_;
bool received_process_group_count_;
int processes_pending_;
};
HistogramSynchronizer::HistogramSynchronizer()
: lock_(),
last_used_sequence_number_(kNeverUsableSequenceNumber),
async_sequence_number_(kNeverUsableSequenceNumber) {
metrics::HistogramController::GetInstance()->Register(this);
}
HistogramSynchronizer::~HistogramSynchronizer() {
RequestContext::OnShutdown();
SetTaskRunnerAndCallback(nullptr, base::NullCallback());
}
HistogramSynchronizer* HistogramSynchronizer::GetInstance() {
return base::Singleton<
HistogramSynchronizer,
base::LeakySingletonTraits<HistogramSynchronizer>>::get();
}
void HistogramSynchronizer::FetchHistograms() {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&HistogramSynchronizer::FetchHistograms));
return;
}
DCHECK_CURRENTLY_ON(BrowserThread::UI);
HistogramSynchronizer* current_synchronizer =
HistogramSynchronizer::GetInstance();
if (current_synchronizer == nullptr)
return;
current_synchronizer->RegisterAndNotifyAllProcesses(
HistogramSynchronizer::UNKNOWN, base::Minutes(1));
}
void FetchHistogramsAsynchronously(scoped_refptr<base::TaskRunner> task_runner,
base::OnceClosure callback,
base::TimeDelta wait_time) {
HistogramSynchronizer::FetchHistogramsAsynchronously(
std::move(task_runner), std::move(callback), wait_time);
}
void HistogramSynchronizer::FetchHistogramsAsynchronously(
scoped_refptr<base::TaskRunner> task_runner,
base::OnceClosure callback,
base::TimeDelta wait_time) {
DCHECK(task_runner);
DCHECK(callback);
HistogramSynchronizer* current_synchronizer =
HistogramSynchronizer::GetInstance();
current_synchronizer->SetTaskRunnerAndCallback(std::move(task_runner),
std::move(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::OnceClosure callback = base::BindOnce(
&HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback,
base::Unretained(this), sequence_number);
RequestContext::Register(std::move(callback), sequence_number);
metrics::HistogramController::GetInstance()->GetHistogramData(
sequence_number);
GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE, base::BindOnce(&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;
request->DecrementProcessesPending();
request->DeleteIfAllDone();
}
void HistogramSynchronizer::SetTaskRunnerAndCallback(
scoped_refptr<base::TaskRunner> task_runner,
base::OnceClosure callback) {
base::OnceClosure old_callback;
scoped_refptr<base::TaskRunner> old_task_runner;
{
base::AutoLock auto_lock(lock_);
old_callback = std::move(callback_);
callback_ = std::move(callback);
old_task_runner = std::move(callback_task_runner_);
callback_task_runner_ = std::move(task_runner);
async_sequence_number_ = kNeverUsableSequenceNumber;
}
InternalPostTask(std::move(old_task_runner), std::move(old_callback));
}
void HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback(
int sequence_number) {
base::OnceClosure callback;
scoped_refptr<base::TaskRunner> task_runner;
{
base::AutoLock lock(lock_);
if (sequence_number != async_sequence_number_)
return;
callback = std::move(callback_);
task_runner = std::move(callback_task_runner_);
callback_.Reset();
}
InternalPostTask(std::move(task_runner), std::move(callback));
}
void HistogramSynchronizer::InternalPostTask(
scoped_refptr<base::TaskRunner> task_runner,
base::OnceClosure callback) {
if (callback.is_null() || !task_runner)
return;
task_runner->PostTask(FROM_HERE, std::move(callback));
}
int HistogramSynchronizer::GetNextAvailableSequenceNumber(
ProcessHistogramRequester requester) {
base::AutoLock auto_lock(lock_);
++last_used_sequence_number_;
if (last_used_sequence_number_ < 0) {
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_;
}
}