/*
 * BSD LICENSE
 *
 * Copyright(c) 2014-2023 Intel Corporation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of Intel Corporation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/**
 * @brief Platform QoS sample LLC occupancy monitoring application
 *
 */

#include "pqos.h"

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/**
 * Defines
 */
#define PQOS_MAX_CORES      1024
#define PQOS_MAX_PIDS       16
#define PQOS_MAX_MON_EVENTS 1

/**
 * Number of cores that are selected in config string
 * for monitoring LLC occupancy
 */
static int sel_monitor_num = 0;

/**
 * The mask to tell which events to display
 */
static enum pqos_mon_event sel_events_max = (enum pqos_mon_event)0;

/**
 * Maintains a table of core, event, number of events that are
 * selected in config string for monitoring
 */
static struct {
        unsigned core;
        enum pqos_mon_event events;
} sel_monitor_core_tab[PQOS_MAX_CORES];

/**
 * Maintains a table of process id, event, number of events that are selected
 * in config string for monitoring LLC occupancy
 */
static struct {
        pid_t pid;
        enum pqos_mon_event events;
} sel_monitor_pid_tab[PQOS_MAX_PIDS];

static struct pqos_mon_data *m_mon_grps[PQOS_MAX_CORES];

/**
 * Maintains the number of process id's you want to track
 */
static int sel_process_num = 0;

/**
 * Flag to determine which library interface to use
 */
static enum pqos_interface interface = PQOS_INTER_MSR;

static void stop_monitoring(void);

/**
 * @brief CTRL-C handler for infinite monitoring loop
 *
 * @param [in] signo signal number
 */
static void __attribute__((noreturn)) monitoring_ctrlc(int signo)
{
        printf("\nExiting[%d]...\n", signo);
        stop_monitoring();
        if (pqos_fini() != PQOS_RETVAL_OK) {
                printf("Error shutting down PQoS library!\n");
                exit(EXIT_FAILURE);
        }
        exit(EXIT_SUCCESS);
}

/**
 * @brief Scale byte value up to KB
 *
 * @param [in] bytes value to be scaled up
 * @return scaled up value in KB's
 */
static inline double
bytes_to_kb(const double bytes)
{
        return bytes / 1024.0;
}

/**
 * @brief Scale byte value up to MB
 *
 * @param [in] bytes value to be scaled up
 * @return scaled up value in MB's
 */
static inline double
bytes_to_mb(const double bytes)
{
        return bytes / (1024.0 * 1024.0);
}

/**
 * @brief Check to determine if processes or cores are monitored
 *
 * @return Process monitoring mode status
 * @retval 0 monitoring cores
 * @retval 1 monitoring processes
 */
static inline int
process_mode(void)
{
        return (sel_process_num <= 0) ? 0 : 1;
}

/**
 * @brief Verifies and translates monitoring config string into
 *        internal monitoring configuration.
 *
 * @param [in] argc Number of arguments in input command
 * @param [in] argv Input arguments for LLC occupancy monitoring
 */
static void
monitoring_get_input(int argc, char *argv[])
{
        int num_args, num_opts = 1, i = 0, sel_pid = 0, help = 0;

        for (i = 0; i < argc; i++) {
                if (!strcmp(argv[i], "-p")) {
                        sel_pid = 1;
                        num_opts++;
                } else if (!strcmp(argv[i], "-I")) {
                        interface = PQOS_INTER_OS;
                        num_opts++;
                } else if (!strcmp(argv[i], "-H") || !strcmp(argv[i], "-h")) {
                        help = 1;
                        num_opts++;
                }
        }
        /* Ensure OS interface selected if monitoring tasks */
        if (sel_pid && interface == PQOS_INTER_MSR) {
                printf("Error: PID monitoring requires OS interface "
                       "selection!\nPlease use the -I option.\n");
                help = 1;
        }
        num_args = (argc - num_opts);
        if (help) {
                printf("Usage:  %s [<core1> <core2> <core3> ...]\n"
                       "        %s -I -p [<pid1> <pid2> <pid3> ...]\n",
                       argv[0], argv[0]);
                printf("Eg   :  %s 1 2 6\n        "
                       "%s -I -p 3564 7638 356\n"
                       "Notes:\n        "
                       "-h      help\n        "
                       "-I      select library OS interface\n        "
                       "-p      select process ID's to monitor LLC occupancy"
                       "\n\n",
                       argv[0], argv[0]);
                exit(EXIT_SUCCESS);
        } else if (num_args == 0) {
                sel_monitor_num = 0;
        } else {
                if (sel_pid) {
                        if (num_args > PQOS_MAX_PIDS)
                                num_args = PQOS_MAX_PIDS;
                        for (i = 0; i < num_args; i++) {
                                sel_monitor_pid_tab[i].pid =
                                    (unsigned)atoi(argv[num_opts + i]);
                        }
                        sel_process_num = (int)num_args;
                } else {
                        if (num_args > PQOS_MAX_CORES)
                                num_args = PQOS_MAX_CORES;
                        for (i = 0; i < num_args; i++) {
                                sel_monitor_core_tab[i].core =
                                    (unsigned)atoi(argv[num_opts + i]);
                        }
                        sel_monitor_num = (int)num_args;
                }
        }
}

