diff options
Diffstat (limited to 'third_party/rust/num_cpus')
28 files changed, 1594 insertions, 0 deletions
diff --git a/third_party/rust/num_cpus/.cargo-checksum.json b/third_party/rust/num_cpus/.cargo-checksum.json new file mode 100644 index 0000000000..496119d1d1 --- /dev/null +++ b/third_party/rust/num_cpus/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"66c7e8dedaa631c5e8d189b896efb691a97529358a56ad78282fbd4cbae3c638","CONTRIBUTING.md":"2390961aab1bba026135338da1216b6cc828dfaeed9357d9c155c55a252d3efb","Cargo.lock":"5a782f53bfc63c9ef7a901472c8ae3599d3f5ecb6f8bb9ce871235b4d9c8bfc1","Cargo.toml":"7a8fa6033b28607f305e144269902dc62c082cf02f76780fcce866810fcf931b","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0593d22d122d4bfec6407115e3907546312976f75473417aaa4c57ecd2095ae6","README.md":"7760d315d3efd2306affa4c8e4dcdb3a245ae30b3c9cfb9cffed2f1116c86362","ci/cgroups/Dockerfile":"567f00918a6422b363e2c21e57bd47cef9dcc03d0ab109c9605e052f83af7e89","examples/values.rs":"46c833324b7339d359054c4f8e8284259e860df206c552c63b5893ade59c16a6","fixtures/cgroups/cgroups/ceil/cpu.cfs_period_us":"d2ace393dc9388863d75d8de140df516d7ffe4aa7ed2f9a545aa71c9930d6638","fixtures/cgroups/cgroups/ceil/cpu.cfs_quota_us":"7ccd86cde0b22ffc2318f2509726d2a13053f6973e96dc5ca6965a56497e485e","fixtures/cgroups/cgroups/good/cpu.cfs_period_us":"d2ace393dc9388863d75d8de140df516d7ffe4aa7ed2f9a545aa71c9930d6638","fixtures/cgroups/cgroups/good/cpu.cfs_quota_us":"cdc3397c35d915e5fe61f8d2bdedcae00a225d55cc6b090580cde1b71c63463b","fixtures/cgroups/cgroups/zero-period/cpu.cfs_period_us":"74d01a0c051c963d9a9b8ab9dbeab1723f0ad8534ea9fa6a942f358d7fa011b4","fixtures/cgroups/cgroups/zero-period/cpu.cfs_quota_us":"1e6ffd8a95fab538ddd645a767e8cc505722d5c8aaf008969f2ed8ab753ff61e","fixtures/cgroups/proc/cgroups/cgroup":"6812299a4409bfd831ed751fdbbfdd9c5749f69acd7b14c5b0a704271a1f74c6","fixtures/cgroups/proc/cgroups/mountinfo":"3187b0b1c0fa192790abced7d435190e8979059186055688e4c3c2ca013398c6","fixtures/cgroups/proc/cgroups/mountinfo_multi_opt":"d1e397752bc5c4558a3230ad847e89c6885362eab68a53b08dbf219de2e0138a","fixtures/cgroups/proc/cgroups/mountinfo_zero_opt":"91b03b270c76460bc19225aa4b743f893c7d32173e9609ef77e5a04814ab81ff","fixtures/cgroups2/cgroups/ceil/cpu.max":"d6eb496d0851963c9e6a9cf33c0a3ef2f08cbbee2a387c093b30ceca23239226","fixtures/cgroups2/cgroups/good/cpu.max":"2f8bd783be33cf80b6a2a0cf810f81ccf85f988c72c9bb76fa6172b6e4ec2b02","fixtures/cgroups2/cgroups/zero-period/cpu.max":"f42f2771a5f669873e0f45400ff64c481618029fe14a5bcb15d51b426646a9aa","fixtures/cgroups2/proc/cgroups/cgroup":"f8327c16331cc79ecbf875bd7bea6cf1831757589c39e16a149bfb3b6f81f7c8","fixtures/cgroups2/proc/cgroups/cgroup_multi":"96e107b552b1f51a8e96199c12e42dc3807e207d5a1be4a901f49db8e23ed869","fixtures/cgroups2/proc/cgroups/mountinfo":"7ac8cda160d4d80de73195fda560b46ed3826f43ef07fb513308549b32b08b56","src/lib.rs":"81fc237964757a53689db848b1df4fed75bbd6a6e7a9063b6bb008161f8c22af","src/linux.rs":"378ea174d00d560e16fda4b09fb523dda5f4c6f420e46af7d317393a39d88dd1"},"package":"0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"}
\ No newline at end of file diff --git a/third_party/rust/num_cpus/CHANGELOG.md b/third_party/rust/num_cpus/CHANGELOG.md new file mode 100644 index 0000000000..5be725c547 --- /dev/null +++ b/third_party/rust/num_cpus/CHANGELOG.md @@ -0,0 +1,150 @@ +## v1.15.0 + +### Fixes + +- update hermit-abi + +## v1.14.0 + +### Features + +- add support for cgroups v2 +- Skip reading files in Miri + +## v1.13.1 + +### Fixes + +- fix parsing zero or multiple optional fields in cgroup mountinfo. + +## v1.13.0 + +### Features + +- add Linux cgroups support when calling `get()`. + +## v1.12.0 + +#### Fixes + +- fix `get` on OpenBSD to ignore offline CPUs +- implement `get_physical` on OpenBSD + +## v1.11.1 + +#### Fixes + +- Use `mem::zeroed` instead of `mem::uninitialized`. + +## v1.11.0 + +#### Features + +- add `hermit` target OS support +- removes `bitrig` support + +#### Fixes + +- fix `get_physical` count with AMD hyperthreading. + +## v1.10.1 + +#### Fixes + +- improve `haiku` CPU detection + +## v1.10.0 + +#### Features + +- add `illumos` target OS support +- add default fallback if target is unknown to `1` + +## v1.9.0 + +#### Features + +- add `sgx` target env support + +## v1.8.0 + +#### Features + +- add `wasm-unknown-unknown` target support + +## v1.7.0 + +#### Features + +- add `get_physical` support for macOS + +#### Fixes + +- use `_SC_NPROCESSORS_CONF` on Unix targets + +### v1.6.2 + +#### Fixes + +- revert 1.6.1 for now + +### v1.6.1 + +#### Fixes + +- fixes sometimes incorrect num on Android/ARM Linux (#45) + +## v1.6.0 + +#### Features + +- `get_physical` gains Windows support + +### v1.5.1 + +#### Fixes + +- fix `get` to return 1 if `sysconf(_SC_NPROCESSORS_ONLN)` failed + +## v1.5.0 + +#### Features + +- `get()` now checks `sched_affinity` on Linux + +## v1.4.0 + +#### Features + +- add `haiku` target support + +## v1.3.0 + +#### Features + +- add `redox` target support + +### v1.2.1 + +#### Fixes + +- fixes `get_physical` count (454ff1b) + +## v1.2.0 + +#### Features + +- add `emscripten` target support +- add `fuchsia` target support + +## v1.1.0 + +#### Features + +- added `get_physical` function to return number of physical CPUs found + +# v1.0.0 + +#### Features + +- `get` function returns number of CPUs (physical and virtual) of current platform diff --git a/third_party/rust/num_cpus/CONTRIBUTING.md b/third_party/rust/num_cpus/CONTRIBUTING.md new file mode 100644 index 0000000000..5685b08c67 --- /dev/null +++ b/third_party/rust/num_cpus/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Contributing + +## License + +Licensed under either of + + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/third_party/rust/num_cpus/Cargo.lock b/third_party/rust/num_cpus/Cargo.lock new file mode 100644 index 0000000000..1e955390a3 --- /dev/null +++ b/third_party/rust/num_cpus/Cargo.lock @@ -0,0 +1,26 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" + +[[package]] +name = "num_cpus" +version = "1.15.0" +dependencies = [ + "hermit-abi", + "libc", +] diff --git a/third_party/rust/num_cpus/Cargo.toml b/third_party/rust/num_cpus/Cargo.toml new file mode 100644 index 0000000000..6d62e8626f --- /dev/null +++ b/third_party/rust/num_cpus/Cargo.toml @@ -0,0 +1,32 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +name = "num_cpus" +version = "1.15.0" +authors = ["Sean McArthur <sean@seanmonstar.com>"] +description = "Get the number of CPUs on a machine." +documentation = "https://docs.rs/num_cpus" +readme = "README.md" +keywords = [ + "cpu", + "cpus", + "cores", +] +categories = ["hardware-support"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/seanmonstar/num_cpus" + +[target."cfg(all(any(target_arch = \"x86_64\", target_arch = \"aarch64\"), target_os = \"hermit\"))".dependencies.hermit-abi] +version = "0.2.6" + +[target."cfg(not(windows))".dependencies.libc] +version = "0.2.26" diff --git a/third_party/rust/num_cpus/LICENSE-APACHE b/third_party/rust/num_cpus/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/third_party/rust/num_cpus/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/num_cpus/LICENSE-MIT b/third_party/rust/num_cpus/LICENSE-MIT new file mode 100644 index 0000000000..8e91dc998a --- /dev/null +++ b/third_party/rust/num_cpus/LICENSE-MIT @@ -0,0 +1,20 @@ +Copyright (c) 2015 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/third_party/rust/num_cpus/README.md b/third_party/rust/num_cpus/README.md new file mode 100644 index 0000000000..1de7d8ec6c --- /dev/null +++ b/third_party/rust/num_cpus/README.md @@ -0,0 +1,28 @@ +# num_cpus + +[![crates.io](https://img.shields.io/crates/v/num_cpus.svg)](https://crates.io/crates/num_cpus) +[![Travis CI Status](https://travis-ci.org/seanmonstar/num_cpus.svg?branch=master)](https://travis-ci.org/seanmonstar/num_cpus) +[![AppVeyor status](https://ci.appveyor.com/api/projects/status/qn8t6grhko5jwno6?svg=true)](https://ci.appveyor.com/project/seanmonstar/num-cpus) + +- [Documentation](https://docs.rs/num_cpus) +- [CHANGELOG](CHANGELOG.md) + +Count the number of CPUs on the current machine. + +## Usage + +Add to Cargo.toml: + +```toml +[dependencies] +num_cpus = "1.0" +``` + +In your `main.rs` or `lib.rs`: + +```rust +extern crate num_cpus; + +// count logical cores this process could try to use +let num = num_cpus::get(); +``` diff --git a/third_party/rust/num_cpus/ci/cgroups/Dockerfile b/third_party/rust/num_cpus/ci/cgroups/Dockerfile new file mode 100644 index 0000000000..7636a477bf --- /dev/null +++ b/third_party/rust/num_cpus/ci/cgroups/Dockerfile @@ -0,0 +1,9 @@ +FROM rust:latest + +WORKDIR /usr/num_cpus + +COPY . . + +RUN cargo build + +CMD [ "cargo", "test", "--lib" ] diff --git a/third_party/rust/num_cpus/examples/values.rs b/third_party/rust/num_cpus/examples/values.rs new file mode 100644 index 0000000000..041cfaf1eb --- /dev/null +++ b/third_party/rust/num_cpus/examples/values.rs @@ -0,0 +1,6 @@ +extern crate num_cpus; + +fn main() { + println!("Logical CPUs: {}", num_cpus::get()); + println!("Physical CPUs: {}", num_cpus::get_physical()); +} diff --git a/third_party/rust/num_cpus/fixtures/cgroups/cgroups/ceil/cpu.cfs_period_us b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/ceil/cpu.cfs_period_us new file mode 100644 index 0000000000..022f514ba1 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/ceil/cpu.cfs_period_us @@ -0,0 +1,2 @@ +100000 + diff --git a/third_party/rust/num_cpus/fixtures/cgroups/cgroups/ceil/cpu.cfs_quota_us b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/ceil/cpu.cfs_quota_us new file mode 100644 index 0000000000..cb2bf3ec7a --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/ceil/cpu.cfs_quota_us @@ -0,0 +1,2 @@ +150000 + diff --git a/third_party/rust/num_cpus/fixtures/cgroups/cgroups/good/cpu.cfs_period_us b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/good/cpu.cfs_period_us new file mode 100644 index 0000000000..022f514ba1 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/good/cpu.cfs_period_us @@ -0,0 +1,2 @@ +100000 + diff --git a/third_party/rust/num_cpus/fixtures/cgroups/cgroups/good/cpu.cfs_quota_us b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/good/cpu.cfs_quota_us new file mode 100644 index 0000000000..16e37df1d1 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/good/cpu.cfs_quota_us @@ -0,0 +1,2 @@ +600000 + diff --git a/third_party/rust/num_cpus/fixtures/cgroups/cgroups/zero-period/cpu.cfs_period_us b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/zero-period/cpu.cfs_period_us new file mode 100644 index 0000000000..77ac542d4f --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/zero-period/cpu.cfs_period_us @@ -0,0 +1,2 @@ +0 + diff --git a/third_party/rust/num_cpus/fixtures/cgroups/cgroups/zero-period/cpu.cfs_quota_us b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/zero-period/cpu.cfs_quota_us new file mode 100644 index 0000000000..26f3b3ddf2 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups/cgroups/zero-period/cpu.cfs_quota_us @@ -0,0 +1 @@ +600000 diff --git a/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/cgroup b/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/cgroup new file mode 100644 index 0000000000..a47f047247 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/cgroup @@ -0,0 +1,3 @@ +12:perf_event:/ +11:cpu,cpuacct:/ +3:devices:/user.slice diff --git a/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/mountinfo b/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/mountinfo new file mode 100644 index 0000000000..37f4e25359 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/mountinfo @@ -0,0 +1,8 @@ +1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered +2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755 +3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw +4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw +5 4 0:4 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:5 - tmpfs tmpfs ro,mode=755 +6 5 0:5 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:6 - cgroup cgroup rw,cpuset +7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct +8 5 0:7 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:8 - cgroup cgroup rw,memory diff --git a/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/mountinfo_multi_opt b/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/mountinfo_multi_opt new file mode 100644 index 0000000000..e03a95b425 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/mountinfo_multi_opt @@ -0,0 +1,8 @@ +1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered +2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755 +3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw +4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw +5 4 0:4 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:5 - tmpfs tmpfs ro,mode=755 +6 5 0:5 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:6 - cgroup cgroup rw,cpuset +7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 shared:8 shared:9 - cgroup cgroup rw,cpu,cpuacct +8 5 0:7 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:8 - cgroup cgroup rw,memory diff --git a/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/mountinfo_zero_opt b/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/mountinfo_zero_opt new file mode 100644 index 0000000000..ad27a964f3 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups/proc/cgroups/mountinfo_zero_opt @@ -0,0 +1,8 @@ +1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered +2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755 +3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw +4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw +5 4 0:4 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:5 - tmpfs tmpfs ro,mode=755 +6 5 0:5 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:6 - cgroup cgroup rw,cpuset +7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu,cpuacct +8 5 0:7 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:8 - cgroup cgroup rw,memory diff --git a/third_party/rust/num_cpus/fixtures/cgroups2/cgroups/ceil/cpu.max b/third_party/rust/num_cpus/fixtures/cgroups2/cgroups/ceil/cpu.max new file mode 100644 index 0000000000..833a8f2d3b --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups2/cgroups/ceil/cpu.max @@ -0,0 +1 @@ +150000 100000 diff --git a/third_party/rust/num_cpus/fixtures/cgroups2/cgroups/good/cpu.max b/third_party/rust/num_cpus/fixtures/cgroups2/cgroups/good/cpu.max new file mode 100644 index 0000000000..e469067a61 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups2/cgroups/good/cpu.max @@ -0,0 +1 @@ +600000 100000 diff --git a/third_party/rust/num_cpus/fixtures/cgroups2/cgroups/zero-period/cpu.max b/third_party/rust/num_cpus/fixtures/cgroups2/cgroups/zero-period/cpu.max new file mode 100644 index 0000000000..24e757f51c --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups2/cgroups/zero-period/cpu.max @@ -0,0 +1 @@ +600000 0 diff --git a/third_party/rust/num_cpus/fixtures/cgroups2/proc/cgroups/cgroup b/third_party/rust/num_cpus/fixtures/cgroups2/proc/cgroups/cgroup new file mode 100644 index 0000000000..35b49db2c0 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups2/proc/cgroups/cgroup @@ -0,0 +1,2 @@ +12::/ +3::/user.slice diff --git a/third_party/rust/num_cpus/fixtures/cgroups2/proc/cgroups/cgroup_multi b/third_party/rust/num_cpus/fixtures/cgroups2/proc/cgroups/cgroup_multi new file mode 100644 index 0000000000..1a9282a6e4 --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups2/proc/cgroups/cgroup_multi @@ -0,0 +1,3 @@ +12::/ +11:cpu,cpuacct:/ +3::/user.slice diff --git a/third_party/rust/num_cpus/fixtures/cgroups2/proc/cgroups/mountinfo b/third_party/rust/num_cpus/fixtures/cgroups2/proc/cgroups/mountinfo new file mode 100644 index 0000000000..da36e4102f --- /dev/null +++ b/third_party/rust/num_cpus/fixtures/cgroups2/proc/cgroups/mountinfo @@ -0,0 +1,5 @@ +1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered +2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755 +3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw +4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw +5 4 0:4 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:5 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot diff --git a/third_party/rust/num_cpus/src/lib.rs b/third_party/rust/num_cpus/src/lib.rs new file mode 100644 index 0000000000..20474af4b3 --- /dev/null +++ b/third_party/rust/num_cpus/src/lib.rs @@ -0,0 +1,459 @@ +//! A crate with utilities to determine the number of CPUs available on the +//! current system. +//! +//! Sometimes the CPU will exaggerate the number of CPUs it contains, because it can use +//! [processor tricks] to deliver increased performance when there are more threads. This +//! crate provides methods to get both the logical and physical numbers of cores. +//! +//! This information can be used as a guide to how many tasks can be run in parallel. +//! There are many properties of the system architecture that will affect parallelism, +//! for example memory access speeds (for all the caches and RAM) and the physical +//! architecture of the processor, so the number of CPUs should be used as a rough guide +//! only. +//! +//! +//! ## Examples +//! +//! Fetch the number of logical CPUs. +//! +//! ``` +//! let cpus = num_cpus::get(); +//! ``` +//! +//! See [`rayon::Threadpool`] for an example of where the number of CPUs could be +//! used when setting up parallel jobs (Where the threadpool example uses a fixed +//! number 8, it could use the number of CPUs). +//! +//! [processor tricks]: https://en.wikipedia.org/wiki/Simultaneous_multithreading +//! [`rayon::ThreadPool`]: https://docs.rs/rayon/1.*/rayon/struct.ThreadPool.html +#![cfg_attr(test, deny(warnings))] +#![deny(missing_docs)] +#![allow(non_snake_case)] + +#[cfg(not(windows))] +extern crate libc; + +#[cfg(target_os = "hermit")] +extern crate hermit_abi; + +#[cfg(target_os = "linux")] +mod linux; +#[cfg(target_os = "linux")] +use linux::{get_num_cpus, get_num_physical_cpus}; + +/// Returns the number of available CPUs of the current system. +/// +/// This function will get the number of logical cores. Sometimes this is different from the number +/// of physical cores (See [Simultaneous multithreading on Wikipedia][smt]). +/// +/// This will always return at least `1`. +/// +/// # Examples +/// +/// ``` +/// let cpus = num_cpus::get(); +/// if cpus > 1 { +/// println!("We are on a multicore system with {} CPUs", cpus); +/// } else { +/// println!("We are on a single core system"); +/// } +/// ``` +/// +/// # Note +/// +/// This will check [sched affinity] on Linux, showing a lower number of CPUs if the current +/// thread does not have access to all the computer's CPUs. +/// +/// This will also check [cgroups], frequently used in containers to constrain CPU usage. +/// +/// [smt]: https://en.wikipedia.org/wiki/Simultaneous_multithreading +/// [sched affinity]: http://www.gnu.org/software/libc/manual/html_node/CPU-Affinity.html +/// [cgroups]: https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt +#[inline] +pub fn get() -> usize { + get_num_cpus() +} + +/// Returns the number of physical cores of the current system. +/// +/// This will always return at least `1`. +/// +/// # Note +/// +/// Physical count is supported only on Linux, mac OS and Windows platforms. +/// On other platforms, or if the physical count fails on supported platforms, +/// this function returns the same as [`get()`], which is the number of logical +/// CPUS. +/// +/// # Examples +/// +/// ``` +/// let logical_cpus = num_cpus::get(); +/// let physical_cpus = num_cpus::get_physical(); +/// if logical_cpus > physical_cpus { +/// println!("We have simultaneous multithreading with about {:.2} \ +/// logical cores to 1 physical core.", +/// (logical_cpus as f64) / (physical_cpus as f64)); +/// } else if logical_cpus == physical_cpus { +/// println!("Either we don't have simultaneous multithreading, or our \ +/// system doesn't support getting the number of physical CPUs."); +/// } else { +/// println!("We have less logical CPUs than physical CPUs, maybe we only have access to \ +/// some of the CPUs on our system."); +/// } +/// ``` +/// +/// [`get()`]: fn.get.html +#[inline] +pub fn get_physical() -> usize { + get_num_physical_cpus() +} + + +#[cfg(not(any(target_os = "linux", target_os = "windows", target_os="macos", target_os="openbsd")))] +#[inline] +fn get_num_physical_cpus() -> usize { + // Not implemented, fall back + get_num_cpus() +} + +#[cfg(target_os = "windows")] +fn get_num_physical_cpus() -> usize { + match get_num_physical_cpus_windows() { + Some(num) => num, + None => get_num_cpus() + } +} + +#[cfg(target_os = "windows")] +fn get_num_physical_cpus_windows() -> Option<usize> { + // Inspired by https://msdn.microsoft.com/en-us/library/ms683194 + + use std::ptr; + use std::mem; + + #[allow(non_upper_case_globals)] + const RelationProcessorCore: u32 = 0; + + #[repr(C)] + #[allow(non_camel_case_types)] + struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION { + mask: usize, + relationship: u32, + _unused: [u64; 2] + } + + extern "system" { + fn GetLogicalProcessorInformation( + info: *mut SYSTEM_LOGICAL_PROCESSOR_INFORMATION, + length: &mut u32 + ) -> u32; + } + + // First we need to determine how much space to reserve. + + // The required size of the buffer, in bytes. + let mut needed_size = 0; + + unsafe { + GetLogicalProcessorInformation(ptr::null_mut(), &mut needed_size); + } + + let struct_size = mem::size_of::<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>() as u32; + + // Could be 0, or some other bogus size. + if needed_size == 0 || needed_size < struct_size || needed_size % struct_size != 0 { + return None; + } + + let count = needed_size / struct_size; + + // Allocate some memory where we will store the processor info. + let mut buf = Vec::with_capacity(count as usize); + + let result; + + unsafe { + result = GetLogicalProcessorInformation(buf.as_mut_ptr(), &mut needed_size); + } + + // Failed for any reason. + if result == 0 { + return None; + } + + let count = needed_size / struct_size; + + unsafe { + buf.set_len(count as usize); + } + + let phys_proc_count = buf.iter() + // Only interested in processor packages (physical processors.) + .filter(|proc_info| proc_info.relationship == RelationProcessorCore) + .count(); + + if phys_proc_count == 0 { + None + } else { + Some(phys_proc_count) + } +} + +#[cfg(windows)] +fn get_num_cpus() -> usize { + #[repr(C)] + struct SYSTEM_INFO { + wProcessorArchitecture: u16, + wReserved: u16, + dwPageSize: u32, + lpMinimumApplicationAddress: *mut u8, + lpMaximumApplicationAddress: *mut u8, + dwActiveProcessorMask: *mut u8, + dwNumberOfProcessors: u32, + dwProcessorType: u32, + dwAllocationGranularity: u32, + wProcessorLevel: u16, + wProcessorRevision: u16, + } + + extern "system" { + fn GetSystemInfo(lpSystemInfo: *mut SYSTEM_INFO); + } + + unsafe { + let mut sysinfo: SYSTEM_INFO = std::mem::zeroed(); + GetSystemInfo(&mut sysinfo); + sysinfo.dwNumberOfProcessors as usize + } +} + +#[cfg(any(target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd"))] +fn get_num_cpus() -> usize { + use std::ptr; + + let mut cpus: libc::c_uint = 0; + let mut cpus_size = std::mem::size_of_val(&cpus); + + unsafe { + cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint; + } + if cpus < 1 { + let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; + unsafe { + libc::sysctl(mib.as_mut_ptr(), + 2, + &mut cpus as *mut _ as *mut _, + &mut cpus_size as *mut _ as *mut _, + ptr::null_mut(), + 0); + } + if cpus < 1 { + cpus = 1; + } + } + cpus as usize +} + +#[cfg(target_os = "openbsd")] +fn get_num_cpus() -> usize { + use std::ptr; + + let mut cpus: libc::c_uint = 0; + let mut cpus_size = std::mem::size_of_val(&cpus); + let mut mib = [libc::CTL_HW, libc::HW_NCPUONLINE, 0, 0]; + let rc: libc::c_int; + + unsafe { + rc = libc::sysctl(mib.as_mut_ptr(), + 2, + &mut cpus as *mut _ as *mut _, + &mut cpus_size as *mut _ as *mut _, + ptr::null_mut(), + 0); + } + if rc < 0 { + cpus = 1; + } + cpus as usize +} + +#[cfg(target_os = "openbsd")] +fn get_num_physical_cpus() -> usize { + use std::ptr; + + let mut cpus: libc::c_uint = 0; + let mut cpus_size = std::mem::size_of_val(&cpus); + let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; + let rc: libc::c_int; + + unsafe { + rc = libc::sysctl(mib.as_mut_ptr(), + 2, + &mut cpus as *mut _ as *mut _, + &mut cpus_size as *mut _ as *mut _, + ptr::null_mut(), + 0); + } + if rc < 0 { + cpus = 1; + } + cpus as usize +} + + +#[cfg(target_os = "macos")] +fn get_num_physical_cpus() -> usize { + use std::ffi::CStr; + use std::ptr; + + let mut cpus: i32 = 0; + let mut cpus_size = std::mem::size_of_val(&cpus); + + let sysctl_name = CStr::from_bytes_with_nul(b"hw.physicalcpu\0") + .expect("byte literal is missing NUL"); + + unsafe { + if 0 != libc::sysctlbyname(sysctl_name.as_ptr(), + &mut cpus as *mut _ as *mut _, + &mut cpus_size as *mut _ as *mut _, + ptr::null_mut(), + 0) { + return get_num_cpus(); + } + } + cpus as usize +} + +#[cfg(any( + target_os = "nacl", + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia") +)] +fn get_num_cpus() -> usize { + // On ARM targets, processors could be turned off to save power. + // Use `_SC_NPROCESSORS_CONF` to get the real number. + #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] + const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_CONF; + #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] + const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_ONLN; + + let cpus = unsafe { libc::sysconf(CONF_NAME) }; + if cpus < 1 { + 1 + } else { + cpus as usize + } +} + +#[cfg(target_os = "haiku")] +fn get_num_cpus() -> usize { + use std::mem; + + #[allow(non_camel_case_types)] + type bigtime_t = i64; + #[allow(non_camel_case_types)] + type status_t = i32; + + #[repr(C)] + pub struct system_info { + pub boot_time: bigtime_t, + pub cpu_count: u32, + pub max_pages: u64, + pub used_pages: u64, + pub cached_pages: u64, + pub block_cache_pages: u64, + pub ignored_pages: u64, + pub needed_memory: u64, + pub free_memory: u64, + pub max_swap_pages: u64, + pub free_swap_pages: u64, + pub page_faults: u32, + pub max_sems: u32, + pub used_sems: u32, + pub max_ports: u32, + pub used_ports: u32, + pub max_threads: u32, + pub used_threads: u32, + pub max_teams: u32, + pub used_teams: u32, + pub kernel_name: [::std::os::raw::c_char; 256usize], + pub kernel_build_date: [::std::os::raw::c_char; 32usize], + pub kernel_build_time: [::std::os::raw::c_char; 32usize], + pub kernel_version: i64, + pub abi: u32, + } + + extern { + fn get_system_info(info: *mut system_info) -> status_t; + } + + let mut info: system_info = unsafe { mem::zeroed() }; + let status = unsafe { get_system_info(&mut info as *mut _) }; + if status == 0 { + info.cpu_count as usize + } else { + 1 + } +} + +#[cfg(target_os = "hermit")] +fn get_num_cpus() -> usize { + unsafe { hermit_abi::get_processor_count() } +} + +#[cfg(not(any( + target_os = "nacl", + target_os = "macos", + target_os = "ios", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "fuchsia", + target_os = "linux", + target_os = "openbsd", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "haiku", + target_os = "hermit", + windows, +)))] +fn get_num_cpus() -> usize { + 1 +} + +#[cfg(test)] +mod tests { + fn env_var(name: &'static str) -> Option<usize> { + ::std::env::var(name).ok().map(|val| val.parse().unwrap()) + } + + #[test] + fn test_get() { + let num = super::get(); + if let Some(n) = env_var("NUM_CPUS_TEST_GET") { + assert_eq!(num, n); + } else { + assert!(num > 0); + assert!(num < 236_451); + } + } + + #[test] + fn test_get_physical() { + let num = super::get_physical(); + if let Some(n) = env_var("NUM_CPUS_TEST_GET_PHYSICAL") { + assert_eq!(num, n); + } else { + assert!(num > 0); + assert!(num < 236_451); + } + } +} diff --git a/third_party/rust/num_cpus/src/linux.rs b/third_party/rust/num_cpus/src/linux.rs new file mode 100644 index 0000000000..295c925fba --- /dev/null +++ b/third_party/rust/num_cpus/src/linux.rs @@ -0,0 +1,595 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufRead, BufReader, Read}; +use std::mem; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Once; + +use libc; + +macro_rules! debug { + ($($args:expr),*) => ({ + if false { + //if true { + println!($($args),*); + } + }); +} + +macro_rules! some { + ($e:expr) => {{ + match $e { + Some(v) => v, + None => { + debug!("NONE: {:?}", stringify!($e)); + return None; + } + } + }}; +} + +pub fn get_num_cpus() -> usize { + match cgroups_num_cpus() { + Some(n) => n, + None => logical_cpus(), + } +} + +fn logical_cpus() -> usize { + let mut set: libc::cpu_set_t = unsafe { mem::zeroed() }; + if unsafe { libc::sched_getaffinity(0, mem::size_of::<libc::cpu_set_t>(), &mut set) } == 0 { + let mut count: u32 = 0; + for i in 0..libc::CPU_SETSIZE as usize { + if unsafe { libc::CPU_ISSET(i, &set) } { + count += 1 + } + } + count as usize + } else { + let cpus = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) }; + if cpus < 1 { + 1 + } else { + cpus as usize + } + } +} + +pub fn get_num_physical_cpus() -> usize { + let file = match File::open("/proc/cpuinfo") { + Ok(val) => val, + Err(_) => return get_num_cpus(), + }; + let reader = BufReader::new(file); + let mut map = HashMap::new(); + let mut physid: u32 = 0; + let mut cores: usize = 0; + let mut chgcount = 0; + for line in reader.lines().filter_map(|result| result.ok()) { + let mut it = line.split(':'); + let (key, value) = match (it.next(), it.next()) { + (Some(key), Some(value)) => (key.trim(), value.trim()), + _ => continue, + }; + if key == "physical id" { + match value.parse() { + Ok(val) => physid = val, + Err(_) => break, + }; + chgcount += 1; + } + if key == "cpu cores" { + match value.parse() { + Ok(val) => cores = val, + Err(_) => break, + }; + chgcount += 1; + } + if chgcount == 2 { + map.insert(physid, cores); + chgcount = 0; + } + } + let count = map.into_iter().fold(0, |acc, (_, cores)| acc + cores); + + if count == 0 { + get_num_cpus() + } else { + count + } +} + +/// Cached CPUs calculated from cgroups. +/// +/// If 0, check logical cpus. +// Allow deprecation warnings, we want to work on older rustc +#[allow(warnings)] +static CGROUPS_CPUS: AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT; + +fn cgroups_num_cpus() -> Option<usize> { + #[allow(warnings)] + static ONCE: Once = ::std::sync::ONCE_INIT; + + ONCE.call_once(init_cgroups); + + let cpus = CGROUPS_CPUS.load(Ordering::Acquire); + + if cpus > 0 { + Some(cpus) + } else { + None + } +} + +fn init_cgroups() { + // Should only be called once + debug_assert!(CGROUPS_CPUS.load(Ordering::SeqCst) == 0); + + // Fails in Miri by default (cannot open files), and Miri does not have parallelism anyway. + if cfg!(miri) { + return; + } + + if let Some(quota) = load_cgroups("/proc/self/cgroup", "/proc/self/mountinfo") { + if quota == 0 { + return; + } + + let logical = logical_cpus(); + let count = ::std::cmp::min(quota, logical); + + CGROUPS_CPUS.store(count, Ordering::SeqCst); + } +} + +fn load_cgroups<P1, P2>(cgroup_proc: P1, mountinfo_proc: P2) -> Option<usize> +where + P1: AsRef<Path>, + P2: AsRef<Path>, +{ + let subsys = some!(Subsys::load_cpu(cgroup_proc)); + let mntinfo = some!(MountInfo::load_cpu(mountinfo_proc, subsys.version)); + let cgroup = some!(Cgroup::translate(mntinfo, subsys)); + cgroup.cpu_quota() +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CgroupVersion { + V1, + V2, +} + +struct Cgroup { + version: CgroupVersion, + base: PathBuf, +} + +struct MountInfo { + version: CgroupVersion, + root: String, + mount_point: String, +} + +struct Subsys { + version: CgroupVersion, + base: String, +} + +impl Cgroup { + fn new(version: CgroupVersion, dir: PathBuf) -> Cgroup { + Cgroup { version: version, base: dir } + } + + fn translate(mntinfo: MountInfo, subsys: Subsys) -> Option<Cgroup> { + // Translate the subsystem directory via the host paths. + debug!( + "subsys = {:?}; root = {:?}; mount_point = {:?}", + subsys.base, mntinfo.root, mntinfo.mount_point + ); + + let rel_from_root = some!(Path::new(&subsys.base).strip_prefix(&mntinfo.root).ok()); + + debug!("rel_from_root: {:?}", rel_from_root); + + // join(mp.MountPoint, relPath) + let mut path = PathBuf::from(mntinfo.mount_point); + path.push(rel_from_root); + Some(Cgroup::new(mntinfo.version, path)) + } + + fn cpu_quota(&self) -> Option<usize> { + let (quota_us, period_us) = match self.version { + CgroupVersion::V1 => (some!(self.quota_us()), some!(self.period_us())), + CgroupVersion::V2 => some!(self.max()), + }; + + // protect against dividing by zero + if period_us == 0 { + return None; + } + + // Ceil the division, since we want to be able to saturate + // the available CPUs, and flooring would leave a CPU un-utilized. + + Some((quota_us as f64 / period_us as f64).ceil() as usize) + } + + fn quota_us(&self) -> Option<usize> { + self.param("cpu.cfs_quota_us") + } + + fn period_us(&self) -> Option<usize> { + self.param("cpu.cfs_period_us") + } + + fn max(&self) -> Option<(usize, usize)> { + let max = some!(self.raw_param("cpu.max")); + let mut max = some!(max.lines().next()).split(' '); + + let quota = some!(max.next().and_then(|quota| quota.parse().ok())); + let period = some!(max.next().and_then(|period| period.parse().ok())); + + Some((quota, period)) + } + + fn param(&self, param: &str) -> Option<usize> { + let buf = some!(self.raw_param(param)); + + buf.trim().parse().ok() + } + + fn raw_param(&self, param: &str) -> Option<String> { + let mut file = some!(File::open(self.base.join(param)).ok()); + + let mut buf = String::new(); + some!(file.read_to_string(&mut buf).ok()); + + Some(buf) + } +} + +impl MountInfo { + fn load_cpu<P: AsRef<Path>>(proc_path: P, version: CgroupVersion) -> Option<MountInfo> { + let file = some!(File::open(proc_path).ok()); + let file = BufReader::new(file); + + file.lines() + .filter_map(|result| result.ok()) + .filter_map(MountInfo::parse_line) + .find(|mount_info| mount_info.version == version) + } + + fn parse_line(line: String) -> Option<MountInfo> { + let mut fields = line.split(' '); + + // 7 5 0:6 </> /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct + let mnt_root = some!(fields.nth(3)); + // 7 5 0:6 / </sys/fs/cgroup/cpu,cpuacct> rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct + let mnt_point = some!(fields.next()); + + // Ignore all fields until the separator(-). + // Note: there could be zero or more optional fields before hyphen. + // See: https://man7.org/linux/man-pages/man5/proc.5.html + // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 <-> cgroup cgroup rw,cpu,cpuacct + // Note: we cannot use `?` here because we need to support Rust 1.13. + match fields.find(|&s| s == "-") { + Some(_) => {} + None => return None, + }; + + // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - <cgroup> cgroup rw,cpu,cpuacct + let version = match fields.next() { + Some("cgroup") => CgroupVersion::V1, + Some("cgroup2") => CgroupVersion::V2, + _ => return None, + }; + + // cgroups2 only has a single mount point + if version == CgroupVersion::V1 { + // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup <rw,cpu,cpuacct> + let super_opts = some!(fields.nth(1)); + + // We only care about the 'cpu' option + if !super_opts.split(',').any(|opt| opt == "cpu") { + return None; + } + } + + Some(MountInfo { + version: version, + root: mnt_root.to_owned(), + mount_point: mnt_point.to_owned(), + }) + } +} + +impl Subsys { + fn load_cpu<P: AsRef<Path>>(proc_path: P) -> Option<Subsys> { + let file = some!(File::open(proc_path).ok()); + let file = BufReader::new(file); + + file.lines() + .filter_map(|result| result.ok()) + .filter_map(Subsys::parse_line) + .fold(None, |previous, line| { + // already-found v1 trumps v2 since it explicitly specifies its controllers + if previous.is_some() && line.version == CgroupVersion::V2 { + return previous; + } + + Some(line) + }) + } + + fn parse_line(line: String) -> Option<Subsys> { + // Example format: + // 11:cpu,cpuacct:/ + let mut fields = line.split(':'); + + let sub_systems = some!(fields.nth(1)); + + let version = if sub_systems.is_empty() { + CgroupVersion::V2 + } else { + CgroupVersion::V1 + }; + + if version == CgroupVersion::V1 && !sub_systems.split(',').any(|sub| sub == "cpu") { + return None; + } + + fields.next().map(|path| Subsys { + version: version, + base: path.to_owned(), + }) + } +} + +#[cfg(test)] +mod tests { + mod v1 { + use super::super::{Cgroup, CgroupVersion, MountInfo, Subsys}; + use std::path::{Path, PathBuf}; + + // `static_in_const` feature is not stable in Rust 1.13. + static FIXTURES_PROC: &'static str = "fixtures/cgroups/proc/cgroups"; + + static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups/cgroups"; + + macro_rules! join { + ($base:expr, $($path:expr),+) => ({ + Path::new($base) + $(.join($path))+ + }) + } + + #[test] + fn test_load_mountinfo() { + // test only one optional fields + let path = join!(FIXTURES_PROC, "mountinfo"); + + let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap(); + + assert_eq!(mnt_info.root, "/"); + assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct"); + + // test zero optional field + let path = join!(FIXTURES_PROC, "mountinfo_zero_opt"); + + let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap(); + + assert_eq!(mnt_info.root, "/"); + assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct"); + + // test multi optional fields + let path = join!(FIXTURES_PROC, "mountinfo_multi_opt"); + + let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap(); + + assert_eq!(mnt_info.root, "/"); + assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct"); + } + + #[test] + fn test_load_subsys() { + let path = join!(FIXTURES_PROC, "cgroup"); + + let subsys = Subsys::load_cpu(path).unwrap(); + + assert_eq!(subsys.base, "/"); + assert_eq!(subsys.version, CgroupVersion::V1); + } + + #[test] + fn test_cgroup_mount() { + let cases = &[ + ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd", + Some("/sys/fs/cgroup/cpu"), + ), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd/", + Some("/sys/fs/cgroup/cpu"), + ), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd/large", + Some("/sys/fs/cgroup/cpu/large"), + ), + // fails + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None), + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None), + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd-other-dir", + None, + ), + ]; + + for &(root, mount_point, subsys, expected) in cases.iter() { + let mnt_info = MountInfo { + version: CgroupVersion::V1, + root: root.into(), + mount_point: mount_point.into(), + }; + let subsys = Subsys { + version: CgroupVersion::V1, + base: subsys.into(), + }; + + let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base); + let expected = expected.map(PathBuf::from); + assert_eq!(actual, expected); + } + } + + #[test] + fn test_cgroup_cpu_quota() { + let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "good")); + assert_eq!(cgroup.cpu_quota(), Some(6)); + } + + #[test] + fn test_cgroup_cpu_quota_divide_by_zero() { + let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "zero-period")); + assert!(cgroup.quota_us().is_some()); + assert_eq!(cgroup.period_us(), Some(0)); + assert_eq!(cgroup.cpu_quota(), None); + } + + #[test] + fn test_cgroup_cpu_quota_ceil() { + let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "ceil")); + assert_eq!(cgroup.cpu_quota(), Some(2)); + } + } + + mod v2 { + use super::super::{Cgroup, CgroupVersion, MountInfo, Subsys}; + use std::path::{Path, PathBuf}; + + // `static_in_const` feature is not stable in Rust 1.13. + static FIXTURES_PROC: &'static str = "fixtures/cgroups2/proc/cgroups"; + + static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups2/cgroups"; + + macro_rules! join { + ($base:expr, $($path:expr),+) => ({ + Path::new($base) + $(.join($path))+ + }) + } + + #[test] + fn test_load_mountinfo() { + // test only one optional fields + let path = join!(FIXTURES_PROC, "mountinfo"); + + let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V2).unwrap(); + + assert_eq!(mnt_info.root, "/"); + assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup"); + } + + #[test] + fn test_load_subsys() { + let path = join!(FIXTURES_PROC, "cgroup"); + + let subsys = Subsys::load_cpu(path).unwrap(); + + assert_eq!(subsys.base, "/"); + assert_eq!(subsys.version, CgroupVersion::V2); + } + + #[test] + fn test_load_subsys_multi() { + let path = join!(FIXTURES_PROC, "cgroup_multi"); + + let subsys = Subsys::load_cpu(path).unwrap(); + + assert_eq!(subsys.base, "/"); + assert_eq!(subsys.version, CgroupVersion::V1); + } + + #[test] + fn test_cgroup_mount() { + let cases = &[ + ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd", + Some("/sys/fs/cgroup/cpu"), + ), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd/", + Some("/sys/fs/cgroup/cpu"), + ), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd/large", + Some("/sys/fs/cgroup/cpu/large"), + ), + // fails + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None), + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None), + ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None), + ( + "/docker/01abcd", + "/sys/fs/cgroup/cpu", + "/docker/01abcd-other-dir", + None, + ), + ]; + + for &(root, mount_point, subsys, expected) in cases.iter() { + let mnt_info = MountInfo { + version: CgroupVersion::V1, + root: root.into(), + mount_point: mount_point.into(), + }; + let subsys = Subsys { + version: CgroupVersion::V1, + base: subsys.into(), + }; + + let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base); + let expected = expected.map(PathBuf::from); + assert_eq!(actual, expected); + } + } + + #[test] + fn test_cgroup_cpu_quota() { + let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "good")); + assert_eq!(cgroup.cpu_quota(), Some(6)); + } + + #[test] + fn test_cgroup_cpu_quota_divide_by_zero() { + let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "zero-period")); + let period = cgroup.max().map(|max| max.1); + + assert_eq!(period, Some(0)); + assert_eq!(cgroup.cpu_quota(), None); + } + + #[test] + fn test_cgroup_cpu_quota_ceil() { + let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "ceil")); + assert_eq!(cgroup.cpu_quota(), Some(2)); + } + } +} |