#!/usr/perl5/bin/perl # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "@(#)psrinfo.pl 1.12 06/03/31 SMI" # # psrinfo: displays information about processors # # See detailed comment in the end of this file. # use strict; use warnings; use locale; use POSIX qw(locale_h strftime); use File::Basename; use Getopt::Long qw(:config no_ignore_case bundling auto_version); use Sun::Solaris::Utils qw(textdomain gettext); use Sun::Solaris::Kstat; ###################################################################### # Configuration variables ###################################################################### # Regexp describing cpu_info kstat fields describing CPU hierarchy. my $valid_id_exp = qr{^(?:chip|core)_id$}; # Translation of kstat name to human-readable form my %translations = ('chip_id' => gettext("The physical processor"), 'core_id' => gettext("The core")); # Localized version of plural forms my %pluralized_names = ('processor' => gettext("processor"), 'processors' => gettext("processors"), 'chip' => gettext("chip"), 'chips' => gettext("chips"), 'core' => gettext("core"), 'cores' => gettext("cores")); # Localized CPU states my %cpu_states = ('on-line' => gettext("on-line"), 'off-line' => gettext("off-line"), 'faulted' => gettext("faulted"), 'powered-off' => gettext("powered-off"), 'no-intr' => gettext("no-intr"), 'spare' => gettext("spare"), 'unknown' => gettext("unknown")); ###################################################################### # Global variables ###################################################################### # Hash with CPU ID as a key and specific per-cpu kstat hash as a value our %cpu_list, # Command name without path and trailing .pl - used for error messages. our $cmdname = basename($0, ".pl"); # Return value our $errors = 0; ###################################################################### # Helper subroutines ###################################################################### # # Print help string if specified or the standard help message and exit setting # errno. # sub usage { my (@msg) = @_; print STDERR $cmdname, ": @msg\n" if (@msg); print STDERR gettext("usage: \n" . "\tpsrinfo [-v] [-p] [processor_id ...]\n" . "\tpsrinfo -s [-p] processor_id\n"); exit(2); } # # Return the input list with duplicates removed. # Count how many times we've seen each element and remove elements seen more # than once. # sub uniq { my %seen; # Have we seen this element already? return (grep { ++$seen{$_} == 1 } @_); } # # Return the intersection of two lists passed by reference # Convert the first list to a hash with seen entries marked as 1-values # Then grep only elements present in the first list from the second list. # As a little optimization, use the shorter list to build a hash. # sub intersect { my ($left, $right) = @_; my %seen; # Set to 1 for everything in the first list # Put the shortest list in $left scalar @$left <= scalar @$right or ($right, $left) = ($left, $right); # Create a hash indexed by elements in @left with ones as a value. map { $seen{$_} = 1 } @$left; # Find members of @right present in @left return (grep { $seen{$_} } @$right); } # # Return elements of the second list not present in the first list. Both lists # are passed by reference. # sub set_subtract { my ($left, $right) = @_; my %seen; # Set to 1 for everything in the first list # Create a hash indexed by elements in @left with ones as a value. map { $seen{$_} = 1 } @$left; # Find members of @right present in @left return (grep { ! $seen{$_} } @$right); } # # Sort the list numerically # Should be called in list context # sub nsort { return (sort { $a <=> $b } @_); } # # Sort list numerically and remove duplicates # Should be called in list context # sub uniqsort { return (sort { $a <=> $b } uniq(@_)); } # # Return the maximum value of its arguments # sub max { my $m = shift; foreach my $el (@_) { $m = $el if $m < $el; } return ($m); } # # Pluralize name if there is more than one instance # Arguments: name, ninstances # sub pluralize { my ($name, $count) = @_; # Remove trailing '_id' from the name. $name =~ s/_id$//; my $plural_name = $count > 1 ? "${name}s" : $name; return ($pluralized_names{$plural_name} || $plural_name) } # # Translate id name into printable form # Look at the %translations table and replace everything found there # Remove trailing _id from the name if there is no translation # sub id_translate { my $name = shift or return; my $translated_name = $translations{$name}; $name =~ s/_id$// unless $translated_name; return ($translated_name || $name); } # # Consolidate consequtive CPU ids as start-end # Input: list of CPUs # Output: string with cpu values with CPU ranges collapsed as x-y # sub collapse { return ('') unless @_; my @args = uniqsort(@_); my $start = shift(@args); my $result = ''; my $end = $start; foreach my $el (@args) { if ($el == ($end + 1)) { $end = $el; } else { if ($end > $start + 1) { $result = "$result $start-$end"; } elsif ($end > $start) { $result = "$result $start, $end"; } else { $result = "$result $start"; } $start = $end = $el; } } if ($end > $start + 1) { $result = "$result $start-$end"; } elsif ($end > $start) { $result = "$result $start $end"; } else { $result = "$result $start"; } # Remove any spaces in the beginning $result =~ s/^\s+//; return ($result); } # # Expand start-end into the list of values # Input: string containing a single numeric ID or x-y range # Output: single value or a list of values # Ranges with start being more than end are inverted # sub expand { my $arg = shift; if ($arg =~ m/^\d+$/) { # single number return ($_); } elsif ($arg =~ m/^(\d+)\-(\d+)$/) { my ($start, $end) = ($1, $2); # $start-$end # Reverse the interval if start > end ($start, $end) = ($end, $start) if $start > $end; return ($start .. $end); } elsif ($arg =~ m/-/) { printf STDERR gettext("%s: invalid processor range %s\n"), $cmdname, $_; } else { printf STDERR gettext("%s: processor %s: Invalid argument\n"), $cmdname, $_; } $errors = 2; return (); } # # Functions for constructing CPU hierarchy. Only used with -vp option. # # # Return numerically sorted list of distinct values of a given cpu_info kstat # field, spanning given CPU set. # # Arguments: # Property name # list of CPUs # sub property_list { my $prop_name = shift; return (uniqsort(map { $cpu_list{$_}->{$prop_name} } @_)); } # # Return subset of CPUs sharing specified value of a given cpu_info kstat field. # Arguments: # Property name # Property value # List of CPUs to select from # sub cpus_by_prop { my $prop_name = shift; my $prop_val = shift; return (grep { $cpu_list{$_}->{$prop_name} == $prop_val } @_); } # # Build component tree # # Arguments: # Reference to the list of CPUs sharing the component # Reference to the list of sub-components # sub build_component_tree { my ($cpus, $comp_list) = @_; # Get the first component and the rest my ($comp_name, @comps) = @$comp_list; my $tree = {}; if (!$comp_name) { $tree->{cpus} = $cpus; return ($tree); } # Get all possible component values foreach my $v (property_list($comp_name, @$cpus)) { my @comp_cpus = cpus_by_prop ($comp_name, $v, @$cpus); $tree->{name} = $comp_name; $tree->{cpus} = $cpus; $tree->{values}->{$v} = build_component_tree(\@comp_cpus, \@comps); } return ($tree); } # # Print the component tree # Arguments: # Reference to a tree # indentation # Output: maximum indentation # sub print_component_tree { my ($tree, $ind) = @_; my $spaces = ' ' x $ind; # indentation string my $vals = $tree->{values}; my $retval = $ind; if ($vals) { # This is not a leaf node # Get node name and translate it to printable format my $id_name = id_translate($tree->{name}); # Examine each sub-node foreach my $comp_val (nsort(keys %$vals)) { my $child_tree = $vals->{$comp_val}; # Sub-tree my $child_id = $child_tree->{name}; # Name of child node my @cpus = @{$child_tree->{cpus}}; # CPUs for the child my $ncpus = scalar @cpus; # Number of CPUs my $cpuname = pluralize('processor', $ncpus); my $cl = collapse(@cpus); # Printable CPU list if (!$child_id) { # Child is a leaf node print $spaces; printf gettext("%s has %d virtual %s"), $id_name, $ncpus, $cpuname; print " ($cl)\n"; $retval = max($retval, $ind + 2); } else { # Child has several values. Let's see how many my $grandchild_tree = $child_tree->{values}; my $nvals = scalar(keys %$grandchild_tree); my $child_id_name = pluralize($child_id, $nvals); print $spaces; printf gettext("%s has %d %s and %d virtual %s"), $id_name, $nvals, $child_id_name, $ncpus, $cpuname; print " ($cl)\n"; # Print the tree for the child $retval = max($retval, print_component_tree($child_tree, $ind + 2)); } } } return ($retval); } ############################ # Main part of the program ############################ # Set message locale setlocale(LC_ALL, ""); textdomain("SUNW_OST_OSCMD"); # # Option processing # my ($opt_v, $opt_p, $opt_silent); GetOptions("p" => \$opt_p, "v" => \$opt_v, "s" => \$opt_silent) || usage(); my $verbosity = 1; my $phys_view; $verbosity |= 2 if $opt_v; $verbosity &= ~1 if $opt_silent; $phys_view = 1 if $opt_p; # Set $phys_verbose if -vp is specified my $phys_verbose = $phys_view && ($verbosity > 1); # Verify options usage(gettext("options -s and -v are mutually exclusive")) if $verbosity == 2; usage(gettext("must specify exactly one processor if -s used")) if (($verbosity == 0) && scalar @ARGV != 1); # # Read cpu_info kstats # my $ks = Sun::Solaris::Kstat->new(strip_strings => 1) or die gettext("$cmdname: kstat_open() failed: %!\n"); my $cpu_info = $ks->{cpu_info} or die gettext("$cmdname: can not read cpu_info kstat\n"); my ( @all_cpus, # List of all CPUs in the system @cpu_args, # CPUs to look at @cpus, # List of CPUs to process @id_list, # list of various xxx_id kstats representing CPU topology %chips, # Hash with chip ID as a key and reference to the list of # virtual CPU IDs, belonging to the chip as a value @chip_list, # List of all chip_id values $ctree, # The component tree ); # # Get information about each CPU. # # Collect list of all CPUs in @cpu_list array # # Construct %cpu_list hash keyed by CPU ID with cpu_info kstat hash as its # value. # # Construct %chips hash keyed by chip ID. It has a 'cpus' entry, which is # a reference to a list of CPU IDs within a chip. # foreach my $id (nsort(keys %$cpu_info)) { # $id is CPU id my $info = $cpu_info->{$id}; # # The name part of the cpu_info kstat should always be a string # cpu_info$id. # # The $ci hash reference holds all data for a specific CPU id. # my $ci = $info->{"cpu_info$id"} or next; # Save CPU-specific information in cpu_list hash, indexed by CPU ID. $cpu_list{$id} = $ci; my $chip_id = $ci->{'chip_id'}; # Collect CPUs within the chip. # $chips{$chip_id} is a reference to a list of CPU IDs belonging to thie # chip. It is automatically created when first referenced. push (@{$chips{$chip_id}}, $id) if (defined($chip_id)); # Collect list of CPU IDs in @cpus push (@all_cpus, $id); } # # Figure out what CPUs to examine. # Look at specific CPUs if any are specified on the command line or at all CPUs # CPU ranges specified in the command line are expanded into lists of CPUs # if (scalar(@ARGV) == 0) { @cpu_args = @all_cpus; } else { # Expand all x-y intervals in the argument list @cpu_args = map { expand($_) } @ARGV; # Detect invalid CPUs in the arguments my @bad_args = set_subtract(\@all_cpus, \@cpu_args); my $nbadargs = scalar @bad_args; if ($nbadargs != 0) { # Warn user about bad CPUs in the command line my $argstr = collapse(@bad_args); if ($nbadargs > 1) { printf STDERR gettext("%s: Invalid processors %s\n"), $cmdname, $argstr; } else { printf STDERR gettext("%s: processor %s: Invalid argument\n"), $cmdname, $argstr; } $errors = 2; } @cpu_args = uniqsort(intersect(\@all_cpus, \@cpu_args)); } # # In physical view, CPUs specified in the command line are only used to identify # chips. The actual CPUs are all CPUs belonging to these chips. # if (! $phys_view) { @cpus = @cpu_args; } else { # Get list of chips spanning all CPUs specified @chip_list = property_list('chip_id', @cpu_args); if (!scalar @chip_list && $errors == 0) { printf STDERR gettext("%s: Physical processor view not supported\n"), $cmdname; exit(1); } # Get list of all CPUs within these chips @cpus = uniqsort(map { @{$chips{$_}} } @chip_list); } if ($phys_verbose) { # # 1) Look at all possible xxx_id properties and remove those that have # NCPU values or one value. Sort the rest. # # 2) Drop ids which have the same number of entries as number of CPUs or # number of chips. # # 3) Build the component tree for the system # my $id = (keys %$cpu_info)[0]; # Get any CPU my $info = $cpu_info->{$id}; my $name = "cpu_info$id"; my $ci = $info->{$name}; # cpu_info kstat for this CPU # Collect all statistic names matching $valid_id_exp @id_list = grep(/$valid_id_exp/, keys(%$ci)); my $ncpus = scalar @cpus; my %prop_nvals; # Number of instances of each property my $nchips = scalar @chip_list; # # Get list of properties which have more than ncpus and less than nchips # instances. # Also collect number of instances for each property. # @id_list = grep { my @ids = property_list($_, @cpus); my $nids = scalar @ids; $prop_nvals{$_} = $nids; ($_ eq "chip_id") || (($nids > $nchips) && ($nids > 1) && ($nids < $ncpus)); } @id_list; # Sort @id_list by number of instances for each property @id_list = sort { $prop_nvals{$a} <=> $prop_nvals{$b} } @id_list; $ctree = build_component_tree(\@cpus, \@id_list); } # # Walk all CPUs specified and print information about them. # Do nothing for physical view - will do everything later. # foreach my $id (@cpus) { last if $phys_view; # physical view is handled later my $cpu = $cpu_list{$id} or next; # Get CPU state and its modification time my $mtime = $cpu->{'state_begin'}; my $mstring = strftime(gettext("%m/%d/%Y %T"), localtime($mtime)); my $status = $cpu->{'state'} || gettext("unknown"); # Get localized version of CPU status $status = $cpu_states{$status} || $status; if ($verbosity == 0) { # Print 1 if CPU is online, 0 if offline. printf "%d\n", $status eq 'on-line'; } elsif (! ($verbosity & 2)) { printf gettext("%d\t%-8s since %s\n"), $id, $status, $mstring; } else { printf gettext("Status of virtual processor %d as of: "), $id; print strftime(gettext("%m/%d/%Y %T"), localtime()); print "\n"; printf gettext(" %s since %s.\n"), $status, $mstring; my $clock_speed = $cpu->{'clock_MHz'}; my $cpu_type = $cpu->{'cpu_type'}; # Display clock speed if ($clock_speed ) { printf gettext(" The %s processor operates at %s MHz,\n"), $cpu_type, $clock_speed; } else { printf gettext(" the %s processor operates at an unknown frequency,\n"), $cpu_type; } # Display FPU type my $fpu = $cpu->{'fpu_type'}; if (! $fpu) { print gettext("\tand has no floating point processor.\n"); } elsif ($fpu =~ m/^[aeiouy]/) { printf gettext("\tand has an %s floating point processor.\n"), $fpu; } else { printf gettext("\tand has a %s floating point processor.\n"), $fpu; } } } # # Physical view print # if ($phys_view) { if ($verbosity == 1) { print scalar @chip_list, "\n"; } elsif ($verbosity == 0) { # Print 1 if all CPUs are online, 0 otherwise. foreach my $chip_id (@chip_list) { # Get CPUs on a chip my @chip_cpus = uniqsort(@{$chips{$chip_id}}); # List of all on-line CPUs on a chip my @online_cpus = grep { ($cpu_list{$_}->{state}) eq 'on-line' } @chip_cpus; # # Print 1 if number of online CPUs equals number of all # CPUs # printf "%d\n", scalar @online_cpus == scalar @chip_cpus; } } else { # Walk the property tree and print everything in it. my $tcores = $ctree->{values}; my $cname = id_translate($ctree->{name}); foreach my $chip (nsort(keys %$tcores)) { my $chipref = $tcores->{$chip}; my @chip_cpus = @{$chipref->{cpus}}; my $ncpus = scalar @chip_cpus; my $cpu_id = $chip_cpus[0]; my $cpu = $cpu_list{$cpu_id}; my $brand = $cpu->{brand} || gettext("(unknown)"); my $impl = $cpu->{implementation} || gettext("(unknown)"); # # Remove cpuid and chipid information from # implementation string and print it. # $impl =~ s/(cpuid|chipid)\s*\w+\s+//; $brand = '' if $impl && $impl =~ /^$brand/; # List of CPUs on a chip my $cpu_name = pluralize('processor', $ncpus); # Collapse range of CPUs into a-b string my $cl = collapse(@chip_cpus); my $childname = $chipref->{name}; if (! $childname) { printf gettext("%s has %d virtual %s "), $cname, $ncpus, $cpu_name; print "($cl)\n"; print " $impl\n" if $impl; print "\t$brand\n" if $brand; } else { # Get child count my $nchildren = scalar(keys(%{$chipref->{values}})); $childname = pluralize($childname, $nchildren); printf gettext("%s has %d %s and %d virtual %s "), $cname, $nchildren, $childname, $ncpus, $cpu_name; print "($cl)\n"; my $ident = print_component_tree ($chipref, 2); my $spaces = ' ' x $ident; print "$spaces$impl\n" if $impl; print "$spaces $brand\n" if $brand; } } } } exit($errors); __END__ # The psrinfo command displays information about virtual and physical processors # in a system. It gets all the information from the 'cpu_info' kstat. # # See detailed comment in the end of this file. # # # # This kstat # has the following components: # # module: cpu_info # instance: CPU ID # name: cpu_infoID where ID is CPU ID # class: misc # # The psrinfo command translates this information from kstat-specific # representation to user-friendly format. # # The psrinfo command has several basic modes of operations: # # 1) Without options, it displays a line per CPU with CPU ID and its status and # the time the status was last set in the following format: # # 0 on-line since MM/DD/YYYY HH:MM:SS # 1 on-line since MM/DD/YYYY HH:MM:SS # ... # # In this mode, the psrinfo command walks the list of CPUs (either from a # command line or all CPUs) and prints the 'state' and 'state_begin' fields # of cpu_info kstat structure for each CPU. The 'state_begin' is converted to # local time. # # 2) With -s option and a single CPU ID as an argument, it displays 1 if the CPU # is online and 0 otherwise. # # 3) With -p option, it displays the number of physical processors in a system. # If any CPUs are specified in the command line, it displays the number of # physical processors containing all virtual CPUs specified. The physical # processor is identified by the 'chip_id' field of the cpu_info kstat. # # The code just walks over all CPUs specified and checks how many different # core_id values they span. # # 4) With -v option, it displays several lines of information per virtual CPU, # including its status, type, operating speed and FPU type. For example: # # Status of virtual processor 0 as of: MM/DD/YYYY HH:MM:SS # on-line since MM/DD/YYYY HH:MM:SS. # The i386 processor operates at XXXX MHz, # and has an i387 compatible floating point processor. # Status of virtual processor 1 as of: MM/DD/YYYY HH:MM:SS # on-line since MM/DD/YYYY HH:MM:SS. # The i386 processor operates at XXXX MHz, # and has an i387 compatible floating point processor. # # This works in the same way as 1), just more kstat fields are massaged in the # output. # # 5) With -vp option, it reports additional information about each physical # processor. This information includes information about sub-components of # each physical processor and virtual CPUs in each sub-component. For # example: # # The physical processor has 2 cores and 4 virtual processors (0-3) # The core has 2 virtual processors (0 1) # The core has 2 virtual processors (2 3) # x86 (GenuineIntel family 15 model 4 step 4 clock 3211 MHz) # Intel(r) Pentium(r) D CPU 3.20GHz # # The implementation does not know anything about physical CPU components # such as cores. Instead it looks at various cpu_info kstat statistics that # look like xxx_id and tries to reconstruct the CPU hierarchy based on these # fields. This works as follows: # # a) All kstats statistic names matching the $valid_id_exp regular expression # are examined and each kstat statistic name is associated with the number # of distinct entries in it. # # b) The resulting list of kstat statistic names is sorted according to the # number of distinct entries, matching each name. For example, there are # fewer chip_id values than core_id values. This implies that the core is # a sub-component of a chip. # # c) All kstat names that have the same number of values as the number of # physical processors ('chip_id' values) or the number of virtual # processors are removed from the list. # # d) The resulting list represents the CPU hierarchy of the machine. It is # translated into a tree showing the hardware hierarchy. Each level of the # hierarchy contains the name, reference to a list of CPUs at this level # and subcomponents, indexed by the value of each component. # The example system above is represented by the following tree: # # $tree = # { # 'name' => 'chip_id', # 'cpus' => [ '0', '1', '2', '3' ] # 'values' => # { # '0' => # { # 'name' => 'core_id', # 'cpus' => [ '0', '1', '2', '3' ] # 'values' => # { # '0' => { 'cpus' => [ '0', '1' ] } # '1' => { 'cpus' => [ '2', '3' ] }, # }, # } # }, # }; # # Each node contains reference to a list of virtual CPUs at this level of # hierarchy - one list for a system as a whole, one for chip 0 and one two # for each cores. node. Non-leaf nodes also contain the symbolic name of # the component as represented in the cpu_info kstat and a hash of # subnodes, indexed by the value of the component. The tree is built by # the build_component_tree() function. # # e) The resulting tree is pretty-printed showing the number of # sub-components and virtual CPUs in each sub-component. The tree is # printed by the print_component_tree() function. # ###################################################################### # Pod section is a copy of psrinfo(1M) man page. ## =pod =head1 NAME psrinfo - displays information about processors =head1 SYNOPSYS psrinfo [-p] [-v] [processor_id...] psrinfo [-p] -s processor_id =head1 DESCRIPTION psrinfo displays information about processors. Each physical processor may support multiple virtual processors. Each virtual processor is an entity with its own interrupt ID, capable of executing independent threads. Without the processor_id operand, psrinfo displays one line for each configured processor, displaying whether it is on-line, non-interruptible (designated by no-intr), spare, off-line, faulted or powered off, and when that status last changed. Use the processor_id operand to display information about a specific processor. See OPERANDS. =head1 OPTIONS The following options are supported: =over =item -s processor_id Silent mode. Displays 1 if the specified processor is fully on-line. Displays 0 if the specified processor is non- interruptible, spare, off-line, faulted or powered off. Use silent mode when using psrinfo in shell scripts. =item -p Display the number of physical processors in a system. When combined with the -v option, reports additional information about each physical processor. =item -v Verbose mode. Displays additional informa- tion about the specified processors, including: processor type, floating point unit type and clock speed. If any of this information cannot be determined, psrinfo displays unknown. When combined with the -p option, reports additional information about each physical processor. On systems with multiple cores per chip, reports information about each core if the core information is provided by the OS in the core_id field of the cpu_info kstat. =back =head1 OPERANDS The following operands are supported: =over =item C The processor ID of the processor about which information is to be displayed. Specify processor_id as an individual processor number (for example, 3), multiple processor numbers separated by spaces (for example, 1 2 3), or a range of processor numbers (for example, 1-4). It is also possible to combine ranges and (individual or multiple) processor_ids (for example, 1-3 5 7-8 9). =back =head1 EXIT STATUS The following exit values are returned: =over =item 0 Successful completion. =item >0 An error occurred. =back =head1 SEE ALSO L, L, L, L =cut