/**
 * @brief Starts monitoring on selected cores/PIDs
 *
 * @param [in] cpu_info cpu information structure
 * @param [in] cap_mon monitoring capabilities structure
 *
 * @return Operation status
 * @retval 0 OK
 * @retval -1 error
 */
static int
setup_monitoring(const struct pqos_cpuinfo *cpu_info,
                 const struct pqos_capability *const cap_mon)
{
        unsigned i;
        const enum pqos_mon_event perf_events = (enum pqos_mon_event)(
            PQOS_PERF_EVENT_IPC | PQOS_PERF_EVENT_LLC_MISS |
            PQOS_PERF_EVENT_LLC_REF);

        const enum pqos_mon_event uncore_events =
            (enum pqos_mon_event)(PQOS_PERF_EVENT_LLC_MISS_PCIE_READ |
                                  PQOS_PERF_EVENT_LLC_MISS_PCIE_WRITE |
                                  PQOS_PERF_EVENT_LLC_REF_PCIE_READ |
                                  PQOS_PERF_EVENT_LLC_REF_PCIE_WRITE);

        for (i = 0; (unsigned)i < cap_mon->u.mon->num_events; i++)
                sel_events_max |= (cap_mon->u.mon->events[i].type);

        /* Remove perf events IPC and LLC MISSES */
        sel_events_max &= ~perf_events;
        /* Remove uncore events */
        sel_events_max &= ~uncore_events;

        if (sel_monitor_num == 0 && sel_process_num == 0) {
                for (i = 0; i < cpu_info->num_cores; i++) {
                        unsigned lcore = cpu_info->cores[i].lcore;

                        sel_monitor_core_tab[sel_monitor_num].core = lcore;
                        sel_monitor_core_tab[sel_monitor_num].events =
                            sel_events_max;
                        m_mon_grps[sel_monitor_num] =
                            malloc(sizeof(**m_mon_grps));
                        sel_monitor_num++;
                }
        }
        if (!process_mode()) {
                for (i = 0; i < (unsigned)sel_monitor_num; i++) {
                        unsigned lcore = sel_monitor_core_tab[i].core;
                        int ret;

                        ret = pqos_mon_start_cores(1, &lcore, sel_events_max,
                                                   NULL, &m_mon_grps[i]);
                        if (ret != PQOS_RETVAL_OK) {
                                printf("Monitoring start error on core %u,"
                                       "status %d\n",
                                       lcore, ret);
                                return ret;
                        }
                }
        } else {
                for (i = 0; i < (unsigned)sel_process_num; i++) {
                        pid_t pid = sel_monitor_pid_tab[i].pid;
                        int ret;

                        ret = pqos_mon_start_pids2(1, &pid,
                                                   PQOS_MON_EVENT_L3_OCCUP,
                                                   NULL, &m_mon_grps[i]);
                        if (ret != PQOS_RETVAL_OK) {
                                printf("Monitoring start error on pid %u,"
                                       "status %d\n",
                                       pid, ret);
                                return ret;
                        }
                }
        }
        return PQOS_RETVAL_OK;
}

/**
 * @brief Stops monitoring on selected cores
 *
 */
