#!/usr/bin/perl

$swaplimit=shift;
if($swaplimit==0) { $swaplimit=200000; }

$older_limit=60000;

$fast=5;
$fast=0;
$slow=90;
$extraslow=180;
$maxextraslow=1200;
$pausecont=$extraslow;
if($fast) { $pause=$fast; }
else { $pause=$slow; }

$notstop_regex="<defunct>|^xz";

$iomin=0.02;
$iomax=0.20;
$MINIDLE=0.65;
$MAXIDLE=0.80;
$MAXIDLETIME=600; # 10 minutes

$continue=$pause;

$lastwait=-1;
$lastsum=-1;
$iowait=0;

sub sigquit {
  my ($sig)=@_;
  print STDERR "Caught a SIG$sig. Save stopped pids and quit.\n";
  unlink("pids.stop.backup");
  rename("pids.stop","pids.stop.backup");
  open(D,">pids.stop");
  foreach $k (keys %stop) {
    if($stop{$k}>0) {
      print D "$k=$stop{$k}\n";
    }
  }
  close(D);
  exit(0);
}

$SIG{TERM}=\&sigquit;
$SIG{QUIT}=\&sigquit;
$SIG{INT}=\&sigquit;
$SIG{HUP}=\&sigquit;
$SIG{TSTP}=\&sigquit;


sub loadpids {
  if(open(D,"pids.stop"))
  {
    while($l=<D>) {
      ($pid,$time)=split("=",$l);
      $time+=0;
      $stop{$pid}=$time;
    }
    close(D);
  }
}

sub sortpid {
  ($resa,undef,$pida)=split(" ",$a);
  ($resb,undef,$pidb)=split(" ",$b);
  if($resa>$older_limit || $resb>$older_limit) { 
    # Normal sort
  }
  elsif($stop{$pida}>0 && $stop{$pidb}>0) {
    return($stop{$pida} <=> $stop{$pidb});
  }
  return($resb <=> $resa);
}
 
sub getcc1plus {
  # cpu <user> <nice> <system> <idle> <iowait> <irq> 
  open(CPU,"/proc/stat");
  (undef,$cpu_user,$cpu_nice,$cpu_sytem,$cpu_idle,$cpu_iowait)=split(' ',<CPU>);
  close(CPU);
  $sum=$cpu_user+$cpu_nice+$cpu_sytem+$cpu_idle+$cpu_iowait;
  if($lastsum<0) {
    $lastsum=$sum;
    $lastwait=$cpu_iowait;
    $lastidle=$cpu_idle;
  }
  elsif(($sum-$lastsum)>15000) {
    $iowait=($cpu_iowait-$lastwait)/($sum-$lastsum);
    $idle=($cpu_idle-$lastidle)/($sum-$lastsum);
    $lastsum=$sum;
    $lastwait=$cpu_iowait;
    $lastidle=$cpu_idle;
  }
  # USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
  # root     12722  0.8  3.8  56452  9644 pts/1    T+   14:07   0:04 /usr/libexec/gcc/armv7hl-slackware-linux-gnueabi/5.3.0/cc1plus -quiet -I 

  # root@opzero:~# ps -eo pid,ppid,stat,pcpu,vsz,rss,size,time,comm | head -2
  # PID  PPID STAT %CPU    VSZ   RSS  SIZE     TIME COMMAND
  #   1     0 Ss    0.2   1848  1296   296 00:00:05 init
  #my @ps=grep(genautomata|genattrtab|/cc1|smokegen|\/as|\/ld/,split("\n",`ps aux`));
  my @ps=grep(/ xz| cc1| gnat1| go1| ecj| ecj1| jc1| java| gij| rust| clang| f951| lto1| d21| makeinfo| genautomata| genattrtab| smokegen| ld|tblgen/,split("\n",`ps -eo pid,ppid,stat,pcpu,vsz,rss,size,time,comm`));
  my @ret=();
  $running=0;
  $stopped=0;
  $genautomata=0;
  $maxres=-1;
  $maxall=-1;
  $resdiff=-1;
  $candidaterun=-1;
  $candidateall=-1;
  $swapstop=0;
  $totres=0;
  %active=();
  foreach(@ps) {
     ($pid,$ppid,$stat,$cpu,$vsz,$res,$size,$time,@cmd)=split;
     $cmd=join(" ",@cmd);
     ($hour,$min,$sec)=split(":",$time);
     $time=$hour*3600+$min*60+$sec;
     if($cmd=~m/genautomata/) { $genautomata=$pid; }
     if($stat=~m/^Z/) {
       # Zombie/defunct, do not count
       next;
     }
     elsif($stat=~m/^T/) {
       if($stop{$pid}==0) { $stop{$pid}=time(); }
       $stopped++;
       $curres=$vsz-$res;
       if($maxres<0 || $res>$maxres) {
	 if($resdiff>0) {
	   $swapstop+=$resdiff;
	 }
	 $maxres=$res;
	 $resdiff=$curres;
	 $candidaterun=$pid;
	 $candidatevsz=$vsz;
       }
       else {
	 $swapstop+=$curres;
       }
     }
     else { 
       $running++;
       $totres+=$res;
     }
     if($maxall<0 || $res>$maxall) {
       $maxall=$res;
       $candidateall=$pid;
     }
     $active{$pid}=1;
     push(@ret,"$res * $pid $stat ($vsz $time) $cmd");
  }
  foreach(keys %stop) {
    if(!$active{$_}) {
      delete($stop{$_});
    }
  }
  @ret=sort sortpid @ret;
  if(0) {
  foreach(@ret) {
    ($res,undef,$pid,$stat,$vsz,$time)=split(" ",$_);
    if($stop{$pid}>0) { $stp=time()-$stop{$pid}; $stp="(stop since $stp)"; } else { $stp=""; }
    print "SP: $_ $stp\n";
  }
  }
  if($maxall<=$older_limit) {
    ($res,undef,$pid,$stat,$vsz,$time)=split(" ",$ret[0]);
    $candidateall=$pid;
  }
  return(@ret);
}

