// 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 "base/metrics/statistics_recorder.h"

#include <memory>

#include "base/at_exit.h"
#include "base/debug/leak_annotations.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram.h"
#include "base/metrics/metrics_hashes.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/values.h"

namespace {

// Initialize histogram statistics gathering system.
base::LazyInstance<base::StatisticsRecorder>::Leaky g_statistics_recorder_ =
    LAZY_INSTANCE_INITIALIZER;

bool HistogramNameLesser(const base::HistogramBase* a,
                         const base::HistogramBase* b) {
  return a->histogram_name() < b->histogram_name();
}

}  // namespace

namespace base {

StatisticsRecorder::HistogramIterator::HistogramIterator(
    const HistogramMap::iterator& iter, bool include_persistent)
    : iter_(iter),
      include_persistent_(include_persistent) {
  // The starting location could point to a persistent histogram when such
  // is not wanted. If so, skip it.
  if (!include_persistent_ && iter_ != histograms_->end() &&
      (iter_->second->flags() & HistogramBase::kIsPersistent)) {
    // This operator will continue to skip until a non-persistent histogram
    // is found.
    operator++();
  }
}

StatisticsRecorder::HistogramIterator::HistogramIterator(
    const HistogramIterator& rhs)
    : iter_(rhs.iter_),
      include_persistent_(rhs.include_persistent_) {
}

StatisticsRecorder::HistogramIterator::~HistogramIterator() {}

StatisticsRecorder::HistogramIterator&
StatisticsRecorder::HistogramIterator::operator++() {
  const HistogramMap::iterator histograms_end = histograms_->end();
  if (iter_ == histograms_end || lock_ == NULL)
    return *this;

  base::AutoLock auto_lock(*lock_);

  for (;;) {
    ++iter_;
    if (iter_ == histograms_end)
      break;
    if (!include_persistent_ && (iter_->second->flags() &
                                 HistogramBase::kIsPersistent)) {
      continue;
    }
    break;
  }

  return *this;
}

StatisticsRecorder::~StatisticsRecorder() {
  DCHECK(lock_);
  DCHECK(histograms_);
  DCHECK(ranges_);

  // Clean out what this object created and then restore what existed before.
  Reset();
  base::AutoLock auto_lock(*lock_);
  histograms_ = existing_histograms_.release();
  callbacks_ = existing_callbacks_.release();
  ranges_ = existing_ranges_.release();
}

// static
void StatisticsRecorder::Initialize() {
  // Tests sometimes create local StatisticsRecorders in order to provide a
  // contained environment of histograms that can be later discarded. If a
  // true global instance gets created in this environment then it will
  // eventually get disconnected when the local instance destructs and
  // restores the previous state, resulting in no StatisticsRecorder at all.
  // The global lazy instance, however, will remain valid thus ensuring that
  // another never gets installed via this method. If a |histograms_| map
  // exists then assume the StatisticsRecorder is already "initialized".
  if (histograms_)
    return;

  // Ensure that an instance of the StatisticsRecorder object is created.
  g_statistics_recorder_.Get();
}

// static
bool StatisticsRecorder::IsActive() {
  if (lock_ == NULL)
    return false;
  base::AutoLock auto_lock(*lock_);
  return NULL != histograms_;
}

// static
HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
    HistogramBase* histogram) {
  // As per crbug.com/79322 the histograms are intentionally leaked, so we need
  // to annotate them. Because ANNOTATE_LEAKING_OBJECT_PTR may be used only once
  // for an object, the duplicates should not be annotated.
  // Callers are responsible for not calling RegisterOrDeleteDuplicate(ptr)
  // twice if (lock_ == NULL) || (!histograms_).
  if (lock_ == NULL) {
    ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
    return histogram;
  }

  HistogramBase* histogram_to_delete = NULL;
  HistogramBase* histogram_to_return = NULL;
  {
    base::AutoLock auto_lock(*lock_);
    if (histograms_ == NULL) {
      histogram_to_return = histogram;
    } else {
      const std::string& name = histogram->histogram_name();
      HistogramMap::iterator it = histograms_->find(name);
      if (histograms_->end() == it) {
        // The StringKey references the name within |histogram| rather than
        // making a copy.
        (*histograms_)[name] = histogram;
        ANNOTATE_LEAKING_OBJECT_PTR(histogram);  // see crbug.com/79322
        // If there are callbacks for this histogram, we set the kCallbackExists
        // flag.
        auto callback_iterator = callbacks_->find(name);
        if (callback_iterator != callbacks_->end()) {
          if (!callback_iterator->second.is_null())
            histogram->SetFlags(HistogramBase::kCallbackExists);
          else
            histogram->ClearFlags(HistogramBase::kCallbackExists);
        }
        histogram_to_return = histogram;
      } else if (histogram == it->second) {
        // The histogram was registered before.
        histogram_to_return = histogram;
      } else {
        // We already have one histogram with this name.
        DCHECK_EQ(histogram->histogram_name(),
                  it->second->histogram_name()) << "hash collision";
        histogram_to_return = it->second;
        histogram_to_delete = histogram;
      }
    }
  }
  delete histogram_to_delete;
  return histogram_to_return;
}

