1 /* Detect the number of processors.
2 
3    Copyright (C) 2009-2021 Free Software Foundation, Inc.
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU Lesser General Public License as published by
7    the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public License
16    along with this program; if not, see <https://www.gnu.org/licenses/>.  */
17 
18 /* Written by Glen Lenker and Bruno Haible.  */
19 
20 #include <config.h>
21 #include "nproc.h"
22 
23 #include <limits.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 
27 #if HAVE_PTHREAD_GETAFFINITY_NP && 0
28 # include <pthread.h>
29 # include <sched.h>
30 #endif
31 #if HAVE_SCHED_GETAFFINITY_LIKE_GLIBC || HAVE_SCHED_GETAFFINITY_NP
32 # include <sched.h>
33 #endif
34 
35 #include <sys/types.h>
36 
37 #if HAVE_SYS_PSTAT_H
38 # include <sys/pstat.h>
39 #endif
40 
41 #if HAVE_SYS_SYSMP_H
42 # include <sys/sysmp.h>
43 #endif
44 
45 #if HAVE_SYS_PARAM_H
46 # include <sys/param.h>
47 #endif
48 
49 #if HAVE_SYS_SYSCTL_H && ! defined __GLIBC__
50 # include <sys/sysctl.h>
51 #endif
52 
53 #if defined _WIN32 && ! defined __CYGWIN__
54 # define WIN32_LEAN_AND_MEAN
55 # include <windows.h>
56 #endif
57 
58 #include "c-ctype.h"
59 
60 #include "minmax.h"
61 
62 #define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))
63 
64 /* Return the number of processors available to the current process, based
65    on a modern system call that returns the "affinity" between the current
66    process and each CPU.  Return 0 if unknown or if such a system call does
67    not exist.  */
68 static unsigned long
num_processors_via_affinity_mask(void)69 num_processors_via_affinity_mask (void)
70 {
71   /* glibc >= 2.3.3 with NPTL and NetBSD 5 have pthread_getaffinity_np,
72      but with different APIs.  Also it requires linking with -lpthread.
73      Therefore this code is not enabled.
74      glibc >= 2.3.4 has sched_getaffinity whereas NetBSD 5 has
75      sched_getaffinity_np.  */
76 #if HAVE_PTHREAD_GETAFFINITY_NP && defined __GLIBC__ && 0
77   {
78     cpu_set_t set;
79 
80     if (pthread_getaffinity_np (pthread_self (), sizeof (set), &set) == 0)
81       {
82         unsigned long count;
83 
84 # ifdef CPU_COUNT
85         /* glibc >= 2.6 has the CPU_COUNT macro.  */
86         count = CPU_COUNT (&set);
87 # else
88         size_t i;
89 
90         count = 0;
91         for (i = 0; i < CPU_SETSIZE; i++)
92           if (CPU_ISSET (i, &set))
93             count++;
94 # endif
95         if (count > 0)
96           return count;
97       }
98   }
99 #elif HAVE_PTHREAD_GETAFFINITY_NP && defined __NetBSD__ && 0
100   {
101     cpuset_t *set;
102 
103     set = cpuset_create ();
104     if (set != NULL)
105       {
106         unsigned long count = 0;
107 
108         if (pthread_getaffinity_np (pthread_self (), cpuset_size (set), set)
109             == 0)
110           {
111             cpuid_t i;
112 
113             for (i = 0;; i++)
114               {
115                 int ret = cpuset_isset (i, set);
116                 if (ret < 0)
117                   break;
118                 if (ret > 0)
119                   count++;
120               }
121           }
122         cpuset_destroy (set);
123         if (count > 0)
124           return count;
125       }
126   }
127 #elif HAVE_SCHED_GETAFFINITY_LIKE_GLIBC /* glibc >= 2.3.4 */
128   {
129     cpu_set_t set;
130 
131     if (sched_getaffinity (0, sizeof (set), &set) == 0)
132       {
133         unsigned long count;
134 
135 # ifdef CPU_COUNT
136         /* glibc >= 2.6 has the CPU_COUNT macro.  */
137         count = CPU_COUNT (&set);
138 # else
139         size_t i;
140 
141         count = 0;
142         for (i = 0; i < CPU_SETSIZE; i++)
143           if (CPU_ISSET (i, &set))
144             count++;
145 # endif
146         if (count > 0)
147           return count;
148       }
149   }
150 #elif HAVE_SCHED_GETAFFINITY_NP /* NetBSD >= 5 */
151   {
152     cpuset_t *set;
153 
154     set = cpuset_create ();
155     if (set != NULL)
156       {
157         unsigned long count = 0;
158 
159         if (sched_getaffinity_np (getpid (), cpuset_size (set), set) == 0)
160           {
161             cpuid_t i;
162 
163             for (i = 0;; i++)
164               {
165                 int ret = cpuset_isset (i, set);
166                 if (ret < 0)
167                   break;
168                 if (ret > 0)
169                   count++;
170               }
171           }
172         cpuset_destroy (set);
173         if (count > 0)
174           return count;
175       }
176   }
177 #endif
178 
179 #if defined _WIN32 && ! defined __CYGWIN__
180   { /* This works on native Windows platforms.  */
181     DWORD_PTR process_mask;
182     DWORD_PTR system_mask;
183 
184     if (GetProcessAffinityMask (GetCurrentProcess (),
185                                 &process_mask, &system_mask))
186       {
187         DWORD_PTR mask = process_mask;
188         unsigned long count = 0;
189 
190         for (; mask != 0; mask = mask >> 1)
191           if (mask & 1)
192             count++;
193         if (count > 0)
194           return count;
195       }
196   }
197 #endif
198 
199   return 0;
200 }
201 
202 
203 /* Return the total number of processors.  Here QUERY must be one of
204    NPROC_ALL, NPROC_CURRENT.  The result is guaranteed to be at least 1.  */
205 static unsigned long int
num_processors_ignoring_omp(enum nproc_query query)206 num_processors_ignoring_omp (enum nproc_query query)
207 {
208   /* On systems with a modern affinity mask system call, we have
209          sysconf (_SC_NPROCESSORS_CONF)
210             >= sysconf (_SC_NPROCESSORS_ONLN)
211                >= num_processors_via_affinity_mask ()
212      The first number is the number of CPUs configured in the system.
213      The second number is the number of CPUs available to the scheduler.
214      The third number is the number of CPUs available to the current process.
215 
216      Note! On Linux systems with glibc, the first and second number come from
217      the /sys and /proc file systems (see
218      glibc/sysdeps/unix/sysv/linux/getsysstats.c).
219      In some situations these file systems are not mounted, and the sysconf call
220      returns 1 or 2 (<https://sourceware.org/bugzilla/show_bug.cgi?id=21542>),
221      which does not reflect the reality.  */
222 
223   if (query == NPROC_CURRENT)
224     {
225       /* Try the modern affinity mask system call.  */
226       {
227         unsigned long nprocs = num_processors_via_affinity_mask ();
228 
229         if (nprocs > 0)
230           return nprocs;
231       }
232 
233 #if defined _SC_NPROCESSORS_ONLN
234       { /* This works on glibc, Mac OS X 10.5, FreeBSD, AIX, OSF/1, Solaris,
235            Cygwin, Haiku.  */
236         long int nprocs = sysconf (_SC_NPROCESSORS_ONLN);
237         if (nprocs > 0)
238           return nprocs;
239       }
240 #endif
241     }
242   else /* query == NPROC_ALL */
243     {
244 #if defined _SC_NPROCESSORS_CONF
245       { /* This works on glibc, Mac OS X 10.5, FreeBSD, AIX, OSF/1, Solaris,
246            Cygwin, Haiku.  */
247         long int nprocs = sysconf (_SC_NPROCESSORS_CONF);
248 
249 # if __GLIBC__ >= 2 && defined __linux__
250         /* On Linux systems with glibc, this information comes from the /sys and
251            /proc file systems (see glibc/sysdeps/unix/sysv/linux/getsysstats.c).
252            In some situations these file systems are not mounted, and the
253            sysconf call returns 1 or 2.  But we wish to guarantee that
254            num_processors (NPROC_ALL) >= num_processors (NPROC_CURRENT).  */
255         if (nprocs == 1 || nprocs == 2)
256           {
257             unsigned long nprocs_current = num_processors_via_affinity_mask ();
258 
259             if (/* nprocs_current > 0 && */ nprocs_current > nprocs)
260               nprocs = nprocs_current;
261           }
262 # endif
263 
264         if (nprocs > 0)
265           return nprocs;
266       }
267 #endif
268     }
269 
270 #if HAVE_PSTAT_GETDYNAMIC
271   { /* This works on HP-UX.  */
272     struct pst_dynamic psd;
273     if (pstat_getdynamic (&psd, sizeof psd, 1, 0) >= 0)
274       {
275         /* The field psd_proc_cnt contains the number of active processors.
276            In newer releases of HP-UX 11, the field psd_max_proc_cnt includes
277            deactivated processors.  */
278         if (query == NPROC_CURRENT)
279           {
280             if (psd.psd_proc_cnt > 0)
281               return psd.psd_proc_cnt;
282           }
283         else
284           {
285             if (psd.psd_max_proc_cnt > 0)
286               return psd.psd_max_proc_cnt;
287           }
288       }
289   }
290 #endif
291 
292 #if HAVE_SYSMP && defined MP_NAPROCS && defined MP_NPROCS
293   { /* This works on IRIX.  */
294     /* MP_NPROCS yields the number of installed processors.
295        MP_NAPROCS yields the number of processors available to unprivileged
296        processes.  */
297     int nprocs =
298       sysmp (query == NPROC_CURRENT && getuid () != 0
299              ? MP_NAPROCS
300              : MP_NPROCS);
301     if (nprocs > 0)
302       return nprocs;
303   }
304 #endif
305 
306   /* Finally, as fallback, use the APIs that don't distinguish between
307      NPROC_CURRENT and NPROC_ALL.  */
308 
309 #if HAVE_SYSCTL && ! defined __GLIBC__ && defined HW_NCPU
310   { /* This works on Mac OS X, FreeBSD, NetBSD, OpenBSD.  */
311     int nprocs;
312     size_t len = sizeof (nprocs);
313     static int mib[2] = { CTL_HW, HW_NCPU };
314 
315     if (sysctl (mib, ARRAY_SIZE (mib), &nprocs, &len, NULL, 0) == 0
316         && len == sizeof (nprocs)
317         && 0 < nprocs)
318       return nprocs;
319   }
320 #endif
321 
322 #if defined _WIN32 && ! defined __CYGWIN__
323   { /* This works on native Windows platforms.  */
324     SYSTEM_INFO system_info;
325     GetSystemInfo (&system_info);
326     if (0 < system_info.dwNumberOfProcessors)
327       return system_info.dwNumberOfProcessors;
328   }
329 #endif
330 
331   return 1;
332 }
333 
334 /* Parse OMP environment variables without dependence on OMP.
335    Return 0 for invalid values.  */
336 static unsigned long int
parse_omp_threads(char const * threads)337 parse_omp_threads (char const* threads)
338 {
339   unsigned long int ret = 0;
340 
341   if (threads == NULL)
342     return ret;
343 
344   /* The OpenMP spec says that the value assigned to the environment variables
345      "may have leading and trailing white space".  */
346   while (*threads != '\0' && c_isspace (*threads))
347     threads++;
348 
349   /* Convert it from positive decimal to 'unsigned long'.  */
350   if (c_isdigit (*threads))
351     {
352       char *endptr = NULL;
353       unsigned long int value = strtoul (threads, &endptr, 10);
354 
355       if (endptr != NULL)
356         {
357           while (*endptr != '\0' && c_isspace (*endptr))
358             endptr++;
359           if (*endptr == '\0')
360             return value;
361           /* Also accept the first value in a nesting level,
362              since we can't determine the nesting level from env vars.  */
363           else if (*endptr == ',')
364             return value;
365         }
366     }
367 
368   return ret;
369 }
370 
371 unsigned long int
num_processors(enum nproc_query query)372 num_processors (enum nproc_query query)
373 {
374   unsigned long int omp_env_limit = ULONG_MAX;
375 
376   if (query == NPROC_CURRENT_OVERRIDABLE)
377     {
378       unsigned long int omp_env_threads;
379       /* Honor the OpenMP environment variables, recognized also by all
380          programs that are based on OpenMP.  */
381       omp_env_threads = parse_omp_threads (getenv ("OMP_NUM_THREADS"));
382       omp_env_limit = parse_omp_threads (getenv ("OMP_THREAD_LIMIT"));
383       if (! omp_env_limit)
384         omp_env_limit = ULONG_MAX;
385 
386       if (omp_env_threads)
387         return MIN (omp_env_threads, omp_env_limit);
388 
389       query = NPROC_CURRENT;
390     }
391   /* Here query is one of NPROC_ALL, NPROC_CURRENT.  */
392   {
393     unsigned long nprocs = num_processors_ignoring_omp (query);
394     return MIN (nprocs, omp_env_limit);
395   }
396 }
397