------------------------------------------------------------------------
--
-- Módulo Torque PBS/ PBS Pro
--
-- VERSÃO AINDA *EM DESENVOLVIMENTO*.
-- A idéia é substituir o torquePBS.lua por este.
--
------------------------------------------------------------------------
--
-- Especificação das funções públicas
--

COMMANDS = {
  torque = {},
  pbsPro = {},
}

COMMANDS.torque.jobsinfo = "qstat -x"
COMMANDS.torque.jobsupdate = "qstat -f"
COMMANDS.torque.nodeinfo = "pbsnodes -a"
COMMANDS.torque.jobstateTable = {
  FINISHED = "ECDA",
  RUNNING = "SRT",
  WATING = "Q",
}
COMMANDS.pbsPro.jobsinfo = "qstat -x"         -- XXX
COMMANDS.pbsPro.jobsupdate = "qstat -fx"
COMMANDS.pbsPro.nodeinfo = "pbsnodes"
COMMANDS.pbsPro.jobstateTable = {
  FINISHED = "EHMF",
  RUNNING = "SRTUW",
  WAITING = "Q",
}

FIELDS = {
  torque = {},
  pbsPro = {},
}

FIELDS.torque.ncpus = "np"
FIELDS.pbsPro.ncpus = "pcpus"

FUNCTIONS = {
  torque = {},
  pbsPro = {},
}

FUNCTIONS.torque.getMemoryInBytes = function(t)
   local _ram = t.physmem and servermanager._getvalunit(t.physmem)
   local _totmem = t.totmem and servermanager._getvalunit(t.totmem)
   return _ram, _totmem
end

FUNCTIONS.torque.getCpuLoad = function(t)
   return string.gsub(t.loadave or "", "(%d+)", "%1")
end

FUNCTIONS.torque.getAvailableMemoryInBytes = function(t)
   return servermanager._getvalunit(t.availmem or "")
end

FUNCTIONS.pbsPro.getMemoryInBytes = function(t)
   local physmem = t["resources_available.mem"]
   local _ram = physmem and servermanager._getvalunit(physmem)
   local virtmem = t["resources_available.vmem"]
   local _vmem = (virtmem and servermanager._getvalunit(virtmem) ) or 0
   return _ram, _ram and (_ram + _vmem)
end

FUNCTIONS.pbsPro.getCpuLoad = function(t)
   return t["resources_assigned.ncpus"]
end

FUNCTIONS.pbsPro.getAvailableMemoryInBytes = function(t)
   -- No PBSPro
   -- available memory = memó total do nó
   -- assigned memory  = memó em uso no nó 
   -- unused memory    = available - assigned
   --
   -- No contexto desta funç, memó disponíl é memó "unused"
   local totmem = FUNCTIONS.pbsPro.getMemoryInBytes(t)
   local rammem = t["resources_assigned.mem"]
   local _uram = rammem and servermanager._getvalunit(rammem)
   local virtmem = t["resources_assigned.vmem"]
   local _uvmem = (virtmem and servermanager._getvalunit(virtmem) ) or 0
   local usedmem = _uram and (_uram + _uvmem)
   return totmem and usedmem and (totmem - usedmem)
end

TRASHDIR="PBStrash/"
PBSERRORFILE="PBSError.log"
PBSOUTPUTFILE="PBSOutput.log"

--
-- Funções de controle do módulo.
--

------------------------------------------------------------------------
-- Incializa o módulo para execução. Essa função deve ser chamada antes
-- de qualquer outra função desta biblioteca. 
--
-- Recebe uma tabela com os parâmetros de inicialização:
--
-- queue_name - nome da fila para qual os jobs serão submetidos (opcional)
-- path - path de execução dos jobs (obrigatório)
-- domain - deve ser definido quando o domínio está contido no nome do nó (opcional)
--
-- Retorna verdadeiro caso o
-- módulo seja inicializado com sucesso. Em caso de erro, retorna nil e
-- a mensagem de erro.
--
-- ok, err = open( initargs )
------------------------------------------------------------------------
open = function ( initargs )
  if initargs then
    for arg, val in pairs(initargs) do
      servermanager[arg] = val
    end
  end
--- REVER
  if not servermanager.path then
servermanager.path = os.getenv("PWD") or ""
  end
  if not servermanager.queue_name then
servermanager.queue_name = "workq"
  end
--
  local pbs = servermanager.pbs or "pbsPro"
  fields = FIELDS[pbs]
  commands = COMMANDS[pbs]
  functions = FUNCTIONS[pbs]

  if os.execute("test -d "..TRASHDIR) ~= 0 and
     not os.execute("mkdir "..TRASHDIR) then
     print("Não foi possível criar o diretório "..TRASHDIR..".")
     print("Usando ./")
     TRASHDIR=""
  end
  print("CARREGADO ",pbs)
  return 1
end

------------------------------------------------------------------------
-- Termina a execução do módulo. Os processos disparados que ainda
-- estejam rodando não são afetados por essa chamada. Essa função não
-- retorna valores.
--
-- close()
------------------------------------------------------------------------
close = function()
  servermanager._nodes = nil
end