loadpids();

@ps=getcc1plus();
for($i=0;$i<=$#ps;$i++) {
  ($res,undef,$pid,$stat,$vsz,$time)=split(" ",$ps[$i]);
  if($stop{$pid}>0) { $stp=time()-$stop{$pid}; $stp="(stop since $stp)"; } else { $stp=""; }
  print $ps[$i]," $stp\n";
}

$firstprint=1;

$t0cont=time();

while(1) {
  open(F,"/proc/meminfo");
  $total=0;
  $avail=0;
  #MemTotal:         248572 kB
  #MemAvailable:     104560 kB
  while(<F>) {
    if(m/MemAvailable/) {
      (undef,$avail)=split;
    }
    elsif(m/MemTotal/) {
      (undef,$total)=split;
    }
  }
  close(F);
  $maxswap=$total/2;
  if($maxswap>$swaplimit) { $maxswap=$swaplimit; }
  $swapuse=0;
  #Filename				Type		Size	Used	Priority
  #/dev/mmcblk0p3                       partition	1678788	205568	-1
  open(F,"/proc/swaps");
  <F>;
  while(<F>) {
    (undef,undef,undef,$used)=split;
    $swapuse+=$used;
  }
  close(F);
  if($continue>0) {
    @ps=getcc1plus();
    $continue--;
  }
  elsif($recheck<=0) {
    @ps=getcc1plus();
    $recheck=30;
  }
  $recheck--;
  $swapnostop=$swapuse-$swapstop;
  if($firstprint) {
    print scalar(localtime)," - FTOT=$total, AVAIL=$avail, SWAP=$swapuse ($swapnostop) IO=".sprintf("%.1f",$iowait*100).", ID=".sprintf("%.1f",$idle*100)." (run=$running, stop=$stopped) MAXRES=$maxres ($resdiff), CANDIDATE=$candidaterun\n";
    $firstprint=0;
  }
  if($avail>($total/4)) { $juststopped--; }
  elsif($avail<($total/8) && ($juststopped>0 || $stopped>0) && $juststopped<$slow) { $juststopped=$slow; }
  if($running==1 && $stopped>0 && $avail<($total/16) && ($idle<$MAXIDLE))
  {
    $continue=$pausecont;
    $norun2=$extraslow;
  }
  if($running>0 && (($totres)/$running)>($avail-($total/8)) && ($idle<$MAXIDLE))
  {
    $continue=$pausecont;
  }
  if($idle<$MAXIDLE) {
    $idletime=time();
  }
  if($norun2>0) { $norun2--; }
  $dbg++;
  if($dbg>=10 && ($continue>0 || $norun2>0 || $juststopped>0)) {
    print scalar(localtime)," - AVAIL=$avail, TOTRES=$totres, ID=".sprintf("%.1f",$idle*100)." (run=$running, stop=$stopped, cont=$continue, nor2=$norun2, js=$juststopped)\n";
    $dbg=0;
  }
  if( $running>1 && ($idle<$MINIDLE || $iowait>$iomax) && ( 
        ($norun2>0) ||
        ( $stopped<($running-1) && ($swapuse>($avail)) && ($avail<($total/2)) ) ||
        ($genautomata) || 
        ($avail<($total/8)) ||
        ($avail<($total/4) && $juststopped>0 ) 
        ) ) {
    print scalar(localtime)," - STOT=$total, AVAIL=$avail, SWAP=$swapuse, ID=".sprintf("%.1f",$idle*100)." (run=$running, stop=$stopped), ID=".sprintf("%.1f",$idle*100)."\n";
    @ps=getcc1plus();
    if($avail<($total/8)) { $juststopped=$pausecont; }
    else { $juststopped+=20; }
    $firstprint=1;
    $first=0;
    if($norun2<=10) { $continue+=10; }
    elsif($continue<$pause) { $continue=$pause; }
    $t0cont=time();
    for($i=0;$i<=$#ps;$i++) {
       ($res,undef,$pid,$stat,$vsz,$time)=split(" ",$ps[$i]);
       if($stop{$pid}>0) { $stp=time()-$stop{$pid}; $stp="(stop since $stp)"; } else { $stp=""; }
       if(!($stat=~m/^T/)) {
         if(($genautomata && $genautomata!=$pid) || (!$genautomata && $first>0)) {
           system("kill -STOP $pid");
	   if($stop{$pid}==0) { $stop{$pid}=time(); }
           print "## Stopping $ps[$i]... $stp\n";
         }
	 else {
           print "## Leave continuing $ps[$i]... $stp\n";
	 }
         $first++;
       }
       else {
	 print "## Already stopped $ps[$i]. $stp\n";
       }
    }
    if($first>0) {
      #sleep(20);
    }
    @ps=getcc1plus();
  }
  elsif($continue==0 && (time()-$idletime)>$MAXIDLETIME && $stopped>0) {
      # Run the older process
      $oldest=0;
      print scalar(localtime)," - CTOT=$total, AVAIL=$avail, SWAP=$swapuse, ID=".sprintf("%.1f",$idle*100)." (run=$running, stop=$stopped)\n";
      @ps=getcc1plus();
      $firstprint=1;
      $now=time();
      $ci=0;
      for($i=0;$i<=$#ps;$i++) {
	 ($res,undef,$pid,$stat,$vsz,$time,$nice)=split(" ",$ps[$i]);
	 if($stat=~m/^T/ && (!$genautomata || $genautomata==$pid)) {
	   if($stop{$pid}==0) { $stop{$pid}=$now; }
	   if(($now-$stop{$pid})>$oldest) {
	     $copid=$pid;
	     $ci=$i;
	     $oldest=($now-$stop{$pid});
	   }
	 }
      }
      system("kill -CONT $copid");
      print "## Too much idle, continuing $ps[$ci]...\n";
      $continue=$pausecont;
      sleep(10);
      @ps=getcc1plus();
  }
  elsif($continue==0 && $norun2<=0 && $stopped && (!$genautomata || $running==0) &&  ( ($swapuse<($avail) && ($avail>($total/4) || ($avail>($total/8) && $juststopped<=0) ) ) || ($avail>($total/2)) || ($running==0) )) {
    # Check if the new process will fit in memory after full run
    #if($running==0 || (($total-$avail)/$running)<($avail-$total/8))
    if($running==0 || (($totres)/$running)<($avail-$total/8))
    {
      print scalar(localtime)," - CTOT=$total, AVAIL=$avail, SWAP=$swapuse, ID=".sprintf("%.1f",$idle*100)." (run=$running, stop=$stopped)\n";
      @ps=getcc1plus();
      $firstprint=1;
      for($i=0;$i<=$#ps;$i++) {
	 ($res,undef,$pid,$stat,$vsz,$time)=split(" ",$ps[$i]);
         if($stop{$pid}>0) { $stp=time()-$stop{$pid}; $stp="(stop since $stp)"; } else { $stp=""; }
	 if($stat=~m/^T/ && (!$genautomata || $genautomata==$pid)) {
	   system("kill -CONT $pid");
	   #delete($stop{$pid});
	   $t0cont=time();
	   print "## Continuing $ps[$i]... $stp\n";
	   $continue=$pausecont;
	   @ps=getcc1plus();
	   last;
	 }
      }
    }
  }
  elsif($continue==0 && $norun2<=0 && $stopped>0 && (!$genautomata || $running==0) && $candidaterun>0 && ($resdiff*1.5)<$avail && $candidatevsz<$avail && $avail>($total/8) && $swapnostop<$avail && ($avail>($total/4) || $juststopped<=0)) {
    if(0) {
    # First of all, stop running processes
    for($i=0;$i<=$#ps;$i++) {
       ($res,undef,$pid,$stat,$vsz,$time)=split(" ",$ps[$i]);
       if(!($stat=~m/^T/)) {
         system("kill -STOP $pid");
         print "## Stopping $ps[$i] to allow more res running...\n";
       }
    }
    }
    # The run the candidate
    for($i=0;$i<=$#ps;$i++) {
       ($res,undef,$pid,$stat,$vsz,$time)=split(" ",$ps[$i]);
       if($stop{$pid}>0) { $stp=time()-$stop{$pid}; $stp="(stop since $stp)"; } else { $stp=""; }
       if($pid==$candidaterun) {
         system("kill -CONT $pid");
	 #delete($stop{$pid});
	 $t0cont=time();
         print "## Fit in memory, continuing $ps[$i]... $stp\n";
	 break;
       }
    }
    $continue=$pausecont;
    sleep(1);
    @ps=getcc1plus();
  }
  elsif($stopped>0) {
    if($running==0) {
      if($pauserun<=0) {
        $running=1;
      }
      else {
        $pauserun--;
      }
    }
    if($running==1) {
      # First of all, stop running processes
      for($i=0;$i<=$#ps;$i++) {
	 ($res,undef,$pid,$stat,$vsz,$time)=split(" ",$ps[$i]);
	 if(!($stat=~m/^T/) && $pid!=$candidateall) {
	   system("kill -STOP $pid");
	   if($stop{$pid}==0) { $stop{$pid}=time(); }
	   print "## ".scalar(localtime)." - Stopping $ps[$i] to allow best candidate to run...\n";
	 }
      }
      # Recalculate best-candidate:
      @ps=getcc1plus();
      # The run the candidate
      for($i=0;$i<=$#ps;$i++) {
	 ($res,undef,$pid,$stat,$vsz,$time)=split(" ",$ps[$i]);
         if($stop{$pid}>0) { $stp=time()-$stop{$pid}; $stp="(stop since $stp)"; } else { $stp=""; }
	 if(($stat=~m/^T/) && $pid==$candidateall) {
	   system("kill -CONT $pid");
	   #delete($stop{$pid});
	   print "## ".scalar(localtime)." - Continuing the best candidate $ps[$i] ($continue)... $stp\n";
           if($norun2<=0 && $continue<=10) { $continue+=10; }
           $norun2+=10;
	   break;
	 }
      }
      #$continue=$pause;
      $pauserun=5;
    }
  }
  if((time()-$t0cont)>($pausecont+10)) {
    $t0cont=time();
    if($continue<=0 || ($idle>$MAXIDLE && $iowait<$iomax)) {
      if($pausecont>$extraslow) {
	 $pausecont-=20;
      }
    }
    else {
      if($pausecont<$maxextraslow && $running<=$stopped) {
	 $pausecont+=20;
      }
    }
  }
  sleep(1);
}
