/*
* BSD LICENSE
*
* Copyright(c) 2016-2020 Intel Corporation. All rights reserved.
* 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.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>
#include <assert.h>
#include <pqos.h>
#include "dlock.h"
#include "tsc.h"
#define DIM(x) (sizeof(x)/sizeof(x[0]))
#define MB (1024 * 1024)
static void *timer_data_ptr = NULL;
static const size_t timer_data_size = 2 * MB;
static void *main_data_ptr = NULL;
static const size_t main_data_size = 96 * MB;
const long long freq_ms = 100;
static struct tsc_prof timer_prof;
static timer_t timerid;
/**
* @brief Allocates memory block and initializes it with random data
*
* This is to avoid any page faults or copy-on-write exceptions later on
* when measuring cycles.
*
* For simplicity, malloc() is used to allocate memory. Ideally special
* allocator should be used that allocates physically contiguous memory block.
*
* @param sz size of memory block in bytes
*
* @return Pointer to allocated memory block
*/
static void *init_memory(const size_t sz)
{
char *p = NULL;
size_t i;
if (sz <= 0)
return NULL;
p = (char *) malloc(sz);
if (p == NULL)
return NULL;
for (i = 0; i < sz; i += 32)
p[i] = (char) rand();
return (void *)p;
}
/**
* @brief Generates random number for the timer handler procedure
*
* This is required as rand() is not thread safe. This dummy implementation
* computes large number of random numbers, stores them in a table and
* re-uses them all over again. This is good enough for the purpose of this
* application.
*
* @return Random number value
*/
static int timer_rand(void)
{
static int _rand_tab[8192]; /* size has to be power of 2 */
static int _rand_idx = -1;
int ret;
/* generate bunch of random numbers */
if (_rand_idx == -1) {
unsigned i;
for (i = 0; i < DIM(_rand_tab); i++)
_rand_tab[i] = rand();
_rand_idx = 0;
}
ret = _rand_tab[_rand_idx];
_rand_idx = (_rand_idx + 1) & (DIM(_rand_tab) - 1);
return ret;
}
/**
* @brief Timer handler procedure
*
* This is not a realistic workload and it is a demonstration code only.
*
* It runs couple thousand of iterations and each iteration is randomizing
* memory locations to run a number of arithmetic operations on them.
*
* @param sig UNUSED
* @param si UNUSED
* @param uc UNUSED
*/
static void timer_handler(int sig, siginfo_t *si, void *uc)
{
const int num_iterations = 5000;
int *p = (int *) timer_data_ptr;
const size_t sz = timer_data_size / sizeof(int);
int m;
(void) (sig);
(void) (si);
(void) (uc);
tsc_start(&timer_prof);
/* START - "latency sensitive" code */
for (m = 0; m < num_iterations; m++) {
const size_t stride = 5;
const int idx0 = timer_rand() % (sz - stride);
const int idx1 = timer_rand() % (sz - stride);
size_t n;
for (n = 0; n < stride; n++)
p[idx0 + n] = 2 * p[idx1 + n] + p[idx0 + n];
}
/* END - "latency sensitive" code */
tsc_end(&timer_prof, 1);
}
/**
* @brief Set up the timer
*
* The timer expiration is delivered as a signal,
* the signal handler is timer_handler() above.
*
* @param freq_nanosecs timer frequency in nanoseconds
*
* @return Operation status
* @retval 0 OK
* @retval <0 error
*/
static int init_timer(const long long freq_nanosecs)
{
sigset_t mask;
struct sigaction sa;
struct sigevent sev;
struct itimerspec its;
/* this will initialize the table with random numbers */
(void) timer_rand();
/* Block timer signal temporarily */
sigemptyset(&mask);
sigaddset(&mask, SIGRTMIN);
if (sigprocmask(SIG_SETMASK, &mask, NULL) == -1) {
printf("Error masking signal!\n");
return -1;
}
/* set signal handler */
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timer_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
printf("Error setting signal handler!\n");
return -1;
}
/* Create the timer */
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGRTMIN;
sev.sigev_value.sival_ptr = &timerid;
if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
printf("Error creating the timer!\n");
return -1;
}
/* Start the timer */
its.it_value.tv_sec = freq_nanosecs / 1000000000;
its.it_value.tv_nsec = freq_nanosecs % 1000000000;
its.it_interval.tv_sec = freq_nanosecs / 1000000000;
its.it_interval.tv_nsec = freq_nanosecs % 1000000000;
if (timer_settime(timerid, 0, &its, NULL) == -1) {
printf("Error starting the timer!\n");
return -1;
}
/* Unlock the timer signal */
if (sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1) {
printf("Error unmasking signal!\n");
return -1;
}
return 0;
}
/**
* @brief Stop & close the timer
*
* @return Operation status
* @retval 0 OK
* @retval <0 error
*/
static int close_timer(void)
{
if (timer_delete(timerid) == -1) {
printf("Error deleting the timer!\n");
return -1;
}
return 0;
}
/**
* @brief Initializes PQoS library
*
* To satisfy dlock_init() requirements CAT is reset here.
* More sophisticated solution would be to look for unused CLOS here and
* pass it on to dlock_init().
*
* @return Operation status
* @retval 0 OK
* @retval <0 error
*/
static int init_pqos(void)
{
const struct pqos_cpuinfo *p_cpu = NULL;
const struct pqos_cap *p_cap = NULL;
struct pqos_config cfg;
int ret;
memset(&cfg, 0, sizeof(cfg));
cfg.fd_log = STDOUT_FILENO;
cfg.verbose = 0;
ret = pqos_init(&cfg);
if (ret != PQOS_RETVAL_OK) {
printf("Error initializing PQoS library!\n");
return -1;
}
/* Get CMT capability and CPU info pointer */
ret = pqos_cap_get(&p_cap, &p_cpu);
if (ret != PQOS_RETVAL_OK) {
pqos_fini();
printf("Error retrieving PQoS capabilities!\n");
return -1;
}
/* Reset CAT */
ret = pqos_alloc_reset(PQOS_REQUIRE_CDP_ANY, PQOS_REQUIRE_CDP_ANY,
PQOS_MBA_ANY);
if (ret != PQOS_RETVAL_OK) {
pqos_fini();
printf("Error resetting CAT!\n");
return -1;
}
return 0;
}
/**
* @brief Closes PQoS library
*
* @return Operation status
* @retval 0 OK
* @retval <0 error
*/
static int close_pqos(void)
{
int ret_val = 0;
if (pqos_fini() != PQOS_RETVAL_OK) {
printf("Error shutting down PQoS library!\n");
ret_val = -1;
}
return ret_val;
}
/**
* @brief Implements memory intensive workload on random locations
*
* This is plain memcpy() on random locations and random sizes.
*
* @param p pointer to memory block on which the workload is to be run
* @param size size of the memory block
*/
static void main_thread(char *p, const size_t size)
{
const size_t half_size = size / 2;
const unsigned loop_iter = 10000000;
unsigned i;
printf("%s() started. please wait ...\n", __func__);
for (i = 0; i < loop_iter; i++) {
const size_t copy_size = 6 * 1024;
const int rnd1 = rand();
const int rnd2 = rand();
const size_t si = half_size + rnd1 % (half_size - copy_size);
const size_t di = rnd2 % (half_size - copy_size);
memcpy(&p[di], &p[si], copy_size);
}
printf("%s() has finished.\n", __func__);
}
/**
* @brief Parses command line options and implements application logic
*
* @param argc number of arguments in the command line
* @param argv table with command line argument strings
*
* @return Process exit code
*/
int main(int argc, char *argv[])
{
const long long freq_nanosecs = freq_ms * 1000LL * 1000LL;
int core_id, lock_data = 1, exit_val = EXIT_SUCCESS;
if (argc < 3) {
printf("Usage: %s <core_id> <lock|nolock>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (strcasecmp(argv[2], "nolock") != 0 &&
strcasecmp(argv[2], "lock") != 0) {
printf("Invalid data lock setting '%s'!\n", argv[2]);
printf("Usage: %s <core_id> <lock|nolock>\n", argv[0]);
exit(EXIT_FAILURE);
}
core_id = atoi(argv[1]);
lock_data = (strcasecmp(argv[2], "nolock") == 0) ? 0 : 1;
/* allocate memory blocks */
main_data_ptr = init_memory(main_data_size);
timer_data_ptr = init_memory(timer_data_size);
if (main_data_ptr == NULL || timer_data_ptr == NULL) {
exit_val = EXIT_FAILURE;
goto error_exit1;
}
if (lock_data) {
/* initialize PQoS and lock the data */
if (init_pqos() != 0) {
exit_val = EXIT_FAILURE;
goto error_exit1;
}
/* lock the timer data */
if (dlock_init(timer_data_ptr,
timer_data_size, 1 /* CLOS */, core_id) != 0) {
printf("Pseudo data lock error!\n");
exit_val = EXIT_FAILURE;
goto error_exit1;
}
}
tsc_init(&timer_prof, "Timer Handler");
if (init_timer(freq_nanosecs) != 0) {
printf("Timer start error!\n");
exit_val = EXIT_FAILURE;
goto error_exit2;
}
main_thread((char *)main_data_ptr, main_data_size);
(void) close_timer();
tsc_print(&timer_prof);
error_exit2:
if (lock_data)
dlock_exit();
error_exit1:
if (lock_data)
(void) close_pqos();
if (main_data_ptr != NULL)
free(main_data_ptr);
if (timer_data_ptr != NULL)
free(timer_data_ptr);
return exit_val;
}