------------------------------------------------------------------------
--
-- Funções de consulta à configuração de servidor.
--
------------------------------------------------------------------------
-- Retorna uma tabela com os nós do servidor especificado.
-- Consulta o PBS. Caso o
-- servidor seja nil, assume localhost. Em caso de erro, retorna nil
-- e a mensagem de erro.
--
-- nodes, err = getnodes(server)
------------------------------------------------------------------------
getnodes = function (server)
   local ids = {}
   local fname = os.tmpname()
   local command = string.format('pbsnodes -a > %s', fname)
   os.execute( command )
   local fd = io.open(fname, "r")
   if not fd then
     return nil, "Falha na abertura do arquivo temporário.".." ["..fname.."]".." de saída do comando: '"..command.."'."
   end
   local str = fd:read("*a")
   if not str then
     return nil, "Falha na leitura do arquivo temporário.".." ["..fname.."]".." de saída do comando: '"..command.."'."
   end	   
   fd:close()
   os.remove(fname)

   str2 = string.gsub("\n"..str,"\n(%S+\n)","\n%%%1")
   for host, node_data in string.gmatch("\n"..str2, "%%(%S+)([^%%]+)") do
     local hostname = string.gsub(host,"%..*","")
     table.insert(ids, hostname)
------
     if not servermanager._nodes[hostname] then
        _add(hostname, node_data)
     end
------
   end

  if #ids > 0 then
     return ids, nil
  else
     return nil, "Não foi possível obter os nós do PBS"
   end
end

------------------------------------------------------------------------
-- Retorna o número de processadores no servidor especificado. Caso o
-- servidor seja nil, assume localhost. Em caso de erro, retorna nil
-- e a mensagem de erro.
--
-- cpus, err = getnumcpus(server)
------------------------------------------------------------------------
getnumcpus = function (server)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   return ninfo.num_processors, nil
end

------------------------------------------------------------------------
-- Retorna a ordem de bytes no servidor especificado. O retorno pode ser
-- LITTLE_ENDIAN ou BIG_ENDIAN. Caso o servidor seja nil, assume
-- localhost. Em caso de erro, retorna nil e a mensagem de erro.
--
-- order, err = getbyteorder(server)
------------------------------------------------------------------------
getbyteorder = function (server)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   return ninfo.byteorder, nil
end

------------------------------------------------------------------------
--
-- Funções de monitoração de servidor.
--
------------------------------------------------------------------------
-- Retorna a quantidade em bytes de memória no servidor especificado.
-- O retorno é uma tabela contendo o total de memória RAM e de Swap, 
-- no formato:
--    { ram = xxx, swap = yyy }
--
-- Caso o servidor seja nil, assume localhost.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- mem, err = getmemory(server)
------------------------------------------------------------------------
getmemory = function (server)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   return ninfo.memory, nil
end

------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de CPU do último minuto no servidor
-- especificado. Esse valor considera o número de processadores que o
-- servidor possui.
--
-- Por exemplo, caso a métrica de ocupação seja o número de processos
-- na fila de prontos, este número estará dividido pela quantidade 
-- de processadores. Caso o servidor seja nil, assume localhost. 
-- 
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada 
-- no servidor em questão. Em caso de erro, retorna nil e a mensagem de
-- erro.
--
-- load, err = getcpuload(server, maxold)
------------------------------------------------------------------------
getcpuload = function (server, maxold)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   servermanager._update(server, maxold)
   if not ninfo.isonline then
     return nil, "Nó não está online (aquisição de CPU)!"
   end
   if not ninfo.cpuload then
     return nil, "Falha de aquisição de carga de CPU!"
   end
   return ninfo.cpuload, nil
end

------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de memória do último minuto no
-- servidor especificado. O retorno é uma tabela contendo o total de
-- memória RAM e de Swap, no formato:
--   { ram = xxx, swap = yyy }
--
-- Caso o servidor seja nil, assume localhost.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada 
-- no servidor em questão. Em caso de erro, retorna nil e a mensagem de
-- erro.
--
-- load, err = getmemoryload(server, maxold)
------------------------------------------------------------------------
getmemoryload = function (server, maxold)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   servermanager._update(server, maxold)
   if not ninfo.isonline then
     return nil, "Nó não está online (aquisição de memória)!"
   end
   if not ninfo.memoryload then
     return nil, "Falha de aquisição de carga da memória!"
   end
   return ninfo.memoryload, nil
end

----------------------------------------------------------------------
-- Retorna o número de jobs que estão sendo executados no servidor
-- especificado.
----------------------------------------------------------------------
getnumberofjobs = function(server, maxold)
   ninfo = servermanager._getnode(server)
   if not ninfo then
      return nil, servermanager.NO_SERVER
   end
   servermanager._update(server, maxold)
   if not ninfo.isonline then
     return nil, "Nó não está online (aquisição de número de jobs)!"
   end
   if not ninfo.njobs then
     return nil, "Falha de aquisição do número de jobs!"
   end

   return ninfo.njobs, nil
end

------------------------------------------------------------------------
-- Retorna uma string que contém informações sobre todos os jobs em
-- execução no servidor.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- info, err = getjobsinfo()
------------------------------------------------------------------------
getjobsinfo = function()
  local tmpname = os.tmpname()
  local command = string.format(commands.jobsinfo..' > %s', tmpname)

