// Copyright (c) 2010 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 "chrome/browser/metrics/histogram_synchronizer.h"

#include "base/metrics/histogram.h"
#include "base/logging.h"
#include "base/threading/thread.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/render_messages.h"
#include "content/browser/browser_thread.h"
#include "content/browser/renderer_host/render_process_host.h"

using base::Time;
using base::TimeDelta;
using base::TimeTicks;

// 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;

HistogramSynchronizer::HistogramSynchronizer()
  : lock_(),
    received_all_renderer_histograms_(&lock_),
    callback_task_(NULL),
    callback_thread_(NULL),
    last_used_sequence_number_(kNeverUsableSequenceNumber),
    async_sequence_number_(kNeverUsableSequenceNumber),
    async_renderers_pending_(0),
    synchronous_sequence_number_(kNeverUsableSequenceNumber),
    synchronous_renderers_pending_(0) {
  DCHECK(histogram_synchronizer_ == NULL);
  histogram_synchronizer_ = this;
}

HistogramSynchronizer::~HistogramSynchronizer() {
  // Just in case we have any pending tasks, clear them out.
  SetCallbackTaskAndThread(NULL, NULL);
  histogram_synchronizer_ = NULL;
}

// static
HistogramSynchronizer* HistogramSynchronizer::CurrentSynchronizer() {
  DCHECK(histogram_synchronizer_ != NULL);
  return histogram_synchronizer_;
}

void HistogramSynchronizer::FetchRendererHistogramsSynchronously(
    TimeDelta wait_time) {
  NotifyAllRenderers(SYNCHRONOUS_HISTOGRAMS);

  TimeTicks start = TimeTicks::Now();
  TimeTicks end_time = start + wait_time;
  int unresponsive_renderer_count;
  {
    base::AutoLock auto_lock(lock_);
    while (synchronous_renderers_pending_ > 0 && TimeTicks::Now() < end_time) {
      wait_time = end_time - TimeTicks::Now();
      received_all_renderer_histograms_.TimedWait(wait_time);
    }
    unresponsive_renderer_count = synchronous_renderers_pending_;
    synchronous_renderers_pending_ = 0;
    synchronous_sequence_number_ = kNeverUsableSequenceNumber;
  }
  UMA_HISTOGRAM_COUNTS("Histogram.RendersNotRespondingSynchronous",
                       unresponsive_renderer_count);
  if (!unresponsive_renderer_count)
    UMA_HISTOGRAM_TIMES("Histogram.FetchRendererHistogramsSynchronously",
                        TimeTicks::Now() - start);
}

// static
void HistogramSynchronizer::FetchRendererHistogramsAsynchronously(
    MessageLoop* callback_thread,
    Task* callback_task,
    int wait_time) {
  DCHECK(callback_thread != NULL);
  DCHECK(callback_task != NULL);

  HistogramSynchronizer* current_synchronizer = CurrentSynchronizer();

  if (current_synchronizer == NULL) {
    // System teardown is happening.
    callback_thread->PostTask(FROM_HERE, callback_task);
    return;
  }

  current_synchronizer->SetCallbackTaskAndThread(callback_thread,
                                                 callback_task);

  int sequence_number =
      current_synchronizer->NotifyAllRenderers(ASYNC_HISTOGRAMS);

  // Post a task that would be called after waiting for wait_time.  This acts
  // as a watchdog, to ensure that a non-responsive renderer won't block us from
  // making the callback.
  BrowserThread::PostDelayedTask(
      BrowserThread::UI, FROM_HERE,
      NewRunnableMethod(
          current_synchronizer,
          &HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback,
          sequence_number),
      wait_time);
}

// static
void HistogramSynchronizer::DeserializeHistogramList(
    int sequence_number,
    const std::vector<std::string>& histograms) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  for (std::vector<std::string>::const_iterator it = histograms.begin();
       it < histograms.end();
       ++it) {
    base::Histogram::DeserializeHistogramInfo(*it);
  }

  HistogramSynchronizer* current_synchronizer = CurrentSynchronizer();
  if (current_synchronizer == NULL)
    return;

  // Record that we have received a histogram from renderer process.
  current_synchronizer->DecrementPendingRenderers(sequence_number);
}