// static
const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
    const BucketRanges* ranges) {
  DCHECK(ranges->HasValidChecksum());
  std::unique_ptr<const BucketRanges> ranges_deleter;

  if (lock_ == NULL) {
    ANNOTATE_LEAKING_OBJECT_PTR(ranges);
    return ranges;
  }

  base::AutoLock auto_lock(*lock_);
  if (ranges_ == NULL) {
    ANNOTATE_LEAKING_OBJECT_PTR(ranges);
    return ranges;
  }

  std::list<const BucketRanges*>* checksum_matching_list;
  RangesMap::iterator ranges_it = ranges_->find(ranges->checksum());
  if (ranges_->end() == ranges_it) {
    // Add a new matching list to map.
    checksum_matching_list = new std::list<const BucketRanges*>();
    ANNOTATE_LEAKING_OBJECT_PTR(checksum_matching_list);
    (*ranges_)[ranges->checksum()] = checksum_matching_list;
  } else {
    checksum_matching_list = ranges_it->second;
  }

  for (const BucketRanges* existing_ranges : *checksum_matching_list) {
    if (existing_ranges->Equals(ranges)) {
      if (existing_ranges == ranges) {
        return ranges;
      } else {
        ranges_deleter.reset(ranges);
        return existing_ranges;
      }
    }
  }
  // We haven't found a BucketRanges which has the same ranges. Register the
  // new BucketRanges.
  checksum_matching_list->push_front(ranges);
  return ranges;
}

// static
void StatisticsRecorder::WriteHTMLGraph(const std::string& query,
                                        std::string* output) {
  if (!IsActive())
    return;

  Histograms snapshot;
  GetSnapshot(query, &snapshot);
  std::sort(snapshot.begin(), snapshot.end(), &HistogramNameLesser);
  for (const HistogramBase* histogram : snapshot) {
    histogram->WriteHTMLGraph(output);
    output->append("<br><hr><br>");
  }
}

// static
void StatisticsRecorder::WriteGraph(const std::string& query,
                                    std::string* output) {
  if (!IsActive())
    return;
  if (query.length())
    StringAppendF(output, "Collections of histograms for %s\n", query.c_str());
  else
    output->append("Collections of all histograms\n");

  Histograms snapshot;
  GetSnapshot(query, &snapshot);
  std::sort(snapshot.begin(), snapshot.end(), &HistogramNameLesser);
  for (const HistogramBase* histogram : snapshot) {
    histogram->WriteAscii(output);
    output->append("\n");
  }
}

// static
std::string StatisticsRecorder::ToJSON(const std::string& query) {
  if (!IsActive())
    return std::string();

  std::string output("{");
  if (!query.empty()) {
    output += "\"query\":";
    EscapeJSONString(query, true, &output);
    output += ",";
  }

  Histograms snapshot;
  GetSnapshot(query, &snapshot);
  output += "\"histograms\":[";
  bool first_histogram = true;
  for (const HistogramBase* histogram : snapshot) {
    if (first_histogram)
      first_histogram = false;
    else
      output += ",";
    std::string json;
    histogram->WriteJSON(&json);
    output += json;
  }
  output += "]}";
  return output;
}

