# # Copyright (c) 2002-2004 Eric Wallengren # This file is part of the Continuous Automated Build and Integration # Environment (CABIE) # # CABIE is distributed under the terms of the GNU General Public # License version 2 or any later version. See the file COPYING for copying # permission or http://www.gnu.org. # # THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED OR # IMPLIED, without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. ANY USE IS AT YOUR OWN RISK. # # Permission to modify the code and to distribute modified code is granted, # provided the above notices are retained, and a notice that the code was # modified is included with the above copyright notice. # # # This package is specifically for running the buildserver and source monitor # under unix operating systems including Cygwin32. # BEGIN{push @LIB, "lib";} # # Package declaration # package unixsys; # # Use Carp for error handling # use Carp; use Sys::Hostname; # # Buildconf for configuration values # my $hostname = hostname(); require "$hostname.pm"; # # For future use # # use Proc::ProcessTable; # # Uncomment if you're using MySQL # use DBI; DBI->trace(0); my $config = new $hostname; # # Global section, please do not edit!!! # my %fields = ( sport => '8091', mport => '8092', port => '8091', makemonitor => '0', makedaemon => '0', bAmMonitor => '0', bAmServer => '0', serverpid => '-1', monitorpid => '-1', volume => 'NA', jobname => 'NA', jobno => '0', hostname => 'NA', ); # # Read commandline args # sub validateargs { my $self = shift; my $hostname = shift; my %Opts = @_; my $arg; my $bMissing = 0; my $sport; my $mport; my @posixargs = qw (m s d); foreach $arg (@posixargs) { if (!defined($Opts{$arg})) { if ($bMissing == 0) { print "Incomplete options: "; $bMissing = 1; } print " -$arg"; } } if ($bMissing) { print "\n"; exit -1; } # # Look for -s (server port) # if ($Opts{s} !~ /^$/ ) { if ($Opts{s} !~ /^[0-9]+$/ ) { print "Invalid port number!\n"; exit 1; } else { $self->sport($Opts{s}); } } # # Look for -m (monitor port) # if ($Opts{m} !~ /^$/ ) { if ($Opts{m} !~ /^[0-9]+$/ ) { print "Invalid port number!\n"; exit 1; } else { $self->makemonitor('1'); $self->mport($Opts{m}); $self->sport($Opts{s}); } } # # Look for -d (create server daemon) # if ($Opts{d}) { $self->makedaemon('1'); } } # # Startup daemons... # sub startdaemons { my $self = shift; my $dir = shift; my $Mtmpdir = $config->BTMP; my $Mnull = $config->NULL; my $serverpid; my $monitorpid; # # If -d was specified on the command line, become a daemon... # if (defined($self->makedaemon) && !$self->bAmServer) { $serverpid = fork; $self->serverpid("$serverpid"); } # # If there was a separate port defined for a monitor, create a new # monitor process... # if ($self->makemonitor && $self->serverpid) { $monitorpid = fork; $self->monitorpid("$monitorpid"); } # # If this is the child process then setup some defaults # if ($self->monitorpid == 0) { if ($self->makemonitor) { $self->bAmMonitor('1'); $self->port($self->mport); } } # # If this is the server child process set port... # if ($self->serverpid == 0) { $self->bAmServer('1'); $self->port($self->sport); } # # If this is the parent process, then monitorpid should be > 0 # if ($self->monitorpid > 0) { open (PID, ">$Mtmpdir/.sourcemonitor.id"); print PID "monitor: ".$self->monitorpid."\n"; close(PID); } # # If this is the parent process, then serverpid should be > 0 # if ($self->serverpid > 0) { open (PID, ">$Mtmpdir/.buildserver.id"); print PID "server: ".$self->serverpid."\n"; close(PID); } if ($self->makedaemon) { # Server reaches this if ($self->serverpid && $self->monitorpid) { exit; } } # Startup the server here using new_server from Msg. if ($self->serverpid == 0) { print "***$port starting server daemon***\n"; open STDIN, "$Mnull" ||die "open $!"; open STDOUT, ">$dir/proc/bsdaemon.log" || die "open $!"; open STDERR, ">$dir/proc/bserror.log" || die "open $!"; } if ($self->monitorpid == 0) { print "***$port starting monitor daemon***\n"; open STDIN, "$Mnull" ||die "open $!"; open STDOUT, ">$dir/proc/msdaemon.log" || die "open $!"; open STDERR, ">$dir/proc/mserror.log" || die "open $!"; } } # # Fork a process and return child pid to calling app # sub forkprocess { my $self = shift; my $cmd = shift; my $wait = shift; my $sleep = shift; my @args = @_; my $pid; if ($pid = fork) { ; } elsif (defined $pid) { # $pid is zero here if defined sleep($sleep); exec($cmd, @args); } if ($wait) { my $sqlquery; my @sqlarray; # # Grab infor from config # my $title = $self->jobname; my $jobno = $self->jobno; my $host = $self->hostname; # # Push pid onto process tree table # push @sqlarray, "$host"; push @sqlarray, "$title"; push @sqlarray, "$jobno"; push @sqlarray, "$cmd @args"; push @sqlarray, "$pid"; $self->run_sql_submit("proctree", @sqlarray); # # Wait for process to complete # waitpid($pid, 0); # # Get the return status for the process # $status = $?/256; # # Remove all entries from proctree associated with this pid # $sqlquery = "delete from proctree where binary server=". "\"$host\" and title=\"$title\" ". "and pid=\"$pid\""; $self->run_sql_remove("$sqlquery"); # # Return status # return $status; } else { return $pid; } } # # Use in place of system for prebuild, build, fail and # postbuild processes to track id's of running processes. # Posix good, Win32 bad. # sub osprocesstree { my $self = shift; my $server = shift; my $title = shift; my $jobno = shift; my $cmd = shift; my @args = @_; my $pid; my @parent; my @empty; my @sqlarray; my $sqlquery; my $recs; $sqlquery = "select * from proctree where binary server=\"$server\" ". "and binary title=\"$title\" and jobno=\"$jobno\""; @sqlarray = $self->run_sqlquery($sqlquery, ";"); $recs = @sqlarray; @sqlarray = @empty; if ($pid = fork) { if (!$recs) { @parent = self->getparentinfo($pid); my $ppid = $parent[0]; my $pcmd = $parent[1]; # # Push appropriate values into array # push @sqlarray, $server; push @sqlarray, $title; push @sqlarray, $jobno; push @sqlarray, $pcmd; push @sqlarray, $ppid; # # Add active process to sql table # $self->run_sql_submit("proctree", @sqlarray); @sqlarray = @empty; } # # Push appropriate values into array # push @sqlarray, $server; push @sqlarray, $title; push @sqlarray, $jobno; push @sqlarray, $cmd; push @sqlarray, $pid; # # Add active process to sql table # $self->run_sql_submit("proctree", @sqlarray); # # Wait for process to complete # waitpid($pid, 0); $sqlquery = "delete from proctree where binary server=\"$server\" ". "and binary title=\"$title\" and jobno=\"$jobno\" ". "and pid=\"$pid\""; $self->run_sql_remove($sqlquery); } elsif (defined $pid) { print "executing $cmd @args\n"; exec($cmd, @args); } } # # get parent process info for supplied pid # sub getparentinfo { my $self = shift; my $pid = shift; my $process; my $pstruct; my @return; $process = new Proc::ProcessTable; foreach $pstruct (@{$process->table}) { if ($pstruct->pid =~ /^$pid$/) { push @return, $pstruct->ppid; push @return, $pstruct->cmndline; } } return @return; } # # Get average MAX duration for the job # sub _get_average { my $self = shift; my $server = shift; my $title = shift; my $total = 0; my $t = 0; my $keep; my $sqlquery; my @sqlarray; my $line; $sqlquery = "select keeplevel from configuration where binary ". "title=\"$title\" and binary server=\"$server\""; @sqlarray = $self->run_sql_query($sqlquery, ";", 0); $keep = $sqlarray[0]; $sqlquery = "select start,end from jobs where binary ". "title=\"$title\" and binary server=\"$server\" ". "and status!=\"2\" ". "order by job desc limit $keep"; @sqlarray = $self->run_sql_query($sqlquery, ";", 0); $max = 0; $min = 0; $sum = 0; $rec = @sqlarray; foreach $line (@sqlarray) { ($start, $end) = split(/;/, $line); $elapsed = $end-$start; if (!$min) { $min = $elapsed; } if ($elapsed < $min) { $min = $elapsed; } if ($elapsed > $max) { $max = $elapsed; } $sum += $elapsed; } if ($rec > 0) { if ($rec == 1) { $max *= .1; return $max; } if ($rec == 2) { $total = $sum/2; $t = $total * .1; $total += $t; return $total; } if ($rec > 2) { $rec = $rec - 2; $sum -= $max; $sum -= $min; $total = $sum / $rec; $t = $total * .1; $total += $t; return $total; } } else { return 0; } } sub _get_hosttime { my $self = shift; my $sqlquery = "select unix_timestamp()"; my @sqlarray = $self->run_sql_query($sqlquery, ";"); return $sqlarray[0]; } sub run_sql_submit { my $self = shift; my $table = shift; my @values = @_; my $picture; my $count = 0; my $sqlserver = $config->SQLSERVER; my $sqlid = $config->SQLID; my $dbh; my $sth; my $ret = 1; $dbh = DBI->connect("dbi:mysql:database=builds;host=$sqlserver", "$sqlid", ''); if (defined($dbh)) { while ($count < @values) { $picture .= "?, "; $count++; } $picture =~ s/, $//g; $sth = $dbh->prepare("INSERT INTO $table VALUES($picture)"); $sth->execute(@values); $dbh->disconnect; } else { $ret = 0; } return $ret; } sub run_sql_remove { my $self = shift; my $sqlquery = shift; my $sqlserver = $config->SQLSERVER; my $sqlid = $config->SQLID; my $dbh; my $sth; my $ret = 1; $dbh = DBI->connect("dbi:mysql:database=builds;host=$sqlserver", "$sqlid", ''); if ($sqlquery =~ /\"\"/ && defined($dbh)) { $dbh->disconnect; undef $dbh; } if (defined($dbh)) { $sth = $dbh->prepare($sqlquery); $sth->execute(); $dbh->disconnect; } else { $ret = 0; } return $ret; } sub run_sql_query { my $self = shift; my $sqlquery = shift; my $delimeter = shift; my $direction = shift; my $sqlserver = $config->SQLSERVER; my $sqlid = $config->SQLID; my $dbh; my $sth; my $line; my $record; my @ret; my @row; my $rv; if ($sqlquery =~ /delete from/) { return "cannot delete records from query function"; } $dbh = DBI->connect("dbi:mysql:database=builds;host=$sqlserver", "$sqlid", ''); if ($sqlquery =~ /\"\"/ && defined($dbh)) { $dbh->disconnect; undef $dbh; } if (defined($dbh)) { $sth = $dbh->prepare($sqlquery); $rv = $sth->execute(); # # If this statement does not start with 'update' # if ($sqlquery !~ /^update/ic) { while ( @row = $sth->fetchrow_array) { $record = ""; foreach $line (@row) { chomp $line; $record .= "$line"."$delimeter"; } $record =~ s/$delimeter$//g; if ($direction) { unshift @ret, $record; } else { push @ret, $record; } } } $dbh->disconnect; return @ret; } else { return undef; } } # # Routine for finding free disk space... # sub diskspace { my $self = shift; my $nums; my $volume; my $free; my $line; my $outfs = $config->JOBDIR; my $df = $config->DFCMD; my @diskinfo = `$df $outfs`; foreach $line (@diskinfo) { chomp $line; if ($line =~ /\//) { ($volume, $nums) = split(/ /, $line); $self->volume($volume); $free = $nums * 1024; } } return $free; } # # Routine for displaying system information # sub sysinfo { my $self = shift; my $info = `uname -a`; chomp $info; return $info; } # # Do not edit anything below this line... # # Object constructor... # sub new { my $that = shift; my $class = ref($that) || $that; my $self = { %fields, }; bless $self, $class; return $self; } # # Autoload definitions in this package... # sub AUTOLOAD { my $self = shift; my $type = ref($self) || croak "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; unless (exists $self->{$name}) { croak "Can't access `$name` field in an object of class $type"; } if (@_) { return $self->{$name} = shift; } else { return $self->{$name}; } } 1;