xref: /illumos-gate/usr/src/cmd/pgstat/pgstat.pl (revision 6a634c9d)
1*d3c97224SAlexander Kolbasov#! /usr/perl5/bin/perl
2*d3c97224SAlexander Kolbasov#
3*d3c97224SAlexander Kolbasov# CDDL HEADER START
4*d3c97224SAlexander Kolbasov#
5*d3c97224SAlexander Kolbasov# The contents of this file are subject to the terms of the
6*d3c97224SAlexander Kolbasov# Common Development and Distribution License (the "License").
7*d3c97224SAlexander Kolbasov# You may not use this file except in compliance with the License.
8*d3c97224SAlexander Kolbasov#
9*d3c97224SAlexander Kolbasov# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10*d3c97224SAlexander Kolbasov# or http://www.opensolaris.org/os/licensing.
11*d3c97224SAlexander Kolbasov# See the License for the specific language governing permissions
12*d3c97224SAlexander Kolbasov# and limitations under the License.
13*d3c97224SAlexander Kolbasov#
14*d3c97224SAlexander Kolbasov# When distributing Covered Code, include this CDDL HEADER in each
15*d3c97224SAlexander Kolbasov# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16*d3c97224SAlexander Kolbasov# If applicable, add the following below this CDDL HEADER, with the
17*d3c97224SAlexander Kolbasov# fields enclosed by brackets "[]" replaced with your own identifying
18*d3c97224SAlexander Kolbasov# information: Portions Copyright [yyyy] [name of copyright owner]
19*d3c97224SAlexander Kolbasov#
20*d3c97224SAlexander Kolbasov# CDDL HEADER END
21*d3c97224SAlexander Kolbasov#
22*d3c97224SAlexander Kolbasov
23*d3c97224SAlexander Kolbasov#
24*d3c97224SAlexander Kolbasov# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
25*d3c97224SAlexander Kolbasov#
26*d3c97224SAlexander Kolbasov
27*d3c97224SAlexander Kolbasov#
28*d3c97224SAlexander Kolbasov# pgstat - tool for displaying Processor Group statistics
29*d3c97224SAlexander Kolbasov#
30*d3c97224SAlexander Kolbasov
31*d3c97224SAlexander Kolbasovuse warnings;
32*d3c97224SAlexander Kolbasovuse strict;
33*d3c97224SAlexander Kolbasovuse File::Basename;
34*d3c97224SAlexander Kolbasovuse List::Util qw(first max min);
35*d3c97224SAlexander Kolbasovuse Errno;
36*d3c97224SAlexander Kolbasovuse POSIX qw(locale_h strftime);
37*d3c97224SAlexander Kolbasovuse Getopt::Long qw(:config no_ignore_case bundling auto_version);
38*d3c97224SAlexander Kolbasovuse Sun::Solaris::Utils qw(textdomain gettext);
39*d3c97224SAlexander Kolbasovuse Sun::Solaris::Pg;
40*d3c97224SAlexander Kolbasov
41*d3c97224SAlexander Kolbasov#
42*d3c97224SAlexander Kolbasov# Constants section
43*d3c97224SAlexander Kolbasov#
44*d3c97224SAlexander Kolbasov# It is possible that wnen trying to parse PG kstats, PG generation changes
45*d3c97224SAlexander Kolbasov# which will cause PG new method to fail with errno set to EAGAIN In this case
46*d3c97224SAlexander Kolbasov# we retry open up to RETRY_COUNT times pausing RETRY_DELAY seconds between each
47*d3c97224SAlexander Kolbasov# retry.
48*d3c97224SAlexander Kolbasov#
49*d3c97224SAlexander Kolbasov# When printing PGs we print them as a little tree with each PG shifted by
50*d3c97224SAlexander Kolbasov# LEVEL_OFFSET from each parent. For example:
51*d3c97224SAlexander Kolbasov#
52*d3c97224SAlexander Kolbasov# PG  RELATIONSHIP                    CPUs
53*d3c97224SAlexander Kolbasov# 0   System                          0-7
54*d3c97224SAlexander Kolbasov# 3    Socket                         0 2 4 6
55*d3c97224SAlexander Kolbasov# 2     Cache                        0 2 4 6
56*d3c97224SAlexander Kolbasov#
57*d3c97224SAlexander Kolbasov#
58*d3c97224SAlexander Kolbasov# DEFAULT_INTERVAL - interval in seconds between snapshot if none is specified
59*d3c97224SAlexander Kolbasov# DEFAULT_COUNT	   - Number of iterations if none is specified
60*d3c97224SAlexander Kolbasov# HWLOAD_UNKNOWN   - Value that we use to represent unknown hardware load
61*d3c97224SAlexander Kolbasov# HWLOAD_UNDEF	   - Value that we use to represent undefined hardware load
62*d3c97224SAlexander Kolbasov#
63*d3c97224SAlexander Kolbasovuse constant {
64*d3c97224SAlexander Kolbasov	VERSION		=> 1.1,
65*d3c97224SAlexander Kolbasov	DEFAULT_INTERVAL => 1,
66*d3c97224SAlexander Kolbasov        DEFAULT_COUNT	=> 1,
67*d3c97224SAlexander Kolbasov	RETRY_COUNT	=> 4,
68*d3c97224SAlexander Kolbasov        RETRY_DELAY	=> 0.25,
69*d3c97224SAlexander Kolbasov	HWLOAD_UNKNOWN	=> -1,
70*d3c97224SAlexander Kolbasov	HWLOAD_UNDEF	=> -2,
71*d3c97224SAlexander Kolbasov	LEVEL_OFFSET	=> 1,
72*d3c97224SAlexander Kolbasov};
73*d3c97224SAlexander Kolbasov
74*d3c97224SAlexander Kolbasov#
75*d3c97224SAlexander Kolbasov# Format for fields, showing percentage headers
76*d3c97224SAlexander Kolbasov#
77*d3c97224SAlexander Kolbasovmy $pcnt_fmt = "%6s";
78*d3c97224SAlexander Kolbasov#
79*d3c97224SAlexander Kolbasov# Format for percentages field
80*d3c97224SAlexander Kolbasov#
81*d3c97224SAlexander Kolbasovmy $pcnt = "%5.1f";
82*d3c97224SAlexander Kolbasov
83*d3c97224SAlexander Kolbasov#
84*d3c97224SAlexander Kolbasov# Return codes
85*d3c97224SAlexander Kolbasov#
86*d3c97224SAlexander Kolbasov#     0    Successful completion.
87*d3c97224SAlexander Kolbasov#
88*d3c97224SAlexander Kolbasov#     1    An error occurred.
89*d3c97224SAlexander Kolbasov#
90*d3c97224SAlexander Kolbasov#     2    Invalid command-line options were specified.
91*d3c97224SAlexander Kolbasov#
92*d3c97224SAlexander Kolbasovuse constant {
93*d3c97224SAlexander Kolbasov	E_SUCCESS => 0,
94*d3c97224SAlexander Kolbasov	E_ERROR => 1,
95*d3c97224SAlexander Kolbasov	E_USAGE => 2,
96*d3c97224SAlexander Kolbasov};
97*d3c97224SAlexander Kolbasov
98*d3c97224SAlexander Kolbasov#
99*d3c97224SAlexander Kolbasov# Valid sort keys for -s and -S options
100*d3c97224SAlexander Kolbasov#
101*d3c97224SAlexander Kolbasovmy @sort_keys = qw(pg hwload swload user sys idle depth breadth);
102*d3c97224SAlexander Kolbasov
103*d3c97224SAlexander Kolbasov# Set message locale
104*d3c97224SAlexander Kolbasovsetlocale(LC_ALL, "");
105*d3c97224SAlexander Kolbasovtextdomain(TEXT_DOMAIN);
106*d3c97224SAlexander Kolbasov
107*d3c97224SAlexander Kolbasov# Get script name for error messages
108*d3c97224SAlexander Kolbasovour $cmdname = basename($0, ".pl");
109*d3c97224SAlexander Kolbasov
110*d3c97224SAlexander Kolbasovmy @pg_list;		# -P pg,...	- PG arguments
111*d3c97224SAlexander Kolbasovmy @cpu_list;		# -c cpu,...	- CPU arguments
112*d3c97224SAlexander Kolbasovmy @sharing_filter_neg; # -R string,... - Prune PGs
113*d3c97224SAlexander Kolbasovmy @sharing_filter;	# -r string,...	- Matching sharing names
114*d3c97224SAlexander Kolbasovmy $do_aggregate;	# -A		- Show summary in the end
115*d3c97224SAlexander Kolbasovmy $do_cpu_utilization; # -C		- Show per-CPU utilization
116*d3c97224SAlexander Kolbasovmy $do_physical;	# -p		- Show physical relationships
117*d3c97224SAlexander Kolbasovmy $do_timestamp;	# -T		- Print timestamp
118*d3c97224SAlexander Kolbasovmy $do_usage;		# -h		- Show usage
119*d3c97224SAlexander Kolbasovmy $do_version;		# -V		- Verbose output
120*d3c97224SAlexander Kolbasovmy $show_top;		# -t		- show top N
121*d3c97224SAlexander Kolbasovmy $sort_order_a;	# -S key	- Ascending sort order
122*d3c97224SAlexander Kolbasovmy $sort_order_d;	# -s key	- Descending sort order
123*d3c97224SAlexander Kolbasovmy $verbose;		# -v		- Verbose output;
124*d3c97224SAlexander Kolbasov
125*d3c97224SAlexander Kolbasov$verbose = 0;
126*d3c97224SAlexander Kolbasov
127*d3c97224SAlexander Kolbasov# Parse options from the command line
128*d3c97224SAlexander KolbasovGetOptions("aggregate|A"	=> \$do_aggregate,
129*d3c97224SAlexander Kolbasov	   "cpus|c=s"		=> \@cpu_list,
130*d3c97224SAlexander Kolbasov	   "showcpu|C"		=> \$do_cpu_utilization,
131*d3c97224SAlexander Kolbasov	   "help|h|?"		=> \$do_usage,
132*d3c97224SAlexander Kolbasov	   "pgs|P=s"		=> \@pg_list,
133*d3c97224SAlexander Kolbasov	   "physical|p"		=> \$do_physical,
134*d3c97224SAlexander Kolbasov	   "relationship|r=s"	=> \@sharing_filter,
135*d3c97224SAlexander Kolbasov	   "norelationship|R=s" => \@sharing_filter_neg,
136*d3c97224SAlexander Kolbasov	   "sort|s=s"		=> \$sort_order_d,
137*d3c97224SAlexander Kolbasov	   "Sort|S=s"		=> \$sort_order_a,
138*d3c97224SAlexander Kolbasov	   "top|t=i"		=> \$show_top,
139*d3c97224SAlexander Kolbasov	   "timestamp|T=s"	=> \$do_timestamp,
140*d3c97224SAlexander Kolbasov	   "version|V"		=> \$do_version,
141*d3c97224SAlexander Kolbasov	   "verbose+"		=> \$verbose,
142*d3c97224SAlexander Kolbasov	   "v+"			=> \$verbose,
143*d3c97224SAlexander Kolbasov) || usage(E_USAGE);
144*d3c97224SAlexander Kolbasov
145*d3c97224SAlexander Kolbasov# Print usage message when -h is given
146*d3c97224SAlexander Kolbasovusage(E_SUCCESS) if $do_usage;
147*d3c97224SAlexander Kolbasov
148*d3c97224SAlexander Kolbasovif ($do_version) {
149*d3c97224SAlexander Kolbasov	printf gettext("%s version %s\n"), $cmdname, VERSION;
150*d3c97224SAlexander Kolbasov	exit(E_SUCCESS);
151*d3c97224SAlexander Kolbasov}
152*d3c97224SAlexander Kolbasov
153*d3c97224SAlexander Kolbasov#
154*d3c97224SAlexander Kolbasov# Verify options
155*d3c97224SAlexander Kolbasov#
156*d3c97224SAlexander Kolbasov# -T should have either u or d argument
157*d3c97224SAlexander Kolbasovif (defined($do_timestamp) && !($do_timestamp eq 'u' || $do_timestamp eq 'd')) {
158*d3c97224SAlexander Kolbasov	printf STDERR gettext("%s: Invalid -T %s argument\n"),
159*d3c97224SAlexander Kolbasov	  $cmdname, $do_timestamp;
160*d3c97224SAlexander Kolbasov	usage(E_USAGE);
161*d3c97224SAlexander Kolbasov}
162*d3c97224SAlexander Kolbasov
163*d3c97224SAlexander Kolbasovif ($sort_order_a && $sort_order_d) {
164*d3c97224SAlexander Kolbasov	printf STDERR gettext("%s: -S and -s flags can not be used together\n"),
165*d3c97224SAlexander Kolbasov	  $cmdname;
166*d3c97224SAlexander Kolbasov	usage(E_USAGE);
167*d3c97224SAlexander Kolbasov}
168*d3c97224SAlexander Kolbasov
169*d3c97224SAlexander Kolbasovif (defined ($show_top) && $show_top <= 0) {
170*d3c97224SAlexander Kolbasov	printf STDERR gettext("%s: -t should specify positive integer\n"),
171*d3c97224SAlexander Kolbasov	  $cmdname;
172*d3c97224SAlexander Kolbasov	usage(E_USAGE);
173*d3c97224SAlexander Kolbasov}
174*d3c97224SAlexander Kolbasov
175*d3c97224SAlexander Kolbasov#
176*d3c97224SAlexander Kolbasov# Figure out requested sorting of the output
177*d3c97224SAlexander Kolbasov# By default 'depth-first' is used
178*d3c97224SAlexander Kolbasov#
179*d3c97224SAlexander Kolbasovmy $sort_key;
180*d3c97224SAlexander Kolbasovmy $sort_reverse;
181*d3c97224SAlexander Kolbasov
182*d3c97224SAlexander Kolbasovif (!($sort_order_a || $sort_order_d)) {
183*d3c97224SAlexander Kolbasov	$sort_key = 'depth';
184*d3c97224SAlexander Kolbasov	$sort_reverse = 1;
185*d3c97224SAlexander Kolbasov} else {
186*d3c97224SAlexander Kolbasov	$sort_key = $sort_order_d || $sort_order_a;
187*d3c97224SAlexander Kolbasov	$sort_reverse = defined($sort_order_d);
188*d3c97224SAlexander Kolbasov}
189*d3c97224SAlexander Kolbasov
190*d3c97224SAlexander Kolbasov#
191*d3c97224SAlexander Kolbasov# Make sure sort key is valid
192*d3c97224SAlexander Kolbasov#
193*d3c97224SAlexander Kolbasovif (!list_match($sort_key, \@sort_keys, 1)) {
194*d3c97224SAlexander Kolbasov	printf STDERR gettext("%s: invalid sort key %s\n"),
195*d3c97224SAlexander Kolbasov	  $cmdname, $sort_key;
196*d3c97224SAlexander Kolbasov	usage(E_USAGE);
197*d3c97224SAlexander Kolbasov}
198*d3c97224SAlexander Kolbasov
199*d3c97224SAlexander Kolbasov#
200*d3c97224SAlexander Kolbasov# Convert -[Rr] string1,string2,... into list (string1, string2, ...)
201*d3c97224SAlexander Kolbasov#
202*d3c97224SAlexander Kolbasov@sharing_filter = map { split /,/ } @sharing_filter;
203*d3c97224SAlexander Kolbasov@sharing_filter_neg = map { split /,/ } @sharing_filter_neg;
204*d3c97224SAlexander Kolbasov
205*d3c97224SAlexander Kolbasov#
206*d3c97224SAlexander Kolbasov# We use two PG snapshot to compare utilization between them. One snapshot is
207*d3c97224SAlexander Kolbasov# kept behind another in time.
208*d3c97224SAlexander Kolbasov#
209*d3c97224SAlexander Kolbasovmy $p = Sun::Solaris::Pg->new(-cpudata => $do_cpu_utilization,
210*d3c97224SAlexander Kolbasov			      -swload => 1,
211*d3c97224SAlexander Kolbasov			      -tags => $do_physical,
212*d3c97224SAlexander Kolbasov			      -retry => RETRY_COUNT,
213*d3c97224SAlexander Kolbasov			      -delay => RETRY_DELAY);
214*d3c97224SAlexander Kolbasov
215*d3c97224SAlexander Kolbasovif (!$p) {
216*d3c97224SAlexander Kolbasov	printf STDERR
217*d3c97224SAlexander Kolbasov	  gettext("%s: can not obtain Processor Group information: $!\n"),
218*d3c97224SAlexander Kolbasov	    $cmdname;
219*d3c97224SAlexander Kolbasov	exit(E_ERROR);
220*d3c97224SAlexander Kolbasov}
221*d3c97224SAlexander Kolbasov
222*d3c97224SAlexander Kolbasovmy $p_initial = $p;
223*d3c97224SAlexander Kolbasovmy $p_dup = Sun::Solaris::Pg->new(-cpudata => $do_cpu_utilization,
224*d3c97224SAlexander Kolbasov				  -swload => 1,
225*d3c97224SAlexander Kolbasov				  -tags => $do_physical,
226*d3c97224SAlexander Kolbasov				  -retry => RETRY_COUNT,
227*d3c97224SAlexander Kolbasov				  -delay => RETRY_DELAY);
228*d3c97224SAlexander Kolbasov
229*d3c97224SAlexander Kolbasovif (!$p_dup) {
230*d3c97224SAlexander Kolbasov	printf STDERR
231*d3c97224SAlexander Kolbasov	  gettext("%s: can not obtain Processor Group information: $!\n"),
232*d3c97224SAlexander Kolbasov	    $cmdname;
233*d3c97224SAlexander Kolbasov	exit(E_ERROR);
234*d3c97224SAlexander Kolbasov}
235*d3c97224SAlexander Kolbasov
236*d3c97224SAlexander Kolbasov#
237*d3c97224SAlexander Kolbasov# Get interval and count
238*d3c97224SAlexander Kolbasov#
239*d3c97224SAlexander Kolbasovmy $count = DEFAULT_COUNT;
240*d3c97224SAlexander Kolbasovmy $interval = DEFAULT_INTERVAL;
241*d3c97224SAlexander Kolbasov
242*d3c97224SAlexander Kolbasovif (scalar @ARGV > 0) {
243*d3c97224SAlexander Kolbasov	$interval = shift @ARGV;
244*d3c97224SAlexander Kolbasov	if (scalar @ARGV > 0) {
245*d3c97224SAlexander Kolbasov		$count = $ARGV[0];
246*d3c97224SAlexander Kolbasov	} else {
247*d3c97224SAlexander Kolbasov		$count = 0;
248*d3c97224SAlexander Kolbasov	}
249*d3c97224SAlexander Kolbasov}
250*d3c97224SAlexander Kolbasov
251*d3c97224SAlexander Kolbasovif (! ($interval=~ m/^\d+\.?\d*$/)) {
252*d3c97224SAlexander Kolbasov	printf STDERR
253*d3c97224SAlexander Kolbasov	  gettext("%s: Invalid interval %s - should be numeric\n"),
254*d3c97224SAlexander Kolbasov	    $cmdname, $interval;
255*d3c97224SAlexander Kolbasov	usage(E_USAGE);
256*d3c97224SAlexander Kolbasov}
257*d3c97224SAlexander Kolbasov
258*d3c97224SAlexander Kolbasovif ($count && ! ($count=~ m/^\d+$/)) {
259*d3c97224SAlexander Kolbasov	printf STDERR
260*d3c97224SAlexander Kolbasov	  gettext("%s: Invalid count %s - should be numeric\n"),
261*d3c97224SAlexander Kolbasov	    $cmdname, $count;
262*d3c97224SAlexander Kolbasov	usage(E_USAGE);
263*d3c97224SAlexander Kolbasov}
264*d3c97224SAlexander Kolbasov
265*d3c97224SAlexander Kolbasovmy $infinite = 1 unless $count;
266*d3c97224SAlexander Kolbasov
267*d3c97224SAlexander Kolbasov#
268*d3c97224SAlexander Kolbasov# Get list of all PGs
269*d3c97224SAlexander Kolbasov#
270*d3c97224SAlexander Kolbasovmy @all_pgs = $p->all_depth_first();
271*d3c97224SAlexander Kolbasov
272*d3c97224SAlexander Kolbasov#
273*d3c97224SAlexander Kolbasov# get list of all CPUs in the system by looking at the root PG cpus
274*d3c97224SAlexander Kolbasov#
275*d3c97224SAlexander Kolbasovmy @all_cpus = $p->cpus($p->root());
276*d3c97224SAlexander Kolbasov
277*d3c97224SAlexander Kolbasov# PGs to work with
278*d3c97224SAlexander Kolbasovmy @pgs = @all_pgs;
279*d3c97224SAlexander Kolbasov
280*d3c97224SAlexander Kolbasovmy $rc = E_SUCCESS;
281*d3c97224SAlexander Kolbasov
282*d3c97224SAlexander Kolbasov#
283*d3c97224SAlexander Kolbasov# Convert CPU and PG lists into proper Perl lists, converting things like
284*d3c97224SAlexander Kolbasov# 1-3,5 into (1, 2, 3, 5). Also convert 'all' into the list of all CPUs or PGs
285*d3c97224SAlexander Kolbasov#
286*d3c97224SAlexander Kolbasov@cpu_list =
287*d3c97224SAlexander Kolbasov  map { $_ eq 'all' ? @all_cpus : $_ }	# all -> (cpu1, cpu2, ...)
288*d3c97224SAlexander Kolbasov  map { split /,/ } @cpu_list;		# x,y -> (x, y)
289*d3c97224SAlexander Kolbasov
290*d3c97224SAlexander Kolbasov@cpu_list = $p->expand(@cpu_list);	# 1-3 -> 1 2 3
291*d3c97224SAlexander Kolbasov
292*d3c97224SAlexander Kolbasov# Same drill for PGs
293*d3c97224SAlexander Kolbasov@pg_list =
294*d3c97224SAlexander Kolbasov  map { $_ eq 'all' ? @all_pgs : $_ }
295*d3c97224SAlexander Kolbasov  map { split /,/ } @pg_list;
296*d3c97224SAlexander Kolbasov
297*d3c97224SAlexander Kolbasov@pg_list = $p->expand(@pg_list);
298*d3c97224SAlexander Kolbasov
299*d3c97224SAlexander Kolbasov#
300*d3c97224SAlexander Kolbasov# Convert CPU list to list of PGs
301*d3c97224SAlexander Kolbasov#
302*d3c97224SAlexander Kolbasovif (scalar @cpu_list) {
303*d3c97224SAlexander Kolbasov
304*d3c97224SAlexander Kolbasov	#
305*d3c97224SAlexander Kolbasov	# Warn about any invalid CPU IDs in the arguments
306*d3c97224SAlexander Kolbasov	# @bad_cpus is a list of invalid CPU IDs
307*d3c97224SAlexander Kolbasov	#
308*d3c97224SAlexander Kolbasov	my @bad_cpus = $p->set_subtract(\@all_cpus, \@cpu_list);
309*d3c97224SAlexander Kolbasov	if (scalar @bad_cpus) {
310*d3c97224SAlexander Kolbasov		printf STDERR
311*d3c97224SAlexander Kolbasov		  gettext("%s: Invalid processor IDs %s\n"),
312*d3c97224SAlexander Kolbasov		    $cmdname, $p->id_collapse(@bad_cpus);
313*d3c97224SAlexander Kolbasov		$rc = E_ERROR;
314*d3c97224SAlexander Kolbasov	}
315*d3c97224SAlexander Kolbasov
316*d3c97224SAlexander Kolbasov	#
317*d3c97224SAlexander Kolbasov	# Find all PGs which have at least some CPUs from @cpu_list
318*d3c97224SAlexander Kolbasov	#
319*d3c97224SAlexander Kolbasov	my @pgs_from_cpus = grep {
320*d3c97224SAlexander Kolbasov		my @cpus = $p->cpus($_);
321*d3c97224SAlexander Kolbasov		scalar($p->intersect(\@cpus, \@cpu_list));
322*d3c97224SAlexander Kolbasov	} @all_pgs;
323*d3c97224SAlexander Kolbasov
324*d3c97224SAlexander Kolbasov	# Combine PGs from @pg_list (if any) with PGs we found
325*d3c97224SAlexander Kolbasov	@pg_list = (@pg_list, @pgs_from_cpus);
326*d3c97224SAlexander Kolbasov}
327*d3c97224SAlexander Kolbasov
328*d3c97224SAlexander Kolbasov#
329*d3c97224SAlexander Kolbasov# If there are any PGs specified by the user, complain about invalid ones
330*d3c97224SAlexander Kolbasov#
331*d3c97224SAlexander Kolbasov@pgs = get_pg_list($p, \@pg_list, \@sharing_filter, \@sharing_filter_neg);
332*d3c97224SAlexander Kolbasov
333*d3c97224SAlexander Kolbasovif (scalar @pg_list > 0) {
334*d3c97224SAlexander Kolbasov	#
335*d3c97224SAlexander Kolbasov	# Warn about any invalid PG
336*d3c97224SAlexander Kolbasov	# @bad_pgs is a list of invalid CPUs in the arguments
337*d3c97224SAlexander Kolbasov	#
338*d3c97224SAlexander Kolbasov	my @bad_pgs = $p->set_subtract(\@all_pgs, \@pg_list);
339*d3c97224SAlexander Kolbasov	if (scalar @bad_pgs) {
340*d3c97224SAlexander Kolbasov		printf STDERR
341*d3c97224SAlexander Kolbasov		  gettext("%s: warning: invalid PG IDs %s\n"),
342*d3c97224SAlexander Kolbasov		    $cmdname, $p->id_collapse(@bad_pgs);
343*d3c97224SAlexander Kolbasov	}
344*d3c97224SAlexander Kolbasov}
345*d3c97224SAlexander Kolbasov
346*d3c97224SAlexander Kolbasov# Do we have any PGs left?
347*d3c97224SAlexander Kolbasovif (scalar(@pgs) == 0) {
348*d3c97224SAlexander Kolbasov	printf STDERR
349*d3c97224SAlexander Kolbasov	gettext("%s: No processor groups matching command line arguments\n"),
350*d3c97224SAlexander Kolbasov	    $cmdname;
351*d3c97224SAlexander Kolbasov	exit(E_USAGE);
352*d3c97224SAlexander Kolbasov}
353*d3c97224SAlexander Kolbasov
354*d3c97224SAlexander Kolbasov#
355*d3c97224SAlexander Kolbasov# Set $do_levels if we should provide output identation by level It doesn't make
356*d3c97224SAlexander Kolbasov# sense to provide identation if PGs are sorted not in topology order.
357*d3c97224SAlexander Kolbasov#
358*d3c97224SAlexander Kolbasovmy $do_levels = ($sort_key eq 'breadth' || $sort_key eq 'depth');
359*d3c97224SAlexander Kolbasov
360*d3c97224SAlexander Kolbasov#
361*d3c97224SAlexander Kolbasov# %name_of_pg hash keeps sharing name, possibly with physical tags appended to
362*d3c97224SAlexander Kolbasov# it for each PG.
363*d3c97224SAlexander Kolbasov#
364*d3c97224SAlexander Kolbasovmy %name_of_pg;
365*d3c97224SAlexander Kolbasov
366*d3c97224SAlexander Kolbasov#
367*d3c97224SAlexander Kolbasov# For calculating proper offsets we need to know minimum and maximum level for
368*d3c97224SAlexander Kolbasov# all PGs
369*d3c97224SAlexander Kolbasov#
370*d3c97224SAlexander Kolbasovmy $max_sharename_len = length('RELATIONSHIP');
371*d3c97224SAlexander Kolbasov
372*d3c97224SAlexander Kolbasovmy $maxlevel;
373*d3c97224SAlexander Kolbasovmy $minlevel;
374*d3c97224SAlexander Kolbasov
375*d3c97224SAlexander Kolbasovif ($do_levels) {
376*d3c97224SAlexander Kolbasov	my @levels = map { $p->level($_) } @pgs;	# Levels for each PG
377*d3c97224SAlexander Kolbasov	$maxlevel = max(@levels);
378*d3c97224SAlexander Kolbasov	$minlevel = min(@levels);
379*d3c97224SAlexander Kolbasov}
380*d3c97224SAlexander Kolbasov
381*d3c97224SAlexander Kolbasov#
382*d3c97224SAlexander Kolbasov# Walk over all PGs and find out the string length that we need to represent
383*d3c97224SAlexander Kolbasov# sharing name + physical tags + indentation level.
384*d3c97224SAlexander Kolbasov#
385*d3c97224SAlexander Kolbasovforeach my $pg (@pgs) {
386*d3c97224SAlexander Kolbasov	my $name =  $p->sh_name ($pg) || "unknown";
387*d3c97224SAlexander Kolbasov	my $level = $p->level($pg) || 0 if $do_levels;
388*d3c97224SAlexander Kolbasov
389*d3c97224SAlexander Kolbasov	if ($do_physical) {
390*d3c97224SAlexander Kolbasov		my $tags = $p->tags($pg);
391*d3c97224SAlexander Kolbasov		$name = "$name [$tags]" if $tags;
392*d3c97224SAlexander Kolbasov		$name_of_pg{$pg} = $name;
393*d3c97224SAlexander Kolbasov	}
394*d3c97224SAlexander Kolbasov
395*d3c97224SAlexander Kolbasov	$name_of_pg{$pg} = $name;
396*d3c97224SAlexander Kolbasov	my $length = length($name);
397*d3c97224SAlexander Kolbasov	$length += $level - $minlevel if $do_levels;
398*d3c97224SAlexander Kolbasov	$max_sharename_len = $length if $length > $max_sharename_len;
399*d3c97224SAlexander Kolbasov}
400*d3c97224SAlexander Kolbasov
401*d3c97224SAlexander Kolbasov# Maximum length of PG ID field
402*d3c97224SAlexander Kolbasovmy $max_pg_len = length(max(@pgs)) + 1;
403*d3c97224SAlexander Kolbasov$max_pg_len = length('PG') if ($max_pg_len) < length('PG');
404*d3c97224SAlexander Kolbasov
405*d3c97224SAlexander Kolbasov#
406*d3c97224SAlexander Kolbasov#
407*d3c97224SAlexander Kolbasov# %pgs hash contains various statistics per PG that is used for sorting.
408*d3c97224SAlexander Kolbasovmy %pgs;
409*d3c97224SAlexander Kolbasov
410*d3c97224SAlexander Kolbasov# Total number of main loop iterations we actually do
411*d3c97224SAlexander Kolbasovmy $total_iterations = 0;
412*d3c97224SAlexander Kolbasov
413*d3c97224SAlexander Kolbasov#
414*d3c97224SAlexander Kolbasov# For summary, keep track of minimum and maximum data per PG
415*d3c97224SAlexander Kolbasov#
416*d3c97224SAlexander Kolbasovmy $history;
417*d3c97224SAlexander Kolbasov
418*d3c97224SAlexander Kolbasov#
419*d3c97224SAlexander Kolbasov# Provide summary output when aggregation is requested and user hits ^C
420*d3c97224SAlexander Kolbasov#
421*d3c97224SAlexander Kolbasov$SIG{'INT'} = \&print_totals if $do_aggregate;
422*d3c97224SAlexander Kolbasov
423*d3c97224SAlexander Kolbasov######################################################################
424*d3c97224SAlexander Kolbasov# Main loop
425*d3c97224SAlexander Kolbasov###########
426*d3c97224SAlexander Kolbasov
427*d3c97224SAlexander Kolbasovwhile ($infinite || $count--) {
428*d3c97224SAlexander Kolbasov	#
429*d3c97224SAlexander Kolbasov	# Print timestamp if -T is specified
430*d3c97224SAlexander Kolbasov	#
431*d3c97224SAlexander Kolbasov	if ($do_timestamp) {
432*d3c97224SAlexander Kolbasov		if ($do_timestamp eq 'u') {
433*d3c97224SAlexander Kolbasov			print time(), "\n";
434*d3c97224SAlexander Kolbasov		} else {
435*d3c97224SAlexander Kolbasov			my $date_str = strftime "%A, %B %e, %Y %r %Z",
436*d3c97224SAlexander Kolbasov			  localtime;
437*d3c97224SAlexander Kolbasov			print "$date_str\n";
438*d3c97224SAlexander Kolbasov		}
439*d3c97224SAlexander Kolbasov	}
440*d3c97224SAlexander Kolbasov
441*d3c97224SAlexander Kolbasov	#
442*d3c97224SAlexander Kolbasov	# Wait for the requested interval
443*d3c97224SAlexander Kolbasov	#
444*d3c97224SAlexander Kolbasov	select(undef, undef, undef, $interval);
445*d3c97224SAlexander Kolbasov
446*d3c97224SAlexander Kolbasov	#
447*d3c97224SAlexander Kolbasov	# Print headers
448*d3c97224SAlexander Kolbasov	# There are two different output formats - one regular and one verbose
449*d3c97224SAlexander Kolbasov	#
450*d3c97224SAlexander Kolbasov	if (!$verbose) {
451*d3c97224SAlexander Kolbasov		printf "%-${max_pg_len}s  %-${max_sharename_len}s ".
452*d3c97224SAlexander Kolbasov		  "$pcnt_fmt  $pcnt_fmt  %-s\n",
453*d3c97224SAlexander Kolbasov		  'PG', 'RELATIONSHIP', 'HW', 'SW', 'CPUS';
454*d3c97224SAlexander Kolbasov	} else {
455*d3c97224SAlexander Kolbasov		printf "%-${max_pg_len}s  %-${max_sharename_len}s" .
456*d3c97224SAlexander Kolbasov		  " $pcnt_fmt %4s %4s $pcnt_fmt $pcnt_fmt $pcnt_fmt $pcnt_fmt %s\n",
457*d3c97224SAlexander Kolbasov		  'PG','RELATIONSHIP',
458*d3c97224SAlexander Kolbasov		  'HW', 'UTIL', 'CAP',
459*d3c97224SAlexander Kolbasov		  'SW', 'USR', 'SYS', 'IDLE', 'CPUS';
460*d3c97224SAlexander Kolbasov	}
461*d3c97224SAlexander Kolbasov
462*d3c97224SAlexander Kolbasov	#
463*d3c97224SAlexander Kolbasov	# Update the data in one of the snapshots
464*d3c97224SAlexander Kolbasov	#
465*d3c97224SAlexander Kolbasov	$p_dup->update();
466*d3c97224SAlexander Kolbasov
467*d3c97224SAlexander Kolbasov	#
468*d3c97224SAlexander Kolbasov	# Do not show offlined CPUs
469*d3c97224SAlexander Kolbasov	#
470*d3c97224SAlexander Kolbasov	my @online_cpus = $p->online_cpus();
471*d3c97224SAlexander Kolbasov
472*d3c97224SAlexander Kolbasov	#
473*d3c97224SAlexander Kolbasov	# Check whether both snapshots belong to the same generation
474*d3c97224SAlexander Kolbasov	#
475*d3c97224SAlexander Kolbasov	if ($p->generation() != $p_dup->generation()) {
476*d3c97224SAlexander Kolbasov		printf gettext("Configuration changed!\n");
477*d3c97224SAlexander Kolbasov		# Swap $p and $p_dup;
478*d3c97224SAlexander Kolbasov		$p = $p_dup;
479*d3c97224SAlexander Kolbasov		$p_dup = Sun::Solaris::Pg->new(
480*d3c97224SAlexander Kolbasov					       -cpudata => $do_cpu_utilization,
481*d3c97224SAlexander Kolbasov					       -swload => 1,
482*d3c97224SAlexander Kolbasov					       -tags => $do_physical,
483*d3c97224SAlexander Kolbasov					       -retry => RETRY_COUNT,
484*d3c97224SAlexander Kolbasov					       -delay => RETRY_DELAY);
485*d3c97224SAlexander Kolbasov		if (!$p_dup) {
486*d3c97224SAlexander Kolbasov			printf STDERR gettext(
487*d3c97224SAlexander Kolbasov			  "%s: can not obtain Processor Group information: $!\n"),
488*d3c97224SAlexander Kolbasov			    $cmdname;
489*d3c97224SAlexander Kolbasov			exit(E_ERROR);
490*d3c97224SAlexander Kolbasov		}
491*d3c97224SAlexander Kolbasov		#
492*d3c97224SAlexander Kolbasov		# Recreate @pg_list since it may have changed
493*d3c97224SAlexander Kolbasov		#
494*d3c97224SAlexander Kolbasov		@pgs = get_pg_list($p, \@pg_list,
495*d3c97224SAlexander Kolbasov				   \@sharing_filter, \@sharing_filter_neg);
496*d3c97224SAlexander Kolbasov
497*d3c97224SAlexander Kolbasov		next;
498*d3c97224SAlexander Kolbasov	}
499*d3c97224SAlexander Kolbasov
500*d3c97224SAlexander Kolbasov	%pgs = ();
501*d3c97224SAlexander Kolbasov
502*d3c97224SAlexander Kolbasov	#
503*d3c97224SAlexander Kolbasov	# Go over each PG and gets its utilization data
504*d3c97224SAlexander Kolbasov	#
505*d3c97224SAlexander Kolbasov	foreach my $pg (@pgs) {
506*d3c97224SAlexander Kolbasov		my ($hwload, $utilization, $capacity, $accuracy) =
507*d3c97224SAlexander Kolbasov		  get_load($p, $p_dup, $pg);
508*d3c97224SAlexander Kolbasov		my @cpus = $p->cpus ($pg);
509*d3c97224SAlexander Kolbasov		my ($user, $sys, $idle, $swload) =
510*d3c97224SAlexander Kolbasov		  $p->sw_utilization($p_dup, $pg);
511*d3c97224SAlexander Kolbasov
512*d3c97224SAlexander Kolbasov		# Adjust idle and swload based on rounding
513*d3c97224SAlexander Kolbasov		($swload, $idle) = get_swload($user, $sys);
514*d3c97224SAlexander Kolbasov
515*d3c97224SAlexander Kolbasov		$pgs{$pg}->{pg} = $pg;
516*d3c97224SAlexander Kolbasov		$pgs{$pg}->{hwload} = $hwload;
517*d3c97224SAlexander Kolbasov		$pgs{$pg}->{swload} = $swload;
518*d3c97224SAlexander Kolbasov		$pgs{$pg}->{user} = $user;
519*d3c97224SAlexander Kolbasov		$pgs{$pg}->{sys} = $sys;
520*d3c97224SAlexander Kolbasov		$pgs{$pg}->{idle} = $idle;
521*d3c97224SAlexander Kolbasov		$pgs{$pg}->{utilization} = $utilization;
522*d3c97224SAlexander Kolbasov		$pgs{$pg}->{capacity} = $capacity;
523*d3c97224SAlexander Kolbasov
524*d3c97224SAlexander Kolbasov		#
525*d3c97224SAlexander Kolbasov		# Record history
526*d3c97224SAlexander Kolbasov		#
527*d3c97224SAlexander Kolbasov		$history->{$pg}->{hwload} += $hwload if $hwload && $hwload >= 0;
528*d3c97224SAlexander Kolbasov		$history->{$pg}->{swload} += $swload if $swload;
529*d3c97224SAlexander Kolbasov		$history->{$pg}->{user} += $user if $user;
530*d3c97224SAlexander Kolbasov		$history->{$pg}->{sys} += $sys if $sys;
531*d3c97224SAlexander Kolbasov		$history->{$pg}->{idle} += $idle if $idle;
532*d3c97224SAlexander Kolbasov		$history->{$pg}->{maxhwload} = $hwload if
533*d3c97224SAlexander Kolbasov		  !defined($history->{$pg}->{maxhwload}) ||
534*d3c97224SAlexander Kolbasov		    $hwload > $history->{$pg}->{maxhwload};
535*d3c97224SAlexander Kolbasov		$history->{$pg}->{minhwload} = $hwload if
536*d3c97224SAlexander Kolbasov		  !defined($history->{$pg}->{minhwload}) ||
537*d3c97224SAlexander Kolbasov		    $hwload < $history->{$pg}->{minhwload};
538*d3c97224SAlexander Kolbasov		$history->{$pg}->{maxswload} = $swload if
539*d3c97224SAlexander Kolbasov		  !defined($history->{$pg}->{maxswload}) ||
540*d3c97224SAlexander Kolbasov		    $swload > $history->{$pg}->{maxswload};
541*d3c97224SAlexander Kolbasov		$history->{$pg}->{minswload} = $swload if
542*d3c97224SAlexander Kolbasov		  !defined($history->{$pg}->{minswload}) ||
543*d3c97224SAlexander Kolbasov		    $swload < $history->{$pg}->{minswload};
544*d3c97224SAlexander Kolbasov	}
545*d3c97224SAlexander Kolbasov
546*d3c97224SAlexander Kolbasov	#
547*d3c97224SAlexander Kolbasov	# Sort the output
548*d3c97224SAlexander Kolbasov	#
549*d3c97224SAlexander Kolbasov	my @sorted_pgs;
550*d3c97224SAlexander Kolbasov	my $npgs = scalar @pgs;
551*d3c97224SAlexander Kolbasov	@sorted_pgs = pg_sort_by_key(\%pgs, $sort_key, $sort_reverse, @pgs);
552*d3c97224SAlexander Kolbasov
553*d3c97224SAlexander Kolbasov	#
554*d3c97224SAlexander Kolbasov	# Should only top N be displayed?
555*d3c97224SAlexander Kolbasov	#
556*d3c97224SAlexander Kolbasov	if ($show_top) {
557*d3c97224SAlexander Kolbasov		$npgs = $show_top if $show_top < $npgs;
558*d3c97224SAlexander Kolbasov		@sorted_pgs = @sorted_pgs[0..$npgs - 1];
559*d3c97224SAlexander Kolbasov	}
560*d3c97224SAlexander Kolbasov
561*d3c97224SAlexander Kolbasov	#
562*d3c97224SAlexander Kolbasov	# Now print everything
563*d3c97224SAlexander Kolbasov	#
564*d3c97224SAlexander Kolbasov	foreach my $pg (@sorted_pgs) {
565*d3c97224SAlexander Kolbasov		my $shname = $name_of_pg{$pg};
566*d3c97224SAlexander Kolbasov		my $level;
567*d3c97224SAlexander Kolbasov
568*d3c97224SAlexander Kolbasov		if ($do_levels) {
569*d3c97224SAlexander Kolbasov			$level = $p->level($pg) - $minlevel;
570*d3c97224SAlexander Kolbasov			$shname = (' ' x (LEVEL_OFFSET * $level)) . $shname;
571*d3c97224SAlexander Kolbasov		}
572*d3c97224SAlexander Kolbasov
573*d3c97224SAlexander Kolbasov		my $hwload = $pgs{$pg}->{hwload} || 0;
574*d3c97224SAlexander Kolbasov		my $swload = $pgs{$pg}->{swload};
575*d3c97224SAlexander Kolbasov
576*d3c97224SAlexander Kolbasov		my @cpus = $p->cpus($pg);
577*d3c97224SAlexander Kolbasov		@cpus = $p->intersect(\@cpus, \@online_cpus);
578*d3c97224SAlexander Kolbasov
579*d3c97224SAlexander Kolbasov		my $cpus = $p->id_collapse(@cpus);
580*d3c97224SAlexander Kolbasov		my $user = $pgs{$pg}->{user};
581*d3c97224SAlexander Kolbasov		my $sys = $pgs{$pg}->{sys};
582*d3c97224SAlexander Kolbasov		my $idle = $pgs{$pg}->{idle};
583*d3c97224SAlexander Kolbasov		my $utilization = $pgs{$pg}->{utilization};
584*d3c97224SAlexander Kolbasov		my $capacity = $pgs{$pg}->{capacity};
585*d3c97224SAlexander Kolbasov
586*d3c97224SAlexander Kolbasov		if (!$verbose) {
587*d3c97224SAlexander Kolbasov			printf "%${max_pg_len}d  %-${max_sharename_len}s " .
588*d3c97224SAlexander Kolbasov			  "%s  %s  %s\n",
589*d3c97224SAlexander Kolbasov			    $pg, $shname,
590*d3c97224SAlexander Kolbasov			    load2str($hwload),
591*d3c97224SAlexander Kolbasov			    load2str($swload),
592*d3c97224SAlexander Kolbasov			    $cpus;
593*d3c97224SAlexander Kolbasov		} else {
594*d3c97224SAlexander Kolbasov			printf
595*d3c97224SAlexander Kolbasov			  "%${max_pg_len}d  %-${max_sharename_len}s " .
596*d3c97224SAlexander Kolbasov			    "%4s %4s %4s %4s %4s %4s %4s %s\n",
597*d3c97224SAlexander Kolbasov			    $pg, $shname,
598*d3c97224SAlexander Kolbasov			      load2str($hwload),
599*d3c97224SAlexander Kolbasov			      number_to_scaled_string($utilization),
600*d3c97224SAlexander Kolbasov			      number_to_scaled_string($capacity),
601*d3c97224SAlexander Kolbasov			      load2str($swload),
602*d3c97224SAlexander Kolbasov			      load2str($user),
603*d3c97224SAlexander Kolbasov			      load2str($sys),
604*d3c97224SAlexander Kolbasov			      load2str($idle),
605*d3c97224SAlexander Kolbasov			      $cpus;
606*d3c97224SAlexander Kolbasov		}
607*d3c97224SAlexander Kolbasov
608*d3c97224SAlexander Kolbasov		#
609*d3c97224SAlexander Kolbasov		# If per-CPU utilization is requested, print it after each
610*d3c97224SAlexander Kolbasov		# corresponding PG
611*d3c97224SAlexander Kolbasov		#
612*d3c97224SAlexander Kolbasov		if ($do_cpu_utilization) {
613*d3c97224SAlexander Kolbasov			my $w = ${max_sharename_len} - length ('CPU');
614*d3c97224SAlexander Kolbasov			foreach my $cpu (sort {$a <=> $b }  @cpus) {
615*d3c97224SAlexander Kolbasov				my ($cpu_utilization,
616*d3c97224SAlexander Kolbasov				    $accuracy, $hw_utilization,
617*d3c97224SAlexander Kolbasov				   $swload) =
618*d3c97224SAlexander Kolbasov				     $p->cpu_utilization($p_dup, $pg, $cpu);
619*d3c97224SAlexander Kolbasov				next unless defined $cpu_utilization;
620*d3c97224SAlexander Kolbasov				my $cpuname = "CPU$cpu";
621*d3c97224SAlexander Kolbasov				if ($do_levels) {
622*d3c97224SAlexander Kolbasov					$cpuname =
623*d3c97224SAlexander Kolbasov					  (' ' x (LEVEL_OFFSET * $level)) .
624*d3c97224SAlexander Kolbasov					    $cpuname;
625*d3c97224SAlexander Kolbasov
626*d3c97224SAlexander Kolbasov				}
627*d3c97224SAlexander Kolbasov
628*d3c97224SAlexander Kolbasov				printf "%-${max_pg_len}s  " .
629*d3c97224SAlexander Kolbasov				  "%-${max_sharename_len}s ",
630*d3c97224SAlexander Kolbasov				  ' ', $cpuname;
631*d3c97224SAlexander Kolbasov				if ($verbose) {
632*d3c97224SAlexander Kolbasov				    printf "%s %4s %4s\n",
633*d3c97224SAlexander Kolbasov				      load2str($cpu_utilization),
634*d3c97224SAlexander Kolbasov				      number_to_scaled_string($hw_utilization),
635*d3c97224SAlexander Kolbasov				      number_to_scaled_string($capacity);
636*d3c97224SAlexander Kolbasov				} else {
637*d3c97224SAlexander Kolbasov					printf "%s  %s\n",
638*d3c97224SAlexander Kolbasov					  load2str($cpu_utilization),
639*d3c97224SAlexander Kolbasov					  load2str($swload);
640*d3c97224SAlexander Kolbasov				}
641*d3c97224SAlexander Kolbasov			}
642*d3c97224SAlexander Kolbasov		}
643*d3c97224SAlexander Kolbasov	}
644*d3c97224SAlexander Kolbasov
645*d3c97224SAlexander Kolbasov	#
646*d3c97224SAlexander Kolbasov	# Swap $p and $p_dup
647*d3c97224SAlexander Kolbasov	#
648*d3c97224SAlexander Kolbasov	($p, $p_dup) = ($p_dup, $p);
649*d3c97224SAlexander Kolbasov
650*d3c97224SAlexander Kolbasov	$total_iterations++;
651*d3c97224SAlexander Kolbasov}
652*d3c97224SAlexander Kolbasov
653*d3c97224SAlexander Kolbasovprint_totals() if $do_aggregate;
654*d3c97224SAlexander Kolbasov
655*d3c97224SAlexander Kolbasov
656*d3c97224SAlexander Kolbasov####################################
657*d3c97224SAlexander Kolbasov# End of main loop
658*d3c97224SAlexander Kolbasov####################################
659*d3c97224SAlexander Kolbasov
660*d3c97224SAlexander Kolbasov
661*d3c97224SAlexander Kolbasov#
662*d3c97224SAlexander Kolbasov# Support Subroutines
663*d3c97224SAlexander Kolbasov#
664*d3c97224SAlexander Kolbasov
665*d3c97224SAlexander Kolbasov#
666*d3c97224SAlexander Kolbasov# Print aggregated information in the end
667*d3c97224SAlexander Kolbasov#
668*d3c97224SAlexander Kolbasovsub print_totals
669*d3c97224SAlexander Kolbasov{
670*d3c97224SAlexander Kolbasov	exit ($rc) unless $total_iterations > 1;
671*d3c97224SAlexander Kolbasov
672*d3c97224SAlexander Kolbasov	printf gettext("\n%s SUMMARY: UTILIZATION OVER %d SECONDS\n\n"),
673*d3c97224SAlexander Kolbasov	  ' ' x 10,
674*d3c97224SAlexander Kolbasov	  $total_iterations * $interval;
675*d3c97224SAlexander Kolbasov
676*d3c97224SAlexander Kolbasov	my @sorted_pgs;
677*d3c97224SAlexander Kolbasov	my $npgs = scalar @pgs;
678*d3c97224SAlexander Kolbasov
679*d3c97224SAlexander Kolbasov	%pgs = ();
680*d3c97224SAlexander Kolbasov
681*d3c97224SAlexander Kolbasov	#
682*d3c97224SAlexander Kolbasov	# Collect data per PG
683*d3c97224SAlexander Kolbasov	#
684*d3c97224SAlexander Kolbasov	foreach my $pg (@pgs) {
685*d3c97224SAlexander Kolbasov		$pgs{$pg}->{pg} = $pg;
686*d3c97224SAlexander Kolbasov
687*d3c97224SAlexander Kolbasov		my ($hwload, $utilization, $capacity, $accuracy) =
688*d3c97224SAlexander Kolbasov		  get_load($p_initial, $p_dup, $pg);
689*d3c97224SAlexander Kolbasov
690*d3c97224SAlexander Kolbasov		my @cpus = $p->cpus ($pg);
691*d3c97224SAlexander Kolbasov		my ($user, $sys, $idle, $swload) =
692*d3c97224SAlexander Kolbasov		  $p_dup->sw_utilization($p_initial, $pg);
693*d3c97224SAlexander Kolbasov
694*d3c97224SAlexander Kolbasov		# Adjust idle and swload based on rounding
695*d3c97224SAlexander Kolbasov		($swload, $idle) = get_swload($user, $sys);
696*d3c97224SAlexander Kolbasov
697*d3c97224SAlexander Kolbasov		$pgs{$pg}->{pg} = $pg;
698*d3c97224SAlexander Kolbasov		$pgs{$pg}->{swload} = $swload;
699*d3c97224SAlexander Kolbasov		$pgs{$pg}->{user} = $user;
700*d3c97224SAlexander Kolbasov		$pgs{$pg}->{sys} = $sys;
701*d3c97224SAlexander Kolbasov		$pgs{$pg}->{idle} = $idle;
702*d3c97224SAlexander Kolbasov		$pgs{$pg}->{hwload} = $hwload;
703*d3c97224SAlexander Kolbasov		$pgs{$pg}->{utilization} = number_to_scaled_string($utilization);
704*d3c97224SAlexander Kolbasov		$pgs{$pg}->{capacity} = number_to_scaled_string($capacity);
705*d3c97224SAlexander Kolbasov		$pgs{$pg}->{minhwload} = $history->{$pg}->{minhwload};
706*d3c97224SAlexander Kolbasov		$pgs{$pg}->{maxhwload} = $history->{$pg}->{maxhwload};
707*d3c97224SAlexander Kolbasov		$pgs{$pg}->{minswload} = $history->{$pg}->{minswload} || 0;
708*d3c97224SAlexander Kolbasov		$pgs{$pg}->{maxswload} = $history->{$pg}->{maxswload} || 0;
709*d3c97224SAlexander Kolbasov	}
710*d3c97224SAlexander Kolbasov
711*d3c97224SAlexander Kolbasov	#
712*d3c97224SAlexander Kolbasov	# Sort PGs according to the sorting options
713*d3c97224SAlexander Kolbasov	#
714*d3c97224SAlexander Kolbasov	@sorted_pgs = pg_sort_by_key(\%pgs, $sort_key, $sort_reverse, @pgs);
715*d3c97224SAlexander Kolbasov
716*d3c97224SAlexander Kolbasov	#
717*d3c97224SAlexander Kolbasov	# Trim to top N if needed
718*d3c97224SAlexander Kolbasov	#
719*d3c97224SAlexander Kolbasov	if ($show_top) {
720*d3c97224SAlexander Kolbasov		$npgs = $show_top if $show_top < $npgs;
721*d3c97224SAlexander Kolbasov		@sorted_pgs = @sorted_pgs[0..$npgs - 1];
722*d3c97224SAlexander Kolbasov	}
723*d3c97224SAlexander Kolbasov
724*d3c97224SAlexander Kolbasov	#
725*d3c97224SAlexander Kolbasov	# Print headers
726*d3c97224SAlexander Kolbasov	#
727*d3c97224SAlexander Kolbasov	my $d = ' ' . '-' x 4;
728*d3c97224SAlexander Kolbasov	if ($verbose) {
729*d3c97224SAlexander Kolbasov		printf "%${max_pg_len}s  %-${max_sharename_len}s %s " .
730*d3c97224SAlexander Kolbasov		  "  ------HARDWARE------ ------SOFTWARE------\n",
731*d3c97224SAlexander Kolbasov		  ' ', ' ', ' ' x 8;
732*d3c97224SAlexander Kolbasov
733*d3c97224SAlexander Kolbasov		printf "%-${max_pg_len}s  %-${max_sharename_len}s",
734*d3c97224SAlexander Kolbasov		  'PG', 'RELATIONSHIP';
735*d3c97224SAlexander Kolbasov
736*d3c97224SAlexander Kolbasov		printf " %4s %4s", 'UTIL', ' CAP';
737*d3c97224SAlexander Kolbasov		printf "  $pcnt_fmt $pcnt_fmt $pcnt_fmt $pcnt_fmt $pcnt_fmt $pcnt_fmt %s\n",
738*d3c97224SAlexander Kolbasov		   'MIN', 'AVG', 'MAX', 'MIN', 'AVG', 'MAX', 'CPUS';
739*d3c97224SAlexander Kolbasov	} else {
740*d3c97224SAlexander Kolbasov		printf  "%${max_pg_len}s  %-${max_sharename_len}s " .
741*d3c97224SAlexander Kolbasov		  "------HARDWARE------" .
742*d3c97224SAlexander Kolbasov		  " ------SOFTWARE------\n", ' ', ' ';
743*d3c97224SAlexander Kolbasov
744*d3c97224SAlexander Kolbasov		printf "%-${max_pg_len}s  %-${max_sharename_len}s",
745*d3c97224SAlexander Kolbasov		  'PG', 'RELATIONSHIP';
746*d3c97224SAlexander Kolbasov
747*d3c97224SAlexander Kolbasov		printf " $pcnt_fmt $pcnt_fmt $pcnt_fmt $pcnt_fmt $pcnt_fmt $pcnt_fmt %s\n",
748*d3c97224SAlexander Kolbasov		   'MIN', 'AVG', 'MAX', 'MIN', 'AVG', 'MAX', 'CPUS';
749*d3c97224SAlexander Kolbasov	}
750*d3c97224SAlexander Kolbasov
751*d3c97224SAlexander Kolbasov	#
752*d3c97224SAlexander Kolbasov	# Print information per PG
753*d3c97224SAlexander Kolbasov	#
754*d3c97224SAlexander Kolbasov	foreach my $pg (@sorted_pgs) {
755*d3c97224SAlexander Kolbasov		my $cpus = $p->cpus($pg);
756*d3c97224SAlexander Kolbasov
757*d3c97224SAlexander Kolbasov		my $shname = $name_of_pg{$pg};
758*d3c97224SAlexander Kolbasov		if ($sort_key eq 'breadth' || $sort_key eq 'depth') {
759*d3c97224SAlexander Kolbasov			my $level = $p->level($pg) - $minlevel;
760*d3c97224SAlexander Kolbasov			$shname = (' ' x (LEVEL_OFFSET * $level)) . $shname;
761*d3c97224SAlexander Kolbasov		}
762*d3c97224SAlexander Kolbasov
763*d3c97224SAlexander Kolbasov		printf "%${max_pg_len}d  %-${max_sharename_len}s ",
764*d3c97224SAlexander Kolbasov		  $pg, $shname;
765*d3c97224SAlexander Kolbasov
766*d3c97224SAlexander Kolbasov		if ($verbose) {
767*d3c97224SAlexander Kolbasov			printf "%4s %4s  ",
768*d3c97224SAlexander Kolbasov			  number_to_scaled_string($pgs{$pg}->{utilization}),
769*d3c97224SAlexander Kolbasov			    number_to_scaled_string($pgs{$pg}->{capacity});
770*d3c97224SAlexander Kolbasov		}
771*d3c97224SAlexander Kolbasov
772*d3c97224SAlexander Kolbasov		if (!defined($pgs{$pg}->{hwload}) ||
773*d3c97224SAlexander Kolbasov		    $pgs{$pg}->{hwload} == HWLOAD_UNDEF) {
774*d3c97224SAlexander Kolbasov			printf "$pcnt_fmt $pcnt_fmt $pcnt_fmt ",
775*d3c97224SAlexander Kolbasov			  '-', '-', '-';
776*d3c97224SAlexander Kolbasov		} else {
777*d3c97224SAlexander Kolbasov			printf "%s %s %s ",
778*d3c97224SAlexander Kolbasov			  load2str($pgs{$pg}->{minhwload}),
779*d3c97224SAlexander Kolbasov			  load2str($pgs{$pg}->{hwload}),
780*d3c97224SAlexander Kolbasov			  load2str($pgs{$pg}->{maxhwload});
781*d3c97224SAlexander Kolbasov		}
782*d3c97224SAlexander Kolbasov		printf "%s %s %s",
783*d3c97224SAlexander Kolbasov		  load2str($pgs{$pg}->{minswload}),
784*d3c97224SAlexander Kolbasov		  load2str($pgs{$pg}->{swload}),
785*d3c97224SAlexander Kolbasov		  load2str($pgs{$pg}->{maxswload});
786*d3c97224SAlexander Kolbasov
787*d3c97224SAlexander Kolbasov		printf " %s\n", $cpus;
788*d3c97224SAlexander Kolbasov	}
789*d3c97224SAlexander Kolbasov
790*d3c97224SAlexander Kolbasov	exit ($rc);
791*d3c97224SAlexander Kolbasov}
792*d3c97224SAlexander Kolbasov
793*d3c97224SAlexander Kolbasov#
794*d3c97224SAlexander Kolbasov# pg_sort_by_key(pgs, key, inverse)
795*d3c97224SAlexander Kolbasov# Sort pgs according to the key specified
796*d3c97224SAlexander Kolbasov#
797*d3c97224SAlexander Kolbasov# Arguments:
798*d3c97224SAlexander Kolbasov#   pgs hash indexed by PG ID
799*d3c97224SAlexander Kolbasov#   sort keyword
800*d3c97224SAlexander Kolbasov#   inverse - inverse sort result if this is T
801*d3c97224SAlexander Kolbasov#
802*d3c97224SAlexander Kolbasovsub pg_sort_by_key
803*d3c97224SAlexander Kolbasov{
804*d3c97224SAlexander Kolbasov	my $pgs = shift;
805*d3c97224SAlexander Kolbasov	my $key = shift;
806*d3c97224SAlexander Kolbasov	my $inverse = shift;
807*d3c97224SAlexander Kolbasov	my @sorted;
808*d3c97224SAlexander Kolbasov
809*d3c97224SAlexander Kolbasov	if ($key eq 'depth' || $key eq 'breadth') {
810*d3c97224SAlexander Kolbasov		my $root = $p->root;
811*d3c97224SAlexander Kolbasov		my @pgs = $key eq 'depth' ?
812*d3c97224SAlexander Kolbasov		  $p->all_depth_first() :
813*d3c97224SAlexander Kolbasov		  $p->all_breadth_first();
814*d3c97224SAlexander Kolbasov		@sorted = reverse(grep { exists($pgs{$_}) } @pgs);
815*d3c97224SAlexander Kolbasov	} else {
816*d3c97224SAlexander Kolbasov		@sorted = sort { $pgs{$a}->{$key} <=> $pgs{$b}->{$key} } @_;
817*d3c97224SAlexander Kolbasov	}
818*d3c97224SAlexander Kolbasov
819*d3c97224SAlexander Kolbasov	return ($inverse ? reverse(@sorted) : @sorted);
820*d3c97224SAlexander Kolbasov}
821*d3c97224SAlexander Kolbasov
822*d3c97224SAlexander Kolbasov#
823*d3c97224SAlexander Kolbasov# Convert numeric load to formatted string
824*d3c97224SAlexander Kolbasov#
825*d3c97224SAlexander Kolbasovsub load2str
826*d3c97224SAlexander Kolbasov{
827*d3c97224SAlexander Kolbasov	my $load = shift;
828*d3c97224SAlexander Kolbasov
829*d3c97224SAlexander Kolbasov	return (sprintf "$pcnt_fmt", '-') if
830*d3c97224SAlexander Kolbasov	  !defined($load) || $load == HWLOAD_UNDEF;
831*d3c97224SAlexander Kolbasov	return (sprintf "$pcnt_fmt", '?') if $load == HWLOAD_UNKNOWN;
832*d3c97224SAlexander Kolbasov	return (sprintf "$pcnt%%", $load);
833*d3c97224SAlexander Kolbasov}
834*d3c97224SAlexander Kolbasov
835*d3c97224SAlexander Kolbasov#
836*d3c97224SAlexander Kolbasov# get_load(snapshot1, snapshot2, pg)
837*d3c97224SAlexander Kolbasov#
838*d3c97224SAlexander Kolbasov# Get various hardware load data for the given PG using two snapshots.
839*d3c97224SAlexander Kolbasov# Arguments: two PG snapshots and PG ID
840*d3c97224SAlexander Kolbasov#
841*d3c97224SAlexander Kolbasov# In scalar context returns the hardware load
842*d3c97224SAlexander Kolbasov# In list context returns a list
843*d3c97224SAlexander Kolbasov# (load, utilization, capacity, accuracy)
844*d3c97224SAlexander Kolbasov#
845*d3c97224SAlexander Kolbasovsub get_load
846*d3c97224SAlexander Kolbasov{
847*d3c97224SAlexander Kolbasov	my $p = shift;
848*d3c97224SAlexander Kolbasov	my $p_dup = shift;
849*d3c97224SAlexander Kolbasov	my $pg = shift;
850*d3c97224SAlexander Kolbasov
851*d3c97224SAlexander Kolbasov	return HWLOAD_UNDEF if !$p->has_utilization($pg);
852*d3c97224SAlexander Kolbasov
853*d3c97224SAlexander Kolbasov	my ($capacity, $utilization, $accuracy, $tdelta);
854*d3c97224SAlexander Kolbasov
855*d3c97224SAlexander Kolbasov
856*d3c97224SAlexander Kolbasov	$accuracy = 100;
857*d3c97224SAlexander Kolbasov	$utilization = 0;
858*d3c97224SAlexander Kolbasov
859*d3c97224SAlexander Kolbasov	$utilization = $p->utilization($p_dup, $pg) || 0;
860*d3c97224SAlexander Kolbasov	$capacity = $p_dup->capacity($pg);
861*d3c97224SAlexander Kolbasov	$accuracy = $p->accuracy($p_dup, $pg) || 0;
862*d3c97224SAlexander Kolbasov	$tdelta = $p->tdelta($p_dup, $pg);
863*d3c97224SAlexander Kolbasov	my $utilization_per_second = $utilization;
864*d3c97224SAlexander Kolbasov	$utilization_per_second /= $tdelta if $tdelta;
865*d3c97224SAlexander Kolbasov
866*d3c97224SAlexander Kolbasov	my $load;
867*d3c97224SAlexander Kolbasov
868*d3c97224SAlexander Kolbasov	if ($accuracy != 100) {
869*d3c97224SAlexander Kolbasov		$load = HWLOAD_UNKNOWN;
870*d3c97224SAlexander Kolbasov	} else {
871*d3c97224SAlexander Kolbasov		$load = $capacity ?
872*d3c97224SAlexander Kolbasov		  $utilization_per_second * 100 / $capacity :
873*d3c97224SAlexander Kolbasov		  HWLOAD_UNKNOWN;
874*d3c97224SAlexander Kolbasov		$capacity *= $tdelta if $tdelta;
875*d3c97224SAlexander Kolbasov	}
876*d3c97224SAlexander Kolbasov
877*d3c97224SAlexander Kolbasov	return (wantarray() ?
878*d3c97224SAlexander Kolbasov		($load, $utilization, $capacity, $accuracy) :
879*d3c97224SAlexander Kolbasov		$load);
880*d3c97224SAlexander Kolbasov}
881*d3c97224SAlexander Kolbasov
882*d3c97224SAlexander Kolbasov#
883*d3c97224SAlexander Kolbasov# Make sure that with the rounding used, user + system + swload add up to 100%.
884*d3c97224SAlexander Kolbasov#
885*d3c97224SAlexander Kolbasov#
886*d3c97224SAlexander Kolbasovsub get_swload
887*d3c97224SAlexander Kolbasov{
888*d3c97224SAlexander Kolbasov	my $user = shift;
889*d3c97224SAlexander Kolbasov	my $sys = shift;
890*d3c97224SAlexander Kolbasov	my $swload;
891*d3c97224SAlexander Kolbasov	my $idle;
892*d3c97224SAlexander Kolbasov
893*d3c97224SAlexander Kolbasov	$user = sprintf "$pcnt", $user;
894*d3c97224SAlexander Kolbasov	$sys  = sprintf  "$pcnt", $sys;
895*d3c97224SAlexander Kolbasov
896*d3c97224SAlexander Kolbasov	$swload = $user + $sys;
897*d3c97224SAlexander Kolbasov	$idle = 100 - $swload;
898*d3c97224SAlexander Kolbasov
899*d3c97224SAlexander Kolbasov	return ($swload, $idle);
900*d3c97224SAlexander Kolbasov}
901*d3c97224SAlexander Kolbasov
902*d3c97224SAlexander Kolbasov#
903*d3c97224SAlexander Kolbasov# get_pg_list(cookie, pg_list, sharing_filter, sharing_filter_neg) Get list OF
904*d3c97224SAlexander Kolbasov# PGs to look at based on all PGs available, user-specified PGs and
905*d3c97224SAlexander Kolbasov# user-specified filters.
906*d3c97224SAlexander Kolbasov#
907*d3c97224SAlexander Kolbasovsub get_pg_list
908*d3c97224SAlexander Kolbasov{
909*d3c97224SAlexander Kolbasov	my $p = shift;
910*d3c97224SAlexander Kolbasov	my $pg_list = shift;
911*d3c97224SAlexander Kolbasov	my $sharing_filter = shift;
912*d3c97224SAlexander Kolbasov	my $sharing_filter_neg = shift;
913*d3c97224SAlexander Kolbasov
914*d3c97224SAlexander Kolbasov	my @all = $p->all();
915*d3c97224SAlexander Kolbasov	my @pg_list = scalar @$pg_list ? @$pg_list : @all;
916*d3c97224SAlexander Kolbasov	my @pgs = $p->intersect(\@all_pgs, \@pg_list);
917*d3c97224SAlexander Kolbasov
918*d3c97224SAlexander Kolbasov	#
919*d3c97224SAlexander Kolbasov	# Now we have list of PGs to work with. Now apply filtering. First list
920*d3c97224SAlexander Kolbasov	# only those matching -R
921*d3c97224SAlexander Kolbasov	#
922*d3c97224SAlexander Kolbasov	@pgs = grep { list_match($p->sh_name($_), \@sharing_filter, 0) } @pgs if
923*d3c97224SAlexander Kolbasov	  @sharing_filter;
924*d3c97224SAlexander Kolbasov
925*d3c97224SAlexander Kolbasov	my @sharing_filter = @$sharing_filter;
926*d3c97224SAlexander Kolbasov	my @sharing_filter_neg = @$sharing_filter_neg;
927*d3c97224SAlexander Kolbasov	# Remove any that doesn't match -r
928*d3c97224SAlexander Kolbasov	@pgs = grep {
929*d3c97224SAlexander Kolbasov		!list_match($p->sh_name($_), \@sharing_filter_neg, 0)
930*d3c97224SAlexander Kolbasov	} @pgs if
931*d3c97224SAlexander Kolbasov	  scalar @sharing_filter_neg;
932*d3c97224SAlexander Kolbasov
933*d3c97224SAlexander Kolbasov	return (@pgs);
934*d3c97224SAlexander Kolbasov}
935*d3c97224SAlexander Kolbasov
936*d3c97224SAlexander Kolbasov#
937*d3c97224SAlexander Kolbasov# usage(rc)
938*d3c97224SAlexander Kolbasov#
939*d3c97224SAlexander Kolbasov# Print short usage message and exit with the given return code.
940*d3c97224SAlexander Kolbasov# If verbose is T, print a bit more information
941*d3c97224SAlexander Kolbasov#
942*d3c97224SAlexander Kolbasovsub usage
943*d3c97224SAlexander Kolbasov{
944*d3c97224SAlexander Kolbasov	my $rc = shift || E_SUCCESS;
945*d3c97224SAlexander Kolbasov
946*d3c97224SAlexander Kolbasov	printf STDERR
947*d3c97224SAlexander Kolbasov	  gettext("Usage:\t%s [-A] [-C] [-p] [-s key | -S key] " .
948*d3c97224SAlexander Kolbasov		  "[-t number] [-T u | d]\n"), $cmdname;
949*d3c97224SAlexander Kolbasov	print STDERR
950*d3c97224SAlexander Kolbasov	  gettext("\t\t[-r string] [-R string] [-P pg ...] [-c processor_id... ]\n");
951*d3c97224SAlexander Kolbasov	print STDERR
952*d3c97224SAlexander Kolbasov	  gettext("\t\t[interval [count]]\n\n");
953*d3c97224SAlexander Kolbasov
954*d3c97224SAlexander Kolbasov	exit ($rc);
955*d3c97224SAlexander Kolbasov}
956*d3c97224SAlexander Kolbasov
957*d3c97224SAlexander Kolbasov#
958*d3c97224SAlexander Kolbasov# list_match(val, list_ref, strict)
959*d3c97224SAlexander Kolbasov# Return T if argument matches any of the elements on the list, undef otherwise.
960*d3c97224SAlexander Kolbasov#
961*d3c97224SAlexander Kolbasovsub list_match
962*d3c97224SAlexander Kolbasov{
963*d3c97224SAlexander Kolbasov	my $arg = shift;
964*d3c97224SAlexander Kolbasov	my $list = shift;
965*d3c97224SAlexander Kolbasov	my $strict = shift;
966*d3c97224SAlexander Kolbasov
967*d3c97224SAlexander Kolbasov	return first { $arg eq $_ } @$list if $strict;
968*d3c97224SAlexander Kolbasov	return first { $arg =~ m/$_/i } @$list;
969*d3c97224SAlexander Kolbasov}
970*d3c97224SAlexander Kolbasov
971*d3c97224SAlexander Kolbasov#
972*d3c97224SAlexander Kolbasov# Convert a number to a string representation
973*d3c97224SAlexander Kolbasov# The number is scaled down until it is small enough to be in a good
974*d3c97224SAlexander Kolbasov# human readable format i.e. in the range 0 thru 1000.
975*d3c97224SAlexander Kolbasov# If it's smaller than 10 there's room enough to provide one decimal place.
976*d3c97224SAlexander Kolbasov#
977*d3c97224SAlexander Kolbasovsub number_to_scaled_string
978*d3c97224SAlexander Kolbasov{
979*d3c97224SAlexander Kolbasov	my $number = shift;
980*d3c97224SAlexander Kolbasov
981*d3c97224SAlexander Kolbasov	return '-' unless defined ($number);
982*d3c97224SAlexander Kolbasov
983*d3c97224SAlexander Kolbasov	# Remove any trailing spaces
984*d3c97224SAlexander Kolbasov	$number =~ s/ //g;
985*d3c97224SAlexander Kolbasov
986*d3c97224SAlexander Kolbasov	return $number unless $number =~ /^[.\d]+$/;
987*d3c97224SAlexander Kolbasov
988*d3c97224SAlexander Kolbasov	my $scale = 1000;
989*d3c97224SAlexander Kolbasov
990*d3c97224SAlexander Kolbasov	return sprintf("%4d", $number) if $number < $scale;
991*d3c97224SAlexander Kolbasov
992*d3c97224SAlexander Kolbasov	my @measurement = ('K', 'M', 'B', 'T');
993*d3c97224SAlexander Kolbasov	my $uom = shift(@measurement);
994*d3c97224SAlexander Kolbasov	my $result;
995*d3c97224SAlexander Kolbasov
996*d3c97224SAlexander Kolbasov	my $save = $number;
997*d3c97224SAlexander Kolbasov
998*d3c97224SAlexander Kolbasov	# Get size in K.
999*d3c97224SAlexander Kolbasov	$number /= $scale;
1000*d3c97224SAlexander Kolbasov
1001*d3c97224SAlexander Kolbasov	while (($number >= $scale) && $uom ne 'B') {
1002*d3c97224SAlexander Kolbasov		$uom = shift(@measurement);
1003*d3c97224SAlexander Kolbasov		$save = $number;
1004*d3c97224SAlexander Kolbasov		$number /= $scale;
1005*d3c97224SAlexander Kolbasov	}
1006*d3c97224SAlexander Kolbasov
1007*d3c97224SAlexander Kolbasov	# check if we should output a decimal place after the point
1008*d3c97224SAlexander Kolbasov	if ($save && (($save / $scale) < 10)) {
1009*d3c97224SAlexander Kolbasov		$result = sprintf("%3.1f$uom", $save / $scale);
1010*d3c97224SAlexander Kolbasov	} else {
1011*d3c97224SAlexander Kolbasov		$result = sprintf("%3d$uom", $number);
1012*d3c97224SAlexander Kolbasov	}
1013*d3c97224SAlexander Kolbasov
1014*d3c97224SAlexander Kolbasov	return ("$result");
1015*d3c97224SAlexander Kolbasov}
1016*d3c97224SAlexander Kolbasov
1017*d3c97224SAlexander Kolbasov
1018*d3c97224SAlexander Kolbasov__END__
1019