File Coverage

blib/lib/Net/Z3950/AsyncZ.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             # $Date: 2004/03/25 22:58:20 $
2             # $Revision: 1.14 $
3              
4             package Net::Z3950::AsyncZ;
5             our $VERSION = '0.10';
6 2     2   51953 use Net::Z3950::AsyncZ::Options::_params;
  0            
  0            
7             use Net::Z3950::AsyncZ::Errors;
8             use Net::Z3950::AsyncZ::ZLoop;
9             use Net::Z3950::AsyncZ::ErrMsg;
10             use Event;
11             use POSIX ":sys_wait_h";
12             use Symbol;
13             use Exporter;
14             use sigtrap qw (die untrapped normal-signals die error-signals);
15             @ISA=qw (Exporter);
16             @EXPORT_OK = qw(asyncZOptions isZ_MARC isZ_GRS isZ_RAW isZ_Error isZ_nonRetryable isZ_Info
17             isZ_DEFAULT noZ_Response isZ_Header isZ_ServerName Z_serverName getZ_RecNum
18             getZ_RecSize delZ_header delZ_pid delZ_serverName prep_Raw get_ZRawRec
19             );
20             %EXPORT_TAGS = (
21             record => [qw(isZ_MARC isZ_GRS isZ_RAW isZ_DEFAULT getZ_RecNum getZ_RecSize)],
22             errors => [qw(isZ_Error isZ_nonRetryable)],
23             header => [qw(isZ_ServerName Z_serverName noZ_Response isZ_Header
24             delZ_header delZ_pid delZ_serverName isZ_Info)]
25             );
26              
27              
28             use IPC::ShareLite qw( LOCK_EX);
29              
30             use strict;
31              
32             my %forkedPID=(); # pids of forked process saved in hash:
33             # keys = pids, values = our indexes to forked processes
34             # these deleted when fork data is processed
35             # if there are no keys left in hash, then the timer loop exits
36             my %exitCode=(); # saves exit codes of forked processes
37             # keys = pids, values = exit codes
38             # processes without 0 values are killed in DESTROY to prevent zombies
39             my %resultTable = (); # saves pids, hosts and report results of child processes
40             # keys = pids, values = [ host, report_results, index, retry_index ]
41             #
42             # SLOT 0 host server address
43             # SLOT 1 report results: boolean = true if report, false if not
44             # SLOT 2 index of process in current cycle (original or retry)
45             # SLOT 3 retry_index:
46             # # -1, -2 or index of process retrying a failed query
47             # # initialized to -1 in original cycle,-2 in retry cycle
48             # (a positive retry_index replaces original cycle's -1)
49             # # the retry_index is always -2 in the retry process:
50             #
51            
52              
53             my $__DBUG = 0;
54             my $_ERROR_VAL = Net::Z3950::AsyncZ::Errors::errorval();
55             $SIG{CHLD} = \&childhandler;
56              
57             sub asyncZOptions { return Net::Z3950::AsyncZ::Options::_params->new(@_); }
58              
59             sub isZ_Header { $_[0] =~ Net::Z3950::AsyncZ::Report::get_pats(); }
60             sub isZ_MARC { $_[0] =~ Net::Z3950::AsyncZ::Report::get_MARC_pat(); }
61             sub isZ_GRS { $_[0] =~ Net::Z3950::AsyncZ::Report::get_GRS_pat(); }
62             sub isZ_RAW { $_[0] =~ Net::Z3950::AsyncZ::Report::get_RAW_pat(); }
63             sub isZ_DEFAULT { $_[0] =~ Net::Z3950::AsyncZ::Report::get_DEFAULT_pat(); }
64             sub getZ_RecNum { $_[0] =~ /\s(\d+)\]/; $1; }
65              
66             sub _setupUTF8 {
67             return if is_utf8_init();
68             local $^W = 0;
69             eval { require MARC::Charset; };
70             local $^W = 1;
71             if ($@) {
72             warn "UTF8 requires MARC::Charset\n";
73             return 0;
74             }
75             set_uft8_init();
76             return 1;
77             }
78              
79             # params: string or ref to string
80             # boolean: true, then substitution uses 'g' modifier
81             # substitution string
82             # if subst string is not defined, empty string is substituted
83             # return: either string or reference to string, depending on whether a reference or a string
84             # was intially passed in paramter $_[0]
85              
86             sub delZ_header {
87             my($str,$g, $subst) = @_;
88             my $pat = Net::Z3950::AsyncZ::Report::get_pats();
89             return _del_headers($str,$pat, $g, $subst);
90             }
91              
92             # see delZ_header
93             sub delZ_pid {
94             my($str,$g, $subst) = @_;
95             return _del_headers($str,'<#--\d+-->', $g, $subst);
96             }
97              
98             # see delZ_header
99             sub delZ_serverName {
100             my($str,$g, $subst) = @_;
101             return _del_headers($str,'', $g, $subst);
102             }
103              
104             sub _del_headers {
105             my $str = ref $_[0] ? ${$_[0]} : $_[0];
106             my $pat = $_[1];
107             my $g = $_[2];
108             my $subst = (defined $_[3]) ? $_[3] : "";
109              
110             if($g) {
111             $str =~ s/$pat/$subst/g;
112             }
113             else {
114             $str =~ s/$pat/$subst/;
115             }
116             return \$str if ref $_[0];
117             return $str;
118              
119             }
120              
121              
122             # make string from array, return ref to string
123             # param: array of raw records
124             sub prep_Raw {
125             my $raw = shift;
126             my $str = join "",@$raw;
127             $raw = delZ_header(\$str); # will get back ref to string
128             $raw = delZ_pid($raw,1); # passing ref will get back ref
129             $raw = delZ_serverName($raw,1);
130             $raw = delZ_header($raw,1,'');
131             return $raw;
132             }
133              
134             # param: ref to string of raw records
135             # return next record
136             sub get_ZRawRec {
137             my $raw = shift;
138             return undef if ! $raw;
139              
140             if ($$raw !~ //) { # presumed last record
141             my $rec = $$raw;
142             $$raw = "";
143             return $rec;
144             }
145             $$raw =~ s/(.*?)//;
146             return $1;
147             }
148              
149             # tests whether line is our substitue for absence of Report:
150             # {!-- library.anu.edu.au --}
151             # It reports previous server's name in curlies, substituted for angle brackets
152             # (like HTML comment) which hold server name in header of each report item
153             sub noZ_Response { $_[0]=~/\{!--\s+.*\s+--\}/; }
154              
155             # tests if line contains server name
156             sub isZ_ServerName { $_[0] =~ //; }
157             sub isZ_PID { $_[0] =~ /<#--\d+-->/; }
158             sub isZ_Info { &isZ_PID || &noZ_Response; }
159             # returns server name
160             sub Z_serverName {
161             if( $_[0] =~ //){
162             return $1 if $1;
163             }
164             return undef;
165             }
166              
167             # returns 0 if not an error
168             # returns 2 if cycle 2 error
169             # returns 1 if non-recoverable cycle 1 error
170             sub isZ_Error {
171             my $err = shift;
172             return 0 if !$err;
173             return 2 if $err->[0] && $err->[1];
174             return 1 if $err->[0] && !$err->[0]->{retry};
175             return 0;
176             }
177              
178             # tests return value of isZ_Error()
179             # returns true if the error was a cycle 1 fatal error
180             sub isZ_nonRetryable { $_[0] == 1; }
181              
182              
183              
184             {
185              
186             my @results=();
187             my @errors=();
188             my @recSize = ();
189             my $busy = 0;
190             my $utf8_init = 0;
191              
192             sub is_utf8_init {
193             $utf8_init;
194             }
195              
196             sub set_uft8_init {
197             $utf8_init = 1;
198             }
199              
200             sub _utf8 {
201             my $index = shift;
202              
203             _setupUTF8() if !$utf8_init;
204             return if !$utf8_init;
205              
206             my $cs = MARC::Charset->new();
207             for(my $i = 0; $i < scalar(@{$results[$index]}); $i++) {
208             $results[$index]->[$i] = $cs->to_utf8($results[$index]->[$i]);
209             }
210             }
211              
212             sub _saveResults {
213             $busy = 1;
214             my ($arr, $index) = @_;
215             $results[$index] = $arr;
216             $busy = 0;
217             }
218              
219             sub _saveErrors {
220             @errors = @_;
221             }
222              
223             sub _isBusy { return $busy; }
224              
225             # returns reference to results array
226             sub getResult {
227             my ($self,$index) = @_;
228             _utf8($index) if $self->{options}[$index]-> _getFieldValue('utf8');
229             return $results[$index];
230             }
231              
232             sub getZ_RecSize { $recSize[$_[0]]; }
233              
234             sub getErrors {
235             my ($self,$index) = @_;
236             return [$errors[$index]->[0], $errors[$index]->[1]] if $errors[$index];
237             return undef;
238             }
239              
240              
241             sub getMaxErrors { return scalar @errors; }
242              
243             sub _callback {
244             $busy = 1;
245             my ($self, $index) = @_;
246             _utf8($index) if $self->{options}[$index]-> _getFieldValue('utf8');
247             my $cb = $self->{options}[$index]-> _getFieldValue('cb');
248             $cb = $self->{cb} if !$cb;
249              
250             my $last_el = scalar(@{$results[$index]})-1;
251             my $size = $results[$index]->[$last_el];
252             $size =~ /\*==(\d+)==\*/;
253             $recSize[$index] = $1 ? $1 : 0;
254             $results[$index]->[$last_el] =~s/\*==(\d+)==\*//;
255            
256             &$cb($index, $results[$index]) if $cb;
257             $busy = 0;
258             }
259              
260             }
261              
262              
263              
264             #-------------------------------------------------------------------#
265             # private paramaters:
266             # start: start time for timers
267             # zl: array of forked processes
268             # errors: reference to Net::Z3950::AsyncZ::Errors object for main process
269             # share: reference to IPC::ShareLite
270             # timer: reference to timer watcher
271             # unlooped: notifies DESTROY when all pipes have been processed,
272             # because DESTROY is called for each closed pipe--hence
273             # makes it safe to do cleanup that applies to main process
274             # monitor_pid: pid of the monitor, for killing it
275             #--------------------------------------------------------------------#
276             sub new {
277             my($class, %args) = @_;
278             my $index = 0;
279              
280             my $self = {
281             start => time(), zl => [], query=>$args{query}, errors=>undef,
282             log=>$args{log} || undef, cb=>$args{cb}, timer => undef,
283             timeout=>$args{timeout} || 25, timeout_min=>$args{timeout_min} || 5,
284             interval => $args{interval} || 1, servers=>$args{servers},
285             options=>$args{options}, unlooped=>0, maxpipes=>$args{maxpipes} || 4,
286             share => undef, monitor => 0 || $args{monitor}, monitor_pid=>undef,
287             swap_check => $args{swap_check} || 0, swap_attempts => $args{swap_attempts} || 5
288             };
289              
290             bless $self,$class;
291             $self->{ errors } = Net::Z3950::AsyncZ::Errors->new($self->{log});
292              
293             %forkedPID=();
294             %exitCode=();
295             %resultTable = ();
296            
297             my $incr = $self->{maxpipes};
298             $self->{share} = new IPC::ShareLite( -key => $$ + 5000,
299             -create => 'yes',
300             -destroy => 'yes');
301             $self->{monitor_pid} = $self->_monitor() if $self->{monitor};
302            
303             $SIG{HUP} = sub {
304             $self->{abort} = 1;
305             $self->{unlooped} = 1; # notify DESTROY that it's safe to kill outstanding processes
306             $! = 227;
307             die "Aborting."
308             };
309            
310              
311             $self->processHosts(-1,%args);
312              
313             # retry servers that returned without error fatal codes
314             my @retries = $self->_getReTries();
315             $args{'servers'} = \@retries;
316             $self->{'servers'} = $args{'servers'};
317             $self->processHosts(-2, %args);
318             $self->_showStats(\%resultTable) if $__DBUG;
319             $self->_processErrors();
320             kill KILL => $self->{monitor_pid} if $self->{monitor};
321             $self->{share} = undef;
322             return $self;
323            
324             }
325              
326              
327             sub processHosts {
328             my ($self, $retry_marker, %args) = @_;
329             my $index = 0;
330             my $count = 0;
331              
332             $self->{unlooped} = 0;
333             $self->{start} = time();
334             %forkedPID=();
335              
336             foreach my $server(@{$args{servers}}) {
337            
338             $self->{server} = $server;
339             $self->{options}[$index] = Net::Z3950::AsyncZ::Options::_params->new(format=>$args{format},
340             num_to_fetch=>$args{num_to_fetch})
341             if ! defined $self->{options}[$index];
342              
343             $self->{options}[$index]->option(_this_server=>$server->[0]);
344             $self->start($index, $retry_marker);
345            
346              
347             if($count == $self->{maxpipes}) {
348             my $mem_avail = $self->{swap_check} ? 0 : 1;
349             my $attempts = 0;
350             while(!$mem_avail) {
351             $mem_avail = is_mem_left();
352             if (!$mem_avail){
353             my $start_t = time();
354             Event->timer(at => time+$self->{swap_check},cb => sub { $_[0]->w->cancel;} );
355             Event::loop;
356             # print STDERR "(swap-check) slept: ", time()-$start_t,"\n" if $__DBUG;
357             }
358             $attempts++;
359             # print STDERR "(swap-check) attempts: $attempts\n" if $__DBUG;;
360             die "Memory resources appear to be too low to continue;\n",
361             "try settng the swap_check to a higher value and or",
362             "allowing for more than $self->{swap_attempts} swap_attempts\n"
363             if $attempts > $self->{swap_attempts};
364             }
365              
366             $self->{timer} =
367             Event->timer(interval => $self->{interval}, hard=>1, cb=> sub { $self->timerCallBack(); } );
368             Event::loop();
369             $count = -1;
370             }
371            
372             $index++;
373             $count++;
374             }
375              
376              
377             # if there are any servers left to wait for, get another loop
378             if(scalar (@{$args{servers}})%$self->{maxpipes} != 0) {
379             $self->{timer} =
380             Event->timer(interval => $self->{interval}, hard=>1, cb=> sub { $self->timerCallBack(); } );
381             Event::loop();
382             }
383            
384             $self->{unlooped} = 1;
385              
386             }
387              
388              
389             sub _getReTries {
390             my $self = shift;
391             my @retries=();
392             my $count=0;
393              
394             foreach my $pid (keys %resultTable) {
395             if($resultTable{$pid}->[1] == 0) {
396             my $err = Net::Z3950::AsyncZ::ErrMsg->new($exitCode{$pid}); ## created for testing only
397             next if !$err->doRetry(); ## not being saved
398             my $index = $resultTable{$pid}->[2];
399             push @retries, $self->{servers}[$index];
400             $self->{options}[$count] = $self->{options}[$index];
401             $resultTable{$pid}->[3] = $count; # save retry index
402             $count++;
403             }
404             }
405              
406             return @retries;
407              
408             }
409              
410             sub start {
411             my $self=shift;
412             return if defined $self->{abort};
413             my $index = shift;
414             my $retry_marker = shift;
415             my $pid;
416              
417             if($pid = fork) {
418             $forkedPID{$pid} = $index;
419             $exitCode{$pid} = -1;
420             $resultTable{$pid}->[0] = @{$self->{servers}[$index]}[0]; # server name
421             $resultTable{$pid}->[1] = 0; # report = false
422             $resultTable{$pid}->[2] = $index; # current index
423             $resultTable{$pid}->[3] = $retry_marker; # retry index
424              
425             print "process $index: \$pid = $pid $resultTable{$pid}->[0] @{$self->{servers}[$index]}[1] @{$self->{servers}[$index]}[2]\n" if $__DBUG;
426             }
427             else {
428             die "Server cannot handle your request at this time" unless defined $pid;
429             $self->{share}->destroy(0);
430              
431             my $update = $self->{options}[$index]->_updateObjectHash($self);
432             my $query = $update->{query} ? $update->{query} : $self->{query};
433             my $log = $update->{log} ? $update->{log} : $self->{log};
434             $self->{options}[$index]->_setFieldValue('_this_pid', $$);
435             my $zerrs = Net::Z3950::AsyncZ::Errors->new($log, @{$self->{server}}[0], $query,
436             $self->{options}[$index]->get_preferredRecordSyntax(),
437             @{$self->{server}}[2]
438             );
439              
440             $self->{zl}[$index] =
441             Net::Z3950::AsyncZ::ZLoop->new(@{$self->{server}},$query,$self->{options}[$index]);
442             $self->{zl}[$index]->setTimer($self->{interval});
443              
444             my $host = @{$self->{servers}[$index]}[0];
445             if ($self->{zl}[$index]->{report} && $self->{share}) {
446             push @{$self->{zl}[$index]->{report}},
447             "*==" . $self->{zl}[$index]->{rsize} . "==*\n";
448            
449             $self->{share}->store(join '',@{$self->{zl}[$index]->{report}});
450             }
451             elsif ($self->{share}) {
452             $self->{share}->store("");
453             }
454             else { exit (Net::Z3950::AsyncZ::ErrMsg::_EINVAL()); }
455            
456              
457             exit 0;
458            
459             }
460              
461              
462             }
463              
464              
465             {
466             my $in_getResult = 0;
467             sub _gettingResult { $in_getResult; }
468              
469             sub _getResult {
470             $in_getResult = 1;
471             my ($self, $pid) = @_;
472              
473             exit (Net::Z3950::AsyncZ::ErrMsg::_EINVAL()) if !$self->{share};
474             $self->{share}->lock(LOCK_EX);
475              
476             while(_isBusy()) { }
477             my $data = $self->{share}->fetch();
478             return if !$data; # presumably should never occur
479             # but it happened once and split doesn't
480             # complain about splitting an undefined value
481             my @data = split "\n", $data;
482              
483             $data[0] =~ //;
484             my $host = $1;
485             $self->{share}->store("\{!\-\- $host \-\-\}") if $host;
486              
487             $data[1] =~ /<#--(\d+)-->/ if $data[1];
488             my $_this_pid = $1 if $1;
489             $resultTable{$_this_pid}->[1] = 1
490             if $_this_pid && exists $resultTable{$_this_pid};
491             splice(@data,1,1);
492             $pid = $_this_pid if $_this_pid;
493             my $index = _getIndex($pid);
494            
495             while(_isBusy()) { }
496             _saveResults(\@data, $index);
497             while(_isBusy()) { }
498             $self->_callback($index); # if $self->{cb};
499              
500              
501             $self->{share}->unlock;
502            
503             $in_getResult = 0;
504             }
505              
506             }
507              
508              
509             sub _getIndex {
510             my $pid = shift;
511             return $resultTable{$pid}->[2] if $resultTable{$pid}->[3] == -1; # cycle 1, no retry index
512             my $current_index = $resultTable{$pid}->[2]; # this process's index, from either cycle
513              
514             foreach $pid (keys %resultTable) { # if this retry index == $current_index,
515             return $resultTable{$pid}->[2] # $current_index must be a cycle 2 index
516             if $resultTable{$pid}->[3] == $current_index; # and this table entry is cycle 1 entry
517             }
518              
519             return $resultTable{$pid}->[2]; # default: returns cycle 1 or 2 index
520             }
521              
522             sub allDone {
523             foreach my $pid (keys %exitCode) {
524             return 0 if $exitCode{$pid} == -1;
525             }
526             return 1;
527             }
528              
529             sub timerCallBack {
530             my $self=shift;
531             my $Seconds = time();
532              
533             foreach my $pid (keys %forkedPID) {
534             while (_gettingResult()) { }
535             $self->_getResult($pid), delete $forkedPID{$pid} if $exitCode{$pid} == 0;
536             }
537            
538             my $endval = $Seconds - $self->{start};
539             if ($endval > $self->{timeout} || allDone() ) {
540             $self->{timer}->cancel();
541             Event::unloop();
542             }
543              
544             }
545              
546              
547             sub _processErrors {
548             my $self = shift;
549             my %cycle_1 = ();
550             my %cycle_2 = ();
551             my @errors = ();
552             my $_count = 0;
553             $__DBUG =0;
554             print "\n\nProcessing Errors\n" if $__DBUG;
555              
556             foreach my $pid (keys %resultTable) {
557             $cycle_2{$pid} = $resultTable{$pid}, next
558             if $resultTable{$pid}->[1] == 0 && $resultTable{$pid}->[3] == -2;
559             $cycle_1{$pid} = $resultTable{$pid}
560             if $resultTable{$pid}->[1] == 0;
561             }
562              
563             print "\nCycle 1\n" if $__DBUG;
564             $self->_showStats(\%cycle_1) if $__DBUG;
565              
566             foreach my $pid_1 (keys %cycle_1) {
567             my $err = Net::Z3950::AsyncZ::ErrMsg->new($exitCode{$pid_1});
568             my $index = _getIndex($pid_1);
569             $errors[$index]->[0] = $err;
570             print $pid_1, " " if $__DBUG;
571             $self->_printError($err) if $__DBUG;
572             }
573              
574             print "\nCycle 2\n" if $__DBUG;
575             $self->_showStats(\%cycle_2) if $__DBUG;
576              
577              
578             foreach my $pid_2 (keys %cycle_2) {
579             my $err = Net::Z3950::AsyncZ::ErrMsg->new($exitCode{$pid_2});
580             my $index = _getIndex($pid_2);
581             $errors[$index]->[1] = $err;
582             print $pid_2, " " if $__DBUG;
583             $self->_printError($err) if $__DBUG;
584             }
585              
586             _saveErrors(@errors);
587             $__DBUG =0;
588             }
589              
590              
591             sub _printError {
592             my $self = shift;
593             my $err = shift;
594             my $errno = $err->{errno};
595             my $num = sprintf( "[%3d]",$errno);
596             print "$num ";
597             print $err->{msg} if $err->{msg};
598             print " NET" if $err->isNetwork();
599             print " SYSTEM" if $err->isSystem();
600             print " TRY AGAIN" if $err->isTryAgain();
601             print " SUCCESS" if $err->isSuccess();
602             print " --Z3950 ERROR " if $err->isZ3950();
603             print " --RETRY " if $err->doRetry();
604             print "\n";
605             }
606              
607             sub childhandler {
608              
609             while((my $retv = waitpid(-1,WNOHANG))>0) {
610             $exitCode{$retv} = $? >> 8;
611             $? = $exitCode{$retv}, die
612             if Net::Z3950::AsyncZ::ErrMsg::_abort($exitCode{$retv});
613             }
614             $SIG{CHLD} = \&childhandler;
615             }
616              
617             use Carp;
618              
619             sub DESTROY {
620             my $self = shift;
621             # Because each process uses this DESTROY method, we have to
622             # wait for the main loop to end before closing its error log
623             # and before killing any potential zombie processes
624              
625              
626              
627             return if !$self->{unlooped};
628              
629             print "DESTROY\n" if $__DBUG;
630             foreach my $pid (keys %exitCode) {
631             if( kill 0 => $pid) {
632             kill 9 => $pid if ($exitCode{$pid} < 0 || $exitCode{$pid} > 0);
633             print "killing $pid\n" if ($exitCode{$pid} < 0 || $exitCode{$pid} > 0) && $__DBUG;
634             }
635             }
636             kill KILL => $self->{monitor_pid} if $self->{monitor};
637             $self->{share} = undef if defined $self->{share};
638              
639             sleep(1); # allow time for remaining killed processes to be reaped
640             }
641              
642              
643              
644              
645              
646              
647             sub _monitor {
648             my $self = shift;
649              
650             $SIG{ALRM} = sub {
651             my $pid = getppid();
652             # print "killing: $pid\n";
653             kill HUP => $pid;
654             kill KILL => $$;
655             };
656              
657             my $pid;
658             if($pid = fork) {
659             return $pid;
660             }
661             else {
662             die "Unable to fork" unless defined $pid;
663             alarm($self->{monitor});
664             while (1) { sleep(10); }
665             }
666            
667             }
668              
669              
670              
671              
672             sub is_mem_left {
673             my $vmstat;
674             if($^O =~ /linux/) {
675             $vmstat = "vmstat 1 3 | ";
676             }
677             else {
678             $vmstat = "vmstat -S 1 3| ";
679             }
680              
681             open VMSTAT, $vmstat or die "can't open vmstat";
682              
683             my (@si,@so,$si_index,$so_index,@fields);
684             my $count=0;
685             while() {
686             sleep(1); # helps to insure that vmstat produces 3 lines of output
687             s/^\s*// and s/\s*$//;
688             s/\s+/;/g;
689             if(/si/i && /so/i) {
690             @fields = split /;/;
691             for(my $i=0; $i< scalar @fields; $i++) {
692             $si_index = $i if $fields[$i] =~ /^si$/i;
693             $so_index = $i if $fields[$i] =~ /^so$/i;
694             }
695             }
696             elsif(/\d/) {
697             @fields = split /;/;
698             $si[$count] = $fields[$si_index];
699             $so[$count] = $fields[$so_index];
700             $count++;
701             }
702             }
703              
704             close VMSTAT;
705             sleep 3 and return 1 if $count < 2; # fix for when vmstat returns after only one cycle
706             return 0 if abs($si[2] - $si[1]) >= 20;
707             return 0 if abs($so[2] - $so[1]) >= 20;
708             return 1;
709              
710              
711             }
712              
713             1;
714              
715              
716             __END__