File Coverage

blib/lib/System/Info/Linux.pm
Criterion Covered Total %
statement 177 204 86.7
branch 88 128 68.7
condition 44 74 59.4
subroutine 15 16 93.7
pod 11 11 100.0
total 335 433 77.3


line stmt bran cond sub pod time code
1             package System::Info::Linux;
2              
3 3     3   104094 use strict;
  3         17  
  3         87  
4 3     3   16 use warnings;
  3         6  
  3         79  
5              
6 3     3   15 use base "System::Info::Base";
  3         19  
  3         7938  
7              
8             our $VERSION = "0.052";
9              
10             =head1 NAME
11              
12             System::Info::Linux - Object for specific Linux info.
13              
14             =head1 DESCRIPTION
15              
16             =head2 $si->prepare_sysinfo
17              
18             Use os-specific tools to find out more about the system.
19              
20             =cut
21              
22             sub prepare_sysinfo {
23 107     107 1 1568 my $self = shift;
24 107         323 $self->SUPER::prepare_sysinfo;
25 107         345 $self->prepare_os;
26 107 50       402 $self->prepare_proc_cpuinfo or return;
27              
28 107         614 for ($self->get_cpu_type) {
29 107 100       451 m/arm/ and do { $self->linux_arm; last };
  3         27  
  3         6  
30 104 50       211 m/aarch64/ and do { $self->linux_arm; last };
  0         0  
  0         0  
31 104 100       282 m/ppc/ and do { $self->linux_ppc; last };
  1         23  
  1         3  
32 103 50       205 m/sparc/ and do { $self->linux_sparc; last };
  0         0  
  0         0  
33 103 100       244 m/s390x/ and do { $self->linux_s390x; last };
  2         35  
  2         17  
34             # default
35 101         257 $self->linux_generic;
36             }
37 107         282 return $self;
38             } # prepare_sysinfo
39              
40             =head2 $si->prepare_os
41              
42             Use os-specific tools to find out more about the operating system.
43              
44             =cut
45              
46             sub _file_info {
47 427     427   910 my ($file, $os) = @_;
48 427 50       13593 open my $fh, "<", $file or return;
49 427         15153 while (<$fh>) {
50 1778 100       5589 m/^\s*[;#]/ and next;
51 1766         2481 chomp;
52 1766 100       4999 m/\S/ or next;
53 1616         2968 s/^\s+//;
54 1616         2920 s/\s+$//;
55 1616 100       7703 if (my ($k, $v) = (m/^(.*\S)\s*=\s*(\S.*)$/)) {
56             # Having a value prevails over being defined
57 1234 100       2627 defined $os->{$k} and next;
58 1226         4580 $v =~ s/^"\s*(.*?)\s*"$/$1/;
59 1226 100       2869 $v =~ m{^["(]?undef(?:ined)?[")]$}i and $v = "undefined";
60 1226         2520 $os->{$k} = $v;
61 1226         4663 next;
62             }
63 382 100       951 m/^[12][0-9]{3}(?:,\s*[12][0-9]{3})*$/ and next; # Copyright years
64 380 100       3268 exists $os->{$_} or $os->{$_} = undef;
65             }
66 427         5583 close $fh;
67             } # _file_info
68              
69             sub _lsb_release {
70 107     107   194 my $os = shift;
71              
72 107 100       333 $ENV{SMOKE_USE_ETC} and return;
73              
74             $os->{DISTRIB_ID} || $os->{DISTRIB_RELEASE} || $os->{DISTRIB_CODENAME}
75 15 0 33     42 or return;
      0        
76              
77             #use DP;die DDumper $os;
78 15 50       11385 open my $ch, "lsb_release -a 2>&1 |" or return;
79 0         0 my %map = (
80             "LSB Version" => "don't care",
81             "Distributor ID" => "DISTRIB_ID",
82             "Description" => "DISTRIB_DESCRIPTION",
83             "Release" => "DISTRIB_RELEASE",
84             "Code" => "DISTRIB_CODENAME",
85             );
86 0         0 while (<$ch>) {
87 0         0 chomp;
88 0 0       0 m/^\s*(\S.*?)\s*:\s*(.*?)\s*$/ or next;
89 0 0 0     0 $os->{$map{$1} || $1} ||= $2 unless $2 eq "n/a";
      0        
90             }
91             } # _lsb_release
92              
93             sub prepare_os {
94 107     107 1 162 my $self = shift;
95              
96 107   100     348 my $etc = $ENV{SMOKE_USE_ETC} || "/etc";
97 890 100       10917 my @dist_file = grep { -f $_ && -s _ } map {
98 107 100       15404 -d $_ ? glob ("$_/*") : ($_)
  770         13525  
99             } glob ("$etc/*[-_][rRvV][eE][lLrR]*"), "$etc/issue",
100             "$etc.defaults/VERSION", "$etc/VERSION", "$etc/release";
101              
102 107         989 my $os = $self->_os;
103 107         220 my %os;
104             my $distro;
105 107         231 foreach my $df (@dist_file) {
106             # use "debian" out of /etc/debian-release
107 427 100 100     2037 unless (defined $distro or $df =~ m/\blsb-/) {
108 107         2162 ($distro = $df) =~ s{^$etc(?:\.defaults)?/}{}i;
109 107         717 $distro =~ s{[-_]?(?:release|version)\b}{}i;
110             }
111 427         1092 _file_info ($df, \%os);
112             }
113 107         523 _lsb_release (\%os);
114              
115 107 50       41197 keys %os or return;
116              
117 107         1035 foreach my $key (keys %os) {
118 1529         2397 my $KEY = uc $key;
119 1529 100       2755 defined $os{$key} or next;
120 1226 100       2458 exists $os{$KEY} or $os{$KEY} = $os{$key};
121             }
122              
123 107 100 66     519 if ($os{DISTRIB_DESCRIPTION}) {
    100 66        
    50 66        
    100 66        
    100          
    100          
124 26         161 $distro = $os{DISTRIB_DESCRIPTION};
125 26 100 100     814 $os{DISTRIB_CODENAME} && $distro !~ m{\b$os{DISTRIB_CODENAME}\b}i and
126             $distro .= " ($os{DISTRIB_CODENAME})";
127 26 100 100     865 if ($os{VERSION_ID} && $distro !~ m{\b$os{VERSION_ID}\b}i) {
    50 33        
128 1         4 $distro .= " $os{VERSION_ID}";
129             }
130             elsif ($os{DISTRIB_RELEASE} && $distro !~ m{\b$os{DISTRIB_RELEASE}\b}i) {
131 0         0 $distro .= " $os{DISTRIB_RELEASE}";
132             }
133             }
134             elsif ($os{PRETTY_NAME}) {
135 52         96 $distro = $os{PRETTY_NAME}; # "openSUSE 12.1 (Asparagus) (x86_64)"
136 52 100       114 if (my $vid = $os{VERSION_ID}) { # wheezy 7 => 7.2
137 51         89 my @rv;
138 51 100       1368 if (@rv = grep m{^$vid\.} => sort keys %os) {
139             # from /etc/debian_version
140 10 50       144 $rv[0] =~ m/^[0-9]+\.\w+$/ and
141             $distro =~ s/\b$vid\b/$rv[0]/;
142             }
143 51 100 66     1977 if (!@rv && defined $os{NAME} and # CentOS Linux 7 = CentOS Linux 7.1.1503
      100        
144             @rv = grep m{^$os{NAME} (?:(?:release|version)\s+)?$vid\.} => sort keys %os) {
145 13 50       212 if ($rv[0] =~ m/\s($vid\.[-.\w]+)/) {
146 13         40 my $vr = $1;
147 13         107 $distro =~ s/\s$vid\b/ $vr/;
148             }
149             }
150             }
151 52         177 $distro =~ s{\s*[-:/,]\s*Version\s*:?\s*}{ };
152 52         113 $distro =~ s/\)\s+\(\w+\)\s*$/)/; # remove architectural part
153 52         193 $distro =~ s/\s+\(?(?:i\d86|x86_64)\)?\s*$//; # i386 i486 i586 x86_64
154 52 100 100     579 $os{VERSION_ID} && $distro !~ m{\b$os{VERSION_ID}\b}i and
155             $distro .= " $os{VERSION_ID}";
156             }
157             elsif ($os{VERSION} && $os{NAME}) {
158 0         0 $distro = qq{$os{NAME} $os{VERSION}};
159             }
160             elsif ($os{VERSION} && $os{CODENAME}) {
161 1 50       42 if (my @welcome = grep s{^\s*Welcome\s+to\s+(\S*$distro\S*)\b.*}{$1}i => keys %os) {
162 1         4 $distro = $welcome[0];
163             }
164 1         5 $distro .= qq{ $os{VERSION}};
165 1 50       15 $distro =~ m/\b$os{CODENAME}\b/ or
166             $distro .= qq{ ($os{CODENAME})};
167             }
168             elsif ($os{MAJORVERSION} && defined $os{MINORVERSION}) {
169 9 50 33     228 -d "/usr/syno" || "@dist_file" =~ m{^\S*/VERSION$} and $distro .= "DSM";
170 9         31 $distro .= qq{ $os{MAJORVERSION}.$os{MINORVERSION}};
171 9 50       26 $os{BUILDNUMBER} and $distro .= qq{-$os{BUILDNUMBER}};
172 9 100       20 $os{SMALLFIXNUMBER} and $distro .= qq{-$os{SMALLFIXNUMBER}};
173             }
174             elsif ($os{DISTRIBVER} && exists $os{NETBSDSRCDIR}) {
175 2         9 (my $dv = $os{DISTRIBVER}) =~ tr{ ''"";}{}d;
176 2         8 $distro .= qq{ NetBSD $dv};
177             }
178             else {
179             # /etc/issue:
180             # Welcome to SUSE LINUX 10.0 "Prague" (i586) - Kernel \r (\l).
181             # Welcome to openSUSE 10.1 "Agama Lizard" (i586) - Kernel \r (\l).
182             # Welcome to openSUSE 10.2 (i586) - Kernel \r (\l).
183             # Welcome to openSUSE 10.2 "Basilisk Lizard" (X86-64) - Kernel \r (\l).
184             # Welcome to openSUSE 10.3 (i586) - Kernel \r (\l).
185             # Welcome to openSUSE 10.3 (X86-64) - Kernel \r (\l).
186             # Welcome to openSUSE 11.1 - Kernel \r (\l).
187             # Welcome to openSUSE 11.2 "Emerald" - Kernel \r (\l).
188             # Welcome to openSUSE 11.3 "Teal" - Kernel \r (\l).
189             # Welcome to openSUSE 11.4 "Celadon" - Kernel \r (\l).
190             # Welcome to openSUSE 12.1 "Asparagus" - Kernel \r (\l).
191             # Welcome to openSUSE 12.2 "Mantis" - Kernel \r (\l).
192             # Welcome to openSUSE 12.3 "Dartmouth" - Kernel \r (\l).
193             # Welcome to openSUSE 13.1 "Bottle" - Kernel \r (\l).
194             # Welcome to openSUSE 13.2 "Harlequin" - Kernel \r (\l).
195             # Welcome to openSUSE Leap 42.1 - Kernel \r (\l).
196             # Welcome to openSUSE 20151218 "Tumbleweed" - Kernel \r (\l).
197             # Welcome to SUSE Linux Enterprise Server 11 SP1 for VMware (x86_64) - Kernel \r (\l).
198             # Ubuntu 10.04.4 LTS \n \l
199             # Debian GNU/Linux wheezy/sid \n \l
200             # Debian GNU/Linux 6.0 \n \l
201             # CentOS release 6.4 (Final)
202             # /etc/redhat-release:
203             # CentOS release 5.7 (Final)
204             # CentOS release 6.4 (Final)
205             # Red Hat Enterprise Linux ES release 4 (Nahant Update 2)
206             # /etc/debian_version:
207             # 6.0.4
208             # wheezy/sid
209             # squeeze/sid
210              
211 17         84 my @key = sort keys %os;
212 17         142 s/\s*\\[rln].*// for @key;
213              
214 17         94 my @vsn = grep m/^[0-9.]+$/ => @key;
215             #$self->{__X__} = { os => \%os, key => \@key, vsn => \@vsn };
216              
217 17 100 0     157 if (my @welcome = grep s{^\s*Welcome\s+to\s+}{}i => @key) {
    100          
    50          
    0          
218 3         21 ($distro = $welcome[0]) =~ s/"([^"]+)"/($1)/;
219             }
220             elsif (my @rel = grep m{\brelease\b}i => @key) {
221 12 50 66     49 @rel > 1 && $rel[0] =~ m/^Enterprise Linux Enterprise/
      66        
222             && $rel[1] =~ m/^Oracle Linux/ and shift @rel;
223 12         27 $distro = $rel[0];
224 12         59 $distro =~ s/ *release//;
225 12         32 $distro =~ s/Red Hat Enterprise Linux/RHEL/; # Too long for subject
226             # RHEL ES 4 (Nahant Update 2) => RHEL Server 4.2 (Nahant)
227 12         29 $distro =~ s/^RHEL ES (\d+)\s+(.*)\s+Update\s+(\d+)/RHEL Server $1.$3 $2/;
228             }
229             elsif ( my @lnx = grep m{\bLinux\b}i => @key ) {
230 2         5 $distro = $lnx[0];
231             }
232             elsif ( $distro && @vsn ) {
233 0         0 $distro .= "-$vsn[0]";
234             }
235             else {
236 0         0 $distro = $key[0];
237             }
238 17         60 $distro =~ s/\s+-\s+Kernel.*//i;
239             }
240 107 50       967 if ($distro =~ s/^\s*(.*\S)\s*$/$1/) {
241 107         584 $self->{__distro} = $distro;
242 107         493 $os .= " [$distro]";
243             }
244 107         272 $self->{__release_info} = \%os;
245 107         623 $self->{__os} = $os;
246             } # prepare_os
247              
248             =head2 $si->linux_generic
249              
250             Check C for these keys:
251              
252             =over
253              
254             =item "processor" (count occurrence for __cpu_count)
255              
256             =item "model name" (part of __cpu)
257              
258             =item "vendor_id" (part of __cpu)
259              
260             =item "cpu mhz" (part of __cpu)
261              
262             =item "cpu cores" (add values to add to __cpu_count)
263              
264             =back
265              
266             =cut
267              
268             sub linux_generic {
269 101     101 1 153 my $self = shift;
270              
271 101   100     664 my $n_phys_id = $self->count_unique_in_cpuinfo (qr/^physical id\s+:/) || 0;
272 101   100     470 my $n_core_id = $self->count_unique_in_cpuinfo (qr/^core id\s+:/) || 0;
273 101   50     450 my $n_processor = $self->count_unique_in_cpuinfo (qr/^processor\s+:/) || 0;
274 101   66     315 my $n_cpu = $n_phys_id || $n_core_id || $n_processor;
275              
276             # ::diag"Np: $n_phys_id, NC: $n_core_id, NP: $n_processor, NC: $n_cpu";
277 101         319 $self->{__cpu_count} = $n_cpu;
278              
279 101         313 my @parts = ("model name", "vendor_id", "cpu mhz");
280 101         173 my %info = map { ($_ => $self->from_cpuinfo ($_)) } @parts;
  303         709  
281 101         247 $self->{__cpu} = sprintf "%s (%s %.0fMHz)", map { $info{$_} } @parts;
  303         1225  
282              
283 101 100       311 if ($n_phys_id) {
284             $n_processor > $n_phys_id and
285 100 100       245 $self->{__cpu_count} .= " [$n_processor cores]";
286 100         413 return;
287             }
288 1 50       4 if ($n_core_id) {
289             $n_processor > $n_core_id and
290 0 0       0 $self->{__cpu_count} .= " [$n_processor cores]";
291 0         0 return;
292             }
293              
294 1         3 my $n_cores = 0;
295 1         18 my $core_id = 0;
296 1         3 my %cores;
297 1         9 for my $cores (grep m/(cpu cores|core id)\s*:\s*\d+/ => $self->_proc_cpuinfo) {
298 0 0       0 my ($tag, $count) = $cores =~ m/^(.*\S)\s*:\s*(\d+)/ or next;
299 0 0       0 if ($tag eq "core id") {
300 0         0 $core_id = $count;
301             }
302             else {
303 0         0 $cores{$core_id} = $count;
304             }
305             }
306 1         10 $n_cores += $cores{$_} for keys %cores;
307              
308 1 50       10 $n_cores > $n_cpu and $self->{__cpu_count} .= " [$n_cores cores]";
309             } # _linux_generic
310              
311             =head2 $si->linux_arm
312              
313             Check C for these keys:
314              
315             =over
316              
317             =item "processor" (count occurrence for __cpu_count)
318              
319             =item "Processor" (part of __cpu)
320              
321             =item "BogoMIPS" (part of __cpu)
322              
323             =back
324              
325             =cut
326              
327             sub linux_arm {
328 3     3 1 12 my $self = shift;
329              
330 3         53 $self->{__cpu_count} = $self->count_in_cpuinfo (qr/^processor\s+:\s+/i);
331              
332 3   66     24 my $cpu = $self->from_cpuinfo ("Processor") ||
333             $self->from_cpuinfo ("Model[_ ]name");
334 3         17 my $bogo = $self->from_cpuinfo ("BogoMIPS");
335 3         34 my $mhz = 100 * int (($bogo + 50) / 100);
336 3         39 $cpu =~ s/\s+/ /g;
337 3 50       23 $mhz and $cpu .= " ($mhz MHz)";
338 3         10 $self->{__cpu} = $cpu;
339             } # _linux_arm
340              
341             =head2 $si->linux_ppc
342              
343             Check C for these keys:
344              
345             =over
346              
347             =item "processor" (count occurrence for __cpu_count)
348              
349             =item "cpu" (part of __cpu)
350              
351             =item "machine" (part of __cpu)
352              
353             =item "clock" (part of __cpu)
354              
355             =item "detected" (alters machine if present)
356              
357             =back
358              
359             =cut
360              
361             sub linux_ppc {
362 1     1 1 8 my $self = shift;
363              
364 1         24 $self->{__cpu_count} = $self->count_in_cpuinfo (qr/^processor\s+:\s+/);
365              
366 1         18 my @parts = qw( cpu machine clock );
367 1         4 my %info = map { ($_ => $self->from_cpuinfo ($_)) } @parts;
  3         78  
368 1 50       13 if ($info{detected} = $self->from_cpuinfo ("detected as")){
369 1         18 $info{detected} =~ s/.*(\b.+Mac G\d).*/$1/;
370 1         10 $info{machine} = $info{detected};
371             }
372              
373 1         7 $self->{__cpu} = sprintf "%s %s (%s)", map { $info{$_} } @parts;
  3         17  
374             } # linux_ppc
375              
376             =head2 $si->linux_sparc
377              
378             Check C for these keys:
379              
380             =over
381              
382             =item "processor" (count occurrence for __cpu_count)
383              
384             =item "cpu" (part of __cpu)
385              
386             =item "Cpu0ClkTck" (part of __cpu)
387              
388             =back
389              
390             =cut
391              
392             sub linux_sparc {
393 0     0 1 0 my $self = shift;
394              
395 0         0 $self->{__cpu_count} = $self->from_cpuinfo ("ncpus active");
396              
397 0         0 my @parts = qw( cpu Cpu0ClkTck );
398 0         0 my %info = map { ($_ => $self->from_cpuinfo ($_)) } @parts;
  0         0  
399 0         0 my $cpu = $info{cpu};
400             $info{Cpu0ClkTck} and
401 0 0       0 $cpu .= sprintf " (%.0fMHz)", hex ($info{Cpu0ClkTck}) / 1_000_000;
402 0         0 $self->{__cpu} = $cpu;
403             } # linux_sparc
404              
405             =head2 $si->linux_s390x
406              
407             Check C for these keys:
408              
409             =over
410              
411             =item "processor" (count occurrence for __cpu_count)
412              
413             =item "Processor" (part of __cpu)
414              
415             =item "BogoMIPS" (part of __cpu)
416              
417             =back
418              
419             =cut
420              
421             sub linux_s390x {
422 2     2 1 16 my $self = shift;
423              
424 2         45 $self->{__cpu_count} = $self->count_in_cpuinfo (qr/^processor\s+\d+:\s+/i);
425              
426 2   33     24 my $cpu = $self->from_cpuinfo ("vendor_id") ||
427             $self->from_cpuinfo ("Processor") ||
428             $self->from_cpuinfo ("Model[_ ]name");
429 2         15 my $bogo = $self->from_cpuinfo (qr{BogoMIPS(?:\s*per[ _]CPU)?}i);
430 2         31 my $mhz = 100 * int (($bogo + 50) / 100);
431 2         16 $cpu =~ s/\s+/ /g;
432 2 50       19 $mhz and $cpu .= " ($mhz MHz)";
433 2         11 $self->{__cpu} = $cpu;
434             } # _linux_s390x
435              
436             =head2 $si->prepare_proc_cpuinfo
437              
438             Read the complete C<< /proc/cpuinfo >>.
439              
440             =cut
441              
442             sub prepare_proc_cpuinfo {
443 107     107 1 186 my $self = shift;
444              
445 107 50       3601 if (open my $pci, "<", "/proc/cpuinfo") {
446 107         40288 chomp (my @pci = <$pci>);
447 107         110927 s/[\s\xa0]+/ /g for @pci;
448 107         7999 s/ $// for @pci;
449 107         359 $self->{__proc_cpuinfo} = \@pci;
450 107         1486 close $pci;
451 107         927 return 1;
452             }
453             } # prepare_proc_cpuinfo
454              
455             =head2 $si->count_in_cpuinfo ($regex)
456              
457             Returns the number of lines $regex matches for.
458              
459             =cut
460              
461             sub count_in_cpuinfo {
462 6     6 1 35 my ($self, $regex) = @_;
463              
464 6         109 return scalar grep /$regex/, $self->_proc_cpuinfo;
465             } # count_in_cpuinfo
466              
467             =head2 $si->count_unique_in_cpuinfo ($regex)
468              
469             Returns the number of lines $regex matches for.
470              
471             =cut
472              
473             sub count_unique_in_cpuinfo {
474 303     303 1 635 my ($self, $regex) = @_;
475              
476 303         1353 my %match = map { $_ => 1 } grep /$regex/ => $self->_proc_cpuinfo;
  4723         8548  
477 303         6433 return scalar keys %match;
478             } # count_unique_in_cpuinfo
479              
480             =head2 $si->from_cpuinfo ($key)
481              
482             Returns the first value of that key in C<< /proc/cpuinfo >>.
483              
484             =cut
485              
486             sub from_cpuinfo {
487 318     318 1 643 my ($self, $key) = @_;
488              
489 318         1495 my ($first) = grep m/^\s*$key\s*[:=]\s*/i => $self->_proc_cpuinfo;
490 318 50       5879 defined $first or $first = "";
491 318         8229 $first =~ s/^\s*$key\s*[:=]\s*//i;
492 318         1822 return $first;
493             } # from_cpuinfo
494              
495             1;
496              
497             __END__