blob: 16a07e0286359d22b1ff8247312283fb2847bd13 [file] [edit]
/*****************************************************************************\
* resctrl_fs.c - helpers for the Linux resctrl filesystem
*****************************************************************************
* Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include "config.h"
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <mntent.h>
#include <stdio.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "slurm/slurm.h"
#include "src/common/log.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/plugins/task/resctrl/resctrl_fs.h"
/* As defined in /proc/filesystems */
#define RESCTRL_FS_TYPE "resctrl"
extern char *resctrl_fs_path(const char *group, const char *file)
{
char *path = xstrdup(RESCTRL_MOUNT_POINT);
if (group)
xstrfmtcat(path, "/%s", group);
if (file)
xstrfmtcat(path, "/%s", file);
return path;
}
extern int resctrl_fs_read_file(const char *path, char **content)
{
int fd = -1, rc = SLURM_SUCCESS;
char *data = NULL;
size_t size = 0, buf_size = 4096;
if ((fd = open(path, (O_RDONLY | O_CLOEXEC))) < 0)
return errno;
data = xmalloc(buf_size + 1);
while (true) {
ssize_t n = read(fd, (data + size), (buf_size - size));
if (n < 0) {
rc = errno;
break;
} else if (!n) {
break;
}
size += n;
if (size == buf_size) {
buf_size *= 2;
xrealloc(data, (buf_size + 1));
}
}
close(fd);
if (rc) {
xfree(data);
return rc;
}
data[size] = '\0';
*content = data;
return SLURM_SUCCESS;
}
extern int resctrl_fs_write_file(const char *path, const char *content)
{
int fd = -1, rc = SLURM_SUCCESS;
size_t len = strlen(content);
ssize_t n = -1;
if ((fd = open(path, (O_WRONLY | O_TRUNC | O_CLOEXEC))) < 0)
return errno;
if ((n = write(fd, content, len)) < 0)
rc = errno;
else if ((size_t) n != len)
rc = EIO;
close(fd);
return rc;
}
extern bool resctrl_fs_supported(void)
{
FILE *fp = NULL;
char line[256];
bool supported = false;
if (!(fp = fopen("/proc/filesystems", "r")))
return false;
/*
* We are looking for a line like:
* "nodev resctrl"
*/
while (fgets(line, sizeof(line), fp)) {
char *type = xstrchr(line, '\t');
if (type && !xstrncmp((type + 1), RESCTRL_FS_TYPE "\n",
strlen(RESCTRL_FS_TYPE "\n"))) {
supported = true;
break;
}
}
fclose(fp);
return supported;
}
/*
* The kernel allows a single resctrl mount, but bind mounts of /sys can
* make it appear more than once.
* RET xmalloc()'d directory resctrl is mounted on, RESCTRL_MOUNT_POINT when
* it is one of them, or NULL
*/
static char *_get_mount_dir(void)
{
FILE *fp = NULL;
struct mntent mnt = { 0 };
struct mntent *ent = NULL;
char line[4096];
char *dir = NULL;
if (!(fp = setmntent("/proc/self/mounts", "r")))
return NULL;
while ((ent = getmntent_r(fp, &mnt, line, sizeof(line)))) {
if (xstrcmp(ent->mnt_type, RESCTRL_FS_TYPE))
continue;
if (!xstrcmp(ent->mnt_dir, RESCTRL_MOUNT_POINT)) {
xfree(dir);
dir = xstrdup(ent->mnt_dir);
break;
}
if (!dir)
dir = xstrdup(ent->mnt_dir);
}
endmntent(fp);
return dir;
}
extern bool resctrl_fs_is_mounted(void)
{
char *dir = _get_mount_dir();
bool mounted = !xstrcmp(dir, RESCTRL_MOUNT_POINT);
xfree(dir);
return mounted;
}
extern int resctrl_fs_mount(const char *options)
{
char *dir = _get_mount_dir();
int rc = SLURM_SUCCESS;
if (dir) {
if (xstrcmp(dir, RESCTRL_MOUNT_POINT)) {
error("resctrl is mounted on %s instead of %s",
dir, RESCTRL_MOUNT_POINT);
rc = EBUSY;
}
xfree(dir);
return rc;
}
if (mount(RESCTRL_FS_TYPE, RESCTRL_MOUNT_POINT, RESCTRL_FS_TYPE, 0,
options)) {
rc = errno;
error("cannot mount resctrl on %s: %s",
RESCTRL_MOUNT_POINT, slurm_strerror(rc));
}
return rc;
}
extern bool resctrl_fs_group_exists(const char *group)
{
char *path = resctrl_fs_path(group, "tasks");
struct stat st = { 0 };
bool exists = !stat(path, &st);
xfree(path);
return exists;
}
extern list_t *resctrl_fs_list_groups(void)
{
char *root = resctrl_fs_path(NULL, NULL);
list_t *groups = list_create(xfree_ptr);
DIR *dir = NULL;
struct dirent *ent = NULL;
if (!(dir = opendir(root))) {
xfree(root);
return groups;
}
while ((ent = readdir(dir))) {
if ((ent->d_name[0] == '.') || !xstrcmp(ent->d_name, "info") ||
!xstrcmp(ent->d_name, "mon_data") ||
!xstrcmp(ent->d_name, "mon_groups"))
continue;
if (resctrl_fs_group_exists(ent->d_name))
list_append(groups, xstrdup(ent->d_name));
}
closedir(dir);
xfree(root);
return groups;
}
extern uint32_t resctrl_fs_max_groups(void)
{
char *info = resctrl_fs_path("info", NULL);
DIR *dir = NULL;
struct dirent *ent = NULL;
uint32_t max_groups = 0;
if (!(dir = opendir(info))) {
xfree(info);
return 0;
}
while ((ent = readdir(dir))) {
char *path = NULL, *content = NULL;
unsigned long num_closids = 0;
if (ent->d_name[0] == '.')
continue;
path = xstrdup_printf("%s/%s/num_closids", info, ent->d_name);
if (!resctrl_fs_read_file(path, &content)) {
num_closids = strtoul(content, NULL, 10);
if (num_closids && (num_closids <= UINT32_MAX) &&
(!max_groups || (num_closids < max_groups)))
max_groups = num_closids;
xfree(content);
}
xfree(path);
}
closedir(dir);
xfree(info);
return max_groups;
}
extern int resctrl_fs_group_create(const char *group)
{
char *path = resctrl_fs_path(group, NULL);
int rc = SLURM_SUCCESS;
if (mkdir(path, 0755) < 0)
rc = errno;
xfree(path);
return rc;
}
extern int resctrl_fs_write_schemata(const char *group, const char *schemata)
{
char *path = resctrl_fs_path(group, "schemata");
char *content = xstrdup(schemata);
size_t len = strlen(content);
int rc = EINVAL;
if (len && (content[len - 1] != '\n'))
xstrcat(content, "\n");
rc = resctrl_fs_write_file(path, content);
xfree(content);
xfree(path);
return rc;
}
/*
* Write every thread listed in a /proc/<pid>/task directory to a tasks file.
* IN task_dir - /proc/<pid>/task of the process
* IN tasks - tasks file of the group
* RET SLURM_SUCCESS or errno
*/
static int _move_threads(const char *task_dir, const char *tasks)
{
DIR *dir = NULL;
struct dirent *ent = NULL;
int rc = SLURM_SUCCESS;
if (!(dir = opendir(task_dir)))
return errno;
while ((ent = readdir(dir))) {
if (!isdigit((unsigned char) ent->d_name[0]))
continue;
rc = resctrl_fs_write_file(tasks, ent->d_name);
if (rc == ESRCH) {
/* The thread exited in the meantime. */
rc = SLURM_SUCCESS;
continue;
}
if (rc)
break;
}
closedir(dir);
return rc;
}
extern int resctrl_fs_move_pid(const char *group, pid_t pid)
{
char *task_dir = NULL;
char *tasks = resctrl_fs_path(group, "tasks");
int rc = EINVAL;
/* The kernel takes 0 as the calling thread, so this slurmstepd. */
if (!pid) {
rc = resctrl_fs_write_file(tasks, "0");
xfree(tasks);
return rc;
}
/*
* We read the /proc/<pid>/task/ directory to find all the threads of
* a given pid. We then move these threads, but there's a race where
* a new thread can be created in the process. So do this operation
* twice. This should mitigate the race.
*/
task_dir = xstrdup_printf("/proc/%d/task", (int) pid);
if (!(rc = _move_threads(task_dir, tasks)))
rc = _move_threads(task_dir, tasks);
xfree(task_dir);
xfree(tasks);
return rc;
}
extern char *resctrl_fs_last_cmd_status(void)
{
char *path = resctrl_fs_path("info", "last_cmd_status");
char *status = NULL;
if (resctrl_fs_read_file(path, &status))
status = NULL;
xfree(path);
if (status)
xstrtrim(status);
return status;
}