/Users/deen/code/yugabyte-db/src/yb/gutil/sysinfo.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- |
2 | | // Copyright (c) 2006, Google Inc. |
3 | | // All rights reserved. |
4 | | // |
5 | | // Redistribution and use in source and binary forms, with or without |
6 | | // modification, are permitted provided that the following conditions are |
7 | | // met: |
8 | | // |
9 | | // * Redistributions of source code must retain the above copyright |
10 | | // notice, this list of conditions and the following disclaimer. |
11 | | // * Redistributions in binary form must reproduce the above |
12 | | // copyright notice, this list of conditions and the following disclaimer |
13 | | // in the documentation and/or other materials provided with the |
14 | | // distribution. |
15 | | // * Neither the name of Google Inc. nor the names of its |
16 | | // contributors may be used to endorse or promote products derived from |
17 | | // this software without specific prior written permission. |
18 | | // |
19 | | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
21 | | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22 | | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
23 | | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24 | | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
25 | | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26 | | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27 | | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
28 | | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29 | | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | | // |
31 | | // The following only applies to changes made to this file as part of YugaByte development. |
32 | | // |
33 | | // Portions Copyright (c) YugaByte, Inc. |
34 | | // |
35 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
36 | | // in compliance with the License. You may obtain a copy of the License at |
37 | | // |
38 | | // http://www.apache.org/licenses/LICENSE-2.0 |
39 | | // |
40 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
41 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
42 | | // or implied. See the License for the specific language governing permissions and limitations |
43 | | // under the License. |
44 | | |
45 | | #if (defined(_WIN32) || defined(__MINGW32__)) && !defined(__CYGWIN__) && !defined(__CYGWIN32) |
46 | | # define PLATFORM_WINDOWS 1 |
47 | | #endif |
48 | | |
49 | | #include <ctype.h> // for isspace() |
50 | | #include <stdlib.h> // for getenv() |
51 | | #include <stdio.h> // for snprintf(), sscanf() |
52 | | #include <string.h> // for memmove(), memchr(), etc. |
53 | | #include <fcntl.h> // for open() |
54 | | #include <errno.h> // for errno |
55 | | #ifdef HAVE_UNISTD_H |
56 | | #include <unistd.h> // for read() |
57 | | #endif |
58 | | #if defined __MACH__ // Mac OS X, almost certainly |
59 | | #include <sys/types.h> |
60 | | #include <sys/sysctl.h> // how we figure out numcpu's on OS X |
61 | | #elif defined __FreeBSD__ |
62 | | #include <sys/sysctl.h> |
63 | | #elif defined __sun__ // Solaris |
64 | | #include <procfs.h> // for, e.g., prmap_t |
65 | | #elif defined(PLATFORM_WINDOWS) |
66 | | #include <process.h> // for getpid() (actually, _getpid()) |
67 | | #include <shlwapi.h> // for SHGetValueA() |
68 | | #include <tlhelp32.h> // for Module32First() |
69 | | #endif |
70 | | |
71 | | #include <mutex> |
72 | | #include <thread> |
73 | | #include <gflags/gflags.h> |
74 | | |
75 | | #include <glog/logging.h> |
76 | | |
77 | | #include "yb/gutil/casts.h" |
78 | | #include "yb/gutil/dynamic_annotations.h" // for RunningOnValgrind |
79 | | #include "yb/gutil/macros.h" |
80 | | #include "yb/gutil/sysinfo.h" |
81 | | #include "yb/gutil/walltime.h" |
82 | | #include "yb/util/logging.h" |
83 | | |
84 | | DEFINE_int32(num_cpus, 0, "Number of CPU cores used in calculations"); |
85 | | |
86 | | // This isn't in the 'base' namespace in tcmallc. But, tcmalloc |
87 | | // exports these functions, so we need to namespace them to avoid |
88 | | // the conflict. |
89 | | namespace base { |
90 | | |
91 | | // ---------------------------------------------------------------------- |
92 | | // CyclesPerSecond() |
93 | | // NumCPUs() |
94 | | // It's important this not call malloc! -- they may be called at |
95 | | // global-construct time, before we've set up all our proper malloc |
96 | | // hooks and such. |
97 | | // ---------------------------------------------------------------------- |
98 | | |
99 | | static double cpuinfo_cycles_per_second = 1.0; // 0.0 might be dangerous |
100 | | static int cpuinfo_num_cpus = 1; // Conservative guess |
101 | | static int cpuinfo_max_cpu_index = -1; |
102 | | |
103 | 2.05M | void SleepForNanoseconds(int64_t nanoseconds) { |
104 | | // Sleep for nanosecond duration |
105 | 2.05M | struct timespec sleep_time; |
106 | 2.05M | sleep_time.tv_sec = nanoseconds / 1000 / 1000 / 1000; |
107 | 2.05M | sleep_time.tv_nsec = (nanoseconds % (1000 * 1000 * 1000)); |
108 | | // Ignore signals and wait for the full interval to elapse. |
109 | 2.05M | while (nanosleep(&sleep_time, &sleep_time) != 0 && errno == EINTR) {} |
110 | 2.05M | } |
111 | | |
112 | 0 | void SleepForMilliseconds(int64_t milliseconds) { |
113 | 0 | SleepForNanoseconds(milliseconds * 1000 * 1000); |
114 | 0 | } |
115 | | |
116 | | // Helper function estimates cycles/sec by observing cycles elapsed during |
117 | | // sleep(). Using small sleep time decreases accuracy significantly. |
118 | 0 | static int64 EstimateCyclesPerSecond(const int estimate_time_ms) { |
119 | 0 | CHECK_GT(estimate_time_ms, 0); |
120 | 0 | if (estimate_time_ms <= 0) |
121 | 0 | return 1; |
122 | 0 | double multiplier = 1000.0 / estimate_time_ms; // scale by this much |
123 | |
|
124 | 0 | const int64 start_ticks = CycleClock::Now(); |
125 | 0 | SleepForMilliseconds(estimate_time_ms); |
126 | 0 | const int64 guess = static_cast<int64>(multiplier * (CycleClock::Now() - start_ticks)); |
127 | 0 | return guess; |
128 | 0 | } |
129 | | |
130 | | // ReadIntFromFile is only called on linux and cygwin platforms. |
131 | | #if defined(__linux__) || defined(__CYGWIN__) || defined(__CYGWIN32__) |
132 | | |
133 | | // Slurp a file with a single read() call into 'buf'. This is only safe to use on small |
134 | | // files in places like /proc where we are guaranteed not to get a partial read. |
135 | | // Any remaining bytes in the buffer are zeroed. |
136 | | // |
137 | | // 'buflen' must be more than large enough to hold the whole file, or else this will |
138 | | // issue a FATAL error. |
139 | | static bool SlurpSmallTextFile(const char* file, char* buf, int buflen) { |
140 | | bool ret = false; |
141 | | int fd = open(file, O_RDONLY); |
142 | | if (fd == -1) return ret; |
143 | | |
144 | | memset(buf, '\0', buflen); |
145 | | auto n = read(fd, buf, buflen - 1); |
146 | | CHECK_NE(n, buflen - 1) << "buffer of len " << buflen << " not large enough to store " |
147 | | << "contents of " << file; |
148 | | if (n > 0) { |
149 | | ret = true; |
150 | | } |
151 | | |
152 | | close(fd); |
153 | | return ret; |
154 | | } |
155 | | |
156 | | // Helper function for reading an int from a file. Returns true if successful |
157 | | // and the memory location pointed to by value is set to the value read. |
158 | | static bool ReadIntFromFile(const char *file, int *value) { |
159 | | char line[1024]; |
160 | | if (!SlurpSmallTextFile(file, line, arraysize(line))) { |
161 | | return false; |
162 | | } |
163 | | char* err; |
164 | | const auto temp_value = strtol(line, &err, 10); |
165 | | if (line[0] != '\0' && (*err == '\n' || *err == '\0')) { |
166 | | *value = narrow_cast<int>(temp_value); |
167 | | return true; |
168 | | } |
169 | | return false; |
170 | | } |
171 | | |
172 | | static int ReadMaxCPUIndex() { |
173 | | char buf[1024]; |
174 | | CHECK(SlurpSmallTextFile("/sys/devices/system/cpu/present", buf, arraysize(buf))); |
175 | | |
176 | | // On a single-core machine, 'buf' will contain the string '0' with a newline. |
177 | | if (strcmp(buf, "0\n") == 0) { |
178 | | return 0; |
179 | | } |
180 | | |
181 | | // On multi-core, it will have a CPU range like '0-7'. |
182 | | CHECK_EQ(0, memcmp(buf, "0-", 2)) << "bad list of possible CPUs: " << buf; |
183 | | |
184 | | char* max_cpu_str = &buf[2]; |
185 | | char* err; |
186 | | auto val = strtol(max_cpu_str, &err, 10); |
187 | | CHECK(*err == '\n' || *err == '\0') << "unable to parse max CPU index from: " << buf; |
188 | | return narrow_cast<int>(val); |
189 | | } |
190 | | |
191 | | #endif |
192 | | |
193 | | // WARNING: logging calls back to InitializeSystemInfo() so it must |
194 | | // not invoke any logging code. Also, InitializeSystemInfo() can be |
195 | | // called before main() -- in fact it *must* be since already_called |
196 | | // isn't protected -- before malloc hooks are properly set up, so |
197 | | // we make an effort not to call any routines which might allocate |
198 | | // memory. |
199 | | |
200 | 20.6k | static void InitializeSystemInfo() { |
201 | 20.6k | bool saw_mhz = false; |
202 | | |
203 | 20.6k | if (YbRunningOnValgrind()) { |
204 | | // Valgrind may slow the progress of time artificially (--scale-time=N |
205 | | // option). We thus can't rely on CPU Mhz info stored in /sys or /proc |
206 | | // files. Thus, actually measure the cps. |
207 | 0 | cpuinfo_cycles_per_second = EstimateCyclesPerSecond(100); |
208 | 0 | saw_mhz = true; |
209 | 0 | } |
210 | | |
211 | | #if defined(__linux__) || defined(__CYGWIN__) || defined(__CYGWIN32__) |
212 | | char line[1024]; |
213 | | char* err; |
214 | | int freq; |
215 | | |
216 | | // If the kernel is exporting the tsc frequency use that. There are issues |
217 | | // where cpuinfo_max_freq cannot be relied on because the BIOS may be |
218 | | // exporintg an invalid p-state (on x86) or p-states may be used to put the |
219 | | // processor in a new mode (turbo mode). Essentially, those frequencies |
220 | | // cannot always be relied upon. The same reasons apply to /proc/cpuinfo as |
221 | | // well. |
222 | | if (!saw_mhz && |
223 | | ReadIntFromFile("/sys/devices/system/cpu/cpu0/tsc_freq_khz", &freq)) { |
224 | | // The value is in kHz (as the file name suggests). For example, on a |
225 | | // 2GHz warpstation, the file contains the value "2000000". |
226 | | cpuinfo_cycles_per_second = freq * 1000.0; |
227 | | saw_mhz = true; |
228 | | } |
229 | | |
230 | | // If CPU scaling is in effect, we want to use the *maximum* frequency, |
231 | | // not whatever CPU speed some random processor happens to be using now. |
232 | | if (!saw_mhz && |
233 | | ReadIntFromFile("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", |
234 | | &freq)) { |
235 | | // The value is in kHz. For example, on a 2GHz machine, the file |
236 | | // contains the value "2000000". |
237 | | cpuinfo_cycles_per_second = freq * 1000.0; |
238 | | saw_mhz = true; |
239 | | } |
240 | | |
241 | | // Read /proc/cpuinfo for other values, and if there is no cpuinfo_max_freq. |
242 | | const char* pname = "/proc/cpuinfo"; |
243 | | int fd = open(pname, O_RDONLY); |
244 | | if (fd == -1) { |
245 | | PLOG(FATAL) << "Unable to read CPU info from /proc. procfs must be mounted."; |
246 | | } |
247 | | |
248 | | double bogo_clock = 1.0; |
249 | | bool saw_bogo = false; |
250 | | int num_cpus = 0; |
251 | | line[0] = line[1] = '\0'; |
252 | | ssize_t chars_read = 0; |
253 | | do { // we'll exit when the last read didn't read anything |
254 | | // Move the next line to the beginning of the buffer |
255 | | const auto oldlinelen = strlen(line); |
256 | | if (sizeof(line) == oldlinelen + 1) // oldlinelen took up entire line |
257 | | line[0] = '\0'; |
258 | | else // still other lines left to save |
259 | | memmove(line, line + oldlinelen+1, sizeof(line) - (oldlinelen+1)); |
260 | | // Terminate the new line, reading more if we can't find the newline |
261 | | char* newline = strchr(line, '\n'); |
262 | | if (newline == NULL) { |
263 | | const auto linelen = strlen(line); |
264 | | const int64_t bytes_to_read = sizeof(line)-1 - linelen; |
265 | | CHECK_GT(bytes_to_read, 0); // because the memmove recovered >=1 bytes |
266 | | chars_read = read(fd, line + linelen, bytes_to_read); |
267 | | line[linelen + chars_read] = '\0'; |
268 | | newline = strchr(line, '\n'); |
269 | | } |
270 | | if (newline != NULL) |
271 | | *newline = '\0'; |
272 | | |
273 | | #if defined(__powerpc__) || defined(__ppc__) |
274 | | // PowerPC cpus report the frequency in "clock" line |
275 | | if (strncasecmp(line, "clock", sizeof("clock")-1) == 0) { |
276 | | const char* freqstr = strchr(line, ':'); |
277 | | if (freqstr) { |
278 | | // PowerPC frequencies are only reported as MHz (check 'show_cpuinfo' |
279 | | // function at arch/powerpc/kernel/setup-common.c) |
280 | | char *endp = strstr(line, "MHz"); |
281 | | if (endp) { |
282 | | *endp = 0; |
283 | | cpuinfo_cycles_per_second = strtod(freqstr+1, &err) * 1000000.0; |
284 | | if (freqstr[1] != '\0' && *err == '\0' && cpuinfo_cycles_per_second > 0) |
285 | | saw_mhz = true; |
286 | | } |
287 | | } |
288 | | #else |
289 | | // When parsing the "cpu MHz" and "bogomips" (fallback) entries, we only |
290 | | // accept postive values. Some environments (virtual machines) report zero, |
291 | | // which would cause infinite looping in WallTime_Init. |
292 | | if (!saw_mhz && strncasecmp(line, "cpu MHz", sizeof("cpu MHz")-1) == 0) { |
293 | | const char* freqstr = strchr(line, ':'); |
294 | | if (freqstr) { |
295 | | cpuinfo_cycles_per_second = strtod(freqstr+1, &err) * 1000000.0; |
296 | | if (freqstr[1] != '\0' && *err == '\0' && cpuinfo_cycles_per_second > 0) |
297 | | saw_mhz = true; |
298 | | } |
299 | | } else if (strncasecmp(line, "bogomips", sizeof("bogomips")-1) == 0) { |
300 | | const char* freqstr = strchr(line, ':'); |
301 | | if (freqstr) { |
302 | | bogo_clock = strtod(freqstr+1, &err) * 1000000.0; |
303 | | if (freqstr[1] != '\0' && *err == '\0' && bogo_clock > 0) |
304 | | saw_bogo = true; |
305 | | } |
306 | | #endif |
307 | | } else if (strncasecmp(line, "processor", sizeof("processor")-1) == 0) { |
308 | | num_cpus++; // count up every time we see an "processor :" entry |
309 | | } |
310 | | } while (chars_read > 0); |
311 | | close(fd); |
312 | | |
313 | | if (!saw_mhz) { |
314 | | if (saw_bogo) { |
315 | | // If we didn't find anything better, we'll use bogomips, but |
316 | | // we're not happy about it. |
317 | | cpuinfo_cycles_per_second = bogo_clock; |
318 | | } else { |
319 | | // If we don't even have bogomips, we'll use the slow estimation. |
320 | | cpuinfo_cycles_per_second = EstimateCyclesPerSecond(1000); |
321 | | } |
322 | | } |
323 | | if (cpuinfo_cycles_per_second == 0.0) { |
324 | | cpuinfo_cycles_per_second = 1.0; // maybe unnecessary, but safe |
325 | | } |
326 | | if (num_cpus > 0) { |
327 | | cpuinfo_num_cpus = num_cpus; |
328 | | } |
329 | | cpuinfo_max_cpu_index = ReadMaxCPUIndex(); |
330 | | |
331 | | #elif defined __FreeBSD__ |
332 | | // For this sysctl to work, the machine must be configured without |
333 | | // SMP, APIC, or APM support. hz should be 64-bit in freebsd 7.0 |
334 | | // and later. Before that, it's a 32-bit quantity (and gives the |
335 | | // wrong answer on machines faster than 2^32 Hz). See |
336 | | // http://lists.freebsd.org/pipermail/freebsd-i386/2004-November/001846.html |
337 | | // But also compare FreeBSD 7.0: |
338 | | // http://fxr.watson.org/fxr/source/i386/i386/tsc.c?v=RELENG70#L223 |
339 | | // 231 error = sysctl_handle_quad(oidp, &freq, 0, req); |
340 | | // To FreeBSD 6.3 (it's the same in 6-STABLE): |
341 | | // http://fxr.watson.org/fxr/source/i386/i386/tsc.c?v=RELENG6#L131 |
342 | | // 139 error = sysctl_handle_int(oidp, &freq, sizeof(freq), req); |
343 | | #if __FreeBSD__ >= 7 |
344 | | uint64_t hz = 0; |
345 | | #else |
346 | | unsigned int hz = 0; |
347 | | #endif |
348 | | size_t sz = sizeof(hz); |
349 | | const char *sysctl_path = "machdep.tsc_freq"; |
350 | | if ( sysctlbyname(sysctl_path, &hz, &sz, NULL, 0) != 0 ) { |
351 | | fprintf(stderr, "Unable to determine clock rate from sysctl: %s: %s\n", |
352 | | sysctl_path, strerror(errno)); |
353 | | cpuinfo_cycles_per_second = EstimateCyclesPerSecond(1000); |
354 | | } else { |
355 | | cpuinfo_cycles_per_second = hz; |
356 | | } |
357 | | // TODO(csilvers): also figure out cpuinfo_num_cpus |
358 | | |
359 | | #elif defined(PLATFORM_WINDOWS) |
360 | | # pragma comment(lib, "shlwapi.lib") // for SHGetValue() |
361 | | // In NT, read MHz from the registry. If we fail to do so or we're in win9x |
362 | | // then make a crude estimate. |
363 | | OSVERSIONINFO os; |
364 | | os.dwOSVersionInfoSize = sizeof(os); |
365 | | DWORD data, data_size = sizeof(data); |
366 | | if (GetVersionEx(&os) && |
367 | | os.dwPlatformId == VER_PLATFORM_WIN32_NT && |
368 | | SUCCEEDED(SHGetValueA(HKEY_LOCAL_MACHINE, |
369 | | "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", |
370 | | "~MHz", NULL, &data, &data_size))) { |
371 | | cpuinfo_cycles_per_second = static_cast<int64>(data) * (1000 * 1000); // was mhz |
372 | | } else { |
373 | | cpuinfo_cycles_per_second = EstimateCyclesPerSecond(500); // TODO <500? |
374 | | } |
375 | | |
376 | | // Get the number of processors. |
377 | | SYSTEM_INFO info; |
378 | | GetSystemInfo(&info); |
379 | | cpuinfo_num_cpus = info.dwNumberOfProcessors; |
380 | | |
381 | | #elif defined(__MACH__) && defined(__APPLE__) |
382 | | // returning "mach time units" per second. the current number of elapsed |
383 | | // mach time units can be found by calling uint64 mach_absolute_time(); |
384 | | // while not as precise as actual CPU cycles, it is accurate in the face |
385 | | // of CPU frequency scaling and multi-cpu/core machines. |
386 | | // Our mac users have these types of machines, and accuracy |
387 | | // (i.e. correctness) trumps precision. |
388 | | // See cycleclock.h: CycleClock::Now(), which returns number of mach time |
389 | | // units on Mac OS X. |
390 | 20.6k | mach_timebase_info_data_t timebase_info; |
391 | 20.6k | mach_timebase_info(&timebase_info); |
392 | 20.6k | double mach_time_units_per_nanosecond = |
393 | 20.6k | static_cast<double>(timebase_info.denom) / |
394 | 20.6k | static_cast<double>(timebase_info.numer); |
395 | 20.6k | cpuinfo_cycles_per_second = mach_time_units_per_nanosecond * 1e9; |
396 | | |
397 | 20.6k | int num_cpus = 0; |
398 | 20.6k | size_t size = sizeof(num_cpus); |
399 | 20.6k | int numcpus_name[] = { CTL_HW, HW_NCPU }; |
400 | 20.6k | if (::sysctl(numcpus_name, arraysize(numcpus_name), &num_cpus, &size, nullptr, 0) |
401 | 20.6k | == 0 |
402 | 20.6k | && (size == sizeof(num_cpus))) |
403 | 20.6k | cpuinfo_num_cpus = num_cpus; |
404 | | |
405 | | #else |
406 | | // Generic cycles per second counter |
407 | | cpuinfo_cycles_per_second = EstimateCyclesPerSecond(1000); |
408 | | #endif |
409 | 20.6k | int std_num_cpus = std::thread::hardware_concurrency(); |
410 | 20.6k | if (std_num_cpus > 0) { |
411 | 20.6k | cpuinfo_num_cpus = std_num_cpus; |
412 | 20.6k | } |
413 | | // On platforms where we can't determine the max CPU index, just use the |
414 | | // number of CPUs. This might break if CPUs are taken offline, but |
415 | | // better than a wild guess. |
416 | 20.6k | if (cpuinfo_max_cpu_index < 0) { |
417 | 20.6k | cpuinfo_max_cpu_index = cpuinfo_num_cpus - 1; |
418 | 20.6k | } |
419 | 20.6k | } |
420 | | |
421 | | std::once_flag init_sys_info_flag; |
422 | | |
423 | 15.1k | double CyclesPerSecond(void) { |
424 | 15.1k | std::call_once(init_sys_info_flag, &InitializeSystemInfo); |
425 | 15.1k | return cpuinfo_cycles_per_second; |
426 | 15.1k | } |
427 | | |
428 | 3.16M | int NumCPUs(void) { |
429 | 3.16M | std::call_once(init_sys_info_flag, &InitializeSystemInfo); |
430 | | |
431 | 3.16M | if (FLAGS_num_cpus != 0) { |
432 | 1 | return FLAGS_num_cpus; |
433 | 3.16M | } else { |
434 | 3.16M | return cpuinfo_num_cpus; |
435 | 3.16M | } |
436 | 3.16M | } |
437 | | |
438 | 33.1k | int RawNumCPUs(void) { |
439 | 33.1k | std::call_once(init_sys_info_flag, &InitializeSystemInfo); |
440 | 33.1k | return cpuinfo_num_cpus; |
441 | 33.1k | } |
442 | | |
443 | 110k | int MaxCPUIndex(void) { |
444 | 110k | std::call_once(init_sys_info_flag, &InitializeSystemInfo); |
445 | 110k | return cpuinfo_max_cpu_index; |
446 | 110k | } |
447 | | |
448 | | } // namespace base |