fix: num_possible_cpus() with hot-unplugged CPUs
[lttng-ust.git] / src / common / smp.c
1 /*
2 * SPDX-License-Identifier: LGPL-2.1-only
3 *
4 * Copyright (C) 2011-2012 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
5 * Copyright (C) 2019 Michael Jeanson <mjeanson@efficios.com>
6 */
7
8 #define _LGPL_SOURCE
9 #include <assert.h>
10 #include <ctype.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <unistd.h>
14 #include <pthread.h>
15 #include <stdlib.h>
16
17 #include <urcu/compiler.h>
18
19 #include "common/align.h"
20 #include "common/logging.h"
21 #include "common/smp.h"
22
23 static int num_possible_cpus_cache;
24
25 #if (defined(__GLIBC__) || defined( __UCLIBC__))
26 int get_num_possible_cpus_fallback(void)
27 {
28 /* On Linux, when some processors are offline
29 * _SC_NPROCESSORS_CONF counts the offline
30 * processors, whereas _SC_NPROCESSORS_ONLN
31 * does not. If we used _SC_NPROCESSORS_ONLN,
32 * getcpu() could return a value greater than
33 * this sysconf, in which case the arrays
34 * indexed by processor would overflow.
35 */
36 return sysconf(_SC_NPROCESSORS_CONF);
37 }
38
39 #else
40
41 /*
42 * The MUSL libc implementation of the _SC_NPROCESSORS_CONF sysconf does not
43 * return the number of configured CPUs in the system but relies on the cpu
44 * affinity mask of the current task.
45 *
46 * So instead we use a strategy similar to GLIBC's, counting the cpu
47 * directories in "/sys/devices/system/cpu" and fallback on the value from
48 * sysconf if it fails.
49 */
50
51 #include <dirent.h>
52 #include <limits.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <sys/types.h>
56
57 #define __max(a,b) ((a)>(b)?(a):(b))
58
59 int get_num_possible_cpus_fallback(void)
60 {
61 int count = 0;
62 DIR *cpudir;
63 struct dirent *entry;
64
65 cpudir = opendir("/sys/devices/system/cpu");
66 if (cpudir == NULL)
67 goto end;
68
69 /*
70 * Count the number of directories named "cpu" followed by and
71 * integer. This is the same strategy as glibc uses.
72 */
73 while ((entry = readdir(cpudir))) {
74 if (entry->d_type == DT_DIR &&
75 strncmp(entry->d_name, "cpu", 3) == 0) {
76
77 char *endptr;
78 unsigned long cpu_num;
79
80 cpu_num = strtoul(entry->d_name + 3, &endptr, 10);
81 if ((cpu_num < ULONG_MAX) && (endptr != entry->d_name + 3)
82 && (*endptr == '\0')) {
83 count++;
84 }
85 }
86 }
87
88 end:
89 /*
90 * Get the sysconf value as a fallback. Keep the highest number.
91 */
92 return __max(sysconf(_SC_NPROCESSORS_CONF), count);
93 }
94 #endif
95
96 /*
97 * Get the CPU possible mask string from sysfs.
98 *
99 * buf: the buffer where the mask will be read.
100 * max_bytes: the maximum number of bytes to write in the buffer.
101 *
102 * Returns the number of bytes read or -1 on error.
103 */
104 int get_possible_cpu_mask_from_sysfs(char *buf, size_t max_bytes)
105 {
106 ssize_t bytes_read = 0;
107 size_t total_bytes_read = 0;
108 int fd = 0;
109
110 if (buf == NULL)
111 return -1;
112
113 fd = open("/sys/devices/system/cpu/possible", O_RDONLY);
114 if (fd < 0)
115 return -1;
116
117 do {
118 bytes_read = read(fd, buf + total_bytes_read,
119 max_bytes - total_bytes_read);
120
121 if (bytes_read < 0) {
122 if (errno == EINTR) {
123 continue; /* retry operation */
124 } else {
125 return -1;
126 }
127 }
128
129 total_bytes_read += bytes_read;
130 assert(total_bytes_read <= max_bytes);
131 } while (max_bytes > total_bytes_read && bytes_read > 0);
132
133 if (close(fd))
134 PERROR("close");
135
136 /*
137 * Make sure the mask read is a null terminated string.
138 */
139 if (total_bytes_read < max_bytes)
140 buf[total_bytes_read] = '\0';
141 else
142 buf[max_bytes - 1] = '\0';
143
144 return total_bytes_read;
145 }
146
147 /*
148 * Get the number of CPUs from the possible cpu mask.
149 *
150 * pmask: the mask to parse.
151 * len: the len of the mask excluding '\0'.
152 *
153 * Returns the number of possible CPUs from the mask or 0 on error.
154 */
155 int get_num_possible_cpus_from_mask(const char *pmask, size_t len)
156 {
157 ssize_t i;
158 unsigned long cpu_index;
159 char *endptr;
160
161 /* We need at least one char to read */
162 if (len < 1)
163 goto error;
164
165 /* Start from the end to read the last CPU index. */
166 for (i = len - 1; i > 0; i--) {
167 /* Break when we hit the first separator. */
168 if ((pmask[i] == ',') || (pmask[i] == '-')) {
169 i++;
170 break;
171 }
172 }
173
174 cpu_index = strtoul(&pmask[i], &endptr, 10);
175
176 /*
177 * If we read a CPU index, increment it by one to return a number of
178 * CPUs.
179 */
180 if ((&pmask[i] != endptr) && (cpu_index < INT_MAX))
181 return (int) cpu_index + 1;
182
183 error:
184 return 0;
185 }
186
187 static void _get_num_possible_cpus(void)
188 {
189 int ret;
190 int buf_len = LTTNG_UST_PAGE_SIZE;
191 char buf[buf_len];
192
193 /* Get the possible cpu mask from sysfs, fallback to sysconf. */
194 ret = get_possible_cpu_mask_from_sysfs((char *) &buf, buf_len);
195 if (ret <= 0)
196 goto fallback;
197
198 /* Parse the possible cpu mask, on failure fallback to sysconf. */
199 ret = get_num_possible_cpus_from_mask((char *) &buf, ret);
200 if (ret > 0)
201 goto end;
202
203 fallback:
204 /* Fallback to sysconf. */
205 ret = get_num_possible_cpus_fallback();
206
207 end:
208 /* If all methods failed, don't store the value. */
209 if (ret < 1)
210 return;
211
212 num_possible_cpus_cache = ret;
213 }
214
215 /*
216 * Returns the total number of CPUs in the system. If the cache is not yet
217 * initialized, get the value from "/sys/devices/system/cpu/possible" or
218 * fallback to sysconf and cache it.
219 *
220 * If all methods fail, don't populate the cache and return 0.
221 */
222 int num_possible_cpus(void)
223 {
224 if (caa_unlikely(!num_possible_cpus_cache))
225 _get_num_possible_cpus();
226
227 return num_possible_cpus_cache;
228 }
This page took 0.033307 seconds and 4 git commands to generate.