├── .formatter.exs ├── .gitignore ├── LICENSE.txt ├── README.md ├── lib └── cpu_info.ex ├── mix.exs ├── mix.lock └── test ├── cpu_info_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | cpu_info-*.tar 24 | 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2019 Susumu Yamazaki 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CpuInfo 2 | 3 | **CpuInfo:** get CPU information, including a type, number of processors, number of physical cores and logical threads of a processor, and status of simultaneous multi-threads (hyper-threading). 4 | 5 | ## Installation 6 | 7 | This package can be installed 8 | by adding `cpu_info` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:cpu_info, "~> 0.2.1"} 14 | ] 15 | end 16 | ``` 17 | 18 | ## Tested Platforms 19 | 20 | * Linux (with or without CUDA, including Jetson Nano) 21 | * macOS (with or without Metal) 22 | * Nerves (compile time and execution time) 23 | 24 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 25 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 26 | be found at [https://hexdocs.pm/cpu_info](https://hexdocs.pm/cpu_info). 27 | 28 | -------------------------------------------------------------------------------- /lib/cpu_info.ex: -------------------------------------------------------------------------------- 1 | defmodule CpuInfo do 2 | @moduledoc """ 3 | 4 | **CpuInfo:** get CPU information, including a type, number of processors, number of physical cores and logical threads of a processor, and status of simultaneous multi-threads (hyper-threading). 5 | 6 | """ 7 | 8 | @latest_versions %{gcc: 9, "g++": 9, clang: 9, "clang++": 9} 9 | 10 | defp os_type do 11 | case :os.type() do 12 | {:unix, :linux} -> :linux 13 | {:unix, :darwin} -> :macos 14 | {:unix, :freebsd} -> :freebsd 15 | {:win32, _} -> :windows 16 | _ -> :unknown 17 | end 18 | end 19 | 20 | @doc """ 21 | Show all profile information on CPU and the system. The results are a map that contains the following keys: 22 | 23 | * **compiler:** its corresponding value is a map that contains the following keys: 24 | * **apple_clang:** (only on macOS) its corresponding value is a list of maps whose values contain the information of the Apple Clang compiler that is on `/usr/bin`; 25 | * **apple_clang++:** (only on macOS) its corresponding value is a list of maps whose values contain the information of the Apple Clang++ compiler that is on `/usr/bin`; 26 | * **cc_env:** its corresponding value is a list of a map whose values contain the information of the C compiler that the environment variable `CC` points; 27 | * **cflags_env:** this is the value of the environment variable `CFLAGS`; 28 | * **clang:** its corresponding value is a list of maps whose values contain the information of the Clang compilers that are executable along `PATH`; 29 | * **clang++:** its corresponding value is a list of maps whose values contain the information of the Clang++ compilers that are executable along `PATH`; 30 | * **cxx_env** its corresponding value is a list of a map whose values contain the information of the C++ compiler that the environment variable `CXX` points; 31 | * **cxxflags_env:** this is the value of the environment variable `CXXFLAGS`; 32 | * **gcc:** its corresponding value is a list of maps whose values contain the information of the GCC compilers that are executable along `PATH`; 33 | * **g++:** its corresponding value is a list of maps whose values contain the information of the G++ compilers that are executable along `PATH`; 34 | * **ldflags_env:** this is the value of the environment variable `LDFLAGS` 35 | * each information of a compiler contains the following keys: 36 | * **bin:** path to the executable; 37 | * **type:** :clang, :gcc, :apple_clang, :unknown, or :undefined; 38 | * **release:** the release version of the release 39 | * **version:** the full name of the version 40 | * **cpu:** its corresponding value is a map that contains the following keys: 41 | * **cpu_model:** a string of the cpu model;s 42 | * **cpu_models:** a list of strings that corresponding to each thread (in case of Linux); 43 | * **cpu_type:** according to :erlang.system_info(:system_architecture); 44 | * **hyper_threading**: :enable or :disable; 45 | * **num_of_cores_of_a_processor:** the number of cores of a processor; 46 | * **num_of_processors:** the number of processors; 47 | * **num_of_threads_of_a_processor:** the number of threads of a processor; 48 | * **total_num_of_cores:** total number of cores; 49 | * **total_num_of_threads:** total number of threads; 50 | * **cuda:** its corresponding value is a map that contains the following keys: 51 | * **bin:** path to executables of CUDA; 52 | * **cuda:** existence of CUDA (true or false) 53 | * **include:** path to include files of CUDA; 54 | * **lib:** path to libraries of CUDA; 55 | * **nvcc:** path to the executable of nvcc; 56 | * **version:** CUDA version number 57 | * **elixir:** its corresponding value is a map that contains the following keys: 58 | * **version:** Elixir version 59 | * **erlang:** its corresponding value is a map that contains the following keys: 60 | * **otp_version:** OTP version 61 | * **kernel:** its corresponding value is a map that contains the following keys: 62 | * **kernel_release:** the release version of kernel; 63 | * **kernel_version:** the full name of the version of kernel; 64 | * **os_type:** type of OS (:macos, :linux, :windows, :freebsd or :unknown); 65 | * **system_version:** the os or distribution name 66 | * **metal:** its corresponding value is a map that contains the following keys: 67 | * **metal:** existence of Metal (true or false) 68 | """ 69 | def all_profile do 70 | os_type = os_type() 71 | cpu_type = %{cpu: cpu_type_sub(os_type)} 72 | kernel_type = %{kernel: kernel_type_sub(os_type)} 73 | cuda_info = %{cuda: cuda(os_type)} 74 | metal_info = %{metal: metal(os_type)} 75 | 76 | elixir_version = %{ 77 | elixir: %{ 78 | version: System.version() 79 | }, 80 | erlang: %{ 81 | otp_version: :erlang.system_info(:otp_release) |> List.to_string() |> String.to_integer() 82 | } 83 | } 84 | 85 | compilers = 86 | %{gcc: cc(:gcc)} 87 | |> Map.merge(%{"g++": cc(:"g++")}) 88 | |> Map.merge(%{clang: cc(:clang)}) 89 | |> Map.merge(%{"clang++": cc(:"clang++")}) 90 | |> Map.merge(%{cc_env: cc_env("CC")}) 91 | |> Map.merge(%{cxx_env: cc_env("CXX")}) 92 | |> Map.merge(%{cflags_env: flags_env("CFLAGS")}) 93 | |> Map.merge(%{cxxflags_env: flags_env("CXXFLAGS")}) 94 | |> Map.merge(%{ldflags_env: flags_env("LDFLAGS")}) 95 | 96 | compilers = 97 | if os_type == :macos do 98 | compilers 99 | |> Map.merge(%{apple_clang: cc(:apple_clang)}) 100 | |> Map.merge(%{"apple_clang++": cc(:"apple_clang++")}) 101 | else 102 | compilers 103 | end 104 | 105 | compilers = %{compiler: compilers} 106 | 107 | Map.merge(cpu_type, cuda_info) 108 | |> Map.merge(metal_info) 109 | |> Map.merge(kernel_type) 110 | |> Map.merge(elixir_version) 111 | |> Map.merge(compilers) 112 | end 113 | 114 | defp confirm_executable(command) do 115 | if is_nil(System.find_executable(command)) do 116 | raise RuntimeError, message: "#{command} isn't found." 117 | end 118 | end 119 | 120 | defp kernel_type_sub(:unknown) do 121 | %{ 122 | kernel_release: :unknown, 123 | kernel_version: :unknown, 124 | system_version: :unknown, 125 | os_type: :unknown 126 | } 127 | end 128 | 129 | defp kernel_type_sub(:windows) do 130 | %{ 131 | kernel_release: :unknown, 132 | kernel_version: :unknown, 133 | system_version: :unknown, 134 | os_type: :windows 135 | } 136 | end 137 | 138 | defp kernel_type_sub(:linux) do 139 | os_info = 140 | File.read!("/etc/os-release") 141 | |> String.split("\n") 142 | |> Enum.reverse() 143 | |> tl 144 | |> Enum.reverse() 145 | |> Enum.map(&String.split(&1, "=")) 146 | |> Enum.map(fn [k, v] -> {k, v |> String.trim("\"")} end) 147 | |> Map.new() 148 | 149 | kernel_release = 150 | case File.read("/proc/sys/kernel/osrelease") do 151 | {:ok, result} -> String.trim(result) 152 | _ -> nil 153 | end 154 | 155 | system_version = Map.get(os_info, "PRETTY_NAME") 156 | 157 | kernel_version = 158 | case File.read("/proc/sys/kernel/version") do 159 | {:ok, result} -> String.trim(result) 160 | _ -> nil 161 | end 162 | 163 | %{ 164 | kernel_release: kernel_release, 165 | kernel_version: kernel_version, 166 | system_version: system_version, 167 | os_type: :linux 168 | } 169 | end 170 | 171 | defp kernel_type_sub(:freebsd) do 172 | confirm_executable("uname") 173 | 174 | kernel_release = 175 | case System.cmd("uname", ["-r"]) do 176 | {result, 0} -> result |> String.trim() 177 | _ -> raise RuntimeError, message: "uname don't work." 178 | end 179 | 180 | system_version = 181 | case System.cmd("uname", ["-r"]) do 182 | {result, 0} -> result |> String.trim() 183 | _ -> "" 184 | end 185 | 186 | kernel_version = 187 | case System.cmd("uname", ["-r"]) do 188 | {result, 0} -> result |> String.trim() 189 | _ -> raise RuntimeError, message: "uname don't work." 190 | end 191 | 192 | %{ 193 | kernel_release: kernel_release, 194 | kernel_version: kernel_version, 195 | system_version: system_version, 196 | os_type: :freebsd 197 | } 198 | end 199 | 200 | defp kernel_type_sub(:macos) do 201 | confirm_executable("uname") 202 | confirm_executable("system_profiler") 203 | 204 | kernel_release = 205 | try do 206 | case System.cmd("uname", ["-r"]) do 207 | {result, 0} -> result |> String.trim() 208 | _ -> :os.version() |> Tuple.to_list() |> Enum.join(".") 209 | end 210 | rescue 211 | _e in ErlangError -> nil 212 | end 213 | 214 | %{ 215 | kernel_release: kernel_release 216 | } 217 | |> Map.merge( 218 | try do 219 | case System.cmd("system_profiler", ["SPSoftwareDataType"]) do 220 | {result, 0} -> result |> detect_system_and_kernel_version() 221 | _ -> nil 222 | end 223 | rescue 224 | _e in ErlangError -> nil 225 | end 226 | ) 227 | end 228 | 229 | defp cpu_type_sub(:unknown) do 230 | cpu_type = 231 | :erlang.system_info(:system_architecture) |> List.to_string() |> String.split("-") |> hd 232 | 233 | %{ 234 | cpu_type: cpu_type, 235 | cpu_model: :unknown, 236 | cpu_models: :unknown, 237 | num_of_processors: :unknown, 238 | num_of_cores_of_a_processor: :unknown, 239 | total_num_of_cores: :unknown, 240 | num_of_threads_of_a_processor: :unknown, 241 | total_num_of_threads: System.schedulers_online(), 242 | hyper_threading: :unknown 243 | } 244 | end 245 | 246 | defp cpu_type_sub(:windows) do 247 | cpu_type = 248 | :erlang.system_info(:system_architecture) |> List.to_string() |> String.split("-") |> hd 249 | 250 | %{ 251 | cpu_type: cpu_type, 252 | cpu_model: :unknown, 253 | cpu_models: :unknown, 254 | num_of_processors: :unknown, 255 | num_of_cores_of_a_processor: :unknown, 256 | total_num_of_cores: :unknown, 257 | num_of_threads_of_a_processor: :unknown, 258 | total_num_of_threads: System.schedulers_online(), 259 | hyper_threading: :unknown 260 | } 261 | end 262 | 263 | defp cpu_type_sub(:linux) do 264 | cpu_type = 265 | :erlang.system_info(:system_architecture) |> List.to_string() |> String.split("-") |> hd 266 | 267 | info = 268 | File.read!("/proc/cpuinfo") 269 | |> String.split("\n\n") 270 | # drop last (emtpy) item 271 | |> Enum.reverse() 272 | |> tl() 273 | |> Enum.reverse() 274 | |> Enum.map(fn cpuinfo -> 275 | String.split(cpuinfo, "\n") 276 | |> Enum.map(fn item -> 277 | [k | v] = String.split(item, ~r"\t+: ") 278 | {k, v} 279 | end) 280 | |> Map.new() 281 | end) 282 | 283 | cpu_models = Enum.map(info, &Map.get(&1, "model name")) |> List.flatten() 284 | 285 | cpu_model = hd(cpu_models) 286 | 287 | num_of_processors = 288 | Enum.map(info, &Map.get(&1, "physical id")) 289 | |> Enum.uniq() 290 | |> Enum.count() 291 | 292 | t1 = 293 | Enum.map(info, &Map.get(&1, "processor")) 294 | |> Enum.uniq() 295 | |> Enum.reject(&is_nil(&1)) 296 | |> length 297 | 298 | t = 299 | Enum.map(info, &Map.get(&1, "cpu cores")) 300 | |> Enum.uniq() 301 | |> Enum.reject(&is_nil(&1)) 302 | |> Enum.map(&(&1 |> hd |> String.to_integer())) 303 | |> Enum.sum() 304 | 305 | total_num_of_cores = if t == 0, do: t1, else: t 306 | 307 | num_of_cores_of_a_processor = div(total_num_of_cores, num_of_processors) 308 | 309 | total_num_of_threads = 310 | Enum.map(info, &Map.get(&1, "processor")) 311 | |> Enum.count() 312 | 313 | num_of_threads_of_a_processor = div(total_num_of_threads, num_of_processors) 314 | 315 | ht = 316 | if total_num_of_cores < total_num_of_threads do 317 | :enabled 318 | else 319 | :disabled 320 | end 321 | 322 | %{ 323 | cpu_type: cpu_type, 324 | cpu_model: cpu_model, 325 | cpu_models: cpu_models, 326 | num_of_processors: num_of_processors, 327 | num_of_cores_of_a_processor: num_of_cores_of_a_processor, 328 | total_num_of_cores: total_num_of_cores, 329 | num_of_threads_of_a_processor: num_of_threads_of_a_processor, 330 | total_num_of_threads: total_num_of_threads, 331 | hyper_threading: ht 332 | } 333 | end 334 | 335 | defp cpu_type_sub(:freebsd) do 336 | confirm_executable("uname") 337 | confirm_executable("sysctl") 338 | 339 | cpu_type = 340 | case System.cmd("uname", ["-m"]) do 341 | {result, 0} -> result |> String.trim() 342 | _ -> raise RuntimeError, message: "uname don't work." 343 | end 344 | 345 | cpu_model = 346 | case System.cmd("sysctl", ["-n", "hw.model"]) do 347 | {result, 0} -> result |> String.trim() 348 | _ -> raise RuntimeError, message: "sysctl don't work." 349 | end 350 | 351 | cpu_models = [cpu_model] 352 | 353 | total_num_of_cores = 354 | case System.cmd("sysctl", ["-n", "kern.smp.cores"]) do 355 | {result, 0} -> result |> String.trim() |> String.to_integer() 356 | _ -> raise RuntimeError, message: "sysctl don't work." 357 | end 358 | 359 | total_num_of_threads = 360 | case System.cmd("sysctl", ["-n", "kern.smp.cpus"]) do 361 | {result, 0} -> result |> String.trim() |> String.to_integer() 362 | _ -> raise RuntimeError, message: "sysctl don't work." 363 | end 364 | 365 | ht = 366 | case System.cmd("sysctl", ["-n", "machdep.hyperthreading_allowed"]) do 367 | {"1\n", 0} -> :enabled 368 | {"0\n", 0} -> :disabled 369 | _ -> raise RuntimeError, message: "sysctl don't work." 370 | end 371 | 372 | %{ 373 | cpu_type: cpu_type, 374 | cpu_model: cpu_model, 375 | cpu_models: cpu_models, 376 | num_of_processors: :unknown, 377 | num_of_cores_of_a_processor: :unknown, 378 | total_num_of_cores: total_num_of_cores, 379 | num_of_threads_of_a_processor: :unknown, 380 | total_num_of_threads: total_num_of_threads, 381 | hyper_threading: ht 382 | } 383 | end 384 | 385 | defp cpu_type_sub(:macos) do 386 | confirm_executable("uname") 387 | confirm_executable("system_profiler") 388 | 389 | cpu_type = 390 | try do 391 | case System.cmd("uname", ["-m"]) do 392 | {result, 0} -> result |> String.trim() 393 | _ -> nil 394 | end 395 | rescue 396 | _e in ErlangError -> nil 397 | end 398 | 399 | %{ 400 | cpu_type: cpu_type 401 | } 402 | |> Map.merge( 403 | try do 404 | case System.cmd("system_profiler", ["SPHardwareDataType"]) do 405 | {result, 0} -> result |> parse_macos 406 | _ -> nil 407 | end 408 | rescue 409 | _e in ErlangError -> nil 410 | end 411 | ) 412 | end 413 | 414 | defp detect_system_and_kernel_version(message) do 415 | trimmed_message = message |> split_trim 416 | 417 | %{ 418 | kernel_version: 419 | trimmed_message 420 | |> Enum.filter(&String.match?(&1, ~r/Kernel Version/)) 421 | |> hd 422 | |> String.split() 423 | |> Enum.slice(2..-1) 424 | |> Enum.join(" "), 425 | system_version: 426 | trimmed_message 427 | |> Enum.filter(&String.match?(&1, ~r/System Version/)) 428 | |> hd 429 | |> String.split() 430 | |> Enum.slice(2..-1) 431 | |> Enum.join(" ") 432 | } 433 | end 434 | 435 | defp parse_macos(message) do 436 | trimmed_message = message |> split_trim 437 | 438 | cpu_model = 439 | Enum.filter(trimmed_message, &String.match?(&1, ~r/(Processor Name|Chip)/)) 440 | |> hd 441 | |> String.split() 442 | |> Enum.slice(2..-1) 443 | |> Enum.join(" ") 444 | 445 | cpu_models = [cpu_model] 446 | 447 | num_of_processors = 448 | trimmed_message 449 | |> Enum.filter(&String.match?(&1, ~r/Number of Processors/)) 450 | |> case do 451 | [match] -> match_to_integer(match) 452 | [] -> 1 453 | end 454 | 455 | num_of_cores = 456 | trimmed_message 457 | |> Enum.filter(&String.match?(&1, ~r/Total Number of Cores/)) 458 | |> hd 459 | 460 | {total_num_of_cores, num_of_pcores, num_of_ecores} = 461 | cond do 462 | String.match?(num_of_cores, ~r/performance .* efficiency/) -> 463 | parse_cores(num_of_cores) 464 | 465 | true -> 466 | {match_to_integer(num_of_cores), 0, 0} 467 | end 468 | 469 | num_of_cores_of_a_processor = div(total_num_of_cores, num_of_processors) 470 | 471 | m_ht = Enum.filter(trimmed_message, &String.match?(&1, ~r/Hyper-Threading Technology/)) 472 | 473 | ht = 474 | if length(m_ht) > 0 and String.match?(hd(m_ht), ~r/Enabled/) do 475 | :enabled 476 | else 477 | :disabled 478 | end 479 | 480 | total_num_of_threads = 481 | total_num_of_cores * 482 | case ht do 483 | :enabled -> 2 484 | :disabled -> 1 485 | end 486 | 487 | num_of_threads_of_a_processor = div(total_num_of_threads, num_of_processors) 488 | 489 | %{ 490 | os_type: :macos, 491 | cpu_model: cpu_model, 492 | cpu_models: cpu_models, 493 | num_of_processors: num_of_processors, 494 | num_of_cores_of_a_processor: num_of_cores_of_a_processor, 495 | total_num_of_cores: total_num_of_cores, 496 | num_of_threads_of_a_processor: num_of_threads_of_a_processor, 497 | total_num_of_threads: total_num_of_threads, 498 | hyper_threading: ht, 499 | num_of_pcores: num_of_pcores, 500 | num_of_ecores: num_of_ecores 501 | } 502 | end 503 | 504 | defp parse_cores(num_of_cores) do 505 | map_cores = 506 | Regex.named_captures( 507 | ~r/Total Number of Cores: (?[0-9]+) \((?[0-9]+) performance and (?[0-9]+) efficiency\)/, 508 | num_of_cores 509 | ) 510 | 511 | { 512 | map_cores |> Map.get("total_num_of_cores", 1) |> String.to_integer(), 513 | map_cores |> Map.get("num_of_pcores", 0) |> String.to_integer(), 514 | map_cores |> Map.get("num_of_ecores", 0) |> String.to_integer() 515 | } 516 | end 517 | 518 | defp split_trim(message) do 519 | message |> String.split("\n") |> Enum.map(&String.trim(&1)) 520 | end 521 | 522 | defp match_to_integer(message) do 523 | Regex.run(~r/[0-9]+/, message) |> hd |> String.to_integer() 524 | end 525 | 526 | def flags_env(env) do 527 | flags = System.get_env(env) 528 | 529 | if is_nil(flags) do 530 | "" 531 | else 532 | flags 533 | end 534 | end 535 | 536 | def cc_env(env) do 537 | System.get_env(env) 538 | |> cc_env_sub() 539 | end 540 | 541 | defp cc_env_sub(nil) do 542 | [] 543 | end 544 | 545 | defp cc_env_sub(cc) do 546 | exe = System.find_executable(cc) 547 | 548 | cond do 549 | is_nil(exe) -> 550 | %{ 551 | bin: cc, 552 | type: :undefined 553 | } 554 | 555 | String.match?(exe, ~r/clang\+\+/) -> 556 | cc_sub([exe], :"clang++") 557 | 558 | String.match?(exe, ~r/clang/) -> 559 | cc_sub([exe], :clang) 560 | 561 | String.match?(exe, ~r/g\+\+/) -> 562 | cc_sub([exe], :"g++") 563 | 564 | String.match?(exe, ~r/gcc/) -> 565 | cc_sub([exe], :gcc) 566 | 567 | true -> 568 | [ 569 | %{ 570 | bin: exe, 571 | type: :unknown 572 | } 573 | ] 574 | end 575 | end 576 | 577 | def cc(:apple_clang) do 578 | exe = "/usr/bin/clang" 579 | 580 | [System.find_executable(exe)] 581 | |> cc_sub(:apple_clang) 582 | end 583 | 584 | def cc(:"apple_clang++") do 585 | exe = "/usr/bin/clang++" 586 | 587 | [System.find_executable(exe)] 588 | |> cc_sub(:"apple_clang++") 589 | end 590 | 591 | def cc(type) do 592 | exe = Atom.to_string(type) 593 | latest_version = Map.get(@latest_versions, type) 594 | 595 | list_executable_versions(exe, 1, latest_version) 596 | |> cc_sub(type) 597 | end 598 | 599 | defp cc_sub(exes, type) do 600 | Enum.map( 601 | exes, 602 | &(%{bin: &1} 603 | |> Map.merge( 604 | execute_to_get_version(&1) 605 | |> parse_versions(type) 606 | |> parse_version_number() 607 | )) 608 | ) 609 | end 610 | 611 | defp list_executable_versions(exe, from, to) do 612 | ([System.find_executable(exe)] ++ 613 | Enum.map(from..to, &System.find_executable(exe <> "-" <> Integer.to_string(&1)))) 614 | |> Enum.filter(&(&1 != nil)) 615 | end 616 | 617 | defp execute_to_get_version(exe) do 618 | System.cmd(exe, ["--version"], stderr_to_stdout: true) 619 | |> elem(0) 620 | end 621 | 622 | defp parse_versions(result, :gcc) do 623 | if String.match?(result, ~r/Copyright \(C\) [0-9]+ Free Software Foundation, Inc\./) do 624 | versions = String.split(result, "\n") |> Enum.at(0) 625 | %{type: :gcc, versions: versions} 626 | else 627 | parse_versions(result, :apple_clang) 628 | end 629 | end 630 | 631 | defp parse_versions(result, :"g++") do 632 | if String.match?(result, ~r/Copyright \(C\) [0-9]+ Free Software Foundation, Inc\./) do 633 | versions = String.split(result, "\n") |> Enum.at(0) 634 | %{type: :"g++", versions: versions} 635 | else 636 | parse_versions(result, :"apple_clang++") 637 | end 638 | end 639 | 640 | defp parse_versions(result, :clang) do 641 | if String.match?(result, ~r/Apple/) do 642 | parse_versions(result, :apple_clang) 643 | else 644 | versions = String.split(result, "\n") |> Enum.at(0) 645 | %{type: :clang, versions: versions} 646 | end 647 | end 648 | 649 | defp parse_versions(result, :"clang++") do 650 | if String.match?(result, ~r/Apple/) do 651 | parse_versions(result, :"apple_clang++") 652 | else 653 | versions = String.split(result, "\n") |> Enum.at(0) 654 | %{type: :"clang++", versions: versions} 655 | end 656 | end 657 | 658 | defp parse_versions(result, :apple_clang) do 659 | %{type: :apple_clang} 660 | |> Map.merge( 661 | Regex.named_captures(~r/(?Apple .* version [0-9.]+ .*)\n/, result) 662 | |> key_string_to_atom() 663 | ) 664 | end 665 | 666 | defp parse_versions(result, :"apple_clang++") do 667 | %{type: :"apple_clang++"} 668 | |> Map.merge( 669 | Regex.named_captures(~r/(?Apple .* version [0-9.]+ .*)\n/, result) 670 | |> key_string_to_atom() 671 | ) 672 | end 673 | 674 | defp key_string_to_atom(map) do 675 | if is_nil(map) do 676 | %{versions: ""} 677 | else 678 | Map.keys(map) 679 | |> Enum.map( 680 | &{ 681 | String.to_atom(&1), 682 | Map.get(map, &1) 683 | } 684 | ) 685 | |> Map.new() 686 | end 687 | end 688 | 689 | defp parse_version_number(map) do 690 | Map.merge( 691 | map, 692 | Regex.named_captures(~r/(?[0-9]+\.[0-9.]+)/, Map.get(map, :versions)) 693 | |> key_string_to_atom() 694 | ) 695 | end 696 | 697 | defp cuda(:linux) do 698 | case File.read("/usr/local/cuda/version.txt") do 699 | {:ok, cuda_version} -> 700 | %{cuda: true} 701 | |> Map.merge(parse_cuda_version(cuda_version)) 702 | |> Map.merge(%{ 703 | bin: find_path("/usr/local/cuda/bin"), 704 | include: find_path("/usr/local/cuda/include"), 705 | lib: find_path("/usr/local/cuda/lib64"), 706 | nvcc: System.find_executable("/usr/local/cuda/bin/nvcc") 707 | }) 708 | 709 | {:error, _reason} -> 710 | %{cuda: false} 711 | end 712 | end 713 | 714 | defp cuda(_) do 715 | %{cuda: false} 716 | end 717 | 718 | defp parse_cuda_version(cuda_version) do 719 | Regex.named_captures(~r/CUDA Version (?[0-9.]+)/, cuda_version) 720 | |> key_string_to_atom() 721 | end 722 | 723 | defp find_path(path) do 724 | if File.exists?(path) do 725 | path 726 | else 727 | nil 728 | end 729 | end 730 | 731 | defp metal(:macos) do 732 | confirm_executable("system_profiler") 733 | 734 | try do 735 | case System.cmd("system_profiler", ["SPDisplaysDataType"]) do 736 | {result, 0} -> result |> detect_metal_supported() 737 | _ -> %{metal: false} 738 | end 739 | rescue 740 | _e in ErlangError -> %{metal: false} 741 | end 742 | end 743 | 744 | defp metal(_) do 745 | %{metal: false} 746 | end 747 | 748 | defp detect_metal_supported(message) do 749 | trimmed_message = message |> split_trim 750 | 751 | %{ 752 | metal: 753 | trimmed_message 754 | |> Enum.map(&String.match?(&1, ~r/Metal( Family)?: Supported/)) 755 | |> Enum.reduce(false, fn x, acc -> x or acc end) 756 | } 757 | end 758 | end 759 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule CpuInfo.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :cpu_info, 7 | version: "0.2.1", 8 | elixir: "~> 1.9", 9 | start_permanent: Mix.env() == :prod, 10 | description: description(), 11 | package: package(), 12 | deps: deps(), 13 | docs: [ 14 | api_reference: false, 15 | main: "CpuInfo" 16 | ] 17 | ] 18 | end 19 | 20 | # Run "mix help compile.app" to learn about applications. 21 | def application do 22 | [ 23 | extra_applications: [:logger] 24 | ] 25 | end 26 | 27 | # Run "mix help deps" to learn about dependencies. 28 | defp deps do 29 | [ 30 | {:earmark, "~> 1.4", only: :dev}, 31 | {:ex_doc, "~> 0.21", only: :dev} 32 | ] 33 | end 34 | 35 | defp description() do 36 | "CpuInfo: get CPU information, including a type, number of processors, number of physical cores and logical threads of a processor, and status of simultaneous multi-threads (hyper-threading)." 37 | end 38 | 39 | defp package() do 40 | [ 41 | name: "cpu_info", 42 | maintainers: [ 43 | "Susumu Yamazaki" 44 | ], 45 | licenses: ["Apache 2.0"], 46 | links: %{"GitHub" => "https://github.com/zeam-vm/cpu_info"} 47 | ] 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm", "5e8806285d8a3a8999bd38e4a73c58d28534c856bc38c44818e5ba85bbda16fb"}, 3 | "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"}, 4 | "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"}, 5 | "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, 6 | "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm", "00e3ebdc821fb3a36957320d49e8f4bfa310d73ea31c90e5f925dc75e030da8f"}, 7 | "ok": {:hex, :ok, "2.3.0", "0a3d513ec9038504dc5359d44e14fc14ef59179e625563a1a144199cdc3a6d30", [:mix], [], "hexpm"}, 8 | } 9 | -------------------------------------------------------------------------------- /test/cpu_info_test.exs: -------------------------------------------------------------------------------- 1 | defmodule CpuInfoTest do 2 | use ExUnit.Case 3 | doctest CpuInfo 4 | end 5 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------