| /* |
| * Copyright (C) 2012 Linux Test Project, Inc. |
| * |
| * This program 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. |
| * |
| * This program 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 this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include "config.h" |
| #include <errno.h> |
| #if HAVE_NUMA_H |
| #include <numa.h> |
| #endif |
| #if HAVE_NUMAIF_H |
| #include <numaif.h> |
| #endif |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <errno.h> |
| |
| #include "test.h" |
| #include "safe_macros.h" |
| #include "numa_helper.h" |
| #include "linux_syscall_numbers.h" |
| |
| unsigned long get_max_node(void) |
| { |
| unsigned long max_node = 0; |
| #if HAVE_NUMA_H |
| #if !defined(LIBNUMA_API_VERSION) || LIBNUMA_API_VERSION < 2 |
| max_node = NUMA_NUM_NODES; |
| /* |
| * NUMA_NUM_NODES is not reliable, libnuma >=2 is looking |
| * at /proc/self/status to figure out correct number. |
| * If buffer is not large enough get_mempolicy will fail with EINVAL. |
| */ |
| if (max_node < 1024) |
| max_node = 1024; |
| #else |
| max_node = numa_max_possible_node() + 1; |
| #endif |
| #endif /* HAVE_NUMA_H */ |
| return max_node; |
| } |
| |
| #if HAVE_NUMA_H |
| static void get_nodemask_allnodes(nodemask_t * nodemask, unsigned long max_node) |
| { |
| unsigned long nodemask_size = max_node / 8; |
| int i; |
| char fn[64]; |
| struct stat st; |
| |
| memset(nodemask, 0, nodemask_size); |
| for (i = 0; i < max_node; i++) { |
| sprintf(fn, "/sys/devices/system/node/node%d", i); |
| if (stat(fn, &st) == 0) |
| nodemask_set(nodemask, i); |
| } |
| } |
| |
| static int filter_nodemask_mem(nodemask_t * nodemask, unsigned long max_node) |
| { |
| #if MPOL_F_MEMS_ALLOWED |
| unsigned long nodemask_size = max_node / 8; |
| memset(nodemask, 0, nodemask_size); |
| /* |
| * avoid numa_get_mems_allowed(), because of bug in getpol() |
| * utility function in older versions: |
| * http://www.spinics.net/lists/linux-numa/msg00849.html |
| * |
| * At the moment numa_available() implementation also uses |
| * get_mempolicy, but let's make explicit check for ENOSYS |
| * here as well in case it changes in future. Silent ignore |
| * of ENOSYS is OK, because without NUMA caller gets empty |
| * set of nodes anyway. |
| */ |
| if (syscall(__NR_get_mempolicy, NULL, nodemask->n, |
| max_node, 0, MPOL_F_MEMS_ALLOWED) < 0) { |
| if (errno == ENOSYS) |
| return 0; |
| return -2; |
| } |
| #else |
| int i; |
| /* |
| * old libnuma/kernel don't have MPOL_F_MEMS_ALLOWED, so let's assume |
| * that we can use any node with memory > 0 |
| */ |
| for (i = 0; i < max_node; i++) { |
| if (!nodemask_isset(nodemask, i)) |
| continue; |
| if (numa_node_size64(i, NULL) <= 0) |
| nodemask_clr(nodemask, i); |
| } |
| #endif /* MPOL_F_MEMS_ALLOWED */ |
| return 0; |
| } |
| |
| static int cpumask_has_cpus(char *cpumask, size_t len) |
| { |
| int j; |
| for (j = 0; j < len; j++) |
| if (cpumask[j] == '\0') |
| return 0; |
| else if ((cpumask[j] > '0' && cpumask[j] <= '9') || |
| (cpumask[j] >= 'a' && cpumask[j] <= 'f')) |
| return 1; |
| return 0; |
| |
| } |
| |
| static void filter_nodemask_cpu(nodemask_t * nodemask, unsigned long max_node) |
| { |
| char *cpumask = NULL; |
| char fn[64]; |
| FILE *f; |
| size_t len; |
| int i, ret; |
| |
| for (i = 0; i < max_node; i++) { |
| if (!nodemask_isset(nodemask, i)) |
| continue; |
| sprintf(fn, "/sys/devices/system/node/node%d/cpumap", i); |
| f = fopen(fn, "r"); |
| if (f) { |
| ret = getdelim(&cpumask, &len, '\n', f); |
| if ((ret > 0) && (!cpumask_has_cpus(cpumask, len))) |
| nodemask_clr(nodemask, i); |
| fclose(f); |
| } |
| } |
| free(cpumask); |
| } |
| #endif /* HAVE_NUMA_H */ |
| |
| /* |
| * get_allowed_nodes_arr - get number and array of available nodes |
| * @num_nodes: pointer where number of available nodes will be stored |
| * @nodes: array of available node ids, this is MPOL_F_MEMS_ALLOWED |
| * node bitmask compacted (without holes), so that each field |
| * contains node number. If NULL only num_nodes is |
| * returned, otherwise it cotains new allocated array, |
| * which caller is responsible to free. |
| * RETURNS: |
| * 0 on success |
| * -1 on allocation failure |
| * -2 on get_mempolicy failure |
| */ |
| int get_allowed_nodes_arr(int flag, int *num_nodes, int **nodes) |
| { |
| int ret = 0; |
| #if HAVE_NUMA_H |
| int i; |
| nodemask_t *nodemask = NULL; |
| #endif |
| *num_nodes = 0; |
| if (nodes) |
| *nodes = NULL; |
| |
| #if HAVE_NUMA_H |
| unsigned long max_node, nodemask_size; |
| |
| if (numa_available() == -1) |
| return 0; |
| |
| max_node = LTP_ALIGN(get_max_node(), sizeof(unsigned long)*8); |
| nodemask_size = max_node / 8; |
| |
| nodemask = malloc(nodemask_size); |
| if (nodes) |
| *nodes = malloc(sizeof(int) * max_node); |
| |
| do { |
| if (nodemask == NULL || (nodes && (*nodes == NULL))) { |
| ret = -1; |
| break; |
| } |
| |
| /* allow all nodes at start, then filter based on flags */ |
| get_nodemask_allnodes(nodemask, max_node); |
| if ((flag & NH_MEMS) == NH_MEMS) { |
| ret = filter_nodemask_mem(nodemask, max_node); |
| if (ret < 0) |
| break; |
| } |
| if ((flag & NH_CPUS) == NH_CPUS) |
| filter_nodemask_cpu(nodemask, max_node); |
| |
| for (i = 0; i < max_node; i++) { |
| if (nodemask_isset(nodemask, i)) { |
| if (nodes) |
| (*nodes)[*num_nodes] = i; |
| (*num_nodes)++; |
| } |
| } |
| } while (0); |
| free(nodemask); |
| #endif |
| return ret; |
| } |
| |
| /* |
| * get_allowed_nodes - convenience function to get fixed number of nodes |
| * @count: how many nodes to get |
| * @...: int pointers, where node ids will be stored |
| * RETURNS: |
| * 0 on success |
| * -1 on allocation failure |
| * -2 on get_mempolicy failure |
| * -3 on not enough allowed nodes |
| */ |
| int get_allowed_nodes(int flag, int count, ...) |
| { |
| int ret; |
| int i, *nodep; |
| va_list ap; |
| int num_nodes = 0; |
| int *nodes = NULL; |
| |
| ret = get_allowed_nodes_arr(flag, &num_nodes, &nodes); |
| if (ret < 0) |
| return ret; |
| |
| va_start(ap, count); |
| for (i = 0; i < count; i++) { |
| nodep = va_arg(ap, int *); |
| if (i < num_nodes) { |
| *nodep = nodes[i]; |
| } else { |
| ret = -3; |
| errno = EINVAL; |
| break; |
| } |
| } |
| free(nodes); |
| va_end(ap); |
| |
| return ret; |
| } |
| |
| static void print_node_info(int flag) |
| { |
| int *allowed_nodes = NULL; |
| int i, ret, num_nodes; |
| |
| ret = get_allowed_nodes_arr(flag, &num_nodes, &allowed_nodes); |
| printf("nodes (flag=%d): ", flag); |
| if (ret == 0) { |
| for (i = 0; i < num_nodes; i++) |
| printf("%d ", allowed_nodes[i]); |
| printf("\n"); |
| } else |
| printf("error(%d)\n", ret); |
| free(allowed_nodes); |
| } |
| |
| /* |
| * nh_dump_nodes - dump info about nodes to stdout |
| */ |
| void nh_dump_nodes(void) |
| { |
| print_node_info(0); |
| print_node_info(NH_MEMS); |
| print_node_info(NH_CPUS); |
| print_node_info(NH_MEMS | NH_CPUS); |
| } |
| |
| /* |
| * is_numa - judge a system is NUMA system or not |
| * @flag: NH_MEMS and/or NH_CPUS |
| * @min_nodes: find at least 'min_nodes' nodes with memory |
| * NOTE: the function is designed to try to find at least 'min_nodes' |
| * available nodes, where each node contains memory. |
| * WARN: Don't use this func in child, as it calls tst_brkm() |
| * RETURNS: |
| * 0 - it's not a NUMA system |
| * 1 - it's a NUMA system |
| */ |
| int is_numa(void (*cleanup_fn)(void), int flag, int min_nodes) |
| { |
| int ret; |
| int numa_nodes = 0; |
| |
| ret = get_allowed_nodes_arr(flag, &numa_nodes, NULL); |
| if (ret < 0) |
| tst_brkm(TBROK | TERRNO, cleanup_fn, "get_allowed_nodes_arr"); |
| |
| if (numa_nodes >= min_nodes) |
| return 1; |
| else |
| return 0; |
| } |