// static
void StatisticsRecorder::GetHistograms(Histograms* output) {
  if (lock_ == NULL)
    return;
  base::AutoLock auto_lock(*lock_);
  if (histograms_ == NULL)
    return;

  for (const auto& entry : *histograms_) {
    output->push_back(entry.second);
  }
}

// static
void StatisticsRecorder::GetBucketRanges(
    std::vector<const BucketRanges*>* output) {
  if (lock_ == NULL)
    return;
  base::AutoLock auto_lock(*lock_);
  if (ranges_ == NULL)
    return;

  for (const auto& entry : *ranges_) {
    for (auto* range_entry : *entry.second) {
      output->push_back(range_entry);
    }
  }
}

// static
HistogramBase* StatisticsRecorder::FindHistogram(base::StringPiece name) {
  // This must be called *before* the lock is acquired below because it will
  // call back into this object to register histograms. Those called methods
  // will acquire the lock at that time.
  ImportGlobalPersistentHistograms();

  if (lock_ == NULL)
    return NULL;
  base::AutoLock auto_lock(*lock_);
  if (histograms_ == NULL)
    return NULL;

  HistogramMap::iterator it = histograms_->find(name);
  if (histograms_->end() == it)
    return NULL;
  return it->second;
}

// static
StatisticsRecorder::HistogramIterator StatisticsRecorder::begin(
    bool include_persistent) {
  DCHECK(histograms_);
  ImportGlobalPersistentHistograms();

  HistogramMap::iterator iter_begin;
  {
    base::AutoLock auto_lock(*lock_);
    iter_begin = histograms_->begin();
  }
  return HistogramIterator(iter_begin, include_persistent);
}

// static
StatisticsRecorder::HistogramIterator StatisticsRecorder::end() {
  HistogramMap::iterator iter_end;
  {
    base::AutoLock auto_lock(*lock_);
    iter_end = histograms_->end();
  }
  return HistogramIterator(iter_end, true);
}

// static
void StatisticsRecorder::InitLogOnShutdown() {
  if (lock_ == nullptr)
    return;
  base::AutoLock auto_lock(*lock_);
  g_statistics_recorder_.Get().InitLogOnShutdownWithoutLock();
}

// static
void StatisticsRecorder::GetSnapshot(const std::string& query,
                                     Histograms* snapshot) {
  if (lock_ == NULL)
    return;
  base::AutoLock auto_lock(*lock_);
  if (histograms_ == NULL)
    return;

  for (const auto& entry : *histograms_) {
    if (entry.second->histogram_name().find(query) != std::string::npos)
      snapshot->push_back(entry.second);
  }
}

// static
bool StatisticsRecorder::SetCallback(
    const std::string& name,
    const StatisticsRecorder::OnSampleCallback& cb) {
  DCHECK(!cb.is_null());
  if (lock_ == NULL)
    return false;
  base::AutoLock auto_lock(*lock_);
  if (histograms_ == NULL)
    return false;

  if (ContainsKey(*callbacks_, name))
    return false;
  callbacks_->insert(std::make_pair(name, cb));

  auto it = histograms_->find(name);
  if (it != histograms_->end())
    it->second->SetFlags(HistogramBase::kCallbackExists);

  return true;
}

// static
void StatisticsRecorder::ClearCallback(const std::string& name) {
  if (lock_ == NULL)
    return;
  base::AutoLock auto_lock(*lock_);
  if (histograms_ == NULL)
    return;

  callbacks_->erase(name);

  // We also clear the flag from the histogram (if it exists).
  auto it = histograms_->find(name);
  if (it != histograms_->end())
    it->second->ClearFlags(HistogramBase::kCallbackExists);
}

// static
StatisticsRecorder::OnSampleCallback StatisticsRecorder::FindCallback(
    const std::string& name) {
  if (lock_ == NULL)
    return OnSampleCallback();
  base::AutoLock auto_lock(*lock_);
  if (histograms_ == NULL)
    return OnSampleCallback();

  auto callback_iterator = callbacks_->find(name);
  return callback_iterator != callbacks_->end() ? callback_iterator->second
                                                : OnSampleCallback();
}

