unit uoddface_cluster;

interface

uses
  Classes, SysUtils,
  Math,
  urosx,
  xmatdefs,
  uauxiliares,
  process,
  uConstantesSimSEE,
  uDataSetGenerico;

const
  // constantes modificables por el usuario
  verboso = 0; // poner a 0 para evitar salidas que no sean de error.
  ntares_por_vez = 10; // cantidad de tareas que intenta lazar cada vez.
  pasar_idEjecutor = True;
  // poner a TRUE para pasarle a la aplicaci�n un idEjecutor �nico.
  pausa_segs = 1;
  // pausa en segundo entre lanzamientos. Para darle tiempo a cada demonio a copiar los datos comunes a su /tmp sin interferirse.
  margenEncolados = -1;
  // Si es positivo tiene que haber  margenEncolados o menos Encolados para lanzar en la cola
  // si es negativo, tiene que haber (-margenEncolados) o menos Encolados_PROPIOS para lanzar (no importa los ajenos)
  TOTAL_NUCLEOS_ACTIVOS = 1120;


  qstat_cmd = '/opt/torque/bin/qstat'; //  '/opt/pbs/bin/qstat';
  qsub_cmd = '/opt/torque/bin/qsub'; // '/opt/pbs/bin/qsub';
  showq_cmd = '/opt/maui/bin/showq';
  crontab_cmd = '/usr/bin/crontab';



type
  { TCola }
  TCola = class
    nombre: string;
    nnucleos: integer;
    maxtareas: integer;
    maxhoras: integer;
    constructor Create(nombre: string; nnucleos, maxhoras, maxtareas: integer);
  end;


  { TRBT_OddFace_Cluster }

  TRBT_OddFace_Cluster = class
    usuario: string;
    home: string;
    yo: string;
    offset_ejecutor: integer;
    aplicacion: string;
    orden: string;
    colas: TList;
    ncolas: integer;
    s: ansistring;
    sql: string;
    db: TDBrosxCon;
    ds: TResultadoQuery;

    constructor Create;

    // ejecuta el comando y retorna la última linea. En mls retorna un stringlist con todas las lineas de salida.
    function exec(apl: string; params: array of string;
      var mls: TStringList): string; overload;
    function exec(comando: string; params: array of string): string; overload;


    // Recibe un array de constantes y retorna un TDAofString con las mismas
    function daofs(a: array of string): TDAofString;

    // writeln si vervoso > 1
    procedure wrln(s: string);

    // writeln independientemente del valor de vervoso.
    procedure wrln_err(s: string);

    // verifica si el programa oddface_repetidoro se está ejecutando y
    // si no lo está lo ejecuta. Este procedimiento se utiliza para mantener
    // en ejecución el repetidor.
    procedure test_repetidor;

