/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <sstream>

#include "android-base/file.h"
#include "android-base/logging.h"
#include "base/macros.h"
#include "base/scoped_flock.h"
#include "metrics.h"

#pragma clang diagnostic push
#pragma clang diagnostic error "-Wconversion"

namespace art {
namespace metrics {

std::string DatumName(DatumId datum) {
  switch (datum) {
#define ART_METRIC(name, Kind, ...) \
  case DatumId::k##name:  \
    return #name;
    ART_METRICS(ART_METRIC)
#undef ART_METRIC

    default:
      LOG(FATAL) << "Unknown datum id: " << static_cast<unsigned>(datum);
      UNREACHABLE();
  }
}

SessionData SessionData::CreateDefault() {
#ifdef _WIN32
  int32_t uid = kInvalidUserId;  // Windows does not support getuid();
#else
  int32_t uid = static_cast<int32_t>(getuid());
#endif

  return SessionData{
    .compilation_reason = CompilationReason::kUnknown,
    .compiler_filter = CompilerFilterReporting::kUnknown,
    .session_id = kInvalidSessionId,
    .uid = uid,
  };
}

ArtMetrics::ArtMetrics() : beginning_timestamp_ {MilliTime()}
#define ART_METRIC(name, Kind, ...) \
  , name##_ {}
ART_METRICS(ART_METRIC)
#undef ART_METRIC
{
}

void ArtMetrics::ReportAllMetricsAndResetValueMetrics(
    const std::vector<MetricsBackend*>& backends) {
  for (auto& backend : backends) {
    backend->BeginReport(MilliTime() - beginning_timestamp_);
  }

#define REPORT_METRIC(name, Kind, ...) name()->Report(backends);
  ART_EVENT_METRICS(REPORT_METRIC)
#undef REPORT_METRIC

#define REPORT_METRIC(name, Kind, ...) name()->ReportAndReset(backends);
  ART_VALUE_METRICS(REPORT_METRIC)
#undef REPORT_METRIC

  for (auto& backend : backends) {
    backend->EndReport();
  }
}

void ArtMetrics::DumpForSigQuit(std::ostream& os) {
  StringBackend backend(std::make_unique<TextFormatter>());
  ReportAllMetricsAndResetValueMetrics({&backend});
  os << backend.GetAndResetBuffer();
}

void ArtMetrics::Reset() {
  beginning_timestamp_ = MilliTime();
#define RESET_METRIC(name, ...) name##_.Reset();
  ART_METRICS(RESET_METRIC)
#undef RESET_METRIC
}

StringBackend::StringBackend(std::unique_ptr<MetricsFormatter> formatter)
    : formatter_(std::move(formatter)) {}

std::string StringBackend::GetAndResetBuffer() {
  return formatter_->GetAndResetBuffer();
}

void StringBackend::BeginOrUpdateSession(const SessionData& session_data) {
  session_data_ = session_data;
}

void StringBackend::BeginReport(uint64_t timestamp_since_start_ms) {
  formatter_->FormatBeginReport(timestamp_since_start_ms, session_data_);
}

void StringBackend::EndReport() {
  formatter_->FormatEndReport();
}

void StringBackend::ReportCounter(DatumId counter_type, uint64_t value) {
  formatter_->FormatReportCounter(counter_type, value);
}

void StringBackend::ReportHistogram(DatumId histogram_type,
                                    int64_t minimum_value_,
                                    int64_t maximum_value_,
                                    const std::vector<uint32_t>& buckets) {
  formatter_->FormatReportHistogram(histogram_type, minimum_value_, maximum_value_, buckets);
}

void TextFormatter::FormatBeginReport(uint64_t timestamp_since_start_ms,
                                      const std::optional<SessionData>& session_data) {
  os_ << "\n*** ART internal metrics ***\n";
  os_ << "  Metadata:\n";
  os_ << "    timestamp_since_start_ms: " << timestamp_since_start_ms << "\n";
  if (session_data.has_value()) {
    os_ << "    session_id: " << session_data->session_id << "\n";
    os_ << "    uid: " << session_data->uid << "\n";
    os_ << "    compilation_reason: " << CompilationReasonName(session_data->compilation_reason)
        << "\n";
    os_ << "    compiler_filter: " << CompilerFilterReportingName(session_data->compiler_filter)
        << "\n";
  }
  os_ << "  Metrics:\n";
}

void TextFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) {
  os_ << "    " << DatumName(counter_type) << ": count = " << value << "\n";
}

void TextFormatter::FormatReportHistogram(DatumId histogram_type,
                                          int64_t minimum_value_,
                                          int64_t maximum_value_,
                                          const std::vector<uint32_t>& buckets) {
  os_ << "    " << DatumName(histogram_type) << ": range = "
      << minimum_value_ << "..." << maximum_value_;
  if (!buckets.empty()) {
    os_ << ", buckets: ";
    bool first = true;
    for (const auto& count : buckets) {
      if (!first) {
        os_ << ",";
      }
      first = false;
      os_ << count;
    }
    os_ << "\n";
  } else {
    os_ << ", no buckets\n";
  }
}

void TextFormatter::FormatEndReport() {
  os_ << "*** Done dumping ART internal metrics ***\n";
}

std::string TextFormatter::GetAndResetBuffer() {
  std::string result = os_.str();
  os_.clear();
  os_.str("");
  return result;
}

void XmlFormatter::FormatBeginReport(uint64_t timestamp_millis,
                                     const std::optional<SessionData>& session_data) {
  tinyxml2::XMLElement* art_runtime_metrics = document_.NewElement("art_runtime_metrics");
  document_.InsertEndChild(art_runtime_metrics);

  art_runtime_metrics->InsertNewChildElement("version")->SetText(version.data());

  tinyxml2::XMLElement* metadata = art_runtime_metrics->InsertNewChildElement("metadata");
  metadata->InsertNewChildElement("timestamp_since_start_ms")->SetText(timestamp_millis);

  if (session_data.has_value()) {
    metadata->InsertNewChildElement("session_id")->SetText(session_data->session_id);
    metadata->InsertNewChildElement("uid")->SetText(session_data->uid);
    metadata
        ->InsertNewChildElement("compilation_reason")
        ->SetText(CompilationReasonName(session_data->compilation_reason));
    metadata
        ->InsertNewChildElement("compiler_filter")
        ->SetText(CompilerFilterReportingName(session_data->compiler_filter));
  }

  art_runtime_metrics->InsertNewChildElement("metrics");
}

void XmlFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) {
  tinyxml2::XMLElement* metrics = document_.RootElement()->FirstChildElement("metrics");

  tinyxml2::XMLElement* counter = metrics->InsertNewChildElement(DatumName(counter_type).data());
  counter->InsertNewChildElement("counter_type")->SetText("count");
  counter->InsertNewChildElement("value")->SetText(value);
}