// static
size_t StatisticsRecorder::GetHistogramCount() {
  if (!lock_)
    return 0;

  base::AutoLock auto_lock(*lock_);
  if (!histograms_)
    return 0;
  return histograms_->size();
}

// static
void StatisticsRecorder::ForgetHistogramForTesting(base::StringPiece name) {
  if (histograms_)
    histograms_->erase(name);
}

// static
std::unique_ptr<StatisticsRecorder>
StatisticsRecorder::CreateTemporaryForTesting() {
  return WrapUnique(new StatisticsRecorder());
}

// static
void StatisticsRecorder::UninitializeForTesting() {
  // Stop now if it's never been initialized.
  if (lock_ == NULL || histograms_ == NULL)
    return;

  // Get the global instance and destruct it. It's held in static memory so
  // can't "delete" it; call the destructor explicitly.
  DCHECK(g_statistics_recorder_.private_instance_);
  g_statistics_recorder_.Get().~StatisticsRecorder();

  // Now the ugly part. There's no official way to release a LazyInstance once
  // created so it's necessary to clear out an internal variable which
  // shouldn't be publicly visible but is for initialization reasons.
  g_statistics_recorder_.private_instance_ = 0;
}

// static
void StatisticsRecorder::ImportGlobalPersistentHistograms() {
  if (lock_ == NULL)
    return;

  // Import histograms from known persistent storage. Histograms could have
  // been added by other processes and they must be fetched and recognized
  // locally. If the persistent memory segment is not shared between processes,
  // this call does nothing.
  GlobalHistogramAllocator* allocator = GlobalHistogramAllocator::Get();
  if (allocator)
    allocator->ImportHistogramsToStatisticsRecorder();
}

// This singleton instance should be started during the single threaded portion
// of main(), and hence it is not thread safe.  It initializes globals to
// provide support for all future calls.
StatisticsRecorder::StatisticsRecorder() {
  if (lock_ == NULL) {
    // This will leak on purpose. It's the only way to make sure we won't race
    // against the static uninitialization of the module while one of our
    // static methods relying on the lock get called at an inappropriate time
    // during the termination phase. Since it's a static data member, we will
    // leak one per process, which would be similar to the instance allocated
    // during static initialization and released only on  process termination.
    lock_ = new base::Lock;
  }

  base::AutoLock auto_lock(*lock_);

  existing_histograms_.reset(histograms_);
  existing_callbacks_.reset(callbacks_);
  existing_ranges_.reset(ranges_);

  histograms_ = new HistogramMap;
  callbacks_ = new CallbackMap;
  ranges_ = new RangesMap;

  InitLogOnShutdownWithoutLock();
}

void StatisticsRecorder::InitLogOnShutdownWithoutLock() {
  if (!vlog_initialized_ && VLOG_IS_ON(1)) {
    vlog_initialized_ = true;
    AtExitManager::RegisterCallback(&DumpHistogramsToVlog, this);
  }
}

// static
void StatisticsRecorder::Reset() {
  // If there's no lock then there is nothing to reset.
  if (!lock_)
    return;

  std::unique_ptr<HistogramMap> histograms_deleter;
  std::unique_ptr<CallbackMap> callbacks_deleter;
  std::unique_ptr<RangesMap> ranges_deleter;
  // We don't delete lock_ on purpose to avoid having to properly protect
  // against it going away after we checked for NULL in the static methods.
  {
    base::AutoLock auto_lock(*lock_);
    histograms_deleter.reset(histograms_);
    callbacks_deleter.reset(callbacks_);
    ranges_deleter.reset(ranges_);
    histograms_ = NULL;
    callbacks_ = NULL;
    ranges_ = NULL;
  }
  // We are going to leak the histograms and the ranges.
}

// static
void StatisticsRecorder::DumpHistogramsToVlog(void* instance) {
  std::string output;
  StatisticsRecorder::WriteGraph(std::string(), &output);
  VLOG(1) << output;
}


// static
StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL;
// static
StatisticsRecorder::CallbackMap* StatisticsRecorder::callbacks_ = NULL;
// static
StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = NULL;
// static
base::Lock* StatisticsRecorder::lock_ = NULL;

}  // namespace base