int HistogramSynchronizer::NotifyAllRenderers(
    RendererHistogramRequester requester) {
  // To iterate over RenderProcessHosts, or to send messages to the hosts, we
  // need to be on the UI thread.
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  int notification_count = 0;
  for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
       !it.IsAtEnd(); it.Advance())
     ++notification_count;

  int sequence_number = GetNextAvailableSequenceNumber(requester,
                                                       notification_count);
  for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator());
       !it.IsAtEnd(); it.Advance()) {
    if (!it.GetCurrentValue()->Send(
        new ViewMsg_GetRendererHistograms(sequence_number)))
      DecrementPendingRenderers(sequence_number);
  }

  return sequence_number;
}

void HistogramSynchronizer::DecrementPendingRenderers(int sequence_number) {
  bool synchronous_completed = false;
  bool asynchronous_completed = false;

  {
    base::AutoLock auto_lock(lock_);
    if (sequence_number == async_sequence_number_) {
      if (--async_renderers_pending_ <= 0)
        asynchronous_completed = true;
    } else if (sequence_number == synchronous_sequence_number_) {
      if (--synchronous_renderers_pending_ <= 0)
        synchronous_completed = true;
    }
  }

  if (asynchronous_completed)
    ForceHistogramSynchronizationDoneCallback(sequence_number);
  else if (synchronous_completed)
    received_all_renderer_histograms_.Signal();
}

void HistogramSynchronizer::SetCallbackTaskAndThread(
    MessageLoop* callback_thread,
    Task* callback_task) {
  Task* old_task = NULL;
  MessageLoop* old_thread = NULL;
  TimeTicks old_start_time;
  int unresponsive_renderers;
  const TimeTicks now = TimeTicks::Now();
  {
    base::AutoLock auto_lock(lock_);
    old_task = callback_task_;
    callback_task_ = callback_task;
    old_thread = callback_thread_;
    callback_thread_ = callback_thread;
    unresponsive_renderers = async_renderers_pending_;
    old_start_time = async_callback_start_time_;
    async_callback_start_time_ = now;
    // Prevent premature calling of our new callbacks.
    async_sequence_number_ = kNeverUsableSequenceNumber;
  }
  // Just in case there was a task pending....
  InternalPostTask(old_thread, old_task, unresponsive_renderers,
                   old_start_time);
}

void HistogramSynchronizer::ForceHistogramSynchronizationDoneCallback(
    int sequence_number) {
  Task* task = NULL;
  MessageLoop* thread = NULL;
  TimeTicks started;
  int unresponsive_renderers;
  {
    base::AutoLock lock(lock_);
    if (sequence_number != async_sequence_number_)
      return;
    task = callback_task_;
    thread = callback_thread_;
    callback_task_ = NULL;
    callback_thread_ = NULL;
    started = async_callback_start_time_;
    unresponsive_renderers = async_renderers_pending_;
  }
  InternalPostTask(thread, task, unresponsive_renderers, started);
}

void HistogramSynchronizer::InternalPostTask(MessageLoop* thread, Task* task,
                                             int unresponsive_renderers,
                                             const base::TimeTicks& started) {
  if (!task || !thread)
    return;
  UMA_HISTOGRAM_COUNTS("Histogram.RendersNotRespondingAsynchronous",
                       unresponsive_renderers);
  if (!unresponsive_renderers) {
    UMA_HISTOGRAM_TIMES("Histogram.FetchRendererHistogramsAsynchronously",
                        TimeTicks::Now() - started);
  }

  thread->PostTask(FROM_HERE, task);
}

int HistogramSynchronizer::GetNextAvailableSequenceNumber(
    RendererHistogramRequester requester,
    int renderer_count) {
  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_ =
        chrome::kHistogramSynchronizerReservedSequenceNumber + 1;
  }
  DCHECK_NE(last_used_sequence_number_,
            chrome::kHistogramSynchronizerReservedSequenceNumber);
  if (requester == ASYNC_HISTOGRAMS) {
    async_sequence_number_ = last_used_sequence_number_;
    async_renderers_pending_ = renderer_count;
  } else if (requester == SYNCHRONOUS_HISTOGRAMS) {
    synchronous_sequence_number_ = last_used_sequence_number_;
    synchronous_renderers_pending_ = renderer_count;
  }
  return last_used_sequence_number_;
}

// static
HistogramSynchronizer* HistogramSynchronizer::histogram_synchronizer_ = NULL;