xref: /illumos-gate/usr/src/cmd/perl/contrib/Sun/Solaris/Pg/Pg.pm (revision 9b9d39d2a32ff806d2431dbcc50968ef1e6d46b2)
1#! /usr/perl5/bin/perl
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22
23#
24# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
25#
26
27#
28# Pg.pm provides object-oriented interface to the Solaris
29# Processor Group kstats
30#
31# See comments in the end
32#
33
34package Sun::Solaris::Pg;
35
36use strict;
37use warnings;
38use Sun::Solaris::Kstat;
39use Carp;
40use Errno;
41use List::Util qw(max sum);
42
43our $VERSION = '1.1';
44
45#
46# Currently the OS does not have the root PG and PGs constitute a forest of
47# small trees. This module gathers all such trees under one root with ID zero.
48# If the root is present already, we do not use faked root.
49#
50
51my $ROOT_ID = 0;
52
53#
54# PG_NO_PARENT means that kstats have PG parent ID and it is set to -1
55# PG_PARENT_UNDEF means that kstats have no PG parent ID
56#
57use constant {
58	PG_NO_PARENT	=> -1,
59	PG_PARENT_UNDEF => -2,
60};
61
62#
63# Sorting order between different sharing relationships. This order is used to
64# break ties between PGs with the same number of CPUs. If there are two PGs with
65# the same set of CPUs, the one with the higher weight will be the parent of the
66# one with the lower weight.
67#
68my %relationships_order = (
69			   'CPU_PM_Idle_Power_Domain' => 1,
70			   'Integer_Pipeline' => 2,
71			   'Cache' => 3,
72			   'CPU_PM_Active_Power_Domain' => 4,
73			   'Floating_Point_Unit' => 5,
74			   'Data_Pipe_to_memory' => 6,
75			   'Memory' => 7,
76			   'Socket' => 8,
77			   'System' => 9,
78			  );
79
80#
81# Object interface to the library. These are methods that can be used by the
82# module user.
83#
84
85#
86# Create a new object representing PG
87# All the heavy lifting is performed by _init function.
88# This function performs all the Perl blessing magic.
89#
90# The new() method accepts arguments in the form of a hash. The following
91# subarguments are supported:
92#
93#   -cpudata	# Collect per-CPU data from kstats if this is T
94#   -tags	# Match PGs to physical relationships if this is T
95#   -swload	# Collect software CPU load if this is T
96#   -retry	# how many times to retry PG initialization when it fails
97#   -delay # Delay in seconds between retries
98#
99# The arguments are passed to _init().
100#
101sub new
102{
103	my $class = shift;
104	my %args = @_;
105	my $retry_count = $args{-retry} || 0;
106	my $retry_delay = $args{-delay} || 1;
107
108	my $self =  _init(@_);
109
110	#
111	# If PG initialization fails with EAGAIN error and the caller requested
112	# retries, retry initialization.
113	#
114	for (; !$self && ($! == &Errno::EAGAIN) && $retry_count;
115	     $retry_count--) {
116		select(undef,undef,undef, $retry_delay);
117		$self = _init(@_);
118	}
119
120	if ($self) {
121		bless($self, $class) if defined($class);
122		bless($self) unless defined($class);
123	}
124
125	return ($self);
126}
127
128#
129# Functions below use internal function _pg_get which returns PG hash reference
130# corresponding to PG ID specified or 'undef' if the PG can't be found.
131#
132
133#
134# All methods return 'undef' in scalar context and an empty list in list
135# context when unrecoverable errors are detected.
136#
137
138#
139# Return the root ID of PG hierarchy
140#
141sub root
142{
143	scalar @_ == 1 or _usage("root(cookie)");
144	my $self = shift;
145
146	return unless $self->{PGTREE};
147
148	return ($ROOT_ID);
149}
150
151#
152# Return list of all pgs numerically sorted In scalar context return number of
153# PGs
154#
155sub all
156{
157	scalar @_ == 1 or _usage("all(cookie)");
158	my $self = shift;
159	my $pgtree =  $self->{PGTREE} or return;
160	my @ids = keys(%{$pgtree});
161
162	return (wantarray() ? _nsort(@ids) : scalar @ids);
163}
164
165#
166# Return list of all pgs by walking the tree depth first.
167#
168sub all_depth_first
169{
170	scalar @_ == 1 or _usage("all_depth_first(cookie)");
171	my $self = shift;
172
173	_walk_depth_first($self, $self->root());
174}
175
176#
177# Return list of all pgs by walking the tree breadth first.
178#
179sub all_breadth_first
180{
181	scalar @_ == 1 or _usage("all_breadth_first(cookie)");
182	my $self = shift;
183
184	_walk_breadth_first($self, $self->root());
185}
186
187#
188# Return list of CPUs in the PG specified
189# CPUs returned are numerically sorted
190# In scalar context return number of CPUs
191#
192sub cpus
193{
194	scalar @_ == 2 or _usage("cpus(cookie, pg)");
195	my $pg = _pg_get(shift, shift) or return;
196	my @cpus =  @{$pg->{cpus}};
197
198	return (wantarray() ? _nsort(@cpus) : _collapse(@cpus));
199}
200
201#
202# Return a parent for a given PG
203# Returns undef if there is no parent
204#
205sub parent
206{
207	scalar @_ == 2 or _usage("parent(cookie, pg)");
208	my $pg = _pg_get(shift, shift) or return;
209	my $parent = $pg->{parent};
210
211	return (defined($parent) && $parent >= 0 ? $parent : undef);
212}
213
214#
215# Return list of children for a given PG
216# In scalar context return list of children
217#
218sub children
219{
220	scalar @_ == 2 or _usage("children(cookie, pg)");
221	my $pg = _pg_get(shift, shift) or return;
222
223	my $children = $pg->{children} or return;
224	my @children = @{$children};
225
226	return (wantarray() ? _nsort(@children) : scalar @children);
227}
228
229#
230# Return sharing name for the PG
231#
232sub sh_name
233{
234	scalar @_ == 2 or _usage("sh_name(cookie, pg)");
235	my $pg = _pg_get(shift, shift) or return;
236	return ($pg->{sh_name});
237}
238
239#
240# Return T if specified PG ID is a leaf PG
241#
242sub is_leaf
243{
244	scalar @_ == 2 or _usage("is_leaf(cookie, pg)");
245	my $pg = _pg_get(shift, shift) or return;
246	return ($pg->{is_leaf});
247}
248
249#
250# Return leaf PGs
251#
252sub leaves
253{
254	scalar @_ == 1 or _usage("leaves(cookie, pg)");
255
256	my $self = shift;
257
258	return (grep { is_leaf($self, $_) } $self->all());
259}
260
261#
262# Update varying data in the snapshot
263#
264sub update
265{
266	scalar @_ == 1 or _usage("update(cookie)");
267
268	my $self = shift;
269	my $ks = $self->{KSTAT};
270
271	$ks->update();
272
273	my $pgtree = $self->{PGTREE};
274	my $pg_info = $ks->{$self->{PG_MODULE}};
275
276	#
277	# Walk PG kstats and copy updated data from kstats to the snapshot
278	#
279	foreach my $id (keys %$pg_info) {
280		my $pg = $pgtree->{$id} or next;
281
282		my $pg_ks = _kstat_get_pg($pg_info, $id,
283					  $self->{USE_OLD_KSTATS});
284		return unless $pg_ks;
285
286		#
287		# Update PG from kstats
288		#
289		$pg->{util} = $pg_ks->{hw_util};
290		$pg->{current_rate} = $pg_ks->{hw_util_rate};
291		$pg->{util_rate_max} = $pg_ks->{hw_util_rate_max};
292		$pg->{util_time_running} = $pg_ks->{hw_util_time_running};
293		$pg->{util_time_stopped} = $pg_ks->{hw_util_time_stopped};
294		$pg->{snaptime} = $pg_ks->{snaptime};
295		$pg->{generation} = $pg_ks->{generation};
296	}
297
298	#
299	# Update software load for each CPU
300	#
301	$self->{CPU_LOAD} = _get_sw_cpu_load($ks);
302
303	#
304	# Get hardware load per CPU
305	#
306	if ($self->{GET_CPU_DATA}) {
307		_get_hw_cpu_load($self);
308	}
309
310	return (1);
311}
312
313#
314# Return list of physical tags for the given PG
315#
316sub tags
317{
318	scalar @_ == 2 or _usage("tags(cookie, pg)");
319	my $pg = _pg_get(shift, shift) or return;
320
321	my $tags = $pg->{tags} or return;
322
323	my @tags = _uniq(@{$tags});
324
325	return (wantarray() ? @tags : join (',', @tags));
326}
327
328#
329# Return list of sharing relationships in the snapshot Relationships are sorted
330# by the level in the hierarchy If any PGs are given on the command line, only
331# return sharing relationships for given PGs, but still keep them sorted.
332#
333sub sharing_relationships
334{
335	scalar @_ or _usage("sharing_relationships(cookie, [pg, ...])");
336
337	my $self = shift;
338	my @pgs = $self->all_breadth_first();
339
340	if (scalar @_ > 0) {
341		#
342		# Caller specified PGs, remove any PGs not in caller's list
343		#
344		my %seen;
345		map { $seen{$_} = 1 } @_;
346
347		# Remove any PGs not provided by user
348		@pgs = grep { $seen{$_} } @pgs;
349	}
350
351	return (_uniq(map { $self->sh_name($_) } @pgs));
352}
353
354#
355# Return PG generation number. If PG is specified in the argument, return its
356# generation, otherwise return snapshot generation.
357# Snapshot generation is calculated as the total of PG generations
358#
359sub generation
360{
361	(scalar @_ == 1 || scalar @_ == 2) or _usage("generation(cookie, [pg])");
362	my $self = shift;
363
364	if (scalar @_ == 0) {
365		my @generations = map { $_->{generation} }
366				  values %{$self->{PGTREE}};
367		return (sum(@generations));
368
369	} else {
370		my $id = shift;
371		my $pg = _pg_get($self, $id) or return;
372		return ($pg->{generation});
373	}
374}
375
376#
377# Return level of PG in the tree, starting from root.
378# PG level is cached in the $pg->{level} field.
379#
380sub level
381{
382	scalar @_ == 2 or _usage("level(cookie, pg)");
383	my $self = shift;
384	my $pgid = shift;
385	my $pg = _pg_get($self, $pgid) or return;
386
387	return $pg->{level} if defined($pg->{level});
388
389	$pg->{level} = 0;
390
391	my $parent = _pg_get($self, $pg->{parent});
392	while ($parent) {
393		$pg->{level}++;
394		$parent = _pg_get($self, $parent->{parent});
395	}
396
397	return ($pg->{level});
398}
399
400#
401# Return T if PG supports utilization We assume that utilization is supported by
402# PG if it shows any non-zero time in util_time_running. It is possible that the
403# same condition may be caused by cpustat(1) running ever since PG was created,
404# but there is not much we can do about it.
405#
406sub has_utilization
407{
408	scalar @_ == 2 or _usage("has_utilization(cookie, pg)");
409	my $pg = _pg_get(shift, shift) or return;
410
411	return ($pg->{util_time_running} != 0);
412}
413
414
415#
416# Return utilization for the PG
417# Utilization is a difference in utilization value between two snapshots.
418# We can only compare utilization between PGs having the same generation ID.
419#
420sub utilization
421{
422	scalar @_ == 3 or _usage("utilization(cookie, cookie1, pg");
423	my $c1 = shift;
424	my $c2 = shift;
425	my $id = shift;
426
427	#
428	# Since we have two cookies, update capacity in both
429	#
430	_capacity_update($c1, $c2, $id);
431
432	my $pg1 = _pg_get($c1, $id) or return;
433	my $pg2 = _pg_get($c2, $id) or return;
434
435	#
436	# Nothing to return if one of the utilizations wasn't measured
437	#
438	return unless ($pg1->{util_time_running} && $pg2->{util_time_running});
439
440	#
441	# Verify generation IDs
442	#
443	return unless $pg1->{generation} eq $pg2->{generation};
444	my $u1 = $pg1->{util};
445	my $u2 = $pg2->{util};
446	return unless defined ($u1) && defined ($u2);
447
448	return (abs($u2 - $u1));
449}
450
451#
452# Return an estimate of PG capacity Capacity is calculated as the maximum of
453# observed utilization expressed in units per second or maximum CPU frequency
454# for all CPUs.
455#
456# We store capacity per sharing relationship, assuming that the same sharing has
457# the same capacity. This may not be true for heterogeneous systems.
458#
459sub capacity
460{
461	scalar @_ == 2 or _usage("capacity(cookie, pg");
462	my $self = shift;
463	my $pgid = shift;
464	my $pg = _pg_get($self, $pgid) or return;
465	my $shname = $pg->{sh_name} or return;
466
467	return (max($self->{MAX_FREQUENCY}, $self->{CAPACITY}->{$shname}));
468}
469
470#
471# Return accuracy of utilization calculation between two snapshots The accuracy
472# is determined based on the total time spent running and not running the
473# counters. If T1 is the time counters were running during the period and T2 is
474# the time they were turned off, the accuracy is T1 / (T1 + T2), expressed in
475# percentages.
476#
477sub accuracy
478{
479	scalar @_ == 3 or _usage("accuracy(cookie, cookie1, pg)");
480	my $c1 = shift;
481	my $c2 = shift;
482	my $id = shift;
483	my $trun;
484	my $tstop;
485
486	my $pg1 = _pg_get($c1, $id) or return;
487	my $pg2 = _pg_get($c2, $id) or return;
488
489	# Both PGs should have the same generation
490	return unless $pg1->{generation} eq $pg2->{generation};
491
492	#
493	# Get time spent with running and stopped counters
494	#
495	$trun = abs($pg2->{util_time_running} -
496		    $pg1->{util_time_running});
497	$tstop = abs($pg2->{util_time_stopped} -
498		     $pg1->{util_time_stopped});
499
500	my $total = $trun + $tstop;
501
502	#
503	# Calculate accuracy as percentage
504	#
505	my $accuracy = $total ? ($trun * 100) / $total : 0;
506	$accuracy = int($accuracy + 0.5);
507	$accuracy = 100 if $accuracy > 100;
508	return ($accuracy);
509}
510
511#
512# Return time difference in seconds between two snapshots
513#
514sub tdelta
515{
516	scalar @_ == 3 or _usage("tdelta(cookie, cookie1, pg)");
517	my $c1 = shift;
518	my $c2 = shift;
519	my $id = shift;
520
521	my $pg1 = _pg_get($c1, $id) or return;
522	my $pg2 = _pg_get($c2, $id) or return;
523
524	return unless $pg1->{generation} eq $pg2->{generation};
525
526	my $t1 = $pg1->{snaptime};
527	my $t2 = $pg2->{snaptime};
528	my $delta = abs($t1 - $t2);
529	return ($delta);
530}
531
532#
533# Return software utilization between two snapshots
534# In scalar context return software load as percentage.
535# In list context return a list (USER, SYSTEM, IDLE, SWLOAD)
536# All loads are returned as percentages
537#
538sub sw_utilization
539{
540	scalar @_ == 3 or _usage("tdelta(cookie, cookie1, pg)");
541
542	my $c1 = shift;
543	my $c2 = shift;
544	my $id = shift;
545
546	my $pg1 = _pg_get($c1, $id) or return;
547	my $pg2 = _pg_get($c2, $id) or return;
548
549	return unless $pg1->{generation} eq $pg2->{generation};
550
551	my @cpus = $c1->cpus($id);
552
553	my $load1 = $c1->{CPU_LOAD};
554	my $load2 = $c2->{CPU_LOAD};
555
556	my $idle = 0;
557	my $user = 0;
558	my $sys = 0;
559	my $total = 0;
560	my $swload = 0;
561
562	foreach my $cpu (@cpus) {
563		my $ld1 = $load1->{$cpu};
564		my $ld2 = $load2->{$cpu};
565		next unless $ld1 && $ld2;
566
567		$idle += $ld2->{cpu_idle} - $ld1->{cpu_idle};
568		$user += $ld2->{cpu_user} - $ld1->{cpu_user};
569		$sys  += $ld2->{cpu_sys}  - $ld1->{cpu_sys};
570	}
571
572	$total = $idle + $user + $sys;
573
574	# Prevent division by zero
575	$total = 1 unless $total;
576
577	$swload = ($user + $sys) * 100 / $total;
578	$idle   = $idle * 100 / $total;
579	$user   = $user * 100 / $total;
580	$sys    = $sys  * 100 / $total;
581
582	return (wantarray() ? ($user, $sys, $idle, $swload) : $swload);
583}
584
585#
586# Return utilization for the PG for a given CPU
587# Utilization is a difference in utilization value between two snapshots.
588# We can only compare utilization between PGs having the same generation ID.
589#
590sub cpu_utilization
591{
592	scalar @_ == 4 or _usage("utilization(cookie, cookie1, pg, cpu");
593	my $c1 = shift;
594	my $c2 = shift;
595	my $id = shift;
596	my $cpu = shift;
597
598	my $idle = 0;
599	my $user = 0;
600	my $sys = 0;
601	my $swtotal = 0;
602	my $swload = 0;
603
604	#
605	# Since we have two cookies, update capacity in both
606	#
607	_capacity_update($c1, $c2, $id);
608
609	my $pg1 = _pg_get($c1, $id) or return;
610	my $pg2 = _pg_get($c2, $id) or return;
611
612	#
613	# Nothing to return if one of the utilizations wasn't measured
614	#
615	return unless ($pg1->{util_time_running} && $pg2->{util_time_running});
616
617	#
618	# Nothing to return if CPU data is missing
619	#
620	return unless $pg1->{cpudata} && $pg2->{cpudata};
621
622	#
623	# Verify generation IDs
624	#
625	return unless $pg1->{generation} eq $pg2->{generation};
626
627	#
628	# Get data for the given CPU
629	#
630	my $cpudata1 = $pg1->{cpudata}->{$cpu};
631	my $cpudata2 = $pg2->{cpudata}->{$cpu};
632
633	return unless $cpudata1 && $cpudata2;
634
635	return unless $cpudata1->{generation} == $cpudata2->{generation};
636
637	my $u1 = $cpudata1->{util};
638	my $u2 = $cpudata2->{util};
639	return unless defined ($u1) && defined ($u2);
640	my $hw_utilization = abs ($u1 - $u2);
641
642	#
643	# Get time spent with running and stopped counters
644	#
645	my $trun = abs($cpudata1->{util_time_running} -
646		       $cpudata2->{util_time_running});
647	my $tstop = abs($cpudata1->{util_time_stopped} -
648			$cpudata2->{util_time_stopped});
649
650	my $total = $trun + $tstop;
651
652	#
653	# Calculate accuracy as percentage
654	#
655	my $accuracy = $total ? ($trun * 100) / $total : 0;
656	$accuracy = int($accuracy + 0.5);
657	$accuracy = 100 if $accuracy > 100;
658
659	my $t1 = $cpudata1->{snaptime};
660	my $t2 = $cpudata2->{snaptime};
661	my $tdelta = abs ($t1 - $t2);
662
663	my $shname = $pg2->{sh_name} or return;
664	my $capacity = max($c2->{MAX_FREQUENCY}, $c2->{CAPACITY}->{$shname});
665	my $utilization = $hw_utilization / $tdelta;
666	$capacity = $utilization unless $capacity;
667	$utilization /= $capacity;
668	$utilization *= 100;
669
670	my $ld1 = $c1->{CPU_LOAD}->{$cpu};
671	my $ld2 = $c2->{CPU_LOAD}->{$cpu};
672
673	if ($ld1 && $ld2) {
674		$idle = $ld2->{cpu_idle} - $ld1->{cpu_idle};
675		$user = $ld2->{cpu_user} - $ld1->{cpu_user};
676		$sys  = $ld2->{cpu_sys}  - $ld1->{cpu_sys};
677
678		$swtotal = $idle + $user + $sys;
679
680		# Prevent division by zero
681		$swtotal = 1 unless $swtotal;
682
683		$swload = ($user + $sys) * 100 / $swtotal;
684		$idle   = $idle * 100 / $swtotal;
685		$user   = $user * 100 / $swtotal;
686		$sys    = $sys  * 100 / $swtotal;
687	}
688
689	return (wantarray() ?
690		($utilization, $accuracy, $hw_utilization,
691		 $swload, $user, $sys, $idle) :
692		$utilization);
693}
694
695#
696# online_cpus(kstat)
697# Return list of on-line CPUs
698#
699sub online_cpus
700{
701	scalar @_ == 1 or _usage("online_cpus(cookie)");
702
703	my $self = shift or return;
704	my $ks = $self->{KSTAT} or return;
705
706	my $cpu_info = $ks->{cpu_info} or return;
707
708	my @cpus = grep {
709		my $cp = $cpu_info->{$_}->{"cpu_info$_"};
710		my $state = $cp->{state};
711		$state eq 'on-line' || $state eq 'no-intr';
712	} keys %{$cpu_info};
713
714	return (wantarray() ? @cpus : _nsort(@cpus));
715}
716
717#
718# Support methods
719#
720# The following methods are not PG specific but are generally useful for PG
721# interface consumers
722#
723
724#
725# Sort the list numerically
726#
727sub nsort
728{
729	scalar @_ > 0 or _usage("nsort(cookie, val, ...)");
730	shift;
731
732	return (_nsort(@_));
733}
734
735#
736# Return the input list with duplicates removed.
737# Should be used in list context
738#
739sub uniq
740{
741	scalar @_ > 0 or _usage("uniq(cookie, val, ...)");
742	shift;
743
744	return (_uniq(@_));
745}
746
747#
748# Sort list numerically and remove duplicates
749# Should be called in list context
750#
751sub uniqsort
752{
753	scalar @_ > 0 or _usage("uniqsort(cookie, val, ...)");
754	shift;
755
756	return (_uniqsort(@_));
757}
758
759
760#
761# Expand all arguments and present them as a numerically sorted list
762# x,y is expanded as (x y)
763# 1-3 ranges are expandes as (1 2 3)
764#
765sub expand
766{
767	scalar @_ > 0 or _usage("expand(cookie, val, ...)");
768	shift;
769
770	return (_uniqsort(map { _expand($_) } @_));
771}
772
773#
774# Consolidate consecutive ids as start-end
775# Input: list of ids
776# Output: string with space-sepated cpu values with ranges
777#   collapsed as x-y
778#
779sub id_collapse
780{
781	scalar @_ > 0 or _usage("collapse(cookie, val, ...)");
782	shift;
783
784	return _collapse(@_);
785}
786
787#
788# Return elements of the second list not present in the first list. Both lists
789# are passed by reference.
790#
791sub set_subtract
792{
793	scalar @_ == 3 or _usage("set_subtract(cookie, left, right)");
794	shift;
795
796	return (_set_subtract(@_));
797}
798
799#
800# Return the intersection of two lists passed by reference
801# Convert the first list to a hash with seen entries marked as 1-values
802# Then grep only elements present in the first list from the second list.
803# As a little optimization, use the shorter list to build a hash.
804#
805sub intersect
806{
807	scalar @_ == 3 or _usage("intersect(cookie, left, right)");
808	shift;
809
810	return (_set_intersect(@_));
811}
812
813#
814# Return elements of the second list not present in the first list. Both lists
815# are passed by reference.
816#
817sub _set_subtract
818{
819	my ($left, $right) = @_;
820	my %seen;	# Set to 1 for everything in the first list
821	# Create a hash indexed by elements in @left with ones as a value.
822	map { $seen{$_} = 1 } @$left;
823	# Find members of @right present in @left
824	return (grep { ! $seen{$_} } @$right);
825}
826
827#
828# END OF PUBLIC INTERFACE
829#
830
831#
832# INTERNAL FUNCTIONS
833#
834
835#
836# _usage(): print error message and terminate the program.
837#
838sub _usage
839{
840	my $msg = shift;
841	Carp::croak "Usage: Sun::Solaris::Pg::$msg";
842}
843
844#
845# Sort the list numerically
846# Should be called in list context
847#
848sub _nsort
849{
850	return (sort { $a <=> $b } @_);
851}
852
853#
854# Return the input list with duplicates removed.
855# Should be used in list context
856#
857sub _uniq
858{
859	my %seen;
860	return (grep { ++$seen{$_} == 1 } @_);
861}
862
863#
864# Sort list numerically and remove duplicates
865# Should be called in list context
866#
867sub _uniqsort
868{
869	return (sort { $a <=> $b } _uniq(@_));
870}
871
872# Get PG from the snapshot by id
873sub _pg_get
874{
875	my $self = shift;
876	my $pgid = shift;
877
878	return unless defined $pgid;
879	my $pgtree = $self->{PGTREE} or return;
880
881	return ($pgtree->{$pgid});
882}
883
884#
885# Copy data from kstat representation to our representation
886# Arguments:
887#   PG kstat
888#   Reference to the list of CPUs.
889# Any CPUs in the PG kstat not present in the CPU list are ignored.
890#
891sub _pg_create_from_kstat
892{
893	my $pg_ks = shift;
894	my $all_cpus = shift;
895	my %all_cpus;
896	my $pg = ();
897
898	#
899	# Mark CPUs available
900	#
901	map { $all_cpus{$_}++ } @$all_cpus;
902
903	return unless $pg_ks;
904
905	#
906	# Convert CPU list in the kstat from x-y,z form to the proper list
907	#
908	my @cpus = _expand($pg_ks->{cpus});
909
910	#
911	# Remove any CPUs not present in the arguments
912	#
913	@cpus = grep { $all_cpus{$_} } @cpus;
914
915	#
916	# Do not create PG unless it has any CPUs
917	#
918	return unless scalar @cpus;
919
920	#
921	# Copy data to the $pg structure
922	#
923	$pg->{ncpus} = scalar @cpus;
924	$pg->{cpus} = \@cpus;
925	$pg->{id} = defined($pg_ks->{pg_id}) ? $pg_ks->{pg_id} : $pg_ks->{id};
926	$pg->{util} = $pg_ks->{hw_util};
927	$pg->{current_rate} = $pg_ks->{hw_util_rate};
928	$pg->{util_rate_max} = $pg_ks->{hw_util_rate_max};
929	$pg->{util_time_running} = $pg_ks->{hw_util_time_running};
930	$pg->{util_time_stopped} = $pg_ks->{hw_util_time_stopped};
931	$pg->{snaptime} = $pg_ks->{snaptime};
932	$pg->{generation} = $pg_ks->{generation};
933	$pg->{sh_name} = $pg_ks->{relationship} || $pg_ks->{sharing_relation};
934	$pg->{parent} = $pg_ks->{parent_pg_id};
935	$pg->{parent} = PG_PARENT_UNDEF unless defined $pg->{parent};
936	#
937	# Replace spaces with underscores in sharing names
938	#
939	$pg->{sh_name} =~ s/ /_/g;
940	$pg->{is_leaf} = 1;
941
942	return $pg;
943}
944
945#
946# Create fake root PG with all CPUs
947# Arguments: list of CPUs
948#
949sub _pg_create_root
950{
951	my $pg = ();
952	my @cpus = @_;
953
954	$pg->{id} = $ROOT_ID;
955	$pg->{ncpus} = scalar @cpus;
956	$pg->{util} = 0;
957	$pg->{current_rate} = 0;
958	$pg->{util_rate_max} = 0;
959	$pg->{util_time_running} = 0;
960	$pg->{util_time_stopped} = 0;
961	$pg->{snaptime} = 0;
962	$pg->{generation} = 0;
963	$pg->{sh_name} = 'System';
964	$pg->{is_leaf} = 0;
965	$pg->{cpus} = \@cpus;
966	$pg->{parent} = PG_NO_PARENT;
967
968	return ($pg);
969}
970
971#
972# _pg_all_from_kstats(SNAPSHOT)
973# Extract all PG information from kstats
974#
975sub _pg_all_from_kstats
976{
977	my $self = shift;
978	my $ks = $self->{KSTAT};
979	my @all_cpus = @{$self->{CPUS}};
980
981	return unless $ks;
982
983	my $pgtree = ();
984	my $pg_info = $ks->{$self->{PG_MODULE}};
985
986	#
987	# Walk all PG kstats and copy them to $pgtree->{$id}
988	#
989	foreach my $id (keys %$pg_info) {
990		my $pg_ks = _kstat_get_pg($pg_info, $id,
991					  $self->{USE_OLD_KSTATS});
992		next unless $pg_ks;
993
994		my $pg = _pg_create_from_kstat($pg_ks, \@all_cpus);
995
996		$pgtree->{$id} = $pg if $pg;
997	}
998
999	#
1000	# OS does not have root PG, so create one.
1001	#
1002	if (!$pgtree->{$ROOT_ID}) {
1003		$pgtree->{$ROOT_ID} = _pg_create_root (@all_cpus);
1004	}
1005
1006	#
1007	# Construct parent-child relationships between PGs
1008	#
1009
1010	#
1011	# Get list of PGs sorted by number of CPUs
1012	# If two PGs have the same number of CPUs, sort by relationship order.
1013	#
1014	my @lineage = sort {
1015		$a->{ncpus} <=> $b->{ncpus} ||
1016		_relationship_order($a->{sh_name}) <=>
1017		_relationship_order($b->{sh_name})
1018	    } values %$pgtree;
1019
1020	#
1021	# For each PG in the lineage discover its parent if it doesn't have one.
1022	#
1023	for (my $i = 0; $i < scalar @lineage; $i++) {
1024		my $pg = $lineage[$i];
1025
1026		#
1027		# Ignore PGs which already have parent in kstats
1028		#
1029		my $parent = $pg->{parent};
1030		next if ($parent >= PG_NO_PARENT);
1031
1032		my $ncpus = $pg->{ncpus};
1033		my @cpus = @{$pg->{cpus}};
1034
1035		#
1036		# Walk the lineage, ignoring any CPUs with the same number of
1037		# CPUs
1038		for (my $j = $i + 1; $j < scalar @lineage; $j++) {
1039			my $pg1 = $lineage[$j];
1040			my @parent_cpus = @{$pg1->{cpus}};
1041			if (_is_subset(\@cpus, \@parent_cpus)) {
1042				$pg->{parent} = $pg1->{id};
1043				last;
1044			}
1045		}
1046	}
1047
1048	#
1049	# Find all top-level PGs and put them under $root
1050	#
1051	foreach my $pgid (keys %$pgtree) {
1052		next if $pgid == $ROOT_ID;
1053		my $pg = $pgtree->{$pgid};
1054		$pg->{parent} = $ROOT_ID unless $pg->{parent} >= 0;
1055	}
1056
1057	#
1058	# Now that we know parents, for each parent add all direct children to
1059	# their parent sets
1060	#
1061	foreach my $pg (@lineage) {
1062		my $parentid = $pg->{parent};
1063		next unless defined $parentid;
1064
1065		my $parent = $pgtree->{$parentid};
1066		push (@{$parent->{children}}, $pg->{id});
1067	}
1068
1069	return ($pgtree);
1070}
1071
1072#
1073# Read kstats and initialize PG object
1074# Collect basic information about cmt_pg
1075# Add list of children and list of CPUs
1076# Returns the hash reference indexed by pg id
1077#
1078# The _init() function accepts arguments in the form of a hash. The following
1079# subarguments are supported:
1080#
1081#   -cpudata	# Collect per-CPU data from kstats if this is T
1082#   -tags	# Match PGs to physical relationships if this is T
1083#   -swload	# Collect software CPU load if this is T
1084
1085sub _init
1086{
1087	my $ks = Sun::Solaris::Kstat->new(strip_strings => 1);
1088	return unless $ks;
1089
1090	my %args = @_;
1091	my $get_cpu_data = $args{-cpudata};
1092	my $get_tags = $args{-tags};
1093	my $get_swload = $args{-swload};
1094
1095	my $self;
1096
1097	my $use_old_kstat_names = scalar(grep {/^pg_hw_perf/ } keys (%$ks)) == 0;
1098
1099	my @frequencies;
1100	$self->{MAX_FREQUENCY} = 0;
1101
1102	$self->{PG_MODULE} = $use_old_kstat_names ? 'pg' : 'pg_hw_perf';
1103	$self->{PG_CPU_MODULE} =  $use_old_kstat_names ?
1104	  'pg_cpu' : 'pg_hw_perf_cpu';
1105	$self->{USE_OLD_KSTATS} = $use_old_kstat_names;
1106
1107	$get_cpu_data = 0 unless  scalar(grep {/^$self->{PG_CPU_MODULE}/ }
1108					 keys (%$ks));
1109
1110	# Get list of PG-related kstats
1111	my $pg_keys = $use_old_kstat_names ? 'pg' : 'pg_hw';
1112
1113	if (scalar(grep { /^$pg_keys/ } keys (%$ks)) == 0) {
1114		if (exists(&Errno::ENOTSUPP)) {
1115			$! = &Errno::ENOTSUPP;
1116		} else {
1117			$! = 48;
1118		}
1119		return;
1120	}
1121
1122
1123	#
1124	# Mapping of cores and chips to CPUs
1125	#
1126	my $hw_mapping;
1127
1128	#
1129	# Get list of all CPUs
1130	#
1131	my $cpu_info = $ks->{cpu_info};
1132
1133	#
1134	# @all-cpus is a list of all cpus
1135	#
1136	my @all_cpus = keys %$cpu_info;
1137
1138	#
1139	# Save list of all CPUs in the snapshot
1140	#
1141	$self->{CPUS} = \@all_cpus;
1142
1143	#
1144	# Find CPUs for each socket and chip
1145	# Also while we scan CPU kstats, get maximum frequency of each CPU.
1146	#
1147	foreach my $id (@all_cpus) {
1148		my $ci = $cpu_info->{$id}->{"cpu_info$id"};
1149		next unless $ci;
1150		my $core_id = $ci->{core_id};
1151		my $chip_id = $ci->{chip_id};
1152
1153		push(@{$hw_mapping->{core}->{$core_id}}, $id)
1154		  if defined $core_id;
1155		push(@{$hw_mapping->{chip}->{$chip_id}}, $id)
1156		  if defined $chip_id;
1157
1158		# Read CPU frequencies separated by commas
1159		my $freqs = $ci->{supported_frequencies_Hz};
1160		my $max_freq = max(split(/:/, $freqs));
1161
1162		# Calculate maximum frequency for the snapshot.
1163		$self->{MAX_FREQUENCY} = $max_freq if
1164		  $self->{MAX_FREQUENCY} < $max_freq;
1165	}
1166
1167	$self->{KSTAT} = $ks;
1168
1169	#
1170	# Convert kstats to PG tree
1171	#
1172	my $pgtree = _pg_all_from_kstats($self);
1173	$self->{PGTREE} = $pgtree;
1174
1175	#
1176	# Find capacity estimate per sharing relationship
1177	#
1178	foreach my $pgid (keys %$pgtree) {
1179		my $pg = $pgtree->{$pgid};
1180		my $shname = $pg->{sh_name};
1181		my $max_rate = $pg->{util_rate_max};
1182		$self->{CAPACITY}->{$shname} = $max_rate if
1183		  !$self->{CAPACITY}->{$shname} ||
1184		    $self->{CAPACITY}->{$shname} < $max_rate;
1185	}
1186
1187	if ($get_tags) {
1188		#
1189		# Walk all PGs and mark all PGs that have corresponding hardware
1190		# entities (system, chips, cores).
1191		#
1192		foreach my $pgid (keys %$pgtree) {
1193			my $pg = $pgtree->{$pgid};
1194			my @cpus = @{$pg->{cpus}};
1195			next unless scalar @cpus > 1;
1196
1197			if (_set_equal (\@cpus, \@all_cpus)) {
1198				#
1199				# PG has all CPUs in the system.
1200				#
1201				push (@{$pg->{tags}}, 'system');
1202			}
1203
1204			foreach my $name ('core', 'chip') {
1205				my $hwdata = $hw_mapping->{$name};
1206				foreach my $id (keys %$hwdata) {
1207					# CPUs for this entity
1208					my @hw_cpus = @{$hwdata->{$id}};
1209					if (_set_equal (\@cpus, \@hw_cpus)) {
1210						#
1211						# PG has exactly the same CPUs
1212						#
1213						push (@{$pg->{tags}}, $name);
1214					}
1215				}
1216			}
1217		}
1218	}
1219
1220	#
1221	# Save software load for each CPU
1222	#
1223	if ($get_swload) {
1224		$self->{CPU_LOAD} = _get_sw_cpu_load($ks);
1225	}
1226
1227	#
1228	# Collect per-CPU utilization data if requested
1229	#
1230	if ($get_cpu_data) {
1231		_get_hw_cpu_load($self);
1232	}
1233
1234	$self->{GET_CPU_DATA} = $get_cpu_data;
1235
1236	#
1237	# Verify that in the end we have the same PG generation for each PG
1238	#
1239	if (! _same_generation($self)) {
1240		$! = &Errno::EAGAIN;
1241		return;
1242	}
1243
1244	return ($self);
1245}
1246
1247#
1248# Verify that topology is the same as at the time snapshot was created
1249#
1250sub _same_generation
1251{
1252	my $self = shift;
1253	my $pgtree =  $self->{PGTREE} or return;
1254
1255	return (0) unless $self;
1256
1257	my $ks = $self->{KSTAT};
1258	$ks->update();
1259	my $pg_info = $ks->{$self->{PG_MODULE}};
1260	foreach my $id (keys %$pg_info) {
1261		my $pg = $pgtree->{$id} or next;
1262
1263		my $pg_ks = _kstat_get_pg($pg_info, $id,
1264					  $self->{USE_OLD_KSTATS});
1265		return unless $pg_ks;
1266		return (0) unless $pg->{generation} == $pg_ks->{generation};
1267	}
1268	return (1);
1269}
1270
1271#
1272# Update capacity for both PGs
1273#
1274sub _capacity_update
1275{
1276	my $c1 = shift;
1277	my $c2 = shift;
1278
1279	my $pgtree1 = $c1->{PGTREE};
1280	my $pgtree2 = $c2->{PGTREE};
1281
1282	foreach my $pgid (keys %$pgtree1) {
1283		my $pg1 = $pgtree1->{$pgid};
1284		my $pg2 = $pgtree2->{$pgid};
1285		next unless $pg1 && $pg2;
1286		next unless $pg1->{generation} != $pg2->{generation};
1287		my $shname1 = $pg1->{sh_name};
1288		my $shname2 = $pg2->{sh_name};
1289		next unless $shname1 eq $shname2;
1290		my $max_rate = max($pg1->{util_rate_max}, $pg2->{util_rate_max});
1291
1292		my $utilization = abs($pg1->{util} - $pg2->{util});
1293		my $tdelta = abs($pg1->{snaptime} - $pg2->{snaptime});
1294		$utilization /= $tdelta if $utilization && $tdelta;
1295		$max_rate = $utilization if
1296		  $utilization && $max_rate < $utilization;
1297
1298		$c1->{CAPACITY}->{$shname1} = $max_rate if
1299		  !$c1->{CAPACITY}->{$shname1} ||
1300		    !$c1->{CAPACITY}->{$shname1} < $max_rate;
1301		$c2->{CAPACITY}->{$shname2} = $max_rate if
1302		  !$c2->{CAPACITY}->{$shname2} ||
1303		    !$c2->{CAPACITY}->{$shname2} < $max_rate;
1304	}
1305}
1306
1307#
1308# Return list of PGs breadth first
1309#
1310sub _walk_depth_first
1311{
1312	my $p = shift;
1313	# Nothing to do if list is empty
1314	return unless scalar (@_);
1315
1316	return (map { ($_, _walk_depth_first ($p, $p->children($_))) } @_);
1317}
1318
1319#
1320# Return list of PGs breadth first
1321#
1322sub _walk_breadth_first
1323{
1324	my $p = shift;
1325	# Nothing to do if list is empty
1326	return unless scalar (@_);
1327
1328	return (@_, _walk_breadth_first($p, map { $p->children($_) } @_));
1329}
1330
1331#
1332# Given the kstat reference (already hashed by module name) and PG ID return the
1333# corresponding kstat.
1334#
1335sub _kstat_get_pg
1336{
1337	my $mod = shift;
1338	my $pgid = shift;
1339	my $use_old_kstats = shift;
1340
1341	my $id_field = $use_old_kstats ? 'id' : 'pg_id';
1342
1343	return ($mod->{$pgid}->{hardware}) if $use_old_kstats;
1344
1345	my @instances = grep { $_->{$id_field} == $pgid }
1346	  values(%{$mod->{$pgid}});
1347	return ($instances[0]);
1348}
1349
1350######################################################################
1351# Set routines
1352#######################################################################
1353#
1354# Return T if one list contains all the elements of another list.
1355# All lists are passed by reference
1356#
1357sub _is_subset
1358{
1359	my ($left, $right) = @_;
1360	my %seen;	# Set to 1 for everything in the first list
1361	# Put the shortest list in $left
1362
1363	Carp::croak "invalid left argument" unless ref ($left) eq 'ARRAY';
1364	Carp::croak "invalid right argument" unless ref ($right) eq 'ARRAY';
1365
1366	# Create a hash indexed by elements in @right with ones as a value.
1367	map { $seen{$_} = 1 } @$right;
1368
1369	# Find members of @left not present in @right
1370	my @extra = grep { !$seen{$_} } @$left;
1371	return (!scalar(@extra));
1372}
1373
1374sub _is_member
1375{
1376	my $set = shift;
1377	my $element = shift;
1378	my %seen;
1379
1380	map { $seen{$_} = 1 } @$set;
1381
1382	return ($seen{$element});
1383}
1384
1385#
1386# Return T if C1 and C2 contain the same elements
1387#
1388sub _set_equal
1389{
1390	my $c1 = shift;
1391	my $c2 = shift;
1392
1393	return 0 unless scalar @$c1 == scalar @$c2;
1394
1395	return (_is_subset($c1, $c2) && _is_subset($c2, $c1));
1396}
1397
1398#
1399# Return the intersection of two lists passed by reference
1400# Convert the first list to a hash with seen entries marked as 1-values
1401# Then grep only elements present in the first list from the second list.
1402# As a little optimization, use the shorter list to build a hash.
1403#
1404sub _set_intersect
1405{
1406	my ($left, $right) = @_;
1407	my %seen;	# Set to 1 for everything in the first list
1408	# Put the shortest list in $left
1409	scalar @$left <= scalar @$right or ($right, $left) = ($left, $right);
1410
1411	# Create a hash indexed by elements in @left with ones as a value.
1412	map { $seen{$_} = 1 } @$left;
1413	# Find members of @right present in @left
1414	return (grep { $seen{$_} } @$right);
1415}
1416
1417#
1418# Expand start-end into the list of values
1419# Input: string containing a single numeric ID or x-y range
1420# Output: single value or a list of values
1421# Ranges with start being more than end are inverted
1422#
1423sub _expand
1424{
1425	# Skip the first argument if it is the object reference
1426	shift if ref $@[0] eq 'HASH';
1427
1428	my $arg = shift;
1429
1430	return unless defined $arg;
1431
1432	my @args = split /,/, $arg;
1433
1434	return map { _expand($_) } @args if scalar @args > 1;
1435
1436	$arg = shift @args;
1437	return unless defined $arg;
1438
1439	if ($arg =~ m/^\d+$/) {
1440		# single number
1441		return ($arg);
1442	} elsif ($arg =~ m/^(\d+)\-(\d+)$/) {
1443		my ($start, $end) = ($1, $2);	# $start-$end
1444		# Reverse the interval if start > end
1445		($start, $end) = ($end, $start) if $start > $end;
1446		return ($start .. $end);
1447	} else {
1448		return $arg;
1449	}
1450	return;
1451}
1452
1453#
1454# Consolidate consecutive ids as start-end
1455# Input: list of ids
1456# Output: string with space-sepated cpu values with ranges
1457#   collapsed as x-y
1458#
1459sub _collapse
1460{
1461	return ('') unless @_;
1462	my @args = _uniqsort(@_);
1463	my $start = shift(@args);
1464	my $result = '';
1465	my $end = $start;	# Initial range consists of the first element
1466	foreach my $el (@args) {
1467		if (!$el =~ /^\d+$/) {
1468			$result = "$result $el";
1469			$end = $el;
1470		} elsif ($el == ($end + 1)) {
1471			#
1472			# Got consecutive ID, so extend end of range without
1473			# printing anything since the range may extend further
1474			#
1475			$end = $el;
1476		} else {
1477			#
1478			# Next ID is not consecutive, so print IDs gotten so
1479			# far.
1480			#
1481			if ($end > $start + 1) {	# range
1482				$result = "$result $start-$end";
1483			} elsif ($end > $start) {	# different values
1484				$result = "$result $start $end";
1485			} else {	# same value
1486				$result = "$result $start";
1487			}
1488
1489			# Try finding consecutive range starting from this ID
1490			$start = $end = $el;
1491		}
1492	}
1493
1494	# Print last ID(s)
1495	if (! ($end =~ /^\d+$/)) {
1496		$result = "$result $end";
1497	} elsif ($end > $start + 1) {
1498		$result = "$result $start-$end";
1499	} elsif ($end > $start) {
1500		$result = "$result $start $end";
1501	} else {
1502		$result = "$result $start";
1503	}
1504	# Remove any spaces in the beginning
1505	$result =~ s/^\s+//;
1506	return ($result);
1507}
1508
1509#
1510# get relationship order from relationship name.
1511# return 0 for all unknown names.
1512#
1513sub _relationship_order
1514{
1515	my $name = shift;
1516	return ($relationships_order{$name} || 0);
1517}
1518
1519#
1520# Get software load for each CPU from kstats
1521# Argument: kstat reference
1522# Returns: reference to the hash with
1523# cpu_idle, cpu_user, cpu_sys keys.
1524#
1525sub _get_sw_cpu_load
1526{
1527	my $ks = shift or return;
1528
1529	my $loads;
1530	my $sys_ks = $ks->{cpu};
1531	foreach my $cpu (keys %$sys_ks) {
1532		my $sys = $sys_ks->{$cpu}->{sys};
1533		$loads->{$cpu}->{cpu_idle} = $sys->{cpu_ticks_idle};
1534		$loads->{$cpu}->{cpu_user} = $sys->{cpu_ticks_user};
1535		$loads->{$cpu}->{cpu_sys} = $sys->{cpu_ticks_kernel};
1536	}
1537
1538	return ($loads);
1539}
1540
1541#
1542# Get software load for each CPU from kstats
1543# Arguments:
1544#  pgtree reference
1545#  kstat reference
1546#
1547# Returns: nothing
1548# Stores CPU load in the $pg->{cpudata} hash for each PG
1549#
1550sub _get_hw_cpu_load
1551{
1552	my $self = shift;
1553	my $pgtree = $self->{PGTREE};
1554	my $ks = $self->{KSTAT};
1555
1556	my $pg_cpu_ks = $ks->{$self->{PG_CPU_MODULE}};
1557
1558	foreach my $pgid (keys %$pgtree) {
1559		my $pg = $pgtree->{$pgid};
1560		my @cpus = @{$pg->{cpus}};
1561		my $cpu;
1562		my $pg_id;
1563		foreach my $cpu (keys %$pg_cpu_ks) {
1564			next unless _is_member(\@cpus, $cpu);
1565			my $cpu_hw_data = $pg_cpu_ks->{$cpu};
1566			foreach my $hw (keys %$cpu_hw_data) {
1567				my $cpudata = $cpu_hw_data->{$hw};
1568
1569				#
1570				# Only consider information for this PG
1571				#
1572				next unless $cpudata->{pg_id} == $pgid;
1573
1574				$pg->{cpudata}->{$cpu}->{generation} =
1575				  $cpudata->{generation};
1576				$pg->{cpudata}->{$cpu}->{util} =
1577				  $cpudata->{hw_util};
1578				$pg->{cpudata}->{$cpu}->{util_time_running} =
1579				  $cpudata->{hw_util_time_running};
1580				$pg->{cpudata}->{$cpu}->{util_time_stopped} =
1581				  $cpudata->{hw_util_time_stopped};
1582				$pg->{cpudata}->{$cpu}->{snaptime} =
1583				  $cpudata->{snaptime};
1584			}
1585		}
1586	}
1587}
1588
15891;
1590
1591__END__
1592
1593#
1594# The information about PG hierarchy is contained in a object return by the
1595# new() method.
1596#
1597# This module can deal with old PG kstats that have 'pg' and 'pg_cpu' as module
1598# names as well as new PG kstats which use 'pg_hw_perf' and ''pg_hw_perf_cpu' as
1599# the module name.
1600#
1601# The object contains the following fields:
1602#
1603#   CPUS		List of all CPUs present.
1604#   CAPACITY		Estimate of capacity for each sharing
1605#   PGTREE		The PG tree. See below for the tree representation.
1606#
1607#   PG_MODULE 		Module name for the PG kstats. It is either 'pg' for
1608#			 old style kstats, or 'pg_hw_perf' for new style kstats.
1609#
1610#   MAX_FREQUENCY	Maximum CPU frequency
1611#   USE_OLD_KSTATS	True if we are dealing with old style kstats
1612#   KSTAT		The kstat object used to generate this hierarchy.
1613#
1614# The PG tree is represented as a hash table indexed by PG ID. Each element of
1615# the table is the hash reference with the following fields:
1616#
1617#   children		Reference to the list of children PG IDs
1618#   cpus		Reference to the list of cpu IDs in the PG
1619#   current_rate	Current utilization rate
1620#   generation		PG generation
1621#   id			PG id
1622#   ncpus		number of CPUs in the PG
1623#   parent		PG parent id, or -1 if there is none.
1624#   sh_name		Sharing name
1625#   snaptime		Snapshot time
1626#   util		Hardware utilization
1627#   util_rate_max	Maximum utilization rate
1628#   util_time_running	Time (in nanoseconds) when utilization data is collected
1629#   util_time_stopped	Time when utilization data is not collected
1630#
1631# The fields (with the exception of 'children') are a copy of the data from
1632# kstats.
1633#
1634# The PG hierarchy in the kernel does not have the root PG. We simulate the root
1635# (System) PG which is the parent of top level PGs in the system. This PG always
1636# has ID 0.
1637#
1638