#! /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 (c) 2010, Oracle and/or its affiliates. All rights reserved. # # # pginfo - tool for displaying Processor Group information # use warnings; use strict; use File::Basename; use Errno; use POSIX qw(locale_h); use Getopt::Long qw(:config no_ignore_case bundling auto_version); use List::Util qw(first max min); use Sun::Solaris::Utils qw(textdomain gettext); use Sun::Solaris::Pg; # # Constants # # It is possible that wnen trying to parse PG information, PG generation changes # which will cause PG new method to fail with errno set to EAGAIN In this case # we retry open up to RETRY_COUNT times pausing RETRY_DELAY seconds between each # retry. # # When printing PGs we print them as a little tree with each PG shifted by # LEVEL_OFFSET from each parent. For example: # # PG RELATIONSHIP CPUs # 0 System 0-7 # 3 Socket 0 2 4 6 # 2 Cache 0 2 4 6 # use constant { VERSION => 1.1, LEVEL_OFFSET => 1, RETRY_COUNT => 4, RETRY_DELAY => 0.25, }; # # Return codes # # 0 Successful completion. # # 1 An error occurred. # # 2 Invalid command-line options were specified. # use constant { E_SUCCESS => 0, E_ERROR => 1, E_USAGE => 2, }; # Set message locale setlocale(LC_ALL, ""); textdomain(TEXT_DOMAIN); # Get script name for error messages our $cmdname = basename($0, ".pl"); # # Process options # my $do_cpulist; # -C - Show CPU IDs my $do_cpus; # -c - Treat args as CPU IDs my $do_physical; # -p - Show physical relationships my $do_sharing_only; # -S - Only show sharing relationships my $do_tree; # -T - Show ASCII tree my $do_usage; # -h - Show usage my $do_version; # -V - Show version my $script_mode; # -I - Only show IDs my $verbose = 0; # -v - Verbose output my @sharing_filter; # -r string,... my @sharing_filter_neg; # -R string,... # Exit code my $rc = E_SUCCESS; # Parse options from the command line GetOptions("cpus|c" => \$do_cpus, "idlist|I" => \$script_mode, "cpulist|C" => \$do_cpulist, "physical|p" => \$do_physical, "help|h|?" => \$do_usage, "sharing|s" => \$do_sharing_only, "relationship|r=s" => \@sharing_filter, "norelationship|R=s" => \@sharing_filter_neg, "tree|topology|T" => \$do_tree, "version|V" => \$do_version, "verbose+" => \$verbose, "v+" => \$verbose, ) || usage(E_USAGE); # Print usage message when -h is given usage(E_SUCCESS) if $do_usage; if ($do_version) { # # Print version information and exit # printf gettext("%s version %s\n"), $cmdname, VERSION; exit(E_SUCCESS); } # # Verify options compatibility # if ($script_mode && $do_cpulist) { printf STDERR gettext("%s: options -I and -C can not be used at the same time\n"), $cmdname; usage(E_USAGE); } if (($script_mode || $do_cpulist) && ($do_physical || $do_sharing_only || $do_tree)) { printf STDERR gettext("%s: options -C and -I can not be used with -p -s or -T\n"), $cmdname; usage(E_USAGE); } if ($do_physical && $do_sharing_only) { printf STDERR gettext("%s: option -p can not be used with -s\n"), $cmdname; usage(E_USAGE); } if ($do_tree && $do_sharing_only) { printf STDERR gettext("%s: option -T can not be used with -s\n"), $cmdname; usage(E_USAGE); } if ($verbose && !($script_mode || $do_cpulist || $do_sharing_only)) { $do_tree = 1; $do_physical = 1; } # # Get PG information # my $p = Sun::Solaris::Pg->new(-tags => $do_physical, -retry => RETRY_COUNT, '-delay' => RETRY_DELAY); if (!$p) { printf STDERR gettext("%s: can not obtain Processor Group information: $!\n"), $cmdname; exit(E_ERROR); } # # Convert -[Rr] string1,string2,... into list (string1, string2, ...) # @sharing_filter = map { split /,/ } @sharing_filter; @sharing_filter_neg = map { split /,/ } @sharing_filter_neg; # # Get list of all PGs in the system # my @all_pgs = $p->all_depth_first(); if (scalar(@all_pgs) == 0) { printf STDERR gettext("%s: this system does not have any Processor groups\n"), $cmdname; exit(E_ERROR); } # # @pgs is the list of PGs we are going to work with after all the option # processing # my @pgs = @all_pgs; # # get list of all CPUs in the system by looking at the root PG cpus # my @all_cpus = $p->cpus($p->root()); # # If there are arguments in the command line, treat them as either PG IDs or as # CPUs that should be converted to PG IDs. # Arguments can be specified as x-y x,y,z and use special keyword 'all' # if (scalar @ARGV) { # # Convert 'all' in arguments to all CPUs or all PGs # my @args; my @all = $do_cpus ? @all_cpus : @all_pgs; @args = map { $_ eq 'all' ? @all : $_ } @ARGV; # Expand any x-y,z ranges @args = $p->expand(@args); if ($do_cpus) { # @bad_cpus is a list of invalid CPU IDs my @bad_cpus = $p->set_subtract(\@all_cpus, \@args); if (scalar @bad_cpus) { printf STDERR gettext("%s: Invalid processor IDs %s\n"), $cmdname, $p->id_collapse(@bad_cpus); $rc = E_ERROR; } # # List of PGs is the list of any PGs that contain specified CPUs # @pgs = grep { my @cpus = $p->cpus($_); scalar($p->intersect(\@cpus, \@args)); } @all_pgs; } else { # @pgs is a list of valid CPUs in the arguments @pgs = $p->intersect(\@all_pgs, \@args); # @bad_pgs is a list of invalid PG IDs my @bad_pgs = $p->set_subtract(\@all_pgs, \@args); if (scalar @bad_pgs) { printf STDERR gettext("%s: Invalid PG IDs %s\n"), $cmdname, $p->id_collapse(@bad_pgs); $rc = E_ERROR; } } } # # Now we have list of PGs to work with. Now apply filtering. First list only # those matching -R # @pgs = grep { list_match($p->sh_name($_), @sharing_filter) } @pgs if scalar @sharing_filter; # Remove any that doesn't match -r @pgs = grep { !list_match($p->sh_name($_), @sharing_filter_neg) } @pgs if scalar @sharing_filter_neg; # Do we have any PGs left? if (scalar(@pgs) == 0) { printf STDERR gettext("%s: no processor groups matching command line arguments %s\n"), $cmdname, "@ARGV"; exit(E_ERROR); } # # Global list of PGs that should be excluded from the output - it is only used # when tree mode is specified. # my @exclude_pgs; if ($do_tree) { @exclude_pgs = grep { list_match($p->sh_name($_), @sharing_filter_neg) } @all_pgs; # # In tree mode add PGs that are in the lineage of given PGs # @pgs = pg_lineage($p, @pgs) } # # -I is specified, print list of all PGs # if ($script_mode) { if (scalar(@pgs)) { @pgs = sort { $a <=> $b } @pgs; print "@pgs\n"; } else { print "none\n"; } exit($rc); } # # -C is specified, print list of all CPUs belonging to PGs # if ($do_cpulist) { my @cpu_list = $p->uniqsort(map { $p->cpus($_) } @pgs); print "@cpu_list\n"; exit($rc); } # Mapping of relationships to list of PGs my %pgs_by_relationship; # Maximum length of all sharing names my $max_sharename_len = length('RELATIONSHIP'); # Maximum length of PG ID my $max_pg_len = length(max(@pgs)) + 1; # # For calculating proper offsets we need to know minimum and maximum level for # all PGs # my @levels = map { $p->level($_) } @pgs; my $maxlevel = max(@levels); my $minlevel = min(@levels); # Calculate maximum string length that should be used to represent PGs foreach my $pg (@pgs) { my $name = $p->sh_name ($pg) || "unknown"; my $level = $p->level($pg) || 0; if ($do_physical) { my $tags = $p->tags($pg); $name = "$name [$tags]" if $tags; } my $length = length($name) + $level - $minlevel; $max_sharename_len = $length if $length > $max_sharename_len; } if ($do_sharing_only) { # # -s - only print sharing relationships # Get list of sharing relationships my @relationships = $p->sharing_relationships(@pgs); if ($verbose) { printf "%-${max_sharename_len}s %s\n", 'RELATIONSHIP', 'PGs'; foreach my $rel (@relationships) { my @pg_rel = grep { $p->sh_name($_) eq $rel } @pgs; my $pg_rel = $p->id_collapse (@pg_rel); $pgs_by_relationship{$rel} = \@pg_rel; } } foreach my $rel (@relationships) { printf "%-${max_sharename_len}s", $rel; if ($verbose) { my @pgs = @{$pgs_by_relationship{$rel}}; my $pgs = $p->id_collapse (@pgs); print ' ', $pgs; } print "\n"; } # we are done exit($rc); } # # Print PGs either in list form or tree form # if (!$do_tree) { my $header; $header = sprintf "%-${max_pg_len}s %-${max_sharename_len}s" . " %s\n", 'PG', 'RELATIONSHIP', 'CPUs'; print $header; map { pg_print ($p, $_) } @pgs; } else { # # Construct a tree from PG hierarchy and prune any PGs that are # specified with -R option # my $pg_tree = pg_make_tree($p); map { pg_remove_from_tree($pg_tree, $_) } @exclude_pgs; # Find top-level PGs my @top_level = grep { $pg_tree->{$_} && !defined($pg_tree->{$_}->{parent}) } @pgs; # Print each top-level node as ASCII tree foreach my $pg (@top_level) { my $children = $pg_tree->{$pg}->{children}; my @children = $children ? @{$children} : (); @children = $p->intersect(\@children, \@pgs); pg_print_tree($p, $pg_tree, $pg, '', '', scalar @children); } } # We are done! exit($rc); ###################################################################### # Internal functions # # # pg_print(cookie, pg) # print PG information in list mode # sub pg_print { my $p = shift; my $pg = shift; my $sharing = $p->sh_name($pg); if ($do_physical) { my $tags = $p->tags($pg); $sharing = "$sharing [$tags]" if $tags; } my $level = $p->level($pg) - $minlevel; $sharing = (' ' x (LEVEL_OFFSET * $level)) . $sharing; my $cpus = $p->cpus($pg); printf "%-${max_pg_len}d %-${max_sharename_len}s", $pg, $sharing; print " $cpus"; print "\n"; } # # pg_showcpus(cookie, pg) # Print CPUs in the current PG # sub pg_showcpus { my $p = shift; my $pg = shift; my @cpus = $p->cpus($pg); my $ncpus = scalar @cpus; return 0 unless $ncpus; my $cpu_string = $p->cpus($pg); return (($ncpus == 1) ? "CPU: $cpu_string": "CPUs: $cpu_string"); } # # pg_print_node(cookie, pg) # print PG as ASCII tree node # sub pg_print_node { my $p = shift; my $pg = shift; my $sharing = $p->sh_name($pg); if ($do_physical) { my $tags = $p->tags($pg); $sharing = "$sharing [$tags]" if $tags; } print "$pg ($sharing)"; my $cpus = pg_showcpus($p, $pg); print " $cpus"; print "\n"; } # # pg_print_tree(cookie, tree, pg, prefix, childprefix, npeers) # print ASCII tree of PGs in the tree # prefix should be used for the current node, childprefix for children nodes # npeers is the number of peers of the current node # sub pg_print_tree { my $p = shift; my $pg_tree = shift; my $pg = shift; return unless defined ($pg); # done! my $prefix = shift; my $childprefix = shift; my $npeers = shift; # Get list of my children my $children = $pg_tree->{$pg}->{children}; my @children = $children ? @{$children} : (); @children = $p->intersect(\@children, \@pgs); my $nchildren = scalar @children; my $printprefix = "$childprefix"; my $printpostfix = $npeers ? "| " : " "; my $bar = $npeers ? "|" : "`"; print $childprefix ? $childprefix : ""; print $prefix ? "$bar" . "-- " : ""; pg_print_node ($p, $pg); my $new_prefix = $npeers ? $prefix : " "; # Print the subtree with a new offset, starting from each child map { pg_print_tree($p, $pg_tree, $_, "| ", "$childprefix$new_prefix", --$nchildren) } @children; } # # list_match(arg, list) # Return arg if argument matches any of the elements on the list # sub list_match { my $arg = shift; return first { $arg =~ m/$_/i } @_; } # # Make a version of PG parent-children relationships from cookie # sub pg_make_tree { my $p = shift; my $pg_tree = (); foreach my $pg ($p->all()) { my @children = $p->children($pg); $pg_tree->{$pg}->{parent} = $p->parent($pg); $pg_tree->{$pg}->{children} = \@children; } return ($pg_tree); } # # pg_remove_from_tree(tree, pg) # Prune PG from the tree # sub pg_remove_from_tree { my $pg_tree = shift; my $pg = shift; my $node = $pg_tree->{$pg}; return unless $node; my @children = @{$node->{children}}; my $parent = $node->{parent}; my $parent_node; # # Children have a new parent # map { $pg_tree->{$_}->{parent} = $parent } @children; # # All children move to the parent (if there is one) # if (defined($parent) && ($parent_node = $pg_tree->{$parent})) { # # Merge children from parent and @children list # my @parent_children = @{$parent_node->{children}}; # # Remove myself from parent children # @parent_children = grep { $_ != $pg } @parent_children; @parent_children = $p->nsort(@parent_children, @children); $parent_node->{children} = \@parent_children; } # Remove current node delete $pg_tree->{$pg}; } # # For a given list of PGs return the full lineage # sub pg_lineage { my $p = shift; return unless scalar @_; my @parents = grep { defined($_) } map { $p->parent ($_) } @_; return ($p->uniq(@_, @parents, pg_lineage ($p, @parents))); } # # Print usage information and exit with the return code specified # sub usage { my $rc = shift; printf STDERR gettext("Usage:\t%s [-T] [-p] [-v] [-r string] [-R string] [pg ... | -c processor_id ...]\n\n"), $cmdname; printf STDERR gettext("\t%s -s [-v] [-r string] [-R string] [pg ... | -c processor_id ...]\n\n"), $cmdname; printf STDERR gettext("\t%s -C | -I [-r string] [-R string] [pg ... | -c processor_id ...]\n\n"), $cmdname; printf STDERR gettext("\t%s -h\n\n"), $cmdname; exit($rc); } __END__