fix: Unify possible CPU number fallback
[lttng-ust.git] / src / common / smp.c
CommitLineData
ebabbf58 1/*
c0c0989a 2 * SPDX-License-Identifier: LGPL-2.1-only
ebabbf58
MD
3 *
4 * Copyright (C) 2011-2012 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
5 * Copyright (C) 2019 Michael Jeanson <mjeanson@efficios.com>
ebabbf58
MD
6 */
7
ebabbf58 8#define _LGPL_SOURCE
66dbdc34
MJ
9#include <assert.h>
10#include <ctype.h>
217babc9 11#include <dirent.h>
66dbdc34
MJ
12#include <errno.h>
13#include <fcntl.h>
feac7204 14#include <limits.h>
ebabbf58
MD
15#include <unistd.h>
16#include <pthread.h>
66dbdc34 17#include <stdlib.h>
217babc9
MJ
18#include <string.h>
19#include <sys/types.h>
ebabbf58 20
74cc1f59
MJ
21#include <urcu/compiler.h>
22
66dbdc34 23#include "common/logging.h"
74cc1f59
MJ
24#include "common/smp.h"
25
217babc9 26#define __max(a,b) ((a)>(b)?(a):(b))
ebabbf58 27
217babc9 28static int num_possible_cpus_cache;
ebabbf58
MD
29
30/*
217babc9
MJ
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.
ebabbf58 47 *
217babc9 48 * Returns 0 or less on error.
ebabbf58 49 */
66dbdc34 50int get_num_possible_cpus_fallback(void)
ebabbf58 51{
217babc9
MJ
52 long max_cpuid = -1;
53
ebabbf58
MD
54 DIR *cpudir;
55 struct dirent *entry;
56
57 cpudir = opendir("/sys/devices/system/cpu");
58 if (cpudir == NULL)
59 goto end;
60
61 /*
217babc9 62 * Iterate on all directories named "cpu" followed by an integer.
ebabbf58
MD
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;
217babc9 69 long cpu_id;
ebabbf58 70
217babc9
MJ
71 cpu_id = strtol(entry->d_name + 3, &endptr, 10);
72 if ((cpu_id < LONG_MAX) && (endptr != entry->d_name + 3)
ebabbf58 73 && (*endptr == '\0')) {
217babc9
MJ
74 if (cpu_id > max_cpuid)
75 max_cpuid = cpu_id;
ebabbf58
MD
76 }
77 }
78 }
79
217babc9
MJ
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
ebabbf58
MD
87end:
88 /*
217babc9 89 * Get the sysconf value as a last resort. Keep the highest number.
ebabbf58 90 */
217babc9 91 return __max(sysconf(_SC_NPROCESSORS_CONF), max_cpuid + 1);
66dbdc34 92}
66dbdc34
MJ
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 */
102int 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;
0446ebf3 106 int fd = -1, ret = -1;
66dbdc34
MJ
107
108 if (buf == NULL)
0446ebf3 109 goto end;
66dbdc34
MJ
110
111 fd = open("/sys/devices/system/cpu/possible", O_RDONLY);
112 if (fd < 0)
0446ebf3 113 goto end;
66dbdc34
MJ
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 {
0446ebf3 123 goto end;
66dbdc34
MJ
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
66dbdc34
MJ
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
0446ebf3
MD
139 if (total_bytes_read > INT_MAX)
140 goto end;
141 ret = (int) total_bytes_read;
142end:
143 if (fd >= 0 && close(fd) < 0)
144 PERROR("close");
145 return ret;
66dbdc34
MJ
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 */
156int 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);
ebabbf58
MD
176
177 /*
66dbdc34
MJ
178 * If we read a CPU index, increment it by one to return a number of
179 * CPUs.
ebabbf58 180 */
66dbdc34
MJ
181 if ((&pmask[i] != endptr) && (cpu_index < INT_MAX))
182 return (int) cpu_index + 1;
183
184error:
185 return 0;
186}
187
188static void _get_num_possible_cpus(void)
189{
190 int ret;
feac7204 191 char buf[LTTNG_UST_CPUMASK_SIZE];
66dbdc34
MJ
192
193 /* Get the possible cpu mask from sysfs, fallback to sysconf. */
feac7204 194 ret = get_possible_cpu_mask_from_sysfs((char *) &buf, LTTNG_UST_CPUMASK_SIZE);
66dbdc34
MJ
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
203fallback:
204 /* Fallback to sysconf. */
205 ret = get_num_possible_cpus_fallback();
206
207end:
208 /* If all methods failed, don't store the value. */
209 if (ret < 1)
ebabbf58 210 return;
66dbdc34
MJ
211
212 num_possible_cpus_cache = ret;
ebabbf58 213}
74cc1f59
MJ
214
215/*
216 * Returns the total number of CPUs in the system. If the cache is not yet
66dbdc34
MJ
217 * initialized, get the value from "/sys/devices/system/cpu/possible" or
218 * fallback to sysconf and cache it.
74cc1f59 219 *
66dbdc34 220 * If all methods fail, don't populate the cache and return 0.
74cc1f59
MJ
221 */
222int 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.036999 seconds and 4 git commands to generate.