#!/usr/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 # 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] # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "@(#)psrinfo.pl 1.9 06/03/09 SMI" # # psrinfo: displays information about processors # # Author: Alexander Kolbasov # # # The psrinfo command displays information about virtual and physical processors # in a system. It gets all the information from the 'cpu_info' kstat. This kstat # has the following components: # # module: cpu_info # instance: CPU ID # name: cpu_infoID where ID is CPU ID # class: misc # # The actual statistic contains various information about CPU, for example: # # cpu_info:0:cpu_info0:class misc # cpu_info:0:cpu_info0:chip_id 0 # cpu_info:0:cpu_info0:core_id 0 # cpu_info:0:cpu_info0:clock_MHz XXXX # cpu_info:0:cpu_info0:cpu_type i386 # cpu_info:0:cpu_info0:fpu_type i387 compatible # cpu_info:0:cpu_info0:vendor_id AuthenticAMD # cpu_info:0:cpu_info0:brand AMD Opteron(tm) Processor # cpu_info:0:cpu_info0:implementation x86 (AuthenticAMD family 15 clock XXXX MHz) # cpu_info:0:cpu_info0:state on-line # cpu_info:0:cpu_info0:state_begin 1141426992 # cpu_info:1:cpu_info1:class misc # cpu_info:1:cpu_info1:chip_id 1 # cpu_info:1:cpu_info1:core_id 1 # cpu_info:1:cpu_info1:clock_MHz XXXX # cpu_info:1:cpu_info1:cpu_type i386 # cpu_info:1:cpu_info1:fpu_type i387 compatible # cpu_info:1:cpu_info1:vendor_id AuthenticAMD # cpu_info:1:cpu_info1:brand AMD Opteron(tm) Processor # cpu_info:1:cpu_info1:implementation x86 (AuthenticAMD clock XXXX MHz) # cpu_info:1:cpu_info1:snaptime 504863.096344076 # cpu_info:1:cpu_info1:state on-line # cpu_info:1:cpu_info1:state_begin 1141426996 # # The psrinfo command ranslates 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 kstat fields that look like # xxx_id and tries to reconstruct the CPU hierarchy based on these fields. # This works as follows: # # a) All kstats matching the $valid_id_exp regular expression are examined # and each kstat name is associated with the number of distinct entries # in it. # # b) The resulting list of kstat names is sorted according to the number of # distinct entries, matching each name. For example, there are less # 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 what sub-components share what parts. # 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' => # { # '1' => { 'cpus' => [ '2', '3' ] }, # '0' => { 'cpus' => [ '0', '1' ] } # }, # } # }, # }; # # Each node contains reference to a list of virtual CPUs belonging to the # 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. # use strict; use warnings; use POSIX qw(strftime); use File::Basename; use Getopt::Long qw(:config no_ignore_case bundling auto_version); use Sun::Solaris::Kstat; ###################################################################### # Configuration variables ###################################################################### # Regexp describing cpu_info kstat fields describing CPU hierarchy. my $valid_id_exp = 'chip_id|core_id'; # Translation of kstat name to human-readable form my %translations = ('chip_id' => 'physical processor'); ###################################################################### # Global variables ###################################################################### # Command name without path and trailing .pl - used for error messages. my $cmdname = basename($0, ".pl"); my ( @cpus, # List of all CPUs in the system %cpu_list, # Hash with CPU ID as a key and specific per-cpu kstat hash as a # value @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 ); ###################################################################### # Helper subroutines ###################################################################### # Print help string if specified or the standard help message and exit setting # errno. sub usage(;$) { my $msg = shift; die ("$cmdname: $msg\n") if $msg; die ("usage: \n\t$cmdname [-v] [-p] [processor_id ...]\n\t$cmdname" . " -s [-p] processor_id\n") unless $msg; } ############################################################################### # Set manipulations. Set manipulations is described in Randal L. Schwartz Unix # Review Column 11 published in November 1996. ## # Return the input list with duplicates removed. Count how many times we've seen # each element and remove frequent flyers. All done in a single grep expression. sub uniq(@) { my %seen; # Have we seen this element already? return grep { ++$seen{$_} == 1 } @_; } # Return the intersection of two sets passed by reference # Convert the first set to a hash with seen entries marked as 1-values # Then grep only elements present in the first set from the second set. # 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 set # Put the shortest vector in $left scalar @$left <= scalar @$right or ($right, $left) = ($left, $right); @seen{@$left} = (1) x @$left; return grep { $seen{$_} } @$right; } ############################## # Little convinience wrappers ## # Sort the list numerically sub nsort(@) { sort { $a <=> $b } @_; } # Sort list numerically and remove duplicates sub uniqsort(@) { nsort(uniq(@_)); } sub max($$) { my ($x, $y) = @_; return ($x > $y ? $x : $y); } ## # Plurize name if there is more than one instance # Arguments: name, ninstances # sub plurize($$) { my ($name, $count) = @_; return ($count > 1 ? "${name}s" : $name); } ## # Translate id name into printable form # Look at the %translations table and replace everything found there # Remove trailing _id. # sub id_translate($) { my $name = shift or return; $name = $translations{$name} || $name; $name =~ s/_id$//; return $name; } # Return specified field from specific cpu_info kstat sub getinfo ($$) { my ($ci, $arg) = @_; return ${ci}->{$arg}; } ####################################################### # Functions dealing with CPU lists on input and output ####################################################### ## # 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 $result = ""; my $start = shift; my $end = $start; foreach my $el (@_) { 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; return ($arg) if $arg =~ m/^\d+$/; # single number return unless $arg =~ m/(\d+)\-(\d+)/; # not a range my ($start, $end) = ($1, $2); # $start-$end # Reverse the interval if start > end ($start, $end) = ($end, $start) if $start > $end; return ($start .. $end); } ###################################################################### # 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; uniqsort(map { getinfo($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; grep { getinfo($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 # # Here is an example of the tree on dual-core system: # $tree = # { # 'name' => 'chip_id', # 'cpus' => [ '0', '1', '2', '3' ] # 'values' => # { # '0' => # { # 'name' => 'core_id', # 'cpus' => [ '0', '1', '2', '3' ] # 'values' => # { # '1' => { 'cpus' => [ '2', '3' ] }, # '0' => { 'cpus' => [ '0', '1' ] } # }, # } # }, # }; # # The function is recursive, so pre-declare it to allow prototype checks # properly. sub build_component_tree($$); sub build_component_tree($$) { my ($cpu_list, $comp_list) = @_; # Get the first component and the rest my ($comp_name, @comps) = @$comp_list; my $tree = {}; if (!$comp_name) { $tree->{cpus} = $cpu_list; return $tree; } # Get all possible component values foreach my $v (property_list($comp_name, @$cpu_list)) { my @comp_cpus = cpus_by_prop ($comp_name, $v, @$cpu_list); $tree->{name} = $comp_name; $tree->{cpus} = $cpu_list; $tree->{values}->{$v} = build_component_tree(\@comp_cpus, \@comps); } return $tree; } ## # Print the component tree # Arguments: # Reference to a tree # identation # Output: maximum identation # This function is recursive, so pre-declare it to allow prototype checks sub print_component_tree($$); sub print_component_tree($$) { my ($tree, $ident) = @_; my $spaces = ' ' x $ident; # identation string my $vals = $tree->{values}; my $retval = $ident; 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 = plurize("processor", $ncpus); my $cl = collapse(@cpus); # Printable CPu list if (!$child_id) { # Child is a leaf node print "${spaces}The $id_name has $ncpus virtual"; print " $cpuname ($cl)\n"; $retval = max($retval, $ident + 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 = plurize(id_translate($child_id), $nvals); print "${spaces}The $id_name has $nvals $child_id_name"; print " and $ncpus virtual $cpuname ($cl)\n"; # Print the three for the child $retval = max($retval, print_component_tree($child_tree, $ident + 2)); } } } return ($retval); } ########################### # Main part of the program ############################ ## # 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("options -s and -v are mutually exclusive") if $verbosity == 2; ## # Read cpu_info kstats # my $ks = Sun::Solaris::Kstat->new() or die "$cmdname: kstat_open() failed: %!\n"; my $cpu_info = $ks->{cpu_info} or die "can not read cpu_info kstat\n"; ### # 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 following should loop over a single entry cpu_info$id foreach my $name (keys %$info) { my $ci = $info->{$name}; # cpu_info kstat for specific CPU $cpu_list{$id} = $ci; my $chip_id = getinfo($ci, 'chip_id'); # Collect CPUs within the chip push (@{$chips{$chip_id}}, $id) if defined $chip_id; push (@cpus, $id); } } ## # Figure oput 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 # my @cpu_args = scalar @ARGV ? map { expand $_ } @ARGV : @cpus; @cpu_args = uniqsort(intersect(\@cpus, \@cpu_args)); @cpus = @cpu_args; usage("must specify exactly one processor if -s used") if (($verbosity == 0) && (scalar @cpus != 1)); ## # Look at all possible xxx_id properties and remove those that have NCPU values # or one value. Sort the rest. ## # Drop ids which have the same number of entries as number of CPUs # # Build the component tree for the system # if ($phys_verbose) { ### # Get list of existing xxx_id entries in cpu_info kstats into @id_list # Read kstat for the first CPU and collect list of fields. # Remove all fields except those matching $valid_id_exp # my $id = (keys %$cpu_info)[0]; my $info = $cpu_info->{$id}; my $name = (keys %$info)[0]; my $ci = $info->{$name}; # kstat for a specific CPU @id_list = grep(/$valid_id_exp/, keys(%$ci)); my $ncpus = scalar @cpus; my @chips = property_list('chip_id', @cpus); my $nchips = scalar @chips; @id_list = grep { my @ids = property_list($_, @cpus); my $nids = scalar @ids; ($_ eq "chip_id") || (($nids > $nchips) && ($nids > 1) && ($nids < $ncpus)); } @id_list; ## # Sort @id_list by number of values in it. # @id_list = sort { my @left = property_list($a, @cpus); my @right = property_list($b, @cpus); (scalar @left) <=> (scalar @right) } @id_list; $ctree = build_component_tree(\@cpus, \@id_list); } # Walk all CPUs specified and print information about them. # If phys_view is set, collect data to be printed later foreach my $id (@cpus) { my $cpu = $cpu_list{$id} or next; # Get CPU state and its modification time my $mtime = getinfo ($cpu, "state_begin"); my $mstring = strftime "%m/%d/%Y %T", localtime($mtime); my $status = getinfo ($cpu, "state") || "unknown"; if ($phys_view) { # Get list of physical processors spanning CPUs from the arguments. push (@chip_list, getinfo($cpu, "chip_id")); } elsif (! $verbosity) { # Print 1 if CPU is online, 0 if offline. printf("%d\n", $status =~ /on-line/ ? 1 : 0); } elsif (! ($verbosity & 2)) { printf ("%d\t%-8s since %s\n", $id, $status, $mstring); } else { my $fpu = getinfo($cpu, "fpu_type"); my $fpu_prefix = ($fpu =~ /^[aeiouy]/) ? "an" : "a"; print "Status of virtual processor $id as of: "; print strftime("%m/%d/%Y %T", localtime()); print "\n"; print " $status since $mstring.\n"; print " The ", getinfo ($cpu, "cpu_type"), " processor operates at ", getinfo ($cpu, "clock_MHz"), " MHz,\n"; print "\tand has $fpu_prefix $fpu floating point processor.\n"; } } # Physical view print if ($phys_view) { # Remove duplicates from the chip list. @chip_list = uniqsort @chip_list; 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 {getinfo ($_, "state") =~ /on-line/ } map { $cpu_list{$_} } @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 = getinfo ($cpu, "brand"); my $impl = getinfo ($cpu, "implementation"); # Remove cpuid and chipid information from implementation string # and print it. $impl =~ s/(cpuid|chipid)\s*\w+\s+// if $impl; $brand = '' unless defined $brand; $brand = '' if $impl && $impl =~ /^$brand/; # List of CPUs on a chip my $cpu_name = plurize("processor", $ncpus); # Collapse range of CPUs into a-b string my $cl = collapse(@chip_cpus); my $childname = $chipref->{name}; if (! $childname) { print "The $cname has $ncpus "; print "virtual $cpu_name ($cl)\n"; print " $impl\n" if $impl; print "\t$brand\n" if $brand; } else { # Get child count my $nchildren = scalar(keys(%{$chipref->{values}})); $childname = plurize(id_translate($childname), $nchildren); print "The $cname has $nchildren $childname"; print " and $ncpus virtual $cpu_name ($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 0; ###################################################################### # 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