xref: /illumos-gate/usr/src/cmd/intrd/intrd.pl (revision bd335c64)
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, Version 1.0 only
7# (the "License").  You may not use this file except in compliance
8# with the License.
9#
10# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11# or http://www.opensolaris.org/os/licensing.
12# See the License for the specific language governing permissions
13# and limitations under the License.
14#
15# When distributing Covered Code, include this CDDL HEADER in each
16# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17# If applicable, add the following below this CDDL HEADER, with the
18# fields enclosed by brackets "[]" replaced with your own identifying
19# information: Portions Copyright [yyyy] [name of copyright owner]
20#
21# CDDL HEADER END
22#
23
24#
25# Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
26# Use is subject to license terms.
27#
28#ident	"%Z%%M%	%I%	%E% SMI"
29#
30
31require 5.6.1;
32use strict;
33use warnings;
34use POSIX;
35use File::Basename("basename");
36
37my $cmdname = basename($0);
38
39my $using_scengen = 0;	# 1 if using scenario simulator
40my $debug = 0;
41
42my $min_sleeptime = 1;
43my $max_sleeptime = 15;
44my $onecpu_sleeptime = (60 * 15);	# used if only 1 CPU on system
45my $sleeptime = $min_sleeptime;	# time to sleep between kstat updates
46
47# For timerange_foo variables, see comments at tail of &getstat()
48
49my $timerange_toohi    = .01;
50my $timerange_hithresh = .0003;
51my $timerange_lothresh = $timerange_hithresh / 2;
52my $unsafe_timerange   = .02;
53
54my $statslen = 60;	# time period (in secs) to keep in @deltas
55
56
57# Parse arguments. intrd does not accept any public arguments; the two
58# arguments below are meant for testing purposes. -D generates a significant
59# amount of syslog output. -S <filename> loads the filename as a perl
60# script. That file is expected to implement a kstat "simulator" which
61# can be used to feed information to intrd and verify intrd's responses.
62
63while ($_ = shift @ARGV) {
64	if ($_ eq "-S" && $#ARGV != -1) {
65		$using_scengen = 1;
66		do $ARGV[0];	# load simulator
67		shift @ARGV;
68	} elsif ($_ eq "-D") {
69		$debug = 1;
70	}
71}
72
73if ($using_scengen == 0) {
74	require Sun::Solaris::Kstat;
75	require Sun::Solaris::Intrs;
76	import Sun::Solaris::Intrs(qw(intrmove));
77	require Sys::Syslog;
78	import Sys::Syslog;
79	openlog($cmdname, 'pid', 'daemon');
80	setlogmask(Sys::Syslog::LOG_UPTO($debug > 0 ? &Sys::Syslog::LOG_DEBUG :
81	    &Sys::Syslog::LOG_INFO));
82}
83
84
85my $asserted = 0;
86my $assert_level = 'debug';	# syslog level for assertion failures
87sub VERIFY($@)
88{
89	my $bad = (shift() == 0);	# $_[0] == 0 means assert failed
90	if ($bad) {
91		my $msg = shift();
92		syslog($assert_level, "VERIFY: $msg", @_);
93		$asserted++;
94	}
95	return ($bad);
96}
97
98
99
100
101sub getstat($);
102sub generate_delta($$);
103sub compress_deltas($);
104sub dumpdelta($);
105
106sub goodness($);
107sub imbalanced($$);
108sub do_reconfig($);
109
110sub goodness_cpu($$);		# private function
111sub move_intr($$$$);		# private function
112sub ivecs_to_string(@);		# private function
113sub do_find_goal($$$$);		# private function
114sub find_goal($$);		# private function
115sub do_reconfig_cpu2cpu($$$$);	# private function
116sub do_reconfig_cpu($$$);	# private function
117
118
119#
120# What follow are the basic data structures routines of intrd.
121#
122# getstat() is responsible for reading the kstats and generating a "stat" hash.
123#
124# generate_delta() is responsible for taking two "stat" hashes and creating
125# a new "delta" hash that represents what has changed over time.
126#
127# compress_deltas() is responsible for taking a list of deltas and generating
128# a single delta hash that encompasses all the time periods described by the
129# deltas.
130
131
132#
133# getstat() is handed a reference to a kstat and generates a hash, returned
134# by reference, containing all the fields from the kstats which we need.
135# If it returns the scalar 0, it failed to gather the kstats, and the caller
136# should react accordingly.
137#
138# getstat() is also responsible for maintaining a reasonable $sleeptime.
139#
140# {"snaptime"}          kstat's snaptime
141# {<cpuid>}             one hash reference per online cpu
142#  ->{"tot"}            == cpu:<cpuid>:sys:cpu_nsec_{user + kernel + idle}
143#  ->{"crtime"}         == cpu:<cpuid>:sys:crtime
144#  ->{"ivecs"}
145#     ->{<cookie#>}     iterates over pci_intrs::config:cookie
146#        ->{"time"}     == pci_intrs:<ivec#>:config:time (in nsec)
147#        ->{"pil"}      == pci_intrs:<ivec#>:config:pil
148#        ->{"crtime"}   == pci_intrs:<ivec#>:config:crtime
149#        ->{"ino"}      == pci_intrs:<ivec#>:config:ino
150#        ->{"buspath"}  == pci_intrs:<ivec#>:config:buspath
151#        ->{"name"}     == pci_intrs:<ivec#>:config:name
152#        ->{"ihs"}      == pci_intrs:<ivec#>:config:ihs
153#
154
155sub getstat($)
156{
157	my ($ks) = @_;
158
159	my $cpucnt = 0;
160	my %stat = ();
161	my ($minsnap, $maxsnap);
162
163	# kstats are not generated atomically. Each kstat hierarchy will
164	# have been generated within the kernel at a different time. On a
165	# thrashing system, we may not run quickly enough in order to get
166	# coherent kstat timing information across all the kstats. To
167	# determine if this is occurring, $minsnap/$maxsnap are used to
168	# find the breadth between the first and last snaptime of all the
169	# kstats we access. $maxsnap - $minsnap roughly represents the
170	# total time taken up in getstat(). If this time approaches the
171	# time between snapshots, our results may not be useful.
172
173	$minsnap = -1;		# snaptime is always a positive number
174	$maxsnap = $minsnap;
175
176	# Iterate over the cpus in cpu:<cpuid>::. Check
177	# cpu_info:<cpuid>:cpu_info<cpuid>:state to make sure the
178	# processor is "on-line". If not, it isn't accepting interrupts
179	# and doesn't concern us.
180	#
181	# Record cpu:<cpuid>:sys:snaptime, and check $minsnap/$maxsnap.
182
183	while (my ($cpu, $cpst) = each %{$ks->{cpu}}) {
184		next if !exists($ks->{cpu_info}{$cpu}{"cpu_info$cpu"}{state});
185		my $state = $ks->{cpu_info}{$cpu}{"cpu_info$cpu"}{state};
186		next if ($state !~ /^on-line\0/);
187		my $cpu_sys = $cpst->{sys};
188
189		$stat{$cpu}{tot} = ($cpu_sys->{cpu_nsec_idle} +
190				    $cpu_sys->{cpu_nsec_user} +
191				    $cpu_sys->{cpu_nsec_kernel});
192		$stat{$cpu}{crtime} = $cpu_sys->{crtime};
193		$stat{$cpu}{ivecs} = {};
194
195		if ($minsnap == -1 || $cpu_sys->{snaptime} < $minsnap) {
196			$minsnap = $cpu_sys->{snaptime};
197		}
198		if ($cpu_sys->{snaptime} > $maxsnap) {
199			$maxsnap = $cpu_sys->{snaptime};
200		}
201		$cpucnt++;
202	}
203
204	if ($cpucnt <= 1) {
205		$sleeptime = $onecpu_sleeptime;
206		return (0);	# nothing to do with 1 CPU
207	}
208
209	# Iterate over the ivecs. If the cpu is not on-line, ignore the
210	# ivecs mapped to it, if any.
211	#
212	# Record pci_intrs:{inum}:config:time, snaptime, crtime, pil,
213	# ino, name, and buspath. Check $minsnap/$maxsnap.
214
215	foreach my $inst (values(%{$ks->{pci_intrs}})) {
216		my $intrcfg = $inst->{config};
217		my $cpu = $intrcfg->{cpu};
218
219		next unless exists $stat{$cpu};
220
221		if ($intrcfg->{snaptime} < $minsnap) {
222			$minsnap = $intrcfg->{snaptime};
223		} elsif ($intrcfg->{snaptime} > $maxsnap) {
224			$maxsnap = $intrcfg->{snaptime};
225		}
226
227		my $cookie = "$intrcfg->{buspath} $intrcfg->{ino}";
228		if (exists $stat{$cpu}{ivecs}{$cookie}) {
229			my $cookiestats = $stat{$cpu}{ivecs}{$cookie};
230
231			$cookiestats->{time} += $intrcfg->{time};
232			$cookiestats->{name} .= "/$intrcfg->{name}";
233
234			# If this new interrupt sharing $cookie represents a
235			# change from an earlier getstat, make sure that
236			# generate_delta will see the change by setting
237			# crtime to the most recent crtime of its components.
238
239			if ($intrcfg->{crtime} > $cookiestats->{crtime}) {
240				$cookiestats->{crtime} = $intrcfg->{crtime};
241			}
242			$cookiestats->{ihs}++;
243			next;
244		}
245		$stat{$cpu}{ivecs}{$cookie}{time} = $intrcfg->{time};
246		$stat{$cpu}{ivecs}{$cookie}{crtime} = $intrcfg->{crtime};
247		$stat{$cpu}{ivecs}{$cookie}{pil} = $intrcfg->{pil};
248		$stat{$cpu}{ivecs}{$cookie}{ino} = $intrcfg->{ino};
249		$stat{$cpu}{ivecs}{$cookie}{buspath} = $intrcfg->{buspath};
250		$stat{$cpu}{ivecs}{$cookie}{name} = $intrcfg->{name};
251		$stat{$cpu}{ivecs}{$cookie}{ihs} = 1;
252	}
253
254	# We define the timerange as the amount of time spent gathering the
255	# various kstats, divided by our sleeptime. If we take a lot of time
256	# to access the kstats, and then we create a delta comparing these
257	# kstats with a prior set of kstats, that delta will cover
258	# substaintially different amount of time depending upon which
259	# interrupt or CPU is being examined.
260	#
261	# By checking the timerange here, we guarantee that any deltas
262	# created from these kstats will contain self-consistent data,
263	# in that all CPUs and interrupts cover a similar span of time.
264	#
265	# We attempt to keep this timerange between $timerange_lothresh and
266	# $timerange_hithresh. If the timerange gets too large, not only are
267	# there the accuracy concerns above, but it means that intrd is using
268	# a lot of CPU time. If the timerange gets too small, that means our
269	# sleep time is large, and we could fail to react quickly enough to a
270	# sudden change.
271	#
272	# Finally, $timerange_toohi is the upper bound. Any timerange above
273	# this is thrown out as garbage. If the stat is safely within this
274	# bound, we treat the stat as representing an instant in time, rather
275	# than the time range it actually spans. We arbitrarily choose minsnap
276	# as the snaptime of the stat.
277
278	$stat{snaptime} = $minsnap;
279	my $timerange = ($maxsnap - $minsnap) / $sleeptime;
280	if ($sleeptime == $onecpu_sleeptime) {
281		$sleeptime = $min_sleeptime; # time to come out of idling
282	} elsif ($timerange > $timerange_hithresh &&
283	    $sleeptime < $max_sleeptime) {
284		$sleeptime++;
285	} elsif ($timerange < $timerange_lothresh &&
286	    $sleeptime > $min_sleeptime) {
287		$sleeptime--;
288	}
289	return (0) if ($timerange > $timerange_toohi);	# i.e. failure
290	return (\%stat);
291}
292
293#
294# dumpdelta takes a reference to our "delta" structure:
295# {"missing"}           "1" if the delta's component stats had inconsistencies
296# {"minsnap"}           time of the first kstat snaptime used in this delta
297# {"maxsnap"}           time of the last kstat snaptime used in this delta
298# {"goodness"}          cost function applied to this delta
299# {"avgintrload"}       avg of interrupt load across cpus, as a percentage
300# {"avgintrnsec"}       avg number of nsec spent in interrupts, per cpu
301# {<cpuid>}             iterates over on-line cpus
302#  ->{"intrs"}          cpu's movable intr time (sum of "time" for each ivec)
303#  ->{"tot"}            CPU load from all sources
304#  ->{"bigintr"}        largest value of {ivecs}{<ivec#>}{time} from below
305#  ->{"intrload"}       intrs / tot
306#  ->{"ivecs"}
307#     ->{<ivec#>}       iterates over ivecs for this cpu
308#        ->{"time"}     time used by this interrupt (in nsec)
309#        ->{"pil"}      pil level of this interrupt
310#        ->{"ino"}      interrupt number
311#        ->{"buspath"}  filename of the directory of the device's bus
312#        ->{"name"}     device name
313#        ->{"ihs"}      number of different handlers sharing this ino
314#
315# It prints out the delta structure in a nice, human readable display.
316#
317
318sub dumpdelta($)
319{
320	my ($delta) = @_;
321
322	# print global info
323
324	syslog('debug', "dumpdelta:");
325	syslog('debug', " RECONFIGURATION IN DELTA") if $delta->{missing} > 0;
326	syslog('debug', " avgintrload: %5.2f%%  avgintrnsec: %d",
327	       $delta->{avgintrload} * 100, $delta->{avgintrnsec});
328	syslog('debug', "    goodness: %5.2f%%", $delta->{goodness} * 100)
329	    if exists($delta->{goodness});
330
331	# iterate over cpus
332
333	while (my ($cpu, $cpst) = each %$delta) {
334		next if !ref($cpst);		# skip non-cpuid entries
335		my $tot = $cpst->{tot};
336		syslog('debug', "    cpu %3d intr %7.3f%%  (bigintr %7.3f%%)",
337		       $cpu, $cpst->{intrload}*100, $cpst->{bigintr}*100/$tot);
338		syslog('debug', "        intrs %d, bigintr %d",
339		       $cpst->{intrs}, $cpst->{bigintr});
340
341		# iterate over ivecs on this cpu
342
343		while (my ($ivec, $ivst) = each %{$cpst->{ivecs}}) {
344			syslog('debug', "    %15s:%5d: %7.3f%%  %d",
345			       ($ivst->{ihs} > 1 ?
346				"$ivst->{name}($ivst->{ihs})" :
347				$ivst->{name}),
348			       $ivec, $ivst->{time}*100 / $tot, $ivst->{time});
349		}
350	}
351}
352
353#
354# generate_delta($stat, $newstat) takes two stat references, returned from
355# getstat(), and creates a %delta. %delta (not surprisingly) contains the
356# same basic info as stat and newstat, but with the timestamps as deltas
357# instead of absolute times. We return a reference to the delta.
358#
359
360sub generate_delta($$)
361{
362	my ($stat, $newstat) = @_;
363
364	my %delta = ();
365	my $intrload;
366	my $intrnsec;
367	my $cpus;
368
369	# Take the worstcase timerange
370	$delta{minsnap} = $stat->{snaptime};
371	$delta{maxsnap} = $newstat->{snaptime};
372	if (VERIFY($delta{maxsnap} > $delta{minsnap},
373	    "generate_delta: stats aren't ascending")) {
374		$delta{missing} = 1;
375		return (\%delta);
376	}
377
378	# if there are a different number of cpus in the stats, set missing
379
380	$delta{missing} = (keys(%$stat) != keys(%$newstat));
381	if (VERIFY($delta{missing} == 0,
382	    "generate_delta: number of CPUs changed")) {
383		return (\%delta);
384	}
385
386	# scan through every cpu in %newstat and compare against %stat
387
388	while (my ($cpu, $newcpst) = each %$newstat) {
389		next if !ref($newcpst);		# skip non-cpuid fields
390
391		# If %stat is missing a cpu from %newstat, then it was just
392		# onlined. Mark missing.
393
394		if (VERIFY(exists $stat->{$cpu} &&
395		    $stat->{$cpu}{crtime} == $newcpst->{crtime},
396		    "generate_delta: cpu $cpu changed")) {
397			$delta{missing} = 1;
398			return (\%delta);
399		}
400		my $cpst = $stat->{$cpu};
401		$delta{$cpu}{tot} = $newcpst->{tot} - $cpst->{tot};
402		if (VERIFY($delta{$cpu}{tot} >= 0,
403		    "generate_delta: deltas are not ascending?")) {
404			$delta{missing} = 1;
405			delete($delta{$cpu});
406			return (\%delta);
407		}
408		# Avoid remote chance of division by zero
409		$delta{$cpu}{tot} = 1 if $delta{$cpu}{tot} == 0;
410		$delta{$cpu}{intrs} = 0;
411		$delta{$cpu}{bigintr} = 0;
412
413		my %ivecs = ();
414		$delta{$cpu}{ivecs} = \%ivecs;
415
416		# if the number of ivecs differs, set missing
417
418		if (VERIFY(keys(%{$cpst->{ivecs}}) ==
419			   keys(%{$newcpst->{ivecs}}),
420			   "generate_delta: cpu $cpu has more/less".
421			   " interrupts")) {
422			$delta{missing} = 1;
423			return (\%delta);
424		}
425
426		while (my ($inum, $newivec) = each %{$newcpst->{ivecs}}) {
427			# If this ivec doesn't exist in $stat, or if $stat
428			# shows a different crtime, set missing.
429
430			if (VERIFY(exists $cpst->{ivecs}{$inum} &&
431				   $cpst->{ivecs}{$inum}{crtime} ==
432				   $newivec->{crtime},
433				   "generate_delta: cpu $cpu inum $inum".
434				   " has changed")) {
435				$delta{missing} = 1;
436				return (\%delta);
437			}
438			my $ivec = $cpst->{ivecs}{$inum};
439
440			# Create $delta{$cpu}{ivecs}{$inum}.
441
442			my %dltivec = ();
443			$delta{$cpu}{ivecs}{$inum} = \%dltivec;
444
445			# calculate time used by this interrupt
446
447			my $time = $newivec->{time} - $ivec->{time};
448			if (VERIFY($time >= 0,
449				   "generate_delta: ivec went backwards?")) {
450				$delta{missing} = 1;
451				delete($delta{$cpu}{ivecs}{$inum});
452				return (\%delta);
453			}
454			$delta{$cpu}{intrs} += $time;
455			$dltivec{time} = $time;
456			if ($time > $delta{$cpu}{bigintr}) {
457				$delta{$cpu}{bigintr} = $time;
458			}
459
460			# Transfer over basic info about the kstat. We
461			# don't have to worry about discrepancies between
462			# ivec and newivec because we verified that both
463			# have the same crtime.
464
465			$dltivec{pil} = $newivec->{pil};
466			$dltivec{ino} = $newivec->{ino};
467			$dltivec{buspath} = $newivec->{buspath};
468			$dltivec{name} = $newivec->{name};
469			$dltivec{ihs} = $newivec->{ihs};
470		}
471		if ($delta{$cpu}{tot} < $delta{$cpu}{intrs}) {
472			# Ewww! Hopefully just a rounding error.
473			# Make something up.
474			$delta{$cpu}{tot} = $delta{$cpu}{intrs};
475		}
476		$delta{$cpu}{intrload} =
477		       $delta{$cpu}{intrs} / $delta{$cpu}{tot};
478		$intrload += $delta{$cpu}{intrload};
479		$intrnsec += $delta{$cpu}{intrs};
480		$cpus++;
481	}
482	if ($cpus > 0) {
483		$delta{avgintrload} = $intrload / $cpus;
484		$delta{avgintrnsec} = $intrnsec / $cpus;
485	} else {
486		$delta{avgintrload} = 0;
487		$delta{avgintrnsec} = 0;
488	}
489	return (\%delta);
490}
491
492
493# compress_delta takes a list of deltas, and returns a single new delta
494# which represents the combined information from all the deltas. The deltas
495# provided are assumed to be sequential in time. The resulting compressed
496# delta looks just like any other delta. This new delta is also more accurate
497# since its statistics are averaged over a longer period than any of the
498# original deltas.
499
500sub compress_deltas ($)
501{
502	my ($deltas) = @_;
503
504	my %newdelta = ();
505	my ($intrs, $tot);
506	my $cpus = 0;
507
508	if (VERIFY($#$deltas != -1,
509		   "compress_deltas: list of delta is empty?")) {
510		return (0);
511	}
512	$newdelta{minsnap} = $deltas->[0]{minsnap};
513	$newdelta{maxsnap} = $deltas->[$#$deltas]{maxsnap};
514	$newdelta{missing} = 0;
515
516	foreach my $delta (@$deltas) {
517		if (VERIFY($delta->{missing} == 0,
518		    "compressing bad deltas?")) {
519			return (0);
520		}
521		while (my ($cpuid, $cpu) = each %$delta) {
522			next if !ref($cpu);
523
524			$intrs += $cpu->{intrs};
525			$tot += $cpu->{tot};
526			$newdelta{$cpuid}{intrs} += $cpu->{intrs};
527			$newdelta{$cpuid}{tot} += $cpu->{tot};
528			if (!exists $newdelta{$cpuid}{ivecs}) {
529				my %ivecs = ();
530				$newdelta{$cpuid}{ivecs} = \%ivecs;
531			}
532			while (my ($inum, $ivec) = each %{$cpu->{ivecs}}) {
533				my $newivecs = $newdelta{$cpuid}{ivecs};
534				$newivecs->{$inum}{time} += $ivec->{time};
535				$newivecs->{$inum}{pil} = $ivec->{pil};
536				$newivecs->{$inum}{ino} = $ivec->{ino};
537				$newivecs->{$inum}{buspath} = $ivec->{buspath};
538				$newivecs->{$inum}{name} = $ivec->{name};
539				$newivecs->{$inum}{ihs} = $ivec->{ihs};
540			}
541		}
542	}
543	foreach my $cpu (values(%newdelta)) {
544		next if !ref($cpu); # ignore non-cpu fields
545		$cpus++;
546
547		my $bigintr = 0;
548		foreach my $ivec (values(%{$cpu->{ivecs}})) {
549			if ($ivec->{time} > $bigintr) {
550				$bigintr = $ivec->{time};
551			}
552		}
553		$cpu->{bigintr} = $bigintr;
554		$cpu->{intrload} = $cpu->{intrs} / $cpu->{tot};
555		$cpu->{tot} = 1 if $cpu->{tot} <= 0;
556	}
557	if ($cpus == 0) {
558		$newdelta{avgintrnsec} = 0;
559		$newdelta{avgintrload} = 0;
560	} else {
561		$newdelta{avgintrnsec} = $intrs / $cpus;
562		$newdelta{avgintrload} = $intrs / $tot;
563	}
564	return (\%newdelta);
565}
566
567
568
569
570
571# What follow are the core functions responsible for examining the deltas
572# generated above and deciding what to do about them.
573#
574# goodness() and its helper goodness_cpu() return a heuristic which describe
575# how good (or bad) the current interrupt balance is. The value returned will
576# be between 0 and 1, with 0 representing maximum goodness, and 1 representing
577# maximum badness.
578#
579# imbalanced() compares a current and historical value of goodness, and
580# determines if there has been enough change to warrant evaluating a
581# reconfiguration of the interrupts
582#
583# do_reconfig(), and its helpers, do_reconfig_cpu(), do_reconfig_cpu2cpu(),
584# find_goal(), do_find_goal(), and move_intr(), are responsible for examining
585# a delta and determining the best possible assignment of interrupts to CPUs.
586#
587# It is important that do_reconfig() be in alignment with goodness(). If
588# do_reconfig were to generate a new interrupt distribution that worsened
589# goodness, we could get into a pathological loop with intrd fighting itself,
590# constantly deciding that things are imbalanced, and then changing things
591# only to make them worse.
592
593
594
595# any goodness over $goodness_unsafe_load is considered really bad
596# goodness must drop by at least $goodness_mindelta for a reconfig
597
598my $goodness_unsafe_load = .9;
599my $goodness_mindelta = .1;
600
601# goodness(%delta) examines a delta and return its "goodness". goodness will
602# be between 0 (best) and 1 (major bad). goodness is determined by evaluating
603# the goodness of each individual cpu, and returning the worst case. This
604# helps on systems with many CPUs, where otherwise a single pathological CPU
605# might otherwise be ignored because the average was OK.
606#
607# To calculate the goodness of an individual CPU, we start by looking at its
608# load due to interrupts. If the load is above a certain high threshold and
609# there is more than one interrupt assigned to this CPU, we set goodness
610# to worst-case. If the load is below the average interrupt load of all CPUs,
611# then we return best-case, since what's to complain about?
612#
613# Otherwise we look at how much the load is above the average, and return
614# that as the goodness, with one caveat: we never return more than the CPU's
615# interrupt load ignoring its largest single interrupt source. This is
616# because a CPU with one high-load interrupt, and no other interrupts, is
617# perfectly balanced. Nothing can be done to improve the situation, and thus
618# it is perfectly balanced even if the interrupt's load is 100%.
619
620sub goodness($)
621{
622	my ($delta) = @_;
623
624	return (1) if $delta->{missing} > 0;
625
626	my $high_goodness = 0;
627	my $goodness;
628
629	foreach my $cpu (values(%$delta)) {
630		next if !ref($cpu);		# skip non-cpuid fields
631
632		$goodness = goodness_cpu($cpu, $delta->{avgintrload});
633		if (VERIFY($goodness >= 0 && $goodness <= 1,
634			   "goodness: cpu goodness out of range?")) {
635			dumpdelta($delta);
636			return (1);
637		}
638		if ($goodness == 1) {
639			return (1);	# worst case, no need to continue
640		}
641		if ($goodness > $high_goodness) {
642			$high_goodness = $goodness;
643		}
644	}
645	return ($high_goodness);
646}
647
648sub goodness_cpu($$)		# private function
649{
650	my ($cpu, $avgintrload) = @_;
651
652	my $goodness;
653	my $load = $cpu->{intrs} / $cpu->{tot};
654
655	return (0) if ($load < $avgintrload);	# low loads are perfectly good
656
657	# Calculate $load_no_bigintr, which represents the load
658	# due to interrupts, excluding the one biggest interrupt.
659	# This is the most gain we can get on this CPU from
660	# offloading interrupts.
661
662	my $load_no_bigintr = ($cpu->{intrs} - $cpu->{bigintr}) / $cpu->{tot};
663
664	# A major imbalance is indicated if a CPU is saturated
665	# with interrupt handling, and it has more than one
666	# source of interrupts. Those other interrupts could be
667	# starved if of a lower pil. Return a goodness of 1,
668	# which is the worst possible return value,
669	# which will effectively contaminate this entire delta.
670
671	my $cnt = keys(%{$cpu->{ivecs}});
672
673	if ($load > $goodness_unsafe_load && $cnt > 1) {
674		return (1);
675	}
676	$goodness = $load - $avgintrload;
677	if ($goodness > $load_no_bigintr) {
678		$goodness = $load_no_bigintr;
679	}
680	return ($goodness);
681}
682
683
684# imbalanced() is used by the main routine to determine if the goodness
685# has shifted far enough from our last baseline to warrant a reassignment
686# of interrupts. A very high goodness indicates that a CPU is way out of
687# whack. If the goodness has varied too much since the baseline, then
688# perhaps a reconfiguration is worth considering.
689
690sub imbalanced ($$)
691{
692	my ($goodness, $baseline) = @_;
693
694	# Return 1 if we are pathological, or creeping away from the baseline
695
696	return (1) if $goodness > .50;
697	return (1) if abs($goodness - $baseline) > $goodness_mindelta;
698	return (0);
699}
700
701# do_reconfig(), do_reconfig_cpu(), and do_reconfig_cpu2cpu(), are the
702# decision-making functions responsible for generating a new interrupt
703# distribution. They are designed with the definition of goodness() in
704# mind, i.e. they use the same definition of "good distribution" as does
705# goodness().
706#
707# do_reconfig() is responsible for deciding whether a redistribution is
708# actually warranted. If the goodness is already pretty good, it doesn't
709# waste the CPU time to generate a new distribution. If it
710# calculates a new distribution and finds that it is not sufficiently
711# improved from the prior distirbution, it will not do the redistribution,
712# mainly to avoid the disruption to system performance caused by
713# rejuggling interrupts.
714#
715# Its main loop works by going through a list of cpus sorted from
716# highest to lowest interrupt load. It removes the highest-load cpus
717# one at a time and hands them off to do_reconfig_cpu(). This function
718# then re-sorts the remaining CPUs from lowest to highest interrupt load,
719# and one at a time attempts to rejuggle interrupts between the original
720# high-load CPU and the low-load CPU. Rejuggling on a high-load CPU is
721# considered finished as soon as its interrupt load is within
722# $goodness_mindelta of the average interrupt load. Such a CPU will have
723# a goodness of below the $goodness_mindelta threshold.
724
725#
726# move_intr(\%delta, $inum, $oldcpu, $newcpu)
727# used by reconfiguration code to move an interrupt between cpus within
728# a delta. This manipulates data structures, and does not actually move
729# the interrupt on the running system.
730#
731sub move_intr($$$$)		# private function
732{
733	my ($delta, $inum, $oldcpuid, $newcpuid) = @_;
734
735	my $ivec = $delta->{$oldcpuid}{ivecs}{$inum};
736
737	# Remove ivec from old cpu
738
739	my $oldcpu = $delta->{$oldcpuid};
740	$oldcpu->{intrs} -= $ivec->{time};
741	$oldcpu->{intrload} = $oldcpu->{intrs} / $oldcpu->{tot};
742	delete($oldcpu->{ivecs}{$inum});
743
744	VERIFY($oldcpu->{intrs} >= 0, "move_intr: intr's time > total time?");
745	VERIFY($ivec->{time} <= $oldcpu->{bigintr},
746	       "move_intr: intr's time > bigintr?");
747
748	if ($ivec->{time} >= $oldcpu->{bigintr}) {
749		my $bigtime = 0;
750
751		foreach my $ivec (values(%{$oldcpu->{ivecs}})) {
752			$bigtime = $ivec->{time} if $ivec->{time} > $bigtime;
753		}
754		$oldcpu->{bigintr} = $bigtime;
755	}
756
757	# Add ivec onto new cpu
758
759	my $newcpu = $delta->{$newcpuid};
760
761	$ivec->{nowcpu} = $newcpuid;
762	$newcpu->{intrs} += $ivec->{time};
763	$newcpu->{intrload} = $newcpu->{intrs} / $newcpu->{tot};
764	$newcpu->{ivecs}{$inum} = $ivec;
765
766	$newcpu->{bigintr} = $ivec->{time}
767		if $ivec->{time} > $newcpu->{bigintr};
768}
769
770sub move_intr_check($$$)	# private function
771{
772	my ($delta, $oldcpuid, $newcpuid) = @_;
773
774	VERIFY($delta->{$oldcpuid}{tot} >= $delta->{$oldcpuid}{intrs},
775	       "Moved interrupts left 100+%% load on src cpu");
776	VERIFY($delta->{$newcpuid}{tot} >= $delta->{$newcpuid}{intrs},
777	       "Moved interrupts left 100+%% load on tgt cpu");
778}
779
780sub ivecs_to_string(@)		# private function
781{
782	my $str = "";
783	foreach my $ivec (@_) {
784		$str = "$str $ivec->{inum}";
785	}
786	return ($str);
787}
788
789
790sub do_reconfig($)
791{
792	my ($delta) = @_;
793
794	my $goodness = $delta->{goodness};
795
796	# We can't improve goodness to better than 0. We should stop here
797	# if, even if we achieve a goodness of 0, the improvement is still
798	# too small to merit the action.
799
800	if ($goodness - 0 < $goodness_mindelta) {
801		syslog('debug', "goodness good enough, don't reconfig");
802		return (0);
803	}
804
805	syslog('notice', "Optimizing interrupt assignments");
806
807	if (VERIFY ($delta->{missing} == 0, "RECONFIG Aborted: should not ".
808	    "have a delta with missing")) {
809		return (-1);
810	}
811
812	# Make a list of all cpuids, and also add some extra information
813	# to the ivec structures.
814
815	my @cpusortlist = ();
816
817	while (my ($cpuid, $cpu) = each %$delta) {
818		next if !ref($cpu);	# skip non-cpu entries
819
820		push(@cpusortlist, $cpuid);
821		while (my ($inum, $ivec) = each %{$cpu->{ivecs}}) {
822			$ivec->{origcpu} = $cpuid;
823			$ivec->{nowcpu} = $cpuid;
824			$ivec->{inum} = $inum;
825		}
826	}
827
828	# Sort the list of CPUs from highest to lowest interrupt load.
829	# Remove the top CPU from that list and attempt to redistribute
830	# its interrupts. If the CPU has a goodness below a threshold,
831	# just ignore the CPU and move to the next one. If the CPU's
832	# load falls below the average load plus that same threshold,
833	# then there are no CPUs left worth reconfiguring, and we're done.
834
835	while (@cpusortlist) {
836		# Re-sort cpusortlist each time, since do_reconfig_cpu can
837		# move interrupts around.
838
839		@cpusortlist =
840		    sort({$delta->{$b}{intrload} <=> $delta->{$a}{intrload}}
841		    @cpusortlist);
842
843		my $cpu = shift(@cpusortlist);
844		if (($delta->{$cpu}{intrload} <= $goodness_unsafe_load) &&
845		    ($delta->{$cpu}{intrload} <=
846		    $delta->{avgintrload} + $goodness_mindelta)) {
847			syslog('debug', "finished reconfig: cpu $cpu load ".
848			    "$delta->{$cpu}{intrload} avgload ".
849			    "$delta->{avgintrload}");
850			last;
851		}
852		if (goodness_cpu($delta->{$cpu}, $delta->{avgintrload}) <
853		    $goodness_mindelta) {
854			next;
855		}
856		do_reconfig_cpu($delta, \@cpusortlist, $cpu);
857	}
858
859	# How good a job did we do? If the improvement was minimal, and
860	# our goodness wasn't pathological (and thus needing any help it
861	# can get), then don't bother moving the interrupts.
862
863	my $newgoodness = goodness($delta);
864	VERIFY($newgoodness <= $goodness,
865	       "reconfig: result has worse goodness?");
866
867	if (($goodness != 1 || $newgoodness == 1) &&
868	    $goodness - $newgoodness < $goodness_mindelta) {
869		syslog('debug', "goodness already near optimum, ".
870		       "don't reconfig");
871		return (0);
872	}
873	syslog('debug', "goodness %5.2f%% --> %5.2f%%", $goodness*100,
874	       $newgoodness*100);
875
876	# Time to move those interrupts!
877
878	my $ret = 1;
879	my $warned = 0;
880	while (my ($cpuid, $cpu) = each %$delta) {
881		next if $cpuid =~ /\D/;
882		while (my ($inum, $ivec) = each %{$cpu->{ivecs}}) {
883			next if ($ivec->{origcpu} == $cpuid);
884
885			if (!intrmove($ivec->{buspath}, $ivec->{ino},
886			    $cpuid)) {
887				syslog('warning', "Unable to move interrupts")
888				    if $warned++ == 0;
889				syslog('debug', "Unable to move buspath ".
890				    "$ivec->{buspath} ino $ivec->{ino} to ".
891				    "cpu $cpuid");
892				$ret = -1;
893			}
894		}
895	}
896
897	syslog('notice', "Interrupt assignments optimized");
898	return ($ret);
899}
900
901sub do_reconfig_cpu($$$)	# private function
902{
903	my ($delta, $cpusortlist, $oldcpuid) = @_;
904
905	# We have been asked to rejuggle interrupts between $oldcpuid and
906	# other CPUs found on $cpusortlist so as to improve the load on
907	# $oldcpuid. We reverse $cpusortlist to get our own copy of the
908	# list, sorted from lowest to highest interrupt load. One at a
909	# time, shift a CPU off of this list of CPUs, and attempt to
910	# rejuggle interrupts between the two CPUs. Don't do this if the
911	# other CPU has a higher load than oldcpuid. We're done rejuggling
912	# once $oldcpuid's goodness falls below a threshold.
913
914	syslog('debug', "reconfiguring $oldcpuid");
915
916	my $cpu = $delta->{$oldcpuid};
917	my $avgintrload = $delta->{avgintrload};
918
919	my @cputargetlist = reverse(@$cpusortlist); # make a copy of the list
920	while ($#cputargetlist != -1) {
921 		last if goodness_cpu($cpu, $avgintrload) < $goodness_mindelta;
922
923		my $tgtcpuid = shift(@cputargetlist);
924		my $tgt = $delta->{$tgtcpuid};
925		my $load = $cpu->{intrload};
926		my $tgtload = $tgt->{intrload};
927		last if $tgtload > $load;
928		do_reconfig_cpu2cpu($delta, $oldcpuid, $tgtcpuid, $load);
929	}
930}
931
932sub do_reconfig_cpu2cpu($$$$)	# private function
933{
934	my ($delta, $srccpuid, $tgtcpuid, $srcload) = @_;
935
936	# We've been asked to consider interrupt juggling between srccpuid
937	# (with a high interrupt load) and tgtcpuid (with a lower interrupt
938	# load). First, make a single list with all of the ivecs from both
939	# CPUs, and sort the list from highest to lowest load.
940
941	syslog('debug', "exchanging intrs between $srccpuid and $tgtcpuid");
942
943	# Gather together all the ivecs and sort by load
944
945	my @ivecs = (values(%{$delta->{$srccpuid}{ivecs}}),
946	    values(%{$delta->{$tgtcpuid}{ivecs}}));
947	return if $#ivecs == -1;
948
949	@ivecs = sort({$b->{time} <=> $a->{time}} @ivecs);
950
951	# Our "goal" load for srccpuid is the average load across all CPUs.
952	# find_goal() will find determine the optimum selection of the
953	# available interrupts which comes closest to this goal without
954	# falling below the goal.
955
956	my $goal = $delta->{avgintrnsec};
957
958	# We know that the interrupt load on tgtcpuid is less than that on
959	# srccpuid, but its load could still be above avgintrnsec. Don't
960	# choose a goal which would bring srccpuid below the load on tgtcpuid.
961
962	my $avgnsec =
963	    ($delta->{$srccpuid}{intrs} + $delta->{$tgtcpuid}{intrs}) / 2;
964	if ($goal < $avgnsec) {
965		$goal = $avgnsec;
966	}
967
968	# If the largest of the interrupts is on srccpuid, leave it there.
969	# This can help minimize the disruption caused by moving interrupts.
970
971	if ($ivecs[0]->{origcpu} == $srccpuid) {
972		syslog('debug', "Keeping $ivecs[0]->{inum} on $srccpuid");
973		$goal -= $ivecs[0]->{time};
974		shift(@ivecs);
975	}
976
977	syslog('debug', "GOAL: inums should total $goal");
978	find_goal(\@ivecs, $goal);
979
980	# find_goal() returned its results to us by setting $ivec->{goal} if
981	# the ivec should be on srccpuid, or clearing it for tgtcpuid.
982	# Call move_intr() to update our $delta with the new results.
983
984	foreach my $ivec (@ivecs) {
985		syslog('debug', "ivec $ivec->{inum} goal $ivec->{goal}");
986		VERIFY($ivec->{nowcpu} == $srccpuid ||
987		    $ivec->{nowcpu} == $tgtcpuid, "cpu2cpu found an ".
988		    "interrupt not currently on src or tgt cpu");
989
990		if ($ivec->{goal} && $ivec->{nowcpu} != $srccpuid) {
991			move_intr($delta, $ivec->{inum}, $ivec->{nowcpu},
992			    $srccpuid);
993		} elsif ($ivec->{goal} == 0 && $ivec->{nowcpu} != $tgtcpuid) {
994			move_intr($delta, $ivec->{inum}, $ivec->{nowcpu},
995			    $tgtcpuid);
996		}
997	}
998	move_intr_check($delta, $srccpuid, $tgtcpuid); # asserts
999
1000	my $newload = $delta->{$srccpuid}{intrs} / $delta->{$srccpuid}{tot};
1001	VERIFY($newload <= $srcload && $newload > $delta->{avgintrload},
1002	    "cpu2cpu: new load didn't end up in expected range");
1003}
1004
1005
1006# find_goal() and its helper do_find_goal() are used to find the best
1007# combination of interrupts in order to generate a load that is as close
1008# as possible to a goal load without falling below that goal. Before returning
1009# to its caller, find_goal() sets a new value in the hash of each interrupt,
1010# {goal}, which if set signifies that this interrupt is one of the interrupts
1011# identified as part of the set of interrupts which best meet the goal.
1012#
1013# The arguments to find_goal are a list of ivecs (hash references), sorted
1014# by descending {time}, and the goal load. The goal is relative to {time}.
1015# The best fit is determined by performing a depth-first search. do_find_goal
1016# is the recursive subroutine which carries out the search.
1017#
1018# It is passed an index as an argument, originally 0. On a given invocation,
1019# it is only to consider interrupts in the ivecs array starting at that index.
1020# It then considers two possibilities:
1021#   1) What is the best goal-fit if I include ivecs[index]?
1022#   2) What is the best goal-fit if I exclude ivecs[index]?
1023# To determine case 1, it subtracts the load of ivecs[index] from the goal,
1024# and calls itself recursively with that new goal and index++.
1025# To determine case 2, it calls itself recursively with the same goal and
1026# index++.
1027#
1028# It then compares the two results, decide which one best meets the goals,
1029# and returns the result. The return value is the best-fit's interrupt load,
1030# followed by a list of all the interrupts which make up that best-fit.
1031#
1032# As an optimization, a second array loads[] is created which mirrors ivecs[].
1033# loads[i] will equal the total loads of all ivecs[i..$#ivecs]. This is used
1034# by do_find_goal to avoid recursing all the way to the end of the ivecs
1035# array if including all remaining interrupts will still leave the best-fit
1036# at below goal load. If so, it then includes all remaining interrupts on
1037# the goal list and returns.
1038#
1039sub find_goal($$)		# private function
1040{
1041	my ($ivecs, $goal) = @_;
1042
1043	my @goals;
1044	my $load;
1045	my $ivec;
1046
1047	if ($goal <= 0) {
1048		@goals = ();	# the empty set will best meet the goal
1049	} else {
1050		syslog('debug', "finding goal from intrs %s",
1051		    ivecs_to_string(@$ivecs));
1052
1053		# Generate @loads array
1054
1055		my $tot = 0;
1056		foreach $ivec (@$ivecs) {
1057			$tot += $ivec->{time};
1058		}
1059		my @loads = ();
1060		foreach $ivec (@$ivecs) {
1061			push(@loads, $tot);
1062			$tot -= $ivec->{time};
1063		}
1064		($load, @goals) = do_find_goal($ivecs, \@loads, $goal, 0);
1065	}
1066	VERIFY($load >= $goal, "find_goal didn't meet goals");
1067	syslog('debug', "goals found: %s", ivecs_to_string(@goals));
1068
1069	# Set or clear $ivec->{goal} for each ivec, based on returned @goals
1070
1071	foreach $ivec (@$ivecs) {
1072		if ($#goals > -1 && $ivec == $goals[0]) {
1073			syslog('debug', "inum $ivec->{inum} on source cpu");
1074			$ivec->{goal} = 1;
1075			shift(@goals);
1076		} else {
1077			syslog('debug', "inum $ivec->{inum} on target cpu");
1078			$ivec->{goal} = 0;
1079		}
1080	}
1081}
1082
1083
1084sub do_find_goal($$$$)		# private function
1085{
1086	my ($ivecs, $loads, $goal, $idx) = @_;
1087
1088	if ($idx > $#{$ivecs}) {
1089		return (0);
1090	}
1091	syslog('debug', "$idx: finding goal $goal inum $ivecs->[$idx]{inum}");
1092
1093	my $load = $ivecs->[$idx]{time};
1094	my @goals_with = ();
1095	my @goals_without = ();
1096	my ($with, $without);
1097
1098	# If we include all remaining items and we're still below goal,
1099	# stop here. We can just return a result that includes $idx and all
1100	# subsequent ivecs. Since this will still be below goal, there's
1101	# nothing better to be done.
1102
1103	if ($loads->[$idx] <= $goal) {
1104		syslog('debug',
1105		    "$idx: including all remaining intrs %s with load %d",
1106		    ivecs_to_string(@$ivecs[$idx .. $#{$ivecs}]),
1107		    $loads->[$idx]);
1108		return ($loads->[$idx], @$ivecs[$idx .. $#{$ivecs}]);
1109	}
1110
1111	# Evaluate the "with" option, i.e. the best matching goal which
1112	# includes $ivecs->[$idx]. If idx's load is more than our goal load,
1113	# stop here. Once we're above the goal, there is no need to consider
1114	# further interrupts since they'll only take us further from the goal.
1115
1116	if ($goal <= $load) {
1117		$with = $load;	# stop here
1118	} else {
1119		($with, @goals_with) =
1120		    do_find_goal($ivecs, $loads, $goal - $load, $idx + 1);
1121		$with += $load;
1122	}
1123	syslog('debug', "$idx: with-load $with intrs %s",
1124	       ivecs_to_string($ivecs->[$idx], @goals_with));
1125
1126	# Evaluate the "without" option, i.e. the best matching goal which
1127	# excludes $ivecs->[$idx].
1128
1129	($without, @goals_without) =
1130	    &do_find_goal($ivecs, $loads, $goal, $idx + 1);
1131	syslog('debug', "$idx: without-load $without intrs %s",
1132	       ivecs_to_string(@goals_without));
1133
1134	# We now have our "with" and "without" options, and we choose which
1135	# best fits the goal. If one is greater than goal and the other is
1136	# below goal, we choose the one that is greater. If they are both
1137	# below goal, then we choose the one that is greater. If they are
1138	# both above goal, then we choose the smaller.
1139
1140	my $which;		# 0 == with, 1 == without
1141	if ($with >= $goal && $without < $goal) {
1142		$which = 0;
1143	} elsif ($with < $goal && $without >= $goal) {
1144		$which = 1;
1145	} elsif ($with >= $goal && $without >= $goal) {
1146		$which = ($without < $with);
1147	} else {
1148		$which = ($without > $with);
1149	}
1150
1151	# Return the load of our best case scenario, followed by all the ivecs
1152	# which compose that goal.
1153
1154	if ($which == 1) {	# without
1155		syslog('debug', "$idx: going without");
1156		return ($without, @goals_without);
1157	} else {
1158		syslog('debug', "$idx: going with");
1159		return ($with, $ivecs->[$idx], @goals_with);
1160	}
1161	# Not reached
1162}
1163
1164
1165
1166
1167syslog('debug', "intrd is starting".($debug ? " (debug)" : ""));
1168
1169my @deltas = ();
1170my $deltas_tottime = 0;		# sum of maxsnap-minsnap across @deltas
1171my $avggoodness;
1172my $baseline_goodness = 0;
1173my $compdelta;
1174
1175my $do_reconfig;
1176
1177# temp variables
1178my $goodness;
1179my $deltatime;
1180my $olddelta;
1181my $olddeltatime;
1182my $delta;
1183my $newstat;
1184my $below_statslen;
1185my $newtime;
1186my $ret;
1187
1188
1189my $gotsig = 0;
1190$SIG{INT} = sub { $gotsig = 1; };     # don't die in the middle of retargeting
1191$SIG{HUP} = $SIG{INT};
1192$SIG{TERM} = $SIG{INT};
1193
1194my $ks;
1195if ($using_scengen == 0) {
1196	$ks = Sun::Solaris::Kstat->new();
1197} else {
1198	$ks = myks_update();	# supplied by the simulator
1199}
1200
1201# If no pci_intrs kstats were found, we need to exit, but we can't because
1202# SMF will restart us and/or report an error to the administrator. But
1203# there's nothing an administrator can do. So print out a message for SMF
1204# logs and silently pause forever.
1205
1206if (!exists($ks->{pci_intrs})) {
1207	print STDERR "$cmdname: no interrupts were found; ".
1208	    "your PCI bus may not yet be supported\n";
1209	pause() while $gotsig == 0;
1210	exit 0;
1211}
1212
1213my $stat = getstat($ks);
1214
1215
1216
1217for (;;) {
1218	sub clear_deltas {
1219		@deltas = ();
1220		$deltas_tottime = 0;
1221		$stat = 0;   # prevent next gen_delta() from setting {missing}
1222	}
1223
1224	# 1. Sleep, update the kstats, and save the new stats in $newstat.
1225
1226	exit 0 if $gotsig;		# if we got ^C / SIGTERM, exit
1227	if ($using_scengen == 0) {
1228		sleep($sleeptime);
1229		exit 0 if $gotsig;	# if we got ^C / SIGTERM, exit
1230		$ks->update();
1231	} else {
1232		$ks = myks_update();
1233	}
1234	$newstat = getstat($ks);
1235
1236	# $stat or $newstat could be zero if they're uninitialized, or if
1237	# getstat() failed. If $stat is zero, move $newstat to $stat, sleep
1238	# and try again. If $newstat is zero, then we also sleep and try
1239	# again, hoping the problem will clear up.
1240
1241	next if (!ref $newstat);
1242	if (!ref $stat) {
1243		$stat = $newstat;
1244		next;
1245	}
1246
1247
1248	# 2. Compare $newstat with the prior set of values, result in %$delta.
1249
1250	$delta = generate_delta($stat, $newstat);
1251	dumpdelta($delta) if $debug;	# Dump most recent stats to stdout.
1252	$stat = $newstat;	# The new stats now become the old stats.
1253
1254
1255	# 3. If $delta->{missing}, then there has been a reconfiguration of
1256	# either cpus or interrupts (probably both). We need to toss out our
1257	# old set of statistics and start from scratch.
1258	#
1259	# Also, if the delta covers a very long range of time, then we've
1260	# been experiencing a system overload that has resulted in intrd
1261	# not being allowed to run effectively for a while now. As above,
1262	# toss our old statistics and start from scratch.
1263
1264	$deltatime = $delta->{maxsnap} - $delta->{minsnap};
1265	if ($delta->{missing} > 0 || $deltatime > $statslen) {
1266		clear_deltas();
1267		syslog('debug', "evaluating interrupt assignments");
1268		next;
1269	}
1270
1271
1272	# 4. Incorporate new delta into the list of deltas, and associated
1273	# statistics. If we've just now received $statslen deltas, then it's
1274	# time to evaluate a reconfiguration.
1275
1276	$below_statslen = ($deltas_tottime < $statslen);
1277	$deltas_tottime += $deltatime;
1278	$do_reconfig = ($below_statslen && $deltas_tottime >= $statslen);
1279	push(@deltas, $delta);
1280
1281	# 5. Remove old deltas if total time is more than $statslen. We use
1282	# @deltas as a moving average of the last $statslen seconds. Shift
1283	# off the olders deltas, but only if that doesn't cause us to fall
1284	# below $statslen seconds.
1285
1286	while (@deltas > 1) {
1287		$olddelta = $deltas[0];
1288		$olddeltatime = $olddelta->{maxsnap} - $olddelta->{minsnap};
1289		$newtime = $deltas_tottime - $olddeltatime;
1290		last if ($newtime < $statslen);
1291
1292		shift(@deltas);
1293		$deltas_tottime = $newtime;
1294	}
1295
1296	# 6. The brains of the operation are here. First, check if we're
1297	# imbalanced, and if so set $do_reconfig. If $do_reconfig is set,
1298	# either because of imbalance or above in step 4, we evaluate a
1299	# new configuration.
1300	#
1301	# First, take @deltas and generate a single "compressed" delta
1302	# which summarizes them all. Pass that to do_reconfig and see
1303	# what it does with it:
1304	#
1305	# $ret == -1 : failure
1306	# $ret ==  0 : current config is optimal (or close enough)
1307	# $ret ==  1 : reconfiguration has occurred
1308	#
1309	# If $ret is -1 or 1, dump all our deltas and start from scratch.
1310	# Step 4 above will set do_reconfig soon thereafter.
1311	#
1312	# If $ret is 0, then nothing has happened because we're already
1313	# good enough. Set baseline_goodness to current goodness.
1314
1315	$compdelta = compress_deltas(\@deltas);
1316	if (VERIFY(ref($compdelta) eq "HASH", "couldn't compress deltas")) {
1317		clear_deltas();
1318		next;
1319	}
1320	$compdelta->{goodness} = goodness($compdelta);
1321	dumpdelta($compdelta) if $debug;
1322
1323	$goodness = $compdelta->{goodness};
1324	syslog('debug', "GOODNESS: %5.2f%%", $goodness * 100);
1325
1326	if ($deltas_tottime >= $statslen &&
1327	    imbalanced($goodness, $baseline_goodness)) {
1328		$do_reconfig = 1;
1329	}
1330
1331	if ($do_reconfig) {
1332		$ret = do_reconfig($compdelta);
1333
1334		if ($ret != 0) {
1335			clear_deltas();
1336			syslog('debug', "do_reconfig FAILED!") if $ret == -1;
1337		} else {
1338			syslog('debug', "setting new baseline of $goodness");
1339			$baseline_goodness = $goodness;
1340		}
1341	}
1342	syslog('debug', "---------------------------------------");
1343}
1344