├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── ci └── cgroups │ └── Dockerfile ├── examples └── values.rs ├── fixtures ├── cgroups │ ├── cgroups │ │ ├── ceil │ │ │ ├── cpu.cfs_period_us │ │ │ └── cpu.cfs_quota_us │ │ ├── good │ │ │ ├── cpu.cfs_period_us │ │ │ └── cpu.cfs_quota_us │ │ └── zero-period │ │ │ ├── cpu.cfs_period_us │ │ │ └── cpu.cfs_quota_us │ └── proc │ │ └── cgroups │ │ ├── cgroup │ │ ├── mountinfo │ │ ├── mountinfo_multi_opt │ │ └── mountinfo_zero_opt └── cgroups2 │ ├── cgroups │ ├── ceil │ │ └── cpu.max │ ├── good │ │ └── cpu.max │ └── zero-period │ │ └── cpu.max │ └── proc │ └── cgroups │ ├── cgroup │ ├── cgroup_multi │ └── mountinfo └── src ├── lib.rs └── linux.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: seanmonstar 2 | patreon: seanmonstar 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | RUST_BACKTRACE: 1 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | ci-pass: 14 | name: CI is green 15 | runs-on: ubuntu-latest 16 | needs: 17 | - msrv 18 | - test-linux 19 | - test-macos 20 | - test-windows 21 | - build-cross 22 | - test-cgroups 23 | steps: 24 | - run: exit 0 25 | 26 | msrv: 27 | runs-on: ubuntu-latest 28 | 29 | strategy: 30 | matrix: 31 | rust: 32 | - 1.13 33 | 34 | steps: 35 | - uses: actions/checkout@v3 36 | - name: Run tests 37 | run: | 38 | rustup default ${{ matrix.rust }} 39 | cargo update 40 | cargo update -p libc --precise 0.2.26 41 | cargo test --verbose 42 | 43 | test-linux: 44 | runs-on: ubuntu-latest 45 | 46 | strategy: 47 | matrix: 48 | rust: 49 | - stable 50 | - beta 51 | - nightly 52 | target: 53 | - x86_64-unknown-linux-gnu 54 | - x86_64-unknown-linux-musl 55 | 56 | steps: 57 | - uses: actions/checkout@v3 58 | - name: Run tests 59 | run: | 60 | rustup default ${{ matrix.rust }} 61 | rustup target add ${{ matrix.target }} 62 | cargo test --verbose --target ${{ matrix.target }} 63 | 64 | test-macos: 65 | runs-on: macos-latest 66 | 67 | strategy: 68 | matrix: 69 | rust: 70 | - stable 71 | - beta 72 | - nightly 73 | target: 74 | - x86_64-apple-darwin 75 | 76 | steps: 77 | - uses: actions/checkout@v3 78 | - name: Run tests 79 | run: | 80 | rustup default ${{ matrix.rust }} 81 | rustup target add ${{ matrix.target }} 82 | cargo test --verbose --target ${{ matrix.target }} 83 | 84 | test-windows: 85 | runs-on: windows-latest 86 | 87 | strategy: 88 | matrix: 89 | rust: 90 | - stable 91 | - beta 92 | - nightly 93 | target: 94 | - x86_64-pc-windows-gnu 95 | - x86_64-pc-windows-msvc 96 | 97 | steps: 98 | - uses: actions/checkout@v3 99 | - name: Run tests 100 | run: | 101 | rustup default ${{ matrix.rust }} 102 | rustup target add ${{ matrix.target }} 103 | cargo test --verbose --target ${{ matrix.target }} 104 | 105 | build-cross: 106 | runs-on: ubuntu-latest 107 | 108 | strategy: 109 | matrix: 110 | rust: 111 | - stable 112 | target: 113 | - aarch64-unknown-linux-gnu 114 | - i686-pc-windows-gnu 115 | - i686-pc-windows-msvc 116 | - i686-unknown-linux-gnu 117 | - aarch64-apple-darwin 118 | - aarch64-pc-windows-msvc 119 | - aarch64-unknown-linux-musl 120 | - arm-unknown-linux-gnueabi 121 | - arm-unknown-linux-gnueabihf 122 | - armv7-unknown-linux-gnueabihf 123 | - powerpc-unknown-linux-gnu 124 | - powerpc64-unknown-linux-gnu 125 | - powerpc64le-unknown-linux-gnu 126 | - riscv64gc-unknown-linux-gnu 127 | - s390x-unknown-linux-gnu 128 | - x86_64-unknown-freebsd 129 | - x86_64-unknown-illumos 130 | - x86_64-unknown-netbsd 131 | - i686-linux-android 132 | - x86_64-linux-android 133 | - arm-linux-androideabi 134 | - aarch64-linux-android 135 | - x86_64-apple-ios 136 | - aarch64-apple-ios 137 | - aarch64-apple-ios-sim 138 | - aarch64-apple-darwin 139 | steps: 140 | - uses: actions/checkout@v3 141 | - name: Run build 142 | run: | 143 | rustup default ${{ matrix.rust }} 144 | rustup target add ${{ matrix.target }} 145 | cargo build --verbose --target ${{ matrix.target }} 146 | 147 | test-cgroups: 148 | runs-on: ubuntu-latest 149 | 150 | steps: 151 | - uses: actions/checkout@v3 152 | - name: Test Cgroup 153 | run: | 154 | docker build -f ci/cgroups/Dockerfile -t num-cpus-cgroups . 155 | # Test without cgroups 156 | docker run -e NUM_CPUS_TEST_GET=4 num-cpus-cgroups 157 | # Only 1 CPU 158 | docker run --cpus="1" -e NUM_CPUS_TEST_GET=1 num-cpus-cgroups 159 | # 1.5 CPUs 160 | docker run --cpus="1.5" -e NUM_CPUS_TEST_GET=2 num-cpus-cgroups 161 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v1.17.0 2 | 3 | ### Fixes 4 | 5 | - update hermit-abi to 0.5.0 6 | - remove special support for nacl 7 | 8 | ## v1.16.0 9 | 10 | ### Features 11 | 12 | - add support for AIX operating system 13 | 14 | ### Fixes 15 | 16 | - update hermit-abi to 0.3.0 17 | 18 | ## v1.15.0 19 | 20 | ### Fixes 21 | 22 | - update hermit-abi 23 | 24 | ## v1.14.0 25 | 26 | ### Features 27 | 28 | - add support for cgroups v2 29 | - Skip reading files in Miri 30 | 31 | ## v1.13.1 32 | 33 | ### Fixes 34 | 35 | - fix parsing zero or multiple optional fields in cgroup mountinfo. 36 | 37 | ## v1.13.0 38 | 39 | ### Features 40 | 41 | - add Linux cgroups support when calling `get()`. 42 | 43 | ## v1.12.0 44 | 45 | #### Fixes 46 | 47 | - fix `get` on OpenBSD to ignore offline CPUs 48 | - implement `get_physical` on OpenBSD 49 | 50 | ## v1.11.1 51 | 52 | #### Fixes 53 | 54 | - Use `mem::zeroed` instead of `mem::uninitialized`. 55 | 56 | ## v1.11.0 57 | 58 | #### Features 59 | 60 | - add `hermit` target OS support 61 | - removes `bitrig` support 62 | 63 | #### Fixes 64 | 65 | - fix `get_physical` count with AMD hyperthreading. 66 | 67 | ## v1.10.1 68 | 69 | #### Fixes 70 | 71 | - improve `haiku` CPU detection 72 | 73 | ## v1.10.0 74 | 75 | #### Features 76 | 77 | - add `illumos` target OS support 78 | - add default fallback if target is unknown to `1` 79 | 80 | ## v1.9.0 81 | 82 | #### Features 83 | 84 | - add `sgx` target env support 85 | 86 | ## v1.8.0 87 | 88 | #### Features 89 | 90 | - add `wasm-unknown-unknown` target support 91 | 92 | ## v1.7.0 93 | 94 | #### Features 95 | 96 | - add `get_physical` support for macOS 97 | 98 | #### Fixes 99 | 100 | - use `_SC_NPROCESSORS_CONF` on Unix targets 101 | 102 | ### v1.6.2 103 | 104 | #### Fixes 105 | 106 | - revert 1.6.1 for now 107 | 108 | ### v1.6.1 109 | 110 | #### Fixes 111 | 112 | - fixes sometimes incorrect num on Android/ARM Linux (#45) 113 | 114 | ## v1.6.0 115 | 116 | #### Features 117 | 118 | - `get_physical` gains Windows support 119 | 120 | ### v1.5.1 121 | 122 | #### Fixes 123 | 124 | - fix `get` to return 1 if `sysconf(_SC_NPROCESSORS_ONLN)` failed 125 | 126 | ## v1.5.0 127 | 128 | #### Features 129 | 130 | - `get()` now checks `sched_affinity` on Linux 131 | 132 | ## v1.4.0 133 | 134 | #### Features 135 | 136 | - add `haiku` target support 137 | 138 | ## v1.3.0 139 | 140 | #### Features 141 | 142 | - add `redox` target support 143 | 144 | ### v1.2.1 145 | 146 | #### Fixes 147 | 148 | - fixes `get_physical` count (454ff1b) 149 | 150 | ## v1.2.0 151 | 152 | #### Features 153 | 154 | - add `emscripten` target support 155 | - add `fuchsia` target support 156 | 157 | ## v1.1.0 158 | 159 | #### Features 160 | 161 | - added `get_physical` function to return number of physical CPUs found 162 | 163 | # v1.0.0 164 | 165 | #### Features 166 | 167 | - `get` function returns number of CPUs (physical and virtual) of current platform 168 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## License 4 | 5 | Licensed under either of 6 | 7 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 8 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 9 | 10 | at your option. 11 | 12 | ### Contribution 13 | 14 | Unless you explicitly state otherwise, any contribution intentionally submitted 15 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 16 | additional terms or conditions. 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "num_cpus" 4 | version = "1.17.0" 5 | description = "Get the number of CPUs on a machine." 6 | authors = ["Sean McArthur "] 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/seanmonstar/num_cpus" 9 | documentation = "https://docs.rs/num_cpus" 10 | keywords = ["cpu", "cpus", "cores"] 11 | categories = ["hardware-support"] 12 | readme = "README.md" 13 | 14 | [target.'cfg(not(windows))'.dependencies] 15 | libc = "0.2.26" 16 | 17 | [target.'cfg(target_os = "hermit")'.dependencies] 18 | hermit-abi = "0.5.0" 19 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2025 Sean McArthur 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # num_cpus 2 | 3 | [![crates.io](https://img.shields.io/crates/v/num_cpus.svg)](https://crates.io/crates/num_cpus) 4 | [![CI Status](https://github.com/seanmonstar/num_cpus/actions/workflows/ci.yml/badge.svg)](https://github.com/seanmonstar/num_cpus/actions) 5 | 6 | - [Documentation](https://docs.rs/num_cpus) 7 | - [CHANGELOG](CHANGELOG.md) 8 | 9 | Count the number of CPUs on the current machine. 10 | 11 | ## Usage 12 | 13 | Add to Cargo.toml: 14 | 15 | ```toml 16 | [dependencies] 17 | num_cpus = "1.0" 18 | ``` 19 | 20 | In your `main.rs` or `lib.rs`: 21 | 22 | ```rust 23 | extern crate num_cpus; 24 | 25 | // count logical cores this process could try to use 26 | let num = num_cpus::get(); 27 | ``` 28 | -------------------------------------------------------------------------------- /ci/cgroups/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:latest 2 | 3 | WORKDIR /usr/num_cpus 4 | 5 | COPY . . 6 | 7 | RUN cargo build 8 | 9 | CMD [ "cargo", "test", "--lib" ] 10 | -------------------------------------------------------------------------------- /examples/values.rs: -------------------------------------------------------------------------------- 1 | extern crate num_cpus; 2 | 3 | fn main() { 4 | println!("Logical CPUs: {}", num_cpus::get()); 5 | println!("Physical CPUs: {}", num_cpus::get_physical()); 6 | } 7 | -------------------------------------------------------------------------------- /fixtures/cgroups/cgroups/ceil/cpu.cfs_period_us: -------------------------------------------------------------------------------- 1 | 100000 2 | 3 | -------------------------------------------------------------------------------- /fixtures/cgroups/cgroups/ceil/cpu.cfs_quota_us: -------------------------------------------------------------------------------- 1 | 150000 2 | 3 | -------------------------------------------------------------------------------- /fixtures/cgroups/cgroups/good/cpu.cfs_period_us: -------------------------------------------------------------------------------- 1 | 100000 2 | 3 | -------------------------------------------------------------------------------- /fixtures/cgroups/cgroups/good/cpu.cfs_quota_us: -------------------------------------------------------------------------------- 1 | 600000 2 | 3 | -------------------------------------------------------------------------------- /fixtures/cgroups/cgroups/zero-period/cpu.cfs_period_us: -------------------------------------------------------------------------------- 1 | 0 2 | 3 | -------------------------------------------------------------------------------- /fixtures/cgroups/cgroups/zero-period/cpu.cfs_quota_us: -------------------------------------------------------------------------------- 1 | 600000 2 | -------------------------------------------------------------------------------- /fixtures/cgroups/proc/cgroups/cgroup: -------------------------------------------------------------------------------- 1 | 12:perf_event:/ 2 | 11:cpu,cpuacct:/ 3 | 3:devices:/user.slice 4 | -------------------------------------------------------------------------------- /fixtures/cgroups/proc/cgroups/mountinfo: -------------------------------------------------------------------------------- 1 | 1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered 2 | 2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755 3 | 3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw 4 | 4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw 5 | 5 4 0:4 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:5 - tmpfs tmpfs ro,mode=755 6 | 6 5 0:5 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:6 - cgroup cgroup rw,cpuset 7 | 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct 8 | 8 5 0:7 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:8 - cgroup cgroup rw,memory 9 | -------------------------------------------------------------------------------- /fixtures/cgroups/proc/cgroups/mountinfo_multi_opt: -------------------------------------------------------------------------------- 1 | 1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered 2 | 2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755 3 | 3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw 4 | 4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw 5 | 5 4 0:4 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:5 - tmpfs tmpfs ro,mode=755 6 | 6 5 0:5 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:6 - cgroup cgroup rw,cpuset 7 | 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 | 8 5 0:7 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:8 - cgroup cgroup rw,memory 9 | -------------------------------------------------------------------------------- /fixtures/cgroups/proc/cgroups/mountinfo_zero_opt: -------------------------------------------------------------------------------- 1 | 1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered 2 | 2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755 3 | 3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw 4 | 4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw 5 | 5 4 0:4 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:5 - tmpfs tmpfs ro,mode=755 6 | 6 5 0:5 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:6 - cgroup cgroup rw,cpuset 7 | 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu,cpuacct 8 | 8 5 0:7 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:8 - cgroup cgroup rw,memory 9 | -------------------------------------------------------------------------------- /fixtures/cgroups2/cgroups/ceil/cpu.max: -------------------------------------------------------------------------------- 1 | 150000 100000 2 | -------------------------------------------------------------------------------- /fixtures/cgroups2/cgroups/good/cpu.max: -------------------------------------------------------------------------------- 1 | 600000 100000 2 | -------------------------------------------------------------------------------- /fixtures/cgroups2/cgroups/zero-period/cpu.max: -------------------------------------------------------------------------------- 1 | 600000 0 2 | -------------------------------------------------------------------------------- /fixtures/cgroups2/proc/cgroups/cgroup: -------------------------------------------------------------------------------- 1 | 12::/ 2 | 3::/user.slice 3 | -------------------------------------------------------------------------------- /fixtures/cgroups2/proc/cgroups/cgroup_multi: -------------------------------------------------------------------------------- 1 | 12::/ 2 | 11:cpu,cpuacct:/ 3 | 3::/user.slice 4 | -------------------------------------------------------------------------------- /fixtures/cgroups2/proc/cgroups/mountinfo: -------------------------------------------------------------------------------- 1 | 1 0 8:1 / / rw,noatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro,data=reordered 2 | 2 1 0:1 / /dev rw,relatime shared:2 - devtmpfs udev rw,size=10240k,nr_inodes=16487629,mode=755 3 | 3 1 0:2 / /proc rw,nosuid,nodev,noexec,relatime shared:3 - proc proc rw 4 | 4 1 0:3 / /sys rw,nosuid,nodev,noexec,relatime shared:4 - sysfs sysfs rw 5 | 5 4 0:4 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:5 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A crate with utilities to determine the number of CPUs available on the 2 | //! current system. 3 | //! 4 | //! Sometimes the CPU will exaggerate the number of CPUs it contains, because it can use 5 | //! [processor tricks] to deliver increased performance when there are more threads. This 6 | //! crate provides methods to get both the logical and physical numbers of cores. 7 | //! 8 | //! This information can be used as a guide to how many tasks can be run in parallel. 9 | //! There are many properties of the system architecture that will affect parallelism, 10 | //! for example memory access speeds (for all the caches and RAM) and the physical 11 | //! architecture of the processor, so the number of CPUs should be used as a rough guide 12 | //! only. 13 | //! 14 | //! 15 | //! ## Examples 16 | //! 17 | //! Fetch the number of logical CPUs. 18 | //! 19 | //! ``` 20 | //! let cpus = num_cpus::get(); 21 | //! ``` 22 | //! 23 | //! See [`rayon::Threadpool`] for an example of where the number of CPUs could be 24 | //! used when setting up parallel jobs (Where the threadpool example uses a fixed 25 | //! number 8, it could use the number of CPUs). 26 | //! 27 | //! [processor tricks]: https://en.wikipedia.org/wiki/Simultaneous_multithreading 28 | //! [`rayon::ThreadPool`]: https://docs.rs/rayon/1.*/rayon/struct.ThreadPool.html 29 | #![cfg_attr(test, deny(warnings))] 30 | #![deny(missing_docs)] 31 | #![allow(non_snake_case)] 32 | 33 | #[cfg(not(windows))] 34 | extern crate libc; 35 | 36 | #[cfg(target_os = "hermit")] 37 | extern crate hermit_abi; 38 | 39 | #[cfg(target_os = "linux")] 40 | mod linux; 41 | #[cfg(target_os = "linux")] 42 | use linux::{get_num_cpus, get_num_physical_cpus}; 43 | 44 | /// Returns the number of available CPUs of the current system. 45 | /// 46 | /// This function will get the number of logical cores. Sometimes this is different from the number 47 | /// of physical cores (See [Simultaneous multithreading on Wikipedia][smt]). 48 | /// 49 | /// This will always return at least `1`. 50 | /// 51 | /// # Examples 52 | /// 53 | /// ``` 54 | /// let cpus = num_cpus::get(); 55 | /// if cpus > 1 { 56 | /// println!("We are on a multicore system with {} CPUs", cpus); 57 | /// } else { 58 | /// println!("We are on a single core system"); 59 | /// } 60 | /// ``` 61 | /// 62 | /// # Note 63 | /// 64 | /// This will check [sched affinity] on Linux, showing a lower number of CPUs if the current 65 | /// thread does not have access to all the computer's CPUs. 66 | /// 67 | /// This will also check [cgroups], frequently used in containers to constrain CPU usage. 68 | /// 69 | /// [smt]: https://en.wikipedia.org/wiki/Simultaneous_multithreading 70 | /// [sched affinity]: http://www.gnu.org/software/libc/manual/html_node/CPU-Affinity.html 71 | /// [cgroups]: https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt 72 | #[inline] 73 | pub fn get() -> usize { 74 | get_num_cpus() 75 | } 76 | 77 | /// Returns the number of physical cores of the current system. 78 | /// 79 | /// This will always return at least `1`. 80 | /// 81 | /// # Note 82 | /// 83 | /// Physical count is supported only on Linux, mac OS and Windows platforms. 84 | /// On other platforms, or if the physical count fails on supported platforms, 85 | /// this function returns the same as [`get()`], which is the number of logical 86 | /// CPUS. 87 | /// 88 | /// # Examples 89 | /// 90 | /// ``` 91 | /// let logical_cpus = num_cpus::get(); 92 | /// let physical_cpus = num_cpus::get_physical(); 93 | /// if logical_cpus > physical_cpus { 94 | /// println!("We have simultaneous multithreading with about {:.2} \ 95 | /// logical cores to 1 physical core.", 96 | /// (logical_cpus as f64) / (physical_cpus as f64)); 97 | /// } else if logical_cpus == physical_cpus { 98 | /// println!("Either we don't have simultaneous multithreading, or our \ 99 | /// system doesn't support getting the number of physical CPUs."); 100 | /// } else { 101 | /// println!("We have less logical CPUs than physical CPUs, maybe we only have access to \ 102 | /// some of the CPUs on our system."); 103 | /// } 104 | /// ``` 105 | /// 106 | /// [`get()`]: fn.get.html 107 | #[inline] 108 | pub fn get_physical() -> usize { 109 | get_num_physical_cpus() 110 | } 111 | 112 | 113 | #[cfg(not(any( 114 | target_os = "linux", 115 | target_os = "windows", 116 | target_os = "macos", 117 | target_os = "openbsd", 118 | target_os = "aix")))] 119 | #[inline] 120 | fn get_num_physical_cpus() -> usize { 121 | // Not implemented, fall back 122 | get_num_cpus() 123 | } 124 | 125 | #[cfg(target_os = "windows")] 126 | fn get_num_physical_cpus() -> usize { 127 | match get_num_physical_cpus_windows() { 128 | Some(num) => num, 129 | None => get_num_cpus() 130 | } 131 | } 132 | 133 | #[cfg(target_os = "windows")] 134 | fn get_num_physical_cpus_windows() -> Option { 135 | // Inspired by https://msdn.microsoft.com/en-us/library/ms683194 136 | 137 | use std::ptr; 138 | use std::mem; 139 | 140 | #[allow(non_upper_case_globals)] 141 | const RelationProcessorCore: u32 = 0; 142 | 143 | #[repr(C)] 144 | #[allow(non_camel_case_types)] 145 | struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION { 146 | mask: usize, 147 | relationship: u32, 148 | _unused: [u64; 2] 149 | } 150 | 151 | extern "system" { 152 | fn GetLogicalProcessorInformation( 153 | info: *mut SYSTEM_LOGICAL_PROCESSOR_INFORMATION, 154 | length: &mut u32 155 | ) -> u32; 156 | } 157 | 158 | // First we need to determine how much space to reserve. 159 | 160 | // The required size of the buffer, in bytes. 161 | let mut needed_size = 0; 162 | 163 | unsafe { 164 | GetLogicalProcessorInformation(ptr::null_mut(), &mut needed_size); 165 | } 166 | 167 | let struct_size = mem::size_of::() as u32; 168 | 169 | // Could be 0, or some other bogus size. 170 | if needed_size == 0 || needed_size < struct_size || needed_size % struct_size != 0 { 171 | return None; 172 | } 173 | 174 | let count = needed_size / struct_size; 175 | 176 | // Allocate some memory where we will store the processor info. 177 | let mut buf = Vec::with_capacity(count as usize); 178 | 179 | let result; 180 | 181 | unsafe { 182 | result = GetLogicalProcessorInformation(buf.as_mut_ptr(), &mut needed_size); 183 | } 184 | 185 | // Failed for any reason. 186 | if result == 0 { 187 | return None; 188 | } 189 | 190 | let count = needed_size / struct_size; 191 | 192 | unsafe { 193 | buf.set_len(count as usize); 194 | } 195 | 196 | let phys_proc_count = buf.iter() 197 | // Only interested in processor packages (physical processors.) 198 | .filter(|proc_info| proc_info.relationship == RelationProcessorCore) 199 | .count(); 200 | 201 | if phys_proc_count == 0 { 202 | None 203 | } else { 204 | Some(phys_proc_count) 205 | } 206 | } 207 | 208 | #[cfg(windows)] 209 | fn get_num_cpus() -> usize { 210 | #[repr(C)] 211 | struct SYSTEM_INFO { 212 | wProcessorArchitecture: u16, 213 | wReserved: u16, 214 | dwPageSize: u32, 215 | lpMinimumApplicationAddress: *mut u8, 216 | lpMaximumApplicationAddress: *mut u8, 217 | dwActiveProcessorMask: *mut u8, 218 | dwNumberOfProcessors: u32, 219 | dwProcessorType: u32, 220 | dwAllocationGranularity: u32, 221 | wProcessorLevel: u16, 222 | wProcessorRevision: u16, 223 | } 224 | 225 | extern "system" { 226 | fn GetSystemInfo(lpSystemInfo: *mut SYSTEM_INFO); 227 | } 228 | 229 | unsafe { 230 | let mut sysinfo: SYSTEM_INFO = std::mem::zeroed(); 231 | GetSystemInfo(&mut sysinfo); 232 | sysinfo.dwNumberOfProcessors as usize 233 | } 234 | } 235 | 236 | #[cfg(any(target_os = "freebsd", 237 | target_os = "dragonfly", 238 | target_os = "netbsd"))] 239 | fn get_num_cpus() -> usize { 240 | use std::ptr; 241 | 242 | let mut cpus: libc::c_uint = 0; 243 | let mut cpus_size = std::mem::size_of_val(&cpus); 244 | 245 | unsafe { 246 | cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint; 247 | } 248 | if cpus < 1 { 249 | let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; 250 | unsafe { 251 | libc::sysctl(mib.as_mut_ptr(), 252 | 2, 253 | &mut cpus as *mut _ as *mut _, 254 | &mut cpus_size as *mut _ as *mut _, 255 | ptr::null_mut(), 256 | 0); 257 | } 258 | if cpus < 1 { 259 | cpus = 1; 260 | } 261 | } 262 | cpus as usize 263 | } 264 | 265 | #[cfg(target_os = "openbsd")] 266 | fn get_num_cpus() -> usize { 267 | use std::ptr; 268 | 269 | let mut cpus: libc::c_uint = 0; 270 | let mut cpus_size = std::mem::size_of_val(&cpus); 271 | let mut mib = [libc::CTL_HW, libc::HW_NCPUONLINE, 0, 0]; 272 | let rc: libc::c_int; 273 | 274 | unsafe { 275 | rc = libc::sysctl(mib.as_mut_ptr(), 276 | 2, 277 | &mut cpus as *mut _ as *mut _, 278 | &mut cpus_size as *mut _ as *mut _, 279 | ptr::null_mut(), 280 | 0); 281 | } 282 | if rc < 0 { 283 | cpus = 1; 284 | } 285 | cpus as usize 286 | } 287 | 288 | #[cfg(target_os = "openbsd")] 289 | fn get_num_physical_cpus() -> usize { 290 | use std::ptr; 291 | 292 | let mut cpus: libc::c_uint = 0; 293 | let mut cpus_size = std::mem::size_of_val(&cpus); 294 | let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; 295 | let rc: libc::c_int; 296 | 297 | unsafe { 298 | rc = libc::sysctl(mib.as_mut_ptr(), 299 | 2, 300 | &mut cpus as *mut _ as *mut _, 301 | &mut cpus_size as *mut _ as *mut _, 302 | ptr::null_mut(), 303 | 0); 304 | } 305 | if rc < 0 { 306 | cpus = 1; 307 | } 308 | cpus as usize 309 | } 310 | 311 | 312 | #[cfg(target_os = "macos")] 313 | fn get_num_physical_cpus() -> usize { 314 | use std::ffi::CStr; 315 | use std::ptr; 316 | 317 | let mut cpus: i32 = 0; 318 | let mut cpus_size = std::mem::size_of_val(&cpus); 319 | 320 | let sysctl_name = CStr::from_bytes_with_nul(b"hw.physicalcpu\0") 321 | .expect("byte literal is missing NUL"); 322 | 323 | unsafe { 324 | if 0 != libc::sysctlbyname(sysctl_name.as_ptr(), 325 | &mut cpus as *mut _ as *mut _, 326 | &mut cpus_size as *mut _ as *mut _, 327 | ptr::null_mut(), 328 | 0) { 329 | return get_num_cpus(); 330 | } 331 | } 332 | cpus as usize 333 | } 334 | 335 | #[cfg(target_os = "aix")] 336 | fn get_num_physical_cpus() -> usize { 337 | match get_smt_threads_aix() { 338 | Some(num) => get_num_cpus() / num, 339 | None => get_num_cpus(), 340 | } 341 | } 342 | 343 | #[cfg(target_os = "aix")] 344 | fn get_smt_threads_aix() -> Option { 345 | let smt = unsafe { 346 | libc::getsystemcfg(libc::SC_SMT_TC) 347 | }; 348 | if smt == u64::MAX { 349 | return None; 350 | } 351 | Some(smt as usize) 352 | } 353 | 354 | #[cfg(any( 355 | target_os = "macos", 356 | target_os = "ios", 357 | target_os = "android", 358 | target_os = "aix", 359 | target_os = "solaris", 360 | target_os = "illumos", 361 | target_os = "fuchsia") 362 | )] 363 | fn get_num_cpus() -> usize { 364 | // On ARM targets, processors could be turned off to save power. 365 | // Use `_SC_NPROCESSORS_CONF` to get the real number. 366 | #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] 367 | const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_CONF; 368 | #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] 369 | const CONF_NAME: libc::c_int = libc::_SC_NPROCESSORS_ONLN; 370 | 371 | let cpus = unsafe { libc::sysconf(CONF_NAME) }; 372 | if cpus < 1 { 373 | 1 374 | } else { 375 | cpus as usize 376 | } 377 | } 378 | 379 | #[cfg(target_os = "haiku")] 380 | fn get_num_cpus() -> usize { 381 | use std::mem; 382 | 383 | #[allow(non_camel_case_types)] 384 | type bigtime_t = i64; 385 | #[allow(non_camel_case_types)] 386 | type status_t = i32; 387 | 388 | #[repr(C)] 389 | pub struct system_info { 390 | pub boot_time: bigtime_t, 391 | pub cpu_count: u32, 392 | pub max_pages: u64, 393 | pub used_pages: u64, 394 | pub cached_pages: u64, 395 | pub block_cache_pages: u64, 396 | pub ignored_pages: u64, 397 | pub needed_memory: u64, 398 | pub free_memory: u64, 399 | pub max_swap_pages: u64, 400 | pub free_swap_pages: u64, 401 | pub page_faults: u32, 402 | pub max_sems: u32, 403 | pub used_sems: u32, 404 | pub max_ports: u32, 405 | pub used_ports: u32, 406 | pub max_threads: u32, 407 | pub used_threads: u32, 408 | pub max_teams: u32, 409 | pub used_teams: u32, 410 | pub kernel_name: [::std::os::raw::c_char; 256usize], 411 | pub kernel_build_date: [::std::os::raw::c_char; 32usize], 412 | pub kernel_build_time: [::std::os::raw::c_char; 32usize], 413 | pub kernel_version: i64, 414 | pub abi: u32, 415 | } 416 | 417 | extern { 418 | fn get_system_info(info: *mut system_info) -> status_t; 419 | } 420 | 421 | let mut info: system_info = unsafe { mem::zeroed() }; 422 | let status = unsafe { get_system_info(&mut info as *mut _) }; 423 | if status == 0 { 424 | info.cpu_count as usize 425 | } else { 426 | 1 427 | } 428 | } 429 | 430 | #[cfg(target_os = "hermit")] 431 | fn get_num_cpus() -> usize { 432 | unsafe { hermit_abi::get_processor_count() } 433 | } 434 | 435 | #[cfg(not(any( 436 | target_os = "macos", 437 | target_os = "ios", 438 | target_os = "android", 439 | target_os = "aix", 440 | target_os = "solaris", 441 | target_os = "illumos", 442 | target_os = "fuchsia", 443 | target_os = "linux", 444 | target_os = "openbsd", 445 | target_os = "freebsd", 446 | target_os = "dragonfly", 447 | target_os = "netbsd", 448 | target_os = "haiku", 449 | target_os = "hermit", 450 | windows, 451 | )))] 452 | fn get_num_cpus() -> usize { 453 | 1 454 | } 455 | 456 | #[cfg(test)] 457 | mod tests { 458 | fn env_var(name: &'static str) -> Option { 459 | ::std::env::var(name).ok().map(|val| val.parse().unwrap()) 460 | } 461 | 462 | #[test] 463 | fn test_get() { 464 | let num = super::get(); 465 | if let Some(n) = env_var("NUM_CPUS_TEST_GET") { 466 | assert_eq!(num, n); 467 | } else { 468 | assert!(num > 0); 469 | assert!(num < 236_451); 470 | } 471 | } 472 | 473 | #[test] 474 | fn test_get_physical() { 475 | let num = super::get_physical(); 476 | if let Some(n) = env_var("NUM_CPUS_TEST_GET_PHYSICAL") { 477 | assert_eq!(num, n); 478 | } else { 479 | assert!(num > 0); 480 | assert!(num < 236_451); 481 | } 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /src/linux.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fs::File; 3 | use std::io::{BufRead, BufReader, Read}; 4 | use std::mem; 5 | use std::path::{Path, PathBuf}; 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | use std::sync::Once; 8 | 9 | use libc; 10 | 11 | macro_rules! debug { 12 | ($($args:expr),*) => ({ 13 | if false { 14 | //if true { 15 | println!($($args),*); 16 | } 17 | }); 18 | } 19 | 20 | macro_rules! some { 21 | ($e:expr) => {{ 22 | match $e { 23 | Some(v) => v, 24 | None => { 25 | debug!("NONE: {:?}", stringify!($e)); 26 | return None; 27 | } 28 | } 29 | }}; 30 | } 31 | 32 | pub fn get_num_cpus() -> usize { 33 | match cgroups_num_cpus() { 34 | Some(n) => n, 35 | None => logical_cpus(), 36 | } 37 | } 38 | 39 | fn logical_cpus() -> usize { 40 | let mut set: libc::cpu_set_t = unsafe { mem::zeroed() }; 41 | if unsafe { libc::sched_getaffinity(0, mem::size_of::(), &mut set) } == 0 { 42 | let mut count: u32 = 0; 43 | for i in 0..libc::CPU_SETSIZE as usize { 44 | if unsafe { libc::CPU_ISSET(i, &set) } { 45 | count += 1 46 | } 47 | } 48 | count as usize 49 | } else { 50 | let cpus = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) }; 51 | if cpus < 1 { 52 | 1 53 | } else { 54 | cpus as usize 55 | } 56 | } 57 | } 58 | 59 | pub fn get_num_physical_cpus() -> usize { 60 | let file = match File::open("/proc/cpuinfo") { 61 | Ok(val) => val, 62 | Err(_) => return get_num_cpus(), 63 | }; 64 | let reader = BufReader::new(file); 65 | let mut map = HashMap::new(); 66 | let mut physid: u32 = 0; 67 | let mut cores: usize = 0; 68 | let mut chgcount = 0; 69 | for line in reader.lines().filter_map(|result| result.ok()) { 70 | let mut it = line.split(':'); 71 | let (key, value) = match (it.next(), it.next()) { 72 | (Some(key), Some(value)) => (key.trim(), value.trim()), 73 | _ => continue, 74 | }; 75 | if key == "physical id" { 76 | match value.parse() { 77 | Ok(val) => physid = val, 78 | Err(_) => break, 79 | }; 80 | chgcount += 1; 81 | } 82 | if key == "cpu cores" { 83 | match value.parse() { 84 | Ok(val) => cores = val, 85 | Err(_) => break, 86 | }; 87 | chgcount += 1; 88 | } 89 | if chgcount == 2 { 90 | map.insert(physid, cores); 91 | chgcount = 0; 92 | } 93 | } 94 | let count = map.into_iter().fold(0, |acc, (_, cores)| acc + cores); 95 | 96 | if count == 0 { 97 | get_num_cpus() 98 | } else { 99 | count 100 | } 101 | } 102 | 103 | /// Cached CPUs calculated from cgroups. 104 | /// 105 | /// If 0, check logical cpus. 106 | // Allow deprecation warnings, we want to work on older rustc 107 | #[allow(warnings)] 108 | static CGROUPS_CPUS: AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT; 109 | 110 | fn cgroups_num_cpus() -> Option { 111 | #[allow(warnings)] 112 | static ONCE: Once = ::std::sync::ONCE_INIT; 113 | 114 | ONCE.call_once(init_cgroups); 115 | 116 | let cpus = CGROUPS_CPUS.load(Ordering::Acquire); 117 | 118 | if cpus > 0 { 119 | Some(cpus) 120 | } else { 121 | None 122 | } 123 | } 124 | 125 | fn init_cgroups() { 126 | // Should only be called once 127 | debug_assert!(CGROUPS_CPUS.load(Ordering::SeqCst) == 0); 128 | 129 | // Fails in Miri by default (cannot open files), and Miri does not have parallelism anyway. 130 | if cfg!(miri) { 131 | return; 132 | } 133 | 134 | if let Some(quota) = load_cgroups("/proc/self/cgroup", "/proc/self/mountinfo") { 135 | if quota == 0 { 136 | return; 137 | } 138 | 139 | let logical = logical_cpus(); 140 | let count = ::std::cmp::min(quota, logical); 141 | 142 | CGROUPS_CPUS.store(count, Ordering::SeqCst); 143 | } 144 | } 145 | 146 | fn load_cgroups(cgroup_proc: P1, mountinfo_proc: P2) -> Option 147 | where 148 | P1: AsRef, 149 | P2: AsRef, 150 | { 151 | let subsys = some!(Subsys::load_cpu(cgroup_proc)); 152 | let mntinfo = some!(MountInfo::load_cpu(mountinfo_proc, subsys.version)); 153 | let cgroup = some!(Cgroup::translate(mntinfo, subsys)); 154 | cgroup.cpu_quota() 155 | } 156 | 157 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 158 | enum CgroupVersion { 159 | V1, 160 | V2, 161 | } 162 | 163 | struct Cgroup { 164 | version: CgroupVersion, 165 | base: PathBuf, 166 | } 167 | 168 | struct MountInfo { 169 | version: CgroupVersion, 170 | root: String, 171 | mount_point: String, 172 | } 173 | 174 | struct Subsys { 175 | version: CgroupVersion, 176 | base: String, 177 | } 178 | 179 | impl Cgroup { 180 | fn new(version: CgroupVersion, dir: PathBuf) -> Cgroup { 181 | Cgroup { version: version, base: dir } 182 | } 183 | 184 | fn translate(mntinfo: MountInfo, subsys: Subsys) -> Option { 185 | // Translate the subsystem directory via the host paths. 186 | debug!( 187 | "subsys = {:?}; root = {:?}; mount_point = {:?}", 188 | subsys.base, mntinfo.root, mntinfo.mount_point 189 | ); 190 | 191 | let rel_from_root = some!(Path::new(&subsys.base).strip_prefix(&mntinfo.root).ok()); 192 | 193 | debug!("rel_from_root: {:?}", rel_from_root); 194 | 195 | // join(mp.MountPoint, relPath) 196 | let mut path = PathBuf::from(mntinfo.mount_point); 197 | path.push(rel_from_root); 198 | Some(Cgroup::new(mntinfo.version, path)) 199 | } 200 | 201 | fn cpu_quota(&self) -> Option { 202 | let (quota_us, period_us) = match self.version { 203 | CgroupVersion::V1 => (some!(self.quota_us()), some!(self.period_us())), 204 | CgroupVersion::V2 => some!(self.max()), 205 | }; 206 | 207 | // protect against dividing by zero 208 | if period_us == 0 { 209 | return None; 210 | } 211 | 212 | // Ceil the division, since we want to be able to saturate 213 | // the available CPUs, and flooring would leave a CPU un-utilized. 214 | 215 | Some((quota_us as f64 / period_us as f64).ceil() as usize) 216 | } 217 | 218 | fn quota_us(&self) -> Option { 219 | self.param("cpu.cfs_quota_us") 220 | } 221 | 222 | fn period_us(&self) -> Option { 223 | self.param("cpu.cfs_period_us") 224 | } 225 | 226 | fn max(&self) -> Option<(usize, usize)> { 227 | let max = some!(self.raw_param("cpu.max")); 228 | let mut max = some!(max.lines().next()).split(' '); 229 | 230 | let quota = some!(max.next().and_then(|quota| quota.parse().ok())); 231 | let period = some!(max.next().and_then(|period| period.parse().ok())); 232 | 233 | Some((quota, period)) 234 | } 235 | 236 | fn param(&self, param: &str) -> Option { 237 | let buf = some!(self.raw_param(param)); 238 | 239 | buf.trim().parse().ok() 240 | } 241 | 242 | fn raw_param(&self, param: &str) -> Option { 243 | let mut file = some!(File::open(self.base.join(param)).ok()); 244 | 245 | let mut buf = String::new(); 246 | some!(file.read_to_string(&mut buf).ok()); 247 | 248 | Some(buf) 249 | } 250 | } 251 | 252 | impl MountInfo { 253 | fn load_cpu>(proc_path: P, version: CgroupVersion) -> Option { 254 | let file = some!(File::open(proc_path).ok()); 255 | let file = BufReader::new(file); 256 | 257 | file.lines() 258 | .filter_map(|result| result.ok()) 259 | .filter_map(MountInfo::parse_line) 260 | .find(|mount_info| mount_info.version == version) 261 | } 262 | 263 | fn parse_line(line: String) -> Option { 264 | let mut fields = line.split(' '); 265 | 266 | // 7 5 0:6 /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct 267 | let mnt_root = some!(fields.nth(3)); 268 | // 7 5 0:6 / rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup rw,cpu,cpuacct 269 | let mnt_point = some!(fields.next()); 270 | 271 | // Ignore all fields until the separator(-). 272 | // Note: there could be zero or more optional fields before hyphen. 273 | // See: https://man7.org/linux/man-pages/man5/proc.5.html 274 | // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 <-> cgroup cgroup rw,cpu,cpuacct 275 | // Note: we cannot use `?` here because we need to support Rust 1.13. 276 | match fields.find(|&s| s == "-") { 277 | Some(_) => {} 278 | None => return None, 279 | }; 280 | 281 | // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup rw,cpu,cpuacct 282 | let version = match fields.next() { 283 | Some("cgroup") => CgroupVersion::V1, 284 | Some("cgroup2") => CgroupVersion::V2, 285 | _ => return None, 286 | }; 287 | 288 | // cgroups2 only has a single mount point 289 | if version == CgroupVersion::V1 { 290 | // 7 5 0:6 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:7 - cgroup cgroup 291 | let super_opts = some!(fields.nth(1)); 292 | 293 | // We only care about the 'cpu' option 294 | if !super_opts.split(',').any(|opt| opt == "cpu") { 295 | return None; 296 | } 297 | } 298 | 299 | Some(MountInfo { 300 | version: version, 301 | root: mnt_root.to_owned(), 302 | mount_point: mnt_point.to_owned(), 303 | }) 304 | } 305 | } 306 | 307 | impl Subsys { 308 | fn load_cpu>(proc_path: P) -> Option { 309 | let file = some!(File::open(proc_path).ok()); 310 | let file = BufReader::new(file); 311 | 312 | file.lines() 313 | .filter_map(|result| result.ok()) 314 | .filter_map(Subsys::parse_line) 315 | .fold(None, |previous, line| { 316 | // already-found v1 trumps v2 since it explicitly specifies its controllers 317 | if previous.is_some() && line.version == CgroupVersion::V2 { 318 | return previous; 319 | } 320 | 321 | Some(line) 322 | }) 323 | } 324 | 325 | fn parse_line(line: String) -> Option { 326 | // Example format: 327 | // 11:cpu,cpuacct:/ 328 | let mut fields = line.split(':'); 329 | 330 | let sub_systems = some!(fields.nth(1)); 331 | 332 | let version = if sub_systems.is_empty() { 333 | CgroupVersion::V2 334 | } else { 335 | CgroupVersion::V1 336 | }; 337 | 338 | if version == CgroupVersion::V1 && !sub_systems.split(',').any(|sub| sub == "cpu") { 339 | return None; 340 | } 341 | 342 | fields.next().map(|path| Subsys { 343 | version: version, 344 | base: path.to_owned(), 345 | }) 346 | } 347 | } 348 | 349 | #[cfg(test)] 350 | mod tests { 351 | mod v1 { 352 | use super::super::{Cgroup, CgroupVersion, MountInfo, Subsys}; 353 | use std::path::{Path, PathBuf}; 354 | 355 | // `static_in_const` feature is not stable in Rust 1.13. 356 | static FIXTURES_PROC: &'static str = "fixtures/cgroups/proc/cgroups"; 357 | 358 | static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups/cgroups"; 359 | 360 | macro_rules! join { 361 | ($base:expr, $($path:expr),+) => ({ 362 | Path::new($base) 363 | $(.join($path))+ 364 | }) 365 | } 366 | 367 | #[test] 368 | fn test_load_mountinfo() { 369 | // test only one optional fields 370 | let path = join!(FIXTURES_PROC, "mountinfo"); 371 | 372 | let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap(); 373 | 374 | assert_eq!(mnt_info.root, "/"); 375 | assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct"); 376 | 377 | // test zero optional field 378 | let path = join!(FIXTURES_PROC, "mountinfo_zero_opt"); 379 | 380 | let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap(); 381 | 382 | assert_eq!(mnt_info.root, "/"); 383 | assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct"); 384 | 385 | // test multi optional fields 386 | let path = join!(FIXTURES_PROC, "mountinfo_multi_opt"); 387 | 388 | let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V1).unwrap(); 389 | 390 | assert_eq!(mnt_info.root, "/"); 391 | assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup/cpu,cpuacct"); 392 | } 393 | 394 | #[test] 395 | fn test_load_subsys() { 396 | let path = join!(FIXTURES_PROC, "cgroup"); 397 | 398 | let subsys = Subsys::load_cpu(path).unwrap(); 399 | 400 | assert_eq!(subsys.base, "/"); 401 | assert_eq!(subsys.version, CgroupVersion::V1); 402 | } 403 | 404 | #[test] 405 | fn test_cgroup_mount() { 406 | let cases = &[ 407 | ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")), 408 | ( 409 | "/docker/01abcd", 410 | "/sys/fs/cgroup/cpu", 411 | "/docker/01abcd", 412 | Some("/sys/fs/cgroup/cpu"), 413 | ), 414 | ( 415 | "/docker/01abcd", 416 | "/sys/fs/cgroup/cpu", 417 | "/docker/01abcd/", 418 | Some("/sys/fs/cgroup/cpu"), 419 | ), 420 | ( 421 | "/docker/01abcd", 422 | "/sys/fs/cgroup/cpu", 423 | "/docker/01abcd/large", 424 | Some("/sys/fs/cgroup/cpu/large"), 425 | ), 426 | // fails 427 | ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None), 428 | ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None), 429 | ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None), 430 | ( 431 | "/docker/01abcd", 432 | "/sys/fs/cgroup/cpu", 433 | "/docker/01abcd-other-dir", 434 | None, 435 | ), 436 | ]; 437 | 438 | for &(root, mount_point, subsys, expected) in cases.iter() { 439 | let mnt_info = MountInfo { 440 | version: CgroupVersion::V1, 441 | root: root.into(), 442 | mount_point: mount_point.into(), 443 | }; 444 | let subsys = Subsys { 445 | version: CgroupVersion::V1, 446 | base: subsys.into(), 447 | }; 448 | 449 | let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base); 450 | let expected = expected.map(PathBuf::from); 451 | assert_eq!(actual, expected); 452 | } 453 | } 454 | 455 | #[test] 456 | fn test_cgroup_cpu_quota() { 457 | let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "good")); 458 | assert_eq!(cgroup.cpu_quota(), Some(6)); 459 | } 460 | 461 | #[test] 462 | fn test_cgroup_cpu_quota_divide_by_zero() { 463 | let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "zero-period")); 464 | assert!(cgroup.quota_us().is_some()); 465 | assert_eq!(cgroup.period_us(), Some(0)); 466 | assert_eq!(cgroup.cpu_quota(), None); 467 | } 468 | 469 | #[test] 470 | fn test_cgroup_cpu_quota_ceil() { 471 | let cgroup = Cgroup::new(CgroupVersion::V1, join!(FIXTURES_CGROUPS, "ceil")); 472 | assert_eq!(cgroup.cpu_quota(), Some(2)); 473 | } 474 | } 475 | 476 | mod v2 { 477 | use super::super::{Cgroup, CgroupVersion, MountInfo, Subsys}; 478 | use std::path::{Path, PathBuf}; 479 | 480 | // `static_in_const` feature is not stable in Rust 1.13. 481 | static FIXTURES_PROC: &'static str = "fixtures/cgroups2/proc/cgroups"; 482 | 483 | static FIXTURES_CGROUPS: &'static str = "fixtures/cgroups2/cgroups"; 484 | 485 | macro_rules! join { 486 | ($base:expr, $($path:expr),+) => ({ 487 | Path::new($base) 488 | $(.join($path))+ 489 | }) 490 | } 491 | 492 | #[test] 493 | fn test_load_mountinfo() { 494 | // test only one optional fields 495 | let path = join!(FIXTURES_PROC, "mountinfo"); 496 | 497 | let mnt_info = MountInfo::load_cpu(path, CgroupVersion::V2).unwrap(); 498 | 499 | assert_eq!(mnt_info.root, "/"); 500 | assert_eq!(mnt_info.mount_point, "/sys/fs/cgroup"); 501 | } 502 | 503 | #[test] 504 | fn test_load_subsys() { 505 | let path = join!(FIXTURES_PROC, "cgroup"); 506 | 507 | let subsys = Subsys::load_cpu(path).unwrap(); 508 | 509 | assert_eq!(subsys.base, "/"); 510 | assert_eq!(subsys.version, CgroupVersion::V2); 511 | } 512 | 513 | #[test] 514 | fn test_load_subsys_multi() { 515 | let path = join!(FIXTURES_PROC, "cgroup_multi"); 516 | 517 | let subsys = Subsys::load_cpu(path).unwrap(); 518 | 519 | assert_eq!(subsys.base, "/"); 520 | assert_eq!(subsys.version, CgroupVersion::V1); 521 | } 522 | 523 | #[test] 524 | fn test_cgroup_mount() { 525 | let cases = &[ 526 | ("/", "/sys/fs/cgroup/cpu", "/", Some("/sys/fs/cgroup/cpu")), 527 | ( 528 | "/docker/01abcd", 529 | "/sys/fs/cgroup/cpu", 530 | "/docker/01abcd", 531 | Some("/sys/fs/cgroup/cpu"), 532 | ), 533 | ( 534 | "/docker/01abcd", 535 | "/sys/fs/cgroup/cpu", 536 | "/docker/01abcd/", 537 | Some("/sys/fs/cgroup/cpu"), 538 | ), 539 | ( 540 | "/docker/01abcd", 541 | "/sys/fs/cgroup/cpu", 542 | "/docker/01abcd/large", 543 | Some("/sys/fs/cgroup/cpu/large"), 544 | ), 545 | // fails 546 | ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/", None), 547 | ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/docker", None), 548 | ("/docker/01abcd", "/sys/fs/cgroup/cpu", "/elsewhere", None), 549 | ( 550 | "/docker/01abcd", 551 | "/sys/fs/cgroup/cpu", 552 | "/docker/01abcd-other-dir", 553 | None, 554 | ), 555 | ]; 556 | 557 | for &(root, mount_point, subsys, expected) in cases.iter() { 558 | let mnt_info = MountInfo { 559 | version: CgroupVersion::V1, 560 | root: root.into(), 561 | mount_point: mount_point.into(), 562 | }; 563 | let subsys = Subsys { 564 | version: CgroupVersion::V1, 565 | base: subsys.into(), 566 | }; 567 | 568 | let actual = Cgroup::translate(mnt_info, subsys).map(|c| c.base); 569 | let expected = expected.map(PathBuf::from); 570 | assert_eq!(actual, expected); 571 | } 572 | } 573 | 574 | #[test] 575 | fn test_cgroup_cpu_quota() { 576 | let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "good")); 577 | assert_eq!(cgroup.cpu_quota(), Some(6)); 578 | } 579 | 580 | #[test] 581 | fn test_cgroup_cpu_quota_divide_by_zero() { 582 | let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "zero-period")); 583 | let period = cgroup.max().map(|max| max.1); 584 | 585 | assert_eq!(period, Some(0)); 586 | assert_eq!(cgroup.cpu_quota(), None); 587 | } 588 | 589 | #[test] 590 | fn test_cgroup_cpu_quota_ceil() { 591 | let cgroup = Cgroup::new(CgroupVersion::V2, join!(FIXTURES_CGROUPS, "ceil")); 592 | assert_eq!(cgroup.cpu_quota(), Some(2)); 593 | } 594 | } 595 | } 596 | --------------------------------------------------------------------------------