File Coverage

blib/lib/System/CPU.pm
Criterion Covered Total %
statement 114 117 97.4
branch 94 100 94.0
condition 20 24 83.3
subroutine 20 20 100.0
pod 4 4 100.0
total 252 265 95.0


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