--REVER
--servermanager.writeCmd("Coletando info sobre jobs com ["..command.."]")
  local result = os.execute(command)
  if result ~= 0 then
     return nil, "Falha na coleta de dados de jobs (qstat). Cod.:"..tostring(result)
  end

  local file = io.open(tmpname, "r")
  if not file then
     local err = "Falha na leitura do arquivo temporário que guarda info sobre jobs."
     return nil, err.." Arq.:["..tmpname.."]"
  end

  local strXML = file:read("*a")
  file:close()
  os.remove(tmpname)

  return strXML
end

------------------------------------------------------------------------
--
-- Funções de execução, monitoração e controle de processos.
--
------------------------------------------------------------------------
-- Executa um comando no servidor especificado.
--
-- * Opcionalmente, pode ser passado o nome de um arquivo de onde a 
-- entrada padrão será direcionada.
-- * Opcionalmente, pode ser passado o nome de um arquivo para onde a 
-- saída padrão será direcionada. 
-- * Opcionalmente, pode ser passado o nome de um arquivo para onde a 
-- saída de erro será direcionada.
-- * A saída padrão e de erro podem ser direcionadas para o mesmo
-- arquivo.
-- * Caso o servidor seja nil, assume localhost.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- handle, err = executecommand(identifier, command, infile, outfile,
--   errorfile, server)
------------------------------------------------------------------------
executecommand = function(id, command, infile, outfile, errorfile, host,
  startdir, output_path)
  -- REVER
  -- Precisamos retirar tudo que vem antes do @ por causa do limite de
  -- 15 caracteres no PBSPro
  id = string.gsub(id,"(.*@)","")
  --
  output_path = output_path or TRASHDIR
  local errorlog = (not errorfile) and output_path.."/"..PBSERRORFILE..id
  local tmpfile = os.tmpname()

  local cmd = command
  if infile then
     cmd = cmd .. " < " .. infile
  end
  if startdir then
    local exec_string = string.format("%s > %s ", cmd, tmpfile)
    servermanager.writeMsg( "Tentando execução:\n\t"..exec_string.."\n" )
    os.execute(exec_string)
    errorlog = nil
  else
  --
  --outfile = outfile or "/dev/null"
  --errorfile = errorfile or "/dev/null"
  --
  --O scp dá um erro!
  --Também não serve usar o tmpfile em /tmp/xxx
  --Unable to copy file 17.tissot.OU to tissot:/dev/null
  --
  --VAMOS TER QUE LIMPAR DEPOIS!!! XXX
  --
    local extraParams = ""
    if servermanager.queue_name then
      extraParams = extraParams .. " -q " .. servermanager.queue_name
    end
-- REVER
-- Está escolhendo sempre a service0
--[[
    if host then
      extraParams = extraParams .. " -l host=" .. host
    end
--]]
    local _outfile = outfile or output_path.."/"..PBSOUTPUTFILE..id
    local _errorfile = errorfile or errorlog
    local exec_string = string.format( [[qsub -N %s -V -o "%s" -e "%s" %s > %s]],
      id, _outfile, _errorfile, extraParams, tmpfile)
    local cmd_string = string.format( [[ksh -c 'cd "%s" && %s']], servermanager.path, cmd)
    servermanager.writeCmd( "Tentando execução:\n\t"..exec_string.."\n" ..
                            "\t\t"..cmd_string.."\n" )
    local fd = io.popen(exec_string, "w")
    fd:write(cmd_string)
    fd:close()
  end  

  local fd = io.open(tmpfile, "r")
  if not fd then
     local strerror = "executecommand: Erro ao abrir arquivo temporário:\n"..
         "\t["..tostring(tmpfile).."]\n"..
         "\tpara aquisição de dados de execução\n" 
     servermanager.writeError( strerror)
     return nil, strerror
  end

  local str = fd:read("*a")
  str = string.gsub( str, "%c", "" )
  fd:close()

  os.remove(tmpfile)
  servermanager._updatecommands()

  local handle ={
     cmdid = id,
     command = command,
     execHost = nil,
     errorfile = errorlog,
     last_update = 0,
--   pid = string.gsub( str, "(%S+)%.%S+","%1"),
--   server = string.gsub( str, "%S+%.(%S+)","%1"),
     pid = string.gsub( str, "([^%.]+)%..*","%1"),
     server = string.gsub( str, "[^%.]+%.(.*)","%1"),
     start_time = servermanager.now()
  }
  handle.pid = tonumber(handle.pid)
  return handle
end

------------------------------------------------------------------------
-- Retorna uma tabela Lua com os dados do comando que devem ser
-- persistentes. Em caso de erro, retorna nil e a mensagem de erro.
--
-- pdata, err = getcommandpersistentdata(handle)
------------------------------------------------------------------------
getcommandpersistentdata = function (handle)
   if type(handle) ~= "table" or not handle.cmdid then
      return nil, "Handle inválido."
   end
   return handle, nil
end

