fix: Unify possible CPU number fallback
[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 <dirent.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <limits.h>
15 #include <unistd.h>
16 #include <pthread.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/types.h>
20
21 #include <urcu/compiler.h>
22
23 #include "common/logging.h"
24 #include "common/smp.h"
25
26 #define __max(a,b) ((a)>(b)?(a):(b))
27
28 static int num_possible_cpus_cache;
29
30 /*
31 * As a fallback to parsing the CPU mask in "/sys/devices/system/cpu/possible",
32 * iterate on all the folders in "/sys/devices/system/cpu" that start with
33 * "cpu" followed by an integer, keep the highest CPU id encountered during
34 * this iteration and add 1 to get a number of CPUs.
35 *
36 * Then get the value from sysconf(_SC_NPROCESSORS_CONF) as a fallback and
37 * return the highest one.
38 *
39 * On Linux, using the value from sysconf can be unreliable since the way it
40 * counts CPUs varies between C libraries and even between versions of the same
41 * library. If we used it directly, getcpu() could return a value greater than
42 * this sysconf, in which case the arrays indexed by processor would overflow.
43 *
44 * As another example, the MUSL libc implementation of the _SC_NPROCESSORS_CONF
45 * sysconf does not return the number of configured CPUs in the system but
46 * relies on the cpu affinity mask of the current task.
47 *
48 * Returns 0 or less on error.
49 */
50 int get_num_possible_cpus_fallback(void)
51 {
52 long max_cpuid = -1;
53
54 DIR *cpudir;
55 struct dirent *entry;
56
57 cpudir = opendir("/sys/devices/system/cpu");
58 if (cpudir == NULL)
59 goto end;
60
61 /*
62 * Iterate on all directories named "cpu" followed by an integer.
63 */
64 while ((entry = readdir(cpudir))) {
65 if (entry->d_type == DT_DIR &&
66 strncmp(entry->d_name, "cpu", 3) == 0) {
67
68 char *endptr;
69 long cpu_id;
70
71 cpu_id = strtol(entry->d_name + 3, &endptr, 10);
72 if ((cpu_id < LONG_MAX) && (endptr != entry->d_name + 3)
73 && (*endptr == '\0')) {
74 if (cpu_id > max_cpuid)
75 max_cpuid = cpu_id;
76 }
77 }
78 }
79
80 /*
81 * If the max CPU id is out of bound, set it to -1 so it results in a
82 * CPU num of 0.
83 */
84 if (max_cpuid < 0 || max_cpuid > INT_MAX)
85 max_cpuid = -1;
86
87 end:
88 /*
89 * Get the sysconf value as a last resort. Keep the highest number.
90 */
91 return __max(sysconf(_SC_NPROCESSORS_CONF), max_cpuid + 1);
92 }
93
94 /*
95 * Get the CPU possible mask string from sysfs.
96 *
97 * buf: the buffer where the mask will be read.
98 * max_bytes: the maximum number of bytes to write in the buffer.
99 *
100 * Returns the number of bytes read or -1 on error.
101 */
102 int get_possible_cpu_mask_from_sysfs(char *buf, size_t max_bytes)
103 {
104 ssize_t bytes_read = 0;
105 size_t total_bytes_read = 0;
106 int fd = -1, ret = -1;
107
108 if (buf == NULL)
109 goto end;
110
111 fd = open("/sys/devices/system/cpu/possible", O_RDONLY);
112 if (fd < 0)
113 goto end;
114
115 do {
116 bytes_read = read(fd, buf + total_bytes_read,
117 max_bytes - total_bytes_read);
118
119 if (bytes_read < 0) {
120 if (errno == EINTR) {
121 continue; /* retry operation */
122 } else {
123 goto end;
124 }
125 }
126
127 total_bytes_read += bytes_read;
128 assert(total_bytes_read <= max_bytes);
129 } while (max_bytes > total_bytes_read && bytes_read > 0);
130
131 /*
132 * Make sure the mask read is a null terminated string.
133 */
134 if (total_bytes_read < max_bytes)
135 buf[total_bytes_read] = '\0';
136 else
137 buf[max_bytes - 1] = '\0';
138
139 if (total_bytes_read > INT_MAX)
140 goto end;
141 ret = (int) total_bytes_read;
142 end:
143 if (fd >= 0 && close(fd) < 0)
144 PERROR("close");
145 return ret;
146 }
147
148 /*
149 * Get the number of CPUs from the possible cpu mask.
150 *
151 * pmask: the mask to parse.
152 * len: the len of the mask excluding '\0'.
153 *
154 * Returns the number of possible CPUs from the mask or 0 on error.
155 */
156 int get_num_possible_cpus_from_mask(const char *pmask, size_t len)
157 {
158 ssize_t i;
159 unsigned long cpu_index;
160 char *endptr;
161
162 /* We need at least one char to read */
163 if (len < 1)
164 goto error;
165
166 /* Start from the end to read the last CPU index. */
167 for (i = len - 1; i > 0; i--) {
168 /* Break when we hit the first separator. */
169 if ((pmask[i] == ',') || (pmask[i] == '-')) {
170 i++;
171 break;
172 }
173 }
174
175 cpu_index = strtoul(&pmask[i], &endptr, 10);
176
177 /*
178 * If we read a CPU index, increment it by one to return a number of
179 * CPUs.
180 */
181 if ((&pmask[i] != endptr) && (cpu_index < INT_MAX))
182 return (int) cpu_index + 1;
183
184 error:
185 return 0;
186 }
187
188 static void _get_num_possible_cpus(void)
189 {
190 int ret;
191 char buf[LTTNG_UST_CPUMASK_SIZE];
192
193 /* Get the possible cpu mask from sysfs, fallback to sysconf. */
194 ret = get_possible_cpu_mask_from_sysfs((char *) &buf, LTTNG_UST_CPUMASK_SIZE);
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.03326 seconds and 4 git commands to generate.