//  Copyright (c) 2007-2012 Hartmut Kaiser
//
//  SPDX-License-Identifier: BSL-1.0
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <hpx/config.hpp>
#if !defined(HPX_COMPUTE_DEVICE_CODE)
#include <hpx/hpx.hpp>
#include <hpx/hpx_init.hpp>
#include <hpx/include/util.hpp>

#include <hpx/modules/program_options.hpp>

#include <cstdint>
#include <iostream>
#include <string>
#include <vector>

///////////////////////////////////////////////////////////////////////////////
// This example demonstrates the creation and use of different types of
// performance counters. It utilizes the sine component, which implements two
// of the demonstrated counters.
//
// The example prints the values of the counters after every fixed amount of
// time. This time is configurable by a command line option --pause=N, where
// N is the amount of milliseconds to wait in between the sample points. The
// default value is 500ms.
//
// Here is a short description of each of the counters:
//
// /sine{locality#0/instance#0}/immediate/explicit
//    This is a custom performance counter fully implemented in the sine
//    component. It evaluates a new value every 1000ms (1s) and delivers this
//    value whenever it is queried.
//
// /sine{locality#0/total}/immediate/implicit
//    This is an immediate counter implemented by registering a function with
//    the counter framework. This function will be called whenever the counter
//    value is queried. It calculates the current value of a sine based on the
//    uptime of the counter.
//
// /statistics{/sine{locality#0/instance#1}/immediate/explicit}/average#100
//    This is an aggregating counter calculating the average value of a given
//    base counter (in this case /sine{locality#0/instance#1}/immediate/explicit,
//    i.e. a second instance of the explicit counter). The base counter is
//    evaluated every 100ms (as specified by the trailing parameter in the
//    counter name). No special code in the sine example is needed for this
//    counter as it reuses the explicit counter and the predefined averaging
//    performance counter implemented in HPX.
//
// Additionally, this example demonstrates starting and stopping performance
// counters. It stops the evaluation of the first explicit counter instance
// every 5 seconds, restarting it after a while.

///////////////////////////////////////////////////////////////////////////////
int monitor(std::uint64_t pause, std::uint64_t values)
{
    // Create the performances counters using their symbolic name.
    std::uint32_t const prefix = hpx::get_locality_id();

    using hpx::performance_counters::performance_counter;
    performance_counter sine_explicit(hpx::util::format(
        "/sine{{locality#{}/instance#{}}}/immediate/explicit", prefix, 0));
    performance_counter sine_implicit(hpx::util::format(
        "/sine{{locality#{}/total}}/immediate/implicit", prefix));
    performance_counter sine_average(
        hpx::util::format("/statistics{{/sine{{locality#{}/instance#{}}}/"
                          "immediate/explicit}}/average@100",
            prefix, 1));

    // We need to explicitly start all counters before we can use them. For
    // certain counters this could be a no-op, in which case start will return
    // 'false'.
    sine_explicit.start();
    sine_implicit.start();
    sine_average.start();

    // retrieve the counter values
    std::uint64_t start_time = 0;
    bool started = true;
    while (values-- > 0)
    {
        // Query the performance counter.
        using hpx::performance_counters::counter_value;
        using hpx::performance_counters::status_is_valid;

        counter_value value1 =
            sine_explicit.get_counter_value(hpx::launch::sync);
        counter_value value2 =
            sine_implicit.get_counter_value(hpx::launch::sync);
        counter_value value3 =
            sine_average.get_counter_value(hpx::launch::sync);

        if (status_is_valid(value1.status_))
        {
            if (!start_time)
                start_time = value2.time_;

            hpx::util::format_to(std::cout, "{:.3}: {:.4}, {:.4}, {:.4}\n",
                static_cast<double>(value2.time_ - start_time) * 1e-9,
                value1.get_value<double>(),
                value2.get_value<double>() / 100000.,
                value3.get_value<double>());
        }

        // stop/restart the sine_explicit counter after every 5 seconds of
        // evaluation
        bool should_run =
            (int(static_cast<double>(value2.time_ - start_time) * 1e-9) / 5) %
                2 !=
            0;
        if (should_run == started)
        {
            if (started)
            {
                sine_explicit.stop();
                started = false;
            }
            else
            {
                sine_explicit.start();
                started = true;
            }
        }

        // give up control to the thread manager, we will be resumed after
        // 'pause' ms
        hpx::this_thread::suspend(std::chrono::milliseconds(pause));
    }
    return 0;
}

///////////////////////////////////////////////////////////////////////////////
int hpx_main(hpx::program_options::variables_map& vm)
{
    // retrieve the command line arguments
    std::uint64_t const pause = vm["pause"].as<std::uint64_t>();
    std::uint64_t const values = vm["values"].as<std::uint64_t>();

    // do main work, i.e. query the performance counters
    std::cout << "starting sine monitoring..." << std::endl;

    int result = 0;
    try
    {
        result = monitor(pause, values);
    }
    catch (hpx::exception const& e)
    {
        std::cerr << "sine_client: caught exception: " << e.what() << std::endl;
        std::cerr << "Have you specified the command line option "
                     "--sine to enable the sine component?"
                  << std::endl;
    }

    // Initiate shutdown of the runtime system.
    hpx::finalize();
    return result;
}

///////////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
    using hpx::program_options::options_description;
    using hpx::program_options::value;

    // Configure application-specific options.
    options_description desc_commandline("usage: sine_client [options]");
    desc_commandline.add_options()("pause",
        value<std::uint64_t>()->default_value(500),
        "milliseconds between each performance counter query")("values",
        value<std::uint64_t>()->default_value(100),
        "number of performance counter queries to perform");

    std::vector<std::string> cfg = {"hpx.components.sine.enabled! = 1"};

    // Initialize and run HPX.
    hpx::init_params init_args;
    init_args.desc_cmdline = desc_commandline;
    init_args.cfg = cfg;

    return hpx::init(argc, argv, init_args);
}
#endif
