/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * 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 <folly/container/Array.h>
#include <folly/io/async/test/RequestContextHelper.h>
#include <folly/portability/GFlags.h>
#include <folly/synchronization/test/Barrier.h>

#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <thread>
#include <vector>

DEFINE_int32(reps, 10, "number of reps");
DEFINE_int32(ops, 1000000, "number of operations per rep");

using namespace folly;

RequestToken token("test");

template <typename Func>
inline uint64_t run_once(int nthr, const Func& fn) {
  folly::test::Barrier b1(nthr + 1);
  std::vector<std::thread> thr(nthr);

  for (int tid = 0; tid < nthr; ++tid) {
    thr[tid] = std::thread([&, tid] {
      b1.wait();
      fn(tid);
    });
  }

  b1.wait();
  /* begin time measurement */
  auto const tbegin = std::chrono::steady_clock::now();

  /* wait for completion */
  for (int i = 0; i < nthr; ++i) {
    thr[i].join();
  }

  /* end time measurement */
  auto const tend = std::chrono::steady_clock::now();

  auto const dur = tend - tbegin;
  return std::chrono::duration_cast<std::chrono::nanoseconds>(dur).count();
}

template <typename Func>
uint64_t runBench(int ops, int nthr, const Func& fn) {
  uint64_t reps = FLAGS_reps;
  uint64_t min = UINTMAX_MAX;
  uint64_t max = 0;
  uint64_t sum = 0;
  std::vector<uint64_t> durs(reps);

  for (uint64_t r = 0; r < reps; ++r) {
    uint64_t dur = run_once(nthr, fn);
    durs[r] = dur;
    sum += dur;
    min = std::min(min, dur);
    max = std::max(max, dur);
    // if each rep takes too long run at least 3 reps
    const uint64_t minute = 60000000000UL;
    if (sum > minute && r >= 2) {
      reps = r + 1;
      break;
    }
  }

  const std::string ns_unit = " ns";
  uint64_t avg = sum / reps;
  uint64_t res = min;
  uint64_t varsum = 0;
  for (uint64_t r = 0; r < reps; ++r) {
    auto term = int64_t(reps * durs[r]) - int64_t(sum);
    varsum += term * term;
  }
  uint64_t dev = uint64_t(std::sqrt(varsum) * std::pow(reps, -1.5));
  std::cout << "   " << std::setw(4) << max / ops << ns_unit;
  std::cout << "   " << std::setw(4) << avg / ops << ns_unit;
  std::cout << "   " << std::setw(4) << dev / ops << ns_unit;
  std::cout << "   " << std::setw(4) << res / ops << ns_unit;
  std::cout << std::endl;
  return res;
}

uint64_t bench_set_clearContextData(int nthr, uint64_t ops) {
  auto fn = [&](int tid) {
    RequestContextScopeGuard g;
    for (uint64_t i = tid; i < ops; i += nthr) {
      RequestContext::get()->setContextData(
          token, std::make_unique<TestData>(tid));
      RequestContext::get()->clearContextData(token);
    }
  };
  return runBench(ops, nthr, fn);
}

uint64_t bench_hasContextData(int nthr, uint64_t ops, bool hit) {
  auto fn = [&](int tid) {
    RequestContextScopeGuard g;
    if (hit) {
      RequestContext::get()->setContextData(
          token, std::make_unique<TestData>(tid));
    }
    for (uint64_t i = tid; i < ops; i += nthr) {
      RequestContext::get()->hasContextData(token);
    }
  };
  return runBench(ops, nthr, fn);
}

uint64_t bench_getContextData(int nthr, uint64_t ops, bool hit) {
  auto fn = [&](int tid) {
    RequestContextScopeGuard g;
    if (hit) {
      RequestContext::get()->setContextData(
          token, std::make_unique<TestData>(tid));
    }
    for (uint64_t i = tid; i < ops; i += nthr) {
      RequestContext::get()->getContextData(token);
    }
  };
  return runBench(ops, nthr, fn);
}

uint64_t bench_onSet(int nthr, uint64_t ops, bool nonempty) {
  auto fn = [&](int tid) {
    RequestContextScopeGuard g;
    if (nonempty) {
      RequestContext::get()->setContextData(
          token, std::make_unique<TestData>(tid));
    }
    for (uint64_t i = tid; i < ops; i += nthr) {
      RequestContext::get()->onSet();
    }
  };
  return runBench(ops, nthr, fn);
}

uint64_t bench_onUnset(int nthr, uint64_t ops, bool nonempty) {
  auto fn = [&](int tid) {
    RequestContextScopeGuard g;
    if (nonempty) {
      RequestContext::get()->setContextData(
          token, std::make_unique<TestData>(tid));
    }
    for (uint64_t i = tid; i < ops; i += nthr) {
      RequestContext::get()->onUnset();
    }
  };
  return runBench(ops, nthr, fn);
}

