#
# 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
# 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
#
#
#
#
# Pg.pm provides object-oriented interface to the Solaris
# Processor Group kstats
#
# See comments in the end
#
use strict;
use warnings;
use Carp;
use Errno;
our $VERSION = '1.1';
#
# Currently the OS does not have the root PG and PGs constitute a forest of
# small trees. This module gathers all such trees under one root with ID zero.
# If the root is present already, we do not use faked root.
#
my $ROOT_ID = 0;
#
# PG_NO_PARENT means that kstats have PG parent ID and it is set to -1
# PG_PARENT_UNDEF means that kstats have no PG parent ID
#
use constant {
PG_NO_PARENT => -1,
PG_PARENT_UNDEF => -2,
};
#
# Sorting order between different sharing relationships. This order is used to
# break ties between PGs with the same number of CPUs. If there are two PGs with
# the same set of CPUs, the one with the higher weight will be the parent of the
# one with the lower weight.
#
my %relationships_order = (
'CPU_PM_Idle_Power_Domain' => 1,
'Integer_Pipeline' => 2,
'Cache' => 3,
'CPU_PM_Active_Power_Domain' => 4,
'Floating_Point_Unit' => 5,
'Data_Pipe_to_memory' => 6,
'Memory' => 7,
'Socket' => 8,
'System' => 9,
);
#
# Object interface to the library. These are methods that can be used by the
# module user.
#
#
# Create a new object representing PG
# All the heavy lifting is performed by _init function.
# This function performs all the Perl blessing magic.
#
# The new() method accepts arguments in the form of a hash. The following
# subarguments are supported:
#
# -cpudata # Collect per-CPU data from kstats if this is T
# -tags # Match PGs to physical relationships if this is T
# -swload # Collect software CPU load if this is T
# -retry # how many times to retry PG initialization when it fails
# -delay # Delay in seconds between retries
#
# The arguments are passed to _init().
#
sub new
{
my $class = shift;
my %args = @_;
#
# If PG initialization fails with EAGAIN error and the caller requested
# retries, retry initialization.
#
$retry_count--) {
select(undef,undef,undef, $retry_delay);
}
if ($self) {
}
return ($self);
}
#
# Functions below use internal function _pg_get which returns PG hash reference
# corresponding to PG ID specified or 'undef' if the PG can't be found.
#
#
# All methods return 'undef' in scalar context and an empty list in list
# context when unrecoverable errors are detected.
#
#
# Return the root ID of PG hierarchy
#
sub root
{
my $self = shift;
return ($ROOT_ID);
}
#
# Return list of all pgs numerically sorted In scalar context return number of
# PGs
#
sub all
{
my $self = shift;
}
#
# Return list of all pgs by walking the tree depth first.
#
sub all_depth_first
{
my $self = shift;
}
#
# Return list of all pgs by walking the tree breadth first.
#
{
my $self = shift;
}
#
# Return list of CPUs in the PG specified
# CPUs returned are numerically sorted
# In scalar context return number of CPUs
#
sub cpus
{
}
#
# Return a parent for a given PG
# Returns undef if there is no parent
#
sub parent
{
}
#
# Return list of children for a given PG
# In scalar context return list of children
#
sub children
{
}
#
# Return sharing name for the PG
#
sub sh_name
{
}
#
# Return T if specified PG ID is a leaf PG
#
sub is_leaf
{
}
#
# Return leaf PGs
#
sub leaves
{
my $self = shift;
}
#
# Update varying data in the snapshot
#
sub update
{
my $self = shift;
#
# Walk PG kstats and copy updated data from kstats to the snapshot
#
$self->{USE_OLD_KSTATS});
return unless $pg_ks;
#
# Update PG from kstats
#
}
#
# Update software load for each CPU
#
#
# Get hardware load per CPU
#
if ($self->{GET_CPU_DATA}) {
}
return (1);
}
#
# Return list of physical tags for the given PG
#
sub tags
{
}
#
# Return list of sharing relationships in the snapshot Relationships are sorted
# by the level in the hierarchy If any PGs are given on the command line, only
# return sharing relationships for given PGs, but still keep them sorted.
#
{
scalar @_ or _usage("sharing_relationships(cookie, [pg, ...])");
my $self = shift;
if (scalar @_ > 0) {
#
# Caller specified PGs, remove any PGs not in caller's list
#
my %seen;
map { $seen{$_} = 1 } @_;
# Remove any PGs not provided by user
}
}
#
# Return PG generation number. If PG is specified in the argument, return its
# generation, otherwise return snapshot generation.
# Snapshot generation is calculated as the total of PG generations
#
sub generation
{
my $self = shift;
if (scalar @_ == 0) {
my @generations = map { $_->{generation} }
return (sum(@generations));
} else {
my $id = shift;
return ($pg->{generation});
}
}
#
# Return level of PG in the tree, starting from root.
# PG level is cached in the $pg->{level} field.
#
sub level
{
my $self = shift;
my $pgid = shift;
while ($parent) {
}
}
#
# Return T if PG supports utilization We assume that utilization is supported by
# PG if it shows any non-zero time in util_time_running. It is possible that the
# same condition may be caused by cpustat(1) running ever since PG was created,
# but there is not much we can do about it.
#
sub has_utilization
{
}
#
# Return utilization for the PG
# Utilization is a difference in utilization value between two snapshots.
# We can only compare utilization between PGs having the same generation ID.
#
sub utilization
{
my $c1 = shift;
my $c2 = shift;
my $id = shift;
#
# Since we have two cookies, update capacity in both
#
#
# Nothing to return if one of the utilizations wasn't measured
#
#
# Verify generation IDs
#
}
#
# Return an estimate of PG capacity Capacity is calculated as the maximum of
# observed utilization expressed in units per second or maximum CPU frequency
# for all CPUs.
#
# We store capacity per sharing relationship, assuming that the same sharing has
# the same capacity. This may not be true for heterogeneous systems.
#
sub capacity
{
my $self = shift;
my $pgid = shift;
}
#
# Return accuracy of utilization calculation between two snapshots The accuracy
# is determined based on the total time spent running and not running the
# counters. If T1 is the time counters were running during the period and T2 is
# the time they were turned off, the accuracy is T1 / (T1 + T2), expressed in
# percentages.
#
sub accuracy
{
my $c1 = shift;
my $c2 = shift;
my $id = shift;
my $trun;
my $tstop;
# Both PGs should have the same generation
#
# Get time spent with running and stopped counters
#
$pg1->{util_time_running});
$pg1->{util_time_stopped});
#
# Calculate accuracy as percentage
#
return ($accuracy);
}
#
# Return time difference in seconds between two snapshots
#
sub tdelta
{
my $c1 = shift;
my $c2 = shift;
my $id = shift;
return ($delta);
}
#
# Return software utilization between two snapshots
# In scalar context return software load as percentage.
# In list context return a list (USER, SYSTEM, IDLE, SWLOAD)
# All loads are returned as percentages
#
sub sw_utilization
{
my $c1 = shift;
my $c2 = shift;
my $id = shift;
my $idle = 0;
my $user = 0;
my $sys = 0;
my $total = 0;
my $swload = 0;
}
# Prevent division by zero
}
#
# Return utilization for the PG for a given CPU
# Utilization is a difference in utilization value between two snapshots.
# We can only compare utilization between PGs having the same generation ID.
#
sub cpu_utilization
{
my $c1 = shift;
my $c2 = shift;
my $id = shift;
my $cpu = shift;
my $idle = 0;
my $user = 0;
my $sys = 0;
my $swtotal = 0;
my $swload = 0;
#
# Since we have two cookies, update capacity in both
#
#
# Nothing to return if one of the utilizations wasn't measured
#
#
# Nothing to return if CPU data is missing
#
#
# Verify generation IDs
#
#
# Get data for the given CPU
#
#
# Get time spent with running and stopped counters
#
$cpudata2->{util_time_running});
$cpudata2->{util_time_stopped});
#
# Calculate accuracy as percentage
#
$utilization /= $capacity;
$utilization *= 100;
# Prevent division by zero
}
return (wantarray() ?
$utilization);
}
#
# online_cpus(kstat)
# Return list of on-line CPUs
#
sub online_cpus
{
my $self = shift or return;
my @cpus = grep {
} keys %{$cpu_info};
}
#
# Support methods
#
# The following methods are not PG specific but are generally useful for PG
# interface consumers
#
#
# Sort the list numerically
#
sub nsort
{
shift;
return (_nsort(@_));
}
#
# Return the input list with duplicates removed.
# Should be used in list context
#
sub uniq
{
shift;
return (_uniq(@_));
}
#
# Sort list numerically and remove duplicates
# Should be called in list context
#
sub uniqsort
{
shift;
return (_uniqsort(@_));
}
#
# Expand all arguments and present them as a numerically sorted list
# x,y is expanded as (x y)
# 1-3 ranges are expandes as (1 2 3)
#
sub expand
{
shift;
}
#
# Consolidate consecutive ids as start-end
# Input: list of ids
# Output: string with space-sepated cpu values with ranges
# collapsed as x-y
#
sub id_collapse
{
shift;
return _collapse(@_);
}
#
# Return elements of the second list not present in the first list. Both lists
# are passed by reference.
#
sub set_subtract
{
shift;
return (_set_subtract(@_));
}
#
# 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
{
shift;
return (_set_intersect(@_));
}
#
# Return elements of the second list not present in the first list. Both lists
# are passed by reference.
#
sub _set_subtract
{
my %seen; # Set to 1 for everything in the first list
# Create a hash indexed by elements in @left with ones as a value.
# Find members of @right present in @left
}
#
# END OF PUBLIC INTERFACE
#
#
# INTERNAL FUNCTIONS
#
#
# _usage(): print error message and terminate the program.
#
sub _usage
{
my $msg = shift;
Carp::croak "Usage: Sun::Solaris::Pg::$msg";
}
#
# Sort the list numerically
# Should be called in list context
#
sub _nsort
{
return (sort { $a <=> $b } @_);
}
#
# Return the input list with duplicates removed.
# Should be used in list context
#
sub _uniq
{
my %seen;
return (grep { ++$seen{$_} == 1 } @_);
}
#
# Sort list numerically and remove duplicates
# Should be called in list context
#
sub _uniqsort
{
return (sort { $a <=> $b } _uniq(@_));
}
# Get PG from the snapshot by id
sub _pg_get
{
my $self = shift;
my $pgid = shift;
return unless defined $pgid;
}
#
# Copy data from kstat representation to our representation
# Arguments:
# PG kstat
# Reference to the list of CPUs.
# Any CPUs in the PG kstat not present in the CPU list are ignored.
#
{
my $pg_ks = shift;
my $all_cpus = shift;
my %all_cpus;
my $pg = ();
#
# Mark CPUs available
#
return unless $pg_ks;
#
# Convert CPU list in the kstat from x-y,z form to the proper list
#
#
# Remove any CPUs not present in the arguments
#
#
# Do not create PG unless it has any CPUs
#
return unless scalar @cpus;
#
# Copy data to the $pg structure
#
#
# Replace spaces with underscores in sharing names
#
return $pg;
}
#
# Create fake root PG with all CPUs
# Arguments: list of CPUs
#
sub _pg_create_root
{
my $pg = ();
my @cpus = @_;
return ($pg);
}
#
# _pg_all_from_kstats(SNAPSHOT)
# Extract all PG information from kstats
#
{
my $self = shift;
return unless $ks;
my $pgtree = ();
#
# Walk all PG kstats and copy them to $pgtree->{$id}
#
$self->{USE_OLD_KSTATS});
next unless $pg_ks;
}
#
# OS does not have root PG, so create one.
#
}
#
# Construct parent-child relationships between PGs
#
#
# Get list of PGs sorted by number of CPUs
# If two PGs have the same number of CPUs, sort by relationship order.
#
my @lineage = sort {
_relationship_order($a->{sh_name}) <=>
_relationship_order($b->{sh_name})
} values %$pgtree;
#
# For each PG in the lineage discover its parent if it doesn't have one.
#
for (my $i = 0; $i < scalar @lineage; $i++) {
#
# Ignore PGs which already have parent in kstats
#
next if ($parent >= PG_NO_PARENT);
#
# Walk the lineage, ignoring any CPUs with the same number of
# CPUs
for (my $j = $i + 1; $j < scalar @lineage; $j++) {
last;
}
}
}
#
# Find all top-level PGs and put them under $root
#
}
#
# Now that we know parents, for each parent add all direct children to
# their parent sets
#
next unless defined $parentid;
}
return ($pgtree);
}
#
# Read kstats and initialize PG object
# Collect basic information about cmt_pg
# Add list of children and list of CPUs
# Returns the hash reference indexed by pg id
#
# The _init() function accepts arguments in the form of a hash. The following
# subarguments are supported:
#
# -cpudata # Collect per-CPU data from kstats if this is T
# -tags # Match PGs to physical relationships if this is T
# -swload # Collect software CPU load if this is T
sub _init
{
return unless $ks;
my %args = @_;
my $self;
my @frequencies;
'pg_cpu' : 'pg_hw_perf_cpu';
keys (%$ks));
# Get list of PG-related kstats
} else {
$! = 48;
}
return;
}
#
# Mapping of cores and chips to CPUs
#
my $hw_mapping;
#
# Get list of all CPUs
#
#
# @all-cpus is a list of all cpus
#
#
# Save list of all CPUs in the snapshot
#
#
# Find CPUs for each socket and chip
# Also while we scan CPU kstats, get maximum frequency of each CPU.
#
next unless $ci;
if defined $core_id;
if defined $chip_id;
# Read CPU frequencies separated by commas
# Calculate maximum frequency for the snapshot.
}
#
# Convert kstats to PG tree
#
#
# Find capacity estimate per sharing relationship
#
}
if ($get_tags) {
#
# Walk all PGs and mark all PGs that have corresponding hardware
# entities (system, chips, cores).
#
next unless scalar @cpus > 1;
#
# PG has all CPUs in the system.
#
}
# CPUs for this entity
#
# PG has exactly the same CPUs
#
}
}
}
}
}
#
# Save software load for each CPU
#
if ($get_swload) {
}
#
# Collect per-CPU utilization data if requested
#
if ($get_cpu_data) {
}
#
# Verify that in the end we have the same PG generation for each PG
#
if (! _same_generation($self)) {
return;
}
return ($self);
}
#
# Verify that topology is the same as at the time snapshot was created
#
sub _same_generation
{
my $self = shift;
return (0) unless $self;
$self->{USE_OLD_KSTATS});
return unless $pg_ks;
}
return (1);
}
#
# Update capacity for both PGs
#
sub _capacity_update
{
my $c1 = shift;
my $c2 = shift;
$max_rate = $utilization if
}
}
#
# Return list of PGs breadth first
#
{
my $p = shift;
# Nothing to do if list is empty
return unless scalar (@_);
return (map { ($_, _walk_depth_first ($p, $p->children($_))) } @_);
}
#
# Return list of PGs breadth first
#
{
my $p = shift;
# Nothing to do if list is empty
return unless scalar (@_);
return (@_, _walk_breadth_first($p, map { $p->children($_) } @_));
}
#
# Given the kstat reference (already hashed by module name) and PG ID return the
# corresponding kstat.
#
sub _kstat_get_pg
{
my $mod = shift;
my $pgid = shift;
my $use_old_kstats = shift;
return ($instances[0]);
}
######################################################################
# Set routines
#######################################################################
#
# Return T if one list contains all the elements of another list.
# All lists are passed by reference
#
sub _is_subset
{
my %seen; # Set to 1 for everything in the first list
# Put the shortest list in $left
# Create a hash indexed by elements in @right with ones as a value.
# Find members of @left not present in @right
return (!scalar(@extra));
}
sub _is_member
{
my $set = shift;
my $element = shift;
my %seen;
}
#
# Return T if C1 and C2 contain the same elements
#
sub _set_equal
{
my $c1 = shift;
my $c2 = shift;
}
#
# 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 _set_intersect
{
my %seen; # Set to 1 for everything in the first list
# Put the shortest list in $left
# Create a hash indexed by elements in @left with ones as a value.
# Find members of @right present in @left
}
#
# 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
{
# Skip the first argument if it is the object reference
shift if ref $@[0] eq 'HASH';
my $arg = shift;
return unless defined $arg;
return unless defined $arg;
if ($arg =~ m/^\d+$/) {
# single number
return ($arg);
} elsif ($arg =~ m/^(\d+)\-(\d+)$/) {
# Reverse the interval if start > end
} else {
return $arg;
}
return;
}
#
# Consolidate consecutive ids as start-end
# Input: list of ids
# Output: string with space-sepated cpu values with ranges
# collapsed as x-y
#
sub _collapse
{
return ('') unless @_;
my $result = '';
if (!$el =~ /^\d+$/) {
$result = "$result $el";
#
# Got consecutive ID, so extend end of range without
# printing anything since the range may extend further
#
} else {
#
# Next ID is not consecutive, so print IDs gotten so
# far.
#
$result = "$result $start-$end";
$result = "$result $start $end";
} else { # same value
$result = "$result $start";
}
# Try finding consecutive range starting from this ID
}
}
# Print last ID(s)
if (! ($end =~ /^\d+$/)) {
$result = "$result $end";
$result = "$result $start-$end";
$result = "$result $start $end";
} else {
$result = "$result $start";
}
# Remove any spaces in the beginning
$result =~ s/^\s+//;
return ($result);
}
#
# get relationship order from relationship name.
# return 0 for all unknown names.
#
{
my $name = shift;
}
#
# Get software load for each CPU from kstats
# Argument: kstat reference
# Returns: reference to the hash with
# cpu_idle, cpu_user, cpu_sys keys.
#
sub _get_sw_cpu_load
{
my $ks = shift or return;
my $loads;
}
return ($loads);
}
#
# Get software load for each CPU from kstats
# Arguments:
# pgtree reference
# kstat reference
#
# Returns: nothing
# Stores CPU load in the $pg->{cpudata} hash for each PG
#
sub _get_hw_cpu_load
{
my $self = shift;
my $cpu;
my $pg_id;
foreach my $hw (keys %$cpu_hw_data) {
#
# Only consider information for this PG
#
$cpudata->{generation};
}
}
}
}
1;
#
# The information about PG hierarchy is contained in a object return by the
# new() method.
#
# This module can deal with old PG kstats that have 'pg' and 'pg_cpu' as module
# names as well as new PG kstats which use 'pg_hw_perf' and ''pg_hw_perf_cpu' as
# the module name.
#
# The object contains the following fields:
#
# CPUS List of all CPUs present.
# CAPACITY Estimate of capacity for each sharing
# PGTREE The PG tree. See below for the tree representation.
#
# PG_MODULE Module name for the PG kstats. It is either 'pg' for
# old style kstats, or 'pg_hw_perf' for new style kstats.
#
# MAX_FREQUENCY Maximum CPU frequency
# USE_OLD_KSTATS True if we are dealing with old style kstats
# KSTAT The kstat object used to generate this hierarchy.
#
# The PG tree is represented as a hash table indexed by PG ID. Each element of
# the table is the hash reference with the following fields:
#
# children Reference to the list of children PG IDs
# cpus Reference to the list of cpu IDs in the PG
# current_rate Current utilization rate
# generation PG generation
# id PG id
# ncpus number of CPUs in the PG
# parent PG parent id, or -1 if there is none.
# sh_name Sharing name
# snaptime Snapshot time
# util Hardware utilization
# util_rate_max Maximum utilization rate
# util_time_running Time (in nanoseconds) when utilization data is collected
# util_time_stopped Time when utilization data is not collected
#
# The fields (with the exception of 'children') are a copy of the data from
# kstats.
#
# The PG hierarchy in the kernel does not have the root PG. We simulate the root
# (System) PG which is the parent of top level PGs in the system. This PG always
# has ID 0.
#