------------------------------------------------------------------------
-- Retorna um handle com os dados do comando que devem ser persistentes.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- handle, err = retrievecommandhandle(pdata)
------------------------------------------------------------------------
retrievecommandhandle = function (pdata)
   if type(pdata) ~= "table" or not pdata.cmdid then
      return nil, "Dados inválidos."
   end

   servermanager._updatecommands()

   local cmdhTable, err = servermanager._getcommandall( pdata.cmdid, pdata.server)
   if err then
      return nil, err
   end

   -- Diferente do pid de um processo, o jobid que usamos no pbs é
   -- gerado pelo nosso sistema. Devemos considerar que qualquer
   -- job que tenha este id faz parte da mesma tarefa...
   if #cmdhTable == 0 then
      return nil, "Comando não encontrado."
   end

   pdata.last_update = 0 

   return pdata, nil
end

------------------------------------------------------------------------
-- Retorna o identificador do processo recebido na função
-- executecommand.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- id, err = getcommandid(handle)
------------------------------------------------------------------------
getcommandid = function (handle)
   if type(handle) ~= "table" or not handle.cmdid then
      return nil, "Handle inválido."
   end

   return handle.cmdid, nil
end

------------------------------------------------------------------------
-- Retorna um identificador unico do processo iniciado através da função
-- executecommand.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- pid, err = getcommandpid(handle)
------------------------------------------------------------------------
getcommandpid = function (handle)
   if type(handle) ~= "table" or not handle.pid or
      not tonumber(handle.pid) then
      return nil, "Handle inválido."
   end

   return tonumber(handle.pid), nil
end