void XmlFormatter::FormatReportHistogram(DatumId histogram_type,
                                         int64_t low_value,
                                         int64_t high_value,
                                         const std::vector<uint32_t>& buckets) {
  tinyxml2::XMLElement* metrics = document_.RootElement()->FirstChildElement("metrics");

  tinyxml2::XMLElement* histogram =
      metrics->InsertNewChildElement(DatumName(histogram_type).data());
  histogram->InsertNewChildElement("counter_type")->SetText("histogram");
  histogram->InsertNewChildElement("minimum_value")->SetText(low_value);
  histogram->InsertNewChildElement("maximum_value")->SetText(high_value);

  tinyxml2::XMLElement* buckets_element = histogram->InsertNewChildElement("buckets");
  for (const auto& count : buckets) {
    buckets_element->InsertNewChildElement("bucket")->SetText(count);
  }
}

void XmlFormatter::FormatEndReport() {}

std::string XmlFormatter::GetAndResetBuffer() {
  tinyxml2::XMLPrinter printer(/*file=*/nullptr, /*compact=*/true);
  document_.Print(&printer);
  std::string result = printer.CStr();
  document_.Clear();

  return result;
}

LogBackend::LogBackend(std::unique_ptr<MetricsFormatter> formatter,
                       android::base::LogSeverity level)
  : StringBackend{std::move(formatter)}, level_{level}
{}

