fix: num_possible_cpus() with hot-unplugged CPUs
[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>
11#include <errno.h>
12#include <fcntl.h>
ebabbf58
MD
13#include <unistd.h>
14#include <pthread.h>
66dbdc34 15#include <stdlib.h>
ebabbf58 16
74cc1f59
MJ
17#include <urcu/compiler.h>
18
66dbdc34
MJ
19#include "common/align.h"
20#include "common/logging.h"
74cc1f59
MJ
21#include "common/smp.h"
22
23static int num_possible_cpus_cache;
ebabbf58
MD
24
25#if (defined(__GLIBC__) || defined( __UCLIBC__))
66dbdc34 26int get_num_possible_cpus_fallback(void)
ebabbf58 27{
ebabbf58
MD
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 */
66dbdc34 36 return sysconf(_SC_NPROCESSORS_CONF);
ebabbf58
MD
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
66dbdc34 59int get_num_possible_cpus_fallback(void)
ebabbf58 60{
66dbdc34 61 int count = 0;
ebabbf58
MD
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
88end:
89 /*
90 * Get the sysconf value as a fallback. Keep the highest number.
91 */
66dbdc34
MJ
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 */
104int 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 */
155int 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);
ebabbf58
MD
175
176 /*
66dbdc34
MJ
177 * If we read a CPU index, increment it by one to return a number of
178 * CPUs.
ebabbf58 179 */
66dbdc34
MJ
180 if ((&pmask[i] != endptr) && (cpu_index < INT_MAX))
181 return (int) cpu_index + 1;
182
183error:
184 return 0;
185}
186
187static 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
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.03601 seconds and 4 git commands to generate.