(*
  nexval( $key, $reserva = 1) , retorna un identificador único global
  el parámetro $reserva indica la cantidad de identificadores consecutivos
  reservados (es decir asigandos y que no volveran ser asignados.
 *)
    function nextval(key: string; reserva: integer = 1): integer;


    procedure remover_crontab;
    procedure instalar_crontab;

    // retorna la cantidad de trabajos esperando para ser despachados
    function getEsperando(var cnt_total_todos, cnt_total_propios,
      cnt_esperando_propios: integer; username, xcola: string): integer;

    // emulador de substr de php
    function substr(const s: string; k_base_0: integer; cnt: integer = -1): string;

    procedure Ayuda;
    procedure lanzar(ntareas: integer; xcola: TCola);
    procedure runLanzarParar(xcola: TCola);
    procedure parar(ntareas: integer);

    // retorna TRUE si la orden es válida.
    function ProcesarOrden: boolean;

    procedure orden_run;
    procedure order_parar_tareas;

    procedure dispatch_cmd;

  private
    procedure orden_lst;
    procedure orden_cancelartrabajos(username, xcola, wk_nidProblema: string);
  end;


implementation

type
  TDAofInteger = array of integer;



function MedirCols(r: string; ColsCap: array of string): TDAOfInteger;
var
  k, i: integer;
  res: TDAOfInteger;
begin
  setlength(res, length(ColsCap));
  for k := 0 to high(ColsCap) do
  begin
    i := pos(ColsCap[k], r);
    if i = 0 then
      raise Exception.Create('ColsCap no encontrada: ' + ColsCap[k]);
    res[k] := i + length(ColsCap[k]);
  end;
  Result := res;
end;

function getColVal(r: string; icols: TDAofInteger; kCol: integer): string;
var
  res: string;
begin
  if kCol = 0 then
    res := copy(r, 1, icols[0] - 1)
  else
    res := copy(r, icols[kCol - 1], icols[kCol] - icols[kCol - 1]);

  Result := trim(res);
end;

{ TCola }

constructor TCola.Create(nombre: string; nnucleos, maxhoras, maxtareas: integer);
begin
  self.nombre := nombre;
  self.nnucleos := nnucleos;
  self.maxhoras := maxhoras;
  self.maxtareas := maxtareas;
end;


constructor TRBT_OddFace_Cluster.Create;
begin
  usuario := GetEnvironmentVariable('USER');
  home := GetEnvironmentVariable('HOME');
  yo := ParamStr(0);

  offset_ejecutor := 0;
  if (usuario = 'simsee') then
    offset_ejecutor := 1000; // para diferenciarlo del usuario rchaer

  aplicacion := '';

  // Para tareas de 1 core usa la cola 'serial'.
  // Para tareas de 2 a 8 cores 'small_jobs'
  // $colas = array( 'quick_jobs', 'publica' );
  // $colas = array( 'quick_jobs','medium_jobs', 'small_jobs', 'serial' );
  colas := TList.Create;
  {$IFDEF CLUSTER_FING}
  colas.add(TCola.Create('serial', 560, 12, 100));
  {$ELSE}
  (**
* Partición normal
Máxima cantidad de recursos por usuario: 160 cores y 512 GB de RAM
Máxima cantidad de trabajos: 60
Tiempo máximo de ejecución: 5 días

* Partición besteffort
Máxima cantidad de recursos por usuario: 400 cores y 1 TB de RAM
Máxima cantidad de trabajos: 400
Tiempo máximo de ejecución: 5 días
  **)

  colas.add(TCola.Create('normal', 560, 12, 60));  // maxhoras = 10 * 24
  colas.add(TCola.Create('besteffort', 1120, 12, 400));
  {$ENDIF}
  ncolas := colas.Count;

  if (ParamCount >= 1) then
    orden := ParamStr(1)
  else
    orden := '?';

  sql := 'SELECT now()';
//  db := TDBrosxCon.Create('', 'https://iie.fing.edu.uy', '443', '/~rchaer/ros/rosx.php');
  db := TDBrosxCon.Create('179.27.96.77', '', '80', '/oddface/rosx.php');


  ds := DB.sql_query(sql);
  if (ds = nil) or (ds.nrows = 0) then
  begin
    ds.Free;
    DB.Free;
    writeln('Error de conectividad con la DB remota ');
    exit;
  end;
  //  writeln('La hora del DB remoto es: ', ds.fields[0].AsString);
end;

function TRBT_OddFace_Cluster.exec(apl: string; params: array of string;
  var mls: TStringList): string;
var
  s: ansistring;
begin
  if RunCommandIndir('', apl, params, s, [poWaitOnExit]) then
  begin
    mls := TStringList.Create;
    mls.LineBreak := #10;
    mls.Text := s;
    if mls.Count > 0 then
      Result := mls[mls.Count - 1]
    else
      Result := '';
  end
  else
    raise Exception.Create('Error TRBT_OddFace_Clustger.exec, comando: ' + apl);
end;

(*
function TRBT_OddFace_Cluster.exec(comando: string; var mls: TStringList): string;
var
  s: ansistring;
begin
  if RunCommandIndir('', '/bin/bash', ['-c', comando], s, [poWaitOnExit] ) then
  begin
    mls := TStringList.Create;
    mls.LineBreak := #10;
    mls.Text := s;
    if mls.Count > 0 then
      Result := mls[mls.Count - 1]
    else
      Result := '';
  end
  else
    raise Exception.Create('Error TRBT_OddFace_Clustger.exec, comando: ' + Comando);
end;
  *)

function TRBT_OddFace_Cluster.exec(comando: string; params: array of string): string;
var
  mls: TStringList;
begin
  Result := exec(comando, params, mls);
  mls.Free;
end;

function TRBT_OddFace_Cluster.daofs(a: array of string): TDAofString;
var
  res: TDAOfString;
  k: integer;
begin
  setlength(res, length(a));
  for k := 0 to high(res) do
    res[k] := a[k];
  Result := res;
end;


procedure TRBT_OddFace_Cluster.wrln(s: string);
begin
  if (verboso > 0) then
    writeln(s);
end;


procedure TRBT_OddFace_Cluster.wrln_err(s: string);
begin
  writeln(s);
end;


(*
  nexval( $key, $reserva = 1) , retorna un identificador �nico global
  el par�metro $reserva indica la cantidad de identificadores consecutivos
  reservados (es decir asigandos y que no volveran ser asignados.
 *)
function TRBT_OddFace_Cluster.nextval(key: string; reserva: integer = 1): integer;
var
  f: textfile;
  res, resx: integer;
  archi: string;
begin
  archi := home + '/nextval_' + key;
  if (fileexists(archi)) then
  begin
    assignfile(f, archi);
    reset(f);
    readln(f, res);
    closefile(f);
  end
  else
    res := 1;

  resx := res + reserva;

  assignfile(f, archi);
  rewrite(f);
  writeln(f, resx);
  closefile(f);
  Result := res + offset_ejecutor;
end;




procedure TRBT_OddFace_Cluster.test_repetidor;
var
  s: ansistring;
  m: TStringList;
begin
    (*
UID        PID  PPID  C STIME TTY          TIME CMD
rchaer   24106     1  0 13:37 ?        00:00:00 /home/rchaer/simsee/fpcbin/oddface_repetidor
  *)
  //   exec('ps -f -C oddface_repetidor', $m);
  RunCommand('/bin/bash', ['-c', 'ps -f -C oddface_repetidor'], s);

  m := TStringList.Create;
  m.Text := s; // se supone que esto la parsea
  if (m.Count <= 1) then
  begin
    writeln('relanzando repetidor...');
    LanzarApl(home + '/SimSEE/bin/oddface_repetidor', []);
  end;
end;



procedure TRBT_OddFace_Cluster.remover_crontab;
var
  tmp: string;
  m: TStringList;
  ul: string;
  fp: textfile;
  n: integer;
  k: integer;
  cnt: integer;
  r: string;
begin
  tmp := home + '/crontab.tmp';
  ul := exec(crontab_cmd, ['-l'], m);

  if (pos('no crontab', ul) = 0) then
  begin
    assignfile(fp, tmp);
    rewrite(fp);
    n := m.Count;
    cnt := 0;
    for k := 0 to n - 1 do
    begin
      r := m[k];
      if (pos(yo, r) = 0) then
      begin
        writeln(fp, r);
        Inc(cnt);
      end;
    end;
    closefile(fp);
    exec(crontab_cmd, ['-r']);
    if (cnt > 0) then
      exec(crontab_cmd, [tmp]);
    deletefile(tmp);
  end;
end;



procedure TRBT_OddFace_Cluster.instalar_crontab;

var
  tmp: string;
  ul: string;
  m: TStringList;
  fp: textfile;
  n: integer;
  k: integer;
  r: string;
begin
  tmp := home + '/crontab.tmp';
  ul := exec(crontab_cmd, ['-l'], m);
  assignfile(fp, tmp);
  rewrite(fp);

  if (pos('no crontab', ul) = 0) then
  begin
    n := m.Count;
    for k := 0 to n - 1 do
    begin
      r := m[k];
      if (pos(yo, r) = 0) then
        writeln(fp, r);
    end;
    exec(crontab_cmd, ['-r']);
  end;

  writeln(fp, '3,8,13,18,23,28,33,38,43,48 * * * * ' + yo + ' run');
  closefile(fp);
  ul := exec(crontab_cmd, [tmp]);
  deletefile(tmp);
end;


function TRBT_OddFace_Cluster.substr(const s: string; k_base_0: integer;
  cnt: integer): string;
begin
  if cnt > 0 then
    Result := copy(s, k_base_0 + 1, cnt)
  else
    Result := copy(s, k_base_0 + 1, length(s) - k_base_0);
end;

procedure TRBT_OddFace_Cluster.Ayuda;
begin
  if orden <> '' then
    writeln('orden desconocida: ' + orden);
  writeln('La sintaxis es ./rbt_oddface_cluster orden [par2]');
  writeln('  Las ordenes conocidas son: ');
  writeln('    "run" que testea el estado del cluster y lanza o para aplicaciones');
  writeln('    "parar_tareas n" que manda parar "n" tareas. ');
  writeln('    "parar_crontab" elimina el crontab del usuario. ');
  writeln('    "instalar_crontab" instala el crontab del usuario. ');
  writeln('    "parar_todo" para las tareas y borra el crontab ');
  writeln('    "lst" resume cantidad de trabajos en ejecución. ');
  writeln('    "cancelar_trabajos [nidProblema]" cancela todos los trabjos de usuario. ');
end;

procedure TRBT_OddFace_Cluster.orden_lst;
var
  nEsperandoTodos, nEsperandoPropios, nTotalTodos, nTotalPropios: integer;
  kCola: integer;
  xcola: TCola;
begin
  for kCola := 0 to colas.Count - 1 do
  begin
    xcola := colas[kCola];
    nEsperandoTodos := getEsperando(nTotalTodos, nTotalPropios,
      nEsperandoPropios, usuario, xcola.nombre);
    writeln('Cola: ', xcola.nombre, ' ============================');
    writeln('TotalTodos: ', nTotalTodos);
    writeln('TotalPropios: ', nTotalPropios);
    writeln('EsperandoTodos: ', nEsperandoTodos);
    writeln('EsperandoPropios: ', nEsperandoPropios);
  end;
end;




procedure TRBT_OddFace_Cluster.orden_cancelartrabajos(
  username, xcola, wk_nidProblema: string);
var
  s: string;
  m: TStringList;
  n, k: integer;
  cnt: integer;
  job_id, Name, usuario, time_use, estado, colax: string;
  icols: array of integer;
  xcola9: string;

begin
(*
* clusteruy
[rchaer@login ~]$ squeue
'            JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)'
'             1670    normal OptimYaw  mdraper  R   22:44:32      1 node01          '



     *    *)

  s := exec('squeue', [], m);
  n := m.Count;
  cnt := 0;
  icols := MedirCols(m[0], ['JOBID', 'PARTITION', 'NAME', 'USER',
    'ST', 'TIME', 'NODES', 'NODELIST(REASON)']);


  if length(xcola) <= 9 then
    xcola9 := trim(xcola)
  else
    xcola9 := trim(copy(xcola, 1, 9));


  for k := 1 to n - 1 do
  begin
    //      wrln( "k: $k, ln: ".$m[$k].":" );
    s := m[k];
    job_id := getColVal(s, icols, 0);
    colax := getColVal(s, icols, 1);
    Name := getColVal(s, icols, 2);
    usuario := getColVal(s, icols, 3);
    estado := getColVal(s, icols, 4);
    time_use := getColVal(s, icols, 5);

    if (usuario = username) and (colax = xcola9) then
    begin
      //      writeln( '['+name+'|'+wk_nidProblema+']' );
      if (wk_nidProblema = '') or (pos(wk_nidProblema, Name) = 1) then
      begin
        exec('/usr/bin/scancel', [job_id]);
        Inc(cnt);
      end;
    end;
  end;
  Writeln('se cancelaron ', cnt, ' trabajos del usuario: ',
    username, ' de la cola: ', xcola);
end;


procedure TRBT_OddFace_Cluster.parar(ntareas: integer);
var
  sql: string;
begin
  sql := 'UPDATE ofe_cnt_terminar SET cnt = ' + IntToStr(ntareas) + ' WHERE 1';
  DB.Exec(sql);
end;



procedure TRBT_OddFace_Cluster.runLanzarParar(xcola: TCola);
var
  nTotalPropios, nTotalTodos: integer;
  nEsperandoPropios, nEsperandoTodos: integer;
  margen: integer;
begin
  nEsperandoPropios := 0;
  nEsperandoTodos := getEsperando(nTotalTodos, nTotalPropios,
    nEsperandoPropios, usuario, xcola.nombre);

  if (margenEncolados > 0) then
  begin
    if (nEsperandoTodos <= margenEncolados) then
    begin
      parar(0);
      margen := min(xcola.maxtareas - nTotalPropios, xcola.nnucleos -
        (nTotalTodos - nEsperandoTodos));
      if margen > 0 then
        lanzar(min(margen, ntares_por_vez), xcola);
    end
    else
      parar(1);
  end
  else
  begin
    if (nEsperandoPropios <= -margenEncolados) then
    begin
      parar(0);
      margen := min(xcola.maxtareas - nTotalPropios, xcola.nnucleos -
        (nTotalTodos - nEsperandoTodos));
      if margen > 0 then
      begin
        lanzar(min(margen, ntares_por_vez), xcola);
      end
    end
    else
      parar(1);
  end;
end;



procedure TRBT_OddFace_Cluster.lanzar(ntareas: integer; xcola: TCola);
var
  kbase, k, selk, nrows: integer;
  rec: TDataRecord;
  id_problema, apl_problema: string;
  aplicacion: string;
  idEjecutor: integer;
  idTrabajo: string;
  archi: string;
  fp: textfile;
  r, ul: string;
begin
  kbase := nextval('ejecutores', ntareas);
  nrows := ds.nrows;
  for  k := 0 to ntareas - 1 do
  begin
    selk := random(nrows);
    ds.go(selk);
    rec := ds.getFilaActiva;
    id_problema := trim(rec.fields[0].AsString);
    apl_problema := trim(rec.fields[1].AsString);
    aplicacion := home + '/SimSEE/bin/' + apl_problema;

    wrln('nrows: ' + IntToStr(nrows) + ' , selk: ' + IntToStr(selk) +
      ' , apl_problema: ' + apl_problema + ' , id_problema: ' + id_problema);


    if ((apl_problema <> '') and fileexists(aplicacion)) then
    begin
      idEjecutor := kbase + k;
      idTrabajo := 'wk_' + id_problema + '_' + IntToStr(idEjecutor);
      archi := home + '/' + idTrabajo + '.sh';
      assignfile(fp, archi);
      rewrite(fp);
      writeln(fp, '#!/bin/bash');
      writeln(fp, '#SBATCH --job-name=' + idTrabajo);
      writeln(fp, '#SBATCH --ntasks=1');
      writeln(fp, '#SBATCH --mem=2048');

      writeln(fp, '#SBATCH --tmp=20G');

      writeln(fp, '#SBATCH --time=' + IntToStr(xcola.maxhoras) + ':00:00');
      //     writeln( fp,  '#SBATCH --time=02:00:00' );
      writeln(fp, '#SBATCH --partition=' + xcola.nombre);

      if xcola.nombre = 'besteffort' then
        writeln(fp, '#SBATCH --qos=besteffort');

      writeln(fp, '#SBATCH --mail-type=FAIL');
      //      writeln( fp,  '#SBATCH --mail-type=ALL' );
      writeln(fp, '#SBATCH --mail-user=rchaer@fing.edu.uy');
      writeln(fp, '#SBATCH --output=/dev/null');
      //     writeln( fp,  '#SBATCH --error=/dev/null');
      writeln(fp, '#SBATCH --open-mode=truncate');

      writeln(fp, 'source /etc/profile.d/modules.sh');

      //   cd ~/miaplicacion
      //  ./mibinario arg1 arg2
      if (pasar_idEjecutor) then
        r := aplicacion + ' ' + IntToStr(idEjecutor) + ' ' + id_problema
      else
        r := aplicacion + ' ' + id_problema;
      writeln(fp, r);
      closefile(fp);

      {$IFNDEF EMULADOR_CLUSTER_UY}
      ul := exec('sbatch', [archi]);
      wrln('idEjecutor: ' + IntToStr(idEjecutor) + ', exec: ' + ul +
        ', cola: ' + xcola.nombre);
      {$ENDIF}
      sleep(pausa_segs * 1000);
    end;
  end;
end;


  (* CLUSTER - FING
procedure TRBT_OddFace_Cluster.lanzar(ntareas: integer; xcola: string);
var
  kbase, k, selk, nrows: integer;
  rec: TDataRecord;
  id_problema, apl_problema: string;
  aplicacion: string;
  idEjecutor: integer;
  archi: string;
  fp: textfile;
  r, ul: string;
begin
  kbase := nextval('ejecutores', ntareas);
  nrows := ds.nrows;
  for  k := 0 to ntareas - 1 do
  begin
    selk := random(nrows);
    rec := ds.go(selk);
    id_problema := trim(rec.fields[0].AsString);
    apl_problema := trim(rec.fields[1].AsString);

    aplicacion := home_binarios + '/simsee/fpcbin/' + apl_problema;

    wrln('nrows: ' + IntToStr(nrows) + ' , selk: ' + IntToStr(
      selk) + ' , apl_problema: ' + apl_problema + ' , id_problema: ' + id_problema);

    if ((apl_problema <> '') and fileexists(aplicacion)) then
    begin
      idEjecutor := kbase + k;
      archi := '/home/' + usuario + '/wk_' + usuario + '_' +
        id_problema + '_' + IntToStr(idEjecutor) + '.sh';
      assignfile(fp, archi);
      rewrite(fp);
      if (xcola = 'publica') then
      begin
        // Agrego segun recomendaci�n de Gabriel Usera 2013-12-15
        //      fwrite( $fp, '#PBS -l nodes=1:inside,walltime=12:00:00'."\n" );
        writeln(fp, '#PBS -l nodes=1:inside,walltime=2:00:00');
        writeln(fp, '#PBS -q publica');
        if (pasar_idEjecutor) then
          r := aplicacion + ' ' + IntToStr(idEjecutor) + ' ' + id_problema
        else
          r := aplicacion + ' ' + id_problema;
        writeln(fp, r);
      end
      else if (xcola = 'quick_jobs') then
      begin
        // Para usar los nodos de 64 cores usar estas en lugar de las anetriores
        //  la siguiente linea es para pedir 64 procesadores por nodo
        //  fwrite( $fp, '#PBS -l nodes=1:ppn=64,walltime=4:00:00'."\n");
        //      fwrite( $fp, '#PBS -l nodes=1,walltime=4:00:00'."\n");
        writeln(fp, '#PBS -l nodes=1,walltime=02:00:00');
        writeln(fp, '#PBS -q quick_jobs');

        if (pasar_idEjecutor) then
        begin
          r := aplicacion + ' ' + IntToStr(idEjecutor) +
            ' ' + id_problema + '  ';
          writeln(fp, r);
        end
        else
        begin
          r := aplicacion + ' ' + id_problema;
          writeln(fp, r);
        end;
      end
      else
      begin
        writeln(fp, '#PBS -l nodes=1,walltime=12:00:00');
        writeln(fp, '#PBS -q ' + xcola);

        if (pasar_idEjecutor) then
        begin
          r := aplicacion + ' ' + IntToStr(idEjecutor) +
            ' ' + id_problema + '  ';
          writeln(fp, r);
        end
        else
        begin
          r := aplicacion + ' ' + id_problema;
          writeln(fp, r);
        end;
      end;

      closefile(fp);
      ul := exec(qsub_cmd + ' -m n ' + archi);
      //   $ul = exec( $qsub_cmd.' ' . $archi);
      wrln('idEjecutor: ' + IntToStr(idEjecutor) + ', exec: ' +
        ul + ', cola: ' + xcola);
      sleep(pausa_segs * 1000);
    end;
  end;
end;

*)


(*
  getEsperando() retorna la cantidad de tareas en estado "Q" en la cola de tareas
  del despachador
 *)
function TRBT_OddFace_Cluster.getEsperando(
  var cnt_total_todos, cnt_total_propios, cnt_esperando_propios: integer;
  username, xcola: string): integer;
var
  s: string;
  m: TStringList;
  n, k: integer;
  cnt_esperando_todos: integer;
  job_id, Name, usuario, time_use, estado, colax: string;
  icols: array of integer;
  xcola9: string;

begin
(*
* clusteruy
[rchaer@login ~]$ squeue
'            JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)'
'             1670    normal OptimYaw  mdraper  R   22:44:32      1 node01          '



     *    *)

{$IFDEF EMULADOR_CLUSTER_UY}
  m := TStringList.Create;
  m.Add('            JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)');
  m.add('             1670    normal OptimYaw  mdraper  R   22:44:32      1 node01          ');
  s := '             1670    normal OptimYaw  mdraper  R   22:44:32      1 node01          ';

{$ELSE}
  s := exec('squeue', [], m);
{$ENDIF}
  n := m.Count;
  cnt_total_todos := 0;
  cnt_total_propios := 0;
  cnt_esperando_todos := 0;
  cnt_esperando_propios := 0;

  icols := MedirCols(m[0], ['JOBID', 'PARTITION', 'NAME', 'USER',
    'ST', 'TIME', 'NODES', 'NODELIST(REASON)']);

  if length(xcola) <= 9 then
    xcola9 := trim(xcola)
  else
    xcola9 := trim(copy(xcola, 1, 9));

  for k := 1 to n - 1 do
  begin
    //      wrln( "k: $k, ln: ".$m[$k].":" );
    s := m[k];
    job_id := getColVal(s, icols, 0);
    colax := getColVal(s, icols, 1);
    Name := getColVal(s, icols, 2);
    usuario := getColVal(s, icols, 3);
    estado := getColVal(s, icols, 4);
    time_use := getColVal(s, icols, 5);

    if colax = xcola9 then
    begin
      Inc(cnt_total_todos);
      if (usuario = username) then
        Inc(cnt_total_propios);

      //            wrln("usr: $usuario , estado: $estado");
      if (estado = 'PD') then
      begin
        if (usuario = username) then
          Inc(cnt_esperando_propios);
        Inc(cnt_esperando_todos);
      end;
    end;
  end;
  Result := cnt_esperando_todos;
end;


{$IFDEF CLUSTER_FING}

(*
  getEsperando() retorna la cantidad de tareas en estado "Q" en la cola de tareas
  del despachador
 *)
function TRBT_OddFace_Cluster.getEsperando(var cnt_propios: integer;
  username, xcola: string): integer;
var
  s: string;
  m: TStringList;
  n, k: integer;
  cnt: integer;
  job_id, Name, usuario, time_use, estado, colax: string;

begin
    (* ejemplo de salida de qstat
      00000000001111111111222222222233333333334444444444555555555566666666667777777777
      01234567890123456789012345678901234567890123456789012345678901234567890123456789
      Job id              Name             User            Time Use S Queue
      ------------------- ---------------- --------------- -------- - -----
      685.cluster         tun023           gusera          1425:59: R publica
      2295.cluster        wrf0909__06      aguti           62:28:00 R publica
      2296.cluster        wrf0909__07      aguti           62:28:37 R publica

     *
     * ojo aveces cambian los largos de lo anterior
     *
      00000000001111111111222222 22223333333333444 4444444555555555 566666666 66 7777777777888888888899999999990000000000
      01234567890123456789012345 67890123456789012 3456789012345678 901234567 89 012345678901234567890
      Job id                     Name              User             Time Use  S  Queue
      -------------------------  ----------------  ---------------  --------  -  -----
      60002.rocks-cluster         clim41            gnaranci         465:36:1  R  small_jobs

     *
     *
     * clusteruy
     [rchaer@login ~]$ squeue
                  JOBID PARTITION     NAME     USER ST       TIME  NODES NODELIST(REASON)
                   1670    normal OptimYaw  mdraper  R   22:44:32      1 node01



     *    *)

  s := exec(qstat_cmd, m);
  n := m.Count;
  cnt := 0;
  cnt_propios := 0;

  for k := 2 to n - 1 do
  begin
    //      wrln( "k: $k, ln: ".$m[$k].":" );
    s := m[k];
    job_id := trim(substr(s, 0, 26));
    Name := trim(substr(s, 26, 43 - 26));
    usuario := trim(substr(s, 43, 59 - 43));
    time_use := trim(substr(s, 59, 68 - 59));
    estado := trim(substr(s, 68, 70 - 68));
    colax := trim(substr(s, 70));

    if colax = xcola then
    begin
      //            wrln("usr: $usuario , estado: $estado");
      if (estado = 'Q') then
      begin
        if (usuario = username) then
          Inc(cnt_propios);
        Inc(cnt);
      end;
    end;
  end;
  Result := cnt;
  // OJO REVISAR EL PHP, aparentemente este return estaba antes del end, lo que haría
  // que la función retorne luego de interpretar la primera línea en el for.

end;

{$ENDIF}



procedure TRBT_OddFace_Cluster.orden_run;
var
  kcola: integer;
  sql: string;
  cluster_fing: string;
begin

  RunCommand('/bin/bash', ['-c', 'rm -f ' + home + '/wk*'], s);

  sql := 'SELECT valor FROM ofe_parametros WHERE nombre=''cluster_fing'' LIMIT 1 ';
  ds := DB.sql_query(sql);
  if (ds.nrows = 0) then
  begin
    writeln('No parameters found for Cluster_fing');
    exit;
  end;

  cluster_fing := trim(ds.GetField(0).AsString);

  if (cluster_fing <> 'ON') then
  begin
    if (cluster_fing = 'OFF') then
    begin
      writeln('Cluster_fing is OFF');
      exit;
    end;
    if (cluster_fing = 'STOP') then
    begin
      sql := 'UPDATE ofe_cnt_terminar SET cnt = 999999 WHERE 1';
      DB.exec(sql);
      sql :=
        'UPDATE ofe_parametros SET valor = ''OFF'' WHERE nombre= ''cluster_fing'' LIMIT 1';
      DB.exec(sql);
      writeln('Cluster_fing is STOPPED');
      exit;
    end;

    if (cluster_fing = 'START') then
    begin
      sql := 'UPDATE ofe_cnt_terminar SET cnt = 0 WHERE 1';
      DB.exec(sql);
      sql :=
        'UPDATE ofe_parametros SET valor = ''ON'' WHERE nombre= ''cluster_fing'' LIMIT 1';
      DB.exec(sql);
    end;
  end;

    (*     * **** rch@20120930
      le agrego consulta a la base de datos de problemas oddface
     * ** *)
  sql := 'SELECT ofe_problemas.nid, ofe_tipos_problema.apl ' +
    'FROM ofe_problemas LEFT JOIN ofe_tipos_problema ON ofe_problemas.tipo = ofe_tipos_problema.nid '
    + ' JOIN ofe_grupo_ucluster ON ofe_problemas.grupo = ofe_grupo_ucluster.grupo ' +
    ' WHERE ofe_grupo_ucluster.ucluster = ''' + Usuario + ''' AND ofe_problemas.estado > 0 ';


  ds := DB.sql_query(sql);

  if (ds.nrows = 0) then
  begin
    wrln('No problems to solve.');
    exit;
  end;

  for kcola := 0 to ncolas - 1 do
  begin
    runLanzarParar(colas[kcola]);
  end;
  exit;
end;


procedure TRBT_OddFace_Cluster.order_parar_tareas;
var
  nTareas: integer;
begin
  if (ParamCount > 1) then
    nTareas := StrToInt(ParamStr(2))
  else
    nTareas := 10000; //para todas

  parar(nTareas);
  exit;

end;

procedure TRBT_OddFace_Cluster.dispatch_cmd;
var
  NombreCola, nHilos, email: string;
  k: integer;
  id_problema, apl_problema: string;
  aplicacion: string;
  idEjecutor: integer;
  idTrabajo: string;
  archi: string;
  fp: textfile;
  r, ul: string;
  xCola: TCola;
  buscando: boolean;
begin
  id_problema := '0';
  email := ParamStr(1);
  nombreCola := ParamStr(2);
  nHilos := ParamStr(3);
  if StrToInt(nHilos) > 40 then
  begin
    writeln('El máximo de nHilos es 40 en el CLusterUY');
    exit;
  end;
  apl_Problema := ParamStr(4);

  buscando := True;
  for k := 0 to colas.Count - 1 do
  begin
    xCola := colas[k];
    if xCola.nombre = nombreCola then
    begin
      buscando := False;
      break;
    end;
  end;

  if buscando then
  begin
    writeln('Nombre de cola inválido: ', nombreCola);
    exit;
  end;

  idEjecutor := nextval('ejecutores', 1);

  aplicacion := home + '/SimSEE/bin/' + apl_Problema;
  if not fileexists(aplicacion) then
  begin
    writeln('No existe la aplciación: ' + aplicacion);
    exit;
  end;

  idTrabajo := 'wk_' + id_problema + '_' + IntToStr(idEjecutor);
  archi := home + '/' + idTrabajo + '.sh';
  assignfile(fp, archi);
  rewrite(fp);
  writeln(fp, '#!/bin/bash');
  writeln(fp, '#SBATCH --job-name=' + idTrabajo);
  writeln(fp, '#SBATCH --ntasks=1');
  writeln(fp, '#SBATCH --cpus-per-task=' + nHilos);
  //      writeln( fp,  '#SBATCH --mem-per-cpu=40' );
  writeln(fp, '#SBATCH --mem=' + IntToStr(2048 * StrToInt(nHilos)));
  //      writeln( fp,  '#SBATCH --mem=2048' );
  writeln(fp, '#SBATCH --tmp=20G');

  writeln(fp, '#SBATCH --time=' + IntToStr(xcola.maxhoras) + ':00:00');
  //     writeln( fp,  '#SBATCH --time=02:00:00' );
  writeln(fp, '#SBATCH --partition=' + xcola.nombre);

  if xcola.nombre = 'besteffort' then
    writeln(fp, '#SBATCH --qos=besteffort');

  writeln(fp, '#SBATCH --mail-type=FAIL');
  //      writeln( fp,  '#SBATCH --mail-type=ALL' );
  writeln(fp, '#SBATCH --mail-user=rchaer@fing.edu.uy');
  writeln(fp, '#SBATCH --output=/dev/null');
  //     writeln( fp,  '#SBATCH --error=/dev/null');
  writeln(fp, '#SBATCH --open-mode=truncate');

  writeln(fp, 'source /etc/profile.d/modules.sh');

  //   cd ~/miaplicacion
  //  ./mibinario arg1 arg2
  if (pasar_idEjecutor) then
    r := aplicacion + ' ' + IntToStr(idEjecutor) + ' ' + id_problema
  else
    r := aplicacion + ' ' + id_problema;
  writeln(fp, r);

  closefile(fp);

      {$IFNDEF EMULADOR_CLUSTER_UY}
  ul := exec('sbatch', [archi]);
  wrln('idEjecutor: ' + IntToStr(idEjecutor) + ', exec: ' + ul +
    ', cola: ' + xcola.nombre);
      {$ENDIF}
end;


procedure GenerarEjecutorOptSimRes();
begin
  (*
  ejecutor=35

nhilos=48
ntareas={$nhilos}


### configuracion de directorios
homedir={$HOME}
bindir={$homedir}/simsee/fpcbin
corridas={$homedir}/simsee/corridas

tmprundir=/scratch/${USER}
mkdir -p {$tmprundir}/corridas

tmpdirsala=${tmprundir}/corridas/{$ejecutor}
mkdir -p tmpdirsala

archi_defs=${tmprundir}/${ejecutor}/${NombreSala}_${escenario}_${planillaSimres3}.txt

rm -rf ${tmprundir}/${ejecutor}/*

cd {$corridas}
cp {$corridas}/${NombreSala}.zip {$tmpdirsala}
unzip -o ${NombreSala}.zip -d ${NombreSala}
rm -f ${NombreSala}.zip

cd {$tmpdirsala}/${NombreSala}
{$bindir}/cmdopt sala={$tmpdirsala}/${NombreSala}/${NombreSala}.ese ejecutor=${ejecutor} escenario=${escenario} semilla=31 nhilos={$nhilos} ntareas={$ntareas}

cd {$tmpdirsala}/${NombreSala}
{$bindir}/cmdsim sala={$tmpdirsala}/${NombreSala}/${NombreSala}.ese ejecutor=${ejecutor} escenario=${escenario} semilla=31 nhilos={$nhilos} ntareas={$ntareas}

cd ${tmprundir}/${ejecutor}
{$bindir}/cmdsimres3 ${archi_defs} resultados ${ejecutor}

cd ${tmprundir}/${ejecutor}
zip -r ${NombreSala}_${escenario}_resultados.zip *

cp ${NombreSala}_${escenario}_resultados.zip {$corridas}/

rm -f ${NombreSala}_${escenario}_resultados.zip
rm -rf ${tmprundir}/${ejecutor}/*
rm -rf ${tmpdirsala}/*
*)
end;

function TRBT_OddFace_Cluster.ProcesarOrden: boolean;
var
  aCola: TCola;
  kCola: integer;
  wk_NidProblema: string;

begin
  if (orden = 'cancelar_trabajos') then
  begin
    if ParamCount > 1 then
      wk_NidProblema := 'wk_' + ParamStr(2)
    else
      wk_NidProblema := '';

    for kCola := 0 to Colas.Count - 1 do
    begin
      aCola := Colas[kCola];
      orden_cancelartrabajos(usuario, aCola.nombre, wk_NidProblema);
    end;
    Result := True;
    exit;
  end;


  if (orden = 'lst') then
  begin
    orden_lst;
    Result := True;
    exit;
  end;


  if (orden = 'run') then
  begin
    //    test_repetidor();
    orden_run;
    Result := True;
    exit;
  end;

  if (orden = 'parar_tareas') then
  begin
    order_parar_tareas;
    Result := True;
    exit;
  end;

  if (orden = 'parar_crontab') then
  begin
    remover_crontab();
    Result := True;
    exit;
  end;

  if (orden = 'parar_todo') then
  begin
    parar(199000);
    remover_crontab();
    Result := True;
    exit;
  end;

  if (orden = 'instalar_crontab') then
  begin
    parar(0);
    instalar_crontab();
    Result := True;
    exit;
  end;


  Result := False;
end;


end.
