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

#include "testing/perf/perf_result_reporter.h"

#include <ostream>
#include <string_view>
#include <vector>

#include "base/check.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "testing/perf/perf_test.h"

namespace {

// These characters mess with either the stdout parsing or the dashboard itself.
static const base::NoDestructor<std::vector<std::string>> kInvalidCharacters{
    {"/", ":", "="}};

void CheckForInvalidCharacters(std::string_view str) {
  for (const auto& invalid : *kInvalidCharacters) {
    CHECK(!str.contains(invalid))
        << "Given invalid character for perf names '" << invalid << "'";
  }
}

}  // namespace

namespace perf_test {

PerfResultReporter::PerfResultReporter(std::string_view metric_basename,
                                       std::string_view story_name)
    : metric_basename_(metric_basename), story_name_(story_name) {
  CheckForInvalidCharacters(metric_basename_);
  CheckForInvalidCharacters(story_name_);
}

PerfResultReporter::~PerfResultReporter() = default;

void PerfResultReporter::RegisterFyiMetric(std::string_view metric_suffix,
                                           std::string_view units) {
  RegisterMetric(metric_suffix, units, false);
}

void PerfResultReporter::RegisterImportantMetric(std::string_view metric_suffix,
                                                 std::string_view units) {
  RegisterMetric(metric_suffix, units, true);
}

void PerfResultReporter::AddResult(std::string_view metric_suffix,
                                   size_t value) const {
  auto info = GetMetricInfoOrFail(metric_suffix);

  PrintResult(metric_basename_, metric_suffix, story_name_, value, info.units,
              info.important);
}

void PerfResultReporter::AddResult(std::string_view metric_suffix,
                                   double value) const {
  auto info = GetMetricInfoOrFail(metric_suffix);

  PrintResult(metric_basename_, metric_suffix, story_name_, value, info.units,
              info.important);
}

void PerfResultReporter::AddResult(std::string_view metric_suffix,
                                   std::string_view value) const {
  auto info = GetMetricInfoOrFail(metric_suffix);

  PrintResult(metric_basename_, metric_suffix, story_name_, value, info.units,
              info.important);
}

void PerfResultReporter::AddResult(std::string_view metric_suffix,
                                   base::TimeDelta value) const {
  auto info = GetMetricInfoOrFail(metric_suffix);

  // Decide what time unit to convert the TimeDelta into. Units are based on
  // the legacy units in
  // https://cs.chromium.org/chromium/src/third_party/catapult/tracing/tracing/value/legacy_unit_info.py?q=legacy_unit_info
  double time = 0;
  if (info.units == "seconds") {
    time = value.InSecondsF();
  } else if (info.units == "ms" || info.units == "milliseconds") {
    time = value.InMillisecondsF();
  } else if (info.units == "us") {
    time = value.InMicrosecondsF();
  } else if (info.units == "ns") {
    time = value.InNanoseconds();
  } else {
    NOTREACHED() << "Attempted to use AddResult with a TimeDelta when "
                 << "registered unit for metric " << metric_suffix << " is "
                 << info.units;
  }

  PrintResult(metric_basename_, metric_suffix, story_name_, time, info.units,
              info.important);
}

void PerfResultReporter::AddResultList(std::string_view metric_suffix,
                                       std::string_view values) const {
  auto info = GetMetricInfoOrFail(metric_suffix);

  PrintResultList(metric_basename_, metric_suffix, story_name_, values,
                  info.units, info.important);
}

void PerfResultReporter::AddResultMeanAndError(
    std::string_view metric_suffix,
    std::string_view mean_and_error) {
  auto info = GetMetricInfoOrFail(metric_suffix);

  PrintResultMeanAndError(metric_basename_, metric_suffix, story_name_,
                          mean_and_error, info.units, info.important);
}

bool PerfResultReporter::GetMetricInfo(std::string_view metric_suffix,
                                       MetricInfo* out) const {
  auto iter = metric_map_.find(metric_suffix);
  if (iter == metric_map_.end()) {
    return false;
  }

  *out = iter->second;
  return true;
}

void PerfResultReporter::RegisterMetric(std::string_view metric_suffix,
                                        std::string_view units,
                                        bool important) {
  CheckForInvalidCharacters(metric_suffix);
  CHECK(metric_map_.count(metric_suffix) == 0);
  metric_map_.emplace(metric_suffix, MetricInfo{std::string(units), important});
}

MetricInfo PerfResultReporter::GetMetricInfoOrFail(
    std::string_view metric_suffix) const {
  MetricInfo info;
  CHECK(GetMetricInfo(metric_suffix, &info))
      << "Attempted to use unregistered metric " << metric_suffix;
  return info;
}

}  // namespace perf_test
