/* * 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 #if HAVE_NUMA_H #include #endif #if HAVE_NUMAIF_H #include #endif #include #include #include #include #include #include #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; }