/* * BSD LICENSE * * Copyright(c) 2019-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. */ #include #include #include #include "pqos.h" #include "common.h" #include "os_cap.h" #include "types.h" #include "log.h" #include "resctrl.h" #include "resctrl_alloc.h" #include "perf_monitoring.h" #define PROC_CPUINFO "/proc/cpuinfo" #define PROC_FILESYSTEMS "/proc/filesystems" #define PROC_MOUNTS "/proc/mounts" static int mba_ctrl = -1; /**< mba ctrl support */ /** * @brief Checks file fname to detect str and sets a flag * * @param [in] fname name of the file to be searched * @param [in] str string being searched for * @param [in] check_symlink a flag indicating if a path must be checked * against symlinks * @param [out] supported pointer to os_supported flag * * @return Operation status * @retval PQOS_RETVAL_OK success */ static int detect_os_support(const char *fname, const char *str, int check_symlink, int *supported) { FILE *fd; char temp[1024]; if (fname == NULL || str == NULL || supported == NULL) return PQOS_RETVAL_PARAM; if (check_symlink) fd = pqos_fopen(fname, "r"); else fd = fopen(fname, "r"); if (fd == NULL) { LOG_DEBUG("%s not found.\n", fname); *supported = 0; return PQOS_RETVAL_OK; } while (fgets(temp, sizeof(temp), fd) != NULL) { if (strstr(temp, str) != NULL) { *supported = 1; fclose(fd); return PQOS_RETVAL_OK; } } fclose(fd); return PQOS_RETVAL_OK; } /** * @brief Read uint64 from file * * @param [in] fname name of the file * @param [in] base numerical base * @param [out] value UINT value * * @return Operation status * @retval PQOS_RETVAL_OK success */ static int readuint64(const char *fname, unsigned base, uint64_t *value) { FILE *fd; char buf[16] = "\0"; char *s = buf; char *endptr = NULL; size_t bytes; ASSERT(fname != NULL); ASSERT(value != NULL); fd = pqos_fopen(fname, "r"); if (fd == NULL) return PQOS_RETVAL_ERROR; bytes = fread(buf, sizeof(buf) - 1, 1, fd); if (bytes == 0 && !feof(fd)) { fclose(fd); return PQOS_RETVAL_ERROR; } fclose(fd); *value = strtoull(s, &endptr, base); if (!((*s != '\0' && *s != '\n') && (*endptr == '\0' || *endptr == '\n'))) { LOG_ERROR("Error converting '%s' to unsigned number!\n", buf); return PQOS_RETVAL_ERROR; } return PQOS_RETVAL_OK; } /** * @brief Retrieves number of closids * * @param [in] dir path to info directory * @param [out] num_closids place to store retrieved value * * @return Operation status * @retval PQOS_RETVAL_OK on success */ static int get_num_closids(const char *dir, unsigned *num_closids) { char path[128]; int ret; uint64_t val; snprintf(path, sizeof(path) - 1, "%s/num_closids", dir); ret = readuint64(path, 10, &val); if (ret == PQOS_RETVAL_OK) *num_closids = val; return ret; } /** * @brief Retrieves number of ways * * @param [in] dir path to info directory * @param [out] num_ways place to store retrieved value * * @return Operation status * @retval PQOS_RETVAL_OK on success */ static int get_num_ways(const char *dir, unsigned *num_ways) { char path[128]; int ret; uint64_t val; snprintf(path, sizeof(path) - 1, "%s/cbm_mask", dir); ret = readuint64(path, 16, &val); if (ret == PQOS_RETVAL_OK) { *num_ways = 0; while (val > 0) { (*num_ways)++; val = val >> 1; } } return ret; } /** * @brief Retrieves shareable bit mask * * @param [in] dir path to info directory * @param [out] shareable_bits place to store retrieved value * * @return Operation status * @retval PQOS_RETVAL_OK on success */ static int get_shareable_bits(const char *dir, uint64_t *shareable_bits) { char path[128]; ASSERT(dir != NULL); snprintf(path, sizeof(path) - 1, "%s/shareable_bits", dir); /* Information not present in info dir */ if (access(path, F_OK) != 0) { LOG_DEBUG("Unable to obtain ways contention bit-mask, %s file " "does not exist\n", path); *shareable_bits = 0; return PQOS_RETVAL_OK; } return readuint64(path, 16, shareable_bits); } int os_cap_init(const enum pqos_interface inter) { int ret; int res_flag = 0; struct stat st; /** * resctrl detection */ ret = detect_os_support(PROC_FILESYSTEMS, "resctrl", 0, &res_flag); if (ret != PQOS_RETVAL_OK) { LOG_ERROR("Fatal error encountered in resctrl detection!\n"); return ret; } LOG_INFO("%s\n", res_flag ? "resctrl detected" : "resctrl not detected. " "Kernel version 4.10 or higher required"); if (res_flag == 0) { LOG_ERROR("OS interface selected but not supported\n"); return PQOS_RETVAL_INTER; } /** * Mount resctrl with default parameters */ if (access(RESCTRL_PATH "/cpus", F_OK) != 0) { LOG_INFO("resctrl not mounted\n"); /** * Check if it is possible to enable MBA CTRL */ ret = resctrl_mount(PQOS_REQUIRE_CDP_OFF, PQOS_REQUIRE_CDP_OFF, PQOS_MBA_CTRL); if (ret == PQOS_RETVAL_OK) { FILE *fd; /* Verify possibility of setting mba value above 100 */ fd = resctrl_alloc_fopen(0, "schemata", "w"); if (fd != NULL) { fprintf(fd, "MB:0=200\n"); if (fclose(fd) == 0) mba_ctrl = 1; else mba_ctrl = 0; } resctrl_umount(); } else mba_ctrl = 0; ret = resctrl_mount(PQOS_REQUIRE_CDP_OFF, PQOS_REQUIRE_CDP_OFF, PQOS_MBA_DEFAULT); if (ret != PQOS_RETVAL_OK) { LOG_INFO("Unable to mount resctrl\n"); return PQOS_RETVAL_RESOURCE; } } if (inter == PQOS_INTER_OS_RESCTRL_MON && stat(RESCTRL_PATH_INFO_L3_MON, &st) != 0) { LOG_ERROR("Resctrl monitoring selected but not supported\n"); return PQOS_RETVAL_INTER; } return ret; } /** * @brief Checks if event is supported by resctrl monitoring * * @param [in] event monitoring event type * @param [out] supported set to 1 if resctrl support is present * @param [out] scale scale factor * * @return Operation status * @retval PQOS_RETVAL_OK success */ static int detect_mon_resctrl_support(const enum pqos_mon_event event, int *supported, uint32_t *scale) { struct stat st; const char *event_name = NULL; int ret; ASSERT(supported != NULL); *supported = 0; /* resctrl monitoring is not supported */ if (stat(RESCTRL_PATH_INFO_L3_MON, &st) != 0) return PQOS_RETVAL_OK; switch (event) { case PQOS_MON_EVENT_L3_OCCUP: event_name = "llc_occupancy"; break; case PQOS_MON_EVENT_TMEM_BW: event_name = "mbm_total_bytes"; break; case PQOS_MON_EVENT_LMEM_BW: event_name = "mbm_local_bytes"; break; default: return PQOS_RETVAL_OK; break; } ret = detect_os_support(RESCTRL_PATH_INFO_L3_MON "/mon_features", event_name, 1, supported); if (scale != NULL) *scale = 1; return ret; } /** * @brief Reads scale factor of perf monitoring event * * @param [in] event_name perf monitoring event name * @param [out] scale scale factor * * @return Operation status * @retval PQOS_RETVAL_OK success */ static int get_mon_perf_scale_factor(const char *event_name, uint32_t *scale) { char path[128]; char buf[16]; double scale_factor; unsigned unit = 1; int ret; FILE *fd; ASSERT(scale != NULL); ASSERT(event_name != NULL); /* read scale factor value */ snprintf(path, sizeof(path) - 1, PERF_MON_PATH "/events/%s.scale", event_name); fd = pqos_fopen(path, "r"); if (fd == NULL) { LOG_ERROR("Failed to open %s perf monitoring event scale " "file!\n", event_name); return PQOS_RETVAL_ERROR; } ret = fscanf(fd, "%10lf", &scale_factor); fclose(fd); if (ret < 1) { LOG_ERROR("Failed to read %s perf monitoring event scale " "factor!\n", event_name); return PQOS_RETVAL_ERROR; } /* read scale factor unit */ snprintf(path, sizeof(path) - 1, PERF_MON_PATH "/events/%s.unit", event_name); fd = pqos_fopen(path, "r"); if (fd == NULL) { LOG_ERROR("Failed to open %s perf monitoring event unit " "file!\n", event_name); return PQOS_RETVAL_ERROR; } if (fgets(buf, sizeof(buf), fd) != NULL) { if (strncmp(buf, "Bytes", sizeof(buf)) == 0) unit = 1; else if (strncmp(buf, "MB", sizeof(buf)) == 0) unit = 1000000; else { LOG_ERROR("Unknown \"%s\" scale factor unit", buf); fclose(fd); return PQOS_RETVAL_ERROR; } } else { LOG_ERROR("Failed to read %s perf monitoring event unit!\n", event_name); fclose(fd); return PQOS_RETVAL_ERROR; } fclose(fd); *scale = (uint32_t)(scale_factor * unit); return PQOS_RETVAL_OK; } /** * @brief Checks if event is supported by perf * * @param [in] event monitoring event type * @param [out] supported set to 1 if perf support is present * @param [out] scale scale factor * * @return Operation status * @retval PQOS_RETVAL_OK success */ static int detect_mon_perf_support(const enum pqos_mon_event event, int *supported, uint32_t *scale) { char path[128]; struct stat st; const char *event_name = NULL; int ret; static int warn = 1; ASSERT(supported != NULL); *supported = 0; *scale = 1; switch (event) { case PQOS_MON_EVENT_L3_OCCUP: event_name = "llc_occupancy"; break; case PQOS_MON_EVENT_LMEM_BW: event_name = "local_bytes"; break; case PQOS_MON_EVENT_TMEM_BW: event_name = "total_bytes"; break; /** * Assume support of perf events */ case PQOS_PERF_EVENT_LLC_MISS: case PQOS_PERF_EVENT_IPC: *supported = 1; return PQOS_RETVAL_OK; default: return PQOS_RETVAL_OK; break; } snprintf(path, sizeof(path) - 1, PERF_MON_PATH "/events/%s", event_name); if (stat(path, &st) != 0) return PQOS_RETVAL_OK; *supported = 1; ret = get_mon_perf_scale_factor(event_name, scale); if (ret != PQOS_RETVAL_OK) return ret; if (*supported && warn) { LOG_WARN("As of Kernel 4.10, Intel(R) RDT perf results per " "core are found to be incorrect.\n"); warn = 0; } return PQOS_RETVAL_OK; } /** * @brief Checks if event is supported * * @param [in] event monitoring event type * @param [out] supported set to 1 if OS support is present * @param [out] scale scale factor * * @return Operation status * @retval PQOS_RETVAL_OK success */ static int detect_mon_support(const enum pqos_mon_event event, int *supported, uint32_t *scale) { int ret; *supported = 0; if (event == PQOS_MON_EVENT_RMEM_BW) { int lmem; int tmem; ret = detect_mon_support(PQOS_MON_EVENT_LMEM_BW, &lmem, scale); if (ret != PQOS_RETVAL_OK) return ret; ret = detect_mon_support(PQOS_MON_EVENT_TMEM_BW, &tmem, scale); if (ret != PQOS_RETVAL_OK) return ret; *supported = (lmem && tmem); return PQOS_RETVAL_OK; } ret = detect_mon_resctrl_support(event, supported, scale); if (ret != PQOS_RETVAL_OK) { LOG_ERROR("Fatal error encountered while checking for resctrl " "monitoring support\n"); return ret; } if (*supported) return ret; ret = detect_mon_perf_support(event, supported, scale); if (ret != PQOS_RETVAL_OK) LOG_ERROR("Fatal error encountered while checking for perf " "monitoring support\n"); return ret; } int os_cap_mon_discover(struct pqos_cap_mon **r_cap, const struct pqos_cpuinfo *cpu) { struct pqos_cap_mon *cap = NULL; int supported; int ret = PQOS_RETVAL_OK; uint64_t num_rmids = 0; unsigned i; enum pqos_mon_event events[] = { /* clang-format off */ PQOS_MON_EVENT_L3_OCCUP, PQOS_MON_EVENT_LMEM_BW, PQOS_MON_EVENT_TMEM_BW, PQOS_MON_EVENT_RMEM_BW, PQOS_PERF_EVENT_LLC_MISS, PQOS_PERF_EVENT_IPC /* clang-format on */ }; ret = detect_os_support(PROC_CPUINFO, "cqm", 0, &supported); if (ret != PQOS_RETVAL_OK) { LOG_ERROR("Fatal error encountered in" " OS detection!\n"); return ret; } if (!supported) return PQOS_RETVAL_RESOURCE; if (access(RESCTRL_PATH_INFO_L3_MON "/num_rmids", F_OK) == 0) { ret = readuint64(RESCTRL_PATH_INFO_L3_MON "/num_rmids", 10, &num_rmids); if (ret != PQOS_RETVAL_OK) return ret; } cap = (struct pqos_cap_mon *)malloc(sizeof(*cap)); if (cap == NULL) return PQOS_RETVAL_RESOURCE; memset(cap, 0, sizeof(*cap)); cap->mem_size = sizeof(*cap); cap->max_rmid = num_rmids; cap->l3_size = cpu->l3.total_size; for (i = 0; i < DIM(events); i++) { int supported; uint32_t scale; struct pqos_cap_mon *mon; struct pqos_monitor *monitor; ret = detect_mon_support(events[i], &supported, &scale); if (ret != PQOS_RETVAL_OK) break; if (!supported) continue; mon = realloc(cap, cap->mem_size + sizeof(struct pqos_monitor)); if (mon == NULL) { ret = PQOS_RETVAL_RESOURCE; break; } monitor = &(mon->events[mon->num_events]); memset(monitor, 0, sizeof(*monitor)); monitor->type = events[i]; monitor->max_rmid = num_rmids; monitor->scale_factor = scale; mon->mem_size += sizeof(struct pqos_monitor); mon->num_events++; cap = mon; } if (ret == PQOS_RETVAL_OK) *r_cap = cap; else free(cap); return ret; } int os_cap_l3ca_discover(struct pqos_cap_l3ca *cap, const struct pqos_cpuinfo *cpu) { struct stat st; const char *info; int cdp_on; int ret = PQOS_RETVAL_OK; ASSERT(cap != NULL); if (stat(RESCTRL_PATH_INFO_L3, &st) == 0) { info = RESCTRL_PATH_INFO_L3; cdp_on = 0; } else if (stat(RESCTRL_PATH_INFO_L3CODE, &st) == 0 && stat(RESCTRL_PATH_INFO_L3DATA, &st) == 0) { info = RESCTRL_PATH_INFO_L3CODE; cdp_on = 1; } else return PQOS_RETVAL_RESOURCE; memset(cap, 0, sizeof(*cap)); cap->mem_size = sizeof(*cap); cap->cdp = cdp_on; cap->cdp_on = cdp_on; cap->way_size = cpu->l3.way_size; ret = get_num_closids(info, &cap->num_classes); if (ret != PQOS_RETVAL_OK) return ret; ret = get_num_ways(info, &cap->num_ways); if (ret != PQOS_RETVAL_OK) return ret; ret = get_shareable_bits(info, &cap->way_contention); if (ret != PQOS_RETVAL_OK) return ret; if (!cdp_on) ret = detect_os_support(PROC_CPUINFO, "cdp_l3", 0, &cap->cdp); return ret; } int os_cap_l2ca_discover(struct pqos_cap_l2ca *cap, const struct pqos_cpuinfo *cpu) { struct stat st; const char *info; int cdp_on; int ret = PQOS_RETVAL_OK; ASSERT(cap != NULL); if (stat(RESCTRL_PATH_INFO_L2, &st) == 0) { info = RESCTRL_PATH_INFO_L2; cdp_on = 0; } else if (stat(RESCTRL_PATH_INFO_L2CODE, &st) == 0 && stat(RESCTRL_PATH_INFO_L2DATA, &st) == 0) { info = RESCTRL_PATH_INFO_L2CODE; cdp_on = 1; } else return PQOS_RETVAL_RESOURCE; memset(cap, 0, sizeof(*cap)); cap->mem_size = sizeof(*cap); cap->cdp = cdp_on; cap->cdp_on = cdp_on; cap->way_size = cpu->l2.way_size; ret = get_num_closids(info, &cap->num_classes); if (ret != PQOS_RETVAL_OK) return ret; ret = get_num_ways(info, &cap->num_ways); if (ret != PQOS_RETVAL_OK) return ret; ret = get_shareable_bits(info, &cap->way_contention); if (ret != PQOS_RETVAL_OK) return ret; if (!cdp_on) ret = detect_os_support(PROC_CPUINFO, "cdp_l2", 0, &cap->cdp); return ret; } int os_cap_get_mba_ctrl(const struct pqos_cap *cap, const struct pqos_cpuinfo *cpu, int *supported, int *enabled) { int ret; ret = pqos_mba_ctrl_enabled(cap, supported, enabled); if (ret != PQOS_RETVAL_OK) return ret; /* resctrl is mounted with default options */ if (mba_ctrl != -1) { *enabled = 0; *supported = mba_ctrl; return PQOS_RETVAL_OK; } if (access(RESCTRL_PATH "/cpus", F_OK) != 0) *enabled = 0; /* check mount flags */ if (*enabled == -1) { ret = detect_os_support(PROC_MOUNTS, "mba_MBps", 0, enabled); if (ret != PQOS_RETVAL_OK) return ret; } /* check for values above 100 */ if (*enabled == -1) { unsigned grp; unsigned count = 0; unsigned i; unsigned *mba_ids, mba_id_num; struct resctrl_schemata *schmt; ret = resctrl_alloc_get_grps_num(cap, &count); if (ret != PQOS_RETVAL_OK) return ret; mba_ids = pqos_cpu_get_mba_ids(cpu, &mba_id_num); if (mba_ids == NULL) return PQOS_RETVAL_ERROR; schmt = resctrl_schemata_alloc(cap, cpu); if (schmt == NULL) { free(mba_ids); return PQOS_RETVAL_ERROR; } for (grp = 0; grp < count && *enabled == -1; grp++) { ret = resctrl_alloc_schemata_read(grp, schmt); if (ret != PQOS_RETVAL_OK) continue; for (i = 0; i < mba_id_num; i++) { struct pqos_mba mba; ret = resctrl_schemata_mba_get( schmt, mba_ids[i], &mba); if (ret == PQOS_RETVAL_OK && mba.mb_max > 100) { *enabled = 1; break; } } } resctrl_schemata_free(schmt); free(mba_ids); } /* get free COS and try to write value above 100 */ if (*enabled == -1) { unsigned grp; unsigned count = 0; struct resctrl_schemata *schmt; FILE *fd; ret = resctrl_alloc_get_grps_num(cap, &count); if (ret != PQOS_RETVAL_OK) return ret; ret = resctrl_alloc_get_unused_group(count, &grp); if (ret != PQOS_RETVAL_OK) { LOG_WARN("Unable to check if MBA CTRL is enabled - " "No free group\n"); goto ctrl_support; } schmt = resctrl_schemata_alloc(cap, cpu); if (schmt == NULL) goto ctrl_support; ret = resctrl_alloc_schemata_read(grp, schmt); if (ret == PQOS_RETVAL_OK) { fd = resctrl_alloc_fopen(grp, "schemata", "w"); if (fd != NULL) { fprintf(fd, "MB:0=2000\n"); if (fclose(fd) == 0) *enabled = 1; else *enabled = 0; } /* restore MBA configuration */ if (*enabled == 1) { ret = resctrl_alloc_schemata_write(grp, schmt); if (ret != PQOS_RETVAL_OK) LOG_WARN("Unable to restore MBA " "settings\n"); } } resctrl_schemata_free(schmt); } ctrl_support: if (*supported != -1) goto ctrl_exit; if (*enabled == 1) *supported = 1; /* Check if MBL monitoring is supported */ else { int mbl = 0; ret = detect_mon_resctrl_support(PQOS_MON_EVENT_LMEM_BW, &mbl, NULL); if (ret != PQOS_RETVAL_OK) return ret; if (!mbl) *supported = 0; } ctrl_exit: if (*supported == 0) *enabled = 0; if (*supported == 1) LOG_INFO("OS support for MBA CTRL detected\n"); else if (*supported == 0) LOG_INFO("OS support for MBA CTRL not detected\n"); else LOG_INFO("OS support for MBA CTRL unknown\n"); return PQOS_RETVAL_OK; } int os_cap_mba_discover(struct pqos_cap_mba *cap, const struct pqos_cpuinfo *cpu) { struct stat st; uint64_t val; const char *info = RESCTRL_PATH_INFO_MB; int ret = PQOS_RETVAL_OK; UNUSED_PARAM(cpu); ASSERT(cap != NULL); if (stat(RESCTRL_PATH_INFO_MB, &st) != 0) return PQOS_RETVAL_RESOURCE; memset(cap, 0, sizeof(*cap)); cap->mem_size = sizeof(*cap); cap->ctrl = -1; cap->ctrl_on = -1; ret = get_num_closids(info, &cap->num_classes); if (ret != PQOS_RETVAL_OK) return ret; /* Detect MBA CTRL status */ ret = detect_os_support(PROC_MOUNTS, "mba_MBps", 0, &(cap->ctrl_on)); if (ret != PQOS_RETVAL_OK) return ret; if (cap->ctrl_on == 1) cap->ctrl = 1; else cap->ctrl = mba_ctrl; ret = readuint64(RESCTRL_PATH_INFO_MB "/min_bandwidth", 10, &val); if (ret != PQOS_RETVAL_OK) return ret; else cap->throttle_max = 100 - val; ret = readuint64(RESCTRL_PATH_INFO_MB "/bandwidth_gran", 10, &val); if (ret != PQOS_RETVAL_OK) return ret; else cap->throttle_step = val; ret = readuint64(RESCTRL_PATH_INFO_MB "/delay_linear", 10, &val); if (ret != PQOS_RETVAL_OK) return ret; else cap->is_linear = (val == 1); return ret; }