uint64_t bench_setContext(int nthr, uint64_t ops, bool nonempty) {
  auto fn = [&](int tid) {
    auto ctx = std::make_shared<RequestContext>();
    if (nonempty) {
      ctx->setContextData(token, std::make_unique<TestData>(1));
    }
    RequestContext::setContext(std::move(ctx));
    ctx = std::make_shared<RequestContext>();
    if (nonempty) {
      ctx->setContextData(token, std::make_unique<TestData>(2));
    }
    for (uint64_t i = tid; i < ops; i += nthr) {
      ctx = RequestContext::setContext(std::move(ctx));
    }
    RequestContext::setContext(nullptr);
  };
  return runBench(ops, nthr, fn);
}

uint64_t bench_RequestContextScopeGuard(int nthr, uint64_t ops, bool nonempty) {
  auto fn = [&](int tid) {
    RequestContextScopeGuard g1;
    if (nonempty) {
      RequestContext::get()->setContextData(
          token, std::make_unique<TestData>(1));
    }
    auto ctx = std::make_shared<RequestContext>();
    if (nonempty) {
      ctx->setContextData(token, std::make_unique<TestData>(2));
    }
    for (uint64_t i = tid; i < ops; i += nthr) {
      RequestContextScopeGuard g2(ctx);
    }
  };
  return runBench(ops, nthr, fn);
}

uint64_t bench_ShallowCopyRequestContextScopeGuard(
    int nthr,
    uint64_t ops,
    int keep,
    bool replace) {
  auto fn = [&](int tid) {
    RequestContextScopeGuard g1;
    auto ctx = RequestContext::get();
    for (int i = 0; i < keep; ++i) {
      ctx->setContextData(
          folly::to<std::string>(1000 + i), std::make_unique<TestData>(i));
    }
    if (replace) {
      ctx->setContextData(token, std::make_unique<TestData>(1));
      for (uint64_t i = tid; i < ops; i += nthr) {
        ShallowCopyRequestContextScopeGuard g2(
            token, std::make_unique<TestData>(2));
      }
    } else {
      for (uint64_t i = tid; i < ops; i += nthr) {
        ShallowCopyRequestContextScopeGuard g2;
      }
    }
  };
  return runBench(ops, nthr, fn);
}

void dottedLine() {
  std::cout
      << "........................................................................"
      << std::endl;
}

void doubleLine() {
  std::cout
      << "========================================================================"
      << std::endl;
}

constexpr auto nthr = folly::make_array<int>(1, 10);

void benches() {
  doubleLine();
  std::cout << std::setw(2) << FLAGS_reps << " reps of " << std::setw(8)
            << FLAGS_ops << " operations\n";
  dottedLine();
  std::cout << "$ numactl -N 1 $dir/request_context_benchmark\n";
  doubleLine();
  std::cout
      << "Test name                         Max time  Avg time  Dev time  Min time"
      << std::endl;
  for (int i : nthr) {
    std::cout << "============================== " << std::setw(2) << i
              << " threads "
              << "==============================" << std::endl;
    const uint64_t ops = FLAGS_ops;
    std::cout << "hasContextData                  ";
    bench_hasContextData(i, ops, true);
    std::cout << "getContextData                  ";
    bench_getContextData(i, ops, true);
    std::cout << "onSet                           ";
    bench_onSet(i, ops, true);
    std::cout << "onUnset                         ";
    bench_onUnset(i, ops, true);
    std::cout << "setContext                      ";
    bench_setContext(i, ops, true);
    std::cout << "RequestContextScopeGuard        ";
    bench_RequestContextScopeGuard(i, ops, true);
    std::cout << "ShallowCopyRequestC...-replace  ";
    bench_ShallowCopyRequestContextScopeGuard(i, ops, 0, true);
    std::cout << "ShallowCopyReq...-keep&replace  ";
    bench_ShallowCopyRequestContextScopeGuard(i, ops, 12, true);
  }
  doubleLine();
}

int main(int argc, char** argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, true);
  benches();
}

/*
========================================================================
10 reps of  1000000 operations
........................................................................
$ numactl -N 1 $dir/request_context_benchmark
========================================================================
Test name                         Max time  Avg time  Dev time  Min time
==============================  1 threads ==============================
hasContextData                        7 ns      7 ns      0 ns      7 ns
getContextData                        7 ns      7 ns      0 ns      7 ns
onSet                                12 ns     12 ns      0 ns     12 ns
onUnset                              12 ns     12 ns      0 ns     12 ns
setContext                           46 ns     44 ns      1 ns     42 ns
RequestContextScopeGuard            113 ns    103 ns      3 ns    101 ns
ShallowCopyRequestC...-replace      213 ns    201 ns      5 ns    196 ns
ShallowCopyReq...-keep&replace      883 ns    835 ns     20 ns    814 ns
============================== 10 threads ==============================
hasContextData                        1 ns      1 ns      0 ns      1 ns
getContextData                        2 ns      1 ns      0 ns      1 ns
onSet                                 2 ns      2 ns      0 ns      1 ns
onUnset                               2 ns      2 ns      0 ns      1 ns
setContext                           11 ns      7 ns      2 ns      5 ns
RequestContextScopeGuard             22 ns     15 ns      5 ns     11 ns
ShallowCopyRequestC...-replace       48 ns     30 ns     11 ns     21 ns
ShallowCopyReq...-keep&replace       98 ns     93 ns      2 ns     91 ns
========================================================================
 */
