/*
* BSD LICENSE
*
* Copyright(c) 2018 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.
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include "pqos.h"
#include "log.h"
#include "types.h"
#include "resctrl.h"
#include "resctrl_monitoring.h"
#include "resctrl_alloc.h"
#define RESCTRL_PATH_INFO_L3_MON RESCTRL_PATH_INFO"/L3_MON"
/**
* ---------------------------------------
* Local data structures
* ---------------------------------------
*/
static const struct pqos_cap *m_cap = NULL;
static const struct pqos_cpuinfo *m_cpu = NULL;
static int supported_events = 0;
static unsigned resctrl_mon_counter = 0;
/**
* @brief Filter directory filenames
*
* This function is used by the scandir function
* to filter hidden (dot) files
*
* @param dir dirent structure containing directory info
*
* @return if directory entry should be included in scandir() output list
* @retval 0 means don't include the entry ("." in our case)
* @retval 1 means include the entry
*/
static int
filter(const struct dirent *dir)
{
return (dir->d_name[0] == '.') ? 0 : 1;
}
/**
* @brief Update monitoring capability structure with supported events
*
* @param cap pqos capability structure
*
* @return Operational Status
* @retval PQOS_RETVAL_OK on success
*/
static void
set_mon_caps(const struct pqos_cap *cap)
{
int ret;
unsigned i;
const struct pqos_capability *p_cap = NULL;
ASSERT(cap != NULL);
/* find monitoring capability */
ret = pqos_cap_get_type(cap, PQOS_CAP_TYPE_MON, &p_cap);
if (ret != PQOS_RETVAL_OK)
return;
/* update capabilities structure */
for (i = 0; i < p_cap->u.mon->num_events; i++) {
struct pqos_monitor *mon = &p_cap->u.mon->events[i];
if (supported_events & mon->type)
mon->os_support = PQOS_OS_MON_RESCTRL;
}
}
int
resctrl_mon_init(const struct pqos_cpuinfo *cpu, const struct pqos_cap *cap)
{
int ret = PQOS_RETVAL_OK;
char buf[64];
FILE *fd;
struct stat st;
ASSERT(cpu != NULL);
ASSERT(cap != NULL);
/**
* Check if resctrl is mounted
*/
if (stat(RESCTRL_PATH_INFO, &st) != 0) {
ret = resctrl_mount(PQOS_REQUIRE_CDP_OFF, PQOS_REQUIRE_CDP_OFF);
if (ret != PQOS_RETVAL_OK) {
LOG_INFO("Unable to mount resctrl\n");
return PQOS_RETVAL_RESOURCE;
}
}
/**
* Resctrl monitiring not supported
*/
if (stat(RESCTRL_PATH_INFO_L3_MON, &st) != 0)
return PQOS_RETVAL_OK;
/**
* Discover supported events
*/
fd = fopen(RESCTRL_PATH_INFO_L3_MON"/mon_features", "r");
if (fd == NULL) {
LOG_ERROR("Failed to obtain resctrl monitoring features\n");
return PQOS_RETVAL_ERROR;
}
while (fgets(buf, sizeof(buf), fd) != NULL) {
if (strncmp(buf, "llc_occupancy\n", sizeof(buf)) == 0) {
LOG_INFO("Detected resctrl support for "
"LLC Occupancy\n");
supported_events |= PQOS_MON_EVENT_L3_OCCUP;
continue;
}
if (strncmp(buf, "mbm_local_bytes\n", sizeof(buf)) == 0) {
LOG_INFO("Detected resctrl support for "
"Local Memory B/W\n");
supported_events |= PQOS_MON_EVENT_LMEM_BW;
continue;
}
if (strncmp(buf, "mbm_total_bytes\n", sizeof(buf)) == 0) {
LOG_INFO("Detected resctrl support "
"Total Memory B/W\n");
supported_events |= PQOS_MON_EVENT_TMEM_BW;
}
}
if ((supported_events & PQOS_MON_EVENT_LMEM_BW) &&
(supported_events & PQOS_MON_EVENT_TMEM_BW))
supported_events |= PQOS_MON_EVENT_RMEM_BW;
fclose(fd);
/**
* Update mon capabilities
*/
set_mon_caps(cap);
m_cap = cap;
m_cpu = cpu;
return ret;
}
int
resctrl_mon_fini(void)
{
m_cap = NULL;
m_cpu = NULL;
return PQOS_RETVAL_OK;
}
/**
* @brief Get core association with ctrl group
*
* @param [in] lcore core id
* @param [out] class_id COS id
*
* @return Operational status
* @retval PQOS_RETVAL_OK on success
*/
static int
alloc_assoc_get(const unsigned lcore, unsigned *class_id)
{
int ret;
unsigned max_cos;
ASSERT(class_id != NULL);
ret = resctrl_alloc_get_grps_num(m_cap, &max_cos);
if (ret != PQOS_RETVAL_OK)
return ret;
if (max_cos == 0) {
*class_id = 0;
return PQOS_RETVAL_OK;
}
ret = resctrl_alloc_assoc_get(lcore, class_id);
if (ret != PQOS_RETVAL_OK)
LOG_ERROR("Failed to retrieve core %u association\n", lcore);
return ret;
}
/**
* @brief Get task association with ctrl group
*
* @param [in] tid task id
* @param [out] class_id COS id
*
* @return Operational status
* @retval PQOS_RETVAL_OK on success
*/
static int
alloc_assoc_get_pid(const pid_t tid, unsigned *class_id)
{
int ret;
unsigned max_cos;
ASSERT(class_id != NULL);
ret = resctrl_alloc_get_grps_num(m_cap, &max_cos);
if (ret != PQOS_RETVAL_OK)
return ret;
if (max_cos == 0) {
*class_id = 0;
return PQOS_RETVAL_OK;
}
ret = resctrl_alloc_assoc_get_pid(tid, class_id);
if (ret != PQOS_RETVAL_OK)
LOG_ERROR("Failed to retrieve task %d association\n", tid);
return ret;
}
/**
* @brief Obtain path to monitoring group
*
* @param [in] class_id COS id
* @param [in] resctrl_group mon group name
* @param [in] file file name insige group
* @param [out] buf Buffer to store path
* @param [in] buf_size buffer size
*/
static void
resctrl_mon_group_path(const unsigned class_id,
const char *resctrl_group,
const char *file,
char *buf,
const unsigned buf_size)
{
ASSERT(buf != NULL);
/* Group name not set - get path to mon_groups directory */
if (resctrl_group == NULL) {
if (class_id == 0)
snprintf(buf, buf_size, RESCTRL_PATH);
else
snprintf(buf, buf_size, RESCTRL_PATH"/COS%u",
class_id);
/* mon group for COS 0 */
} else if (class_id == 0)
snprintf(buf, buf_size, RESCTRL_PATH"/mon_groups/%s",
resctrl_group);
/* mon group for the other classes */
else
snprintf(buf, buf_size, RESCTRL_PATH"/COS%u/mon_groups/%s",
class_id, resctrl_group);
/* Append file name */
if (file != NULL)
strncat(buf, file, buf_size - strlen(buf));
}
/**
* @brief Write CPU mask to file
*
* @param [in] class_id COS id
* @param [in] resctrl_group mon group name
* @param [in] mask CPU mask to write
*
* @return Operational status
* @retval PQOS_RETVAL_OK on success
*/
static int
resctrl_mon_cpumask_write(const unsigned class_id,
const char *resctrl_group,
const struct resctrl_cpumask *mask)
{
FILE *fd;
char path[128];
int ret;
ASSERT(mask != NULL);
resctrl_mon_group_path(class_id, resctrl_group, "/cpus", path,
sizeof(path));
fd = fopen(path, "w");
if (fd == NULL)
return PQOS_RETVAL_ERROR;
ret = resctrl_cpumask_write(fd, mask);
if (fclose(fd) != 0)
return PQOS_RETVAL_ERROR;
return ret;
}
/**
* @brief Read CPU mask from file
*
* @param [in] class_id COS id
* @param [in] resctrl_group mon group name
* @param [out] mask CPU mask to write
*
* @return Operational status
* @retval PQOS_RETVAL_OK on success
*/
static int
resctrl_mon_cpumask_read(const unsigned class_id,
const char *resctrl_group,
struct resctrl_cpumask *mask)
{
FILE *fd;
char path[128];
int ret;
ASSERT(mask != NULL);
resctrl_mon_group_path(class_id, resctrl_group, "/cpus", path,
sizeof(path));
fd = fopen(path, "r");
if (fd == NULL)
return PQOS_RETVAL_ERROR;
ret = resctrl_cpumask_read(fd, mask);
fclose(fd);
return ret;
}
/**
* @brief Read counter value
*
* @param [in] class_id COS id
* @param [in] resctrl_group mon group name
* @param [in] event resctrl mon event
* @param [out] value counter value
*
* @return Operational status
* @retval PQOS_RETVAL_OK on success
*/
static int
resctrl_mon_read_counters(const unsigned class_id,
const char *resctrl_group,
const enum pqos_mon_event event,
uint64_t *value)
{
int ret = PQOS_RETVAL_OK;
unsigned *sockets = NULL;
unsigned sockets_num;
unsigned socket;
char buf[128];
const char *name;
ASSERT(resctrl_group != NULL);
ASSERT(value != NULL);
switch (event) {
case PQOS_MON_EVENT_L3_OCCUP:
name = "llc_occupancy";
break;
case PQOS_MON_EVENT_LMEM_BW:
name = "mbm_local_bytes";
break;
case PQOS_MON_EVENT_TMEM_BW:
name = "mbm_total_bytes";
break;
default:
LOG_ERROR("Unknown resctrl event\n");
return PQOS_RETVAL_PARAM;
break;
}
*value = 0;
resctrl_mon_group_path(class_id, resctrl_group, NULL, buf, sizeof(buf));
sockets = pqos_cpu_get_sockets(m_cpu, &sockets_num);
if (sockets == NULL) {
ret = PQOS_RETVAL_ERROR;
goto resctrl_mon_read_exit;
}
for (socket = 0; socket < sockets_num; socket++) {
char path[128];
FILE *fd;
unsigned long long counter;
snprintf(path, sizeof(path), "%s/mon_data/mon_L3_%02u/%s", buf,
sockets[socket], name);
fd = fopen(path, "r");
if (fd == NULL) {
ret = PQOS_RETVAL_ERROR;
goto resctrl_mon_read_exit;
}
if (fscanf(fd, "%llu", &counter) == 1)
*value += counter;
fclose(fd);
}
resctrl_mon_read_exit:
if (sockets != NULL)
free(sockets);
return ret;
}
/**
* @brief Check if mon group is empty (no cores/tasks assigned)
*
* @param [in] class_id COS id
* @param [in] resctrl_group mon group name
* @param [out] empty 1 when group is empty
*
* @return Operational status
* @retval PQOS_RETVAL_OK on success
*/
static int
resctrl_mon_empty(const unsigned class_id,
const char *resctrl_group,
int *empty)
{
int ret;
FILE *fd;
char path[128];
char buf[128];
struct resctrl_cpumask mask;
unsigned i;
unsigned max_threshold_occupancy;
uint64_t value;
ASSERT(resctrl_group != NULL);
ASSERT(empty != NULL);
*empty = 1;
/*
* Some cores are assigned to group?
*/
ret = resctrl_mon_cpumask_read(class_id, resctrl_group, &mask);
if (ret != PQOS_RETVAL_OK)
return ret;
for (i = 0; i < sizeof(mask.tab); i++)
if (mask.tab[i] > 0) {
*empty = 0;
return PQOS_RETVAL_OK;
}
/*
*Some tasks are assigned to group?
*/
resctrl_mon_group_path(class_id, resctrl_group, "/tasks", path,
sizeof(path));
fd = fopen(path, "r");
if (fd == NULL)
return PQOS_RETVAL_ERROR;
/* Search tasks file for any task ID */
memset(buf, 0, sizeof(buf));
if (fgets(buf, sizeof(buf), fd) != NULL) {
*empty = 0;
fclose(fd);
return PQOS_RETVAL_OK;
}
fclose(fd);
/*
* Check if llc occupancy is lower than max_occupancy_threshold
*/
if ((supported_events & PQOS_MON_EVENT_L3_OCCUP) == 0)
return PQOS_RETVAL_OK;
ret = resctrl_mon_read_counters(class_id, resctrl_group,
PQOS_MON_EVENT_L3_OCCUP, &value);
if (ret != PQOS_RETVAL_OK)
return ret;
fd = fopen(RESCTRL_PATH"/info/L3_MON/max_threshold_occupancy", "r");
if (fd == NULL)
return PQOS_RETVAL_ERROR;
if (fscanf(fd, "%u", &max_threshold_occupancy) != 1)
ret = PQOS_RETVAL_ERROR;
else if (value > max_threshold_occupancy)
*empty = 0;
fclose(fd);
return PQOS_RETVAL_OK;
}
/**
* @brief Create directory if not exists
*
* @param[in] path directory path
*
* @return Operation status
* @retval PQOS_RETVAL_OK on success
*/
static int
resctrl_mon_mkdir(const char *path)
{
ASSERT(path != NULL);
if (mkdir(path, 0755) == -1 && errno != EEXIST)
return PQOS_RETVAL_BUSY;
return PQOS_RETVAL_OK;
}
/**
* @brief Remove directory if exists
*
* @param[in] path directory path
*
* @return Operation status
* @retval PQOS_RETVAL_OK on success
*/
static int
resctrl_mon_rmdir(const char *path)
{
ASSERT(path != NULL);
if (rmdir(path) == -1 && errno != ENOENT)
return PQOS_RETVAL_ERROR;
return PQOS_RETVAL_OK;
}
int
resctrl_mon_assoc_get(const unsigned lcore,
char *name,
const unsigned name_size)
{
int ret;
unsigned class_id;
char dir[256];
struct dirent **namelist = NULL;
int num_groups;
int i;
ASSERT(name != NULL);
if (supported_events == 0)
return PQOS_RETVAL_RESOURCE;
ret = alloc_assoc_get(lcore, &class_id);
if (ret != PQOS_RETVAL_OK)
return ret;
resctrl_mon_group_path(class_id, "", NULL, dir, sizeof(dir));
num_groups = scandir(dir, &namelist, filter, NULL);
if (num_groups < 0) {
LOG_ERROR("Failed to read monitoring groups for COS %u\n",
class_id);
return PQOS_RETVAL_ERROR;
}
for (i = 0; i < num_groups; i++) {
struct resctrl_cpumask mask;
ret = resctrl_mon_cpumask_read(class_id, namelist[i]->d_name,
&mask);
if (ret != PQOS_RETVAL_OK)
goto resctrl_mon_assoc_get_exit;
if (resctrl_cpumask_get(lcore, &mask)) {
strncpy(name, namelist[i]->d_name, name_size);
ret = PQOS_RETVAL_OK;
goto resctrl_mon_assoc_get_exit;
}
}
/* Core not associated with mon group */
ret = PQOS_RETVAL_RESOURCE;
resctrl_mon_assoc_get_exit:
free(namelist);
return ret;
}
int
resctrl_mon_assoc_set(const unsigned lcore, const char *name)
{
unsigned class_id = 0;
int ret;
char path[128];
struct resctrl_cpumask cpumask;
ASSERT(name != NULL);
ret = alloc_assoc_get(lcore, &class_id);
if (ret != PQOS_RETVAL_OK)
return ret;
resctrl_mon_group_path(class_id, name, NULL, path, sizeof(path));
ret = resctrl_mon_mkdir(path);
if (ret != PQOS_RETVAL_OK)
return ret;
ret = resctrl_mon_cpumask_read(class_id, name, &cpumask);
if (ret != PQOS_RETVAL_OK)
return ret;
resctrl_cpumask_set(lcore, &cpumask);
ret = resctrl_mon_cpumask_write(class_id, name, &cpumask);
if (ret != PQOS_RETVAL_OK)
LOG_ERROR("Could not assign core %u to resctrl monitoring "
"group\n", lcore);
return ret;
}
/* @brief Restore association of \a lcore to monitoring group
*
* @param [in] lcore CPU logical core id
* @param [in] name name of monitoring group
*
* @return Operational status
* @retval PQOS_RETVAL_OK on success
*/
static int
resctrl_mon_assoc_restore(const unsigned lcore, const char *name)
{
int ret;
char buf[128];
unsigned class_id;
struct stat st;
ASSERT(name != NULL);
ret = resctrl_mon_assoc_get(lcore, buf, sizeof(buf));
/* core already associated with mon group */
if (ret == PQOS_RETVAL_OK)
return PQOS_RETVAL_OK;
ret = alloc_assoc_get(lcore, &class_id);
if (ret != PQOS_RETVAL_OK)
return ret;
resctrl_mon_group_path(class_id, name, NULL, buf, sizeof(buf));
if (stat(buf, &st) != 0) {
LOG_WARN("Could not restore core association with mon "
"group %s does not exists\n", buf);
return PQOS_RETVAL_RESOURCE;
}
ret = resctrl_mon_assoc_set(lcore, name);
return ret;
}
int
resctrl_mon_assoc_get_pid(const pid_t task,
char *name,
const unsigned name_size)
{
int ret;
unsigned class_id;
char dir[256];
struct dirent **namelist = NULL;
int num_groups;
int i;
ASSERT(name != NULL);
if (supported_events == 0)
return PQOS_RETVAL_RESOURCE;
ret = alloc_assoc_get_pid(task, &class_id);
if (ret != PQOS_RETVAL_OK)
return ret;
resctrl_mon_group_path(class_id, "", NULL, dir, sizeof(dir));
num_groups = scandir(dir, &namelist, filter, NULL);
if (num_groups < 0) {
LOG_ERROR("Failed to read monitoring groups for COS %u\n",
class_id);
return PQOS_RETVAL_ERROR;
}
for (i = 0; i < num_groups; i++) {
char path[256];
char buf[128];
FILE *fd;
resctrl_mon_group_path(class_id, namelist[i]->d_name, "/tasks",
path, sizeof(path));
fd = fopen(path, "r");
if (fd == NULL)
goto resctrl_mon_assoc_get_pid_exit;
while (fgets(buf, sizeof(buf), fd) != NULL) {
char *endptr = NULL;
pid_t value = strtol(buf, &endptr, 10);
if (!(*buf != '\0' &&
(*endptr == '\0' || *endptr == '\n'))) {
ret = PQOS_RETVAL_ERROR;
fclose(fd);
goto resctrl_mon_assoc_get_pid_exit;
}
if (value == task) {
strncpy(name, namelist[i]->d_name, name_size);
ret = PQOS_RETVAL_OK;
fclose(fd);
goto resctrl_mon_assoc_get_pid_exit;
}
}
fclose(fd);
}
/* Task not associated with mon group */
ret = PQOS_RETVAL_RESOURCE;
resctrl_mon_assoc_get_pid_exit:
free(namelist);
return ret;
}
int
resctrl_mon_assoc_set_pid(const pid_t task, const char *name)
{
unsigned class_id;
int ret;
char path[128];
FILE *fd;
ASSERT(name != NULL);
ret = alloc_assoc_get_pid(task, &class_id);
if (ret != PQOS_RETVAL_OK)
return ret;
resctrl_mon_group_path(class_id, name, NULL, path, sizeof(path));
ret = resctrl_mon_mkdir(path);
if (ret != PQOS_RETVAL_OK) {
LOG_ERROR("Failed to create resctrl monitoring group!\n");
return ret;
}
strncat(path, "/tasks", sizeof(path) - strlen(path) - 1);
fd = fopen(path, "w");
if (fd == NULL)
return PQOS_RETVAL_ERROR;
fprintf(fd, "%d\n", task);
if (fclose(fd) != 0) {
LOG_ERROR("Could not assign TID %d to resctrl monitoring "
"group\n", task);
return PQOS_RETVAL_ERROR;
}
return PQOS_RETVAL_OK;
}
int
resctrl_mon_start(struct pqos_mon_data *group)
{
char *resctrl_group = NULL;
char buf[128];
int ret = PQOS_RETVAL_OK;
unsigned i;
ASSERT(group != NULL);
ASSERT(group->tid_nr == 0 || group->tid_map != NULL);
ASSERT(group->num_cores == 0 || group->cores != NULL);
/**
* Create new monitoring gorup
*/
if (group->resctrl_mon_group == NULL) {
snprintf(buf, sizeof(buf), "%d-%u",
(int)getpid(), resctrl_mon_counter++);
resctrl_group = strdup(buf);
if (resctrl_group == NULL) {
LOG_DEBUG("Memory allocation failed\n");
ret = PQOS_RETVAL_ERROR;
goto resctrl_mon_start_exit;
}
/**
* Reuse group
*/
} else
resctrl_group = group->resctrl_mon_group;
/**
* Add pids to the resctrl group
*/
for (i = 0; i < group->tid_nr; i++) {
ret = resctrl_mon_assoc_set_pid(group->tid_map[i],
resctrl_group);
if (ret != PQOS_RETVAL_OK)
goto resctrl_mon_start_exit;
}
/**
* Add cores to the resctrl group
*/
for (i = 0; i < group->num_cores; i++) {
ret = resctrl_mon_assoc_set(group->cores[i], resctrl_group);
if (ret != PQOS_RETVAL_OK)
goto resctrl_mon_start_exit;
}
group->resctrl_mon_group = resctrl_group;
resctrl_mon_start_exit:
if (ret != PQOS_RETVAL_OK && group->resctrl_mon_group != resctrl_group)
free(resctrl_group);
return ret;
}
/**
* @brief Function to stop resctrl event counters
*
* @param group monitoring structure
*
* @return Operation status
* @retval PQOS_RETVAL_OK on success
*/
int
resctrl_mon_stop(struct pqos_mon_data *group)
{
int ret;
unsigned max_cos;
unsigned cos;
unsigned i;
ASSERT(group != NULL);
ret = resctrl_alloc_get_grps_num(m_cap, &max_cos);
if (ret != PQOS_RETVAL_OK)
return ret;
if (group->resctrl_mon_group != NULL) {
cos = 0;
do {
char buf[128];
resctrl_mon_group_path(cos, group->resctrl_mon_group,
NULL, buf, sizeof(buf));
ret = resctrl_mon_rmdir(buf);
if (ret != PQOS_RETVAL_OK) {
LOG_ERROR("Failed to remove resctrl "
"monitoring group\n");
goto resctrl_mon_stop_exit;
}
} while (++cos < max_cos);
free(group->resctrl_mon_group);
group->resctrl_mon_group = NULL;
} else if (group->num_pids > 0) {
/**
* Add pids to the default group
*/
for (i = 0; i < group->tid_nr; i++) {
const pid_t tid = group->tid_map[i];
if (resctrl_alloc_task_validate(tid) !=
PQOS_RETVAL_OK) {
LOG_DEBUG("resctrl_mon_stop: Skipping "
"non-existant PID: %d\n", tid);
continue;
}
ret = resctrl_mon_assoc_set_pid(tid, NULL);
if (ret != PQOS_RETVAL_OK)
goto resctrl_mon_stop_exit;
}
} else
return PQOS_RETVAL_PARAM;
resctrl_mon_stop_exit:
return ret;
}
/**
* @brief Gives the difference between two values with regard to the possible
* overrun
*
* @param old_value previous value
* @param new_value current value
* @return difference between the two values
*/
static uint64_t
get_delta(const uint64_t old_value, const uint64_t new_value)
{
if (old_value > new_value)
return (UINT64_MAX - old_value) + new_value;
else
return new_value - old_value;
}
/**
* @brief This function removes all empty monitoring
* groups associated with \a group
*
* @param group monitoring structure
*
* @return Operation status
* @retval PQOS_RETVAL_OK on success
* @retval PQOS_RETVAL_ERROR if error occurs
*/
static int
resctrl_mon_purge(struct pqos_mon_data *group)
{
unsigned max_cos;
unsigned cos;
int ret;
ASSERT(group != NULL);
ASSERT(m_cap != NULL);
ret = resctrl_alloc_get_grps_num(m_cap, &max_cos);
if (ret != PQOS_RETVAL_OK)
return ret;
cos = 0;
do {
int empty;
struct stat st;
uint64_t value;
char buf[128];
resctrl_mon_group_path(cos, group->resctrl_mon_group, NULL, buf,
sizeof(buf));
if (stat(buf, &st) != 0)
continue;
ret = resctrl_mon_empty(cos, group->resctrl_mon_group, &empty);
if (ret != PQOS_RETVAL_OK)
return ret;
if (!empty)
continue;
/* store counter values */
if (supported_events & PQOS_MON_EVENT_LMEM_BW) {
ret = resctrl_mon_read_counters(cos,
group->resctrl_mon_group,
PQOS_MON_EVENT_LMEM_BW, &value);
if (ret != PQOS_RETVAL_OK)
return ret;
group->resctrl_values_storage.mbm_local += value;
}
if (supported_events & PQOS_MON_EVENT_TMEM_BW) {
ret = resctrl_mon_read_counters(cos,
group->resctrl_mon_group,
PQOS_MON_EVENT_TMEM_BW, &value);
if (ret != PQOS_RETVAL_OK)
return ret;
group->resctrl_values_storage.mbm_total += value;
}
ret = resctrl_mon_rmdir(buf);
if (ret != PQOS_RETVAL_OK) {
LOG_WARN("Failed to remove empty mon group %s: %m\n",
buf);
return ret;
}
LOG_INFO("Deleted empty mon group %s\n", buf);
} while (++cos < max_cos);
return ret;
}
/**
* @brief This function polls all resctrl counters
*
* Reads counters for all events and stores values
*
* @param group monitoring structure
*
* @return Operation status
* @retval PQOS_RETVAL_OK on success
* @retval PQOS_RETVAL_ERROR if error occurs
*/
int
resctrl_mon_poll(struct pqos_mon_data *group, const enum pqos_mon_event event)
{
int ret;
uint64_t value = 0;
unsigned max_cos;
unsigned cos;
unsigned i;
uint64_t old_value;
ASSERT(group != NULL);
ASSERT(m_cap != NULL);
ret = resctrl_alloc_get_grps_num(m_cap, &max_cos);
if (ret != PQOS_RETVAL_OK)
return ret;
/*
* When core COS assoc changes then kernel resets monitoring group
* assoc. We need to restore monitoring assoc for cores
*/
for (i = 0; i < group->num_cores; i++) {
ret = resctrl_mon_assoc_restore(group->cores[i],
group->resctrl_mon_group);
if (ret != PQOS_RETVAL_OK)
goto resctrl_mon_poll_exit;
}
/* Search COSes for given resctrl mon group */
cos = 0;
do {
uint64_t val;
struct stat st;
char buf[128];
resctrl_mon_group_path(cos, group->resctrl_mon_group, NULL, buf,
sizeof(buf));
if (stat(buf, &st) != 0)
continue;
ret = resctrl_mon_read_counters(cos, group->resctrl_mon_group,
event, &val);
if (ret != PQOS_RETVAL_OK)
goto resctrl_mon_poll_exit;
value += val;
} while (++cos < max_cos);
/**
* Set value
*/
switch (event) {
case PQOS_MON_EVENT_L3_OCCUP:
group->values.llc = value;
break;
case PQOS_MON_EVENT_LMEM_BW:
old_value = group->values.mbm_local;
group->values.mbm_local =
value + group->resctrl_values_storage.mbm_local;
group->values.mbm_local_delta =
get_delta(old_value, group->values.mbm_local);
break;
case PQOS_MON_EVENT_TMEM_BW:
old_value = group->values.mbm_total;
group->values.mbm_total =
value + group->resctrl_values_storage.mbm_total;
group->values.mbm_total_delta =
get_delta(old_value, group->values.mbm_total);
break;
default:
return PQOS_RETVAL_ERROR;
}
/*
* If this group is empty, save the values for
* next poll and clear the group.
*/
ret = resctrl_mon_purge(group);
if (ret != PQOS_RETVAL_OK)
goto resctrl_mon_poll_exit;
resctrl_mon_poll_exit:
return ret;
}
int
resctrl_mon_reset(void)
{
int ret;
unsigned grps;
int num_groups;
unsigned cos = 0;
if (supported_events == 0)
return PQOS_RETVAL_RESOURCE;
ret = resctrl_alloc_get_grps_num(m_cap, &grps);
if (ret != PQOS_RETVAL_OK)
return ret;
do {
struct dirent **namelist = NULL;
char dir[256];
int i;
resctrl_mon_group_path(cos, "", NULL, dir, sizeof(dir));
num_groups = scandir(dir, &namelist, filter, NULL);
if (num_groups < 0) {
LOG_ERROR("Failed to read monitoring groups for "
"COS %u\n", cos);
return PQOS_RETVAL_ERROR;
}
for (i = 0; i < num_groups; i++) {
char path[256];
resctrl_mon_group_path(cos, namelist[i]->d_name, NULL,
path, sizeof(path));
ret = resctrl_mon_rmdir(path);
if (ret != PQOS_RETVAL_OK) {
free(namelist);
return ret;
}
}
free(namelist);
} while (++cos < grps);
return ret;
}
int
resctrl_mon_is_event_supported(const enum pqos_mon_event event)
{
return (supported_events & event) == event;
}
int
resctrl_mon_active(unsigned *monitoring_status)
{
unsigned group_idx = 0;
unsigned resctrl_group_count = 0;
int ret;
if (supported_events == 0) {
*monitoring_status = 0;
return PQOS_RETVAL_OK;
}
ret = resctrl_alloc_get_grps_num(m_cap, &resctrl_group_count);
if (ret != PQOS_RETVAL_OK) {
LOG_ERROR("Failed to count resctrl groups");
return ret;
}
do {
int files_count;
char path[256];
struct dirent **mon_group_files = NULL;
resctrl_mon_group_path(group_idx, "", NULL, path, DIM(path));
/* check content of mon_groups directory */
files_count = scandir(path, &mon_group_files, filter, NULL);
free(mon_group_files);
if (files_count < 0) {
LOG_ERROR("Could not scan %s directory!\n", path);
return PQOS_RETVAL_ERROR;
} else if (files_count > 0) {
/* directory is not empty - monitoring is active */
*monitoring_status = 1;
return PQOS_RETVAL_OK;
}
} while (++group_idx < resctrl_group_count);
*monitoring_status = 0;
return PQOS_RETVAL_OK;
}