// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/metrics/histogram_base.h"

#include <limits.h>

#include <memory>
#include <set>
#include <string_view>
#include <utility>

#include "base/check_op.h"
#include "base/functional/callback.h"
#include "base/json/json_string_value_serializer.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/sparse_histogram.h"
#include "base/metrics/statistics_recorder.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/pickle.h"
#include "base/process/process_handle.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/trace_event/histogram_scope.h"  // no-presubmit-check
#include "base/values.h"

namespace base {

std::string HistogramTypeToString(HistogramType type) {
  switch (type) {
    case HISTOGRAM:
      return "HISTOGRAM";
    case LINEAR_HISTOGRAM:
      return "LINEAR_HISTOGRAM";
    case BOOLEAN_HISTOGRAM:
      return "BOOLEAN_HISTOGRAM";
    case CUSTOM_HISTOGRAM:
      return "CUSTOM_HISTOGRAM";
    case SPARSE_HISTOGRAM:
      return "SPARSE_HISTOGRAM";
    case DUMMY_HISTOGRAM:
      return "DUMMY_HISTOGRAM";
  }
  NOTREACHED();
}

HistogramBase::CountAndBucketData::CountAndBucketData(Count32 count,
                                                      int64_t sum,
                                                      ListValue buckets)
    : count(count), sum(sum), buckets(std::move(buckets)) {}

HistogramBase::CountAndBucketData::~CountAndBucketData() = default;

HistogramBase::CountAndBucketData::CountAndBucketData(
    CountAndBucketData&& other) = default;

HistogramBase::CountAndBucketData& HistogramBase::CountAndBucketData::operator=(
    CountAndBucketData&& other) = default;

const HistogramBase::Sample32 HistogramBase::kSampleType_MAX = INT_MAX;

// static
HistogramBase* HistogramBase::DeserializeInfo(
    PickleIterator* iter,
    HistogramBase::NameMapper mapper) {
  int type;
  if (!iter->ReadInt(&type)) {
    return nullptr;
  }

  HistogramBase* result;
  switch (type) {
    case HISTOGRAM:
      result = Histogram::DeserializeInfoImpl(iter, mapper);
      break;
    case LINEAR_HISTOGRAM:
      result = LinearHistogram::DeserializeInfoImpl(iter, mapper);
      break;
    case BOOLEAN_HISTOGRAM:
      result = BooleanHistogram::DeserializeInfoImpl(iter, mapper);
      break;
    case CUSTOM_HISTOGRAM:
      result = CustomHistogram::DeserializeInfoImpl(iter, mapper);
      break;
    case SPARSE_HISTOGRAM:
      result = SparseHistogram::DeserializeInfoImpl(iter, mapper);
      break;
    default:
      return nullptr;
  }

  if (result != nullptr &&
      result->GetHistogramType() != static_cast<HistogramType>(type)) {
    // If there's a type mismatch, this could be a DummyHistogram returned by
    // FactoryGetInternal() due to invalid arguments. In this case, return
    // nullptr to indicate an error.
    return nullptr;
  }
  return result;
}

HistogramBase::HistogramBase(DurableStringView name)
    : histogram_name_(name->data()),
      histogram_name_length_(base::saturated_cast<uint16_t>(name->length())),
      flags_(kNoFlags) {
  DCHECK(!name->empty());
  DCHECK_LT(name->length(), static_cast<size_t>(UINT16_MAX));
}

HistogramBase::~HistogramBase() = default;

void HistogramBase::CheckName(std::string_view name) const {
  DCHECK_EQ(histogram_name(), name)
      << "Provided histogram name doesn't match instance name. Are you using a "
         "dynamic string in a macro?";
}

void HistogramBase::SetFlags(int32_t flags) {
  flags_.fetch_or(static_cast<uint16_t>(flags), std::memory_order_relaxed);
}

void HistogramBase::ClearFlags(int32_t flags) {
  flags_.fetch_and(static_cast<uint16_t>(~flags), std::memory_order_relaxed);
}

bool HistogramBase::HasFlags(int32_t flags) const {
  // Check this->flags() is a superset of |flags|, i.e. every flag in |flags| is
  // included.
  return (this->flags() & flags) == flags;
}

void HistogramBase::AddTimeMillisecondsGranularity(TimeDelta time) {
  Add(saturated_cast<Sample32>(time.InMilliseconds()));
}

void HistogramBase::AddTimeMicrosecondsGranularity(TimeDelta time) {
  // Intentionally drop high-resolution reports on clients with low-resolution
  // clocks. High-resolution metrics cannot make use of low-resolution data and
  // reporting it merely adds noise to the metric. https://crbug.com/807615#c16
  if (TimeTicks::IsHighResolution()) {
    Add(saturated_cast<Sample32>(time.InMicroseconds()));
  }
}

void HistogramBase::AddBoolean(bool value) {
  Add(value ? 1 : 0);
}

void HistogramBase::SerializeInfo(Pickle* pickle) const {
  pickle->WriteInt(GetHistogramType());
  SerializeInfoImpl(pickle);
}

uint32_t HistogramBase::FindCorruption(const HistogramSamples& samples) const {
  // Not supported by default.
  return NO_INCONSISTENCIES;
}

void HistogramBase::WriteJSON(std::string* output,
                              JSONVerbosityLevel verbosity_level) const {
  CountAndBucketData count_and_bucket_data = GetCountAndBucketData();
  DictValue parameters = GetParameters();

  JSONStringValueSerializer serializer(output);
  DictValue root;
  root.Set("name", histogram_name());
  root.Set("count", count_and_bucket_data.count);
  root.Set("sum", static_cast<double>(count_and_bucket_data.sum));
  root.Set("flags", flags());
  root.Set("params", std::move(parameters));
  if (verbosity_level != JSON_VERBOSITY_LEVEL_OMIT_BUCKETS) {
    root.Set("buckets", std::move(count_and_bucket_data.buckets));
  }
  root.Set("pid", static_cast<int>(GetUniqueIdForProcess().GetUnsafeValue()));
  serializer.Serialize(root);
}

void HistogramBase::FindAndRunCallbacks(HistogramBase::Sample32 sample) const {
  auto event_id = trace_event::HistogramScope::GetFlowId();
  StatisticsRecorder::GlobalSampleCallback global_sample_callback =
      StatisticsRecorder::global_sample_callback();
  if (global_sample_callback) {
    global_sample_callback(histogram_name(), name_hash(), sample, event_id);
  }

  // We check the flag first since it is very cheap and we can avoid the
  // function call and lock overhead of FindAndRunHistogramCallbacks().
  if (!HasFlags(kCallbackExists)) {
    return;
  }

  StatisticsRecorder::FindAndRunHistogramCallbacks(
      base::PassKey<HistogramBase>(), histogram_name(), name_hash(), sample,
      event_id);
}

HistogramBase::CountAndBucketData HistogramBase::GetCountAndBucketData() const {
  std::unique_ptr<HistogramSamples> snapshot = SnapshotSamples();
  Count32 count = snapshot->TotalCount();
  int64_t sum = snapshot->sum();
  std::unique_ptr<SampleCountIterator> it = snapshot->Iterator();

  ListValue buckets;
  while (!it->Done()) {
    Sample32 bucket_min;
    int64_t bucket_max;
    Count32 bucket_count;
    it->Get(&bucket_min, &bucket_max, &bucket_count);

    DictValue bucket_value;
    bucket_value.Set("low", bucket_min);
    // TODO(crbug.com/40228085): Make base::Value able to hold int64_t and
    // remove this cast.
    bucket_value.Set("high", static_cast<int>(bucket_max));
    bucket_value.Set("count", bucket_count);
    buckets.Append(std::move(bucket_value));
    it->Next();
  }

  return CountAndBucketData(count, sum, std::move(buckets));
}

void HistogramBase::WriteAsciiBucketGraph(double x_count,
                                          int line_length,
                                          std::string* output) const {
  int x_remainder = line_length - x_count;

  while (0 < x_count--) {
    output->append("-");
  }
  output->append("O");
  while (0 < x_remainder--) {
    output->append(" ");
  }
}

const std::string HistogramBase::GetSimpleAsciiBucketRange(
    Sample32 sample) const {
  return StringPrintf("%d", sample);
}

void HistogramBase::WriteAsciiBucketValue(Count32 current,
                                          double scaled_sum,
                                          std::string* output) const {
  StringAppendF(output, " (%d = %3.1f%%)", current, current / scaled_sum);
}

void HistogramBase::WriteAscii(std::string* output) const {
  base::DictValue graph_dict = ToGraphDict();
  output->append(*graph_dict.FindString("header"));
  output->append("\n");
  output->append(*graph_dict.FindString("body"));
}

// static
DurableStringView HistogramBase::GetPermanentName(std::string_view name) {
  // A set of histogram names that provides the "permanent" lifetime required by
  // histogram objects for those strings that are not already code constants or
  // held in persistent memory. The container used for `permanent_names` MUST
  // support pointer-stability for its keys, due to small-string-optimization
  // in std::string.
  static base::NoDestructor<std::set<std::string, std::less<>>> permanent_names;
  static base::NoDestructor<Lock> permanent_names_lock;

  AutoLock lock(*permanent_names_lock);
  auto it = permanent_names->lower_bound(name);
  if (it == permanent_names->end() || *it != name) {
    it = permanent_names->emplace_hint(it, name);
  }
  return DurableStringView(*it);
}

}  // namespace base
