// Copyright 2022 The Abseil Authors
//
// 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
//
//     https://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 <algorithm>
#include <atomic>
#include <cstddef>
#include <cstring>
#include <memory>
#include <new>
#include <random>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "absl/base/config.h"
#include "absl/container/internal/layout.h"
#include "absl/log/internal/vlog_config.h"
#include "absl/memory/memory.h"
#include "absl/random/distributions.h"
#include "absl/strings/str_cat.h"
#include "benchmark/benchmark.h"

namespace absl {
ABSL_NAMESPACE_BEGIN
namespace log_internal {
// Performance of `UpdateLogSites` depends upon the number and organization of
// `VLogSite`s in the program.  We can synthesize some on the heap to mimic
// their layout and linkage in static data.
class SyntheticBinary {
 public:
  explicit SyntheticBinary(const size_t num_tus,
                           const size_t max_extra_static_data_bytes_per_tu,
                           const size_t max_sites_per_tu,
                           const int num_shuffles) {
    per_tu_data_.reserve(num_tus);
    auto sites = std::make_unique<VLogSite*[]>(num_tus * max_sites_per_tu);
    for (size_t i = 0; i < num_tus; i++) {
      const std::string filename =
          absl::StrCat("directory-", i / 100, "/subdirectory-", i % 100 / 10,
                       "/file-", i % 10, ".cc");
      container_internal::Layout<char, VLogSite, char> layout(
          filename.size() + 1,
          absl::LogUniform<size_t>(bitgen_, 1, max_sites_per_tu),
          absl::LogUniform<size_t>(bitgen_, 0,
                                   max_extra_static_data_bytes_per_tu));
      auto buf = std::make_unique<char[]>(layout.AllocSize());
      layout.PoisonPadding(buf.get());
      memcpy(layout.Pointer<0>(buf.get()), filename.c_str(),
             filename.size() + 1);
      for (VLogSite &site : layout.Slice<1>(buf.get())) {
        sites[num_sites_++] =
            new (&site) VLogSite(layout.Pointer<0>(buf.get()));
        // The last one is a dangling pointer but will be fixed below.
        site.next_.store(&site + 1, std::memory_order_seq_cst);
      }
      num_extra_static_data_bytes_ += layout.Size<2>();
      per_tu_data_.push_back(PerTU{layout, std::move(buf)});
    }
    // Now link the files together back-to-front into a circular list.
    for (size_t i = 0; i < num_tus; i++) {
      auto &tu = per_tu_data_[i];
      auto &next_tu = per_tu_data_[(i + 1) % num_tus];
      tu.layout.Slice<1>(tu.buf.get())
          .back()
          .next_.store(next_tu.layout.Pointer<1>(next_tu.buf.get()),
                       std::memory_order_seq_cst);
    }
    // Now do some shufflin'.
    auto new_sites = std::make_unique<VLogSite*[]>(num_sites_);
    for (int shuffle_num = 0; shuffle_num < num_shuffles; shuffle_num++) {
      // Each shuffle cuts the ring into three pieces and rearranges them.
      const size_t cut_a = absl::Uniform(bitgen_, size_t{0}, num_sites_);
      const size_t cut_b = absl::Uniform(bitgen_, size_t{0}, num_sites_);
      const size_t cut_c = absl::Uniform(bitgen_, size_t{0}, num_sites_);
      if (cut_a == cut_b || cut_b == cut_c || cut_a == cut_c) continue;
      // The same cuts, sorted:
      const size_t cut_1 = std::min({cut_a, cut_b, cut_c});
      const size_t cut_3 = std::max({cut_a, cut_b, cut_c});
      const size_t cut_2 = cut_a ^ cut_b ^ cut_c ^ cut_1 ^ cut_3;
      VLogSite *const tmp = sites[cut_1]->next_.load(std::memory_order_seq_cst);
      sites[cut_1]->next_.store(
          sites[cut_2]->next_.load(std::memory_order_seq_cst),
          std::memory_order_seq_cst);
      sites[cut_2]->next_.store(
          sites[cut_3]->next_.load(std::memory_order_seq_cst),
          std::memory_order_seq_cst);
      sites[cut_3]->next_.store(tmp, std::memory_order_seq_cst);
      memcpy(&new_sites[0], &sites[0], sizeof(VLogSite *) * (cut_1 + 1));
      memcpy(&new_sites[cut_1 + 1], &sites[cut_2 + 1],
             sizeof(VLogSite *) * (cut_3 - cut_2));
      memcpy(&new_sites[cut_1 + 1 + cut_3 - cut_2], &sites[cut_1 + 1],
             sizeof(VLogSite *) * (cut_2 - cut_1));
      memcpy(&new_sites[cut_3 + 1], &sites[cut_3 + 1],
             sizeof(VLogSite *) * (num_sites_ - cut_3 - 1));
      sites.swap(new_sites);
    }
    const char *last_file = nullptr;
    for (size_t i = 0; i < num_sites_; i++) {
      if (sites[i]->file_ == last_file) continue;
      last_file = sites[i]->file_;
      num_new_files_++;
    }
    absl::log_internal::SetVModuleListHeadForTestOnly(sites[0]);
    sites[num_tus - 1]->next_.store(nullptr, std::memory_order_seq_cst);
  }
  ~SyntheticBinary() {
    static_assert(std::is_trivially_destructible<VLogSite>::value, "");
    absl::log_internal::SetVModuleListHeadForTestOnly(nullptr);
  }

