File Coverage

blib/lib/System/Info/Linux.pm
Criterion Covered Total %
statement 188 218 86.2
branch 96 140 68.5
condition 47 77 61.0
subroutine 15 16 93.7
pod 11 11 100.0
total 357 462 77.2


line stmt bran cond sub pod time code
1             package System::Info::Linux;
2              
3 3     3   98029 use strict;
  3         6  
  3         110  
4 3     3   17 use warnings;
  3         5  
  3         171  
5              
6 3     3   58 use base "System::Info::Base";
  3         5  
  3         14536  
7              
8             our $VERSION = "0.054";
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 134     134 1 290 my $self = shift;
24 134         599 $self->SUPER::prepare_sysinfo;
25 134         620 $self->prepare_os;
26 134 50       722 $self->prepare_proc_cpuinfo or return;
27              
28 134         852 for ($self->get_cpu_type) {
29 134 100       687 m/arm/ and do { $self->linux_arm; last };
  3         33  
  3         7  
30 131 50       414 m/aarch64/ and do { $self->linux_arm; last };
  0         0  
  0         0  
31 131 100       502 m/ppc/ and do { $self->linux_ppc; last };
  1         15  
  1         4  
32 130 50       387 m/sparc/ and do { $self->linux_sparc; last };
  0         0  
  0         0  
33 130 100       400 m/s390x/ and do { $self->linux_s390x; last };
  2         26  
  2         6  
34             # default
35 128         398 $self->linux_generic;
36             }
37 134         591 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 544     544   1403 my ($file, $os) = @_;
48 544 50       24493 open my $fh, "<", $file or return;
49 544         30837 while (<$fh>) {
50 2428 100       8771 m/^\s*[;#]/ and next;
51 2416         4004 chomp;
52 2416 100       7974 m/\S/ or next;
53 2232         4918 s/^\s+//;
54 2232         5366 s/\s+$//;
55 2232 100       17540 if (my ($k, $v) = (m/^\s*(.*\S)\s*=\s*(\S.*?)\s*$/)) {
56             # Having a value prevails over being defined
57 1738 100       4375 defined $os->{$k} and next;
58 1726         9465 $v =~ s/^"\s*(.*?)\s*"$/$1/;
59 1726         3933 $v =~ s/^'\s*(.*?)\s*'$/$1/;
60 1726 100       7562 $v =~ m{^["(]?undef(?:ined)?[")]$}i and $v = "undefined";
61 1726         4699 $os->{$k} = $v;
62 1726         7636 next;
63             }
64 494 100       1482 m/^[12][0-9]{3}(?:,\s*[12][0-9]{3})*(?:\s*,\s*)*$/ and next; # Copyright years
65 491 100       1108 m/^[12][0-9]{3}(?:,\s*[12][0-9]{3})*.*All rights reserved/i and next; # Copyright years
66 490 100       5251 exists $os->{$_} or $os->{$_} = undef;
67             }
68 544         8969 close $fh;
69             } # _file_info
70              
71             sub _lsb_release {
72 134     134   262 my $os = shift;
73              
74 134 100       642 $ENV{SMOKE_USE_ETC} and return;
75              
76             $os->{DISTRIB_ID} || $os->{DISTRIB_RELEASE} || $os->{DISTRIB_CODENAME}
77 15 0 33     49 or return;
      0        
78              
79             #use DP;die DDumper $os;
80 15 50       24963 open my $ch, "lsb_release -a 2>&1 |" or return;
81 0         0 my %map = (
82             "LSB Version" => "don't care",
83             "Distributor ID" => "DISTRIB_ID",
84             "Description" => "DISTRIB_DESCRIPTION",
85             "Release" => "DISTRIB_RELEASE",
86             "Code" => "DISTRIB_CODENAME",
87             );
88 0         0 while (<$ch>) {
89 0         0 chomp;
90 0 0       0 m/^\s*(\S.*?)\s*:\s*(.*?)\s*$/ or next;
91 0 0 0     0 $os->{$map{$1} || $1} ||= $2 unless $2 eq "n/a";
      0        
92             }
93             } # _lsb_release
94              
95             sub prepare_os {
96 134     134 1 1533 my $self = shift;
97              
98 134   100     666 my $etc = $ENV{SMOKE_USE_ETC} || "/etc";
99 1125 100       13541 my @dist_file = grep { -f $_ && -s _ } map {
100 134 100       25569 -d $_ ? glob ("$_/*") : ($_)
  974         25012  
101             } glob ("$etc/*[-_][rRvV][eE][lLrR]*"), "$etc/issue",
102             "$etc.defaults/VERSION", "$etc/VERSION", "$etc/release";
103              
104 134         1572 my $os = $self->_os;
105 134         351 my %os;
106             my $distro;
107 134         317 foreach my $df (@dist_file) {
108             # use "debian" out of /etc/debian-release
109 544 100 100     2844 unless (defined $distro or $df =~ m/\blsb-/) {
110 134         5019 ($distro = $df) =~ s{^$etc(?:\.defaults)?/}{}i;
111 134         1151 $distro =~ s{[-_]?(?:release|version)\b}{}i;
112             }
113 544         1481 _file_info ($df, \%os);
114             }
115 134         634 _lsb_release (\%os);
116              
117 134 50       48585 keys %os or return;
118              
119 134         1417 foreach my $key (keys %os) {
120 2116         3614 my $KEY = uc $key;
121 2116 100       4505 defined $os{$key} or next;
122 1726 100       5063 exists $os{$KEY} or $os{$KEY} = $os{$key};
123             }
124              
125 134 100 100     1219 if ($os{DISTRIB_DESCRIPTION}) {
    100 66        
    100 66        
    50 66        
    100 66        
    100          
    100          
126 28         258 $distro = $os{DISTRIB_DESCRIPTION};
127 28 100 100     942 $os{DISTRIB_CODENAME} && $distro !~ m{\b$os{DISTRIB_CODENAME}\b}i and
128             $distro .= " ($os{DISTRIB_CODENAME})";
129 28 100 100     849 if ($os{VERSION_ID} && $distro !~ m{\b$os{VERSION_ID}\b}i) {
    50 33        
130 1         3 $distro .= " $os{VERSION_ID}";
131             }
132             elsif ($os{DISTRIB_RELEASE} && $distro !~ m{\b$os{DISTRIB_RELEASE}\b}i) {
133 0         0 $distro .= " $os{DISTRIB_RELEASE}";
134             }
135             }
136             elsif ($os{DISTRIBVER} && defined $os{NETBSD_OFFICIAL_RELEASE}) { # NetBSD 9
137 1         54 my @k = grep m/^netbsd\s+\d/i => keys %os;
138 1 50       20 ($distro = @k == 1 ? $k[0] : "NetBSD $os{DISTRIBVER}") =~ s{/.*}{};
139             }
140             elsif ($os{PRETTY_NAME}) {
141 74         209 $distro = $os{PRETTY_NAME}; # "openSUSE 12.1 (Asparagus) (x86_64)"
142 74 100       264 if (my $vid = $os{VERSION_ID}) { # wheezy 7 => 7.2
143 73         166 my @rv;
144 73 100       3492 if (@rv = grep m{^$vid\.} => sort keys %os) {
145             # from /etc/debian_version
146 14 50       325 $rv[0] =~ m/^[0-9]+\.\w+$/ and
147             $distro =~ s/\b$vid\b/$rv[0]/;
148             }
149 73 100 66     5645 if (!@rv && defined $os{NAME} and # CentOS Linux 7 = CentOS Linux 7.1.1503
      100        
150             @rv = grep m{^$os{NAME} (?:(?:release|version)\s+)?$vid\.} => sort keys %os) {
151 13 50       1571 if ($rv[0] =~ m/\s($vid\.[-.\w]+)/) {
152 13         68 my $vr = $1;
153 13         177 $distro =~ s/\s$vid\b/ $vr/;
154             }
155             }
156             }
157 74         371 $distro =~ s{\s*[-:/,]\s*Version\s*:?\s*}{ };
158 74         220 $distro =~ s/\)\s+\(\w+\)\s*$/)/; # remove architectural part
159 74         548 $distro =~ s/\s+\(?(?:i\d86|x86_64)\)?\s*$//; # i386 i486 i586 x86_64
160 74 100 100     1517 $os{VERSION_ID} && $distro !~ m{\b$os{VERSION_ID}\b}i and
161             $distro .= " $os{VERSION_ID}";
162             }
163             elsif ($os{VERSION} && $os{NAME}) {
164 0         0 $distro = qq{$os{NAME} $os{VERSION}};
165             }
166             elsif ($os{VERSION} && $os{CODENAME}) {
167 1 50       127 if (my @welcome = grep s{^\s*Welcome\s+to\s+(\S*$distro\S*)\b.*}{$1}i => keys %os) {
168 1         3 $distro = $welcome[0];
169             }
170 1         5 $distro .= qq{ $os{VERSION}};
171 1 50       18 $distro =~ m/\b$os{CODENAME}\b/ or
172             $distro .= qq{ ($os{CODENAME})};
173             }
174             elsif ($os{MAJORVERSION} && defined $os{MINORVERSION}) {
175 11 50 33     653 -d "/usr/syno" || "@dist_file" =~ m{^\S*/VERSION$} and $distro .= "DSM";
176 11         46 $distro .= qq{ $os{MAJORVERSION}.$os{MINORVERSION}};
177 11 50       44 $os{BUILDNUMBER} and $distro .= qq{-$os{BUILDNUMBER}};
178 11 100       39 $os{SMALLFIXNUMBER} and $distro .= qq{-$os{SMALLFIXNUMBER}};
179             }
180             elsif ($os{DISTRIBVER} && exists $os{NETBSDSRCDIR}) {
181 2         10 (my $dv = $os{DISTRIBVER}) =~ tr{ ''"";}{}d;
182 2         6 $distro .= qq{ NetBSD $dv};
183             }
184             else {
185             # /etc/issue:
186             # Welcome to SUSE LINUX 10.0 "Prague" (i586) - Kernel \r (\l).
187             # Welcome to openSUSE 10.1 "Agama Lizard" (i586) - Kernel \r (\l).
188             # Welcome to openSUSE 10.2 (i586) - Kernel \r (\l).
189             # Welcome to openSUSE 10.2 "Basilisk Lizard" (X86-64) - Kernel \r (\l).
190             # Welcome to openSUSE 10.3 (i586) - Kernel \r (\l).
191             # Welcome to openSUSE 10.3 (X86-64) - Kernel \r (\l).
192             # Welcome to openSUSE 11.1 - Kernel \r (\l).
193             # Welcome to openSUSE 11.2 "Emerald" - Kernel \r (\l).
194             # Welcome to openSUSE 11.3 "Teal" - Kernel \r (\l).
195             # Welcome to openSUSE 11.4 "Celadon" - Kernel \r (\l).
196             # Welcome to openSUSE 12.1 "Asparagus" - Kernel \r (\l).
197             # Welcome to openSUSE 12.2 "Mantis" - Kernel \r (\l).
198             # Welcome to openSUSE 12.3 "Dartmouth" - Kernel \r (\l).
199             # Welcome to openSUSE 13.1 "Bottle" - Kernel \r (\l).
200             # Welcome to openSUSE 13.2 "Harlequin" - Kernel \r (\l).
201             # Welcome to openSUSE Leap 42.1 - Kernel \r (\l).
202             # Welcome to openSUSE 20151218 "Tumbleweed" - Kernel \r (\l).
203             # Welcome to SUSE Linux Enterprise Server 11 SP1 for VMware (x86_64) - Kernel \r (\l).
204             # Ubuntu 10.04.4 LTS \n \l
205             # Debian GNU/Linux wheezy/sid \n \l
206             # Debian GNU/Linux 6.0 \n \l
207             # CentOS release 6.4 (Final)
208             # /etc/redhat-release:
209             # CentOS release 5.7 (Final)
210             # CentOS release 6.4 (Final)
211             # Red Hat Enterprise Linux ES release 4 (Nahant Update 2)
212             # /etc/debian_version:
213             # 6.0.4
214             # wheezy/sid
215             # squeeze/sid
216              
217 17         104 my @key = sort keys %os;
218 17         185 s/\s*\\[rln].*// for @key;
219              
220 17         116 my @vsn = grep m/^[0-9.]+$/ => @key;
221             #$self->{__X__} = { os => \%os, key => \@key, vsn => \@vsn };
222              
223 17 100 0     235 if (my @welcome = grep s{^\s*Welcome\s+to\s+}{}i => @key) {
    100          
    50          
    0          
224 3         22 ($distro = $welcome[0]) =~ s/"([^"]+)"/($1)/;
225             }
226             elsif (my @rel = grep m{\brelease\b}i => @key) {
227 12 50 66     56 @rel > 1 && $rel[0] =~ m/^Enterprise Linux Enterprise/
      66        
228             && $rel[1] =~ m/^Oracle Linux/ and shift @rel;
229 12         33 $distro = $rel[0];
230 12         98 $distro =~ s/ *release//;
231 12         41 $distro =~ s/Red Hat Enterprise Linux/RHEL/; # Too long for subject
232             # RHEL ES 4 (Nahant Update 2) => RHEL Server 4.2 (Nahant)
233 12         39 $distro =~ s/^RHEL ES (\d+)\s+(.*)\s+Update\s+(\d+)/RHEL Server $1.$3 $2/;
234             }
235             elsif ( my @lnx = grep m{\bLinux\b}i => @key ) {
236 2         7 $distro = $lnx[0];
237             }
238             elsif ( $distro && @vsn ) {
239 0         0 $distro .= "-$vsn[0]";
240             }
241             else {
242 0         0 $distro = $key[0];
243             }
244 17         69 $distro =~ s/\s+-\s+Kernel.*//i;
245             }
246 134 50       2390 if ($distro =~ s/^\s*(.*\S)\s*$/$1/) {
247 134         574 $self->{__distro} = $distro;
248 134         442 $os .= " [$distro]";
249             }
250 134         434 $self->{__release_info} = \%os;
251 134         884 $self->{__os} = $os;
252             } # prepare_os
253              
254             =head2 $si->linux_generic
255              
256             Check C for these keys:
257              
258             =over
259              
260             =item "processor" (count occurrence for __cpu_count)
261              
262             =item "model name" (part of __cpu)
263              
264             =item "vendor_id" (part of __cpu)
265              
266             =item "cpu mhz" (part of __cpu)
267              
268             =item "cpu cores" (add values to add to __cpu_count)
269              
270             =back
271              
272             =cut
273              
274             sub linux_generic {
275 128     128 1 259 my $self = shift;
276              
277 128   100     1243 my $n_phys_id = $self->count_unique_in_cpuinfo (qr/^physical id\s+:/) || 0;
278 128   100     932 my $n_core_id = $self->count_unique_in_cpuinfo (qr/^core id\s+:/) || 0;
279 128   50     771 my $n_processor = $self->count_unique_in_cpuinfo (qr/^processor\s+:/) || 0;
280 128   66     671 my $n_cpu = $n_phys_id || $n_core_id || $n_processor;
281              
282             # ::diag"Np: $n_phys_id, NC: $n_core_id, NP: $n_processor, NC: $n_cpu";
283 128         496 $self->{__cpu_count} = $n_cpu;
284              
285 128         257 { my @tags = ("model name", "vendor_id", "cpu mhz");
  128         424  
286 128         279 my %info = map { ($_ => $self->from_cpuinfo ($_)) } @tags;
  384         1343  
287 128 50       606 unless (defined $info{$tags[0]}) {
288             # riscv64 -> rv64imafdc
289 0         0 $info{$tags[0]} = $self->from_cpuinfo ("isa");
290             }
291 128 50       1998 $info{$tags[2]} and $info{$tags[2]} = sprintf "%.0fMHz", $info{$tags[2]};
292 128         287 my @cpui;
293 128 50       484 if ($info{$tags[0]}) {
294 128         460 push @cpui => $info{shift @tags};
295 128         452 push @cpui => "(".(join " " => grep { length } @info{@tags}).")";
  256         1613  
296             }
297             else {
298 0         0 push @cpui => grep { length } @info{@tags};
  0         0  
299             }
300 128         854 $self->{__cpu} = join " " => @cpui;
301             }
302              
303 128 100       459 if ($n_phys_id) {
304             $n_processor > $n_phys_id and
305 127 100       427 $self->{__cpu_count} .= " [$n_processor cores]";
306 127         501 return;
307             }
308 1 50       3 if ($n_core_id) {
309             $n_processor > $n_core_id and
310 0 0       0 $self->{__cpu_count} .= " [$n_processor cores]";
311 0         0 return;
312             }
313              
314 1         19 my $n_cores = 0;
315 1         3 my $core_id = 0;
316 1         3 my %cores;
317 1         9 for my $cores (grep m/(cpu cores|core id)\s*:\s*\d+/ => $self->_proc_cpuinfo) {
318 0 0       0 my ($tag, $count) = $cores =~ m/^(.*\S)\s*:\s*(\d+)/ or next;
319 0 0       0 if ($tag eq "core id") {
320 0         0 $core_id = $count;
321             }
322             else {
323 0         0 $cores{$core_id} = $count;
324             }
325             }
326 1         7 $n_cores += $cores{$_} for keys %cores;
327              
328 1 50       6 $n_cores > $n_cpu and $self->{__cpu_count} .= " [$n_cores cores]";
329             } # _linux_generic
330              
331             =head2 $si->linux_arm
332              
333             Check C for these keys:
334              
335             =over
336              
337             =item "processor" (count occurrence for __cpu_count)
338              
339             =item "Processor" (part of __cpu)
340              
341             =item "BogoMIPS" (part of __cpu)
342              
343             =back
344              
345             =cut
346              
347             sub linux_arm {
348 3     3 1 40 my $self = shift;
349              
350 3         60 $self->{__cpu_count} = $self->count_in_cpuinfo (qr/^processor\s+:\s+/i);
351              
352 3   66     35 my $cpu = $self->from_cpuinfo ("Processor") ||
353             $self->from_cpuinfo ("Model[_ ]name");
354 3         15 my $bogo = $self->from_cpuinfo ("BogoMIPS");
355 3         39 my $mhz = 100 * int (($bogo + 50) / 100);
356 3         31 $cpu =~ s/\s+/ /g;
357 3 50       23 $mhz and $cpu .= " ($mhz MHz)";
358 3         15 $self->{__cpu} = $cpu;
359             } # _linux_arm
360              
361             =head2 $si->linux_ppc
362              
363             Check C for these keys:
364              
365             =over
366              
367             =item "processor" (count occurrence for __cpu_count)
368              
369             =item "cpu" (part of __cpu)
370              
371             =item "machine" (part of __cpu)
372              
373             =item "clock" (part of __cpu)
374              
375             =item "detected" (alters machine if present)
376              
377             =back
378              
379             =cut
380              
381             sub linux_ppc {
382 1     1 1 5 my $self = shift;
383              
384 1         24 $self->{__cpu_count} = $self->count_in_cpuinfo (qr/^processor\s+:\s+/);
385              
386 1         10 my @tags = qw( cpu machine clock );
387 1         6 my %info = map { ($_ => $self->from_cpuinfo ($_)) } @tags;
  3         15  
388 1 50       9 if ($info{detected} = $self->from_cpuinfo ("detected as")){
389 1         14 $info{detected} =~ s/.*(\b.+Mac G\d).*/$1/;
390 1         4 $info{machine} = $info{detected};
391             }
392              
393 1         7 $self->{__cpu} = sprintf "%s %s (%s)", map { $info{$_} } @tags;
  3         17  
394             } # linux_ppc
395              
396             =head2 $si->linux_sparc
397              
398             Check C for these keys:
399              
400             =over
401              
402             =item "processor" (count occurrence for __cpu_count)
403              
404             =item "cpu" (part of __cpu)
405              
406             =item "Cpu0ClkTck" (part of __cpu)
407              
408             =back
409              
410             =cut
411              
412             sub linux_sparc {
413 0     0 1 0 my $self = shift;
414              
415 0         0 $self->{__cpu_count} = $self->from_cpuinfo ("ncpus active");
416              
417 0         0 my @tags = qw( cpu Cpu0ClkTck );
418 0         0 my %info = map { ($_ => $self->from_cpuinfo ($_)) } @tags;
  0         0  
419 0         0 my $cpu = $info{cpu};
420             $info{Cpu0ClkTck} and
421 0 0       0 $cpu .= sprintf " (%.0fMHz)", hex ($info{Cpu0ClkTck}) / 1_000_000;
422 0         0 $self->{__cpu} = $cpu;
423             } # linux_sparc
424              
425             =head2 $si->linux_s390x
426              
427             Check C for these keys:
428              
429             =over
430              
431             =item "processor" (count occurrence for __cpu_count)
432              
433             =item "Processor" (part of __cpu)
434              
435             =item "BogoMIPS" (part of __cpu)
436              
437             =back
438              
439             =cut
440              
441             sub linux_s390x {
442 2     2 1 8 my $self = shift;
443              
444 2         39 $self->{__cpu_count} = $self->count_in_cpuinfo (qr/^processor\s+\d+:\s+/i);
445              
446 2   33     27 my $cpu = $self->from_cpuinfo ("vendor_id") ||
447             $self->from_cpuinfo ("Processor") ||
448             $self->from_cpuinfo ("Model[_ ]name");
449 2         17 my $bogo = $self->from_cpuinfo (qr{BogoMIPS(?:\s*per[ _]CPU)?}i);
450 2         43 my $mhz = 100 * int (($bogo + 50) / 100);
451 2         10 $cpu =~ s/\s+/ /g;
452 2 50       20 $mhz and $cpu .= " ($mhz MHz)";
453 2         14 $self->{__cpu} = $cpu;
454             } # _linux_s390x
455              
456             =head2 $si->prepare_proc_cpuinfo
457              
458             Read the complete C<< /proc/cpuinfo >>.
459              
460             =cut
461              
462             sub prepare_proc_cpuinfo {
463 134     134 1 326 my $self = shift;
464              
465 134 50       6516 if (open my $pci, "<", "/proc/cpuinfo") {
466 134         71757 chomp (my @pci = <$pci>);
467 134         230555 s/[\s\xa0]+/ /g for @pci;
468 134         13534 s/ $// for @pci;
469 134         788 $self->{__proc_cpuinfo} = \@pci;
470 134         2430 close $pci;
471 134         1292 return 1;
472             }
473             } # prepare_proc_cpuinfo
474              
475             =head2 $si->count_in_cpuinfo ($regex)
476              
477             Returns the number of lines $regex matches for.
478              
479             =cut
480              
481             sub count_in_cpuinfo {
482 6     6 1 25 my ($self, $regex) = @_;
483              
484 6         98 return scalar grep /$regex/, $self->_proc_cpuinfo;
485             } # count_in_cpuinfo
486              
487             =head2 $si->count_unique_in_cpuinfo ($regex)
488              
489             Returns the number of lines $regex matches for.
490              
491             =cut
492              
493             sub count_unique_in_cpuinfo {
494 384     384 1 959 my ($self, $regex) = @_;
495              
496 384         2585 my %match = map { $_ => 1 } grep /$regex/ => $self->_proc_cpuinfo;
  4912         12166  
497 384         11348 return scalar keys %match;
498             } # count_unique_in_cpuinfo
499              
500             =head2 $si->from_cpuinfo ($key)
501              
502             Returns the first value of that key in C<< /proc/cpuinfo >>.
503              
504             =cut
505              
506             sub from_cpuinfo {
507 399     399 1 1117 my ($self, $key) = @_;
508              
509 399         2856 my ($first) = grep m/^\s*$key\s*[:=]\s*/i => $self->_proc_cpuinfo;
510 399 50       10419 defined $first or $first = "";
511 399         35522 $first =~ s/^\s*$key\s*[:=]\s*//i;
512 399         3238 return $first;
513             } # from_cpuinfo
514              
515             1;
516              
517             __END__