// -*-C++-*-
// This file is a part of the IncludeOS unikernel - www.includeos.org
//
// Copyright 2015 Oslo and Akershus University College of Applied Sciences
// and Alfred Bratterud
//
// 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.

#pragma once
#ifndef API_PROFILE_HEADER
#define API_PROFILE_HEADER

#include <cstdint>
#include <array>
#include <string>
#include <vector>
#include <arch.hpp>

struct Sample {
  uint32_t    samp; // samples
  void*       addr; // function address
  std::string name; // function name
};

struct StackSampler
{
  // sets up stack sampling configuration and internal timer
  // the stack sampling will happen in the background afterwards
  static void begin();

  // total number of waking samples taken
  static uint64_t samples_total() noexcept;

  // total number of samples taken while asleep
  static uint64_t samples_asleep() noexcept;

  // retrieve N top results/hotspots
  static std::vector<Sample> results(int N);

  // print N top results to stdout
  static void print(int N);

  // enable or disable sample-taking
  // eg. disable sampling during stack sample result printout
  static void set_mask(bool);

  enum mode_t {
    MODE_CURRENT,
    MODE_CALLER,
    MODE_DUMMY
  };

  // set sampling mode
  static void set_mode(mode_t);
};

/**
 * @brief Scoped Profiler
 *
 * Measures the average execution time of a scope
 *
 * Example:
 * Create a ScopedProfiler in the scope that should be measuered:
 *
 * void my_function()
 * {
 *   ScopedProfiler sp;
 *
 *   do_stuff();
 *   do_more_stuff();
 * }
 *
 * Call ScopedProfiler::get_statistics() to get a formatted std::string of
 * statistics
 *
 */
class ScopedProfiler
{
 public:
  inline ScopedProfiler(const char* nme = nullptr)
    : name(nme)
  {
    record();
    // memory clobbered make sure ScopedProfilers aren't moved around
    __sw_barrier();
  }

  void record();
  ~ScopedProfiler();

  // No copying or moving
  ScopedProfiler(const ScopedProfiler&) = delete;
  ScopedProfiler& operator=(const ScopedProfiler&) = delete;
  ScopedProfiler(ScopedProfiler&&) = delete;
  ScopedProfiler& operator=(ScopedProfiler&&) = delete;

  /**
   * @brief Returns profiling statistics as a formatted std::string
   *
   * Formats the output like:
   *
   *  Average Time (us) | Samples | Function Name
   *  --------------------------------------------------------------------------------
   *         172.768148 |    1212 | Foo::my_function()
   *          40.603411 |    1223 | Foo::my_other_function()
   *          15.460829 |     602 | Bar::Bar()
   *          15.189189 |     600 | Bar::~Bar()
   *  --------------------------------------------------------------------------------
   *
   */
  static std::string get_statistics(bool sorted = true);

 private:
  uint64_t tick_start;
  const char*    name;

  struct Entry
  {
    void*       function_address;  // This identifies an Entry
                                   // function_address == 0 => the Entry is unused
    const char* name;
    std::string function_name;
    unsigned    num_samples;
    uint64_t    cycles_average;
    uint64_t    nanos_start;
  };


  // The guard to use, before calling rdtsc
  static enum class Guard
  {
    NOT_SELECTED,
    LFENCE,
    MFENCE,
    NOT_AVAILABLE,
  } guard;

  // Use plain array for performance (cache locality)
  // Increase size if there is a need to profile more than 64 scopes
  static std::array<Entry, 64> entries;
};

struct HeapDiag
{
  static std::string to_string();
};

#endif
