// Copyright 2020 the V8 project 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 "src/builtins/profile-data-reader.h"

#include <fstream>
#include <iostream>
#include <unordered_map>

#include "src/base/lazy-instance.h"
#include "src/flags/flags.h"
#include "src/utils/utils.h"

namespace v8 {
namespace internal {

namespace {

class ProfileDataFromFileInternal : public ProfileDataFromFile {
 public:
  bool hash_has_value() const { return hash_has_value_; }

  void set_hash(int hash) {
    hash_ = hash;
    hash_has_value_ = true;
  }

  void AddCountToBlock(size_t block_id, uint32_t count) {
    if (block_counts_by_id_.size() <= block_id) {
      // std::vector initializes new data to zero when resizing.
      block_counts_by_id_.resize(block_id + 1);
    }
    block_counts_by_id_[block_id] += count;
  }

 private:
  bool hash_has_value_ = false;
};

const std::unordered_map<std::string, ProfileDataFromFileInternal>&
EnsureInitProfileData() {
  static base::LeakyObject<
      std::unordered_map<std::string, ProfileDataFromFileInternal>>
      data;
  static bool initialized = false;

  if (initialized) return *data.get();
  initialized = true;
  const char* filename = FLAG_turbo_profiling_log_file;
  if (filename == nullptr) return *data.get();
  std::ifstream file(filename);
  CHECK_WITH_MSG(file.good(), "Can't read log file");
  for (std::string line; std::getline(file, line);) {
    std::string token;
    std::istringstream line_stream(line);
    if (!std::getline(line_stream, token, ',')) continue;
    if (token == ProfileDataFromFileConstants::kBlockCounterMarker) {
      // Any line starting with kBlockCounterMarker is a block usage count.
      // As defined by Logger::BasicBlockCounterEvent, the format is:
      //   literal kBlockCounterMarker , builtin_name , block_id , usage_count
      std::string builtin_name;
      CHECK(std::getline(line_stream, builtin_name, ','));
      CHECK(std::getline(line_stream, token, ','));
      char* end = nullptr;
      uint32_t id = static_cast<uint32_t>(strtoul(token.c_str(), &end, 0));
      CHECK(errno == 0 && end != token.c_str());
      std::getline(line_stream, token, ',');
      CHECK(line_stream.eof());
      uint32_t count = static_cast<uint32_t>(strtoul(token.c_str(), &end, 0));
      CHECK(errno == 0 && end != token.c_str());
      ProfileDataFromFileInternal& counters_and_hash =
          (*data.get())[builtin_name];
      // We allow concatenating data from several Isolates, so we might see the
      // same block multiple times. Just sum them all.
      counters_and_hash.AddCountToBlock(id, count);
    } else if (token == ProfileDataFromFileConstants::kBuiltinHashMarker) {
      // Any line starting with kBuiltinHashMarker is a function hash record.
      // As defined by Logger::BuiltinHashEvent, the format is:
      //   literal kBuiltinHashMarker , builtin_name , hash
      std::string builtin_name;
      CHECK(std::getline(line_stream, builtin_name, ','));
      std::getline(line_stream, token, ',');
      CHECK(line_stream.eof());
      char* end = nullptr;
      int hash = static_cast<int>(strtol(token.c_str(), &end, 0));
      CHECK(errno == 0 && end != token.c_str());
      ProfileDataFromFileInternal& counters_and_hash =
          (*data.get())[builtin_name];
      // We allow concatenating data from several Isolates, but expect them all
      // to be running the same build. Any file with mismatched hashes for a
      // function is considered ill-formed.
      CHECK_IMPLIES(counters_and_hash.hash_has_value(),
                    counters_and_hash.hash() == hash);
      counters_and_hash.set_hash(hash);
    }
  }
  for (const auto& pair : *data.get()) {
    // Every function is required to have a hash in the log.
    CHECK(pair.second.hash_has_value());
  }
  if (data.get()->size() == 0) {
    PrintF(
        "No basic block counters were found in log file.\n"
        "Did you build with v8_enable_builtins_profiling=true\n"
        "and run with --turbo-profiling-log-builtins?\n");
  }

  return *data.get();
}

}  // namespace

const ProfileDataFromFile* ProfileDataFromFile::TryRead(const char* name) {
  const auto& data = EnsureInitProfileData();
  auto it = data.find(name);
  return it == data.end() ? nullptr : &it->second;
}

}  // namespace internal
}  // namespace v8