File Coverage

blib/lib/System/CPU.pm
Criterion Covered Total %
statement 124 127 97.6
branch 101 108 93.5
condition 23 27 85.1
subroutine 21 21 100.0
pod 5 5 100.0
total 274 288 95.1


line stmt bran cond sub pod time code
1             package System::CPU;
2              
3 2     2   113197 use 5.006;
  2         11  
4 2     2   10 use strict;
  2         6  
  2         66  
5 2     2   9 use warnings;
  2         7  
  2         57  
6              
7 2     2   11 use List::Util qw(sum);
  2         4  
  2         5191  
8              
9             our $VERSION = '1.03';
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 2969 my %opt = @_;
133 1         5 my ($proc, $phys, $log) = get_cpu(%opt);
134 1         4 my $name = get_name(%opt);
135 1         9 my $arch = get_arch(%opt);
136             return {
137 1         18 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 15984 return _linux_cpu() if $^O =~ /linux|android/i;
147 44 100       191 return _bsd_cpu() if $^O =~ /bsd|darwin|dragonfly/i;
148 37 100       125 return _solaris_cpu() if $^O =~ /osf|solaris|sunos|svr5|sco/i;
149 31 100       101 return _aix_cpu() if $^O =~ /aix/i;
150 21 100       55 return _gnu_cpu() if $^O =~ /gnu/i;
151 17 100       51 return _haiku_cpu() if $^O =~ /haiku/i;
152 13 100       48 return _hpux_cpu() if $^O =~ /hp-?ux/i;
153 9 100       33 return _irix_cpu() if $^O =~ /irix/i;
154             return (undef, undef, $ENV{NUMBER_OF_PROCESSORS})
155 5 100       42 if $^O =~ /mswin|mingw|msys|cygwin/i;
156              
157 1         11 die "OS identifier '$^O' not recognized. Contact dkechag\@cpan.org to add support.";
158             }
159              
160             sub get_ncpu {
161 25     25 1 61 my $ncpu = get_cpu();
162 25         130 return $ncpu;
163             }
164              
165             sub get_name {
166 19     19 1 11487 my %opt = @_;
167 19         26 my $name;
168 19 100       226 if ($^O =~ /linux|android/i) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
169 4         12 ($name) = _proc_cpuinfo();
170             } elsif ($^O =~ /bsd|darwin|dragonfly/i) {
171 4         44 chomp($name = `sysctl -n machdep.cpu.brand_string 2>/dev/null`);
172 4 100       110 chomp($name = `sysctl -n hw.model 2>/dev/null`) unless $name;
173             } elsif ($^O =~ /mswin|mingw|msys|cygwin/i) {
174 1         5 $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       69 $name = $1 if $out =~ /:\s*(.*)/;
178             } elsif ($^O =~ /irix/i) {
179 2         8 my @out = grep {/CPU:/i} `hinv 2>/dev/null`;
  4         54  
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       82 $name = $1 if $out =~ /:\s*(?:")?(.*?)(?:")?\s*$/m;
184             } elsif ($^O =~ /hp-?ux/i) {
185 2         15 my $out = `machinfo`;
186 2 100       85 if ($out =~ /processor model:\s*\d*\s*(.+?)$/im) {
    50          
187 1         4 $name = $1;
188             } elsif ($out =~ /\s*\d*\s*(.+(?:MHz|GHz).+)$/m) {
189 1         5 $name = $1;
190             }
191             } elsif ($^O =~ /osf|solaris|sunos|svr5|sco/i) {
192 1         4 my $out = `kstat -p cpu_info`;
193 1 50       38 $name = $1 if $out =~ /:brand\s*(.*)$/m;
194             } else {
195 1         13 die "OS identifier '$^O' not recognized. Contact dkechag\@cpan.org to add support.";
196             }
197              
198 18 100       107 unless ($opt{raw}) {
199 17 100       98 $name =~ s/\s+/ /g if $name; # I don't like some systems giving excess whitespace.
200 17 100       66 $name =~ s/\((?:R|TM)\)//g if $name; # I don't like Intel's (R)s and (TM)s
201             }
202 18   100     138 return $name || "";
203             }
204              
205              
206             sub get_arch {
207 7 100   7 1 8421 return _uname_m() if $^O =~ /linux|android|bsd|darwin|dragonfly|gnu|osf|solaris|sunos|svr5|sco|hp-?ux/i;
208 4 100       23 return _uname_p() if $^O =~ /aix|irix/i;
209 3 100       14 return _getarch() if $^O =~ /haiku/i;
210 2 100       15 return $ENV{PROCESSOR_ARCHITECTURE} if $^O =~ /mswin|mingw|msys|cygwin/i;
211              
212 1         17 die "OS identifier '$^O' not recognized. Contact dkechag\@cpan.org to add support.";
213             }
214              
215             sub _solaris_cpu {
216 6     6   9 my $ncpu;
217 6 50       28 if (-x '/usr/sbin/psrinfo') {
218 0         0 my $count = grep {/on-?line/} `psrinfo 2>/dev/null`;
  0         0  
219 0 0       0 $ncpu = $count if $count;
220             } else {
221 6         1508 my @output = grep {/^NumCPU = \d+/} `uname -X 2>/dev/null`;
  22         129  
222 6 100       99 $ncpu = (split ' ', $output[0])[2] if @output;
223             }
224 6         29 return (undef, undef, $ncpu);
225             }
226              
227             sub _bsd_cpu {
228 7     7   16 my $prof = `system_profiler -detailLevel mini SPHardwareDataType SPSoftwareDataType 2>/dev/null`;
229 7 100       313 my $proc = $prof ? 1 : undef;
230 7 100 100     45 $proc = $1 if $prof && $prof =~ /Number of (?:Processors|CPUs): (\d+)/i;
231 7         29 chomp(my $cpus = `sysctl -n hw.logicalcpu 2>/dev/null`);
232 7 100       148 chomp($cpus = `sysctl -n hw.ncpu 2>/dev/null`) unless $cpus; # Old system fallback
233 7 100       67 return ($proc, undef, undef) unless $cpus;
234 5         12 chomp(my $cores = `sysctl -n hw.physicalcpu 2>/dev/null`);
235 5   66     76 $cores ||= $cpus;
236 5         24 return ($proc, $cores, $cpus);
237             }
238              
239             sub _linux_cpu {
240 9     9   22 my ($name, $phys, $cores, $cpus) = _proc_cpuinfo();
241 9         130 return $phys, $cores, $cpus;
242             }
243              
244             sub _aix_cpu {
245 10     10   15 my $ncpu;
246 10         24 my @output = `lparstat -i 2>/dev/null | grep "^Online Virtual CPUs"`;
247 10 100       270 if (@output) {
248 4         18 $output[0] =~ /(\d+)\n$/;
249 4 100       14 $ncpu = $1 if $1;
250             }
251 10 100       37 if (!$ncpu) {
252 8         16 @output = `pmcycles -m 2>/dev/null`;
253 8 100       118 if (@output) {
254 4         6 $ncpu = scalar @output;
255             } else {
256 4         9 @output = `lsdev -Cc processor -S Available 2>/dev/null`;
257 4 100       67 $ncpu = scalar @output if @output;
258             }
259             }
260 10         44 return (undef, undef, $ncpu);
261             }
262              
263             sub _haiku_cpu {
264 4     4   5 my $ncpu;
265 4         13 my @output = `sysinfo -cpu 2>/dev/null | grep "^CPU #"`;
266 4 100       101 $ncpu = scalar @output if @output;
267 4         17 return (undef, undef, $ncpu);;
268             }
269              
270             sub _hpux_cpu {
271 4     4   12 my $ncpu = grep { /^processor/ } `ioscan -fkC processor 2>/dev/null`;
  20         109  
272 4   100     59 return (undef, undef, $ncpu || undef);
273             }
274              
275             sub _irix_cpu {
276 4     4   5 my $ncpu;
277 4         10 my @out = grep {/\s+processors?$/i} `hinv -c processor 2>/dev/null`;
  12         107  
278 4 100       55 $ncpu = (split ' ', $out[0])[0] if @out;
279 4         19 return (undef, undef, $ncpu);;
280             }
281              
282             sub _gnu_cpu {
283 4     4   6 my $ncpu;
284 4         11 chomp(my @output = `nproc --all 2>/dev/null`);
285 4 100       95 $ncpu = $output[0] if @output;
286 4         17 return (undef, undef, $ncpu);;
287             }
288              
289             sub _proc_cpuinfo {
290 13     13   21 my (@physical, @cores, $phys, $cpus, $name);
291 13 100 66     201 if (-f '/proc/cpuinfo' && open my $fh, '<', '/proc/cpuinfo') {
292 10         4453 while (<$fh>) {
293 973 100       4353 $cpus++ if /^processor\s*:/i;
294 973 100       1653 push @physical, $1 if /^physical id\s*:\s*(\d+)/i;
295 973 100       1675 push @cores, $1 if /^cpu cores\s*:\s*(\d+)/i;
296 973 100       2629 $name = $1 if /^model name\s*:\s*(.*)/i;
297             }
298 10 100 100     118 return $name, undef, $cores[0], $cpus if !@physical && @cores;
299 8 100       25 @cores = (0) unless @cores;
300 8         13 my %hash;
301             $hash{$physical[$_]} = $_ < scalar(@cores) ? $cores[$_] : $cores[0]
302 8 50       76 for 0 .. $#physical;
303 8   100     39 my $phys = keys %hash || undef;
304 8   66     66 my $cores = sum(values %hash) || $cpus;
305 8         88 return $name, $phys, $cores, $cpus;
306             }
307 3         825 return;
308             }
309              
310             sub _uname_m {
311 5     5   12419 chomp( my $arch = `uname -m 2>/dev/null` );
312 5   100     188 return $arch || _uname_p();
313             }
314              
315             sub _uname_p {
316 3     3   8 chomp( my $arch = `uname -p 2>/dev/null` );
317 3         60 return $arch;
318             }
319              
320             sub _getarch {
321 1     1   5 chomp( my $arch = `getarch 2>/dev/null` );
322 1         46 return $arch;
323             }
324              
325             =head1 AUTHOR
326              
327             Dimitrios Kechagias, C<< >>
328              
329             =head1 BUGS
330              
331             Please report any bugs or feature requests to L.
332              
333             You can also submit PRs with fixes/enhancements directly.
334              
335             =head1 SUPPORT
336              
337             You can find documentation for this module with the perldoc command.
338              
339             perldoc System::CPU
340              
341             You can also look for information at:
342              
343             =over 4
344              
345             =item * GitHub
346              
347             L
348              
349             =item * Search CPAN
350              
351             L
352              
353             =back
354              
355             =head1 ACKNOWLEDGEMENTS
356              
357             Some code borrowed from L.
358              
359             =head1 LICENSE AND COPYRIGHT
360              
361             This software is copyright (c) 2023 by Dimitrios Kechagias.
362              
363             This is free software; you can redistribute it and/or modify it under
364             the same terms as the Perl 5 programming language system itself.
365              
366             =cut
367              
368             1; # End of System::CPU