File Coverage

blib/lib/System/CPU.pm
Criterion Covered Total %
statement 122 125 97.6
branch 98 104 94.2
condition 23 27 85.1
subroutine 21 21 100.0
pod 5 5 100.0
total 269 282 95.3


line stmt bran cond sub pod time code
1             package System::CPU;
2              
3 2     2   107036 use 5.006;
  2         8  
4 2     2   11 use strict;
  2         3  
  2         63  
5 2     2   11 use warnings;
  2         3  
  2         59  
6              
7 2     2   10 use List::Util qw(sum);
  2         4  
  2         5268  
8              
9             our $VERSION = '1.02';
10              
11             =head1 NAME
12              
13             System::CPU - Cross-platform CPU information / topology
14              
15             =head1 SYNOPSIS
16              
17             use System::CPU;
18              
19             # Number of logical cores. E.g. on SMT systems these will be Hyper-Threads
20             my $logical_cpu = System::CPU::get_ncpu();
21              
22             # On some platforms you can also get the number of processors and physical cores
23             my ($phys_processors, $phys_cpu, $logical_cpu) = System::CPU::get_cpu();
24              
25             # Model name of the CPU
26             my $name = System::CPU::get_name();
27              
28             # CPU Architecture
29             my $arch = System::CPU::get_arch();
30              
31             # Get all the above in a hash
32             my $hash = System::CPU::get_hash();
33              
34             =head1 DESCRIPTION
35              
36             A pure Perl module with no dependencies to get basic CPU information on any platform.
37             The data you can get differs depending on platform, but for many systems running
38             Linux/BSD/MacOS you can get extra nuance like number of threads vs cores etc.
39              
40             It was created for L with the C function modeled
41             after the one on L. In fact, some code was copied from that function as
42             it had the most reliable way to consistently get the logical cpus of the system.
43              
44             =head1 FUNCTIONS
45              
46             =head2 get_cpu
47              
48             Returns as detailed CPU topology as the platform allows. A list of three values
49             will be returned, with the first and the second possibly C:
50              
51             my ($phys_processors, $phys_cpu, $logical_cpu) = System::CPU::get_cpu();
52              
53             For many Linux, MacOS, BSD systems the number of physical processors (sockets),
54             as well as the number of physical CPU cores and logical CPUs (CPU threads) will
55             be returned.
56              
57             For the systems where the extra information is not available (i.e. all other OSes
58             and some Linux/MacOS/BSD setups), the first two values will be C.
59              
60             =head2 get_ncpu
61              
62             my $logical_cpus = System::CPU::get_ncpu();
63              
64             This function behaves very similar to C - in fact code is borrowed
65             from it. The number of logical CPUs will be returned, this is the number of hyper-threads
66             for SMT systems and the number of cores for most others.
67              
68             =head2 get_name
69              
70             my $cpu_name = System::CPU::get_name(raw => $raw?);
71              
72             Returns the CPU model name. By default it will remove some extra spaces and Intel's
73             (TM) and (R), but you can pass in the C argument to avoid this cleanup.
74              
75             =head2 get_arch
76              
77             my $arch = System::CPU::get_arch();
78              
79             Will return the CPU architecture as reported by the system. There is no standarized
80             form, e.g. Linux will report aarch64 on a system where Darwin would report arm64
81             etc.
82              
83             =head2 get_hash
84              
85             my $hash = System::CPU::get_hash(%opt?);
86              
87             Will return all the information the module can access in a hash. Accepts the options
88             of the other functions. Example hash output:
89              
90             {
91             arch => 'arm64',
92             logical_cores => 10,
93             name => 'Apple M2 Pro',
94             physical_cores => 10,
95             processors => 1
96             }
97              
98             =head1 CAVEATS
99              
100             Since text output from user commands is parsed for most platforms, only the English
101             language locales are supported.
102              
103             =head1 NOTES
104              
105             I did try to use existing solutions before writing my own. L has issues
106             installing on modern Linux systems (I tried submitting a PR, but the author seems
107             unresponsive).
108              
109             L is the most promising, however, it returns a simple "core" count which
110             seems to inconsistently be either physical cores or threads depending on the platform.
111             The author got back to me, so I will try to sort that out, as that module is more
112             generic than System::CPU.
113              
114             There are also several platform-specific modules, most requiring a compiler too
115             (e.g. L, L, various C<*::Sysinfo>).
116              
117             In the end, I wanted to get the CPU topology where possible - number of processors/sockets,
118             cores, threads separately, something that wasn't readily available.
119              
120             I intend to support all systems possible with this simple pure Perl module. If you
121             have access to a system that is not supported or where the module cannot currently
122             give you the correct output, feel free to contact me about extending support.
123              
124             Currently supported systems:
125              
126             Linux/Android, BSD/MacOS, Win32/Cygwin, AIX, Solaris, IRIX, HP-UX, Haiku, GNU
127             and variants of those.
128              
129             =cut
130              
131             sub get_hash {
132 1     1 1 3003 my %opt = @_;
133 1         4 my ($proc, $phys, $log) = get_cpu(%opt);
134 1         4 my $name = get_name(%opt);
135 1         4 my $arch = get_arch(%opt);
136             return {
137 1         12 processors => $proc,
138             logical_cores => $log,
139             physical_cores => $phys,
140             name => $name,
141             arch => $arch,
142             };
143             }
144              
145             sub get_cpu {
146 53 100   53 1 16319 return _linux_cpu() if $^O =~ /linux|android/i;
147 44 100       212 return _bsd_cpu() if $^O =~ /bsd|darwin|dragonfly/i;
148 37 100       128 return _solaris_cpu() if $^O =~ /osf|solaris|sunos|svr5|sco/i;
149 31 100       103 return _aix_cpu() if $^O =~ /aix/i;
150 21 100       73 return _gnu_cpu() if $^O =~ /gnu/i;
151 17 100       56 return _haiku_cpu() if $^O =~ /haiku/i;
152 13 100       93 return _hpux_cpu() if $^O =~ /hp-?ux/i;
153 9 100       31 return _irix_cpu() if $^O =~ /irix/i;
154             return (undef, undef, $ENV{NUMBER_OF_PROCESSORS})
155 5 100       38 if $^O =~ /mswin|mingw|msys|cygwin/i;
156              
157 1         12 die "OS identifier '$^O' not recognized. Contact dkechag\@cpan.org to add support.";
158             }
159              
160             sub get_ncpu {
161 25     25 1 56 my $ncpu = get_cpu();
162 25         127 return $ncpu;
163             }
164              
165             sub get_name {
166 18     18 1 12195 my %opt = @_;
167 18         31 my $name;
168 18 100       173 if ($^O =~ /linux|android/i) {
    100          
    100          
    100          
    100          
    100          
    100          
169 4         13 ($name) = _proc_cpuinfo();
170             } elsif ($^O =~ /bsd|darwin|dragonfly/i) {
171 4         55 chomp($name = `sysctl -n machdep.cpu.brand_string 2>/dev/null`);
172 4 100       103 chomp($name = `sysctl -n hw.model 2>/dev/null`) unless $name;
173             } elsif ($^O =~ /mswin|mingw|msys|cygwin/i) {
174 1         3 $name = $ENV{PROCESSOR_IDENTIFIER};
175             } elsif ($^O =~ /aix/i) {
176 2         6 chomp(my $out = `prtconf | grep -i "Processor Type" 2>/dev/null`);
177 2 100       55 $name = $1 if $out =~ /:\s*(.*)/;
178             } elsif ($^O =~ /irix/i) {
179 2         6 my @out = grep {/CPU:/i} `hinv 2>/dev/null`;
  4         74  
180 2 100 66     38 $name = $1 if @out && $out[0] =~ /CPU:\s*(.*)/i;
181             } elsif ($^O =~ /haiku/i) {
182 2         6 my $out = `sysinfo -cpu 2>/dev/null | grep "^CPU #"`;
183 2 50       94 $name = $1 if $out =~ /:\s*(?:")?(.*?)(?:")?\s*$/m;
184             } elsif ($^O =~ /hp-?ux/i) {
185 2         6 my $out = `machinfo`;
186 2 100       91 if ($out =~ /processor model:\s*\d*\s*(.+?)$/im) {
    50          
187 1         5 $name = $1;
188             } elsif ($out =~ /\s*\d*\s*(.+(?:MHz|GHz).+)$/m) {
189 1         4 $name = $1;
190             }
191             } else {
192 1         10 die "OS identifier '$^O' not recognized. Contact dkechag\@cpan.org to add support.";
193             }
194              
195 17 100       97 unless ($opt{raw}) {
196 16 100       84 $name =~ s/\s+/ /g if $name; # I don't like some systems giving excess whitespace.
197 16 100       57 $name =~ s/\((?:R|TM)\)//g if $name; # I don't like Intel's (R)s and (TM)s
198             }
199 17   100     131 return $name || "";
200             }
201              
202              
203             sub get_arch {
204 7 100   7 1 8643 return _uname_m() if $^O =~ /linux|android|bsd|darwin|dragonfly|gnu|osf|solaris|sunos|svr5|sco|hp-?ux/i;
205 4 100       40 return _uname_p() if $^O =~ /aix|irix/i;
206 3 100       14 return _getarch() if $^O =~ /haiku/i;
207 2 100       12 return $ENV{PROCESSOR_ARCHITECTURE} if $^O =~ /mswin|mingw|msys|cygwin/i;
208              
209 1         15 die "OS identifier '$^O' not recognized. Contact dkechag\@cpan.org to add support.";
210             }
211              
212             sub _solaris_cpu {
213 6     6   10 my $ncpu;
214 6 50       28 if (-x '/usr/sbin/psrinfo') {
215 0         0 my $count = grep {/on-?line/} `psrinfo 2>/dev/null`;
  0         0  
216 0 0       0 $ncpu = $count if $count;
217             } else {
218 6         1513 my @output = grep {/^NumCPU = \d+/} `uname -X 2>/dev/null`;
  22         130  
219 6 100       87 $ncpu = (split ' ', $output[0])[2] if @output;
220             }
221 6         28 return (undef, undef, $ncpu);
222             }
223              
224             sub _bsd_cpu {
225 7     7   19 my $prof = `system_profiler -detailLevel mini SPHardwareDataType SPSoftwareDataType 2>/dev/null`;
226 7 100       296 my $proc = $prof ? 1 : undef;
227 7 100 100     57 $proc = $1 if $prof && $prof =~ /Number of (?:Processors|CPUs): (\d+)/i;
228 7         18 chomp(my $cpus = `sysctl -n hw.logicalcpu 2>/dev/null`);
229 7 100       92 chomp($cpus = `sysctl -n hw.ncpu 2>/dev/null`) unless $cpus; # Old system fallback
230 7 100       55 return ($proc, undef, undef) unless $cpus;
231 5         9 chomp(my $cores = `sysctl -n hw.physicalcpu 2>/dev/null`);
232 5   66     59 $cores ||= $cpus;
233 5         24 return ($proc, $cores, $cpus);
234             }
235              
236             sub _linux_cpu {
237 9     9   24 my ($name, $phys, $cores, $cpus) = _proc_cpuinfo();
238 9         158 return $phys, $cores, $cpus;
239             }
240              
241             sub _aix_cpu {
242 10     10   30 my $ncpu;
243 10         23 my @output = `lparstat -i 2>/dev/null | grep "^Online Virtual CPUs"`;
244 10 100       253 if (@output) {
245 4         16 $output[0] =~ /(\d+)\n$/;
246 4 100       12 $ncpu = $1 if $1;
247             }
248 10 100       24 if (!$ncpu) {
249 8         16 @output = `pmcycles -m 2>/dev/null`;
250 8 100       131 if (@output) {
251 4         7 $ncpu = scalar @output;
252             } else {
253 4         19 @output = `lsdev -Cc processor -S Available 2>/dev/null`;
254 4 100       93 $ncpu = scalar @output if @output;
255             }
256             }
257 10         45 return (undef, undef, $ncpu);
258             }
259              
260             sub _haiku_cpu {
261 4     4   7 my $ncpu;
262 4         8 my @output = `sysinfo -cpu 2>/dev/null | grep "^CPU #"`;
263 4 100       109 $ncpu = scalar @output if @output;
264 4         17 return (undef, undef, $ncpu);;
265             }
266              
267             sub _hpux_cpu {
268 4     4   13 my $ncpu = grep { /^processor/ } `ioscan -fkC processor 2>/dev/null`;
  20         126  
269 4   100     72 return (undef, undef, $ncpu || undef);
270             }
271              
272             sub _irix_cpu {
273 4     4   6 my $ncpu;
274 4         11 my @out = grep {/\s+processors?$/i} `hinv -c processor 2>/dev/null`;
  12         106  
275 4 100       48 $ncpu = (split ' ', $out[0])[0] if @out;
276 4         19 return (undef, undef, $ncpu);;
277             }
278              
279             sub _gnu_cpu {
280 4     4   7 my $ncpu;
281 4         11 chomp(my @output = `nproc --all 2>/dev/null`);
282 4 100       98 $ncpu = $output[0] if @output;
283 4         19 return (undef, undef, $ncpu);;
284             }
285              
286             sub _proc_cpuinfo {
287 13     13   24 my (@physical, @cores, $phys, $cpus, $name);
288 13 100 66     173 if (-f '/proc/cpuinfo' && open my $fh, '<', '/proc/cpuinfo') {
289 10         4571 while (<$fh>) {
290 973 100       4470 $cpus++ if /^processor\s*:/i;
291 973 100       1657 push @physical, $1 if /^physical id\s*:\s*(\d+)/i;
292 973 100       1645 push @cores, $1 if /^cpu cores\s*:\s*(\d+)/i;
293 973 100       2683 $name = $1 if /^model name\s*:\s*(.*)/i;
294             }
295 10 100 100     120 return $name, undef, $cores[0], $cpus if !@physical && @cores;
296 8 100       21 @cores = (0) unless @cores;
297 8         20 my %hash;
298             $hash{$physical[$_]} = $_ < scalar(@cores) ? $cores[$_] : $cores[0]
299 8 50       82 for 0 .. $#physical;
300 8   100     26 my $phys = keys %hash || undef;
301 8   66     72 my $cores = sum(values %hash) || $cpus;
302 8         87 return $name, $phys, $cores, $cpus;
303             }
304 3         916 return;
305             }
306              
307             sub _uname_m {
308 5     5   8767 chomp( my $arch = `uname -m 2>/dev/null` );
309 5   100     184 return $arch || _uname_p();
310             }
311              
312             sub _uname_p {
313 3     3   9 chomp( my $arch = `uname -p 2>/dev/null` );
314 3         56 return $arch;
315             }
316              
317             sub _getarch {
318 1     1   3 chomp( my $arch = `getarch 2>/dev/null` );
319 1         32 return $arch;
320             }
321              
322             =head1 AUTHOR
323              
324             Dimitrios Kechagias, C<< >>
325              
326             =head1 BUGS
327              
328             Please report any bugs or feature requests to L.
329              
330             You can also submit PRs with fixes/enhancements directly.
331              
332             =head1 SUPPORT
333              
334             You can find documentation for this module with the perldoc command.
335              
336             perldoc System::CPU
337              
338             You can also look for information at:
339              
340             =over 4
341              
342             =item * GitHub
343              
344             L
345              
346             =item * Search CPAN
347              
348             L
349              
350             =back
351              
352             =head1 ACKNOWLEDGEMENTS
353              
354             Some code borrowed from L.
355              
356             =head1 LICENSE AND COPYRIGHT
357              
358             This software is copyright (c) 2023 by Dimitrios Kechagias.
359              
360             This is free software; you can redistribute it and/or modify it under
361             the same terms as the Perl 5 programming language system itself.
362              
363             =cut
364              
365             1; # End of System::CPU