static void
stop_monitoring(void)
{
        unsigned i, mon_number = 0;

        if (!process_mode())
                mon_number = (unsigned)sel_monitor_num;
        else
                mon_number = (unsigned)sel_process_num;

        for (i = 0; i < mon_number; i++) {
                int ret;

                ret = pqos_mon_stop(m_mon_grps[i]);
                if (ret != PQOS_RETVAL_OK)
                        printf("Monitoring stop error!\n");
        }
}

/**
 * @brief Reads monitoring event data
 */
static void
monitoring_loop(void)
{
        unsigned mon_number = 0;
        int ret = PQOS_RETVAL_OK;
        int i;

        if (signal(SIGINT, monitoring_ctrlc) == SIG_ERR)
                printf("Failed to catch SIGINT!\n");

        if (!process_mode())
                mon_number = (unsigned)sel_monitor_num;
        else
                mon_number = (unsigned)sel_process_num;

        while (1) {
                ret = pqos_mon_poll(m_mon_grps, (unsigned)mon_number);
                if (ret != PQOS_RETVAL_OK) {
                        printf("Failed to poll monitoring data!\n");
                        return;
                }
                if (!process_mode()) {
                        printf("    CORE    LLC[KB]    MBL[MB]    MBR[MB]\n");
                        for (i = 0; i < sel_monitor_num; i++) {
                                const struct pqos_event_values *pv =
                                    &m_mon_grps[i]->values;
                                double llc = bytes_to_kb(pv->llc);
                                double mbr = bytes_to_mb(pv->mbm_remote_delta);
                                double mbl = bytes_to_mb(pv->mbm_local_delta);

                                printf("%8u %10.1f %10.1f %10.1f\n",
                                       m_mon_grps[i]->cores[0], llc, mbl, mbr);
                        }

                } else {
                        printf("PID       LLC[KB]\n");
                        for (i = 0; i < sel_process_num; i++) {
                                const struct pqos_event_values *pv =
                                    &m_mon_grps[i]->values;
                                double llc = bytes_to_kb(pv->llc);

                                printf("%6d %10.1f\n", m_mon_grps[i]->pids[0],
                                       llc);
                        }
                }
                printf("\nPress Enter to continue or Ctrl+c to exit");
                if (getchar() != '\n')
                        break;
                printf("\e[1;1H\e[2J");
        }
}

int
main(int argc, char *argv[])
{
        struct pqos_config config;
        const struct pqos_cpuinfo *p_cpu = NULL;
        const struct pqos_cap *p_cap = NULL;
        int ret, exit_val = EXIT_SUCCESS;
        const struct pqos_capability *cap_mon = NULL;

        /* Get input from user */
        monitoring_get_input(argc, argv);

        memset(&config, 0, sizeof(config));
        config.fd_log = STDOUT_FILENO;
        config.verbose = 0;
        config.interface = interface;

        /* PQoS Initialization - Check and initialize CAT and CMT capability */
        ret = pqos_init(&config);
        if (ret != PQOS_RETVAL_OK) {
                printf("Error initializing PQoS library!\n");
                exit_val = EXIT_FAILURE;
                goto error_exit;
        }
        /* Get CMT capability and CPU info pointer */
        ret = pqos_cap_get(&p_cap, &p_cpu);
        if (ret != PQOS_RETVAL_OK) {
                printf("Error retrieving PQoS capabilities!\n");
                exit_val = EXIT_FAILURE;
                goto error_exit;
        }
        ret = pqos_cap_get_type(p_cap, PQOS_CAP_TYPE_MON, &cap_mon);
        if (ret != PQOS_RETVAL_OK) {
                printf("Error retrieving monitoring capabilities\n");
                exit_val = EXIT_FAILURE;
                goto error_exit;
        }
        /* Setup the monitoring resources */
        ret = setup_monitoring(p_cpu, cap_mon);
        if (ret != PQOS_RETVAL_OK) {
                printf("Error Setting up monitoring!\n");
                exit_val = EXIT_FAILURE;
                goto error_exit;
        }
        /* Start Monitoring */
        monitoring_loop();
        /* Stop Monitoring */
        stop_monitoring();
error_exit:
        ret = pqos_fini();
        if (ret != PQOS_RETVAL_OK)
                printf("Error shutting down PQoS library!\n");
        return exit_val;
}
