/*
 * PyPerf Profile Python Processes with Python stack-trace.
 *        For Linux, uses BCC, eBPF. Embedded C.
 *
 * Example of using BPF to profile Python Processes with Python stack-trace.
 *
 * USAGE: PyPerf [-d|--duration DURATION_MS] [-c|--sample-rate SAMPLE_RATE]
 *               [-v|--verbosity LOG_VERBOSITY]
 *
 * Copyright (c) Facebook, Inc.
 * Licensed under the Apache License, Version 2.0 (the "License")
 */

#include <cinttypes>
#include <cstdlib>
#include <string>
#include <vector>

#include "PyPerfDefaultPrinter.h"
#include "PyPerfLoggingHelper.h"
#include "PyPerfUtil.h"

int main(int argc, char** argv) {
  // Argument parsing helpers
  int pos = 1;

  auto parseIntArg = [&](std::vector<std::string> argNames, uint64_t& target) {
    std::string arg(argv[pos]);
    for (const auto& name : argNames) {
      if (arg == name) {
        if (pos == argc) {
          std::fprintf(stderr, "Expect value after %s\n", arg.c_str());
          std::exit(1);
        }
        pos++;
        std::string value(argv[pos]);
        try {
          target = std::stoi(value);
        } catch (const std::exception& e) {
          std::fprintf(stderr, "Expect integer value after %s, got %s: %s\n",
                       arg.c_str(), value.c_str(), e.what());
          std::exit(1);
        }
        return true;
      }
    }
    return false;
  };

  auto parseBoolArg = [&](std::vector<std::string> argNames, bool& target) {
    std::string arg(argv[pos]);
    for (const auto& name : argNames) {
      if (arg == ("--" + name)) {
        target = true;
        return true;
      }
      if (arg == "--no-" + name) {
        target = false;
        return true;
      }
    }
    return false;
  };

  // Default argument values
  uint64_t sampleRate = 1000000;
  uint64_t durationMs = 1000;
  uint64_t verbosityLevel = 0;
  bool showGILState = true;
  bool showThreadState = true;
  bool showPthreadIDState = false;

  while (true) {
    if (pos >= argc) {
      break;
    }
    bool found = false;
    found = found || parseIntArg({"-c", "--sample-rate"}, sampleRate);
    found = found || parseIntArg({"-d", "--duration"}, durationMs);
    found = found || parseIntArg({"-v", "--verbose"}, verbosityLevel);
    found = found || parseBoolArg({"show-gil-state"}, showGILState);
    found = found || parseBoolArg({"show-thread-state"}, showThreadState);
    found =
        found || parseBoolArg({"show-pthread-id-state"}, showPthreadIDState);
    if (!found) {
      std::fprintf(stderr, "Unexpected argument: %s\n", argv[pos]);
      std::exit(1);
    }
    pos++;
  }

  ebpf::pyperf::setVerbosity(verbosityLevel);
  ebpf::pyperf::logInfo(1, "Profiling Sample Rate: %" PRIu64 "\n", sampleRate);
  ebpf::pyperf::logInfo(1, "Profiling Duration: %" PRIu64 "ms\n", durationMs);
  ebpf::pyperf::logInfo(1, "Showing GIL state: %d\n", showGILState);
  ebpf::pyperf::logInfo(1, "Showing Thread state: %d\n", showThreadState);
  ebpf::pyperf::logInfo(1, "Showing Pthread ID state: %d\n",
                        showPthreadIDState);

  ebpf::pyperf::PyPerfUtil util;
  util.init();

  ebpf::pyperf::PyPerfDefaultPrinter printer(showGILState, showThreadState,
                                             showPthreadIDState);
  util.profile(sampleRate, durationMs, &printer);

  return 0;
}