File Coverage

blib/lib/System/Info/Linux.pm
Criterion Covered Total %
statement 184 214 85.9
branch 91 134 67.9
condition 44 74 59.4
subroutine 15 16 93.7
pod 11 11 100.0
total 345 449 76.8


line stmt bran cond sub pod time code
1             package System::Info::Linux;
2              
3 3     3   106900 use strict;
  3         18  
  3         87  
4 3     3   15 use warnings;
  3         5  
  3         79  
5              
6 3     3   15 use base "System::Info::Base";
  3         19  
  3         7895  
7              
8             our $VERSION = "0.053";
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 118     118 1 231 my $self = shift;
24 118         423 $self->SUPER::prepare_sysinfo;
25 118         406 $self->prepare_os;
26 118 50       527 $self->prepare_proc_cpuinfo or return;
27              
28 118         680 for ($self->get_cpu_type) {
29 118 100       561 m/arm/ and do { $self->linux_arm; last };
  3         48  
  3         10  
30 115 50       233 m/aarch64/ and do { $self->linux_arm; last };
  0         0  
  0         0  
31 115 100       241 m/ppc/ and do { $self->linux_ppc; last };
  1         36  
  1         7  
32 114 50       277 m/sparc/ and do { $self->linux_sparc; last };
  0         0  
  0         0  
33 114 100       244 m/s390x/ and do { $self->linux_s390x; last };
  2         25  
  2         4  
34             # default
35 112         285 $self->linux_generic;
36             }
37 118         281 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 475     475   1038 my ($file, $os) = @_;
48 475 50       14893 open my $fh, "<", $file or return;
49 475         12742 while (<$fh>) {
50 1985 100       6018 m/^\s*[;#]/ and next;
51 1973         3209 chomp;
52 1973 100       5626 m/\S/ or next;
53 1811         3290 s/^\s+//;
54 1811         3201 s/\s+$//;
55 1811 100       8688 if (my ($k, $v) = (m/^(.*\S)\s*=\s*(\S.*)$/)) {
56             # Having a value prevails over being defined
57 1385 100       2995 defined $os->{$k} and next;
58 1377         5056 $v =~ s/^"\s*(.*?)\s*"$/$1/;
59 1377 100       3146 $v =~ m{^["(]?undef(?:ined)?[")]$}i and $v = "undefined";
60 1377         3005 $os->{$k} = $v;
61 1377         5014 next;
62             }
63 426 100       1019 m/^[12][0-9]{3}(?:,\s*[12][0-9]{3})*$/ and next; # Copyright years
64 424 100       3523 exists $os->{$_} or $os->{$_} = undef;
65             }
66 475         5765 close $fh;
67             } # _file_info
68              
69             sub _lsb_release {
70 118     118   210 my $os = shift;
71              
72 118 100       394 $ENV{SMOKE_USE_ETC} and return;
73              
74             $os->{DISTRIB_ID} || $os->{DISTRIB_RELEASE} || $os->{DISTRIB_CODENAME}
75 15 0 33     40 or return;
      0        
76              
77             #use DP;die DDumper $os;
78 15 50       11701 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 118     118 1 191 my $self = shift;
95              
96 118   100     371 my $etc = $ENV{SMOKE_USE_ETC} || "/etc";
97 979 100       11107 my @dist_file = grep { -f $_ && -s _ } map {
98 118 100       14483 -d $_ ? glob ("$_/*") : ($_)
  852         14954  
99             } glob ("$etc/*[-_][rRvV][eE][lLrR]*"), "$etc/issue",
100             "$etc.defaults/VERSION", "$etc/VERSION", "$etc/release";
101              
102 118         1019 my $os = $self->_os;
103 118         274 my %os;
104             my $distro;
105 118         289 foreach my $df (@dist_file) {
106             # use "debian" out of /etc/debian-release
107 475 100 100     1999 unless (defined $distro or $df =~ m/\blsb-/) {
108 118         2401 ($distro = $df) =~ s{^$etc(?:\.defaults)?/}{}i;
109 118         809 $distro =~ s{[-_]?(?:release|version)\b}{}i;
110             }
111 475         1259 _file_info ($df, \%os);
112             }
113 118         536 _lsb_release (\%os);
114              
115 118 50       32072 keys %os or return;
116              
117 118         1097 foreach my $key (keys %os) {
118 1713         2654 my $KEY = uc $key;
119 1713 100       3130 defined $os{$key} or next;
120 1377 100       2652 exists $os{$KEY} or $os{$KEY} = $os{$key};
121             }
122              
123 118 100 66     627 if ($os{DISTRIB_DESCRIPTION}) {
    100 66        
    50 66        
    100 66        
    100          
    100          
124 27         177 $distro = $os{DISTRIB_DESCRIPTION};
125 27 100 100     824 $os{DISTRIB_CODENAME} && $distro !~ m{\b$os{DISTRIB_CODENAME}\b}i and
126             $distro .= " ($os{DISTRIB_CODENAME})";
127 27 100 100     902 if ($os{VERSION_ID} && $distro !~ m{\b$os{VERSION_ID}\b}i) {
    50 33        
128 1         5 $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 60         117 $distro = $os{PRETTY_NAME}; # "openSUSE 12.1 (Asparagus) (x86_64)"
136 60 100       146 if (my $vid = $os{VERSION_ID}) { # wheezy 7 => 7.2
137 59         103 my @rv;
138 59 100       1638 if (@rv = grep m{^$vid\.} => sort keys %os) {
139             # from /etc/debian_version
140 11 50       167 $rv[0] =~ m/^[0-9]+\.\w+$/ and
141             $distro =~ s/\b$vid\b/$rv[0]/;
142             }
143 59 100 66     2396 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       196 if ($rv[0] =~ m/\s($vid\.[-.\w]+)/) {
146 13         42 my $vr = $1;
147 13         106 $distro =~ s/\s$vid\b/ $vr/;
148             }
149             }
150             }
151 60         204 $distro =~ s{\s*[-:/,]\s*Version\s*:?\s*}{ };
152 60         135 $distro =~ s/\)\s+\(\w+\)\s*$/)/; # remove architectural part
153 60         236 $distro =~ s/\s+\(?(?:i\d86|x86_64)\)?\s*$//; # i386 i486 i586 x86_64
154 60 100 100     735 $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       40 if (my @welcome = grep s{^\s*Welcome\s+to\s+(\S*$distro\S*)\b.*}{$1}i => keys %os) {
162 1         5 $distro = $welcome[0];
163             }
164 1         4 $distro .= qq{ $os{VERSION}};
165 1 50       14 $distro =~ m/\b$os{CODENAME}\b/ or
166             $distro .= qq{ ($os{CODENAME})};
167             }
168             elsif ($os{MAJORVERSION} && defined $os{MINORVERSION}) {
169 11 50 33     316 -d "/usr/syno" || "@dist_file" =~ m{^\S*/VERSION$} and $distro .= "DSM";
170 11         44 $distro .= qq{ $os{MAJORVERSION}.$os{MINORVERSION}};
171 11 50       34 $os{BUILDNUMBER} and $distro .= qq{-$os{BUILDNUMBER}};
172 11 100       26 $os{SMALLFIXNUMBER} and $distro .= qq{-$os{SMALLFIXNUMBER}};
173             }
174             elsif ($os{DISTRIBVER} && exists $os{NETBSDSRCDIR}) {
175 2         7 (my $dv = $os{DISTRIBVER}) =~ tr{ ''"";}{}d;
176 2         7 $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         90 my @key = sort keys %os;
212 17         144 s/\s*\\[rln].*// for @key;
213              
214 17         97 my @vsn = grep m/^[0-9.]+$/ => @key;
215             #$self->{__X__} = { os => \%os, key => \@key, vsn => \@vsn };
216              
217 17 100 0     174 if (my @welcome = grep s{^\s*Welcome\s+to\s+}{}i => @key) {
    100          
    50          
    0          
218 3         23 ($distro = $welcome[0]) =~ s/"([^"]+)"/($1)/;
219             }
220             elsif (my @rel = grep m{\brelease\b}i => @key) {
221 12 50 66     56 @rel > 1 && $rel[0] =~ m/^Enterprise Linux Enterprise/
      66        
222             && $rel[1] =~ m/^Oracle Linux/ and shift @rel;
223 12         26 $distro = $rel[0];
224 12         63 $distro =~ s/ *release//;
225 12         47 $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         34 $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         6 $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         49 $distro =~ s/\s+-\s+Kernel.*//i;
239             }
240 118 50       1011 if ($distro =~ s/^\s*(.*\S)\s*$/$1/) {
241 118         621 $self->{__distro} = $distro;
242 118         465 $os .= " [$distro]";
243             }
244 118         282 $self->{__release_info} = \%os;
245 118         580 $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 112     112 1 213 my $self = shift;
270              
271 112   100     707 my $n_phys_id = $self->count_unique_in_cpuinfo (qr/^physical id\s+:/) || 0;
272 112   100     532 my $n_core_id = $self->count_unique_in_cpuinfo (qr/^core id\s+:/) || 0;
273 112   50     508 my $n_processor = $self->count_unique_in_cpuinfo (qr/^processor\s+:/) || 0;
274 112   66     375 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 112         298 $self->{__cpu_count} = $n_cpu;
278              
279 112         176 { my @tags = ("model name", "vendor_id", "cpu mhz");
  112         350  
280 112         223 my %info = map { ($_ => $self->from_cpuinfo ($_)) } @tags;
  336         817  
281 112 50       399 unless (defined $info{$tags[0]}) {
282             # riscv64 -> rv64imafdc
283 0         0 $info{$tags[0]} = $self->from_cpuinfo ("isa");
284             }
285 112 50       1008 $info{$tags[2]} and $info{$tags[2]} = sprintf "%.0fMHz", $info{$tags[2]};
286 112         247 my @cpui;
287 112 50       286 if ($info{$tags[0]}) {
288 112         260 push @cpui => $info{shift @tags};
289 112         311 push @cpui => "(".(join " " => grep { length } @info{@tags}).")";
  224         624  
290             }
291             else {
292 0         0 push @cpui => grep { length } @info{@tags};
  0         0  
293             }
294 112         513 $self->{__cpu} = join " " => @cpui;
295             }
296              
297 112 100       290 if ($n_phys_id) {
298             $n_processor > $n_phys_id and
299 111 100       262 $self->{__cpu_count} .= " [$n_processor cores]";
300 111         282 return;
301             }
302 1 50       3 if ($n_core_id) {
303             $n_processor > $n_core_id and
304 0 0       0 $self->{__cpu_count} .= " [$n_processor cores]";
305 0         0 return;
306             }
307              
308 1         2 my $n_cores = 0;
309 1         2 my $core_id = 0;
310 1         2 my %cores;
311 1         5 for my $cores (grep m/(cpu cores|core id)\s*:\s*\d+/ => $self->_proc_cpuinfo) {
312 0 0       0 my ($tag, $count) = $cores =~ m/^(.*\S)\s*:\s*(\d+)/ or next;
313 0 0       0 if ($tag eq "core id") {
314 0         0 $core_id = $count;
315             }
316             else {
317 0         0 $cores{$core_id} = $count;
318             }
319             }
320 1         6 $n_cores += $cores{$_} for keys %cores;
321              
322 1 50       5 $n_cores > $n_cpu and $self->{__cpu_count} .= " [$n_cores cores]";
323             } # _linux_generic
324              
325             =head2 $si->linux_arm
326              
327             Check C for these keys:
328              
329             =over
330              
331             =item "processor" (count occurrence for __cpu_count)
332              
333             =item "Processor" (part of __cpu)
334              
335             =item "BogoMIPS" (part of __cpu)
336              
337             =back
338              
339             =cut
340              
341             sub linux_arm {
342 3     3 1 14 my $self = shift;
343              
344 3         63 $self->{__cpu_count} = $self->count_in_cpuinfo (qr/^processor\s+:\s+/i);
345              
346 3   66     22 my $cpu = $self->from_cpuinfo ("Processor") ||
347             $self->from_cpuinfo ("Model[_ ]name");
348 3         23 my $bogo = $self->from_cpuinfo ("BogoMIPS");
349 3         30 my $mhz = 100 * int (($bogo + 50) / 100);
350 3         23 $cpu =~ s/\s+/ /g;
351 3 50       17 $mhz and $cpu .= " ($mhz MHz)";
352 3         11 $self->{__cpu} = $cpu;
353             } # _linux_arm
354              
355             =head2 $si->linux_ppc
356              
357             Check C for these keys:
358              
359             =over
360              
361             =item "processor" (count occurrence for __cpu_count)
362              
363             =item "cpu" (part of __cpu)
364              
365             =item "machine" (part of __cpu)
366              
367             =item "clock" (part of __cpu)
368              
369             =item "detected" (alters machine if present)
370              
371             =back
372              
373             =cut
374              
375             sub linux_ppc {
376 1     1 1 8 my $self = shift;
377              
378 1         25 $self->{__cpu_count} = $self->count_in_cpuinfo (qr/^processor\s+:\s+/);
379              
380 1         12 my @tags = qw( cpu machine clock );
381 1         4 my %info = map { ($_ => $self->from_cpuinfo ($_)) } @tags;
  3         11  
382 1 50       10 if ($info{detected} = $self->from_cpuinfo ("detected as")){
383 1         20 $info{detected} =~ s/.*(\b.+Mac G\d).*/$1/;
384 1         8 $info{machine} = $info{detected};
385             }
386              
387 1         8 $self->{__cpu} = sprintf "%s %s (%s)", map { $info{$_} } @tags;
  3         13  
388             } # linux_ppc
389              
390             =head2 $si->linux_sparc
391              
392             Check C for these keys:
393              
394             =over
395              
396             =item "processor" (count occurrence for __cpu_count)
397              
398             =item "cpu" (part of __cpu)
399              
400             =item "Cpu0ClkTck" (part of __cpu)
401              
402             =back
403              
404             =cut
405              
406             sub linux_sparc {
407 0     0 1 0 my $self = shift;
408              
409 0         0 $self->{__cpu_count} = $self->from_cpuinfo ("ncpus active");
410              
411 0         0 my @tags = qw( cpu Cpu0ClkTck );
412 0         0 my %info = map { ($_ => $self->from_cpuinfo ($_)) } @tags;
  0         0  
413 0         0 my $cpu = $info{cpu};
414             $info{Cpu0ClkTck} and
415 0 0       0 $cpu .= sprintf " (%.0fMHz)", hex ($info{Cpu0ClkTck}) / 1_000_000;
416 0         0 $self->{__cpu} = $cpu;
417             } # linux_sparc
418              
419             =head2 $si->linux_s390x
420              
421             Check C for these keys:
422              
423             =over
424              
425             =item "processor" (count occurrence for __cpu_count)
426              
427             =item "Processor" (part of __cpu)
428              
429             =item "BogoMIPS" (part of __cpu)
430              
431             =back
432              
433             =cut
434              
435             sub linux_s390x {
436 2     2 1 10 my $self = shift;
437              
438 2         37 $self->{__cpu_count} = $self->count_in_cpuinfo (qr/^processor\s+\d+:\s+/i);
439              
440 2   33     31 my $cpu = $self->from_cpuinfo ("vendor_id") ||
441             $self->from_cpuinfo ("Processor") ||
442             $self->from_cpuinfo ("Model[_ ]name");
443 2         20 my $bogo = $self->from_cpuinfo (qr{BogoMIPS(?:\s*per[ _]CPU)?}i);
444 2         27 my $mhz = 100 * int (($bogo + 50) / 100);
445 2         13 $cpu =~ s/\s+/ /g;
446 2 50       18 $mhz and $cpu .= " ($mhz MHz)";
447 2         13 $self->{__cpu} = $cpu;
448             } # _linux_s390x
449              
450             =head2 $si->prepare_proc_cpuinfo
451              
452             Read the complete C<< /proc/cpuinfo >>.
453              
454             =cut
455              
456             sub prepare_proc_cpuinfo {
457 118     118 1 231 my $self = shift;
458              
459 118 50       4062 if (open my $pci, "<", "/proc/cpuinfo") {
460 118         43937 chomp (my @pci = <$pci>);
461 118         122819 s/[\s\xa0]+/ /g for @pci;
462 118         9026 s/ $// for @pci;
463 118         357 $self->{__proc_cpuinfo} = \@pci;
464 118         1416 close $pci;
465 118         898 return 1;
466             }
467             } # prepare_proc_cpuinfo
468              
469             =head2 $si->count_in_cpuinfo ($regex)
470              
471             Returns the number of lines $regex matches for.
472              
473             =cut
474              
475             sub count_in_cpuinfo {
476 6     6 1 31 my ($self, $regex) = @_;
477              
478 6         69 return scalar grep /$regex/, $self->_proc_cpuinfo;
479             } # count_in_cpuinfo
480              
481             =head2 $si->count_unique_in_cpuinfo ($regex)
482              
483             Returns the number of lines $regex matches for.
484              
485             =cut
486              
487             sub count_unique_in_cpuinfo {
488 336     336 1 727 my ($self, $regex) = @_;
489              
490 336         1446 my %match = map { $_ => 1 } grep /$regex/ => $self->_proc_cpuinfo;
  5251         9673  
491 336         7145 return scalar keys %match;
492             } # count_unique_in_cpuinfo
493              
494             =head2 $si->from_cpuinfo ($key)
495              
496             Returns the first value of that key in C<< /proc/cpuinfo >>.
497              
498             =cut
499              
500             sub from_cpuinfo {
501 351     351 1 810 my ($self, $key) = @_;
502              
503 351         1619 my ($first) = grep m/^\s*$key\s*[:=]\s*/i => $self->_proc_cpuinfo;
504 351 50       6259 defined $first or $first = "";
505 351         8757 $first =~ s/^\s*$key\s*[:=]\s*//i;
506 351         1966 return $first;
507             } # from_cpuinfo
508              
509             1;
510              
511             __END__