#!/usr/bin/perl

$swaplimit=shift;
if($swaplimit==0) { $swaplimit=200000; }
$midfactor=64;
$hifactor=$midfactor*2;
$lowfactor=$hifactor*2;

$MINRUN=15;
$maxrun=$MAXRUN=20;
$mintime=2;
$minnice=0;
#$MINRUN=8;
#$maxrun=$MAXRUN=12;

$fast=5;
$fast=0;
$slow=15;
$extraslow=30;
$pausecont=$extraslow;
if($fast) { $pause=$fast; }
else { $pause=$slow; }

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

$iomin=0.02;
$iomax=0.20;
$MINIDLE=0.005;
$MAXIDLE=0.1;

$continue=$pause;

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

sub sigquit {
  my ($sig)=@_;
  print STDERR "Caught a SIG$sig. Save stopped pids and quit.\n";
  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 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| makeinfo| genautomata| genattrtab| smokegen| ld/,split("\n",`ps -eo pid,ppid,stat,pcpu,vsz,rss,size,times,nice,comm`));
  my @ret=();
  $running=0;
  $sleeping=0;
  $stopped=0;
  $genautomata=0;
  $maxres=-1;
  $maxall=-1;
  $resdiff=-1;
  $candidaterun=-1;
  $candidateall=-1;
  $swapstop=0;
  $totres=0;
  foreach($i=0;$i<=$#ps;$i++) {
    #($pid,$ppid,$stat,$cpu,$vsz,$res,$size,$time,$nice,@cmd)=split(' ',$ps[$i]);
    #($hour,$min,$sec)=split(":",$time);
    #$time=$hour*3600+$min*60+$sec;
    #if($ps[$i]=~m/\.SlackBuild|ldconfig/ || ($time<$mintime && !($stat=~m/^T/)))
    if($ps[$i]=~m/\.SlackBuild|ldconfig/)
    {
      # Remove any SlackBuild script or defunct process
      splice(@ps,$i,1);
      $i--;
    }
  }
  %stopped=();
  foreach(@ps) {
     ($pid,$ppid,$stat,$cpu,$vsz,$res,$size,$time,$nice,@cmd)=split;
     $cmd=join(" ",@cmd);
     #($hour,$min,$sec)=split(":",$time);
     #$time=$hour*3600+$min*60+$sec;
     #if($cmd=~m/genautomata/) { $genautomata=$pid; } # Avoid detecting genautomata on powerful host
     if($stat=~m/^Z/) {
       # Zombie/defunct, do not count
       next;
     }
     elsif($stat=~m/^T/) {
       $stopped{$pid}=1;
       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 { 
       if($time>0) {
         $running++;
       }
       else {
         $sleeping++;
       }
       $totres+=$res;
     }
     if($maxall<0 || $res>$maxall) {
       $maxall=$res;
       $candidateall=$pid;
     }
     push(@ret,"$res * $pid $stat ($vsz $time $nice) $cmd");
  }
  $lastrun=$lastrun*0.9+$running*0.1;
  foreach(keys %stop) {
    if(!$stopped{$_}) {
      delete($stop{$_});
    }
  }
  return(sort { $b <=> $a } @ret);
}

loadpids();

@ps=getcc1plus();
for($i=0;$i<=$#ps;$i++) {
  print $ps[$i],"\n";
}