------------------------------------------------------------------------
-- Retorna o estado de um processo iniciado através da função 
-- executecommand.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando.
--
-- O retorno pode ser RUNNING, NOT_RESPONDING, WAITING ou FINISHED. 
-- Em caso de erro, retorna nil e a mensagem de erro.
--
------------
-- TORQUE --
------------
-- job_state The state of the job.
-- 'Q'    /* Job Queued record */
-- 'S'    /* Job run (Started) */
-- 'R'    /* Job Rerun record */
-- 'C'    /* Job Checkpointed and held */
-- 'T'    /* Job resTart (from chkpnt) record */
-- 'E'    /* Job Ended/usage record */
-- 'D'    /* Job Deleted by request */
-- 'A'    /* Job Abort by server */
--
-------------
-- PBS Pro --
-------------
-- E (Exiting) The job has finished, with or without errors, and PBS is
--             cleaning up post-execution. 
-- F (Finished) The job has completed execution, job failed during
--             execution, or job was deleted.
-- H (Held)    The job is held. 
-- M (Moved)   The job was moved to another server.
-- Q (Queued)  The job resides in an execution or routing queue pending
--             execution or routing.  It is not in held or waiting state.
-- R (Running) The job is in an execution queue and is running. 
-- S (Suspend) The job was executing and has been suspended. The job
--             retains its assigned resources but does not use CPU cycles
--             or walltime. 
-- T (Transiting) The job is being routed or moved to a new destination. 
-- U (User suspend) The job was running on a workstation configured for
--             cycle harvesting and the keyboard/mouse is currently busy.
--             The job is suspended until the workstation has been idle
--             for a configured amount of time. 
-- W (Waiting) The job is not held but the Execution_Time attribute
--             contains a time which has not yet been reached. 
--
-- status, err = getcommandstatus(handle, maxold)
------------------------------------------------------------------------
getcommandstatus = function (handle, maxold)
   if (not maxold) or ((servermanager.now() - handle.last_update)
      > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end

   local cmdhTable, err = servermanager._getcommandall( handle.cmdid,
     handle.server)
   if err then
      return nil, err
   end

   local status = servermanager.NOT_RESPONDING
   for i, cmdh in pairs(cmdhTable) do
     local state = cmdh.job_state
     if string.find(commands.jobstateTable.FINISHED, state) then
        status = servermanager.FINISHED
        servermanager._checkforerror(handle)
     elseif string.find(commands.jobstateTable.WAITING, state) then
        status = servermanager.WAITING
     else
        status = servermanager.RUNNING
        break
     end
   end

   return status
end

------------------------------------------------------------------------
-- Retorna a maquina que esta executando o comando definido por handle.
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando. Em caso de erro, retorna
-- nil e a mensagem de erro.
--
-- host, err = getcommandexechost(handle, maxold)
------------------------------------------------------------------------
getcommandexechost = function (handle, maxold)
   if handle.host then
     return handle.host
   end
   if (not maxold) or ((servermanager.now() - handle.last_update)
      > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end
-- -- EM ABERTO: Pega o host do primeiro job
-- local cmdh, err = servermanager._getcommand(handle.cmdid,handle.server)
-- if err then
--    return nil, err
-- end
   -- Fazemos a concatenação de todos os hosts que compõem o job.
   local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
   if err then
      return nil, err
   end
  
   local exectab = {}
   local exechost
   for i, cmdh in pairs(cmds) do
     local ehost = cmdh["exec_host"]
     if ehost and not exectab[ehost] then
       exectab[ehost] = true
       if exechost then
         exechost = exechost .. "/" .. ehost
       else
         exechost = ehost
       end
     end
   end
   handle.host = exechost
   return handle.host
end

------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de CPU do último minuto pelo
-- processo especificado. Esse valor considera o número de processadores
-- que o servidor possui. Por exemplo, caso o processo execute em apenas
-- um processador, este número estará dividido pela quantidade de
-- processadores.  Caso o servidor seja nil, assume localhost.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter. 
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando.  Em caso de erro, retorna
-- nil e a mensagem de erro.
--
-- load, err = getcommandcpuload(handle, maxold)
------------------------------------------------------------------------
getcommandcpuload = function (handle, maxold)
   -- Não tenho a ocupação do último minuto no PBS.
   -- Vou usar cput/walltime
   if (not maxold) or ((servermanager.now() - handle.last_update)
      > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end

   local procs, err = servermanager.getcommandallinfo(handle, maxold)
   if err then
      return nil, err
   end
  
   if not procs or #procs == 0 then
     return 0
   end
   local totcpuperc = 0
   for i, proc in pairs(procs) do
     totcpuperc = totcpuperc + proc.CPUPerc
   end
   return totcpuperc / #procs
end

------------------------------------------------------------------------
-- Retorna todas as informações sobre um determinado comando.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada 
-- no servidor que estiver executando o comando.
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- load, err = getcommandallinfo(handle, maxold)
------------------------------------------------------------------------
getcommandallinfo = function (handle, maxold)
   if (not maxold) or ((servermanager.now() - handle.last_update)
      > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end

   local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
   if err then
      return nil, err
   end
  
   local procs = {}
   for i, cmdh in pairs(cmds) do
     local ehost = cmdh["exec_host"] or "unknown"
     local ram = cmdh["resources_used.mem"] or servermanager.ERROR_CODE
     local swap = cmdh["resources_used.vmem"] or servermanager.ERROR_CODE
     local cput = cmdh["resources_used.cput"] or servermanager.ERROR_CODE
     local walltime = cmdh["resources_used.walltime"] or servermanager.ERROR_CODE
     local cpuperc = 0
     if walltime ~= 0 then
       local server = cmdh["exec_host"] or handle.server
       local ncpus, err = servermanager.getnumcpus(server)
       ncpus = ncpus or 1

       cpuperc = (cput / ncpus) /walltime
     end
     local state
     local job_state = cmdh.job_state
     if job_state then
       if string.find(commands.jobstateTable.FINISHED, job_state) then
          state = servermanager.FINISHED
          servermanager._checkforerror(handle)
       elseif string.find(commands.jobstateTable.WAITING, job_state) then
          state = servermanager.WAITING
       else
          state = servermanager.RUNNING
       end
     else
        state = servermanager.NOT_RESPONDING
     end
  
     local procinfo = {
        pid = tonumber(cmdh.pid),
        ppid = 0, -- XXX IMPORTANTE
        command = handle.command,
        execHost = ehost,
        state = state,
        processorId = 0, -- XXX 
        memoryRamSizeMb = ram / (1024 * 1024),
        memorySwapSizeMb = swap / (1024 * 1024),
        CPUPerc = cpuperc,
        CPUTimeSec = cput,
        wallTimeSec = walltime,
     }
     table.insert(procs, procinfo)
   end
   if #procs < 1 then
     return nil, "Não há informações sobre esse comando."
   end
   -- EM ABERTO: Alteramos o valor do campo walltime do primeiro
   -- processo!
   -- Não podemos usar o walltime dos processos. Usamos um walltime
   -- global do job, considerando a chamada da executecommand.
   procs[1].wallTimeSec = servermanager.now() - handle.start_time
   return procs
end

------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de memória do último minuto pelo
-- processo especificado. O retorno é uma tabela contendo o total de
-- memória RAM e de Swap, no formato:
--     { ram = xxx, swap = yyy }
---------------------------------
-- MAS QUAL A UNIDADE?!?! Bytes?!
---------------------------------
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando. 
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- load, err = getcommandmemoryload(handle, maxold)
------------------------------------------------------------------------
getcommandmemoryload = function (handle, maxold)
   if (not maxold) or ((servermanager.now() - handle.last_update)
     > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end
-- -- EM ABERTO: Como vamos fazer o somatório nesse caso?!
-- local cmdh, err = servermanager._getcommand(handle.cmdid,handle.server)
-- if err then
--    return nil, err
-- end
   -- Fazemos um somatório das memórias de todos os jobs.
   local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
   if err then
      return nil, err
   end
  
   local totram
   local totswap
   for i, cmdh in pairs(cmds) do
     local ram = cmdh["resources_used.mem"]
     local swap = cmdh["resources_used.vmem"]
     if ram then
       totram = (totram or 0) + ram
     end
     if swap then
       totswap = (totswap or 0) + swap
     end
   end
   totram = totram or servermanager.ERROR_CODE
   totswap = totswap or servermanager.ERROR_CODE
   return { ram = totram, swap = totswap}
end

------------------------------------------------------------------------
-- Retorna os tempos de execução do processo especificado. O retorno é
-- uma tabela contendo os tempos de usuário, sistema e total (tempo de
-- parede), no formato:
--     { user = xxx, system = yyy, elapsed = zzz }
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando.  Em caso de erro, retorna
-- nil e a mensagem de erro.
--
-- time, err = getcommandtimeusage(handle, maxold)
------------------------------------------------------------------------------
getcommandtimeusage = function (handle, maxold)
   if (not maxold) or ((servermanager.now() - handle.last_update)
      > maxold) then
      servermanager._updatecommands()
      handle.last_update = servermanager.now()
   end

 ---- EM ABERTO: Como vamos fazer o somatório nesse caso?!
-- local cmdh, err = servermanager._getcommand(handle.cmdid,handle.server)
-- if err then
--    return nil, err
-- end
-- local cput = cmdh["resources_used.cput"] or servermanager.ERROR_CODE
-- local walltime = cmdh["resources_used.walltime"] or servermanager.ERROR_CODE
  
   local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
   if err then
      return nil, err
   end
  
   local cput
   for i, cmdh in pairs(cmds) do
     if cmdh["resources_used.cput"] then
       cput = (cput or 0) + cmdh["resources_used.cput"]
     end
   end
   local walltime = servermanager.now() - handle.start_time

   local system = cput or servermanager.ERROR_CODE
   local real = walltime
   local info = {
     user = system,
     system = system,
     elapsed = real,
   }

   return info, nil
end

------------------------------------------------------------------------
-- Interrompe um processo iniciado através da função executecommand.
-- Retorna verdadeiro caso tenha sucesso. A definição de sucesso é que
-- uma chamada a getcommandstatus para o mesmo processo retornará o
-- valor FINISHED. Retorna falso caso o processo não termine.  Em caso
-- de erro, retorna nil e a mensagem de erro.
--
-- ok, err = killcommand(handle)
------------------------------------------------------------------------
killcommand = function (handle)
   if type(handle) ~= "table" or not handle.cmdid then
      return nil, "Handle inválido."
   end

   servermanager._updatecommands()

   local cmds, err = servermanager._getcommandall(handle.cmdid,handle.server)
   if err then
      return nil, err
   end
   for i, cmdh in pairs(cmds) do
     local command = string.format("qdel %s.%s", cmdh.pid, handle.server)
     servermanager.writeMsg("Cancelando comando ["..command.."]")
     os.execute(command)
   end
   servermanager._checkforerror(handle)

   return 1
end

------------------------------------------------------------------------
-- Libera todos os recursos relacionados ao processo especificado. Esta
-- função precisa ser chamada após o término do processo para que
-- eventuais recursos alocados a ele possam ser liberados. Após a
-- chamada desta função, o handle do processo não poderá mais ser
-- utilizado.  Em caso de erro, retorna nil e a mensagem de erro.
--
-- ok, err = releasecommandinfo(handle)
------------------------------------------------------------------------
releasecommandinfo = function (handle)
   return 1
end

------------------------------------------------------------------------
--
-- Especificação das funções privadas
--
------------------------------------------------------------------------
_checkforerror = function (handle)
  if not handle.errorfile then
     return
  end
  -- Erro se colocamos o path absoluto ao chamar o qsub.
  local errorfile = handle.errorfile
  local str
  local fd = io.open(errorfile, "r")
  if not fd then
     servermanager.writeError( "check: Erro ao abrir arquivo temporário:\n"..
                              "\t["..tostring(errorfile).."]\n"..
                              "\tpara aquisição de dados de execução\n" )
     handle.errorfile = nil
     return
  end
  str = fd:read("*a")
  fd:close()
  os.remove(errorfile)
  handle.errorfile=nil
  if str and string.len(str) > 0 then
    local errormsg = string.format(
      "Possível erro na execução do comando %s:\n%s",
      handle.cmdid, str)
    servermanager.writeError(errormsg)
  end
end

_getnode = function(server)
   server = server or "localhost"
   return servermanager._nodes[server]
end
 
_updatecommands = function( )
  local cmds = {}
  local fname = os.tmpname()
  local command = string.format(commands.jobsupdate..' > %s', fname)

--REVER
--servermanager.writeCmd("Coletando info com ["..command.."]")
  local result = os.execute(command)
  if result ~= 0 then
     local err = "Falha na coleta de dados de comandos (qstat)."
     servermanager.writeError(err.." Cod.:"..tostring(result))
     return
  end

  local fd = io.open(fname, "r")
  if not fd then
     local err = "Falha na leitura do arquivo temporário do qstat."
     servermanager.writeError(err.." Arq.:["..fname.."]")
     return
  end

  local str = fd:read("*a")
  fd:close()
  os.remove(fname)

  string.gsub(str, "Job Id: (%d+)%.(%S+)(.-)\n\n",
    function(pid, server, attrs)
 --   attrs = string.gsub(attrs, "%c", "")
      attrs = string.gsub(attrs, "  ", " ")
      local tb = {pid = pid}
      string.gsub(attrs, "%s*(%S+) = ([%S ]+)",
                  function (attr, val) tb[attr] = val 
    end)
      local cmdid = tb.Job_Name

      -- Transforma o tempo em segundos
      if tb["resources_used.cput"] then
        tb["resources_used.cput"] =
          servermanager._str2sec(tb["resources_used.cput"])
      end
 
      if tb["resources_used.walltime"] then
        tb["resources_used.walltime"] =
          servermanager._str2sec(tb["resources_used.walltime"])
      end
 
      -- Transforma para bytes
      if tb["resources_used.mem"] then
        tb["resources_used.mem"] =
          servermanager._getvalunit(tb["resources_used.mem"])
      end
 
      if tb["resources_used.vmem"] then
        tb["resources_used.vmem"] =
          servermanager._getvalunit(tb["resources_used.vmem"])
      end
 
      if tb.exec_host then
        tb.exec_host = string.gsub(tb.exec_host, "^(%w+).+$", "%1")
      end

      if not cmds[server] then
         cmds[server] = {}
      end
 
      local cmdh = cmds[server]
      if not cmdh[cmdid] then
        cmdh[cmdid] = {}
      end
      table.insert(cmdh[cmdid], tb)
    end)

  servermanager._cmds = cmds
end

_commandsdump = function( )
   for host,cmds in pairs(servermanager._cmds) do
      for cmdid, procs in pairs(cmds) do
        print("Atributos do processo "..cmdid)
        for i,attrs in pairs(procs) do
          for attr, val in pairs(attrs) do
            print(attr, val)
          end
        end
      end
      print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
   end
end

_getcommandall = function( cmdid, host)
   if servermanager._cmds[host] then
      local cmdh = servermanager._cmds[host][cmdid]
      if type(cmdh) == "table" then
        return cmdh
      end
   end

   return nil, "Comando não encontrado."
end

_getcommand = function( cmdid, host) -- EM ABERTO: PEGO O 1o PROCESSO!
   local cmdh = servermanager._getcommandall( cmdid, host)
   if type(cmdh) == "table" and type(cmdh[1]) == "table" then
     return cmdh[1]
   end

   return nil, "Comando não encontrado."
end

_updatenode = function( node)
  local fname = os.tmpname()
  local domain = node.domain or servermanager.domain or ""
  local command = string.format(commands.nodeinfo..' %s%s > %s',
                                node.name, domain, fname)

--REVER
--servermanager.writeMsg("Coletando info com ["..command.."]")
  os.execute( command )

  local fd = io.open(fname, "r")
  if not fd then
    servermanager.writeError("Falha na leitura do arquivo temporário.".." ["..fname.."]")
    return nil
  end

  local str = fd:read("*a")
  fd:close()
  os.remove(fname)
  return _parse_node_data(str)
end

_parse_node_data = function( str)
  local attr_table = {}
  for attr, val in string.gmatch(str, "(%S+) = ([^\n]+)") do
    attr_table[attr] = val
  end
  if attr_table.status then
    for attr, val in string.gmatch(attr_table.status, "(%w+)=([^,]+)") do
      -- Não sobreescreve um valor já definido
      attr_table[attr] = attr_table[attr] or val
    end
  end
  return attr_table
end

_add = function(nodename, node_data)
   local errcode = servermanager.ERROR_CODE
      local new_node = {}
      local STRING = type("")
      local NUMBER = type(0)

      local numcpus = 1
      local ram = errcode
      local swap = errcode
      local byteorder = servermanager.LITTLE_ENDIAN

      local t = servermanager._parse_node_data( node_data)

      -- NCPUS
      local _ncpus = t[fields.ncpus] and
              tonumber(string.gsub(t[fields.ncpus], "(%d+)", "%1"), 10)
      if _ncpus and type(_ncpus) == NUMBER and _ncpus > 0 then 
         numcpus = _ncpus
      else
         servermanager.writeError("Falha de aquisição de número de cpus!")
      end

      if servermanager._isonline(t.state) then
      -- MEMORY
      local _ram, _totmem = functions.getMemoryInBytes(t)
      if _ram and type(_ram) == NUMBER and _ram >= 0 then
         ram = _ram
         if _totmem and type(_totmem) == NUMBER and _totmem >= 0 then
            swap = _totmem - _ram
         end
      end
      if ram == errcode or swap == errcode then
         servermanager.writeError( "Falha de aquisição de memória!")
      end

      -- BYTE ORDER
      --
      -- XXX Não implementado.
      --
      local byteorder = t.byteorder
      if type(byteorder) ~= STRING or (byteorder ~= "LITTLE_ENDIAN" and
         byteorder ~= "BIG_ENDIAN") then
         byteorder = servermanager.LITTLE_ENDIAN
--       servermanager.writeError( "Falha de aquisição de byteorder!")
      else
         if byteorder == "LITTLE_ENDIAN" then
            byteorder = servermanager.LITTLE_ENDIAN
         else
            byteorder = servermanager.BIG_ENDIAN
         end
      end
else print("HOST "..nodename.." is down")
      end

      new_node.name = nodename
      new_node.num_processors = numcpus
      new_node.memory = { ram = ram, swap = swap }
      new_node.memory_ram_info_mb = ram
      new_node.memory_swap_info_mb = swap
      new_node.byteorder = byteorder
      new_node.last_update = 0

      servermanager._nodes[nodename] = new_node
end

------------------------------------------------------------------------
-- _isonline(state)
-- O parâmetro state indica o estado de um nó.
-- Caso state seja nil, será retornado false.
------------------------------------------------------------------------
_isonline = function(state)
     -- node-attribute values (state,ntype) 
     --   free
     --   offline
     --   down
     --   reserve
     --   job-exclusive
     --   job-sharing
     --   busy
     --   state-unknown
     --   time-shared
     --   cluster
     if not state or string.find(state,"offline") or
       string.find(state, "down") or
       string.find(state, "state-unknown") then
       return false
     end
     return true
end
------------------------------------------------------------------------
-- _update(server, maxload)
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor em questão.
--
-- Campos estáticos do nó:
-- numcpus
-- memory
--          { ram = xxx, swap = yyy }
-- byteorder
--          LITTLE_ENDIAN ou BIG_ENDIAN.
--
-- Campos dinâmicos do nó:
-- cpuload
-- memoryload
--          { ram = xxx, swap = yyy }
-- njobs
------------------------------------------------------------------------
_update = function(node, freq)
   local errcode = servermanager.ERROR_CODE
   local nodeinfo = servermanager._getnode(node)
   if freq and (servermanager.now() - nodeinfo.last_update) < freq then
      return nodeinfo
   end

   nodeinfo.last_update = servermanager.now()
   local NUMBER = type(0)
-----
   local t = servermanager._updatenode(nodeinfo)
   if not t then
      return nodeinfo
   end

   -- Load Average
   local _load = functions.getCpuLoad(t)
   _load = tonumber(_load)
   if type(_load) == NUMBER then 
      nodeinfo.cpuload = _load / nodeinfo.num_processors * 100
   end

   -- Memory Use
   local _mem = functions.getAvailableMemoryInBytes(t)
   if type(_mem) == NUMBER then
      local mload = (nodeinfo.memory.ram + nodeinfo.memory.swap) - _mem
      if mload <= nodeinfo.memory.ram then 
         nodeinfo.memoryload = {ram = mload, swap = 0}
      else
         local ramtot = nodeinfo.memory.ram
         nodeinfo.memoryload = {ram = ramtot, swap = mload - ramtot}
      end
   end

   -- Number of Jobs
   -- obtem só o número de jobs sem se preocupar em quais são
   local _, _njobs = string.gsub(t.jobs or "", "%d+/[^, ]+[, ]?[, ]?", "")
   nodeinfo.njobs = _njobs

   nodeinfo.isonline = servermanager._isonline(t.state)
   return nodeinfo
end

_getvalunit = function (str)
  local val = string.gsub(str, "(%d+)[KkMmGgTt]?b", "%1")
  val = tonumber(val)
  local unit = string.gsub(str, "%d+([KkMmGgTt]?)b", "%1")
  val = servermanager._memvaluetobytes(val, unit)

  return val
end

_memvaluetobytes = function (value,unit)
  unit = string.upper(unit)
  if unit == "T" then
    return value * (1024^4)
  elseif unit == "G" then
    return value * (1024^3)
  elseif unit == "M" then
    return value * (1024^2)
  elseif unit == "K" then
    return value * 1024
  else
    return value
  end
end

_str2sec = function (str) -- Retorna segundos
  -- str pode ser "SS", "MM:SS" ou "HH:MM:SS"
  local tot = 0
  string.gsub(str, "(%d+)", function (n)
    tot = tot * 60 + n
  end)

  return tot
end

writeMsg = function(str)
  local msg = "SGAD ["..os.date("%d/%m/%y - %H:%M:%S").."] - "..str
  print(msg)
  io.flush()
end
writeError = writeMsg
writeCmd = writeMsg

------------------------------------------------------------------------
servermanager = {
  -------------
  -- private --
  -------------
  NO_SERVER= "Servidor não existente", -- Colocar num arquivo de mensagens?
  _nodes   = {},
  _cmds    = {},
  _add     = _add,
  _commandsdump = _commandsdump,
  _getcommand = _getcommand,
  _getcommandall = _getcommandall,
  _getnode = _getnode,
  _getvalunit = _getvalunit,
  _memvaluetobytes = _memvaluetobytes,
  _parse_node_data = _parse_node_data,
  _str2sec = _str2sec,
  _update  = _update,
  _updatecommand = _updatecommand,
  _updatecommands = _updatecommands,
  _updatenode = _updatenode,
  _isonline = _isonline,
  _checkforerror = _checkforerror,

  -------------
  -- public --
  -------------
  now = now,
  sleep = sleep,
  writeMsg = writeMsg,
  writeError = writeError,
  writeCmd = writeCmd,

  -- Funções de controle do módulo:
  open = open,
  close = close,

  -- Funções de consulta à configuração de servidor:
  getnodes = getnodes,
  getnumcpus = getnumcpus,
  getmemory = getmemory,
  getbyteorder = getbyteorder,

  -- Funções de monitoração de servidor:
  getcpuload = getcpuload,
  getmemoryload = getmemoryload,
  getnumberofjobs = getnumberofjobs,
  getjobsinfo = getjobsinfo,

  -- Funções de execução, monitoração e controle de processos:
  executecommand = executecommand,
  retrievecommandhandle = retrievecommandhandle,       -- EXTRA
  getcommandpersistentdata = getcommandpersistentdata, -- EXTRA
  getcommandallinfo = getcommandallinfo,               -- EXTRA
  getcommandid = getcommandid,
  getcommandpid = getcommandpid,
  getcommandstatus = getcommandstatus,
  getcommandexechost = getcommandexechost,
  getcommandcpuload = getcommandcpuload,
  getcommandmemoryload = getcommandmemoryload,
  getcommandtimeusage = getcommandtimeusage,
  killcommand = killcommand,
  releasecommandinfo = releasecommandinfo,

  -- Constantes do módulo:
  RUNNING = 0,
  NOT_RESPONDING = 1,
  WAITING = 2,
  FINISHED = 3,

  LITTLE_ENDIAN = 0,
  BIG_ENDIAN = 1,

  ERROR_CODE = -1,
}