void LogBackend::BeginReport(uint64_t timestamp_since_start_ms) {
  StringBackend::GetAndResetBuffer();
  StringBackend::BeginReport(timestamp_since_start_ms);
}

void LogBackend::EndReport() {
  StringBackend::EndReport();
  LOG_STREAM(level_) << StringBackend::GetAndResetBuffer();
}

FileBackend::FileBackend(std::unique_ptr<MetricsFormatter> formatter,
                         const std::string& filename)
  : StringBackend{std::move(formatter)}, filename_{filename}
{}

void FileBackend::BeginReport(uint64_t timestamp_since_start_ms) {
  StringBackend::GetAndResetBuffer();
  StringBackend::BeginReport(timestamp_since_start_ms);
}

void FileBackend::EndReport() {
  StringBackend::EndReport();
  std::string error_message;
  auto file{
      LockedFile::Open(filename_.c_str(), O_CREAT | O_WRONLY | O_APPEND, true, &error_message)};
  if (file.get() == nullptr) {
    LOG(WARNING) << "Could open metrics file '" << filename_ << "': " << error_message;
  } else {
    if (!android::base::WriteStringToFd(StringBackend::GetAndResetBuffer(), file.get()->Fd())) {
      PLOG(WARNING) << "Error writing metrics to file";
    }
  }
}

// Make sure CompilationReasonName and CompilationReasonForName are inverses.
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kError)) ==
              CompilationReason::kError);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kUnknown)) ==
              CompilationReason::kUnknown);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kFirstBoot)) ==
              CompilationReason::kFirstBoot);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kBootAfterOTA)) ==
              CompilationReason::kBootAfterOTA);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kPostBoot)) ==
              CompilationReason::kPostBoot);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kInstall)) ==
              CompilationReason::kInstall);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kInstallFast)) ==
              CompilationReason::kInstallFast);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kInstallBulk)) ==
              CompilationReason::kInstallBulk);
static_assert(
    CompilationReasonFromName(CompilationReasonName(CompilationReason::kInstallBulkSecondary)) ==
    CompilationReason::kInstallBulkSecondary);
static_assert(
    CompilationReasonFromName(CompilationReasonName(CompilationReason::kInstallBulkDowngraded)) ==
    CompilationReason::kInstallBulkDowngraded);
static_assert(CompilationReasonFromName(
                  CompilationReasonName(CompilationReason::kInstallBulkSecondaryDowngraded)) ==
              CompilationReason::kInstallBulkSecondaryDowngraded);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kBgDexopt)) ==
              CompilationReason::kBgDexopt);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kABOTA)) ==
              CompilationReason::kABOTA);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kInactive)) ==
              CompilationReason::kInactive);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kShared)) ==
              CompilationReason::kShared);
static_assert(
    CompilationReasonFromName(CompilationReasonName(CompilationReason::kInstallWithDexMetadata)) ==
    CompilationReason::kInstallWithDexMetadata);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kPrebuilt)) ==
              CompilationReason::kPrebuilt);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kCmdLine)) ==
              CompilationReason::kCmdLine);
static_assert(CompilationReasonFromName(CompilationReasonName(CompilationReason::kVdex)) ==
              CompilationReason::kVdex);
static_assert(
    CompilationReasonFromName(CompilationReasonName(CompilationReason::kBootAfterMainlineUpdate)) ==
    CompilationReason::kBootAfterMainlineUpdate);

}  // namespace metrics
}  // namespace art

#pragma clang diagnostic pop  // -Wconversion
