#!/usr/local/bin/perl

# process-cleaner.pl
#   A rude attempt to purge stale processes.
#     The script assumes that a process that persists without a change
#     in CPU consumption is a candidate and attempts to kill it.
#
#   Expected inputs (command line):
#     a list of process names to consider
#
#   Other data files:
#     a list of processes from our last invocation
#
#   05/15/19igorl@ayradyss.org99 Version 1.1
#   This script is free, public domain, no strings attached
#   Igor S. Livshits <mailto:igorl@ayradyss.org>


# Define some global variables
#
$true= (1==1);
$false= (1==0);
$openToWrite= ">";
$newLine= "\n";
$commentDelimiter= "#";
$comma= ",";
$space= " ";

$tempDirectory= "/var/tmp/";
$candidatesFileName= $tempDirectory . "candidates";
$oldCandidatesFileName= $tempDirectory . "old_candidates";

$ps= "ps -ea -o pid,user,time,stime,comm";


# Read in acceptable processes
#
@processFilters= @ARGV;

# Preserve previous candidates
#
if (-T $candidatesFileName)
{				# previous files exists, rename it
  $result= rename($candidatesFileName, $oldCandidatesFileName) or
    die "Failed to preserve previous candidates list <$oldCandidatesFileName>";
}


# Check current processes and dump candidates to a file
#
@candidates= ();		# clear our list of candidates
open(PROCESSES, "$ps |") or die "Cannot read current processes";

while ($candidate= <PROCESSES>)
{
  foreach $filter (@processFilters)
  {				# add the process if it matches a filter
    if ($candidate=~ /$filter$/)
    {
      push(@candidates, $candidate);
      last;			# skip to the next candidate
    }
  }
}
close(PROCESSES);

@candidates= sort(@candidates);

# Save our list of current candidates
#
open(CANDIDATES, $openToWrite.$candidatesFileName) or die "Cannot save data.";
print(CANDIDATES @candidates);
close(CANDIDATES);

# Find persistent candidates and mark them for murder
#
exit unless (-T $oldCandidatesFileName); # skip if we lack comparison data
exit unless @candidates;

$index= 0;			# reset our array index
@victims= ();			# clear our victim list
open(OLD, $oldCandidatesFileName) or die "Cannot access previous candidates";

CandidateCheck:
while (<OLD>)
{				# both are sorted; so, check in parallel
  next				# fast forward past candidates
    if ($_ lt @candidates[$index]);

  while ($_ gt @candidates[$index])
  {				# fast forward current candidates
    last CandidateCheck		# reached the end of current candidates 
      if (++$index == @candidates); 
  }
  
  if ($_ eq @candidates[$index])
  {				# found a match
    push(@victims, $_);
  }				
}

close(OLD);


# Extract process IDs from the process report
#
@victimIDs= ();
foreach $victim (@victims)
{
  $victimID= $victim;		# copy to preserve orignal formatting
  while ($victimID =~ s/^\s+//) {}; # kill leading white spaces
  ($victimID)= split($space, $victimID);
  push(@victimIDs, $victimID);
};


# Notify operators while killing victims
#
if (@victims)
{				# make sure we have at least one
  print "Victim list:", $newLine, @victims, $newLine;
  $result= kill 'TERM', @victimIDs;

  if ($result == 1)
  {
    print "One process was successfully signaled.$newLine";
  }
  else
  {
    print "$result processes were successfully signaled.$newLine";
  }
}


# Terminate gracefully
#
exit;