  size_t num_sites() const { return num_sites_; }
  size_t num_new_files() const { return num_new_files_; }
  size_t num_extra_static_data_bytes() const {
    return num_extra_static_data_bytes_;
  }

 private:
  struct PerTU {
    container_internal::Layout<char, VLogSite, char> layout;
    std::unique_ptr<char[]> buf;
  };

  std::mt19937 bitgen_;
  std::vector<PerTU> per_tu_data_;
  size_t num_sites_ = 0;
  size_t num_new_files_ = 0;
  size_t num_extra_static_data_bytes_ = 0;
};

namespace {
void BM_UpdateVModuleEmpty(benchmark::State& state) {
  SyntheticBinary bin(static_cast<size_t>(state.range(0)), 10 * 1024 * 1024,
                      256, state.range(1));
  for (auto s : state) {
    absl::log_internal::UpdateVModule("");
  }
  state.SetItemsProcessed(static_cast<int>(bin.num_new_files()));
}
BENCHMARK(BM_UpdateVModuleEmpty)
    ->ArgPair(100, 200)
    ->ArgPair(1000, 2000)
    ->ArgPair(10000, 20000);

void BM_UpdateVModuleShort(benchmark::State& state) {
  SyntheticBinary bin(static_cast<size_t>(state.range(0)), 10 * 1024 * 1024,
                      256, state.range(1));
  for (auto s : state) {
    absl::log_internal::UpdateVModule("directory2/*=4");
  }
  state.SetItemsProcessed(static_cast<int>(bin.num_new_files()));
}
BENCHMARK(BM_UpdateVModuleShort)
    ->ArgPair(100, 200)
    ->ArgPair(1000, 2000)
    ->ArgPair(10000, 20000);

void BM_UpdateVModuleLong(benchmark::State& state) {
  SyntheticBinary bin(static_cast<size_t>(state.range(0)), 10 * 1024 * 1024,
                      256, state.range(1));
  for (auto s : state) {
    absl::log_internal::UpdateVModule(
        "d?r?c?o?y2/*=4,d?*r?*c?**o?*y1/*=2,d?*rc?**o?*y3/*=2,,"
        "d?*r?*c?**o?*1/*=1,d?*r?**o?*y1/*=2,d?*r???***y1/*=7,"
        "d?*r?**o?*y1/aaa=6");
  }
  state.SetItemsProcessed(static_cast<int>(bin.num_new_files()));
}
BENCHMARK(BM_UpdateVModuleLong)
    ->ArgPair(100, 200)
    ->ArgPair(1000, 2000)
    ->ArgPair(10000, 20000);
}  // namespace
}  // namespace log_internal
ABSL_NAMESPACE_END
}  // namespace absl