$firstprint=1;

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
  do("./conf.dat");
  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 || 1) {
    @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, slp=$sleeping) MAXRES=$maxres ($resdiff), CANDIDATE=$candidaterun\n";
    $firstprint=0;
  }
  elsif($avail>($total/$hifactor)) { $juststopped--; }
  if($running==1 && $stopped>0 && $avail<($total/$lowfactor))
  {
    $continue=$extraslow;
    $norun2=$extraslow;
  }
  if($running>0 && (($totres)/$running)>($avail-($total/$hifactor)))
  {
    $continue=$extraslow;
  }
  if($norun2>0) { $norun2--; }
  $dbg++;
  if($dbg>=10 && ($continue>0 || $norun2>0 || $juststopped>0 || $iowait>$iomax || ($iowait>=$iomin && $stopped>0) || ($lastrun>=$MAXRUN) || ($running<$MINRUN && $stopped>0) || 1 ))
  {
    print scalar(localtime)," - AVAIL=$avail, TOTRES=$totres, IO=".sprintf("%.1f",$iowait*100).", ID=".sprintf("%.1f",$idle*100)." (run=$running, stop=$stopped, slp=$sleeping, cont=$continue, nor2=$norun2, js=$juststopped, ls=".sprintf("%.2f",$lastrun).")\n";
    $dbg=0;
  }
  if( $running>1 && (
        ($norun2>0 && 0) ||
        ( $stopped<($running-1) && ((($swapuse/2)>($avail)) || ($avail<($total/$lowfactor))) ) ||
        ($genautomata && 0) || 
        ($avail<($total/$hifactor)) ||
        ($avail<($total/$midfactor) && $juststopped>0 ) ||
	($iowait>$iomax && $avail<($total/2)) 
        ) ) {
    print scalar(localtime)," - STOT=$total, AVAIL=$avail, SWAP=$swapuse, IO=".sprintf("%.1f",$iowait*100).", ID=".sprintf("%.1f",$idle*100)." (run=$running, stop=$stopped, slp=$sleeping, js=$juststopped)\n";
    @ps=getcc1plus();
    if($run<$stopped) {
      #if($avail<($total/$hifactor)) { $juststopped=$extraslow; }
      #else { $juststopped+=5; }
    }
    else { $juststopped=0; }
    $firstprint=1;
    $first=0;
    if($norun2<=10) { $continue+=10; }
    elsif($continue<$pause) { $continue=$pause; }
    for($i=$#ps;$i>=0;$i--) {
       ($res,undef,$pid,$stat,$vsz,$time,$nice,@cmd)=split(" ",$ps[$i]);
       $cmd=$cmd[0];
       if(!($stat=~m/^T/) && ($time>$mintime) && ($nice>$minnice)) {
         if($cmd=~m/cc1plus|clang|rust/) {
           system("kill -STOP $pid");
	   $stop{$pid}=time();
           $first++;
           print "A# Stopping $ps[$i]...\n";
           sleep(10);
           last;
         }
	 else {
           #print "## Leave continuing $ps[$i]...\n";
	 }
       }
       else {
	 #print "## Already stopped $ps[$i].\n";
       }
    }
    if(!$first) {
      for($i=$#ps;$i>=0;$i--) {
	 ($res,undef,$pid,$stat,$vsz,$time,$nice,@cmd)=split(" ",$ps[$i]);
	 $cmd=$cmd[0];
	 if(!($stat=~m/^T/) && ($time>0)) {
	   if(($genautomata && $genautomata!=$pid) || (!$genautomata && $first>=0)) {
	     system("kill -STOP $pid");
	     $stop{$pid}=time();
	     $first++;
	     print "B# Stopping $ps[$i]...\n";
	     sleep(10);
	     last;
	   }
	   else {
	     print "## Leave continuing $ps[$i]...\n";
	   }
	 }
	 else {
	   #print "## Already stopped $ps[$i].\n";
	 }
      }
    }
    if($first>0) {
      #sleep(20);
    }
    @ps=getcc1plus();
  }
  elsif(((($running<$MINRUN) && ($lastrun<$MINRUN)) || ($idle>=$MAXIDLE && $iowait<$iomax)) &&
	 ($continue==0 && $norun2<=0 && $stopped && ($iowait<$iomin) && (!$genautomata || $running==0) &&  ( ($swapuse<($avail) && ($avail>($total/$midfactor) || ($avail>($total/$hifactor) && $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/$hifactor))
    if(($running==0 || (($totres)/$running)<($avail-$total/$hifactor)))
    {
      print scalar(localtime)," - CTOT=$total, AVAIL=$avail, SWAP=$swapuse, IO=".sprintf("%.1f",$iowait*100).", ID=".sprintf("%.1f",$idle*100)." (run=$running, stop=$stopped, slp=$sleeping, lastrun=$lastrun)\n";
      @ps=getcc1plus();
      $firstprint=1;
      if((($running<$MINRUN) && ($lastrun<$MINRUN))|| ($idle>=$MAXIDLE && $iowait<$iomax))
      {
	# Changing priority based on stopped time
	$ci=-1;
	$now=time();
	$maxval=2000000000;
	$co=-1;
	$copid=undef;
	$oldest=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}=time(); }
	     $val=$i+$nice*2-($now-$stop{$pid})/1000;
	     if($val<$maxval) {
	       $ci=$i;
	       $ni=$nice;
	       $maxval=$val;
	     }
	     if(($now-$stop{$pid})>$oldest) {
	       $co=$i;
	       $copid=$pid;
	       $no=$nice;
	       $oldest=($now-$stop{$pid});
	       if($oldest>$maxold && $oldest<1000000000) { $maxold=$oldest; }
	     }
	   }
	}
	if($ci>=0) {
	   $i=$ci;
	   ($res,undef,$pid,$stat,$vsz,$time,$nice)=split(" ",$ps[$i]);
	   system("kill -CONT $pid");
	   $val=$now-$stop{$pid};
	   print "## Continuing $ps[$i] ($pid=>$maxval [$i, $val/$ni], oldest=$copid [$co, $oldest/$no] max=$maxold)...\n";
	   delete($stop{$pid});
	   $continue=$pausecont;
	   @ps=getcc1plus();
	}
      }
      else {
	for($i=0;$i<=$#ps;$i++) {
	   ($res,undef,$pid,$stat,$vsz,$time,$nice)=split(" ",$ps[$i]);
	   if($stat=~m/^T/ && (!$genautomata || $genautomata==$pid)) {
	     system("kill -CONT $pid");
	     print "## Continuing $ps[$i]...\n";
	     delete($stop{$pid});
	     $continue=$pausecont;
	     @ps=getcc1plus();
	     last;
	   }
	}
      }
    }
  }
  elsif(((($running<$MINRUN) && ($lastrun<$MINRUN))|| ($idle>=$MAXIDLE && $iowait<$iomax)) &&
	($continue==0 && $norun2<=0 && $stopped>0 && ($iowait<$iomin) && (!$genautomata || $running==0) && $candidaterun>0 && ($resdiff*1.5)<($avail-$swapuse/2) && $candidatevsz<$avail && $avail>($total/$hifactor) && $swapnostop<$avail && ($avail>($total/$midfactor) || $juststopped<=0))
       ) {
    if(0) {
    # First of all, stop running processes
    for($i=0;$i<=$#ps;$i++) {
       ($res,undef,$pid,$stat,$vsz,$time,$nice)=split(" ",$ps[$i]);
       if(!($stat=~m/^T/)) {
         system("kill -STOP $pid");
	 $stop{$pid}=time();
         print "C# 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,$nice)=split(" ",$ps[$i]);
       if($pid==$candidaterun) {
         system("kill -CONT $pid");
         print "## Fit in memory, continuing $ps[$i]...\n";
	 break;
       }
    }
    $continue=$pausecont;
    sleep(1);
    @ps=getcc1plus();
  }
  elsif($stopped>0 && (($running<$MINRUN && $lastrun<$MINRUN)|| ($idle>=$MAXIDLE && $iowait<$iomax))) {
    if($running==0) {
      if($pauserun<=0) {
        $running=1;
      }
      else {
        $pauserun--;
      }
    }
    if($running>0 && ($running<$MINRUN || ($idle>=$MAXIDLE && $iowait<$iomax))) {
      # First of all, stop running processes
      if(0) {
      for($i=0;$i<=$#ps;$i++) {
	 ($res,undef,$pid,$stat,$vsz,$time,$nice)=split(" ",$ps[$i]);
	 if(!($stat=~m/^T/) && $pid!=$candidateall) {
	   system("kill -STOP $pid");
	   $stop{$pid}=time();
	   print "D# ".scalar(localtime)." - Stopping $ps[$i] to allow best candidate to run...\n";
	 }
      }
      }
      # The run the candidate
      for($i=0;$i<=$#ps;$i++) {
	 ($res,undef,$pid,$stat,$vsz,$time,$nice)=split(" ",$ps[$i]);
	 if(($stat=~m/^T/) && $pid==$candidateall) {
	   system("kill -CONT $pid");
	   print "## ".scalar(localtime)." - Continuing the best candidate $ps[$i] ($continue)...\n";
	   delete($stop{$pid});
           if($norun2<=0 && $continue<=10) { $continue+=10; }
           $norun2+=10;
	   break;
	 }
      }
      #$continue=$pause;
      $pauserun=5;
    }
  }
  elsif($lastrun>$maxrun && $running>$MINRUN && $idle<$MINIDLE)
  {
    print scalar(localtime)," - STOT=$total, AVAIL=$avail, SWAP=$swapuse, IO=".sprintf("%.1f",$iowait*100).", ID=".sprintf("%.1f",$idle*100)." (run=$running, stop=$stopped, slp=$sleeping, lastrun=$lastrun)\n";
    $maxrun=$MINRUN;
    for($i=$#ps;$i>=0;$i--) {
       ($res,undef,$pid,$stat,$vsz,$time,$nice,@cmd)=split(" ",$ps[$i]);
       $cmd=join(" ",@cmd);
       if(!($stat=~m/^T/) && ($time>$mintime) && ($nice>$minnice)) {
         #if(1 || $cmd=~m/cc1plus|clang|rust/)
         if(! ($cmd=~m/$notstop_regex/))
	 {
           system("kill -STOP $pid");
	   $stop{$pid}=time();
           $first++;
           print "R# Stopping $ps[$i]...\n";
           sleep(10);
           last;
         }
	 else {
           #print "## Leave continuing $ps[$i]...\n";
	 }
       }
       else {
	 #print "## Already stopped $ps[$i].\n";
       }
    }
  }
  else {
    $maxrun=$MAXRUN;
  }
  sleep(1);
}
